mirror of
https://github.com/tailscale/tailscale.git
synced 2025-10-10 09:45:08 +00:00
wgengine/packet: refactor and expose UDP header marshaling (#408)
Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>
This commit is contained in:

committed by
GitHub

parent
5e0ff494a5
commit
059b1d10bb
@@ -137,7 +137,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, b []byte, q *packet.QDecode, r Response, why string) {
|
||||
func (f *Filter) logRateLimit(runflags RunFlags, b []byte, q *packet.ParsedPacket, r Response, why string) {
|
||||
var verdict string
|
||||
|
||||
if r == Drop && (runflags&LogDrops) != 0 && dropBucket.Allow() {
|
||||
@@ -161,7 +161,7 @@ func (f *Filter) logRateLimit(runflags RunFlags, b []byte, q *packet.QDecode, r
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Filter) RunIn(b []byte, q *packet.QDecode, rf RunFlags) Response {
|
||||
func (f *Filter) RunIn(b []byte, q *packet.ParsedPacket, rf RunFlags) Response {
|
||||
r := f.pre(b, q, rf)
|
||||
if r == Accept || r == Drop {
|
||||
// already logged
|
||||
@@ -173,7 +173,7 @@ func (f *Filter) RunIn(b []byte, q *packet.QDecode, rf RunFlags) Response {
|
||||
return r
|
||||
}
|
||||
|
||||
func (f *Filter) RunOut(b []byte, q *packet.QDecode, rf RunFlags) Response {
|
||||
func (f *Filter) RunOut(b []byte, q *packet.ParsedPacket, rf RunFlags) Response {
|
||||
r := f.pre(b, q, rf)
|
||||
if r == Drop || r == Accept {
|
||||
// already logged
|
||||
@@ -184,7 +184,7 @@ func (f *Filter) RunOut(b []byte, q *packet.QDecode, rf RunFlags) Response {
|
||||
return r
|
||||
}
|
||||
|
||||
func (f *Filter) runIn(q *packet.QDecode) (r Response, why string) {
|
||||
func (f *Filter) runIn(q *packet.ParsedPacket) (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.
|
||||
@@ -239,7 +239,7 @@ func (f *Filter) runIn(q *packet.QDecode) (r Response, why string) {
|
||||
return Drop, "no rules matched"
|
||||
}
|
||||
|
||||
func (f *Filter) runOut(q *packet.QDecode) (r Response, why string) {
|
||||
func (f *Filter) runOut(q *packet.ParsedPacket) (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
|
||||
@@ -251,7 +251,7 @@ func (f *Filter) runOut(q *packet.QDecode) (r Response, why string) {
|
||||
return Accept, "ok out"
|
||||
}
|
||||
|
||||
func (f *Filter) pre(b []byte, q *packet.QDecode, rf RunFlags) Response {
|
||||
func (f *Filter) pre(b []byte, q *packet.ParsedPacket, rf RunFlags) Response {
|
||||
if len(b) == 0 {
|
||||
// wireguard keepalive packet, always permit.
|
||||
return Accept
|
||||
@@ -262,13 +262,17 @@ func (f *Filter) pre(b []byte, q *packet.QDecode, rf RunFlags) Response {
|
||||
}
|
||||
q.Decode(b)
|
||||
|
||||
if q.IPProto == packet.Junk {
|
||||
// Junk packets are dangerous; always drop them.
|
||||
f.logRateLimit(rf, b, q, Drop, "junk")
|
||||
switch q.IPProto {
|
||||
case packet.Unknown:
|
||||
// Unknown packets are dangerous; always drop them.
|
||||
f.logRateLimit(rf, b, q, Drop, "unknown")
|
||||
return Drop
|
||||
} else if q.IPProto == packet.Fragment {
|
||||
case packet.IPv6:
|
||||
f.logRateLimit(rf, b, q, Drop, "ipv6")
|
||||
return Drop
|
||||
case packet.Fragment:
|
||||
// Fragments after the first always need to be passed through.
|
||||
// Very small fragments are considered Junk by QDecode.
|
||||
// Very small fragments are considered Junk by ParsedPacket.
|
||||
f.logRateLimit(rf, b, q, Accept, "fragment")
|
||||
return Accept
|
||||
}
|
||||
|
@@ -14,10 +14,10 @@ import (
|
||||
)
|
||||
|
||||
// Type aliases only in test code: (but ideally nowhere)
|
||||
type QDecode = packet.QDecode
|
||||
type ParsedPacket = packet.ParsedPacket
|
||||
type IP = packet.IP
|
||||
|
||||
var Junk = packet.Junk
|
||||
var Unknown = packet.Unknown
|
||||
var ICMP = packet.ICMP
|
||||
var TCP = packet.TCP
|
||||
var UDP = packet.UDP
|
||||
@@ -84,34 +84,34 @@ func TestFilter(t *testing.T) {
|
||||
|
||||
type InOut struct {
|
||||
want Response
|
||||
p QDecode
|
||||
p ParsedPacket
|
||||
}
|
||||
tests := []InOut{
|
||||
// Basic
|
||||
{Accept, qdecode(TCP, 0x08010101, 0x01020304, 999, 22)},
|
||||
{Accept, qdecode(UDP, 0x08010101, 0x01020304, 999, 22)},
|
||||
{Accept, qdecode(ICMP, 0x08010101, 0x01020304, 0, 0)},
|
||||
{Drop, qdecode(TCP, 0x08010101, 0x01020304, 0, 0)},
|
||||
{Accept, qdecode(TCP, 0x08010101, 0x01020304, 0, 22)},
|
||||
{Drop, qdecode(TCP, 0x08010101, 0x01020304, 0, 21)},
|
||||
{Accept, qdecode(TCP, 0x11223344, 0x08012233, 0, 443)},
|
||||
{Drop, qdecode(TCP, 0x11223344, 0x08012233, 0, 444)},
|
||||
{Accept, qdecode(TCP, 0x11223344, 0x647a6232, 0, 999)},
|
||||
{Accept, qdecode(TCP, 0x11223344, 0x647a6232, 0, 0)},
|
||||
{Accept, parsed(TCP, 0x08010101, 0x01020304, 999, 22)},
|
||||
{Accept, parsed(UDP, 0x08010101, 0x01020304, 999, 22)},
|
||||
{Accept, parsed(ICMP, 0x08010101, 0x01020304, 0, 0)},
|
||||
{Drop, parsed(TCP, 0x08010101, 0x01020304, 0, 0)},
|
||||
{Accept, parsed(TCP, 0x08010101, 0x01020304, 0, 22)},
|
||||
{Drop, parsed(TCP, 0x08010101, 0x01020304, 0, 21)},
|
||||
{Accept, parsed(TCP, 0x11223344, 0x08012233, 0, 443)},
|
||||
{Drop, parsed(TCP, 0x11223344, 0x08012233, 0, 444)},
|
||||
{Accept, parsed(TCP, 0x11223344, 0x647a6232, 0, 999)},
|
||||
{Accept, parsed(TCP, 0x11223344, 0x647a6232, 0, 0)},
|
||||
|
||||
// localNets prefilter - accepted by policy filter, but
|
||||
// unexpected dst IP.
|
||||
{Drop, qdecode(TCP, 0x08010101, 0x10203040, 0, 443)},
|
||||
{Drop, parsed(TCP, 0x08010101, 0x10203040, 0, 443)},
|
||||
|
||||
// Stateful UDP. Note each packet is run through the input
|
||||
// filter, then the output filter (which sets conntrack
|
||||
// state).
|
||||
// Initially empty cache
|
||||
{Drop, qdecode(UDP, 0x77777777, 0x66666666, 4242, 4343)},
|
||||
{Drop, parsed(UDP, 0x77777777, 0x66666666, 4242, 4343)},
|
||||
// Return packet from previous attempt is allowed
|
||||
{Accept, qdecode(UDP, 0x66666666, 0x77777777, 4343, 4242)},
|
||||
{Accept, parsed(UDP, 0x66666666, 0x77777777, 4343, 4242)},
|
||||
// Because of the return above, initial attempt is allowed now
|
||||
{Accept, qdecode(UDP, 0x77777777, 0x66666666, 4242, 4343)},
|
||||
{Accept, parsed(UDP, 0x77777777, 0x66666666, 4242, 4343)},
|
||||
}
|
||||
for i, test := range tests {
|
||||
if got, _ := acl.runIn(&test.p); test.want != got {
|
||||
@@ -144,7 +144,7 @@ func TestNoAllocs(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := int(testing.AllocsPerRun(1000, func() {
|
||||
var q QDecode
|
||||
var q ParsedPacket
|
||||
if test.in {
|
||||
acl.RunIn(test.packet, &q, 0)
|
||||
} else {
|
||||
@@ -187,7 +187,7 @@ func BenchmarkFilter(b *testing.B) {
|
||||
for _, bench := range benches {
|
||||
b.Run(bench.name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
var q QDecode
|
||||
var q ParsedPacket
|
||||
// This branch seems to have no measurable impact on performance.
|
||||
if bench.in {
|
||||
acl.RunIn(bench.packet, &q, 0)
|
||||
@@ -207,7 +207,7 @@ func TestPreFilter(t *testing.T) {
|
||||
}{
|
||||
{"empty", Accept, []byte{}},
|
||||
{"short", Drop, []byte("short")},
|
||||
{"junk", Drop, rawdefault(Junk, 10)},
|
||||
{"junk", Drop, rawdefault(Unknown, 10)},
|
||||
{"fragment", Accept, rawdefault(Fragment, 40)},
|
||||
{"tcp", noVerdict, rawdefault(TCP, 200)},
|
||||
{"udp", noVerdict, rawdefault(UDP, 200)},
|
||||
@@ -215,15 +215,15 @@ func TestPreFilter(t *testing.T) {
|
||||
}
|
||||
f := NewAllowNone(t.Logf)
|
||||
for _, testPacket := range packets {
|
||||
got := f.pre([]byte(testPacket.b), &QDecode{}, LogDrops|LogAccepts)
|
||||
got := f.pre([]byte(testPacket.b), &ParsedPacket{}, LogDrops|LogAccepts)
|
||||
if got != testPacket.want {
|
||||
t.Errorf("%q got=%v want=%v packet:\n%s", testPacket.desc, got, testPacket.want, packet.Hexdump(testPacket.b))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func qdecode(proto packet.IPProto, src, dst packet.IP, sport, dport uint16) QDecode {
|
||||
return QDecode{
|
||||
func parsed(proto packet.IPProto, src, dst packet.IP, sport, dport uint16) ParsedPacket {
|
||||
return ParsedPacket{
|
||||
IPProto: proto,
|
||||
SrcIP: src,
|
||||
DstIP: dst,
|
||||
@@ -277,7 +277,7 @@ func rawpacket(proto packet.IPProto, src, dst packet.IP, sport, dport uint16, tr
|
||||
hdr[9] = 6
|
||||
// flags + fragOff
|
||||
bin.PutUint16(hdr[6:8], (1<<13)|1234)
|
||||
case Junk:
|
||||
case Unknown:
|
||||
default:
|
||||
panic("unknown protocol")
|
||||
}
|
||||
|
@@ -133,7 +133,7 @@ func ipInList(ip packet.IP, netlist []Net) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func matchIPPorts(mm Matches, q *packet.QDecode) bool {
|
||||
func matchIPPorts(mm Matches, q *packet.ParsedPacket) bool {
|
||||
for _, acl := range mm {
|
||||
for _, dst := range acl.Dsts {
|
||||
if !dst.Net.Includes(q.DstIP) {
|
||||
@@ -153,7 +153,7 @@ func matchIPPorts(mm Matches, q *packet.QDecode) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func matchIPWithoutPorts(mm Matches, q *packet.QDecode) bool {
|
||||
func matchIPWithoutPorts(mm Matches, q *packet.ParsedPacket) bool {
|
||||
for _, acl := range mm {
|
||||
for _, dst := range acl.Dsts {
|
||||
if !dst.Net.Includes(q.DstIP) {
|
||||
|
Reference in New Issue
Block a user