wgengine, net/packet, cmd/tailscale: add ICMP echo

Updates tailscale/corp#754

Signed-off-by: James Tucker <james@tailscale.com>
This commit is contained in:
James Tucker
2022-04-21 18:49:01 -07:00
committed by James Tucker
parent 66f9292835
commit ae483d3446
14 changed files with 215 additions and 45 deletions

29
net/packet/icmp.go Normal file
View File

@@ -0,0 +1,29 @@
// Copyright (c) 2022 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
import (
crand "crypto/rand"
"encoding/binary"
)
// ICMPEchoPayload generates a new random ID/Sequence pair, and returns a uint32
// derived from them, along with the id, sequence and given payload in a buffer.
// It returns an error if the random source could not be read.
func ICMPEchoPayload(payload []byte) (idSeq uint32, buf []byte) {
buf = make([]byte, len(payload)+4)
// make a completely random id/sequence combo, which is very unlikely to
// collide with a running ping sequence on the host system. Errors are
// ignored, that would result in collisions, but errors reading from the
// random device are rare, and will cause this process universe to soon end.
crand.Read(buf[:4])
idSeq = binary.LittleEndian.Uint32(buf)
copy(buf[4:], payload)
return
}

View File

@@ -434,6 +434,29 @@ func (q *Parsed) IsEchoResponse() bool {
}
}
// EchoIDSeq extracts the identifier/sequence bytes from an ICMP Echo response,
// and returns them as a uint32, used to lookup internally routed ICMP echo
// responses. This function is intentionally lightweight as it is called on
// every incoming ICMP packet.
func (q *Parsed) EchoIDSeq() uint32 {
switch q.IPProto {
case ipproto.ICMPv4:
offset := ip4HeaderLength + icmp4HeaderLength
if len(q.b) < offset+4 {
return 0
}
return binary.LittleEndian.Uint32(q.b[offset:])
case ipproto.ICMPv6:
offset := ip6HeaderLength + icmp6HeaderLength
if len(q.b) < offset+4 {
return 0
}
return binary.LittleEndian.Uint32(q.b[offset:])
default:
return 0
}
}
func Hexdump(b []byte) string {
out := new(strings.Builder)
for i := 0; i < len(b); i += 16 {

View File

@@ -151,6 +151,11 @@ type Wrapper struct {
// OnTSMPPongReceived, if non-nil, is called whenever a TSMP pong arrives.
OnTSMPPongReceived func(packet.TSMPPongReply)
// OnICMPEchoResponseReceived, if non-nil, is called whenever a ICMP echo response
// arrives. If the packet is to be handled internally this returns true,
// false otherwise.
OnICMPEchoResponseReceived func(*packet.Parsed) bool
// PeerAPIPort, if non-nil, returns the peerapi port that's
// running for the given IP address.
PeerAPIPort func(netaddr.IP) (port uint16, ok bool)
@@ -575,6 +580,14 @@ func (t *Wrapper) filterIn(buf []byte) filter.Response {
}
}
if p.IsEchoResponse() {
if f := t.OnICMPEchoResponseReceived; f != nil && f(p) {
// Note: this looks dropped in metrics, even though it was
// handled internally.
return filter.DropSilently
}
}
// Issue 1526 workaround: if we see disco packets over
// Tailscale from ourselves, then drop them, as that shouldn't
// happen unless a networking stack is confused, as it seems