diff --git a/net/packet/doc.go b/net/packet/doc.go index 40dc87a14..9d76f736a 100644 --- a/net/packet/doc.go +++ b/net/packet/doc.go @@ -4,9 +4,9 @@ // 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. +// Parsed 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, diff --git a/net/packet/packet.go b/net/packet/packet.go index fe206c964..c1a510e8a 100644 --- a/net/packet/packet.go +++ b/net/packet/packet.go @@ -29,10 +29,10 @@ var ( put32 = binary.BigEndian.PutUint32 ) -// ParsedPacket is a minimal decoding of a packet suitable for use in filters. +// 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 ParsedPacket struct { +type Parsed struct { // b is the byte buffer that this decodes. b []byte // subofs is the offset of IP subprotocol. @@ -55,7 +55,7 @@ type ParsedPacket struct { // NextHeader type NextHeader uint8 -func (p *ParsedPacket) String() string { +func (p *Parsed) String() string { if p.IPVersion == 6 { return fmt.Sprintf("IPv6{Proto=%d}", p.IPProto) } @@ -108,7 +108,7 @@ func ipChecksum(b []byte) uint16 { // 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) { +func (q *Parsed) Decode(b []byte) { q.b = b if len(b) < ipHeaderLength { @@ -224,7 +224,7 @@ func (q *ParsedPacket) Decode(b []byte) { } } -func (q *ParsedPacket) IPHeader() IP4Header { +func (q *Parsed) IPHeader() IP4Header { ipid := get16(q.b[4:6]) return IP4Header{ IPID: ipid, @@ -234,7 +234,7 @@ func (q *ParsedPacket) IPHeader() IP4Header { } } -func (q *ParsedPacket) ICMPHeader() ICMP4Header { +func (q *Parsed) ICMPHeader() ICMP4Header { return ICMP4Header{ IP4Header: q.IPHeader(), Type: ICMP4Type(q.b[q.subofs+0]), @@ -242,7 +242,7 @@ func (q *ParsedPacket) ICMPHeader() ICMP4Header { } } -func (q *ParsedPacket) UDPHeader() UDP4Header { +func (q *Parsed) UDPHeader() UDP4Header { return UDP4Header{ IP4Header: q.IPHeader(), SrcPort: q.SrcPort, @@ -252,37 +252,37 @@ func (q *ParsedPacket) UDPHeader() UDP4Header { // 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 { +func (q *Parsed) 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 { +func (q *Parsed) 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 { +func (q *Parsed) 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 { +func (q *Parsed) 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 { +func (q *Parsed) IsTCPSyn() bool { return (q.TCPFlags & TCPSynAck) == TCPSyn } // IsError reports whether q is an IPv4 ICMP "Error" packet. -func (q *ParsedPacket) IsError() bool { +func (q *Parsed) IsError() bool { if q.IPProto == ICMP && len(q.b) >= q.subofs+8 { switch ICMP4Type(q.b[q.subofs]) { case ICMP4Unreachable, ICMP4TimeExceeded: @@ -293,7 +293,7 @@ func (q *ParsedPacket) IsError() bool { } // IsEchoRequest reports whether q is an IPv4 ICMP Echo Request. -func (q *ParsedPacket) IsEchoRequest() bool { +func (q *Parsed) 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 @@ -302,7 +302,7 @@ func (q *ParsedPacket) IsEchoRequest() bool { } // IsEchoRequest reports whether q is an IPv4 ICMP Echo Response. -func (q *ParsedPacket) IsEchoResponse() bool { +func (q *Parsed) 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 diff --git a/net/packet/packet_test.go b/net/packet/packet_test.go index 4ed31ca5e..7175e11e2 100644 --- a/net/packet/packet_test.go +++ b/net/packet/packet_test.go @@ -41,7 +41,7 @@ var icmpRequestBuffer = []byte{ 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, } -var icmpRequestDecode = ParsedPacket{ +var icmpRequestDecode = Parsed{ b: icmpRequestBuffer, subofs: 20, dataofs: 24, @@ -67,7 +67,7 @@ var icmpReplyBuffer = []byte{ 0x72, 0x65, 0x70, 0x6c, 0x79, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, } -var icmpReplyDecode = ParsedPacket{ +var icmpReplyDecode = Parsed{ b: icmpReplyBuffer, subofs: 20, dataofs: 24, @@ -91,7 +91,7 @@ var ipv6PacketBuffer = []byte{ 0x85, 0x00, 0x38, 0x04, 0x00, 0x00, 0x00, 0x00, } -var ipv6PacketDecode = ParsedPacket{ +var ipv6PacketDecode = Parsed{ b: ipv6PacketBuffer, IPVersion: 6, IPProto: ICMPv6, @@ -103,7 +103,7 @@ var unknownPacketBuffer = []byte{ 0x45, 0x74, 0x63, 0x70, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, } -var unknownPacketDecode = ParsedPacket{ +var unknownPacketDecode = Parsed{ b: unknownPacketBuffer, IPVersion: 0, IPProto: Unknown, @@ -123,7 +123,7 @@ var tcpPacketBuffer = []byte{ 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, } -var tcpPacketDecode = ParsedPacket{ +var tcpPacketDecode = Parsed{ b: tcpPacketBuffer, subofs: 20, dataofs: 40, @@ -151,7 +151,7 @@ var udpRequestBuffer = []byte{ 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, } -var udpRequestDecode = ParsedPacket{ +var udpRequestDecode = Parsed{ b: udpRequestBuffer, subofs: 20, dataofs: 28, @@ -178,7 +178,7 @@ var udpReplyBuffer = []byte{ 0x72, 0x65, 0x70, 0x6c, 0x79, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, } -var udpReplyDecode = ParsedPacket{ +var udpReplyDecode = Parsed{ b: udpReplyBuffer, subofs: 20, dataofs: 28, @@ -191,10 +191,10 @@ var udpReplyDecode = ParsedPacket{ DstPort: 123, } -func TestParsedPacket(t *testing.T) { +func TestParsed(t *testing.T) { tests := []struct { name string - qdecode ParsedPacket + qdecode Parsed want string }{ {"tcp", tcpPacketDecode, "TCP{1.2.3.4:123 > 5.6.7.8:567}"}, @@ -226,7 +226,7 @@ func TestDecode(t *testing.T) { tests := []struct { name string buf []byte - want ParsedPacket + want Parsed }{ {"icmp", icmpRequestBuffer, icmpRequestDecode}, {"ipv6", ipv6PacketBuffer, ipv6PacketDecode}, @@ -237,7 +237,7 @@ func TestDecode(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var got ParsedPacket + var got Parsed got.Decode(tt.buf) if !reflect.DeepEqual(got, tt.want) { t.Errorf("mismatch\n got: %#v\nwant: %#v", got, tt.want) @@ -246,7 +246,7 @@ func TestDecode(t *testing.T) { } allocs := testing.AllocsPerRun(1000, func() { - var got ParsedPacket + var got Parsed got.Decode(tests[0].buf) }) if allocs != 0 { @@ -267,7 +267,7 @@ func BenchmarkDecode(b *testing.B) { for _, bench := range benches { b.Run(bench.name, func(b *testing.B) { for i := 0; i < b.N; i++ { - var p ParsedPacket + var p Parsed p.Decode(bench.buf) } }) diff --git a/wgengine/filter/filter.go b/wgengine/filter/filter.go index ac7111788..91dfc6226 100644 --- a/wgengine/filter/filter.go +++ b/wgengine/filter/filter.go @@ -153,7 +153,7 @@ func maybeHexdump(flag RunFlags, b []byte) string { var acceptBucket = rate.NewLimiter(rate.Every(10*time.Second), 3) var dropBucket = rate.NewLimiter(rate.Every(5*time.Second), 10) -func (f *Filter) logRateLimit(runflags RunFlags, q *packet.ParsedPacket, dir direction, r Response, why string) { +func (f *Filter) logRateLimit(runflags RunFlags, q *packet.Parsed, dir direction, r Response, why string) { var verdict string if r == Drop && omitDropLogging(q, dir) { @@ -186,7 +186,7 @@ var dummyPacket = []byte{ // CheckTCP determines whether TCP traffic from srcIP to dstIP:dstPort // is allowed. func (f *Filter) CheckTCP(srcIP, dstIP netaddr.IP, dstPort uint16) Response { - pkt := &packet.ParsedPacket{} + pkt := &packet.Parsed{} pkt.Decode(dummyPacket) // initialize private fields pkt.IPVersion = 4 pkt.IPProto = packet.TCP @@ -201,7 +201,7 @@ func (f *Filter) CheckTCP(srcIP, dstIP netaddr.IP, dstPort uint16) Response { // RunIn determines whether this node is allowed to receive q from a // Tailscale peer. -func (f *Filter) RunIn(q *packet.ParsedPacket, rf RunFlags) Response { +func (f *Filter) RunIn(q *packet.Parsed, rf RunFlags) Response { dir := in r := f.pre(q, rf, dir) if r == Accept || r == Drop { @@ -216,7 +216,7 @@ func (f *Filter) RunIn(q *packet.ParsedPacket, rf RunFlags) Response { // RunOut determines whether this node is allowed to send q to a // Tailscale peer. -func (f *Filter) RunOut(q *packet.ParsedPacket, rf RunFlags) Response { +func (f *Filter) RunOut(q *packet.Parsed, rf RunFlags) Response { dir := out r := f.pre(q, rf, dir) if r == Drop || r == Accept { @@ -229,7 +229,7 @@ func (f *Filter) RunOut(q *packet.ParsedPacket, rf RunFlags) Response { } // runIn runs the input-specific part of the filter logic. -func (f *Filter) runIn(q *packet.ParsedPacket) (r Response, why string) { +func (f *Filter) runIn(q *packet.Parsed) (r Response, why string) { // A compromised peer could try to send us packets for // destinations we didn't explicitly advertise. This check is to // prevent that. @@ -290,7 +290,7 @@ func (f *Filter) runIn(q *packet.ParsedPacket) (r Response, why string) { } // runIn runs the output-specific part of the filter logic. -func (f *Filter) runOut(q *packet.ParsedPacket) (r Response, why string) { +func (f *Filter) runOut(q *packet.Parsed) (r Response, why string) { if q.IPProto == packet.UDP { t := tuple{q.DstIP, q.SrcIP, q.DstPort, q.SrcPort} var ti interface{} = t // allocate once, rather than twice inside mutex @@ -324,7 +324,7 @@ func (d direction) String() string { // pre runs the direction-agnostic filter logic. dir is only used for // logging. -func (f *Filter) pre(q *packet.ParsedPacket, rf RunFlags, dir direction) Response { +func (f *Filter) pre(q *packet.Parsed, rf RunFlags, dir direction) Response { if len(q.Buffer()) == 0 { // wireguard keepalive packet, always permit. return Accept @@ -354,7 +354,7 @@ func (f *Filter) pre(q *packet.ParsedPacket, rf RunFlags, dir direction) Respons return Drop case packet.Fragment: // Fragments after the first always need to be passed through. - // Very small fragments are considered Junk by ParsedPacket. + // Very small fragments are considered Junk by Parsed. f.logRateLimit(rf, q, dir, Accept, "fragment") return Accept } @@ -373,13 +373,13 @@ const ( // deemed a packet to Drop, should bypass the [rate-limited] logging. // We don't want to log scary & spammy reject warnings for packets // that are totally normal, like IPv6 route announcements. -func omitDropLogging(p *packet.ParsedPacket, dir direction) bool { +func omitDropLogging(p *packet.Parsed, dir direction) bool { b := p.Buffer() switch dir { case out: switch p.IPVersion { case 4: - // ParsedPacket.Decode zeros out ParsedPacket.IPProtocol for protocols + // Parsed.Decode zeros out Parsed.IPProtocol for protocols // it doesn't know about, so parse it out ourselves if needed. ipProto := p.IPProto if ipProto == 0 && len(b) > 8 { diff --git a/wgengine/filter/filter_test.go b/wgengine/filter/filter_test.go index 28aa945c0..5fa9e9fe9 100644 --- a/wgengine/filter/filter_test.go +++ b/wgengine/filter/filter_test.go @@ -134,7 +134,7 @@ func TestFilter(t *testing.T) { type InOut struct { want Response - p packet.ParsedPacket + p packet.Parsed } tests := []InOut{ // Basic @@ -199,7 +199,7 @@ func TestNoAllocs(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { got := int(testing.AllocsPerRun(1000, func() { - q := &packet.ParsedPacket{} + q := &packet.Parsed{} q.Decode(test.packet) if test.in { acl.RunIn(q, 0) @@ -274,7 +274,7 @@ func BenchmarkFilter(b *testing.B) { b.Run(bench.name, func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - q := &packet.ParsedPacket{} + q := &packet.Parsed{} q.Decode(bench.packet) // This branch seems to have no measurable impact on performance. if bench.in { @@ -303,7 +303,7 @@ func TestPreFilter(t *testing.T) { } f := NewAllowNone(t.Logf) for _, testPacket := range packets { - p := &packet.ParsedPacket{} + p := &packet.Parsed{} p.Decode(testPacket.b) got := f.pre(p, LogDrops|LogAccepts, in) if got != testPacket.want { @@ -312,8 +312,8 @@ func TestPreFilter(t *testing.T) { } } -func parsed(proto packet.IP4Proto, src, dst packet.IP4, sport, dport uint16) packet.ParsedPacket { - return packet.ParsedPacket{ +func parsed(proto packet.IP4Proto, src, dst packet.IP4, sport, dport uint16) packet.Parsed { + return packet.Parsed{ IPProto: proto, SrcIP: src, DstIP: dst, @@ -385,13 +385,13 @@ func rawdefault(proto packet.IP4Proto, trimLength int) []byte { return rawpacket(proto, ip, ip, port, port, trimLength) } -func parseHexPkt(t *testing.T, h string) *packet.ParsedPacket { +func parseHexPkt(t *testing.T, h string) *packet.Parsed { t.Helper() b, err := hex.DecodeString(strings.ReplaceAll(h, " ", "")) if err != nil { t.Fatalf("failed to read hex %q: %v", h, err) } - p := new(packet.ParsedPacket) + p := new(packet.Parsed) p.Decode(b) return p } @@ -399,13 +399,13 @@ func parseHexPkt(t *testing.T, h string) *packet.ParsedPacket { func TestOmitDropLogging(t *testing.T) { tests := []struct { name string - pkt *packet.ParsedPacket + pkt *packet.Parsed dir direction want bool }{ { name: "v4_tcp_out", - pkt: &packet.ParsedPacket{IPVersion: 4, IPProto: packet.TCP}, + pkt: &packet.Parsed{IPVersion: 4, IPProto: packet.TCP}, dir: out, want: false, }, @@ -435,19 +435,19 @@ func TestOmitDropLogging(t *testing.T) { }, { name: "v4_multicast_out_low", - pkt: &packet.ParsedPacket{IPVersion: 4, DstIP: packet.NewIP4(net.ParseIP("224.0.0.0"))}, + pkt: &packet.Parsed{IPVersion: 4, DstIP: packet.NewIP4(net.ParseIP("224.0.0.0"))}, dir: out, want: true, }, { name: "v4_multicast_out_high", - pkt: &packet.ParsedPacket{IPVersion: 4, DstIP: packet.NewIP4(net.ParseIP("239.255.255.255"))}, + pkt: &packet.Parsed{IPVersion: 4, DstIP: packet.NewIP4(net.ParseIP("239.255.255.255"))}, dir: out, want: true, }, { name: "v4_link_local_unicast", - pkt: &packet.ParsedPacket{IPVersion: 4, DstIP: packet.NewIP4(net.ParseIP("169.254.1.2"))}, + pkt: &packet.Parsed{IPVersion: 4, DstIP: packet.NewIP4(net.ParseIP("169.254.1.2"))}, dir: out, want: true, }, diff --git a/wgengine/filter/match4.go b/wgengine/filter/match4.go index 7d724b28d..c0d0a8168 100644 --- a/wgengine/filter/match4.go +++ b/wgengine/filter/match4.go @@ -102,7 +102,7 @@ func newMatches4(ms []Match) (ret matches4) { // match returns whether q's source IP and destination IP:port match // any of ms. -func (ms matches4) match(q *packet.ParsedPacket) bool { +func (ms matches4) match(q *packet.Parsed) bool { for _, m := range ms { if !ip4InList(q.SrcIP, m.srcs) { continue @@ -122,7 +122,7 @@ func (ms matches4) match(q *packet.ParsedPacket) bool { // matchIPsOnly returns whether q's source and destination IP match // any of ms. -func (ms matches4) matchIPsOnly(q *packet.ParsedPacket) bool { +func (ms matches4) matchIPsOnly(q *packet.Parsed) bool { for _, m := range ms { if !ip4InList(q.SrcIP, m.srcs) { continue diff --git a/wgengine/tstun/tun.go b/wgengine/tstun/tun.go index 2698b8815..40ba756e0 100644 --- a/wgengine/tstun/tun.go +++ b/wgengine/tstun/tun.go @@ -45,14 +45,14 @@ var ( errOffsetTooSmall = errors.New("offset smaller than PacketStartOffset") ) -// parsedPacketPool holds a pool of ParsedPacket structs for use in filtering. +// parsedPacketPool holds a pool of Parsed structs for use in filtering. // This is needed because escape analysis cannot see that parsed packets // do not escape through {Pre,Post}Filter{In,Out}. -var parsedPacketPool = sync.Pool{New: func() interface{} { return new(packet.ParsedPacket) }} +var parsedPacketPool = sync.Pool{New: func() interface{} { return new(packet.Parsed) }} // FilterFunc is a packet-filtering function with access to the TUN device. // It must not hold onto the packet struct, as its backing storage will be reused. -type FilterFunc func(*packet.ParsedPacket, *TUN) filter.Response +type FilterFunc func(*packet.Parsed, *TUN) filter.Response // TUN wraps a tun.Device from wireguard-go, // augmenting it with filtering and packet injection. @@ -214,7 +214,7 @@ func (t *TUN) poll() { } } -func (t *TUN) filterOut(p *packet.ParsedPacket) filter.Response { +func (t *TUN) filterOut(p *packet.Parsed) filter.Response { if t.PreFilterOut != nil { if t.PreFilterOut(p, t) == filter.Drop { @@ -278,7 +278,7 @@ func (t *TUN) Read(buf []byte, offset int) (int, error) { } } - p := parsedPacketPool.Get().(*packet.ParsedPacket) + p := parsedPacketPool.Get().(*packet.Parsed) defer parsedPacketPool.Put(p) p.Decode(buf[offset : offset+n]) @@ -301,7 +301,7 @@ func (t *TUN) Read(buf []byte, offset int) (int, error) { } func (t *TUN) filterIn(buf []byte) filter.Response { - p := parsedPacketPool.Get().(*packet.ParsedPacket) + p := parsedPacketPool.Get().(*packet.Parsed) defer parsedPacketPool.Put(p) p.Decode(buf) diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 46715f136..b4ffe9065 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -370,7 +370,7 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) { } // echoRespondToAll is an inbound post-filter responding to all echo requests. -func echoRespondToAll(p *packet.ParsedPacket, t *tstun.TUN) filter.Response { +func echoRespondToAll(p *packet.Parsed, t *tstun.TUN) filter.Response { if p.IsEchoRequest() { header := p.ICMPHeader() header.ToResponse() @@ -391,7 +391,7 @@ func echoRespondToAll(p *packet.ParsedPacket, t *tstun.TUN) filter.Response { // stack, and intercepts any packets that should be handled by // tailscaled directly. Other packets are allowed to proceed into the // main ACL filter. -func (e *userspaceEngine) handleLocalPackets(p *packet.ParsedPacket, t *tstun.TUN) filter.Response { +func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.TUN) filter.Response { if verdict := e.handleDNS(p, t); verdict == filter.Drop { // local DNS handled the packet. return filter.Drop @@ -420,7 +420,7 @@ func (e *userspaceEngine) isLocalAddr(ip packet.IP4) bool { } // handleDNS is an outbound pre-filter resolving Tailscale domains. -func (e *userspaceEngine) handleDNS(p *packet.ParsedPacket, t *tstun.TUN) filter.Response { +func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.TUN) filter.Response { if p.DstIP == magicDNSIP && p.DstPort == magicDNSPort && p.IPProto == packet.UDP { request := tsdns.Packet{ Payload: append([]byte(nil), p.Payload()...),