mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-13 06:07:34 +00:00
net/packet: rename from wgengine/packet.
Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
@@ -13,9 +13,9 @@ import (
|
||||
|
||||
"github.com/golang/groupcache/lru"
|
||||
"golang.org/x/time/rate"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/wgengine/packet"
|
||||
)
|
||||
|
||||
type filterState struct {
|
||||
|
@@ -12,8 +12,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/wgengine/packet"
|
||||
)
|
||||
|
||||
var Unknown = packet.Unknown
|
||||
|
@@ -10,7 +10,7 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"tailscale.com/wgengine/packet"
|
||||
"tailscale.com/net/packet"
|
||||
)
|
||||
|
||||
func NewIP(ip net.IP) packet.IP4 {
|
||||
|
@@ -1,16 +0,0 @@
|
||||
// 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 contains packet parsing and marshaling utilities.
|
||||
//
|
||||
// ParsedPacket provides allocation-free minimal packet header
|
||||
// decoding, for use in packet filtering. The other types in the
|
||||
// package are for constructing and marshaling packets into []bytes.
|
||||
//
|
||||
// To support allocation-free parsing, this package defines IPv4 and
|
||||
// IPv6 address types. You should prefer to use netaddr's types,
|
||||
// except where you absolutely need allocation-free IP handling
|
||||
// (i.e. in the tunnel datapath) and are willing to implement all
|
||||
// codepaths and data structures twice, once per IP family.
|
||||
package packet
|
@@ -1,48 +0,0 @@
|
||||
// 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
|
||||
}
|
@@ -1,80 +0,0 @@
|
||||
// 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 ICMP4Type uint8
|
||||
|
||||
const (
|
||||
ICMP4EchoReply ICMP4Type = 0x00
|
||||
ICMP4EchoRequest ICMP4Type = 0x08
|
||||
ICMP4Unreachable ICMP4Type = 0x03
|
||||
ICMP4TimeExceeded ICMP4Type = 0x0b
|
||||
)
|
||||
|
||||
func (t ICMP4Type) String() string {
|
||||
switch t {
|
||||
case ICMP4EchoReply:
|
||||
return "EchoReply"
|
||||
case ICMP4EchoRequest:
|
||||
return "EchoRequest"
|
||||
case ICMP4Unreachable:
|
||||
return "Unreachable"
|
||||
case ICMP4TimeExceeded:
|
||||
return "TimeExceeded"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type ICMP4Code uint8
|
||||
|
||||
const (
|
||||
ICMP4NoCode ICMP4Code = 0
|
||||
)
|
||||
|
||||
// ICMPHeader represents an ICMP packet header.
|
||||
type ICMP4Header struct {
|
||||
IP4Header
|
||||
Type ICMP4Type
|
||||
Code ICMP4Code
|
||||
}
|
||||
|
||||
const (
|
||||
icmpHeaderLength = 4
|
||||
// icmpTotalHeaderLength is the length of all headers in a ICMP packet.
|
||||
icmpAllHeadersLength = ipHeaderLength + icmpHeaderLength
|
||||
)
|
||||
|
||||
func (ICMP4Header) Len() int {
|
||||
return icmpAllHeadersLength
|
||||
}
|
||||
|
||||
func (h ICMP4Header) 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.IP4Header.Marshal(buf)
|
||||
|
||||
put16(buf[22:24], ipChecksum(buf))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *ICMP4Header) ToResponse() {
|
||||
// TODO: this doesn't implement ToResponse correctly, as it
|
||||
// assumes the ICMP request type.
|
||||
h.Type = ICMP4EchoReply
|
||||
h.Code = ICMP4NoCode
|
||||
h.IP4Header.ToResponse()
|
||||
}
|
@@ -1,148 +0,0 @@
|
||||
// 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"
|
||||
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
// IP4 is an IPv4 address.
|
||||
type IP4 uint32
|
||||
|
||||
// NewIP converts a standard library IP address into an IP.
|
||||
// It panics if b is not an IPv4 address.
|
||||
func NewIP4(b net.IP) IP4 {
|
||||
b4 := b.To4()
|
||||
if b4 == nil {
|
||||
panic(fmt.Sprintf("To4(%v) failed", b))
|
||||
}
|
||||
return IP4(get32(b4))
|
||||
}
|
||||
|
||||
// IPFromNetaddr converts a netaddr.IP to an IP.
|
||||
func IP4FromNetaddr(ip netaddr.IP) IP4 {
|
||||
ipbytes := ip.As4()
|
||||
return IP4(get32(ipbytes[:]))
|
||||
}
|
||||
|
||||
// Netaddr converts an 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))
|
||||
}
|
||||
|
||||
func (ip IP4) String() string {
|
||||
return fmt.Sprintf("%d.%d.%d.%d", byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip))
|
||||
}
|
||||
|
||||
func (ip IP4) IsMulticast() bool {
|
||||
return byte(ip>>24)&0xf0 == 0xe0
|
||||
}
|
||||
|
||||
func (ip IP4) IsLinkLocalUnicast() bool {
|
||||
return byte(ip>>24) == 169 && byte(ip>>16) == 254
|
||||
}
|
||||
|
||||
// IP4Proto is either a real IP protocol (TCP, UDP, ...) or an special
|
||||
// value like Unknown. If it is a real IP protocol, its value
|
||||
// corresponds to its IP protocol number.
|
||||
type IP4Proto uint8
|
||||
|
||||
const (
|
||||
// Unknown represents an unknown or unsupported protocol; it's deliberately the zero value.
|
||||
Unknown IP4Proto = 0x00
|
||||
ICMP IP4Proto = 0x01
|
||||
IGMP IP4Proto = 0x02
|
||||
ICMPv6 IP4Proto = 0x3a
|
||||
TCP IP4Proto = 0x06
|
||||
UDP IP4Proto = 0x11
|
||||
// Fragment is a special value. It's not really an IPProto value
|
||||
// so we're using the unassigned 0xFF value.
|
||||
// TODO(dmytro): special values should be taken out of here.
|
||||
Fragment IP4Proto = 0xFF
|
||||
)
|
||||
|
||||
func (p IP4Proto) String() string {
|
||||
switch p {
|
||||
case Fragment:
|
||||
return "Frag"
|
||||
case ICMP:
|
||||
return "ICMP"
|
||||
case UDP:
|
||||
return "UDP"
|
||||
case TCP:
|
||||
return "TCP"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// IPHeader represents an IP packet header.
|
||||
type IP4Header struct {
|
||||
IPProto IP4Proto
|
||||
IPID uint16
|
||||
SrcIP IP4
|
||||
DstIP IP4
|
||||
}
|
||||
|
||||
const ipHeaderLength = 20
|
||||
|
||||
func (IP4Header) Len() int {
|
||||
return ipHeaderLength
|
||||
}
|
||||
|
||||
func (h IP4Header) 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 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) < 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
@@ -1,343 +0,0 @@
|
||||
// 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 (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"tailscale.com/types/strbuilder"
|
||||
)
|
||||
|
||||
// RFC1858: prevent overlapping fragment attacks.
|
||||
const minFrag = 60 + 20 // max IPv4 header + basic TCP header
|
||||
|
||||
const (
|
||||
TCPSyn = 0x02
|
||||
TCPAck = 0x10
|
||||
TCPSynAck = TCPSyn | TCPAck
|
||||
)
|
||||
|
||||
var (
|
||||
get16 = binary.BigEndian.Uint16
|
||||
get32 = binary.BigEndian.Uint32
|
||||
|
||||
put16 = binary.BigEndian.PutUint16
|
||||
put32 = binary.BigEndian.PutUint32
|
||||
)
|
||||
|
||||
// ParsedPacket 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 ParsedPacket struct {
|
||||
// b is the byte buffer that this decodes.
|
||||
b []byte
|
||||
// subofs is the offset of IP subprotocol.
|
||||
subofs int
|
||||
// dataofs is the offset of IP subprotocol payload.
|
||||
dataofs int
|
||||
// length is the total length of the packet.
|
||||
// This is not the same as len(b) because b can have trailing zeros.
|
||||
length int
|
||||
|
||||
IPVersion uint8 // 4, 6, or 0
|
||||
IPProto IP4Proto // IP subprotocol (UDP, TCP, etc); the NextHeader field for IPv6
|
||||
SrcIP IP4 // IP source address (not used for IPv6)
|
||||
DstIP IP4 // IP destination address (not used for IPv6)
|
||||
SrcPort uint16 // TCP/UDP source port
|
||||
DstPort uint16 // TCP/UDP destination port
|
||||
TCPFlags uint8 // TCP flags (SYN, ACK, etc)
|
||||
}
|
||||
|
||||
// NextHeader
|
||||
type NextHeader uint8
|
||||
|
||||
func (p *ParsedPacket) String() string {
|
||||
if p.IPVersion == 6 {
|
||||
return fmt.Sprintf("IPv6{Proto=%d}", p.IPProto)
|
||||
}
|
||||
switch p.IPProto {
|
||||
case Unknown:
|
||||
return "Unknown{???}"
|
||||
}
|
||||
sb := strbuilder.Get()
|
||||
sb.WriteString(p.IPProto.String())
|
||||
sb.WriteByte('{')
|
||||
writeIPPort(sb, p.SrcIP, p.SrcPort)
|
||||
sb.WriteString(" > ")
|
||||
writeIPPort(sb, p.DstIP, p.DstPort)
|
||||
sb.WriteByte('}')
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func writeIPPort(sb *strbuilder.Builder, ip IP4, port uint16) {
|
||||
sb.WriteUint(uint64(byte(ip >> 24)))
|
||||
sb.WriteByte('.')
|
||||
sb.WriteUint(uint64(byte(ip >> 16)))
|
||||
sb.WriteByte('.')
|
||||
sb.WriteUint(uint64(byte(ip >> 8)))
|
||||
sb.WriteByte('.')
|
||||
sb.WriteUint(uint64(byte(ip)))
|
||||
sb.WriteByte(':')
|
||||
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(get16(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,
|
||||
// and shouldn't need any memory allocation.
|
||||
func (q *ParsedPacket) Decode(b []byte) {
|
||||
q.b = b
|
||||
|
||||
if len(b) < ipHeaderLength {
|
||||
q.IPVersion = 0
|
||||
q.IPProto = Unknown
|
||||
return
|
||||
}
|
||||
|
||||
// Check that it's IPv4.
|
||||
// TODO(apenwarr): consider IPv6 support
|
||||
q.IPVersion = (b[0] & 0xF0) >> 4
|
||||
switch q.IPVersion {
|
||||
case 4:
|
||||
q.IPProto = IP4Proto(b[9])
|
||||
case 6:
|
||||
q.IPProto = IP4Proto(b[6]) // "Next Header" field
|
||||
return
|
||||
default:
|
||||
q.IPVersion = 0
|
||||
q.IPProto = Unknown
|
||||
return
|
||||
}
|
||||
|
||||
q.length = int(get16(b[2:4]))
|
||||
if len(b) < q.length {
|
||||
// Packet was cut off before full IPv4 length.
|
||||
q.IPProto = Unknown
|
||||
return
|
||||
}
|
||||
|
||||
// If it's valid IPv4, then the IP addresses are valid
|
||||
q.SrcIP = IP4(get32(b[12:16]))
|
||||
q.DstIP = IP4(get32(b[16:20]))
|
||||
|
||||
q.subofs = int((b[0] & 0x0F) << 2)
|
||||
sub := b[q.subofs:]
|
||||
|
||||
// We don't care much about IP fragmentation, except insofar as it's
|
||||
// used for firewall bypass attacks. The trick is make the first
|
||||
// fragment of a TCP or UDP packet so short that it doesn't fit
|
||||
// the TCP or UDP header, so we can't read the port, in hope that
|
||||
// it'll sneak past. Then subsequent fragments fill it in, but we're
|
||||
// missing the first part of the header, so we can't read that either.
|
||||
//
|
||||
// A "perfectly correct" implementation would have to reassemble
|
||||
// fragments before deciding what to do. But the truth is there's
|
||||
// zero reason to send such a short first fragment, so we can treat
|
||||
// it as Unknown. We can also treat any subsequent fragment that starts
|
||||
// at such a low offset as Unknown.
|
||||
fragFlags := get16(b[6:8])
|
||||
moreFrags := (fragFlags & 0x20) != 0
|
||||
fragOfs := fragFlags & 0x1FFF
|
||||
if fragOfs == 0 {
|
||||
// This is the first fragment
|
||||
if moreFrags && len(sub) < minFrag {
|
||||
// Suspiciously short first fragment, dump it.
|
||||
q.IPProto = Unknown
|
||||
return
|
||||
}
|
||||
// otherwise, this is either non-fragmented (the usual case)
|
||||
// or a big enough initial fragment that we can read the
|
||||
// whole subprotocol header.
|
||||
switch q.IPProto {
|
||||
case ICMP:
|
||||
if len(sub) < icmpHeaderLength {
|
||||
q.IPProto = Unknown
|
||||
return
|
||||
}
|
||||
q.SrcPort = 0
|
||||
q.DstPort = 0
|
||||
q.dataofs = q.subofs + icmpHeaderLength
|
||||
return
|
||||
case TCP:
|
||||
if len(sub) < tcpHeaderLength {
|
||||
q.IPProto = Unknown
|
||||
return
|
||||
}
|
||||
q.SrcPort = get16(sub[0:2])
|
||||
q.DstPort = get16(sub[2:4])
|
||||
q.TCPFlags = sub[13] & 0x3F
|
||||
headerLength := (sub[12] & 0xF0) >> 2
|
||||
q.dataofs = q.subofs + int(headerLength)
|
||||
return
|
||||
case UDP:
|
||||
if len(sub) < udpHeaderLength {
|
||||
q.IPProto = Unknown
|
||||
return
|
||||
}
|
||||
q.SrcPort = get16(sub[0:2])
|
||||
q.DstPort = get16(sub[2:4])
|
||||
q.dataofs = q.subofs + udpHeaderLength
|
||||
return
|
||||
default:
|
||||
q.IPProto = Unknown
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// This is a fragment other than the first one.
|
||||
if fragOfs < minFrag {
|
||||
// First frag was suspiciously short, so we can't
|
||||
// trust the followup either.
|
||||
q.IPProto = Unknown
|
||||
return
|
||||
}
|
||||
// otherwise, we have to permit the fragment to slide through.
|
||||
// Second and later fragments don't have sub-headers.
|
||||
// Ideally, we would drop fragments that we can't identify,
|
||||
// but that would require statefulness. Anyway, receivers'
|
||||
// kernels know to drop fragments where the initial fragment
|
||||
// doesn't arrive.
|
||||
q.IPProto = Fragment
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (q *ParsedPacket) IPHeader() IP4Header {
|
||||
ipid := get16(q.b[4:6])
|
||||
return IP4Header{
|
||||
IPID: ipid,
|
||||
IPProto: q.IPProto,
|
||||
SrcIP: q.SrcIP,
|
||||
DstIP: q.DstIP,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *ParsedPacket) ICMPHeader() ICMP4Header {
|
||||
return ICMP4Header{
|
||||
IP4Header: q.IPHeader(),
|
||||
Type: ICMP4Type(q.b[q.subofs+0]),
|
||||
Code: ICMP4Code(q.b[q.subofs+1]),
|
||||
}
|
||||
}
|
||||
|
||||
func (q *ParsedPacket) UDPHeader() UDP4Header {
|
||||
return UDP4Header{
|
||||
IP4Header: q.IPHeader(),
|
||||
SrcPort: q.SrcPort,
|
||||
DstPort: q.DstPort,
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer returns the entire packet buffer.
|
||||
// This is a read-only view; that is, q retains the ownership of the buffer.
|
||||
func (q *ParsedPacket) Buffer() []byte {
|
||||
return q.b
|
||||
}
|
||||
|
||||
// Sub returns the IP subprotocol section.
|
||||
// This is a read-only view; that is, q retains the ownership of the buffer.
|
||||
func (q *ParsedPacket) Sub(begin, n int) []byte {
|
||||
return q.b[q.subofs+begin : q.subofs+begin+n]
|
||||
}
|
||||
|
||||
// Payload returns the payload of the IP subprotocol section.
|
||||
// This is a read-only view; that is, q retains the ownership of the buffer.
|
||||
func (q *ParsedPacket) Payload() []byte {
|
||||
return q.b[q.dataofs:q.length]
|
||||
}
|
||||
|
||||
// Trim trims the buffer to its IPv4 length.
|
||||
// Sometimes packets arrive from an interface with extra bytes on the end.
|
||||
// This removes them.
|
||||
func (q *ParsedPacket) Trim() []byte {
|
||||
return q.b[:q.length]
|
||||
}
|
||||
|
||||
// IsTCPSyn reports whether q is a TCP SYN packet
|
||||
// (i.e. the first packet in a new connection).
|
||||
func (q *ParsedPacket) IsTCPSyn() bool {
|
||||
return (q.TCPFlags & TCPSynAck) == TCPSyn
|
||||
}
|
||||
|
||||
// IsError reports whether q is an IPv4 ICMP "Error" packet.
|
||||
func (q *ParsedPacket) IsError() bool {
|
||||
if q.IPProto == ICMP && len(q.b) >= q.subofs+8 {
|
||||
switch ICMP4Type(q.b[q.subofs]) {
|
||||
case ICMP4Unreachable, ICMP4TimeExceeded:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsEchoRequest reports whether q is an IPv4 ICMP Echo Request.
|
||||
func (q *ParsedPacket) IsEchoRequest() bool {
|
||||
if q.IPProto == ICMP && len(q.b) >= q.subofs+8 {
|
||||
return ICMP4Type(q.b[q.subofs]) == ICMP4EchoRequest &&
|
||||
ICMP4Code(q.b[q.subofs+1]) == ICMP4NoCode
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsEchoRequest reports whether q is an IPv4 ICMP Echo Response.
|
||||
func (q *ParsedPacket) IsEchoResponse() bool {
|
||||
if q.IPProto == ICMP && len(q.b) >= q.subofs+8 {
|
||||
return ICMP4Type(q.b[q.subofs]) == ICMP4EchoReply &&
|
||||
ICMP4Code(q.b[q.subofs+1]) == ICMP4NoCode
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func Hexdump(b []byte) string {
|
||||
out := new(strings.Builder)
|
||||
for i := 0; i < len(b); i += 16 {
|
||||
if i > 0 {
|
||||
fmt.Fprintf(out, "\n")
|
||||
}
|
||||
fmt.Fprintf(out, " %04x ", i)
|
||||
j := 0
|
||||
for ; j < 16 && i+j < len(b); j++ {
|
||||
if j == 8 {
|
||||
fmt.Fprintf(out, " ")
|
||||
}
|
||||
fmt.Fprintf(out, "%02x ", b[i+j])
|
||||
}
|
||||
for ; j < 16; j++ {
|
||||
if j == 8 {
|
||||
fmt.Fprintf(out, " ")
|
||||
}
|
||||
fmt.Fprintf(out, " ")
|
||||
}
|
||||
fmt.Fprintf(out, " ")
|
||||
for j = 0; j < 16 && i+j < len(b); j++ {
|
||||
if b[i+j] >= 32 && b[i+j] < 128 {
|
||||
fmt.Fprintf(out, "%c", b[i+j])
|
||||
} else {
|
||||
fmt.Fprintf(out, ".")
|
||||
}
|
||||
}
|
||||
}
|
||||
return out.String()
|
||||
}
|
@@ -1,349 +0,0 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIP4String(t *testing.T) {
|
||||
const str = "1.2.3.4"
|
||||
ip := NewIP4(net.ParseIP(str))
|
||||
|
||||
var got string
|
||||
allocs := testing.AllocsPerRun(1000, func() {
|
||||
got = ip.String()
|
||||
})
|
||||
|
||||
if got != str {
|
||||
t.Errorf("got %q; want %q", got, str)
|
||||
}
|
||||
if allocs != 1 {
|
||||
t.Errorf("allocs = %v; want 1", allocs)
|
||||
}
|
||||
}
|
||||
|
||||
var icmpRequestBuffer = []byte{
|
||||
// IP header up to checksum
|
||||
0x45, 0x00, 0x00, 0x27, 0xde, 0xad, 0x00, 0x00, 0x40, 0x01, 0x8c, 0x15,
|
||||
// source ip
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
// destination ip
|
||||
0x05, 0x06, 0x07, 0x08,
|
||||
// ICMP header
|
||||
0x08, 0x00, 0x7d, 0x22,
|
||||
// "request_payload"
|
||||
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
|
||||
}
|
||||
|
||||
var icmpRequestDecode = ParsedPacket{
|
||||
b: icmpRequestBuffer,
|
||||
subofs: 20,
|
||||
dataofs: 24,
|
||||
length: len(icmpRequestBuffer),
|
||||
|
||||
IPVersion: 4,
|
||||
IPProto: ICMP,
|
||||
SrcIP: NewIP4(net.ParseIP("1.2.3.4")),
|
||||
DstIP: NewIP4(net.ParseIP("5.6.7.8")),
|
||||
SrcPort: 0,
|
||||
DstPort: 0,
|
||||
}
|
||||
|
||||
var icmpReplyBuffer = []byte{
|
||||
0x45, 0x00, 0x00, 0x25, 0x21, 0x52, 0x00, 0x00, 0x40, 0x01, 0x49, 0x73,
|
||||
// source ip
|
||||
0x05, 0x06, 0x07, 0x08,
|
||||
// destination ip
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
// ICMP header
|
||||
0x00, 0x00, 0xe6, 0x9e,
|
||||
// "reply_payload"
|
||||
0x72, 0x65, 0x70, 0x6c, 0x79, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
|
||||
}
|
||||
|
||||
var icmpReplyDecode = ParsedPacket{
|
||||
b: icmpReplyBuffer,
|
||||
subofs: 20,
|
||||
dataofs: 24,
|
||||
length: len(icmpReplyBuffer),
|
||||
|
||||
IPVersion: 4,
|
||||
IPProto: ICMP,
|
||||
SrcIP: NewIP4(net.ParseIP("1.2.3.4")),
|
||||
DstIP: NewIP4(net.ParseIP("5.6.7.8")),
|
||||
SrcPort: 0,
|
||||
DstPort: 0,
|
||||
}
|
||||
|
||||
// IPv6 Router Solicitation
|
||||
var ipv6PacketBuffer = []byte{
|
||||
0x60, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3a, 0xff,
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xfb, 0x57, 0x1d, 0xea, 0x9c, 0x39, 0x8f, 0xb7,
|
||||
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
0x85, 0x00, 0x38, 0x04, 0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
var ipv6PacketDecode = ParsedPacket{
|
||||
b: ipv6PacketBuffer,
|
||||
IPVersion: 6,
|
||||
IPProto: ICMPv6,
|
||||
}
|
||||
|
||||
// This is a malformed IPv4 packet.
|
||||
// Namely, the string "tcp_payload" follows the first byte of the IPv4 header.
|
||||
var unknownPacketBuffer = []byte{
|
||||
0x45, 0x74, 0x63, 0x70, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
|
||||
}
|
||||
|
||||
var unknownPacketDecode = ParsedPacket{
|
||||
b: unknownPacketBuffer,
|
||||
IPVersion: 0,
|
||||
IPProto: Unknown,
|
||||
}
|
||||
|
||||
var tcpPacketBuffer = []byte{
|
||||
// IP header up to checksum
|
||||
0x45, 0x00, 0x00, 0x37, 0xde, 0xad, 0x00, 0x00, 0x40, 0x06, 0x49, 0x5f,
|
||||
// source ip
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
// destination ip
|
||||
0x05, 0x06, 0x07, 0x08,
|
||||
// TCP header with SYN, ACK set
|
||||
0x00, 0x7b, 0x02, 0x37, 0x00, 0x00, 0x12, 0x34, 0x00, 0x00, 0x00, 0x00,
|
||||
0x50, 0x12, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// "request_payload"
|
||||
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
|
||||
}
|
||||
|
||||
var tcpPacketDecode = ParsedPacket{
|
||||
b: tcpPacketBuffer,
|
||||
subofs: 20,
|
||||
dataofs: 40,
|
||||
length: len(tcpPacketBuffer),
|
||||
|
||||
IPVersion: 4,
|
||||
IPProto: TCP,
|
||||
SrcIP: NewIP4(net.ParseIP("1.2.3.4")),
|
||||
DstIP: NewIP4(net.ParseIP("5.6.7.8")),
|
||||
SrcPort: 123,
|
||||
DstPort: 567,
|
||||
TCPFlags: TCPSynAck,
|
||||
}
|
||||
|
||||
var udpRequestBuffer = []byte{
|
||||
// IP header up to checksum
|
||||
0x45, 0x00, 0x00, 0x2b, 0xde, 0xad, 0x00, 0x00, 0x40, 0x11, 0x8c, 0x01,
|
||||
// source ip
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
// destination ip
|
||||
0x05, 0x06, 0x07, 0x08,
|
||||
// UDP header
|
||||
0x00, 0x7b, 0x02, 0x37, 0x00, 0x17, 0x72, 0x1d,
|
||||
// "request_payload"
|
||||
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
|
||||
}
|
||||
|
||||
var udpRequestDecode = ParsedPacket{
|
||||
b: udpRequestBuffer,
|
||||
subofs: 20,
|
||||
dataofs: 28,
|
||||
length: len(udpRequestBuffer),
|
||||
|
||||
IPVersion: 4,
|
||||
IPProto: UDP,
|
||||
SrcIP: NewIP4(net.ParseIP("1.2.3.4")),
|
||||
DstIP: NewIP4(net.ParseIP("5.6.7.8")),
|
||||
SrcPort: 123,
|
||||
DstPort: 567,
|
||||
}
|
||||
|
||||
var udpReplyBuffer = []byte{
|
||||
// IP header up to checksum
|
||||
0x45, 0x00, 0x00, 0x29, 0x21, 0x52, 0x00, 0x00, 0x40, 0x11, 0x49, 0x5f,
|
||||
// source ip
|
||||
0x05, 0x06, 0x07, 0x08,
|
||||
// destination ip
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
// UDP header
|
||||
0x02, 0x37, 0x00, 0x7b, 0x00, 0x15, 0xd3, 0x9d,
|
||||
// "reply_payload"
|
||||
0x72, 0x65, 0x70, 0x6c, 0x79, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
|
||||
}
|
||||
|
||||
var udpReplyDecode = ParsedPacket{
|
||||
b: udpReplyBuffer,
|
||||
subofs: 20,
|
||||
dataofs: 28,
|
||||
length: len(udpReplyBuffer),
|
||||
|
||||
IPProto: UDP,
|
||||
SrcIP: NewIP4(net.ParseIP("1.2.3.4")),
|
||||
DstIP: NewIP4(net.ParseIP("5.6.7.8")),
|
||||
SrcPort: 567,
|
||||
DstPort: 123,
|
||||
}
|
||||
|
||||
func TestParsedPacket(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
qdecode ParsedPacket
|
||||
want string
|
||||
}{
|
||||
{"tcp", tcpPacketDecode, "TCP{1.2.3.4:123 > 5.6.7.8:567}"},
|
||||
{"icmp", icmpRequestDecode, "ICMP{1.2.3.4:0 > 5.6.7.8:0}"},
|
||||
{"unknown", unknownPacketDecode, "Unknown{???}"},
|
||||
{"ipv6", ipv6PacketDecode, "IPv6{Proto=58}"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.qdecode.String()
|
||||
if got != tt.want {
|
||||
t.Errorf("got %q; want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var sink string
|
||||
allocs := testing.AllocsPerRun(1000, func() {
|
||||
sink = tests[0].qdecode.String()
|
||||
})
|
||||
_ = sink
|
||||
if allocs != 1 {
|
||||
t.Errorf("allocs = %v; want 1", allocs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
buf []byte
|
||||
want ParsedPacket
|
||||
}{
|
||||
{"icmp", icmpRequestBuffer, icmpRequestDecode},
|
||||
{"ipv6", ipv6PacketBuffer, ipv6PacketDecode},
|
||||
{"unknown", unknownPacketBuffer, unknownPacketDecode},
|
||||
{"tcp", tcpPacketBuffer, tcpPacketDecode},
|
||||
{"udp", udpRequestBuffer, udpRequestDecode},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var got ParsedPacket
|
||||
got.Decode(tt.buf)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("mismatch\n got: %#v\nwant: %#v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
allocs := testing.AllocsPerRun(1000, func() {
|
||||
var got ParsedPacket
|
||||
got.Decode(tests[0].buf)
|
||||
})
|
||||
if allocs != 0 {
|
||||
t.Errorf("allocs = %v; want 0", allocs)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode(b *testing.B) {
|
||||
benches := []struct {
|
||||
name string
|
||||
buf []byte
|
||||
}{
|
||||
{"icmp", icmpRequestBuffer},
|
||||
{"unknown", unknownPacketBuffer},
|
||||
{"tcp", tcpPacketBuffer},
|
||||
}
|
||||
|
||||
for _, bench := range benches {
|
||||
b.Run(bench.name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
var p ParsedPacket
|
||||
p.Decode(bench.buf)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalRequest(t *testing.T) {
|
||||
// Too small to hold our packets, but only barely.
|
||||
var small [20]byte
|
||||
var large [64]byte
|
||||
|
||||
icmpHeader := icmpRequestDecode.ICMPHeader()
|
||||
udpHeader := udpRequestDecode.UDPHeader()
|
||||
tests := []struct {
|
||||
name string
|
||||
header Header
|
||||
want []byte
|
||||
}{
|
||||
{"icmp", &icmpHeader, icmpRequestBuffer},
|
||||
{"udp", &udpHeader, udpRequestBuffer},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.header.Marshal(small[:])
|
||||
if err != errSmallBuffer {
|
||||
t.Errorf("got err: nil; want: %s", errSmallBuffer)
|
||||
}
|
||||
|
||||
dataOffset := tt.header.Len()
|
||||
dataLength := copy(large[dataOffset:], []byte("request_payload"))
|
||||
end := dataOffset + dataLength
|
||||
err = tt.header.Marshal(large[:end])
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("got err: %s; want nil", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(large[:end], tt.want) {
|
||||
t.Errorf("got %x; want %x", large[:end], tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalResponse(t *testing.T) {
|
||||
var buf [64]byte
|
||||
|
||||
icmpHeader := icmpRequestDecode.ICMPHeader()
|
||||
udpHeader := udpRequestDecode.UDPHeader()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
header Header
|
||||
want []byte
|
||||
}{
|
||||
{"icmp", &icmpHeader, icmpReplyBuffer},
|
||||
{"udp", &udpHeader, udpReplyBuffer},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.header.ToResponse()
|
||||
|
||||
dataOffset := tt.header.Len()
|
||||
dataLength := copy(buf[dataOffset:], []byte("reply_payload"))
|
||||
end := dataOffset + dataLength
|
||||
err := tt.header.Marshal(buf[:end])
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("got err: %s; want nil", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(buf[:end], tt.want) {
|
||||
t.Errorf("got %x; want %x", buf[:end], tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,53 +0,0 @@
|
||||
// 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 UDP4Header struct {
|
||||
IP4Header
|
||||
SrcPort uint16
|
||||
DstPort uint16
|
||||
}
|
||||
|
||||
const (
|
||||
udpHeaderLength = 8
|
||||
// udpTotalHeaderLength is the length of all headers in a UDP packet.
|
||||
udpTotalHeaderLength = ipHeaderLength + udpHeaderLength
|
||||
)
|
||||
|
||||
func (UDP4Header) Len() int {
|
||||
return udpTotalHeaderLength
|
||||
}
|
||||
|
||||
func (h UDP4Header) 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.IP4Header.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.IP4Header.MarshalPseudo(buf)
|
||||
|
||||
// UDP checksum with IP pseudo header.
|
||||
put16(buf[26:28], ipChecksum(buf[8:]))
|
||||
|
||||
h.IP4Header.Marshal(buf)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *UDP4Header) ToResponse() {
|
||||
h.SrcPort, h.DstPort = h.DstPort, h.SrcPort
|
||||
h.IP4Header.ToResponse()
|
||||
}
|
@@ -16,9 +16,9 @@ import (
|
||||
|
||||
"github.com/tailscale/wireguard-go/device"
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/wgengine/filter"
|
||||
"tailscale.com/wgengine/packet"
|
||||
)
|
||||
|
||||
const maxBufferSize = device.MaxMessageSize
|
||||
|
@@ -11,9 +11,9 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/tailscale/wireguard-go/tun/tuntest"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/wgengine/filter"
|
||||
"tailscale.com/wgengine/packet"
|
||||
)
|
||||
|
||||
func udp(src, dst packet.IP4, sport, dport uint16) []byte {
|
||||
|
@@ -32,6 +32,7 @@ import (
|
||||
"tailscale.com/internal/deepprint"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/net/interfaces"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/net/tshttpproxy"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -42,7 +43,6 @@ import (
|
||||
"tailscale.com/wgengine/filter"
|
||||
"tailscale.com/wgengine/magicsock"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
"tailscale.com/wgengine/packet"
|
||||
"tailscale.com/wgengine/router"
|
||||
"tailscale.com/wgengine/tsdns"
|
||||
"tailscale.com/wgengine/tstun"
|
||||
|
Reference in New Issue
Block a user