January 31, 2015
Continuing our dive into the Internet Protocol Suite from Go (See part 1 Raw sockets in Go: IP layer), we are going to the link layer, so we can see the IP headers. This will also allow us to craft our own IP headers, or handle address families outside IP. We’ll send ping packets (ICMP echo request) and watch the kernel’s response.
This isn’t wrapped in Go, so we need a syscall. Otherwise it’s very similar to the IP layer in part 1, and pretty similar to the C equivalent.
On the first line of
main we request the
AF_INET family, meaning IPv4. We could ask for a different address family (
AF_* constants) – here’s a list of address families. Most of the protocols in that list are rare (AF_IPX, AF_APPLETALK, etc). We’re in a IP world today.
Other useful address families:
AF_UNIXfor unix domain sockets. It is used in net.DialUnix and net.ListenUnix. The POSIX name for AF_UNIX is
AF_LOCAL, but Go largely sticks to AF_UNIX. They are equivalent.
- An odd / interesting one is
AF_NETLINK, which is for talking to the kernel. Read about it man 7 netlink or at Linux Journal. Docker has a netlink package.
The second parameter,
SOCK_RAW is what makes this a raw socket, where we receive IP packets. SOCK_STREAM would give us TCP, SOCK_DGRAM would give UDP.
The third parameter filters packets so we only receive ICMP. You need a protocol here. As
man 7 raw says “Receiving of all IP protocols via IPPROTO_RAW is not possible using raw sockets”. We’ll do that in the next post in this series, at the physical / device driver layer.
Build and run it as root (only root or
CAP_NET_RAW can open raw sockets). In a different window
ping localhost. You should see something like this:
45 00 00 3C EA FF 40 00 40 06 51 BA 7F 00 00 01 7F 00 00 01 …
This is the IP Header. First byte 45 is 4 for the IP version (IPv4), and 5 for length of this header (5 32-bit words), and so on. This is just like the receive example in the previous post except that we also see the IP header.
IPPROTO_ICMP in the Socket call with
wget localhost. The first 20 bytes will be similar (the IP header), then you should see a TCP packet, and finally HTTP.
If you have UNIX Network Programmings by Richard Stevens, you might be as confused as I was by section 28.4 which claims “received TCP packets are never passed to a raw socket.” That’s clearly not true. It’s a historic note related to BSD.
man 7 raw says “Raw sockets may tap all IP protocols in Linux, even protocols like ICMP or TCP” but “This should not be relied upon in portable programs”.
That Stevens book is widely considered the best reference for unix network programming. It’s also very expensive. There’s a soft cover “International Student Edition” (meaning India) you can get at abebooks.com for much less.
There’s an IP header type in the
go.net sub-repository, with
ParseHeader methods. That file doesn’t depend on anything else in there, so grab it
wget https://raw.githubusercontent.com/golang/net/master/ipv4/header.go, change the
package statement to
main, and we’ll play with it.
We’re going to send an ICMP ping packet, creating the IP header ourselves.
Download that as
send.go, and the
header.go from net repo, and build it
go build send.go header.go. Now run the receive in one window (remember to sudo), and send in another. Receive should output something like this:
45 00 00 1E 1C D4 00 00 40 01 60 09 7F 00 00 01 7F 00 00 01 08 00 37 21 00 00 00 00 C0 DE
45 00 00 1E 1C D5 00 00 40 01 60 08 7F 00 00 01 7F 00 00 01 00 00 3F 21 00 00 00 00 C0 DE
The first line is our ICMP Echo Request packet, with a few IP header fields filled in by the kernel. The second line is the kernel’s response! (yes I find that very exciting). Notice byte 21 is 0, instead of 8. That’s the only difference between an ICMP Echo message (ping) and an ICMP Echo Reply message (pong).
syscall.IPPROTO_RAW as the last argument to
Socket because we will be including our own IP header. If you wanted to use
syscall.IPPROTO_ICMP there, you also need to set the
IP_HDRINCL socket option:
syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
I think that last argument is only used so the kernel knows what protocol to put in the IP header’s Protocol field. If you’re building the header yourself, there’s not much point using anything except IPPROTO_RAW.
To build a basic
ping client, combine send and receive into one program, put send timestamp in the data part (instead of 0xC0DE), and print the elapsed time when you get the Echo Reply. If you actually need to do ICMP (other than to play with raw sockets), the net repo has an icmp package.
In the final post I’ll write about the physical layer, and we’ll get to work with Ethernet packets.