tipv6 traceroute hops inserter - icmphop - Add hops in ipv6 traceroute
HTML git clone git://git.z3bra.org/icmphop.git
DIR Log
DIR Files
DIR Refs
DIR README
---
DIR commit f2fc653a824358e5317b9900a1991cd82f462ae0
HTML Author: Willy Goiffon <dev@z3bra.org>
Date: Wed, 12 Oct 2022 17:37:16 +0200
ipv6 traceroute hops inserter
Diffstat:
A README | 40 +++++++++++++++++++++++++++++++
A config.mk | 5 +++++
A go.mod | 13 +++++++++++++
A icmphop.go | 175 +++++++++++++++++++++++++++++++
A makefile | 19 +++++++++++++++++++
A mkfile | 18 ++++++++++++++++++
6 files changed, 270 insertions(+), 0 deletions(-)
---
DIR diff --git a/README b/README
t@@ -0,0 +1,40 @@
+icmphop
+=======
+Insert hops in ipv6 traceroute
+
+The program will craft ICMP timeout exceeded error messages to effectively
+add hops between a client and the target ipv6 address.
+
+This is of no use, besides printing funny stuff in traceroutes using
+reverse DNS entries.
+
+Features
+-----
+- IPv6 only
+- Insert up to 15 hops
+
+Usage
+-----
+
+ # icmphop -n 5 -i hop0 &
+ # ip -6 addr add fd00:c001:dad::1/96 dev hop0
+ # ip link set hop0 up
+ % tracepath -6 fd00:c001:dad::5
+ 1?: [LOCALHOST] 0.014ms pmtu 1500
+ 1: fd00:c001:dad::1 0.108ms
+ 1: fd00:c001:dad::1 0.081ms
+ 2: fd00:c001:dad::2 0.091ms asymm 1
+ 3: fd00:c001:dad::3 0.087ms asymm 1
+ 4: fd00:c001:dad::4 0.082ms asymm 1
+ 5: fd00:c001:dad::5 0.078ms reached
+ Resume: pmtu 1500 hops 5 back 1
+
+The requested ipv6 address last byte must be the same as the number of
+hops. For example, with 10 hops, use fd00:c001:dad:a.
+
+Installation
+-----
+Edit config.mk as needed, then build/install with the following commands:
+
+ make
+ make install
DIR diff --git a/config.mk b/config.mk
t@@ -0,0 +1,5 @@
+GO = go
+GOOS = `{uname -s | tr A-Z a-z}
+
+PREFIX = /usr/local
+MANDIR = ${PREFIX}/man
DIR diff --git a/go.mod b/go.mod
t@@ -0,0 +1,13 @@
+module z3bra.org/icmphop
+
+go 1.19
+
+require (
+ golang.org/x/net v0.0.0-20221004154528-8021a29435af
+ golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c
+)
+
+require (
+ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
+ golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
+)
DIR diff --git a/icmphop.go b/icmphop.go
t@@ -0,0 +1,175 @@
+/*
+ * Copy me if you can.
+ * by wgs
+ */
+
+package main
+
+import (
+ "bytes"
+ "encoding/binary"
+ "flag"
+ "log"
+ "net"
+
+ "golang.org/x/net/icmp"
+ "golang.zx2c4.com/wireguard/tun"
+)
+
+const TUN_HEADER = 4
+const IPV6_HEADER = 40
+const ICMP_HEADER = 8
+
+/* compute IPv6 checksum for a given packet */
+func checksum(body []byte, srcIP, dstIP net.IP) (crc []byte) {
+ out := make([]byte, 2)
+ // from golang.org/x/net/icmp/message.go
+ checksum := func(b []byte) uint16 {
+ csumcv := len(b) - 1 // checksum coverage
+ s := uint32(0)
+ for i := 0; i < csumcv; i += 2 {
+ s += uint32(b[i+1])<<8 | uint32(b[i])
+ }
+ if csumcv&1 == 0 {
+ s += uint32(b[csumcv])
+ }
+ s = s>>16 + s&0xffff
+ s = s + s>>16
+ return ^uint16(s)
+ }
+
+ b := body
+ l := len(b)
+ psh := icmp.IPv6PseudoHeader(srcIP, dstIP)
+ b = append(psh, b...)
+ off := 2 * net.IPv6len
+ binary.BigEndian.PutUint32(b[off:off+4], uint32(l))
+ s := checksum(b)
+ out[0] ^= byte(s)
+ out[1] ^= byte(s >> 8)
+
+ return out
+}
+
+func ipv6_header(src, dst net.IP, len uint16) []byte {
+ header := make([]byte, IPV6_HEADER)
+
+ // Packet version (ipv6), Traffic class and Flow label
+ header[0] = 0x60
+ header[1] = 0x00
+ header[2] = 0x00
+ header[3] = 0x00
+
+ sz := new(bytes.Buffer)
+ binary.Write(sz, binary.BigEndian, len)
+ header[4] = sz.Bytes()[0] // Packet size
+ header[5] = sz.Bytes()[1] // Packet size
+ header[6] = 0x3a // Next header (58, ICMPv6)
+ header[7] = 0x40 // Hop Limit (64)
+
+ copy(header[8:], src[:16])
+ copy(header[24:], dst[:16])
+
+ return header
+}
+
+func icmp_message(icmp_type, icmp_code uint8, src, dst net.IP, payload []byte) []byte {
+ len := len(payload)
+ message := make([]byte, ICMP_HEADER+len)
+
+ message[0] = icmp_type
+ message[1] = icmp_code
+
+ // Reserved bytes
+ message[2] = 0x00
+ message[3] = 0x00
+ message[4] = 0x00
+ message[5] = 0x00
+ message[6] = 0x00
+ message[7] = 0x00
+
+ // Copy initial packet as return packet payload
+ copy(message[ICMP_HEADER:], payload)
+
+ crc := checksum(message, src, dst)
+ message[2] = crc[0]
+ message[3] = crc[1]
+
+ return message
+}
+
+func main() {
+ ifname := flag.String("i", "tun", "Interface name")
+ mtu := flag.Int("m", 1500, "Interface MTU")
+ hop := flag.Int("n", 4, "Hops number to insert")
+ verbose := flag.Bool("v", false, "Enable verbose logging")
+
+ flag.Parse()
+
+ tun, err := tun.CreateTUN(*ifname, *mtu)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ hops := uint8(*hop)
+
+ for {
+ buf := make([]byte, *mtu+TUN_HEADER)
+ plen, err := tun.Read(buf, TUN_HEADER)
+ if err != nil {
+ log.Fatalf("Read %s: %s", *ifname, err.Error())
+ }
+
+ // Invalid packet, ignore it
+ if plen < IPV6_HEADER {
+ continue
+ }
+
+ // ICMP error message size is limited to 576 bytes.
+ // Shring payload size to its bare minimum of 8 bytes in such case.
+ if plen > 576 {
+ plen = TUN_HEADER + IPV6_HEADER + ICMP_HEADER + 8
+ }
+
+ packet := buf[TUN_HEADER : TUN_HEADER+plen]
+
+ // Only handle destination address whose last byte ends
+ // with the number of hops requested
+ if packet[39] != 0x00+hops {
+ continue
+ }
+
+ ttl := uint8(packet[7])
+
+ if ttl < hops+1 {
+ var icmp, ipv6 []byte
+
+ src := make([]byte, 16)
+ dst := make([]byte, 16)
+
+ // Invert source and destination address
+ copy(dst, packet[8:8+16])
+ copy(src, packet[24:24+16])
+ src[15] = 0x00 + ttl // Reuse ttl as the last byte of source address
+
+ ipv6 = ipv6_header(src, dst, uint16(plen+8))
+
+ if ttl < hops {
+ icmp = icmp_message(0x03, 0x00, src, dst, packet)
+ } else {
+ icmp = icmp_message(0x01, 0x04, src, dst, packet)
+ }
+
+ // Construct full return packet
+ b := make([]byte, TUN_HEADER)
+ b = append(b, ipv6...)
+ b = append(b, icmp...)
+
+ if *verbose == true {
+ log.Printf("%s %s > %s ICMP [%d:%d]", *ifname, net.IP(src).String(), net.IP(dst).String(), icmp[0], icmp[1])
+ }
+
+ tun.Write(b, TUN_HEADER)
+ }
+ }
+}
DIR diff --git a/makefile b/makefile
t@@ -0,0 +1,19 @@
+-include config.mk
+
+.SUFFIXES: .go
+
+all: icmphop
+
+.go:
+ ${GO} build -o $@ $<
+
+clean:
+ rm -f icmphop
+
+install: icmphop
+ mkdir -p ${DESTDIR}${PREFIX}/bin
+ cp icmphop ${DESTDIR}${PREFIX}/bin/icmphop
+ chmod 755 ${DESTDIR}${PREFIX}/bin/icmphop
+
+uninstall:
+ rm ${DESTDIR}${PREFIX}/bin/icmphop
DIR diff --git a/mkfile b/mkfile
t@@ -0,0 +1,18 @@
+<config.mk
+
+all:V: icmphop
+
+%: %.go
+ $GO build -o $stem $stem.go
+
+clean:V:
+ rm -f icmphop
+
+install:V: icmphop
+ mkdir -p ${DESTDIR}${PREFIX}/bin
+ cp icmphop ${DESTDIR}${PREFIX}/bin/icmphop
+ chmod 755 ${DESTDIR}${PREFIX}/bin/icmphop
+ mkdir -p ${DESTDIR}${MANDIR}/man1
+
+uninstall:V:
+ rm ${DESTDIR}${PREFIX}/bin/icmphop