Show HN: Hopsum – making routers do arithmetic with expired packets

anagogistis1 pts0 comments

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,...

hopsum checksum probe reply router ipv4

Related Articles