mirror of
https://github.com/slackhq/nebula.git
synced 2026-07-01 02:50:29 +02:00
add IPv6 reject packet generation (#1766)
* add IPv6 reject packet generation (ICMPv6 Destination Unreachable and TCP RST) * use ICMPv6 code 1 (administratively prohibited) and cap body at 1000 bytes * cleanup, use ICMP error code 13 for ipv4 * better docs * cleanup
This commit is contained in:
+246
-19
@@ -4,26 +4,50 @@ import (
|
||||
"encoding/binary"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
const (
|
||||
// Need 96 bytes for the largest reject packet:
|
||||
// MaxIPv4RejectPacketSize is the largest IPv4 reject packet:
|
||||
// - 20 byte ipv4 header
|
||||
// - 8 byte icmpv4 header
|
||||
// - 68 byte body (60 byte max orig ipv4 header + 8 byte orig icmpv4 header)
|
||||
MaxRejectPacketSize = ipv4.HeaderLen + 8 + 60 + 8
|
||||
maxIPv4RejectPacketSize = ipv4.HeaderLen + 8 + 60 + 8
|
||||
|
||||
// MaxRejectPacketSize is sized for the largest possible reject packet (IPv6):
|
||||
// - 40 byte ipv6 header
|
||||
// - 8 byte icmpv6 header
|
||||
// - up to 1000 byte body (original packet, possibly truncated. We want to stay
|
||||
// under the MTU with Nebula overhead included)
|
||||
maxIPv6RejectPacketSize = ipv6.HeaderLen + 8 + 1000
|
||||
|
||||
MaxRejectPacketSize = maxIPv6RejectPacketSize
|
||||
)
|
||||
|
||||
func CreateRejectPacket(packet []byte, out []byte) []byte {
|
||||
if len(packet) < ipv4.HeaderLen || int(packet[0]>>4) != ipv4.Version {
|
||||
if len(packet) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch packet[9] {
|
||||
case 6: // tcp
|
||||
return ipv4CreateRejectTCPPacket(packet, out)
|
||||
version := int(packet[0] >> 4)
|
||||
switch version {
|
||||
case ipv4.Version:
|
||||
if len(packet) < ipv4.HeaderLen {
|
||||
return nil
|
||||
}
|
||||
switch packet[9] {
|
||||
case 6: // tcp
|
||||
return ipv4CreateRejectTCPPacket(packet, out)
|
||||
default:
|
||||
return ipv4CreateRejectICMPPacket(packet, out)
|
||||
}
|
||||
case ipv6.Version:
|
||||
if len(packet) < ipv6.HeaderLen {
|
||||
return nil
|
||||
}
|
||||
return ipv6CreateRejectPacket(packet, out)
|
||||
default:
|
||||
return ipv4CreateRejectICMPPacket(packet, out)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,10 +60,7 @@ func ipv4CreateRejectICMPPacket(packet []byte, out []byte) []byte {
|
||||
}
|
||||
|
||||
// ICMP reply includes original header and first 8 bytes of the packet
|
||||
packetLen := len(packet)
|
||||
if packetLen > ihl+8 {
|
||||
packetLen = ihl + 8
|
||||
}
|
||||
packetLen := min(len(packet), ihl+8)
|
||||
|
||||
outLen := ipv4.HeaderLen + 8 + packetLen
|
||||
if outLen > cap(out) {
|
||||
@@ -71,14 +92,14 @@ func ipv4CreateRejectICMPPacket(packet []byte, out []byte) []byte {
|
||||
|
||||
// ICMP Destination Unreachable
|
||||
icmpOut := out[ipv4.HeaderLen:]
|
||||
icmpOut[0] = 3 // type (Destination unreachable)
|
||||
icmpOut[1] = 3 // code (Port unreachable error)
|
||||
icmpOut[2] = 0 // checksum
|
||||
icmpOut[3] = 0 // .
|
||||
icmpOut[4] = 0 // unused
|
||||
icmpOut[5] = 0 // .
|
||||
icmpOut[6] = 0 // .
|
||||
icmpOut[7] = 0 // .
|
||||
icmpOut[0] = 3 // type (Destination unreachable)
|
||||
icmpOut[1] = 13 // code (Communication administratively prohibited)
|
||||
icmpOut[2] = 0 // checksum
|
||||
icmpOut[3] = 0 // .
|
||||
icmpOut[4] = 0 // unused
|
||||
icmpOut[5] = 0 // .
|
||||
icmpOut[6] = 0 // .
|
||||
icmpOut[7] = 0 // .
|
||||
|
||||
// Copy original IP header and first 8 bytes as body
|
||||
copy(icmpOut[8:], packet[:packetLen])
|
||||
@@ -165,6 +186,197 @@ func ipv4CreateRejectTCPPacket(packet []byte, out []byte) []byte {
|
||||
return out
|
||||
}
|
||||
|
||||
func ipv6CreateRejectPacket(packet []byte, out []byte) []byte {
|
||||
proto := ipv6FindUpperProtocol(packet)
|
||||
switch proto {
|
||||
case 6: // tcp
|
||||
return ipv6CreateRejectTCPPacket(packet, out)
|
||||
default:
|
||||
return ipv6CreateRejectICMPPacket(packet, out)
|
||||
}
|
||||
}
|
||||
|
||||
func ipv6FindUpperProtocol(packet []byte) uint8 {
|
||||
nextHeader := packet[6]
|
||||
offset := ipv6.HeaderLen
|
||||
|
||||
for {
|
||||
switch nextHeader {
|
||||
case 0, 43, 60: // Hop-by-Hop, Routing, Destination
|
||||
if len(packet) < offset+2 {
|
||||
return nextHeader
|
||||
}
|
||||
nextHeader = packet[offset]
|
||||
offset += int(packet[offset+1]+1) << 3
|
||||
|
||||
case 44: // Fragment
|
||||
if len(packet) < offset+8 {
|
||||
return nextHeader
|
||||
}
|
||||
nextHeader = packet[offset]
|
||||
offset += 8
|
||||
|
||||
case 51: // AH
|
||||
if len(packet) < offset+2 {
|
||||
return nextHeader
|
||||
}
|
||||
nextHeader = packet[offset]
|
||||
offset += int(packet[offset+1]+2) << 2
|
||||
|
||||
default:
|
||||
return nextHeader
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ipv6CreateRejectICMPPacket(packet []byte, out []byte) []byte {
|
||||
// Include as much of the original packet as possible, up to 1000 bytes,
|
||||
// so the response fits comfortably within any tunnel MTU.
|
||||
packetLen := min(len(packet), 1000)
|
||||
|
||||
outLen := ipv6.HeaderLen + 8 + packetLen
|
||||
if outLen > cap(out) {
|
||||
return nil
|
||||
}
|
||||
|
||||
out = out[:outLen]
|
||||
|
||||
// IPv6 header
|
||||
ipHdr := out[0:ipv6.HeaderLen]
|
||||
ipHdr[0] = ipv6.Version << 4 // version, traffic class (high bits)
|
||||
ipHdr[1] = 0 // traffic class (low bits), flow label (high bits)
|
||||
ipHdr[2] = 0 // flow label
|
||||
ipHdr[3] = 0 // flow label
|
||||
|
||||
payloadLen := uint16(outLen - ipv6.HeaderLen)
|
||||
binary.BigEndian.PutUint16(ipHdr[4:], payloadLen) // payload length
|
||||
ipHdr[6] = 58 // next header (ICMPv6)
|
||||
ipHdr[7] = 64 // hop limit
|
||||
|
||||
// Swap dest / src IPs (each 16 bytes, src at 8, dst at 24)
|
||||
copy(ipHdr[8:24], packet[24:40])
|
||||
copy(ipHdr[24:40], packet[8:24])
|
||||
|
||||
// ICMPv6 Destination Unreachable
|
||||
icmpOut := out[ipv6.HeaderLen:]
|
||||
icmpOut[0] = 1 // type (Destination Unreachable)
|
||||
icmpOut[1] = 1 // code (Communication with destination administratively prohibited)
|
||||
icmpOut[2] = 0 // checksum
|
||||
icmpOut[3] = 0 // .
|
||||
icmpOut[4] = 0 // unused
|
||||
icmpOut[5] = 0 // .
|
||||
icmpOut[6] = 0 // .
|
||||
icmpOut[7] = 0 // .
|
||||
|
||||
copy(icmpOut[8:], packet[:packetLen])
|
||||
|
||||
// ICMPv6 checksum uses a pseudo-header
|
||||
csum := ipv6PseudoheaderChecksum(ipHdr[8:24], ipHdr[24:40], 58, uint32(payloadLen))
|
||||
binary.BigEndian.PutUint16(icmpOut[2:], tcpipChecksum(icmpOut, csum))
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func ipv6CreateRejectTCPPacket(packet []byte, out []byte) []byte {
|
||||
const tcpLen = 20
|
||||
|
||||
offset := ipv6FindUpperProtocolOffset(packet)
|
||||
if len(packet) < offset+tcpLen {
|
||||
return nil
|
||||
}
|
||||
|
||||
outLen := ipv6.HeaderLen + tcpLen
|
||||
if outLen > cap(out) {
|
||||
return nil
|
||||
}
|
||||
|
||||
out = out[:outLen]
|
||||
|
||||
// IPv6 header
|
||||
ipHdr := out[0:ipv6.HeaderLen]
|
||||
ipHdr[0] = ipv6.Version << 4 // version, traffic class (high bits)
|
||||
ipHdr[1] = 0 // traffic class (low bits), flow label (high bits)
|
||||
ipHdr[2] = 0 // flow label
|
||||
ipHdr[3] = 0 // flow label
|
||||
|
||||
binary.BigEndian.PutUint16(ipHdr[4:], tcpLen) // payload length
|
||||
ipHdr[6] = 6 // next header (TCP)
|
||||
ipHdr[7] = 64 // hop limit
|
||||
|
||||
// Swap dest / src IPs
|
||||
copy(ipHdr[8:24], packet[24:40])
|
||||
copy(ipHdr[24:40], packet[8:24])
|
||||
|
||||
// TCP RST
|
||||
tcpIn := packet[offset:]
|
||||
var ackSeq, seq uint32
|
||||
outFlags := byte(0b00000100) // RST
|
||||
|
||||
inAck := tcpIn[13]&0b00010000 != 0
|
||||
if inAck {
|
||||
seq = binary.BigEndian.Uint32(tcpIn[8:])
|
||||
} else {
|
||||
inSyn := uint32((tcpIn[13] & 0b00000010) >> 1)
|
||||
inFin := uint32(tcpIn[13] & 0b00000001)
|
||||
ackSeq = binary.BigEndian.Uint32(tcpIn[4:]) + inSyn + inFin + uint32(len(tcpIn)) - uint32(tcpIn[12]>>4)<<2
|
||||
outFlags |= 0b00010000 // ACK
|
||||
}
|
||||
|
||||
tcpOut := out[ipv6.HeaderLen:]
|
||||
// Swap dest / src ports
|
||||
copy(tcpOut[0:2], tcpIn[2:4])
|
||||
copy(tcpOut[2:4], tcpIn[0:2])
|
||||
binary.BigEndian.PutUint32(tcpOut[4:], seq)
|
||||
binary.BigEndian.PutUint32(tcpOut[8:], ackSeq)
|
||||
tcpOut[12] = (tcpLen >> 2) << 4 // data offset, reserved, NS
|
||||
tcpOut[13] = outFlags // CWR, ECE, URG, ACK, PSH, RST, SYN, FIN
|
||||
tcpOut[14] = 0 // window size
|
||||
tcpOut[15] = 0 // .
|
||||
tcpOut[16] = 0 // checksum
|
||||
tcpOut[17] = 0 // .
|
||||
tcpOut[18] = 0 // URG Pointer
|
||||
tcpOut[19] = 0 // .
|
||||
|
||||
// Calculate checksum with IPv6 pseudo-header
|
||||
csum := ipv6PseudoheaderChecksum(ipHdr[8:24], ipHdr[24:40], 6, tcpLen)
|
||||
binary.BigEndian.PutUint16(tcpOut[16:], tcpipChecksum(tcpOut, csum))
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func ipv6FindUpperProtocolOffset(packet []byte) int {
|
||||
nextHeader := packet[6]
|
||||
offset := ipv6.HeaderLen
|
||||
|
||||
for {
|
||||
switch nextHeader {
|
||||
case 0, 43, 60: // Hop-by-Hop, Routing, Destination
|
||||
if len(packet) < offset+2 {
|
||||
return offset
|
||||
}
|
||||
nextHeader = packet[offset]
|
||||
offset += int(packet[offset+1]+1) << 3
|
||||
|
||||
case 44: // Fragment
|
||||
if len(packet) < offset+8 {
|
||||
return offset
|
||||
}
|
||||
nextHeader = packet[offset]
|
||||
offset += 8
|
||||
|
||||
case 51: // AH
|
||||
if len(packet) < offset+2 {
|
||||
return offset
|
||||
}
|
||||
nextHeader = packet[offset]
|
||||
offset += int(packet[offset+1]+2) << 2
|
||||
|
||||
default:
|
||||
return offset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func CreateICMPEchoResponse(packet, out []byte) []byte {
|
||||
// Return early if this is not a simple ICMP Echo Request
|
||||
//TODO: make constants out of these
|
||||
@@ -236,3 +448,18 @@ func ipv4PseudoheaderChecksum(src, dst []byte, proto, length uint32) (csum uint3
|
||||
csum += length >> 16
|
||||
return csum
|
||||
}
|
||||
|
||||
// based on:
|
||||
// - https://github.com/google/gopacket/blob/v1.1.19/layers/tcpip.go#L37-L48
|
||||
func ipv6PseudoheaderChecksum(src, dst []byte, proto, length uint32) (csum uint32) {
|
||||
for i := 0; i < 16; i += 2 {
|
||||
csum += uint32(src[i]) << 8
|
||||
csum += uint32(src[i+1])
|
||||
csum += uint32(dst[i]) << 8
|
||||
csum += uint32(dst[i+1])
|
||||
}
|
||||
csum += proto
|
||||
csum += length & 0xffff
|
||||
csum += length >> 16
|
||||
return csum
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user