Skip to content

kuzand/hopsum

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hopsum

One's-complement arithmetic, performed by a router that is only trying to tell you your packet expired. A hop returns a sum, hence the name.

A router that drops a packet on TTL zero may reply with ICMP Time Exceeded, quoting the original IPv4 header and the first eight bytes of payload. The ICMP checksum is computed over all of it, so part of its input is yours to choose. Send IPv4 protocol 253 with the payload 0xF4FF || a || b || c and everything you did not choose cancels out: a valid IPv4 header already sums to 0xFFFF, and 0xF4FF absorbs the Time Exceeded type and code. What comes back in the checksum field is ~(a ⊞ b ⊞ c).

That single probe is the whole machine. Addition, subtraction, and multiplication are built by sending replies back in as the next operands. The router never multiplies anything; it only ever sums the words in your payload and complements the result.

As arithmetic this is gloriously impractical, since every probe spends a round trip on work a CPU finishes in one instruction. The point is that the router computes anything for you at all, as a byproduct of an error message it was obliged to send.

Install

Linux, Go 1.22+, and cap_net_raw:

go install github.com/kuzand/hopsum@latest
sudo setcap cap_net_raw+ep "$(go env GOPATH)/bin/hopsum"

Or from a clone:

go build -o hopsum . && sudo setcap cap_net_raw+ep ./hopsum

Every build writes a new file with no capabilities, so setcap has to follow each one.

Commands

Command Probes Result
checksum a [b [c]] 1 ~(a ⊞ b ⊞ c), the reply as it arrives
negate a 1 ~a, the same primitive with b and c left at zero
verify a [b [c]] expected 1 exits 0 if the reply matches
add a [b [c]] 2 a ⊞ b ⊞ c, the second probe cancelling the first complement
sub a b 2 a ⊟ b, computed as a ⊞ ~b
mul a b 2 per step a * b by double-and-add

Operands are 16-bit words, and the payload holds three of them. Where a command takes several, a lone operand may instead be up to 48 bits and is cut into words from the low end, so checksum 0x0064012c and checksum 0x0064 0x012c are the same probe. Leading zeros are free, since 0 is the additive identity here, and add on one wide operand folds it back down to 16 bits.

With -x, the operands of checksum, verify and add are read as a message instead of as numbers: up to six hex octets, written together or separated however is convenient. A trailing odd octet is padded on the right with a zero byte, as RFC 1071 requires, so the three bytes 12 34 56 sum as 0x1234 ⊞ 0x5600. The pad exists only for the checksum and is never sent.

$ ./hopsum -x checksum 12 34 56
Target: 10.128.0.1
Probes: 1
Result: 0x97cb (38859)

That is the real Internet checksum of those three bytes, computed by a router that was only trying to report an expired packet.

For mul, a step is one double or one add, so the cost scales with the bit length and the popcount of the multiplier. Both factors are known locally once a reply is back, so it multiplies by whichever one is cheaper.

$ ./hopsum mul 12 5
Target: 10.128.0.1
Probes: 6
Result: 0x003c (60)

Flags

Flag Default Meaning
-dst 1.1.1.1 where the packet is aimed; the TTL should expire long before it
-ttl 1 IPv4 TTL, where 1 is usually your own gateway
-q off print only the decimal result, for pipes
-x off read operands as hex octets, padded per RFC 1071, not as numbers
-timeout 3s how long to wait for one reply
-pace 500ms minimum gap between sends
-retries 3 resends when a reply goes missing

Operands are decimal or 0x hex. - reads one word from stdin, so with -q the probes compose:

./hopsum -q add 100 200 | ./hopsum -q mul - 5

Notes

The arithmetic is the checksum's own, which means modulo 65535 rather than 2^16, with 0x0000 and 0xFFFF both behaving as zero. A product that lands on a multiple of 65535 comes back as 0xFFFF.

The whole trick rests on the IPv4 header checksum cancelling itself. IPv6 has no such field, so none of this carries over.

Whether anything answers is painfully path-dependent. TTL 1 is normally your own gateway and usually works, but past that, carrier NAT and tunnelled IPv4 often mean nothing comes back at all. Run traceroute over the path you intend to use before blaming the tool, and if it dies just past the gateway, a VPN will usually revive it by moving TTL expiry onto routers that do answer.

You also need a hop that quotes your eight bytes without rewriting them, and Time Exceeded is widely rate-limited, which is why sends are paced and missed replies retried rather than reported as failures.

Layout

  • probe.go - raw sockets, the single Probe, and Time Exceeded parsing
  • calc.go - add / sub / mul, built by chaining probes
  • main.go - the CLI

About

Parasitic computing with the ICMP Time Exceeded checksum.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages