ticmphop.go - icmphop - Add hops in ipv6 traceroute
(HTM) git clone git://git.z3bra.org/icmphop.git
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
---
ticmphop.go (5214B)
---
1 /*
2 * Insert hops in traceroute (ipv6 only)
3 *
4 * Create a virtual interface, and send "time exceeded" messages
5 * until the TTL match a specified number, effectively adding hops
6 * to a traceroute.
7 *
8 * The source of the error messages is changed so the traceroute
9 * appears to be incrementing, up to the requested destination.
10 *
11 * by wgs
12 */
13
14 package main
15
16 import (
17 "bytes"
18 "encoding/binary"
19 "flag"
20 "log"
21 "net"
22
23 "golang.org/x/net/icmp"
24 "golang.zx2c4.com/wireguard/tun"
25 )
26
27 const (
28 TUN_HEADER = 4
29 IPV6_HEADER = 40
30 ICMP_HEADER = 4
31 )
32
33 /*
34 * Compute IPv6 checksum for a given packet
35 * https://datatracker.ietf.org/doc/html/rfc4443#section-2.3
36 */
37 func checksum(body []byte, srcIP, dstIP net.IP) (crc []byte) {
38 out := make([]byte, 2)
39 // from golang.org/x/net/icmp/message.go
40 checksum := func(b []byte) uint16 {
41 csumcv := len(b) - 1 // checksum coverage
42 s := uint32(0)
43 for i := 0; i < csumcv; i += 2 {
44 s += uint32(b[i+1])<<8 | uint32(b[i])
45 }
46 if csumcv&1 == 0 {
47 s += uint32(b[csumcv])
48 }
49 s = s>>16 + s&0xffff
50 s = s + s>>16
51 return ^uint16(s)
52 }
53
54 b := body
55 l := len(b)
56 psh := icmp.IPv6PseudoHeader(srcIP, dstIP)
57 b = append(psh, b...)
58 off := 2 * net.IPv6len
59 binary.BigEndian.PutUint32(b[off:off+4], uint32(l))
60 s := checksum(b)
61 out[0] ^= byte(s)
62 out[1] ^= byte(s >> 8)
63
64 return out
65 }
66
67 func ipv6_header(src, dst net.IP, len uint16) []byte {
68 header := make([]byte, IPV6_HEADER)
69
70 // Packet version (ipv6), Traffic class and Flow label
71 copy(header[:4], []byte{0x60, 0, 0, 0})
72
73 sz := new(bytes.Buffer)
74 binary.Write(sz, binary.BigEndian, len)
75 header[4] = sz.Bytes()[0] // Packet size
76 header[5] = sz.Bytes()[1] // Packet size
77 header[6] = 0x3a // Next header (58, ICMPv6)
78 header[7] = 0x40 // Hop Limit (64)
79
80 // source / destination ipv6
81 copy(header[8:], src[:16])
82 copy(header[24:], dst[:16])
83
84 return header
85 }
86
87 func icmp_message(icmp_type, icmp_code uint8, src, dst net.IP, payload []byte) []byte {
88 message := make([]byte, ICMP_HEADER)
89
90 message[0] = icmp_type
91 message[1] = icmp_code
92
93 switch icmp_type {
94 case 1: // Destination unreachable
95 fallthrough
96 case 3: // Time Exceeded
97 message = append(message, 0, 0, 0, 0) // unused
98 message = append(message, payload...)
99 case 129: // Echo reply
100 // payload must include identifier + seqnum
101 // from original message
102 message = append(message, payload...)
103 }
104
105 crc := checksum(message, src, dst)
106 message[2] = crc[0]
107 message[3] = crc[1]
108
109 return message
110 }
111
112 func main() {
113 ifname := flag.String("i", "tun", "Interface name")
114 mtu := flag.Int("m", 1500, "Interface MTU")
115 hop := flag.Int("n", 4, "Hops number to insert")
116 verbose := flag.Bool("v", false, "Enable verbose logging")
117
118 flag.Parse()
119
120 tun, err := tun.CreateTUN(*ifname, *mtu)
121 if err != nil {
122 log.Fatal(err)
123 }
124
125 hops := uint8(*hop)
126
127 for {
128 var icmp, ipv6 []byte
129
130 src := make([]byte, 16)
131 dst := make([]byte, 16)
132 lens := make([]int, 1)
133 bufs := make([][]byte, 1)
134 bufs[0] = make([]byte, *mtu+TUN_HEADER)
135 n, err := tun.Read(bufs, lens, TUN_HEADER)
136 if err != nil {
137 log.Fatalf("Read %s: %s", *ifname, err.Error())
138 }
139 if n < 1 {
140 continue
141 }
142
143 sz := lens[0]
144
145 // Invalid packet, ignore it
146 if sz < IPV6_HEADER {
147 continue
148 }
149
150 packet := bufs[0][TUN_HEADER : TUN_HEADER+sz]
151
152 /*
153 * Skip packet if the specified number of hops cannot
154 * be inserted by only changing the last byte
155 */
156 if packet[39] < hops {
157 if *verbose == true {
158 log.Printf("%s %s > %s Dropped (last byte 0x%02x < 0x%02x)",
159 *ifname,
160 net.IP(packet[24:40]).String(),
161 net.IP(packet[8:24]).String(),
162 packet[39], hops-1)
163 }
164 continue
165 }
166
167 ttl := uint8(packet[7])
168
169 // Invert source and destination address
170 copy(dst, packet[8:8+16])
171 copy(src, packet[24:24+16])
172
173 /*
174 * If TTL is lower than the configured number of hops,
175 * start sending ICMP time exceeded replies to the
176 * originating source.
177 */
178 if ttl < hops+1 {
179 /*
180 * Use hexa representation of TTL as the
181 * last byte of source address, thus
182 * incrementing hops until final destination
183 * is reached
184 */
185 src[15] = src[15] - hops + ttl
186
187 // ICMP error must fit in 1280 bytes (ipv6 min. mtu)
188 if sz+IPV6_HEADER+ICMP_HEADER > 1280 {
189 sz = 1228
190 }
191
192 ipv6 = ipv6_header(src, dst, uint16(sz+ICMP_HEADER+4))
193
194 if ttl < hops {
195 // Time exceeded
196 icmp = icmp_message(3, 0, src, dst, packet[:sz])
197 } else {
198 // Destination port unreachable
199 icmp = icmp_message(1, 4, src, dst, packet[:sz])
200 }
201 } else {
202 // Skip everything not ICMPv6
203 if packet[6] != 58 {
204 continue
205 }
206
207 switch packet[IPV6_HEADER] {
208 case 128: // ICMPv6 echo request
209 ipv6 = ipv6_header(src, dst, uint16(sz-IPV6_HEADER))
210 icmp = icmp_message(129, 0, src, dst, packet[IPV6_HEADER+4:])
211 default:
212 continue
213 }
214 }
215
216 // Construct final return packet
217 b := make([][]byte, 1)
218 b[0] = make([]byte, TUN_HEADER)
219 b[0] = append(b[0], ipv6...)
220 b[0] = append(b[0], icmp...)
221
222 if *verbose == true {
223 log.Printf("%s %s > %s ICMP Type 0x%02x Code %d (%d bytes)",
224 *ifname,
225 net.IP(src).String(),
226 net.IP(dst).String(),
227 icmp[0],
228 icmp[1],
229 len(b[0]))
230 }
231
232 tun.Write(b, TUN_HEADER)
233 }
234 }