Search code examples
pythonnetwork-programmingipdata-link-layer

Communicate on the data link layer (prior to obtaining IP address) with Python


Is it possible, with Python, to communicate directly on the data link layer, prior-to or outside of, an IP address? Similarly to communicating with USB?

I have a client interested in trying this. As far I can know, there's no way. But I never want to underestimate the power of Python.


Solution

  • There's nothing intrinsic about Python which prevents you from writing your own user-level networking stack. However, if you want to say, access the raw ethernet driver to send raw ethernet packets, that has to be supported by the operating system.

    I'll try to paint a vague picture of what's going on. Some of this you may know already (or not). A conventional operating system provides an abstraction called the system call layer to allow programs to interact with hardware. This abstraction is typically somewhat "high level" in that it abstracts away some of the details of the hardware. In an operating system which implements Unix abstractions, one of the network abstraction system calls is socket(int domain, int type, int proto), which creates a new socket endpoint. What's getting abstracted away here? Well, for most protocols, dealing with data-link layer details becomes unnecessary. Obviously you lose some flexibility here so that you can gain safety (you don't have to worry about clobbering other OS data structures if you have raw hardware access) and convenience (most people don't need to implement a user-level networking stack).

    So, whether it "can" be done without modifying your kernel depends on what abstractions are provided by the OS. Linux provides the packet(7) interface which allows you to use AF_PACKET as your socket domain. According to the man page, "Packet sockets are used to receive or send raw packets at the device driver (OSI Layer 2) level."

    So can this be accessed in Python? You bet!

     import socket
     s = socket(socket.AF_PACKET, socket.SOCK_RAW)
     s.bind(("eth1", 0))
    

    s should now be a socket which you can use to send raw packets. See the other Stack Overflow post for more information about how to do this -- they do a better job than I can. It looks like this technique should work on Windows as well, as I suspect they provide a similar abstraction.