tailcfg: add FilterRule.IPProto

Updates #1516

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/1550/head
Brad Fitzpatrick 4 years ago committed by Brad Fitzpatrick
parent 32562a82a9
commit 90a6fb7ffe

@ -35,7 +35,8 @@ import (
// 10: 2021-01-17: client understands MapResponse.PeerSeenChange // 10: 2021-01-17: client understands MapResponse.PeerSeenChange
// 11: 2021-03-03: client understands IPv6, multiple default routes, and goroutine dumping // 11: 2021-03-03: client understands IPv6, multiple default routes, and goroutine dumping
// 12: 2021-03-04: client understands PingRequest // 12: 2021-03-04: client understands PingRequest
const CurrentMapRequestVersion = 12 // 13: 2021-03-19: client understands FilterRule.IPProto
const CurrentMapRequestVersion = 13
type StableID string type StableID string
@ -693,6 +694,17 @@ type FilterRule struct {
// DstPorts are the port ranges to allow once a source IP // DstPorts are the port ranges to allow once a source IP
// matches (is in the CIDR described by SrcIPs & SrcBits). // matches (is in the CIDR described by SrcIPs & SrcBits).
DstPorts []NetPortRange DstPorts []NetPortRange
// IPProto are the IP protocol numbers to match.
//
// As a special case, nil or empty means TCP, UDP, and ICMP.
//
// Numbers outside the uint8 range (below 0 or above 255) are
// reserved for Tailscale's use. Unknown ones are ignored.
//
// Depending on the IPProto values, DstPorts may or may not be
// used.
IPProto []int `json:",omitempty"`
} }
var FilterAllowAll = []FilterRule{ var FilterAllowAll = []FilterRule{

@ -182,6 +182,7 @@ func matchesFamily(ms matches, keep func(netaddr.IP) bool) matches {
var ret matches var ret matches
for _, m := range ms { for _, m := range ms {
var retm Match var retm Match
retm.IPProto = m.IPProto
for _, src := range m.Srcs { for _, src := range m.Srcs {
if keep(src.IP) { if keep(src.IP) {
retm.Srcs = append(retm.Srcs, src) retm.Srcs = append(retm.Srcs, src)

@ -7,6 +7,7 @@ package filter
import ( import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"reflect"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
@ -16,19 +17,27 @@ import (
"inet.af/netaddr" "inet.af/netaddr"
"tailscale.com/net/packet" "tailscale.com/net/packet"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"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 {
return Match{
IPProto: defaultProtos,
Srcs: srcs,
Dsts: dsts,
}
}
matches := []Match{ matches := []Match{
{Srcs: nets("8.1.1.1", "8.2.2.2"), Dsts: 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")),
{Srcs: nets("8.1.1.1", "8.2.2.2"), Dsts: netports("5.6.7.8:27-28")}, m(nets("8.1.1.1", "8.2.2.2"), netports("5.6.7.8:27-28")),
{Srcs: nets("2.2.2.2"), Dsts: netports("8.1.1.1:22")}, m(nets("2.2.2.2"), netports("8.1.1.1:22")),
{Srcs: nets("0.0.0.0/0"), Dsts: netports("100.122.98.50:*")}, m(nets("0.0.0.0/0"), netports("100.122.98.50:*")),
{Srcs: nets("0.0.0.0/0"), Dsts: netports("0.0.0.0/0:443")}, m(nets("0.0.0.0/0"), netports("0.0.0.0/0:443")),
{Srcs: nets("153.1.1.1", "153.1.1.2", "153.3.3.3"), Dsts: netports("1.2.3.4:999")}, m(nets("153.1.1.1", "153.1.1.2", "153.3.3.3"), netports("1.2.3.4:999")),
{Srcs: nets("::1", "::2"), Dsts: netports("2001::1:22", "2001::2:22")}, m(nets("::1", "::2"), netports("2001::1:22", "2001::2:22")),
{Srcs: nets("::/0"), Dsts: netports("::/0:443")}, m(nets("::/0"), netports("::/0:443")),
} }
// Expects traffic to 100.122.98.50, 1.2.3.4, 5.6.7.8, // Expects traffic to 100.122.98.50, 1.2.3.4, 5.6.7.8,
@ -89,6 +98,9 @@ func TestFilter(t *testing.T) {
// unexpected dst IP. // unexpected dst IP.
{Drop, parsed(packet.TCP, "8.1.1.1", "16.32.48.64", 0, 443)}, {Drop, parsed(packet.TCP, "8.1.1.1", "16.32.48.64", 0, 443)},
{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
{Drop, parsed(132 /* SCTP */, "8.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
@ -707,3 +719,91 @@ func netports(netPorts ...string) (ret []NetPortRange) {
} }
return ret return ret
} }
func TestMatchesFromFilterRules(t *testing.T) {
tests := []struct {
name string
in []tailcfg.FilterRule
want []Match
}{
{
name: "empty",
want: []Match{},
},
{
name: "implicit_protos",
in: []tailcfg.FilterRule{
{
SrcIPs: []string{"100.64.1.1"},
DstPorts: []tailcfg.NetPortRange{{
IP: "*",
Ports: tailcfg.PortRange{First: 22, Last: 22},
}},
},
},
want: []Match{
{
IPProto: []packet.IPProto{
packet.TCP,
packet.UDP,
packet.ICMPv4,
packet.ICMPv6,
},
Dsts: []NetPortRange{
{
Net: netaddr.MustParseIPPrefix("0.0.0.0/0"),
Ports: PortRange{22, 22},
},
{
Net: netaddr.MustParseIPPrefix("::0/0"),
Ports: PortRange{22, 22},
},
},
Srcs: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("100.64.1.1/32"),
},
},
},
},
{
name: "explicit_protos",
in: []tailcfg.FilterRule{
{
IPProto: []int{int(packet.TCP)},
SrcIPs: []string{"100.64.1.1"},
DstPorts: []tailcfg.NetPortRange{{
IP: "1.2.0.0/16",
Ports: tailcfg.PortRange{First: 22, Last: 22},
}},
},
},
want: []Match{
{
IPProto: []packet.IPProto{
packet.TCP,
},
Dsts: []NetPortRange{
{
Net: netaddr.MustParseIPPrefix("1.2.0.0/16"),
Ports: PortRange{22, 22},
},
},
Srcs: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("100.64.1.1/32"),
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := MatchesFromFilterRules(tt.in)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("wrong\n got: %v\nwant: %v\n", got, tt.want)
}
})
}
}

@ -47,11 +47,13 @@ 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 {
Dsts []NetPortRange IPProto []packet.IPProto // required set (no default value at this layer)
Srcs []netaddr.IPPrefix Dsts []NetPortRange
Srcs []netaddr.IPPrefix
} }
func (m Match) String() string { func (m Match) String() string {
// TODO(bradfitz): use strings.Builder, add String tests
srcs := []string{} srcs := []string{}
for _, src := range m.Srcs { for _, src := range m.Srcs {
srcs = append(srcs, src.String()) srcs = append(srcs, src.String())
@ -72,13 +74,16 @@ func (m Match) String() string {
} else { } else {
ds = "[" + strings.Join(dsts, ",") + "]" ds = "[" + strings.Join(dsts, ",") + "]"
} }
return fmt.Sprintf("%v=>%v", ss, ds) return fmt.Sprintf("%v%v=>%v", m.IPProto, ss, ds)
} }
type matches []Match type matches []Match
func (ms matches) match(q *packet.Parsed) bool { func (ms matches) match(q *packet.Parsed) bool {
for _, m := range ms { for _, m := range ms {
if !protoInList(q.IPProto, m.IPProto) {
continue
}
if !ipInList(q.Src.IP, m.Srcs) { if !ipInList(q.Src.IP, m.Srcs) {
continue continue
} }
@ -117,3 +122,12 @@ func ipInList(ip netaddr.IP, netlist []netaddr.IPPrefix) bool {
} }
return false return false
} }
func protoInList(proto packet.IPProto, valid []packet.IPProto) bool {
for _, v := range valid {
if proto == v {
return true
}
}
return false
}

@ -8,6 +8,7 @@ package filter
import ( import (
"inet.af/netaddr" "inet.af/netaddr"
"tailscale.com/net/packet"
) )
// Clone makes a deep copy of Match. // Clone makes a deep copy of Match.
@ -18,6 +19,7 @@ func (src *Match) Clone() *Match {
} }
dst := new(Match) dst := new(Match)
*dst = *src *dst = *src
dst.IPProto = append(src.IPProto[:0:0], src.IPProto...)
dst.Dsts = append(src.Dsts[:0:0], src.Dsts...) dst.Dsts = append(src.Dsts[:0:0], src.Dsts...)
dst.Srcs = append(src.Srcs[:0:0], src.Srcs...) dst.Srcs = append(src.Srcs[:0:0], src.Srcs...)
return dst return dst
@ -26,6 +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 {
Dsts []NetPortRange IPProto []packet.IPProto
Srcs []netaddr.IPPrefix Dsts []NetPortRange
Srcs []netaddr.IPPrefix
}{}) }{})

@ -9,9 +9,17 @@ import (
"strings" "strings"
"inet.af/netaddr" "inet.af/netaddr"
"tailscale.com/net/packet"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
) )
var defaultProtos = []packet.IPProto{
packet.TCP,
packet.UDP,
packet.ICMPv4,
packet.ICMPv6,
}
// MatchesFromFilterRules converts tailcfg FilterRules into Matches. // MatchesFromFilterRules converts tailcfg FilterRules into Matches.
// If an error is returned, the Matches result is still valid, // If an error is returned, the Matches result is still valid,
// containing the rules that were successfully converted. // containing the rules that were successfully converted.
@ -22,6 +30,17 @@ func MatchesFromFilterRules(pf []tailcfg.FilterRule) ([]Match, error) {
for _, r := range pf { for _, r := range pf {
m := Match{} m := Match{}
if len(r.IPProto) == 0 {
m.IPProto = append([]packet.IPProto(nil), defaultProtos...)
} else {
m.IPProto = make([]packet.IPProto, 0, len(r.IPProto))
for _, n := range r.IPProto {
if n >= 0 && n <= 0xff {
m.IPProto = append(m.IPProto, packet.IPProto(n))
}
}
}
for i, s := range r.SrcIPs { for i, s := range r.SrcIPs {
var bits *int var bits *int
if len(r.SrcBits) > i { if len(r.SrcBits) > i {

@ -106,9 +106,13 @@ func netports(netPorts ...string) (ret []filter.NetPortRange) {
} }
func setfilter(logf logger.Logf, tun *TUN) { func setfilter(logf logger.Logf, tun *TUN) {
protos := []packet.IPProto{
packet.TCP,
packet.UDP,
}
matches := []filter.Match{ matches := []filter.Match{
{Srcs: nets("5.6.7.8"), Dsts: netports("1.2.3.4:89-90")}, {IPProto: protos, Srcs: nets("5.6.7.8"), Dsts: netports("1.2.3.4:89-90")},
{Srcs: nets("1.2.3.4"), Dsts: netports("5.6.7.8:98")}, {IPProto: protos, Srcs: nets("1.2.3.4"), Dsts: netports("5.6.7.8:98")},
} }
var sb netaddr.IPSetBuilder var sb netaddr.IPSetBuilder
sb.AddPrefix(netaddr.MustParseIPPrefix("1.2.0.0/16")) sb.AddPrefix(netaddr.MustParseIPPrefix("1.2.0.0/16"))

Loading…
Cancel
Save