GitHub - kuzand/hopsum: Parasitic computing with the ICMP Time Exceeded checksum. · GitHub
/" data-turbo-transient="true" />
Skip to content
Search/
Sign in<br>Sign upAppearance settings
You signed in with another tab or window. Reload to refresh your session.<br>You signed out in another tab or window. Reload to refresh your session.<br>You switched accounts on another tab or window. Reload to refresh your session.
Dismiss alert
{{ message }}
kuzand
hopsum
Public
Notifications<br>You must be signed in to change notification settings
Fork
Star
main
BranchesTags
Go to file
CodeOpen more actions menu
Folders and files<br>NameNameLast commit message<br>Last commit date<br>Latest commit
History<br>3 Commits<br>3 Commits
.gitignore
.gitignore
LICENSE
LICENSE
README.md
README.md
calc.go
calc.go
go.mod
go.mod
main.go
main.go
probe.go
probe.go
View all files
Repository files navigation
hopsum
One's-complement arithmetic, performed by a router that is only trying to tell<br>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,<br>quoting the original IPv4 header and the first eight bytes of payload. The ICMP<br>checksum is computed over all of it, so part of its input is yours to choose.<br>Send IPv4 protocol 253 with the payload 0xF4FF || a || b || c and everything<br>you did not choose cancels out: a valid IPv4 header already sums to 0xFFFF,<br>and 0xF4FF absorbs the Time Exceeded type and code. What comes back in the<br>checksum field is ~(a ⊞ b ⊞ c).
That single probe is the whole machine. Addition, subtraction, and multiplication<br>are built by sending replies back in as the next operands. The router never<br>multiplies anything; it only ever sums the words in your payload and complements<br>the result.
As arithmetic this is gloriously impractical, since every probe spends a round<br>trip on work a CPU finishes in one instruction. The point is that the router<br>computes anything for you at all, as a byproduct of an error message it was<br>obliged to send.
Install
Linux, Go 1.22+, and cap_net_raw:
go install github.com/kuzand/hopsum@latest<br>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<br>each one.
Commands
Command<br>Probes<br>Result
checksum a [b [c]]<br>~(a ⊞ b ⊞ c), the reply as it arrives
negate a<br>~a, the same primitive with b and c left at zero
verify a [b [c]] expected<br>exits 0 if the reply matches
add a [b [c]]<br>a ⊞ b ⊞ c, the second probe cancelling the first complement
sub a b<br>a ⊟ b, computed as a ⊞ ~b
mul a b<br>2 per step<br>a * b by double-and-add
Operands are 16-bit words, and the payload holds three of them. Where a command<br>takes several, a lone operand may instead be up to 48 bits and is cut into words<br>from the low end, so checksum 0x0064012c and checksum 0x0064 0x012c are the<br>same probe. Leading zeros are free, since 0 is the additive identity here, and<br>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<br>instead of as numbers: up to six hex octets, written together or separated<br>however is convenient. A trailing odd octet is padded on the right with a zero<br>byte, as RFC 1071 requires, so<br>the three bytes 12 34 56 sum as 0x1234 ⊞ 0x5600. The pad exists only for<br>the checksum and is never sent.
$ ./hopsum -x checksum 12 34 56<br>Target: 10.128.0.1<br>Probes: 1<br>Result: 0x97cb (38859)
That is the real Internet checksum of those three bytes, computed by a router<br>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<br>length and the popcount of the multiplier. Both factors are known locally once a<br>reply is back, so it multiplies by whichever one is cheaper.
$ ./hopsum mul 12 5<br>Target: 10.128.0.1<br>Probes: 6<br>Result: 0x003c (60)
Flags
Flag<br>Default<br>Meaning
-dst<br>1.1.1.1<br>where the packet is aimed; the TTL should expire long before it
-ttl<br>IPv4 TTL, where 1 is usually your own gateway
-q<br>off<br>print only the decimal result, for pipes
-x<br>off<br>read operands as hex octets, padded per RFC 1071, not as numbers
-timeout<br>3s<br>how long to wait for one reply
-pace<br>500ms<br>minimum gap between sends
-retries<br>resends when a reply goes missing
Operands are decimal or 0x hex. - reads one word from stdin, so with -q the<br>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<br>2^16, with 0x0000 and 0xFFFF both behaving as zero. A product that lands on<br>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 this particular cancellation trick does not carry over directly to IPv6.
Whether anything answers is painfully path-dependent. TTL 1 is normally your own<br>gateway and usually works, but past that,...