diff --git a/net/packet/icmp4.go b/net/packet/icmp4.go index a48ef584f..8a1568114 100644 --- a/net/packet/icmp4.go +++ b/net/packet/icmp4.go @@ -6,6 +6,13 @@ package packet import "encoding/binary" +// icmp4HeaderLength is the size of the ICMPv4 packet header, not +// including the outer IP layer or the variable "response data" +// trailer. +const icmp4HeaderLength = 4 + +// ICMP4Type is an ICMPv4 type, as specified in +// https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml type ICMP4Type uint8 const ( @@ -30,31 +37,29 @@ func (t ICMP4Type) String() string { } } +// ICMP4Code is an ICMPv4 code, as specified in +// https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml type ICMP4Code uint8 const ( ICMP4NoCode ICMP4Code = 0 ) -// ICMP4Header represents an ICMPv4 packet header. +// ICMP4Header is an IPv4+ICMPv4 header. type ICMP4Header struct { IP4Header Type ICMP4Type Code ICMP4Code } -const ( - icmp4HeaderLength = 4 - // icmp4AllHeadersLength is the length of all headers in a ICMPv4 packet. - icmp4AllHeadersLength = ip4HeaderLength + icmp4HeaderLength -) - -func (ICMP4Header) Len() int { - return icmp4AllHeadersLength +// Len implements Header. +func (h ICMP4Header) Len() int { + return h.IP4Header.Len() + icmp4HeaderLength } +// Marshal implements Header. func (h ICMP4Header) Marshal(buf []byte) error { - if len(buf) < icmp4AllHeadersLength { + if len(buf) < h.Len() { return errSmallBuffer } if len(buf) > maxPacketLength { @@ -68,11 +73,14 @@ func (h ICMP4Header) Marshal(buf []byte) error { h.IP4Header.Marshal(buf) - binary.BigEndian.PutUint16(buf[22:24], ipChecksum(buf)) + binary.BigEndian.PutUint16(buf[22:24], ip4Checksum(buf)) return nil } +// ToResponse implements Header. TODO: it doesn't implement it +// correctly, instead it statically generates an ICMP Echo Reply +// packet. func (h *ICMP4Header) ToResponse() { // TODO: this doesn't implement ToResponse correctly, as it // assumes the ICMP request type. diff --git a/net/packet/icmp6.go b/net/packet/icmp6.go index 7759d78e9..0b48059e5 100644 --- a/net/packet/icmp6.go +++ b/net/packet/icmp6.go @@ -4,6 +4,13 @@ package packet +// icmp6HeaderLength is the size of the ICMPv6 packet header, not +// including the outer IP layer or the variable "response data" +// trailer. +const icmp6HeaderLength = 4 + +// ICMP6Type is an ICMPv6 type, as specified in +// https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml type ICMP6Type uint8 const ( @@ -28,10 +35,10 @@ func (t ICMP6Type) String() string { } } +// ICMP6Code is an ICMPv6 code, as specified in +// https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml type ICMP6Code uint8 const ( ICMP6NoCode ICMP6Code = 0 ) - -const icmp6HeaderLength = 4 diff --git a/net/packet/ip4.go b/net/packet/ip4.go index e71674b84..2731bed99 100644 --- a/net/packet/ip4.go +++ b/net/packet/ip4.go @@ -14,13 +14,13 @@ import ( // IP4 is an IPv4 address. type IP4 uint32 -// IPFromNetaddr converts a netaddr.IP to an IP. Panics if !ip.Is4. +// IPFromNetaddr converts a netaddr.IP to an IP4. Panics if !ip.Is4. func IP4FromNetaddr(ip netaddr.IP) IP4 { ipbytes := ip.As4() return IP4(binary.BigEndian.Uint32(ipbytes[:])) } -// Netaddr converts an IP to a netaddr.IP. +// Netaddr converts ip to a netaddr.IP. func (ip IP4) Netaddr() netaddr.IP { return netaddr.IPv4(byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip)) } @@ -29,15 +29,21 @@ func (ip IP4) String() string { return fmt.Sprintf("%d.%d.%d.%d", byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip)) } +// IsMulticast returns whether ip is a multicast address. func (ip IP4) IsMulticast() bool { return byte(ip>>24)&0xf0 == 0xe0 } +// IsLinkLocalUnicast returns whether ip is a link-local unicast +// address. func (ip IP4) IsLinkLocalUnicast() bool { return byte(ip>>24) == 169 && byte(ip>>16) == 254 } -// IPHeader represents an IP packet header. +// ip4HeaderLength is the length of an IPv4 header with no IP options. +const ip4HeaderLength = 20 + +// IP4Header represents an IPv4 packet header. type IP4Header struct { IPProto IPProto IPID uint16 @@ -45,59 +51,87 @@ type IP4Header struct { DstIP IP4 } -const ip4HeaderLength = 20 - -func (IP4Header) Len() int { +// Len implements Header. +func (h IP4Header) Len() int { return ip4HeaderLength } +// Marshal implements Header. func (h IP4Header) Marshal(buf []byte) error { - if len(buf) < ip4HeaderLength { + if len(buf) < h.Len() { return errSmallBuffer } if len(buf) > maxPacketLength { return errLargePacket } - buf[0] = 0x40 | (ip4HeaderLength >> 2) // IPv4 - buf[1] = 0x00 // DHCP, ECN - binary.BigEndian.PutUint16(buf[2:4], uint16(len(buf))) - binary.BigEndian.PutUint16(buf[4:6], h.IPID) - binary.BigEndian.PutUint16(buf[6:8], 0) // flags, offset - buf[8] = 64 // TTL - buf[9] = uint8(h.IPProto) - binary.BigEndian.PutUint16(buf[10:12], 0) // blank IP header checksum - binary.BigEndian.PutUint32(buf[12:16], uint32(h.SrcIP)) - binary.BigEndian.PutUint32(buf[16:20], uint32(h.DstIP)) + buf[0] = 0x40 | (byte(h.Len() >> 2)) // IPv4 + IHL + buf[1] = 0x00 // DSCP + ECN + binary.BigEndian.PutUint16(buf[2:4], uint16(len(buf))) // Total length + binary.BigEndian.PutUint16(buf[4:6], h.IPID) // ID + binary.BigEndian.PutUint16(buf[6:8], 0) // Flags + fragment offset + buf[8] = 64 // TTL + buf[9] = uint8(h.IPProto) // Inner protocol + // Blank checksum. This is necessary even though we overwrite + // it later, because the checksum computation runs over these + // bytes and expects them to be zero. + binary.BigEndian.PutUint16(buf[10:12], 0) + binary.BigEndian.PutUint32(buf[12:16], uint32(h.SrcIP)) // Src + binary.BigEndian.PutUint32(buf[16:20], uint32(h.DstIP)) // Dst - binary.BigEndian.PutUint16(buf[10:12], ipChecksum(buf[0:20])) + binary.BigEndian.PutUint16(buf[10:12], ip4Checksum(buf[0:20])) // Checksum return nil } -// MarshalPseudo serializes the header into buf in the "pseudo-header" -// form required when calculating UDP checksums. Overwrites the first -// h.Length() bytes of buf. -func (h IP4Header) MarshalPseudo(buf []byte) error { - if len(buf) < ip4HeaderLength { - return errSmallBuffer - } - if len(buf) > maxPacketLength { - return errLargePacket - } - - length := len(buf) - ip4HeaderLength - binary.BigEndian.PutUint32(buf[8:12], uint32(h.SrcIP)) - binary.BigEndian.PutUint32(buf[12:16], uint32(h.DstIP)) - buf[16] = 0x0 - buf[17] = uint8(h.IPProto) - binary.BigEndian.PutUint16(buf[18:20], uint16(length)) - return nil -} - // ToResponse implements Header. func (h *IP4Header) ToResponse() { h.SrcIP, h.DstIP = h.DstIP, h.SrcIP // Flip the bits in the IPID. If incoming IPIDs are distinct, so are these. h.IPID = ^h.IPID } + +// ip4Checksum computes an IPv4 checksum, as specified in +// https://tools.ietf.org/html/rfc1071 +func ip4Checksum(b []byte) uint16 { + var ac uint32 + i := 0 + n := len(b) + for n >= 2 { + ac += uint32(binary.BigEndian.Uint16(b[i : i+2])) + n -= 2 + i += 2 + } + if n == 1 { + ac += uint32(b[i]) << 8 + } + for (ac >> 16) > 0 { + ac = (ac >> 16) + (ac & 0xffff) + } + return uint16(^ac) +} + +// ip4PseudoHeaderOffset is the number of bytes by which the IPv4 UDP +// pseudo-header is smaller than the real IPv4 header. +const ip4PseudoHeaderOffset = 8 + +// marshalPseudo serializes h into buf in the "pseudo-header" form +// required when calculating UDP checksums. The pseudo-header starts +// at buf[ip4PseudoHeaderOffset] so as to abut the following UDP +// header, while leaving enough space in buf for a full IPv4 header. +func (h IP4Header) marshalPseudo(buf []byte) error { + if len(buf) < h.Len() { + return errSmallBuffer + } + if len(buf) > maxPacketLength { + return errLargePacket + } + + length := len(buf) - h.Len() + binary.BigEndian.PutUint32(buf[8:12], uint32(h.SrcIP)) + binary.BigEndian.PutUint32(buf[12:16], uint32(h.DstIP)) + buf[16] = 0x0 + buf[17] = uint8(h.IPProto) + binary.BigEndian.PutUint16(buf[18:20], uint16(length)) + return nil +} diff --git a/net/packet/ip6.go b/net/packet/ip6.go index 992da098b..cdff94093 100644 --- a/net/packet/ip6.go +++ b/net/packet/ip6.go @@ -10,8 +10,10 @@ import ( "inet.af/netaddr" ) +// IP6 is an IPv6 address. type IP6 [16]byte +// IP6FromNetaddr converts a netaddr.IP to an IP6. Panics if !ip.Is6. func IP6FromNetaddr(ip netaddr.IP) IP6 { if !ip.Is6() { panic(fmt.Sprintf("IP6FromNetaddr called with non-v6 addr %q", ip)) @@ -19,6 +21,7 @@ func IP6FromNetaddr(ip netaddr.IP) IP6 { return IP6(ip.As16()) } +// Netaddr converts ip to a netaddr.IP. func (ip IP6) Netaddr() netaddr.IP { return netaddr.IPFrom16(ip) } @@ -27,4 +30,5 @@ func (ip IP6) String() string { return ip.Netaddr().String() } +// ip6HeaderLength is the length of an IPv6 header with no IP options. const ip6HeaderLength = 40 diff --git a/net/packet/packet.go b/net/packet/packet.go index 726135bcc..0aa5b7351 100644 --- a/net/packet/packet.go +++ b/net/packet/packet.go @@ -22,8 +22,6 @@ const ( ) // Parsed is a minimal decoding of a packet suitable for use in filters. -// -// In general, it only supports IPv4. The IPv6 parsing is very minimal. type Parsed struct { // b is the byte buffer that this decodes. b []byte @@ -100,25 +98,6 @@ func writeIP6Port(sb *strbuilder.Builder, ip IP6, port uint16) { sb.WriteUint(uint64(port)) } -// based on https://tools.ietf.org/html/rfc1071 -func ipChecksum(b []byte) uint16 { - var ac uint32 - i := 0 - n := len(b) - for n >= 2 { - ac += uint32(binary.BigEndian.Uint16(b[i : i+2])) - n -= 2 - i += 2 - } - if n == 1 { - ac += uint32(b[i]) << 8 - } - for (ac >> 16) > 0 { - ac = (ac >> 16) + (ac & 0xffff) - } - return uint16(^ac) -} - // Decode extracts data from the packet in b into q. // It performs extremely simple packet decoding for basic IPv4 packet types. // It extracts only the subprotocol id, IP addresses, and (if any) ports, diff --git a/net/packet/udp4.go b/net/packet/udp4.go index a633bf072..82aa30179 100644 --- a/net/packet/udp4.go +++ b/net/packet/udp4.go @@ -6,23 +6,23 @@ package packet import "encoding/binary" -// UDPHeader represents an UDP packet header. +// udpHeaderLength is the size of the UDP packet header, not including +// the outer IP header. +const udpHeaderLength = 8 + +// UDP4Header is an IPv4+UDP header. type UDP4Header struct { IP4Header SrcPort uint16 DstPort uint16 } -const ( - // udpHeaderLength is the size of the UDP packet header, not - // including the outer IP header. - udpHeaderLength = 8 -) - -func (UDP4Header) Len() int { - return ip4HeaderLength + udpHeaderLength +// Len implements Header. +func (h UDP4Header) Len() int { + return h.IP4Header.Len() + udpHeaderLength } +// Marshal implements Header. func (h UDP4Header) Marshal(buf []byte) error { if len(buf) < h.Len() { return errSmallBuffer @@ -40,14 +40,15 @@ func (h UDP4Header) Marshal(buf []byte) error { binary.BigEndian.PutUint16(buf[26:28], 0) // blank checksum // UDP checksum with IP pseudo header. - h.IP4Header.MarshalPseudo(buf) - binary.BigEndian.PutUint16(buf[26:28], ipChecksum(buf[8:])) + h.IP4Header.marshalPseudo(buf) + binary.BigEndian.PutUint16(buf[26:28], ip4Checksum(buf[ip4PseudoHeaderOffset:])) h.IP4Header.Marshal(buf) return nil } +// ToResponse implements Header. func (h *UDP4Header) ToResponse() { h.SrcPort, h.DstPort = h.DstPort, h.SrcPort h.IP4Header.ToResponse()