wgengine: don't lose filter state on filter reconfig.
We were abandoning the UDP port LRU every time we got a new packet filter from tailcontrol, which caused return packets to suddenly stop arriving.pull/220/head
parent
4336de0d98
commit
f53e78e0d5
|
@ -320,7 +320,7 @@ func (b *LocalBackend) updateFilter(netMap *controlclient.NetworkMap) {
|
||||||
} else {
|
} else {
|
||||||
b.logf("netmap packet filter: (suppressed)\n")
|
b.logf("netmap packet filter: (suppressed)\n")
|
||||||
}
|
}
|
||||||
b.e.SetFilter(filter.New(netMap.PacketFilter))
|
b.e.SetFilter(filter.New(netMap.PacketFilter, b.e.GetFilter()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,14 @@ import (
|
||||||
"tailscale.com/wgengine/packet"
|
"tailscale.com/wgengine/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type filterState struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
lru *lru.Cache
|
||||||
|
}
|
||||||
|
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
matches Matches
|
matches Matches
|
||||||
|
state *filterState
|
||||||
udpMu sync.Mutex
|
|
||||||
udplru *lru.Cache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Response int
|
type Response int
|
||||||
|
@ -66,17 +69,25 @@ var MatchAllowAll = Matches{
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAllowAll() *Filter {
|
func NewAllowAll() *Filter {
|
||||||
return New(MatchAllowAll)
|
return New(MatchAllowAll, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAllowNone() *Filter {
|
func NewAllowNone() *Filter {
|
||||||
return New(nil)
|
return New(nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(matches Matches) *Filter {
|
func New(matches Matches, shareStateWith *Filter) *Filter {
|
||||||
|
var state *filterState
|
||||||
|
if shareStateWith != nil {
|
||||||
|
state = shareStateWith.state
|
||||||
|
} else {
|
||||||
|
state = &filterState{
|
||||||
|
lru: lru.New(LRU_MAX),
|
||||||
|
}
|
||||||
|
}
|
||||||
f := &Filter{
|
f := &Filter{
|
||||||
matches: matches,
|
matches: matches,
|
||||||
udplru: lru.New(LRU_MAX),
|
state: state,
|
||||||
}
|
}
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
@ -144,6 +155,10 @@ func (f *Filter) runIn(q *packet.QDecode) (r Response, why string) {
|
||||||
switch q.IPProto {
|
switch q.IPProto {
|
||||||
case packet.ICMP:
|
case packet.ICMP:
|
||||||
// If any port is open to an IP, allow ICMP to it.
|
// If any port is open to an IP, allow ICMP to it.
|
||||||
|
// TODO(apenwarr): allow ICMP packets on existing sessions.
|
||||||
|
// Right now ICMP Echo Response doesn't always work, and
|
||||||
|
// probably important ICMP responses on TCP sessions
|
||||||
|
// also get blocked.
|
||||||
if matchIPWithoutPorts(f.matches, q) {
|
if matchIPWithoutPorts(f.matches, q) {
|
||||||
return Accept, "icmp ok"
|
return Accept, "icmp ok"
|
||||||
}
|
}
|
||||||
|
@ -165,9 +180,9 @@ func (f *Filter) runIn(q *packet.QDecode) (r Response, why string) {
|
||||||
case packet.UDP:
|
case packet.UDP:
|
||||||
t := tuple{q.SrcIP, q.DstIP, q.SrcPort, q.DstPort}
|
t := tuple{q.SrcIP, q.DstIP, q.SrcPort, q.DstPort}
|
||||||
|
|
||||||
f.udpMu.Lock()
|
f.state.mu.Lock()
|
||||||
_, ok := f.udplru.Get(t)
|
_, ok := f.state.lru.Get(t)
|
||||||
f.udpMu.Unlock()
|
f.state.mu.Unlock()
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
return Accept, "udp cached"
|
return Accept, "udp cached"
|
||||||
|
@ -182,12 +197,13 @@ func (f *Filter) runIn(q *packet.QDecode) (r Response, why string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Filter) runOut(q *packet.QDecode) (r Response, why string) {
|
func (f *Filter) runOut(q *packet.QDecode) (r Response, why string) {
|
||||||
|
// TODO(apenwarr): create sessions on ICMP Echo Request too.
|
||||||
if q.IPProto == packet.UDP {
|
if q.IPProto == packet.UDP {
|
||||||
t := tuple{q.DstIP, q.SrcIP, q.DstPort, q.SrcPort}
|
t := tuple{q.DstIP, q.SrcIP, q.DstPort, q.SrcPort}
|
||||||
|
|
||||||
f.udpMu.Lock()
|
f.state.mu.Lock()
|
||||||
f.udplru.Add(t, t)
|
f.state.lru.Add(t, t)
|
||||||
f.udpMu.Unlock()
|
f.state.mu.Unlock()
|
||||||
}
|
}
|
||||||
return Accept, "ok out"
|
return Accept, "ok out"
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ func TestFilter(t *testing.T) {
|
||||||
{SrcIPs: []IP{0}, DstPorts: ippr(0, 443, 443)},
|
{SrcIPs: []IP{0}, DstPorts: ippr(0, 443, 443)},
|
||||||
{SrcIPs: []IP{0x99010101, 0x99010102, 0x99030303}, DstPorts: ippr(0x01020304, 999, 999)},
|
{SrcIPs: []IP{0x99010101, 0x99010102, 0x99030303}, DstPorts: ippr(0x01020304, 999, 999)},
|
||||||
}
|
}
|
||||||
acl := New(mm)
|
acl := New(mm, nil)
|
||||||
|
|
||||||
for _, ent := range []Matches{Matches{mm[0]}, mm} {
|
for _, ent := range []Matches{Matches{mm[0]}, mm} {
|
||||||
b, err := json.Marshal(ent)
|
b, err := json.Marshal(ent)
|
||||||
|
|
|
@ -36,6 +36,7 @@ type userspaceEngine struct {
|
||||||
router Router
|
router Router
|
||||||
magicConn *magicsock.Conn
|
magicConn *magicsock.Conn
|
||||||
linkMon *monitor.Mon
|
linkMon *monitor.Mon
|
||||||
|
filt *filter.Filter
|
||||||
|
|
||||||
wgLock sync.Mutex // serializes all wgdev operations
|
wgLock sync.Mutex // serializes all wgdev operations
|
||||||
lastReconfig string
|
lastReconfig string
|
||||||
|
@ -380,7 +381,13 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *userspaceEngine) GetFilter() *filter.Filter {
|
||||||
|
return e.filt
|
||||||
|
}
|
||||||
|
|
||||||
func (e *userspaceEngine) SetFilter(filt *filter.Filter) {
|
func (e *userspaceEngine) SetFilter(filt *filter.Filter) {
|
||||||
|
e.filt = filt
|
||||||
|
|
||||||
var filtin, filtout func(b []byte) device.FilterResult
|
var filtin, filtout func(b []byte) device.FilterResult
|
||||||
if filt == nil {
|
if filt == nil {
|
||||||
e.logf("wgengine: nil filter provided; no access restrictions.\n")
|
e.logf("wgengine: nil filter provided; no access restrictions.\n")
|
||||||
|
|
|
@ -63,6 +63,11 @@ func (e *watchdogEngine) watchdog(name string, fn func()) {
|
||||||
func (e *watchdogEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string) error {
|
func (e *watchdogEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string) error {
|
||||||
return e.watchdogErr("Reconfig", func() error { return e.wrap.Reconfig(cfg, dnsDomains) })
|
return e.watchdogErr("Reconfig", func() error { return e.wrap.Reconfig(cfg, dnsDomains) })
|
||||||
}
|
}
|
||||||
|
func (e *watchdogEngine) GetFilter() *filter.Filter {
|
||||||
|
var x *filter.Filter
|
||||||
|
e.watchdog("GetFilter", func() { x = e.wrap.GetFilter() })
|
||||||
|
return x
|
||||||
|
}
|
||||||
func (e *watchdogEngine) SetFilter(filt *filter.Filter) {
|
func (e *watchdogEngine) SetFilter(filt *filter.Filter) {
|
||||||
e.watchdog("SetFilter", func() { e.wrap.SetFilter(filt) })
|
e.watchdog("SetFilter", func() { e.wrap.SetFilter(filt) })
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,6 +101,9 @@ type Engine interface {
|
||||||
// sends an updated network map.
|
// sends an updated network map.
|
||||||
Reconfig(cfg *wgcfg.Config, dnsDomains []string) error
|
Reconfig(cfg *wgcfg.Config, dnsDomains []string) error
|
||||||
|
|
||||||
|
// GetFilter returns the current packet filter, if any
|
||||||
|
GetFilter() *filter.Filter
|
||||||
|
|
||||||
// SetFilter updates the packet filter.
|
// SetFilter updates the packet filter.
|
||||||
SetFilter(*filter.Filter)
|
SetFilter(*filter.Filter)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue