wgengine/filter: allow ICMP response packets.

Longer term, we should probably update the packet filter to be fully
stateful, for both TCP and ICMP. That is, only ICMP packets related to
a session *we* initiated should be allowed back in. But this is
reasonably secure for now, since wireguard is already trimming most
traffic. The current code would not protect against eg. Ping-of-Death style
attacks from VPN nodes.

Fixes tailscale/tailscale#290.

Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>
This commit is contained in:
Avery Pennarun
2020-04-29 03:53:32 -04:00
parent dbc1f71e5d
commit 85e675940d
2 changed files with 34 additions and 9 deletions

View File

@@ -166,12 +166,15 @@ func (f *Filter) RunOut(b []byte, q *packet.QDecode, rf RunFlags) Response {
func (f *Filter) runIn(q *packet.QDecode) (r Response, why string) {
switch q.IPProto {
case packet.ICMP:
// 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 q.IsEchoResponse() || q.IsError() {
// ICMP responses are allowed.
// TODO(apenwarr): consider using conntrack state.
// We could choose to reject all packets that aren't
// related to an existing ICMP-Echo, TCP, or UDP
// session.
return Accept, "icmp response ok"
} else if matchIPWithoutPorts(f.matches, q) {
// If any port is open to an IP, allow ICMP to it.
return Accept, "icmp ok"
}
case packet.TCP:
@@ -209,7 +212,6 @@ func (f *Filter) runIn(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 {
t := tuple{q.DstIP, q.SrcIP, q.DstPort, q.SrcPort}
var ti interface{} = t // allocate once, rather than twice inside mutex