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 }