2021-02-19

Recently I noticed that the ping source code has an interesting trick. It creates a UDP socket and bind connect to the destnation using port 1025. The code is here.

At first glance it is strange as we know the ping just uses ICMP to detect the connection of two ip.

So Let’s see what happened.

In one terminal we use tcpdump to capture the packet.

    root@ubuntu:/home/test# tcpdump -nn -vv host 8.8.8.8

In another terminal we strace the ping.

    test@ubuntu:~$ strace -o ping.txt ping 8.8.8.8 -c 1

After the ping terminated. We can see the tcmpdump has no packet related with the 1025 port.

    root@ubuntu:/home/test# tcpdump -nn -vv host 8.8.8.8
    tcpdump: listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
    07:29:19.390097 IP (tos 0x0, ttl 64, id 9390, offset 0, flags [DF], proto ICMP (1), length 84)
        192.168.80.146 > 8.8.8.8: ICMP echo request, id 2, seq 1, length 64
    07:29:19.688639 IP (tos 0x0, ttl 128, id 44019, offset 0, flags [none], proto ICMP (1), length 84)
        8.8.8.8 > 192.168.80.146: ICMP echo reply, id 2, seq 1, length 64

Let’s see the strace log.

    socket(AF_INET, SOCK_DGRAM, IPPROTO_IP) = 5
    connect(5, {sa_family=AF_INET, sin_port=htons(1025), sin_addr=inet_addr("8.8.8.8")}, 16) = 0
    getsockname(5, {sa_family=AF_INET, sin_port=htons(43043), sin_addr=inet_addr("192.168.80.146")}, [16]) = 0
    close(5)                                = 0

The UDP 1025 port is just there and exists just socket/connect/getsockname/close.

So after search the internet I just found this is a trick to get current source IP that ping program used.

The use of 1025 is in the condition of no source ip specified. If we specify the source IP, there is no connect in the strace log.

    test@ubuntu:~$ strace -o ping.txt ping -I 192.168.80.146  8.8.8.8 -c 1

Finally let’s just go to kernel to see.

    SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
            int, addrlen)
    {
        return __sys_connect(fd, uservaddr, addrlen);
    }

    __sys_connect
        ->__sys_connect_file
            ->sock->ops->connect(inet_stream_connect)
                ->__inet_stream_connect
                    ->sk->sk_prot->connect(ip4_datagram_connect)
                        ->__ip4_datagram_connect
                            ->ip_route_connect
                                ->ip_route_connect_init
                                ->__ip_route_output_key
                                    ->ip_route_output_key_hash
                                        ->ip_route_output_key_hash_rcu
                                ->flowi4_update_output
                                ->ip_route_output_flow

It seems that ‘ip_route_output_key_hash_rcu’ choose the source address.

    ip_route_output_key_hash_rcu
	if (!fl4->saddr) {
		if (ipv4_is_multicast(fl4->daddr))
			fl4->saddr = inet_select_addr(dev_out, 0,
						      fl4->flowi4_scope);
		else if (!fl4->daddr)
			fl4->saddr = inet_select_addr(dev_out, 0,
						      RT_SCOPE_HOST);
	}


    __ip4_datagram_connect
    if (!inet->inet_saddr)
        inet->inet_saddr = fl4->saddr;	/* Update source address */

In the getsockname syscall, we can see it gets the source IP from ‘inet->inet_saddr’.

    int inet_getname(struct socket *sock, struct sockaddr *uaddr,
            int peer)
    {
        struct sock *sk		= sock->sk;
        struct inet_sock *inet	= inet_sk(sk);
        DECLARE_SOCKADDR(struct sockaddr_in *, sin, uaddr);

        sin->sin_family = AF_INET;
        if (peer) {
            ...
        } else {
            __be32 addr = inet->inet_rcv_saddr;
            if (!addr)
                addr = inet->inet_saddr;
            sin->sin_port = inet->inet_sport;
            sin->sin_addr.s_addr = addr;
        }
        ...
    }
    EXPORT_SYMBOL(inet_getname);

Reference

https://echorand.me/posts/my-own-ping/

https://jeffpar.github.io/kbarchive/kb/129/Q129065/

https://github.com/iputils/iputils/issues/125

https://stackoverflow.com/questions/25879280/getting-my-own-ip-address-by-connecting-using-udp-socket



blog comments powered by Disqus