net/packet: generate ICMP destination unreachable packets

Updates tailscale/corp#34257
Signed-off-by: Fran Bull <fran@tailscale.com>
This commit is contained in:
Fran Bull
2026-07-20 07:33:00 -07:00
committed by franbull
parent f65372c9ba
commit 8df4816be4
4 changed files with 261 additions and 0 deletions
+62
View File
@@ -7,6 +7,7 @@ import (
crand "crypto/rand"
"encoding/binary"
"net/netip"
)
// ICMPEchoPayload generates a new random ID/Sequence pair, and returns a uint32
@@ -26,3 +27,64 @@ func ICMPEchoPayload(payload []byte) (idSeq uint32, buf []byte) {
return
}
// icmpDestUnreachableUnusedLen is the number of unused bytes that both ICMPv4
// and ICMPv6 "Destination Unreachable" messages have between header and bits
// from the invoking packet.
const icmpDestUnreachableUnusedLen = 4
const minIPv6MTU = 1280 // RFC 2460, section 5
// GenerateICMPHostUnreachable builds an ICMPv4 or ICMPv6 "Destination
// Unreachable" message according to RFC 792 and RFC 4443, section 3.1.
// from and to must both be of the same address family; otherwise
// GenerateICMPHostUnreachable returns nil.
func GenerateICMPHostUnreachable(from, to netip.Addr, invoking *Parsed) []byte {
buf := invoking.Buffer()
switch {
case from.Is4() && to.Is4():
// RFC 792
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Type | Code | Checksum |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | unused |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Internet Header + 64 bits of Original Data Datagram |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
ipHeaderLen := len(buf) - len(invoking.Transport())
return Generate(ICMP4Header{
IP4Header: IP4Header{Src: from, Dst: to},
Type: ICMP4Unreachable,
Code: ICMP4HostUnreachable,
}, icmpDestUnreachablePayload(buf, ipHeaderLen+8))
case from.Is6() && to.Is6():
// RFC 4443, section 3.1
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Type | Code | Checksum |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Unused |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | As much of invoking packet |
// + as possible without the ICMPv6 packet +
// | exceeding the minimum IPv6 MTU |
maxOrig := minIPv6MTU - (ICMP6Header{}).Len() - icmpDestUnreachableUnusedLen
return Generate(ICMP6Header{
IP6Header: IP6Header{Src: from, Dst: to},
Type: ICMP6Unreachable,
Code: ICMP6AddressUnreachable,
}, icmpDestUnreachablePayload(buf, maxOrig))
default:
return nil
}
}
// icmpDestUnreachablePayload composes the payload for an ICMP "Destination Unreachable" packet.
func icmpDestUnreachablePayload(orig []byte, maxOrig int) []byte {
n := min(len(orig), maxOrig)
payload := make([]byte, icmpDestUnreachableUnusedLen+n)
copy(payload[icmpDestUnreachableUnusedLen:], orig[:n])
return payload
}