mirror of
https://github.com/tailscale/tailscale.git
synced 2025-05-06 15:46:53 +00:00
net/packet, wgengine/filter: support SCTP
Add proto to flowtrack.Tuple. Add types/ipproto leaf package to break a cycle. Server-side ACL work remains. Updates #1516 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
90a6fb7ffe
commit
01b90df2fa
@ -39,6 +39,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
|
||||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||||
tailscale.com/types/empty from tailscale.com/ipn
|
tailscale.com/types/empty from tailscale.com/ipn
|
||||||
|
tailscale.com/types/ipproto from tailscale.com/net/flowtrack+
|
||||||
tailscale.com/types/key from tailscale.com/derp+
|
tailscale.com/types/key from tailscale.com/derp+
|
||||||
tailscale.com/types/logger from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/types/logger from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/types/netmap from tailscale.com/ipn
|
tailscale.com/types/netmap from tailscale.com/ipn
|
||||||
|
@ -113,6 +113,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
tailscale.com/tstime from tailscale.com/wgengine/magicsock
|
tailscale.com/tstime from tailscale.com/wgengine/magicsock
|
||||||
tailscale.com/types/empty from tailscale.com/control/controlclient+
|
tailscale.com/types/empty from tailscale.com/control/controlclient+
|
||||||
tailscale.com/types/flagtype from tailscale.com/cmd/tailscaled
|
tailscale.com/types/flagtype from tailscale.com/cmd/tailscaled
|
||||||
|
tailscale.com/types/ipproto from tailscale.com/net/flowtrack+
|
||||||
tailscale.com/types/key from tailscale.com/derp+
|
tailscale.com/types/key from tailscale.com/derp+
|
||||||
tailscale.com/types/logger from tailscale.com/cmd/tailscaled+
|
tailscale.com/types/logger from tailscale.com/cmd/tailscaled+
|
||||||
tailscale.com/types/netmap from tailscale.com/control/controlclient+
|
tailscale.com/types/netmap from tailscale.com/control/controlclient+
|
||||||
|
@ -15,16 +15,18 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
|
"tailscale.com/types/ipproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tuple is a 4-tuple of source and destination IP and port.
|
// Tuple is a 5-tuple of proto, source and destination IP and port.
|
||||||
type Tuple struct {
|
type Tuple struct {
|
||||||
|
Proto ipproto.Proto
|
||||||
Src netaddr.IPPort
|
Src netaddr.IPPort
|
||||||
Dst netaddr.IPPort
|
Dst netaddr.IPPort
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t Tuple) String() string {
|
func (t Tuple) String() string {
|
||||||
return fmt.Sprintf("(%v => %v)", t.Src, t.Dst)
|
return fmt.Sprintf("(%v %v => %v)", t.Proto, t.Src, t.Dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache is an LRU cache keyed by Tuple.
|
// Cache is an LRU cache keyed by Tuple.
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const tcpHeaderLength = 20
|
const tcpHeaderLength = 20
|
||||||
|
const sctpHeaderLength = 12
|
||||||
|
|
||||||
// maxPacketLength is the largest length that all headers support.
|
// maxPacketLength is the largest length that all headers support.
|
||||||
// IPv4 headers using uint16 for this forces an upper bound of 64KB.
|
// IPv4 headers using uint16 for this forces an upper bound of 64KB.
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
|
"tailscale.com/types/ipproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ip4HeaderLength is the length of an IPv4 header with no IP options.
|
// ip4HeaderLength is the length of an IPv4 header with no IP options.
|
||||||
@ -16,7 +17,7 @@ const ip4HeaderLength = 20
|
|||||||
|
|
||||||
// IP4Header represents an IPv4 packet header.
|
// IP4Header represents an IPv4 packet header.
|
||||||
type IP4Header struct {
|
type IP4Header struct {
|
||||||
IPProto IPProto
|
IPProto ipproto.Proto
|
||||||
IPID uint16
|
IPID uint16
|
||||||
Src netaddr.IP
|
Src netaddr.IP
|
||||||
Dst netaddr.IP
|
Dst netaddr.IP
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
|
"tailscale.com/types/ipproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ip6HeaderLength is the length of an IPv6 header with no IP options.
|
// ip6HeaderLength is the length of an IPv6 header with no IP options.
|
||||||
@ -15,7 +16,7 @@ const ip6HeaderLength = 40
|
|||||||
|
|
||||||
// IP6Header represents an IPv6 packet header.
|
// IP6Header represents an IPv6 packet header.
|
||||||
type IP6Header struct {
|
type IP6Header struct {
|
||||||
IPProto IPProto
|
IPProto ipproto.Proto
|
||||||
IPID uint32 // only lower 20 bits used
|
IPID uint32 // only lower 20 bits used
|
||||||
Src netaddr.IP
|
Src netaddr.IP
|
||||||
Dst netaddr.IP
|
Dst netaddr.IP
|
||||||
|
@ -11,9 +11,22 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
|
"tailscale.com/types/ipproto"
|
||||||
"tailscale.com/types/strbuilder"
|
"tailscale.com/types/strbuilder"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Unknown = ipproto.Unknown
|
||||||
|
TCP = ipproto.TCP
|
||||||
|
UDP = ipproto.UDP
|
||||||
|
SCTP = ipproto.SCTP
|
||||||
|
IGMP = ipproto.IGMP
|
||||||
|
ICMPv4 = ipproto.ICMPv4
|
||||||
|
ICMPv6 = ipproto.ICMPv6
|
||||||
|
TSMP = ipproto.TSMP
|
||||||
|
Fragment = ipproto.Fragment
|
||||||
|
)
|
||||||
|
|
||||||
// RFC1858: prevent overlapping fragment attacks.
|
// RFC1858: prevent overlapping fragment attacks.
|
||||||
const minFrag = 60 + 20 // max IPv4 header + basic TCP header
|
const minFrag = 60 + 20 // max IPv4 header + basic TCP header
|
||||||
|
|
||||||
@ -44,7 +57,7 @@ type Parsed struct {
|
|||||||
// 6), or 0 if the packet doesn't look like IPv4 or IPv6.
|
// 6), or 0 if the packet doesn't look like IPv4 or IPv6.
|
||||||
IPVersion uint8
|
IPVersion uint8
|
||||||
// IPProto is the IP subprotocol (UDP, TCP, etc.). Valid iff IPVersion != 0.
|
// IPProto is the IP subprotocol (UDP, TCP, etc.). Valid iff IPVersion != 0.
|
||||||
IPProto IPProto
|
IPProto ipproto.Proto
|
||||||
// SrcIP4 is the source address. Family matches IPVersion. Port is
|
// SrcIP4 is the source address. Family matches IPVersion. Port is
|
||||||
// valid iff IPProto == TCP || IPProto == UDP.
|
// valid iff IPProto == TCP || IPProto == UDP.
|
||||||
Src netaddr.IPPort
|
Src netaddr.IPPort
|
||||||
@ -130,7 +143,7 @@ func (q *Parsed) decode4(b []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check that it's IPv4.
|
// Check that it's IPv4.
|
||||||
q.IPProto = IPProto(b[9])
|
q.IPProto = ipproto.Proto(b[9])
|
||||||
q.length = int(binary.BigEndian.Uint16(b[2:4]))
|
q.length = int(binary.BigEndian.Uint16(b[2:4]))
|
||||||
if len(b) < q.length {
|
if len(b) < q.length {
|
||||||
// Packet was cut off before full IPv4 length.
|
// Packet was cut off before full IPv4 length.
|
||||||
@ -210,6 +223,14 @@ func (q *Parsed) decode4(b []byte) {
|
|||||||
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
|
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
|
||||||
q.dataofs = q.subofs + udpHeaderLength
|
q.dataofs = q.subofs + udpHeaderLength
|
||||||
return
|
return
|
||||||
|
case SCTP:
|
||||||
|
if len(sub) < sctpHeaderLength {
|
||||||
|
q.IPProto = Unknown
|
||||||
|
return
|
||||||
|
}
|
||||||
|
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
|
||||||
|
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
|
||||||
|
return
|
||||||
case TSMP:
|
case TSMP:
|
||||||
// Inter-tailscale messages.
|
// Inter-tailscale messages.
|
||||||
q.dataofs = q.subofs
|
q.dataofs = q.subofs
|
||||||
@ -244,7 +265,7 @@ func (q *Parsed) decode6(b []byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
q.IPProto = IPProto(b[6])
|
q.IPProto = ipproto.Proto(b[6])
|
||||||
q.length = int(binary.BigEndian.Uint16(b[4:6])) + ip6HeaderLength
|
q.length = int(binary.BigEndian.Uint16(b[4:6])) + ip6HeaderLength
|
||||||
if len(b) < q.length {
|
if len(b) < q.length {
|
||||||
// Packet was cut off before the full IPv6 length.
|
// Packet was cut off before the full IPv6 length.
|
||||||
@ -301,6 +322,14 @@ func (q *Parsed) decode6(b []byte) {
|
|||||||
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
|
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
|
||||||
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
|
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
|
||||||
q.dataofs = q.subofs + udpHeaderLength
|
q.dataofs = q.subofs + udpHeaderLength
|
||||||
|
case SCTP:
|
||||||
|
if len(sub) < sctpHeaderLength {
|
||||||
|
q.IPProto = Unknown
|
||||||
|
return
|
||||||
|
}
|
||||||
|
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
|
||||||
|
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
|
||||||
|
return
|
||||||
case TSMP:
|
case TSMP:
|
||||||
// Inter-tailscale messages.
|
// Inter-tailscale messages.
|
||||||
q.dataofs = q.subofs
|
q.dataofs = q.subofs
|
||||||
|
@ -305,6 +305,39 @@ var ipv4TSMPDecode = Parsed{
|
|||||||
Dst: mustIPPort("100.74.70.3:0"),
|
Dst: mustIPPort("100.74.70.3:0"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IPv4 SCTP
|
||||||
|
var sctpBuffer = []byte{
|
||||||
|
// IPv4 header:
|
||||||
|
0x45, 0x00,
|
||||||
|
0x00, 0x20, // 20 + 12 bytes total
|
||||||
|
0x00, 0x00, // ID
|
||||||
|
0x00, 0x00, // Fragment
|
||||||
|
0x40, // TTL
|
||||||
|
byte(SCTP),
|
||||||
|
// Checksum, unchecked:
|
||||||
|
1, 2,
|
||||||
|
// source IP:
|
||||||
|
0x64, 0x5e, 0x0c, 0x0e,
|
||||||
|
// dest IP:
|
||||||
|
0x64, 0x4a, 0x46, 0x03,
|
||||||
|
// Src Port, Dest Port:
|
||||||
|
0x00, 0x7b, 0x01, 0xc8,
|
||||||
|
// Verification tag:
|
||||||
|
1, 2, 3, 4,
|
||||||
|
// Checksum: (unchecked)
|
||||||
|
5, 6, 7, 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
var sctpDecode = Parsed{
|
||||||
|
b: sctpBuffer,
|
||||||
|
subofs: 20,
|
||||||
|
length: 20 + 12,
|
||||||
|
IPVersion: 4,
|
||||||
|
IPProto: SCTP,
|
||||||
|
Src: mustIPPort("100.94.12.14:123"),
|
||||||
|
Dst: mustIPPort("100.74.70.3:456"),
|
||||||
|
}
|
||||||
|
|
||||||
func TestParsedString(t *testing.T) {
|
func TestParsedString(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -320,6 +353,7 @@ func TestParsedString(t *testing.T) {
|
|||||||
{"igmp", igmpPacketDecode, "IGMP{192.168.1.82:0 > 224.0.0.251:0}"},
|
{"igmp", igmpPacketDecode, "IGMP{192.168.1.82:0 > 224.0.0.251:0}"},
|
||||||
{"unknown", unknownPacketDecode, "Unknown{???}"},
|
{"unknown", unknownPacketDecode, "Unknown{???}"},
|
||||||
{"ipv4_tsmp", ipv4TSMPDecode, "TSMP{100.94.12.14:0 > 100.74.70.3:0}"},
|
{"ipv4_tsmp", ipv4TSMPDecode, "TSMP{100.94.12.14:0 > 100.74.70.3:0}"},
|
||||||
|
{"sctp", sctpDecode, "SCTP{100.94.12.14:123 > 100.74.70.3:456}"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@ -357,6 +391,7 @@ func TestDecode(t *testing.T) {
|
|||||||
{"unknown", unknownPacketBuffer, unknownPacketDecode},
|
{"unknown", unknownPacketBuffer, unknownPacketDecode},
|
||||||
{"invalid4", invalid4RequestBuffer, invalid4RequestDecode},
|
{"invalid4", invalid4RequestBuffer, invalid4RequestDecode},
|
||||||
{"ipv4_tsmp", ipv4TSMPBuffer, ipv4TSMPDecode},
|
{"ipv4_tsmp", ipv4TSMPBuffer, ipv4TSMPDecode},
|
||||||
|
{"ipv4_sctp", sctpBuffer, sctpDecode},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/net/flowtrack"
|
"tailscale.com/net/flowtrack"
|
||||||
|
"tailscale.com/types/ipproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TailscaleRejectedHeader is a TSMP message that says that one
|
// TailscaleRejectedHeader is a TSMP message that says that one
|
||||||
@ -39,7 +40,7 @@ type TailscaleRejectedHeader struct {
|
|||||||
IPDst netaddr.IP // IPv4 or IPv6 header's dst IP
|
IPDst netaddr.IP // IPv4 or IPv6 header's dst IP
|
||||||
Src netaddr.IPPort // rejected flow's src
|
Src netaddr.IPPort // rejected flow's src
|
||||||
Dst netaddr.IPPort // rejected flow's dst
|
Dst netaddr.IPPort // rejected flow's dst
|
||||||
Proto IPProto // proto that was rejected (TCP or UDP)
|
Proto ipproto.Proto // proto that was rejected (TCP or UDP)
|
||||||
Reason TailscaleRejectReason // why the connection was rejected
|
Reason TailscaleRejectReason // why the connection was rejected
|
||||||
|
|
||||||
// MaybeBroken is whether the rejection is non-terminal (the
|
// MaybeBroken is whether the rejection is non-terminal (the
|
||||||
@ -57,7 +58,7 @@ type TailscaleRejectedHeader struct {
|
|||||||
const rejectFlagBitMaybeBroken = 0x1
|
const rejectFlagBitMaybeBroken = 0x1
|
||||||
|
|
||||||
func (rh TailscaleRejectedHeader) Flow() flowtrack.Tuple {
|
func (rh TailscaleRejectedHeader) Flow() flowtrack.Tuple {
|
||||||
return flowtrack.Tuple{Src: rh.Src, Dst: rh.Dst}
|
return flowtrack.Tuple{Proto: rh.Proto, Src: rh.Src, Dst: rh.Dst}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rh TailscaleRejectedHeader) String() string {
|
func (rh TailscaleRejectedHeader) String() string {
|
||||||
@ -181,7 +182,7 @@ func (pp *Parsed) AsTailscaleRejectedHeader() (h TailscaleRejectedHeader, ok boo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
h = TailscaleRejectedHeader{
|
h = TailscaleRejectedHeader{
|
||||||
Proto: IPProto(p[1]),
|
Proto: ipproto.Proto(p[1]),
|
||||||
Reason: TailscaleRejectReason(p[2]),
|
Reason: TailscaleRejectReason(p[2]),
|
||||||
IPSrc: pp.Src.IP,
|
IPSrc: pp.Src.IP,
|
||||||
IPDst: pp.Dst.IP,
|
IPDst: pp.Dst.IP,
|
||||||
|
@ -1,28 +1,32 @@
|
|||||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package packet
|
// Package ipproto contains IP Protocol constants.
|
||||||
|
package ipproto
|
||||||
|
|
||||||
// IPProto is an IP subprotocol as defined by the IANA protocol
|
import "fmt"
|
||||||
|
|
||||||
|
// Proto is an IP subprotocol as defined by the IANA protocol
|
||||||
// numbers list
|
// numbers list
|
||||||
// (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml),
|
// (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml),
|
||||||
// or the special values Unknown or Fragment.
|
// or the special values Unknown or Fragment.
|
||||||
type IPProto uint8
|
type Proto uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Unknown represents an unknown or unsupported protocol; it's
|
// Unknown represents an unknown or unsupported protocol; it's
|
||||||
// deliberately the zero value. Strictly speaking the zero
|
// deliberately the zero value. Strictly speaking the zero
|
||||||
// value is IPv6 hop-by-hop extensions, but we don't support
|
// value is IPv6 hop-by-hop extensions, but we don't support
|
||||||
// those, so this is still technically correct.
|
// those, so this is still technically correct.
|
||||||
Unknown IPProto = 0x00
|
Unknown Proto = 0x00
|
||||||
|
|
||||||
// Values from the IANA registry.
|
// Values from the IANA registry.
|
||||||
ICMPv4 IPProto = 0x01
|
ICMPv4 Proto = 0x01
|
||||||
IGMP IPProto = 0x02
|
IGMP Proto = 0x02
|
||||||
ICMPv6 IPProto = 0x3a
|
ICMPv6 Proto = 0x3a
|
||||||
TCP IPProto = 0x06
|
TCP Proto = 0x06
|
||||||
UDP IPProto = 0x11
|
UDP Proto = 0x11
|
||||||
|
SCTP Proto = 0x84
|
||||||
|
|
||||||
// TSMP is the Tailscale Message Protocol (our ICMP-ish
|
// TSMP is the Tailscale Message Protocol (our ICMP-ish
|
||||||
// thing), an IP protocol used only between Tailscale nodes
|
// thing), an IP protocol used only between Tailscale nodes
|
||||||
@ -33,7 +37,7 @@ const (
|
|||||||
// scheme". We never accept these from the host OS stack nor
|
// scheme". We never accept these from the host OS stack nor
|
||||||
// send them to the host network stack. It's only used between
|
// send them to the host network stack. It's only used between
|
||||||
// nodes.
|
// nodes.
|
||||||
TSMP IPProto = 99
|
TSMP Proto = 99
|
||||||
|
|
||||||
// Fragment represents any non-first IP fragment, for which we
|
// Fragment represents any non-first IP fragment, for which we
|
||||||
// don't have the sub-protocol header (and therefore can't
|
// don't have the sub-protocol header (and therefore can't
|
||||||
@ -41,11 +45,13 @@ const (
|
|||||||
//
|
//
|
||||||
// 0xFF is reserved in the IANA registry, so we steal it for
|
// 0xFF is reserved in the IANA registry, so we steal it for
|
||||||
// internal use.
|
// internal use.
|
||||||
Fragment IPProto = 0xFF
|
Fragment Proto = 0xFF
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p IPProto) String() string {
|
func (p Proto) String() string {
|
||||||
switch p {
|
switch p {
|
||||||
|
case Unknown:
|
||||||
|
return "Unknown"
|
||||||
case Fragment:
|
case Fragment:
|
||||||
return "Frag"
|
return "Frag"
|
||||||
case ICMPv4:
|
case ICMPv4:
|
||||||
@ -58,9 +64,11 @@ func (p IPProto) String() string {
|
|||||||
return "UDP"
|
return "UDP"
|
||||||
case TCP:
|
case TCP:
|
||||||
return "TCP"
|
return "TCP"
|
||||||
|
case SCTP:
|
||||||
|
return "SCTP"
|
||||||
case TSMP:
|
case TSMP:
|
||||||
return "TSMP"
|
return "TSMP"
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return fmt.Sprintf("IPProto-%d", int(p))
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,6 +14,7 @@ import (
|
|||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/net/flowtrack"
|
"tailscale.com/net/flowtrack"
|
||||||
"tailscale.com/net/packet"
|
"tailscale.com/net/packet"
|
||||||
|
"tailscale.com/types/ipproto"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -352,18 +353,18 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) {
|
|||||||
if f.matches4.match(q) {
|
if f.matches4.match(q) {
|
||||||
return Accept, "tcp ok"
|
return Accept, "tcp ok"
|
||||||
}
|
}
|
||||||
case packet.UDP:
|
case packet.UDP, packet.SCTP:
|
||||||
t := flowtrack.Tuple{Src: q.Src, Dst: q.Dst}
|
t := flowtrack.Tuple{Proto: q.IPProto, Src: q.Src, Dst: q.Dst}
|
||||||
|
|
||||||
f.state.mu.Lock()
|
f.state.mu.Lock()
|
||||||
_, ok := f.state.lru.Get(t)
|
_, ok := f.state.lru.Get(t)
|
||||||
f.state.mu.Unlock()
|
f.state.mu.Unlock()
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
return Accept, "udp cached"
|
return Accept, "cached"
|
||||||
}
|
}
|
||||||
if f.matches4.match(q) {
|
if f.matches4.match(q) {
|
||||||
return Accept, "udp ok"
|
return Accept, "ok"
|
||||||
}
|
}
|
||||||
case packet.TSMP:
|
case packet.TSMP:
|
||||||
return Accept, "tsmp ok"
|
return Accept, "tsmp ok"
|
||||||
@ -409,18 +410,18 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
|
|||||||
if f.matches6.match(q) {
|
if f.matches6.match(q) {
|
||||||
return Accept, "tcp ok"
|
return Accept, "tcp ok"
|
||||||
}
|
}
|
||||||
case packet.UDP:
|
case packet.UDP, packet.SCTP:
|
||||||
t := flowtrack.Tuple{Src: q.Src, Dst: q.Dst}
|
t := flowtrack.Tuple{Proto: q.IPProto, Src: q.Src, Dst: q.Dst}
|
||||||
|
|
||||||
f.state.mu.Lock()
|
f.state.mu.Lock()
|
||||||
_, ok := f.state.lru.Get(t)
|
_, ok := f.state.lru.Get(t)
|
||||||
f.state.mu.Unlock()
|
f.state.mu.Unlock()
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
return Accept, "udp cached"
|
return Accept, "cached"
|
||||||
}
|
}
|
||||||
if f.matches6.match(q) {
|
if f.matches6.match(q) {
|
||||||
return Accept, "udp ok"
|
return Accept, "ok"
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return Drop, "Unknown proto"
|
return Drop, "Unknown proto"
|
||||||
@ -430,15 +431,16 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
|
|||||||
|
|
||||||
// runIn runs the output-specific part of the filter logic.
|
// runIn runs the output-specific part of the filter logic.
|
||||||
func (f *Filter) runOut(q *packet.Parsed) (r Response, why string) {
|
func (f *Filter) runOut(q *packet.Parsed) (r Response, why string) {
|
||||||
if q.IPProto != packet.UDP {
|
switch q.IPProto {
|
||||||
return Accept, "ok out"
|
case ipproto.UDP, ipproto.SCTP:
|
||||||
|
tuple := flowtrack.Tuple{
|
||||||
|
Proto: q.IPProto,
|
||||||
|
Src: q.Dst, Dst: q.Src, // src/dst reversed
|
||||||
}
|
}
|
||||||
|
|
||||||
tuple := flowtrack.Tuple{Src: q.Dst, Dst: q.Src} // src/dst reversed
|
|
||||||
|
|
||||||
f.state.mu.Lock()
|
f.state.mu.Lock()
|
||||||
f.state.lru.Add(tuple, nil)
|
f.state.lru.Add(tuple, nil)
|
||||||
f.state.mu.Unlock()
|
f.state.mu.Unlock()
|
||||||
|
}
|
||||||
return Accept, "ok out"
|
return Accept, "ok out"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,19 +18,24 @@ import (
|
|||||||
"tailscale.com/net/packet"
|
"tailscale.com/net/packet"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
|
"tailscale.com/types/ipproto"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newFilter(logf logger.Logf) *Filter {
|
func newFilter(logf logger.Logf) *Filter {
|
||||||
m := func(srcs []netaddr.IPPrefix, dsts []NetPortRange) Match {
|
m := func(srcs []netaddr.IPPrefix, dsts []NetPortRange, protos ...ipproto.Proto) Match {
|
||||||
|
if protos == nil {
|
||||||
|
protos = defaultProtos
|
||||||
|
}
|
||||||
return Match{
|
return Match{
|
||||||
IPProto: defaultProtos,
|
IPProto: protos,
|
||||||
Srcs: srcs,
|
Srcs: srcs,
|
||||||
Dsts: dsts,
|
Dsts: dsts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
matches := []Match{
|
matches := []Match{
|
||||||
m(nets("8.1.1.1", "8.2.2.2"), netports("1.2.3.4:22", "5.6.7.8:23-24")),
|
m(nets("8.1.1.1", "8.2.2.2"), netports("1.2.3.4:22", "5.6.7.8:23-24")),
|
||||||
|
m(nets("9.1.1.1", "9.2.2.2"), netports("1.2.3.4:22", "5.6.7.8:23-24"), packet.SCTP),
|
||||||
m(nets("8.1.1.1", "8.2.2.2"), netports("5.6.7.8:27-28")),
|
m(nets("8.1.1.1", "8.2.2.2"), netports("5.6.7.8:27-28")),
|
||||||
m(nets("2.2.2.2"), netports("8.1.1.1:22")),
|
m(nets("2.2.2.2"), netports("8.1.1.1:22")),
|
||||||
m(nets("0.0.0.0/0"), netports("100.122.98.50:*")),
|
m(nets("0.0.0.0/0"), netports("100.122.98.50:*")),
|
||||||
@ -100,7 +105,9 @@ func TestFilter(t *testing.T) {
|
|||||||
{Drop, parsed(packet.TCP, "1::", "2602::1", 0, 443)},
|
{Drop, parsed(packet.TCP, "1::", "2602::1", 0, 443)},
|
||||||
|
|
||||||
// Don't allow protocols not specified by filter
|
// Don't allow protocols not specified by filter
|
||||||
{Drop, parsed(132 /* SCTP */, "8.1.1.1", "1.2.3.4", 999, 22)},
|
{Drop, parsed(packet.SCTP, "8.1.1.1", "1.2.3.4", 999, 22)},
|
||||||
|
// But SCTP is allowed for 9.1.1.1
|
||||||
|
{Accept, parsed(packet.SCTP, "9.1.1.1", "1.2.3.4", 999, 22)},
|
||||||
}
|
}
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
aclFunc := acl.runIn4
|
aclFunc := acl.runIn4
|
||||||
@ -532,7 +539,7 @@ func mustIP(s string) netaddr.IP {
|
|||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsed(proto packet.IPProto, src, dst string, sport, dport uint16) packet.Parsed {
|
func parsed(proto ipproto.Proto, src, dst string, sport, dport uint16) packet.Parsed {
|
||||||
sip, dip := mustIP(src), mustIP(dst)
|
sip, dip := mustIP(src), mustIP(dst)
|
||||||
|
|
||||||
var ret packet.Parsed
|
var ret packet.Parsed
|
||||||
@ -553,7 +560,7 @@ func parsed(proto packet.IPProto, src, dst string, sport, dport uint16) packet.P
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func raw6(proto packet.IPProto, src, dst string, sport, dport uint16, trimLen int) []byte {
|
func raw6(proto ipproto.Proto, src, dst string, sport, dport uint16, trimLen int) []byte {
|
||||||
u := packet.UDP6Header{
|
u := packet.UDP6Header{
|
||||||
IP6Header: packet.IP6Header{
|
IP6Header: packet.IP6Header{
|
||||||
Src: mustIP(src),
|
Src: mustIP(src),
|
||||||
@ -582,7 +589,7 @@ func raw6(proto packet.IPProto, src, dst string, sport, dport uint16, trimLen in
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func raw4(proto packet.IPProto, src, dst string, sport, dport uint16, trimLength int) []byte {
|
func raw4(proto ipproto.Proto, src, dst string, sport, dport uint16, trimLength int) []byte {
|
||||||
u := packet.UDP4Header{
|
u := packet.UDP4Header{
|
||||||
IP4Header: packet.IP4Header{
|
IP4Header: packet.IP4Header{
|
||||||
Src: mustIP(src),
|
Src: mustIP(src),
|
||||||
@ -622,7 +629,7 @@ func raw4(proto packet.IPProto, src, dst string, sport, dport uint16, trimLength
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func raw4default(proto packet.IPProto, trimLength int) []byte {
|
func raw4default(proto ipproto.Proto, trimLength int) []byte {
|
||||||
return raw4(proto, "8.8.8.8", "8.8.8.8", 53, 53, trimLength)
|
return raw4(proto, "8.8.8.8", "8.8.8.8", 53, 53, trimLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -743,7 +750,7 @@ func TestMatchesFromFilterRules(t *testing.T) {
|
|||||||
},
|
},
|
||||||
want: []Match{
|
want: []Match{
|
||||||
{
|
{
|
||||||
IPProto: []packet.IPProto{
|
IPProto: []ipproto.Proto{
|
||||||
packet.TCP,
|
packet.TCP,
|
||||||
packet.UDP,
|
packet.UDP,
|
||||||
packet.ICMPv4,
|
packet.ICMPv4,
|
||||||
@ -779,7 +786,7 @@ func TestMatchesFromFilterRules(t *testing.T) {
|
|||||||
},
|
},
|
||||||
want: []Match{
|
want: []Match{
|
||||||
{
|
{
|
||||||
IPProto: []packet.IPProto{
|
IPProto: []ipproto.Proto{
|
||||||
packet.TCP,
|
packet.TCP,
|
||||||
},
|
},
|
||||||
Dsts: []NetPortRange{
|
Dsts: []NetPortRange{
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/net/packet"
|
"tailscale.com/net/packet"
|
||||||
|
"tailscale.com/types/ipproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate go run tailscale.com/cmd/cloner --type=Match --output=match_clone.go
|
//go:generate go run tailscale.com/cmd/cloner --type=Match --output=match_clone.go
|
||||||
@ -47,7 +48,7 @@ func (npr NetPortRange) String() string {
|
|||||||
// Match matches packets from any IP address in Srcs to any ip:port in
|
// Match matches packets from any IP address in Srcs to any ip:port in
|
||||||
// Dsts.
|
// Dsts.
|
||||||
type Match struct {
|
type Match struct {
|
||||||
IPProto []packet.IPProto // required set (no default value at this layer)
|
IPProto []ipproto.Proto // required set (no default value at this layer)
|
||||||
Dsts []NetPortRange
|
Dsts []NetPortRange
|
||||||
Srcs []netaddr.IPPrefix
|
Srcs []netaddr.IPPrefix
|
||||||
}
|
}
|
||||||
@ -123,7 +124,7 @@ func ipInList(ip netaddr.IP, netlist []netaddr.IPPrefix) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func protoInList(proto packet.IPProto, valid []packet.IPProto) bool {
|
func protoInList(proto ipproto.Proto, valid []ipproto.Proto) bool {
|
||||||
for _, v := range valid {
|
for _, v := range valid {
|
||||||
if proto == v {
|
if proto == v {
|
||||||
return true
|
return true
|
||||||
|
@ -8,7 +8,7 @@ package filter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/net/packet"
|
"tailscale.com/types/ipproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Clone makes a deep copy of Match.
|
// Clone makes a deep copy of Match.
|
||||||
@ -28,7 +28,7 @@ func (src *Match) Clone() *Match {
|
|||||||
// A compilation failure here means this code must be regenerated, with command:
|
// A compilation failure here means this code must be regenerated, with command:
|
||||||
// tailscale.com/cmd/cloner -type Match
|
// tailscale.com/cmd/cloner -type Match
|
||||||
var _MatchNeedsRegeneration = Match(struct {
|
var _MatchNeedsRegeneration = Match(struct {
|
||||||
IPProto []packet.IPProto
|
IPProto []ipproto.Proto
|
||||||
Dsts []NetPortRange
|
Dsts []NetPortRange
|
||||||
Srcs []netaddr.IPPrefix
|
Srcs []netaddr.IPPrefix
|
||||||
}{})
|
}{})
|
||||||
|
@ -11,9 +11,10 @@ import (
|
|||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/net/packet"
|
"tailscale.com/net/packet"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
|
"tailscale.com/types/ipproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultProtos = []packet.IPProto{
|
var defaultProtos = []ipproto.Proto{
|
||||||
packet.TCP,
|
packet.TCP,
|
||||||
packet.UDP,
|
packet.UDP,
|
||||||
packet.ICMPv4,
|
packet.ICMPv4,
|
||||||
@ -31,12 +32,12 @@ func MatchesFromFilterRules(pf []tailcfg.FilterRule) ([]Match, error) {
|
|||||||
m := Match{}
|
m := Match{}
|
||||||
|
|
||||||
if len(r.IPProto) == 0 {
|
if len(r.IPProto) == 0 {
|
||||||
m.IPProto = append([]packet.IPProto(nil), defaultProtos...)
|
m.IPProto = append([]ipproto.Proto(nil), defaultProtos...)
|
||||||
} else {
|
} else {
|
||||||
m.IPProto = make([]packet.IPProto, 0, len(r.IPProto))
|
m.IPProto = make([]ipproto.Proto, 0, len(r.IPProto))
|
||||||
for _, n := range r.IPProto {
|
for _, n := range r.IPProto {
|
||||||
if n >= 0 && n <= 0xff {
|
if n >= 0 && n <= 0xff {
|
||||||
m.IPProto = append(m.IPProto, packet.IPProto(n))
|
m.IPProto = append(m.IPProto, ipproto.Proto(n))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.TUN)
|
|||||||
|
|
||||||
// Either a SYN or a RST came back. Remove it in either case.
|
// Either a SYN or a RST came back. Remove it in either case.
|
||||||
|
|
||||||
f := flowtrack.Tuple{Dst: pp.Src, Src: pp.Dst} // src/dst reversed
|
f := flowtrack.Tuple{Proto: pp.IPProto, Dst: pp.Src, Src: pp.Dst} // src/dst reversed
|
||||||
removed := e.removeFlow(f)
|
removed := e.removeFlow(f)
|
||||||
if removed && pp.TCPFlags&packet.TCPRst != 0 {
|
if removed && pp.TCPFlags&packet.TCPRst != 0 {
|
||||||
e.logf("open-conn-track: flow TCP %v got RST by peer", f)
|
e.logf("open-conn-track: flow TCP %v got RST by peer", f)
|
||||||
@ -107,7 +107,7 @@ func (e *userspaceEngine) trackOpenPostFilterOut(pp *packet.Parsed, t *tstun.TUN
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
flow := flowtrack.Tuple{Src: pp.Src, Dst: pp.Dst}
|
flow := flowtrack.Tuple{Proto: pp.IPProto, Src: pp.Src, Dst: pp.Dst}
|
||||||
|
|
||||||
// iOS likes to probe Apple IPs on all interfaces to check for connectivity.
|
// iOS likes to probe Apple IPs on all interfaces to check for connectivity.
|
||||||
// Don't start timers tracking those. They won't succeed anyway. Avoids log spam
|
// Don't start timers tracking those. They won't succeed anyway. Avoids log spam
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/tailscale/wireguard-go/tun/tuntest"
|
"github.com/tailscale/wireguard-go/tun/tuntest"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/net/packet"
|
"tailscale.com/net/packet"
|
||||||
|
"tailscale.com/types/ipproto"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/wgengine/filter"
|
"tailscale.com/wgengine/filter"
|
||||||
)
|
)
|
||||||
@ -106,7 +107,7 @@ func netports(netPorts ...string) (ret []filter.NetPortRange) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setfilter(logf logger.Logf, tun *TUN) {
|
func setfilter(logf logger.Logf, tun *TUN) {
|
||||||
protos := []packet.IPProto{
|
protos := []ipproto.Proto{
|
||||||
packet.TCP,
|
packet.TCP,
|
||||||
packet.UDP,
|
packet.UDP,
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user