mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-12 05:37:32 +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:

committed by
Brad Fitzpatrick

parent
90a6fb7ffe
commit
01b90df2fa
@@ -15,16 +15,18 @@ import (
|
||||
"fmt"
|
||||
|
||||
"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 {
|
||||
Src netaddr.IPPort
|
||||
Dst netaddr.IPPort
|
||||
Proto ipproto.Proto
|
||||
Src netaddr.IPPort
|
||||
Dst netaddr.IPPort
|
||||
}
|
||||
|
||||
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.
|
||||
|
@@ -10,6 +10,7 @@ import (
|
||||
)
|
||||
|
||||
const tcpHeaderLength = 20
|
||||
const sctpHeaderLength = 12
|
||||
|
||||
// maxPacketLength is the largest length that all headers support.
|
||||
// IPv4 headers using uint16 for this forces an upper bound of 64KB.
|
||||
|
@@ -1,66 +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
|
||||
|
||||
// IPProto is an IP subprotocol as defined by the IANA protocol
|
||||
// numbers list
|
||||
// (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml),
|
||||
// or the special values Unknown or Fragment.
|
||||
type IPProto uint8
|
||||
|
||||
const (
|
||||
// Unknown represents an unknown or unsupported protocol; it's
|
||||
// deliberately the zero value. Strictly speaking the zero
|
||||
// value is IPv6 hop-by-hop extensions, but we don't support
|
||||
// those, so this is still technically correct.
|
||||
Unknown IPProto = 0x00
|
||||
|
||||
// Values from the IANA registry.
|
||||
ICMPv4 IPProto = 0x01
|
||||
IGMP IPProto = 0x02
|
||||
ICMPv6 IPProto = 0x3a
|
||||
TCP IPProto = 0x06
|
||||
UDP IPProto = 0x11
|
||||
|
||||
// TSMP is the Tailscale Message Protocol (our ICMP-ish
|
||||
// thing), an IP protocol used only between Tailscale nodes
|
||||
// (still encrypted by WireGuard) that communicates why things
|
||||
// failed, etc.
|
||||
//
|
||||
// Proto number 99 is reserved for "any private encryption
|
||||
// scheme". We never accept these from the host OS stack nor
|
||||
// send them to the host network stack. It's only used between
|
||||
// nodes.
|
||||
TSMP IPProto = 99
|
||||
|
||||
// Fragment represents any non-first IP fragment, for which we
|
||||
// don't have the sub-protocol header (and therefore can't
|
||||
// figure out what the sub-protocol is).
|
||||
//
|
||||
// 0xFF is reserved in the IANA registry, so we steal it for
|
||||
// internal use.
|
||||
Fragment IPProto = 0xFF
|
||||
)
|
||||
|
||||
func (p IPProto) String() string {
|
||||
switch p {
|
||||
case Fragment:
|
||||
return "Frag"
|
||||
case ICMPv4:
|
||||
return "ICMPv4"
|
||||
case IGMP:
|
||||
return "IGMP"
|
||||
case ICMPv6:
|
||||
return "ICMPv6"
|
||||
case UDP:
|
||||
return "UDP"
|
||||
case TCP:
|
||||
return "TCP"
|
||||
case TSMP:
|
||||
return "TSMP"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
@@ -9,6 +9,7 @@ import (
|
||||
"errors"
|
||||
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/types/ipproto"
|
||||
)
|
||||
|
||||
// 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.
|
||||
type IP4Header struct {
|
||||
IPProto IPProto
|
||||
IPProto ipproto.Proto
|
||||
IPID uint16
|
||||
Src netaddr.IP
|
||||
Dst netaddr.IP
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
"encoding/binary"
|
||||
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/types/ipproto"
|
||||
)
|
||||
|
||||
// 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.
|
||||
type IP6Header struct {
|
||||
IPProto IPProto
|
||||
IPProto ipproto.Proto
|
||||
IPID uint32 // only lower 20 bits used
|
||||
Src netaddr.IP
|
||||
Dst netaddr.IP
|
||||
|
@@ -11,9 +11,22 @@ import (
|
||||
"strings"
|
||||
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/types/ipproto"
|
||||
"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.
|
||||
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.
|
||||
IPVersion uint8
|
||||
// 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
|
||||
// valid iff IPProto == TCP || IPProto == UDP.
|
||||
Src netaddr.IPPort
|
||||
@@ -130,7 +143,7 @@ func (q *Parsed) decode4(b []byte) {
|
||||
}
|
||||
|
||||
// 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]))
|
||||
if len(b) < q.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.dataofs = q.subofs + udpHeaderLength
|
||||
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:
|
||||
// Inter-tailscale messages.
|
||||
q.dataofs = q.subofs
|
||||
@@ -244,7 +265,7 @@ func (q *Parsed) decode6(b []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
q.IPProto = IPProto(b[6])
|
||||
q.IPProto = ipproto.Proto(b[6])
|
||||
q.length = int(binary.BigEndian.Uint16(b[4:6])) + ip6HeaderLength
|
||||
if len(b) < q.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.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
|
||||
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:
|
||||
// Inter-tailscale messages.
|
||||
q.dataofs = q.subofs
|
||||
|
@@ -305,6 +305,39 @@ var ipv4TSMPDecode = Parsed{
|
||||
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) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -320,6 +353,7 @@ func TestParsedString(t *testing.T) {
|
||||
{"igmp", igmpPacketDecode, "IGMP{192.168.1.82:0 > 224.0.0.251:0}"},
|
||||
{"unknown", unknownPacketDecode, "Unknown{???}"},
|
||||
{"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 {
|
||||
@@ -357,6 +391,7 @@ func TestDecode(t *testing.T) {
|
||||
{"unknown", unknownPacketBuffer, unknownPacketDecode},
|
||||
{"invalid4", invalid4RequestBuffer, invalid4RequestDecode},
|
||||
{"ipv4_tsmp", ipv4TSMPBuffer, ipv4TSMPDecode},
|
||||
{"ipv4_sctp", sctpBuffer, sctpDecode},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@@ -17,6 +17,7 @@ import (
|
||||
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/net/flowtrack"
|
||||
"tailscale.com/types/ipproto"
|
||||
)
|
||||
|
||||
// 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
|
||||
Src netaddr.IPPort // rejected flow's src
|
||||
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
|
||||
|
||||
// MaybeBroken is whether the rejection is non-terminal (the
|
||||
@@ -57,7 +58,7 @@ type TailscaleRejectedHeader struct {
|
||||
const rejectFlagBitMaybeBroken = 0x1
|
||||
|
||||
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 {
|
||||
@@ -181,7 +182,7 @@ func (pp *Parsed) AsTailscaleRejectedHeader() (h TailscaleRejectedHeader, ok boo
|
||||
return
|
||||
}
|
||||
h = TailscaleRejectedHeader{
|
||||
Proto: IPProto(p[1]),
|
||||
Proto: ipproto.Proto(p[1]),
|
||||
Reason: TailscaleRejectReason(p[2]),
|
||||
IPSrc: pp.Src.IP,
|
||||
IPDst: pp.Dst.IP,
|
||||
|
Reference in New Issue
Block a user