Skip to content

Commit fe1c568

Browse files
authored
add IPv6 support to CreateICMPEchoResponse (#1767)
The function previously only handled IPv4 ICMP Echo Request packets. This adds handling for IPv6 ICMPv6 Echo Request (type 128) by generating a proper Echo Reply (type 129) with correct pseudo-header checksum.
1 parent e4cc80a commit fe1c568

2 files changed

Lines changed: 155 additions & 0 deletions

File tree

iputil/packet.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,21 @@ func ipv6FindUpperProtocolOffset(packet []byte) int {
378378
}
379379

380380
func CreateICMPEchoResponse(packet, out []byte) []byte {
381+
if len(packet) < 1 {
382+
return nil
383+
}
384+
385+
switch packet[0] >> 4 {
386+
case 4:
387+
return createICMPv4EchoResponse(packet, out)
388+
case 6:
389+
return createICMPv6EchoResponse(packet, out)
390+
default:
391+
return nil
392+
}
393+
}
394+
395+
func createICMPv4EchoResponse(packet, out []byte) []byte {
381396
// Return early if this is not a simple ICMP Echo Request
382397
//TODO: make constants out of these
383398
if !(len(packet) >= 28 && len(packet) <= 9001 && packet[0] == 0x45 && packet[9] == 0x01 && packet[20] == 0x08) {
@@ -411,6 +426,43 @@ func CreateICMPEchoResponse(packet, out []byte) []byte {
411426
return out
412427
}
413428

429+
func createICMPv6EchoResponse(packet, out []byte) []byte {
430+
// IPv6 header (40 bytes) + ICMPv6 header (8 bytes minimum)
431+
if len(packet) < ipv6.HeaderLen+8 || len(packet) > 9001 {
432+
return nil
433+
}
434+
435+
// Next Header must be ICMPv6 (58)
436+
if packet[6] != 58 {
437+
return nil
438+
}
439+
440+
// ICMPv6 type must be Echo Request (128)
441+
if packet[ipv6.HeaderLen] != 128 {
442+
return nil
443+
}
444+
445+
out = out[:len(packet)]
446+
copy(out, packet)
447+
448+
// Swap src/dst addresses (bytes 8-23 and 24-39)
449+
copy(out[8:24], packet[24:40])
450+
copy(out[24:40], packet[8:24])
451+
452+
// Change ICMPv6 type to Echo Reply (129)
453+
icmp := out[ipv6.HeaderLen:]
454+
icmp[0] = 129
455+
icmp[2] = 0
456+
icmp[3] = 0
457+
458+
// ICMPv6 checksum uses a pseudo-header with src, dst, length, and next header
459+
payloadLen := uint32(len(icmp))
460+
csum := ipv6PseudoheaderChecksum(out[8:24], out[24:40], 58, payloadLen)
461+
binary.BigEndian.PutUint16(icmp[2:], tcpipChecksum(icmp, csum))
462+
463+
return out
464+
}
465+
414466
// calculates the TCP/IP checksum defined in rfc1071. The passed-in
415467
// csum is any initial checksum data that's already been computed.
416468
//

iputil/packet_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,106 @@ func Test_CreateRejectPacketIPv6_ExtensionHeaders(t *testing.T) {
239239
tcpOut := rejectPacket[ipv6.HeaderLen:]
240240
assert.Equal(t, byte(0b00000100), tcpOut[13]) // RST only
241241
}
242+
243+
func TestCreateICMPEchoResponse_IPv4(t *testing.T) {
244+
// Build a simple IPv4 ICMP Echo Request
245+
packet := make([]byte, 28)
246+
packet[0] = 0x45 // version 4, IHL 5
247+
binary.BigEndian.PutUint16(packet[2:], uint16(28)) // total length
248+
packet[8] = 64 // TTL
249+
packet[9] = 1 // protocol ICMP
250+
copy(packet[12:16], net.IPv4(10, 0, 0, 1).To4()) // src
251+
copy(packet[16:20], net.IPv4(10, 0, 0, 2).To4()) // dst
252+
packet[20] = 8 // ICMP Echo Request
253+
254+
out := make([]byte, len(packet))
255+
result := CreateICMPEchoResponse(packet, out)
256+
assert.NotNil(t, result)
257+
assert.Equal(t, byte(0x45), result[0])
258+
// src/dst swapped
259+
assert.Equal(t, net.IPv4(10, 0, 0, 2).To4(), net.IP(result[12:16]))
260+
assert.Equal(t, net.IPv4(10, 0, 0, 1).To4(), net.IP(result[16:20]))
261+
// ICMP Echo Reply
262+
assert.Equal(t, byte(0), result[20])
263+
}
264+
265+
func TestCreateICMPEchoResponse_IPv6(t *testing.T) {
266+
src := net.ParseIP("fd00::1").To16()
267+
dst := net.ParseIP("fd00::2").To16()
268+
269+
// Build an IPv6 ICMPv6 Echo Request packet
270+
// IPv6 header (40 bytes) + ICMPv6 (8 bytes)
271+
packet := make([]byte, 48)
272+
packet[0] = 0x60 // version 6
273+
payloadLen := uint16(8) // ICMPv6 header only
274+
binary.BigEndian.PutUint16(packet[4:], payloadLen)
275+
packet[6] = 58 // Next Header: ICMPv6
276+
packet[7] = 64 // Hop Limit
277+
copy(packet[8:24], src) // src address
278+
copy(packet[24:40], dst) // dst address
279+
280+
// ICMPv6 Echo Request
281+
icmp := packet[40:]
282+
icmp[0] = 128 // type: Echo Request
283+
icmp[1] = 0 // code
284+
binary.BigEndian.PutUint16(icmp[4:], 1) // identifier
285+
binary.BigEndian.PutUint16(icmp[6:], 1) // sequence number
286+
287+
// Compute correct checksum for the request
288+
csum := ipv6PseudoheaderChecksum(src, dst, 58, uint32(payloadLen))
289+
binary.BigEndian.PutUint16(icmp[2:], tcpipChecksum(icmp, csum))
290+
291+
out := make([]byte, len(packet))
292+
result := CreateICMPEchoResponse(packet, out)
293+
assert.NotNil(t, result)
294+
295+
// Version should still be 6
296+
assert.Equal(t, byte(6), result[0]>>4)
297+
// src/dst swapped
298+
assert.Equal(t, dst, net.IP(result[8:24]))
299+
assert.Equal(t, src, net.IP(result[24:40]))
300+
// ICMPv6 Echo Reply type
301+
assert.Equal(t, byte(129), result[40])
302+
303+
// Verify checksum is valid (tcpipChecksum returns 0 when data+checksum is correct)
304+
respIcmp := result[40:]
305+
verifyCsum := ipv6PseudoheaderChecksum(result[8:24], result[24:40], 58, uint32(payloadLen))
306+
assert.Equal(t, uint16(0), tcpipChecksum(respIcmp, verifyCsum))
307+
}
308+
309+
func TestCreateICMPEchoResponse_IPv6_NotEchoRequest(t *testing.T) {
310+
src := net.ParseIP("fd00::1").To16()
311+
dst := net.ParseIP("fd00::2").To16()
312+
313+
packet := make([]byte, 48)
314+
packet[0] = 0x60
315+
binary.BigEndian.PutUint16(packet[4:], 8)
316+
packet[6] = 58
317+
packet[7] = 64
318+
copy(packet[8:24], src)
319+
copy(packet[24:40], dst)
320+
321+
// ICMPv6 type 1 (Destination Unreachable) - not Echo Request
322+
packet[40] = 1
323+
324+
out := make([]byte, len(packet))
325+
result := CreateICMPEchoResponse(packet, out)
326+
assert.Nil(t, result)
327+
}
328+
329+
func TestCreateICMPEchoResponse_IPv6_NotICMPv6(t *testing.T) {
330+
src := net.ParseIP("fd00::1").To16()
331+
dst := net.ParseIP("fd00::2").To16()
332+
333+
packet := make([]byte, 48)
334+
packet[0] = 0x60
335+
binary.BigEndian.PutUint16(packet[4:], 8)
336+
packet[6] = 6 // TCP, not ICMPv6
337+
packet[7] = 64
338+
copy(packet[8:24], src)
339+
copy(packet[24:40], dst)
340+
341+
out := make([]byte, len(packet))
342+
result := CreateICMPEchoResponse(packet, out)
343+
assert.Nil(t, result)
344+
}

0 commit comments

Comments
 (0)