-4

I have my machine set up with a running VPN client and want to connect to hosts in the internet either through the VPN tunnel or directly via the local interface by specifying the bind address on the sockets. Consider the following code sample:

import socket, subprocess, re

def get_ipv4_address():
    ifc_resp = subprocess.Popen(["ifconfig"], stdout=subprocess.PIPE).communicate()
    patt = re.compile(r'inet\s*\w*\S*:\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})')
    return patt.findall(ifc_resp[0])

def check_sock(addr):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind((addr, 0))
    s.connect(("www.google.com", 80))
    s.close()

def main():
    addrs = sorted(a for a in get_ipv4_address() if not a.startswith("127.0."))
    print "Checking addresses " + ", ".join(addrs)
    for addr in addrs:
        print "Connecting via " + addr
        check_sock(addr)

main()

When I run this, the connection can be set up without problems through the VPN IP address. Though the program hangs on connecting to the local network on connect:

>>> python binddemo.py 
Checking addresses 10.200.195.233, 192.168.2.33
Connecting via 10.200.195.233
Connecting via 192.168.2.33
^CTraceback (most recent call last):
  File "binddemo.py", line 22, in <module>
    main()
  File "binddemo.py", line 20, in main
    check_sock(addr)
  File "binddemo.py", line 12, in check_sock
    s.connect(("www.google.com", 80))
  File "/usr/lib/python2.7/socket.py", line 228, in meth
    return getattr(self._sock,name)(*args)
KeyboardInterrupt

I don't understand why this is happening, as the local NIC should still have a route correctly set to reach out to the internet:

>>> ip route
default via 10.200.195.1 dev tun0  proto static  metric 50 
default via 192.168.2.1 dev wlan0  proto static  metric 600 
10.200.195.0/24 dev tun0  proto kernel  scope link  src 10.200.195.233  metric 50 
62.113.253.4 via 192.168.2.1 dev wlan0  proto static  metric 600 
169.254.0.0/16 dev wlan0  scope link  metric 1000 
192.168.2.0/24 dev wlan0  proto kernel  scope link  src 192.168.2.33  metric 600 

Connections specified to a plain IP address (instead of the google host) equally fail, so I suppose it's not the DNS lookup making trouble. Any ideas how to fix that or at least further investigate?

Michael
  • 7,316
  • 1
  • 37
  • 63

2 Answers2

2

Using bind() on a client socket that is not going to listen() is not doing anything. What interface is used for outgoing connections is entirely dependent on your routing configuration.

You have setup two default gateways but the one that goes to your wlan interface has a higher metric than your tunneling device. So linux is always going to use the tuneling interface as the default gateway.

Here is a good, more in-depth explanation: https://serverfault.com/questions/648276/routing-selection-specificity-vs-metric

[Edit 1]

After looking at your routing table more closer, removing the default gateway for the tunneling device should solve your problem, because you also have a net route setup that should route all traffic to your vpn correctly. So try removing this one:

default via 10.200.195.1 dev tun0  proto static  metric 50 

because this one will route your vpn traffic through tun0:

10.200.195.0/24 dev tun0  proto kernel  scope link  src 10.200.195.233  metric 50 

[Edit 2]

If you want to keep the default route via the tunneling devices, you could use network namespaces: https://superuser.com/questions/241178/how-to-use-different-network-interfaces-for-different-processes/750412#750412

You will still not be able to access both networks from a single program though.

Community
  • 1
  • 1
  • Is there no way to preserve the default route and still specify other routes on demand? I'd prefer to keep the default through the VPN. – Michael Feb 21 '17 at 10:22
  • The problem is: your vpn has a limited adress range (10.200.195.0/24) so you can setup a net route. The internet has not :) so you usually point your default gw to the internet-facing router. – Christian Eichelmann Feb 21 '17 at 10:24
  • I mean, in the end I want to bypass the routing configuration of the machine and select between different VPNs and their gateways how to route the traffic programmatically. Relying on the routing won't be of help in any way to me. I had hoped that I can do that by specifying different outgoing IP addresses for the sockets. – Michael Feb 21 '17 at 10:28
  • I'd also be fine with specifying virtual devices, like for `ping -I dev`. – Michael Feb 21 '17 at 10:30
  • Well if you want to do it per process, modern kernels support `network namespaces`. This has nothing to do with python, you can just bind different processes to different interfaces. Check out this answer: http://superuser.com/questions/241178/how-to-use-different-network-interfaces-for-different-processes/750412#750412. Just be aware that you can't access both networks from the same process. – Christian Eichelmann Feb 21 '17 at 10:33
  • Thay may work out, though I still hope to find a solution that works for one process as well. After reading a bit around your link, I hope that it may work out to set up proper policy routing, based on source IP, combined with the sockets that I already configured. So, thanks for the hint! – Michael Feb 21 '17 at 10:55
  • Probably similar like that one: https://kindlund.wordpress.com/2007/11/19/configuring-multiple-default-routes-in-linux/ – Michael Feb 21 '17 at 11:00
  • What finally worked out: – Michael Feb 21 '17 at 12:15
1

What finally worked out:

echo "1 localwlan" >> /etc/iproute2/rt_tables
ip route add default table localwlan via 192.168.2.33 dev wlan0
ip rule add from 192.168.2.33 lookup localwlan

Thanks @Christian Eichelman for pointing me in the right direction

Michael
  • 7,316
  • 1
  • 37
  • 63