Can Python select what network adapter when opening a socket?

36,283

Solution 1

On Windows, if you know the IP address of the interface you want to use, just bind to that before you connect. On Linux,use socket option SO_BINDTODEVICE as suggested by JimB (seems to be a privileged call too).

i.e. on Windows

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('192.168.0.1', 0))
s.connect(('...'))

Binding source address under Windows, selects the interface with the same IP address as that device, even if that IP address has a higher routing metric cost. This doesn't work under Linux though, as it always overwrites the source address with the IP address of the selected device. Routing is done based solely on the destination address. The only exception it seems is if you set source address to 127.0.0.1, then Linux prevents these packets from going out of that box.

Solution 2

I can't speak much for Windows, but on Linux the interface is normally not chosen until a routing decision is made, therefore you usually don't have a say on which interface your packets leave.

You do have the option though, of using SO_BINDTODEVICE (see man 7 socket) on Linux. This binds a socket to a device, however, only root can set this option on a socket.


Just checked, and the python socket library doesn't have SO_BINDTODEVICE defined, but you get it from socket.h:

# from socket.h
# define SO_BINDTODEVICE 25

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, 25, 'eth0')

See also:

Share:
36,283
jenny
Author by

jenny

I am an agile software developer who loves to make ideas come to life. My experience ranges from embedded software written in C all the way up to responsive web development. Python, C, JavaScript, and HTML are my most fluent languages that have been honed over 7+ years of professional development. When my top programing languages don't make sense for a given project, I fall back on my experience in C# (.NET), C++ and VB.NET to get the job done. I pride my self in creating clean, efficient, and testable code. I've worked for small companies my entire career and am comfortable working collaboratively or solo to get things done.

Updated on March 26, 2020

Comments

  • jenny
    jenny about 4 years

    The target machine running the python application will have three network interfaces available to it. In general all three networks will be vastly different, however there is a possibility that two of the three could be on similar networks.

    In the example below I do not have control over the destination address on ETH 2 (as it a pre-configured system), so I forced into selecting what adapter to use programmaticly.

    I am fairly sure that this will fall on how the OS works with routing the connections. My hope is that there will be a platform independent way to solve the issue using python, because there is a possibility that this application will need to run on Windows 7 as well as a Linux machine.

    Example Code

    import socket
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('192.168.0.2', 8000)) # Which device will this connect to??
    

    Normal Case

    • ETH 0 Source: 192.168.0.1
    • ETH 0 Destination: 192.168.0.2
    • ETH 1 Source: 10.20.30.1
    • ETH 1 Destination: 10.20.30.2
    • ETH 2 Source: 60.50.40.1
    • ETH 2 Destination: 60.50.40.1

    Possible Trouble Case

    • ETH 0 Source: 192.168.0.1
    • ETH 0 Destination: 192.168.0.2
    • ETH 1 Source: 10.20.30.1
    • ETH 1 Destination: 10.20.30.2
    • ETH 2 Source: 192.168.0.3
    • ETH 2 Destination: 192.168.0.2

    Additional Information
    Adapters ETH0,1,and 2 are all connected to different physical netoworks

  • JimB
    JimB over 12 years
    Addresses are system-wide, and binding to a specific address doesn't choose the interface from which the packets leave. That decision is made during routing.
  • jenny
    jenny over 12 years
    Well this is exactly what I did not want to hear (about the routing). The good news is that the target system is now more than likely going to be Linux. Thanks for the example and links, both very helpful.
  • Lloyd Macrohon
    Lloyd Macrohon over 12 years
    Typically, you have a unique ip address per device. SO_BINDTODEVICE is Linux specific, so this was another way around it.
  • JimB
    JimB over 12 years
    The question was how to select the network adapter, which bind does not do. Bind sets the address/port for your socket, that's all. The interface that receives the packets will most likely be the one you want, but your outgoing packets will leave via the interface determined during routing, usually the one with the default GW.
  • Lloyd Macrohon
    Lloyd Macrohon over 12 years
    Yes, that bit, I know. I just assumed that if you set the source address, the routing sub-system would pick an entry in the routing table that has at least a route back to it. If it's done solely on destination address. Then this won't work.
  • Lloyd Macrohon
    Lloyd Macrohon over 12 years
    I just did a quick python test on Linux. Bind source address to 127.0.0.1 and send to an external address (goes through default route). Connect fails with Invalid argument. Bind source address to IP address on wlan0, then it works. So it looks like it takes into account source address, not just destination address. I will have to try this in C later to see if this is a Python issue or a system issue, but I have to run for now.
  • jenny
    jenny over 12 years
    I too am looking into this (I am gathering a few switches and laptops). When you are testing this, try to use something other than local host as well for another data point.
  • Lloyd Macrohon
    Lloyd Macrohon over 12 years
    I've done my experiment on my laptop (Linux: eth0 and wlan0), and on my desktop (Windows: 2 ethernet cards), i.e. no loopback. Monitored traffic with Wireshark. Linux will use the source address of the device it went through. Windows allows you to bind a source address and will choose the device with that IP address, even if that device has a higher routing metric. I did both tests in C but Python should be the same. So my suggestion is to use SO_BINDTODEVICE on Linux and bind to IP address on Windows.
  • jenny
    jenny over 12 years
    Great information and thanks for testing your thoughts. I ran a few similar tests on Windows 7 and XP and concur. When I have my Linux boxes up I will repeat the tests.
  • jenny
    jenny over 12 years
    If you don't mind, can you move your findings up to your answer? I find it very useful and I'm sure others will as well.
  • ninjalj
    ninjalj over 12 years
    Linux can base its routing decisions also on source address, that's part of what policy routing allows, see ip rule.
  • Gearoid Murphy
    Gearoid Murphy over 5 years
    You can use socket.SO_BINDTODEVICE instead of the raw value of '25'