wgengine/filter: implement a destination IP pre-filter.
Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
+37
-14
@@ -23,9 +23,22 @@ type filterState struct {
|
||||
|
||||
// Filter is a stateful packet filter.
|
||||
type Filter struct {
|
||||
logf logger.Logf
|
||||
logf logger.Logf
|
||||
// localNets is the list of IP prefixes that we know to be "local"
|
||||
// to this node. All packets coming in over tailscale must have a
|
||||
// destination within localNets, regardless of the policy filter
|
||||
// below. A nil localNets rejects all incoming traffic.
|
||||
localNets []Net
|
||||
// matches is a list of match->action rules applied to all packets
|
||||
// arriving over tailscale tunnels. Matches are checked in order,
|
||||
// and processing stops at the first matching rule. The default
|
||||
// policy if no rules match is to drop the packet.
|
||||
matches Matches
|
||||
state *filterState
|
||||
// state is the connection tracking state attached to this
|
||||
// filter. It is used to allow incoming traffic that is a response
|
||||
// to an outbound connection that this node made, even if those
|
||||
// incoming packets don't get accepted by matches above.
|
||||
state *filterState
|
||||
}
|
||||
|
||||
// Response is a verdict: either a Drop, Accept, or noVerdict skip to
|
||||
@@ -75,21 +88,23 @@ var MatchAllowAll = Matches{
|
||||
Match{[]NetPortRange{NetPortRangeAny}, []Net{NetAny}},
|
||||
}
|
||||
|
||||
// NewAllowAll returns a packet filter that accepts everything.
|
||||
func NewAllowAll(logf logger.Logf) *Filter {
|
||||
return New(MatchAllowAll, nil, logf)
|
||||
// NewAllowAll returns a packet filter that accepts everything to and
|
||||
// from localNets.
|
||||
func NewAllowAll(localNets []Net, logf logger.Logf) *Filter {
|
||||
return New(MatchAllowAll, localNets, nil, logf)
|
||||
}
|
||||
|
||||
// NewAllowNone returns a packet filter that rejects everything.
|
||||
func NewAllowNone(logf logger.Logf) *Filter {
|
||||
return New(nil, nil, logf)
|
||||
return New(nil, nil, nil, logf)
|
||||
}
|
||||
|
||||
// New creates a new packet Filter with the given Matches rules.
|
||||
// If shareStateWith is non-nil, the returned filter shares state
|
||||
// with the previous one, to enable rules to be changed at runtime
|
||||
// without breaking existing flows.
|
||||
func New(matches Matches, shareStateWith *Filter, logf logger.Logf) *Filter {
|
||||
// New creates a new packet filter. The filter enforces that incoming
|
||||
// packets must be destined to an IP in localNets, and must be allowed
|
||||
// by matches. If shareStateWith is non-nil, the returned filter
|
||||
// shares state with the previous one, to enable rules to be changed
|
||||
// at runtime without breaking existing flows.
|
||||
func New(matches Matches, localNets []Net, shareStateWith *Filter, logf logger.Logf) *Filter {
|
||||
var state *filterState
|
||||
if shareStateWith != nil {
|
||||
state = shareStateWith.state
|
||||
@@ -99,9 +114,10 @@ func New(matches Matches, shareStateWith *Filter, logf logger.Logf) *Filter {
|
||||
}
|
||||
}
|
||||
f := &Filter{
|
||||
logf: logf,
|
||||
matches: matches,
|
||||
state: state,
|
||||
logf: logf,
|
||||
matches: matches,
|
||||
localNets: localNets,
|
||||
state: state,
|
||||
}
|
||||
return f
|
||||
}
|
||||
@@ -159,6 +175,13 @@ func (f *Filter) RunOut(b []byte, q *packet.QDecode, rf RunFlags) Response {
|
||||
}
|
||||
|
||||
func (f *Filter) runIn(q *packet.QDecode) (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.
|
||||
if !ipInList(q.DstIP, f.localNets) {
|
||||
return Drop, "destination not allowed"
|
||||
}
|
||||
|
||||
switch q.IPProto {
|
||||
case packet.ICMP:
|
||||
if q.IsEchoResponse() || q.IsError() {
|
||||
|
||||
Reference in New Issue
Block a user