Binding client socket to device (e.g. using dig)

Aleksander Morgado aleksander at aleksander.es
Wed May 23 10:02:01 UTC 2018


Hey,

I've got a setup with multiple default routes, with different metrics, e.g.:

$ ip addr show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel
state UP group default qlen 1000
    link/ether 10:7b:44:a3:3f:4c brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.10/24 brd 192.168.100.255 scope global dynamic
noprefixroute eth0
       valid_lft 32241sec preferred_lft 32241sec
    inet6 fe80::195b:5079:1ec2:8ac6/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

$ ip addr show dev wwan0
6: wwan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel
state UP group default qlen 1000
    link/ether 9e:ab:90:fe:81:17 brd ff:ff:ff:ff:ff:ff
    inet 192.168.2.121/24 brd 192.168.2.255 scope global dynamic
noprefixroute wwan0
       valid_lft 81547sec preferred_lft 81547sec

$ ip route
default via 192.168.100.1 dev eth0 proto dhcp metric 100
default via 192.168.2.16 dev wwan0 proto dhcp metric 700

In order to "detect connectivity" via each default route, I'd like to
use DNS resolution queries using dig. This is a very common use case
with wwan devices (e.g. 4G modems), as we usually get the DNS servers
reported by the network and those are the only 'remote IPs' we know
are for sure available at the end of the 4G connection (the 4G
connection may be giving access to a private subnet with custom DNS
servers, for example). Using pings isn't really an option, because not
all operators allow ICMP to the DNS servers, it seems.

I tried to use the "-b IP#PORT" option in dig to bind the queries to
the IP addresses of either eth0 or wwan0, but that is only making the
source packets bind to a specific IP address, the actual packet gets
still routed via the default route with lowest metric. E.g. if I do
this, binding to the wwan0 IP address 192.168.2.121, the packet gets
routed via eth0 not via wwan0, so it fails:

$ dig @8.8.8.8 nio.io -4 -b 192.168.2.121 +short +time=5 +tries=2
; <<>> DiG 9.12.1-P2 <<>> @8.8.8.8 nio.io -4 -b 192.168.2.121 +short
+time=5 +tries=2
; (1 server found)
;; global options: +cmd
;; connection timed out; no servers could be reached

$ sudo tcpdump -i eth0 -n udp port 53
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
11:55:02.730497 IP 192.168.2.121.56968 > 8.8.8.8.53: 58816+ [1au] A?
nio.io. (47)
11:55:07.730529 IP 192.168.2.121.56968 > 8.8.8.8.53: 58816+ [1au] A?
nio.io. (47)

In order to really bind to a specific interface, e.g. equivalent to a
"ping -I <iface>", I hacked a quick patch to allow this in a very very
ugly way as a proof of concept:

diff --git a/lib/isc/unix/socket.c b/lib/isc/unix/socket.c
index ae1b7498d6..e8ccfbaf1d 100644
--- a/lib/isc/unix/socket.c
+++ b/lib/isc/unix/socket.c
@@ -5603,6 +5603,20 @@ isc__socket_bind(isc_socket_t *sock0, const
isc_sockaddr_t *sockaddr,
                                                ISC_MSG_FAILED, "failed"));
                /* Press on... */
        }
+
+       /* Explicitly bind to a given device */
+       {
+               const char *device;
+
+               device = getenv ("DIG_BIND_TO_DEVICE");
+               if (device && setsockopt(sock->fd, SOL_SOCKET,
SO_BINDTODEVICE, device, strlen(device) + 1) < 0) {
+                       UNEXPECTED_ERROR(__FILE__, __LINE__,
+                                        "setsockopt(%d) %s", sock->fd,
+                                        isc_msgcat_get(isc_msgcat,
ISC_MSGSET_GENERAL,
+
ISC_MSG_FAILED, "failed"));
+               }
+       }
+

So now if I run it like this, it works as expected:
$ sudo DIG_BIND_TO_DEVICE=wwan0 dig @8.8.8.8 isc.org -4 +short +time=5 +tries=2
149.20.64.69

$ sudo tcpdump -i wwan0 -n udp port 53
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on wwan0, link-type EN10MB (Ethernet), capture size 262144 bytes
11:57:44.409515 IP 192.168.2.121.56203 > 8.8.8.8.53: 17023+ [1au] A?
isc.org. (48)
11:57:44.495663 IP 8.8.8.8.53 > 192.168.2.121.56203: 17023$ 1/0/1 A
149.20.64.69 (52)

Question now, would this be a nice to have feature in dig/nslookup? I
can probably try to prepare a proper patch adding new command line
options to support this.

Is there any other way to achieve the same thing? (without adding
additional explicit routes to the DNS servers...)

Cheers!

-- 
Aleksander
https://aleksander.es


More information about the bind-workers mailing list