wgengine/packet: refactor and expose UDP header marshaling (#408)
Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>main
parent
5e0ff494a5
commit
059b1d10bb
@ -0,0 +1,48 @@ |
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package packet |
||||
|
||||
import ( |
||||
"errors" |
||||
"math" |
||||
) |
||||
|
||||
const tcpHeaderLength = 20 |
||||
|
||||
// maxPacketLength is the largest length that all headers support.
|
||||
// IPv4 headers using uint16 for this forces an upper bound of 64KB.
|
||||
const maxPacketLength = math.MaxUint16 |
||||
|
||||
var ( |
||||
errSmallBuffer = errors.New("buffer too small") |
||||
errLargePacket = errors.New("packet too large") |
||||
) |
||||
|
||||
// Header is a packet header capable of marshaling itself into a byte buffer.
|
||||
type Header interface { |
||||
// Len returns the length of the header after marshaling.
|
||||
Len() int |
||||
// Marshal serializes the header into buf in wire format.
|
||||
// It clobbers the header region, which is the first h.Length() bytes of buf.
|
||||
// It explicitly initializes every byte of the header region,
|
||||
// so pre-zeroing it on reuse is not required. It does not allocate memory.
|
||||
// It fails if and only if len(buf) < Length().
|
||||
Marshal(buf []byte) error |
||||
// ToResponse transforms the header into one for a response packet.
|
||||
// For instance, this swaps the source and destination IPs.
|
||||
ToResponse() |
||||
} |
||||
|
||||
// Generate generates a new packet with the given header and payload.
|
||||
// Unlike Header.Marshal, this does allocate memory.
|
||||
func Generate(h Header, payload []byte) []byte { |
||||
hlen := h.Len() |
||||
buf := make([]byte, hlen+len(payload)) |
||||
|
||||
copy(buf[hlen:], payload) |
||||
h.Marshal(buf) |
||||
|
||||
return buf |
||||
} |
||||
@ -0,0 +1,78 @@ |
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package packet |
||||
|
||||
type ICMPType uint8 |
||||
|
||||
const ( |
||||
ICMPEchoReply ICMPType = 0x00 |
||||
ICMPEchoRequest ICMPType = 0x08 |
||||
ICMPUnreachable ICMPType = 0x03 |
||||
ICMPTimeExceeded ICMPType = 0x0b |
||||
) |
||||
|
||||
func (t ICMPType) String() string { |
||||
switch t { |
||||
case ICMPEchoReply: |
||||
return "EchoReply" |
||||
case ICMPEchoRequest: |
||||
return "EchoRequest" |
||||
case ICMPUnreachable: |
||||
return "Unreachable" |
||||
case ICMPTimeExceeded: |
||||
return "TimeExceeded" |
||||
default: |
||||
return "Unknown" |
||||
} |
||||
} |
||||
|
||||
type ICMPCode uint8 |
||||
|
||||
const ( |
||||
ICMPNoCode ICMPCode = 0 |
||||
) |
||||
|
||||
// ICMPHeader represents an ICMP packet header.
|
||||
type ICMPHeader struct { |
||||
IPHeader |
||||
Type ICMPType |
||||
Code ICMPCode |
||||
} |
||||
|
||||
const ( |
||||
icmpHeaderLength = 4 |
||||
// icmpTotalHeaderLength is the length of all headers in a ICMP packet.
|
||||
icmpAllHeadersLength = ipHeaderLength + icmpHeaderLength |
||||
) |
||||
|
||||
func (ICMPHeader) Len() int { |
||||
return icmpAllHeadersLength |
||||
} |
||||
|
||||
func (h ICMPHeader) Marshal(buf []byte) error { |
||||
if len(buf) < icmpAllHeadersLength { |
||||
return errSmallBuffer |
||||
} |
||||
if len(buf) > maxPacketLength { |
||||
return errLargePacket |
||||
} |
||||
// The caller does not need to set this.
|
||||
h.IPProto = ICMP |
||||
|
||||
buf[20] = uint8(h.Type) |
||||
buf[21] = uint8(h.Code) |
||||
|
||||
h.IPHeader.Marshal(buf) |
||||
|
||||
put16(buf[22:24], ipChecksum(buf)) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (h *ICMPHeader) ToResponse() { |
||||
h.Type = ICMPEchoReply |
||||
h.Code = ICMPNoCode |
||||
h.IPHeader.ToResponse() |
||||
} |
||||
@ -0,0 +1,127 @@ |
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package packet |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net" |
||||
) |
||||
|
||||
// IP is an IPv4 address.
|
||||
type IP uint32 |
||||
|
||||
// NewIP converts a standard library IP address into an IP.
|
||||
// It panics if b is not an IPv4 address.
|
||||
func NewIP(b net.IP) IP { |
||||
b4 := b.To4() |
||||
if b4 == nil { |
||||
panic(fmt.Sprintf("To4(%v) failed", b)) |
||||
} |
||||
return IP(get32(b4)) |
||||
} |
||||
|
||||
func (ip IP) String() string { |
||||
return fmt.Sprintf("%d.%d.%d.%d", byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip)) |
||||
} |
||||
|
||||
// IPProto is either a real IP protocol (ITCP, UDP, ...) or an special value like Unknown.
|
||||
// If it is a real IP protocol, its value corresponds to its IP protocol number.
|
||||
type IPProto uint8 |
||||
|
||||
const ( |
||||
// Unknown represents an unknown or unsupported protocol; it's deliberately the zero value.
|
||||
Unknown IPProto = 0x00 |
||||
ICMP IPProto = 0x01 |
||||
TCP IPProto = 0x06 |
||||
UDP IPProto = 0x11 |
||||
// IPv6 and Fragment are special values. They're not really IPProto values
|
||||
// so we're using the unassigned 0xFE and 0xFF values for them.
|
||||
// TODO(dmytro): special values should be taken out of here.
|
||||
IPv6 IPProto = 0xFE |
||||
Fragment IPProto = 0xFF |
||||
) |
||||
|
||||
func (p IPProto) String() string { |
||||
switch p { |
||||
case Fragment: |
||||
return "Frag" |
||||
case ICMP: |
||||
return "ICMP" |
||||
case UDP: |
||||
return "UDP" |
||||
case TCP: |
||||
return "TCP" |
||||
case IPv6: |
||||
return "IPv6" |
||||
default: |
||||
return "Unknown" |
||||
} |
||||
} |
||||
|
||||
// IPHeader represents an IP packet header.
|
||||
type IPHeader struct { |
||||
IPProto IPProto |
||||
IPID uint16 |
||||
SrcIP IP |
||||
DstIP IP |
||||
} |
||||
|
||||
const ipHeaderLength = 20 |
||||
|
||||
func (IPHeader) Len() int { |
||||
return ipHeaderLength |
||||
} |
||||
|
||||
func (h IPHeader) Marshal(buf []byte) error { |
||||
if len(buf) < ipHeaderLength { |
||||
return errSmallBuffer |
||||
} |
||||
if len(buf) > maxPacketLength { |
||||
return errLargePacket |
||||
} |
||||
|
||||
buf[0] = 0x40 | (ipHeaderLength >> 2) // IPv4
|
||||
buf[1] = 0x00 // DHCP, ECN
|
||||
put16(buf[2:4], uint16(len(buf))) |
||||
put16(buf[4:6], h.IPID) |
||||
put16(buf[6:8], 0) // flags, offset
|
||||
buf[8] = 64 // TTL
|
||||
buf[9] = uint8(h.IPProto) |
||||
put16(buf[10:12], 0) // blank IP header checksum
|
||||
put32(buf[12:16], uint32(h.SrcIP)) |
||||
put32(buf[16:20], uint32(h.DstIP)) |
||||
|
||||
put16(buf[10:12], ipChecksum(buf[0:20])) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// MarshalPseudo serializes the header into buf in pseudo format.
|
||||
// It clobbers the header region, which is the first h.Length() bytes of buf.
|
||||
// It explicitly initializes every byte of the header region,
|
||||
// so pre-zeroing it on reuse is not required. It does not allocate memory.
|
||||
func (h IPHeader) MarshalPseudo(buf []byte) error { |
||||
if len(buf) < ipHeaderLength { |
||||
return errSmallBuffer |
||||
} |
||||
if len(buf) > maxPacketLength { |
||||
return errLargePacket |
||||
} |
||||
|
||||
length := len(buf) - ipHeaderLength |
||||
put32(buf[8:12], uint32(h.SrcIP)) |
||||
put32(buf[12:16], uint32(h.DstIP)) |
||||
buf[16] = 0x0 |
||||
buf[17] = uint8(h.IPProto) |
||||
put16(buf[18:20], uint16(length)) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (h *IPHeader) 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 |
||||
} |
||||
@ -0,0 +1,53 @@ |
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package packet |
||||
|
||||
// UDPHeader represents an UDP packet header.
|
||||
type UDPHeader struct { |
||||
IPHeader |
||||
SrcPort uint16 |
||||
DstPort uint16 |
||||
} |
||||
|
||||
const ( |
||||
udpHeaderLength = 8 |
||||
// udpTotalHeaderLength is the length of all headers in a UDP packet.
|
||||
udpTotalHeaderLength = ipHeaderLength + udpHeaderLength |
||||
) |
||||
|
||||
func (UDPHeader) Len() int { |
||||
return udpTotalHeaderLength |
||||
} |
||||
|
||||
func (h UDPHeader) Marshal(buf []byte) error { |
||||
if len(buf) < udpTotalHeaderLength { |
||||
return errSmallBuffer |
||||
} |
||||
if len(buf) > maxPacketLength { |
||||
return errLargePacket |
||||
} |
||||
// The caller does not need to set this.
|
||||
h.IPProto = UDP |
||||
|
||||
length := len(buf) - h.IPHeader.Len() |
||||
put16(buf[20:22], h.SrcPort) |
||||
put16(buf[22:24], h.DstPort) |
||||
put16(buf[24:26], uint16(length)) |
||||
put16(buf[26:28], 0) // blank checksum
|
||||
|
||||
h.IPHeader.MarshalPseudo(buf) |
||||
|
||||
// UDP checksum with IP pseudo header.
|
||||
put16(buf[26:28], ipChecksum(buf[8:])) |
||||
|
||||
h.IPHeader.Marshal(buf) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (h *UDPHeader) ToResponse() { |
||||
h.SrcPort, h.DstPort = h.DstPort, h.SrcPort |
||||
h.IPHeader.ToResponse() |
||||
} |
||||
Loading…
Reference in new issue