48

I am writing an application to test a network driver for handling corrupted data. And I thought of sending this data using raw socket, so it will not be corrected by the sending machine's TCP-IP stack.

I am writing this application solely on Linux. I have code examples of using raw sockets in system-calls, but I would really like to keep my test as dynamic as possible, and write most if not all of it in Python.

I have googled the web a bit for explanations and examples of the usage of raw sockets in python, but haven't found anything really enlightening. Just a a very old code example that demonstrates the idea, but in no means work.

From what I gathered, Raw Socket usage in Python is nearly identical in semantics to UNIX's raw socket, but without the structs that define the packets structure.

I was wondering if it would even be better not to write the raw socket part of the test in Python, but in C with system-calls, and call it from the main Python code?

Avihu Turzion
  • 3,284
  • 4
  • 25
  • 34

8 Answers8

62

You do it like this:

First you disable your network card's automatic checksumming:

sudo ethtool -K eth1 tx off

And then send your dodgy frame from python 2 (You'll have to convert to Python 3 yourself):

#!/usr/bin/env python
from socket import socket, AF_PACKET, SOCK_RAW
s = socket(AF_PACKET, SOCK_RAW)
s.bind(("eth1", 0))

# We're putting together an ethernet frame here, 
# but you could have anything you want instead
# Have a look at the 'struct' module for more 
# flexible packing/unpacking of binary data
# and 'binascii' for 32 bit CRC
src_addr = "\x01\x02\x03\x04\x05\x06"
dst_addr = "\x01\x02\x03\x04\x05\x06"
payload = ("["*30)+"PAYLOAD"+("]"*30)
checksum = "\x1a\x2b\x3c\x4d"
ethertype = "\x08\x01"

s.send(dst_addr+src_addr+ethertype+payload+checksum)

Done.

brice
  • 24,329
  • 7
  • 79
  • 95
  • 2
    I suspect this is wrong; that the AF_PACKET interface does not exposes the checksum part to userspace, so it is automatically generated and checked by the driver/hardware, and there is nothing we could do about it. Wireshark says the "checksum" I sent is part of "data". – lvella Mar 27 '13 at 03:31
  • 1
    It depends on your hardware/driver. Some drivers will ignore a checksum if already present, while some will take it as part of data. It's actually a pain in the neck to do reliably across different drivers. This problem will exist in a C implementation also. Not putting the checksum seems to always results in the correct behaviour (ie: the packet checksum is calculated by the driver/hardware). – brice Mar 27 '13 at 10:36
  • 2
    That doesn't make much sense. In the drivers that could handle an explicit checksum, how is it supposed to know that those 4 null bytes in the end (or any 4 bytes, for that matters) is to be used as the checksum? Even if the bytes matches the correct checksum, this would imply the driver has to calculate it, but appends it to the frame only if the last 4 bytes are not already what it calculated: a highly unlikely and error prone behavior. – lvella Mar 27 '13 at 13:22
  • 1
    you can configure the behaviour yourself, depending on the card/driver. Have a look at [ethtool](http://linux.die.net/man/8/ethtool). It will be something like `ethtool -K tx off` (on Linux anyway...) – brice Mar 27 '13 at 13:27
  • 1
    Oh, so if you don't explicitly disable automatic checksum generation on the interface, you must not send it via AF_PACKET. I think this information should be noted in the answer... or not, because it talks specifically about Python. – lvella Mar 27 '13 at 13:32
  • 1
    AF_PACKET is correct, and is the only one that really makes sense. As per [man page](http://linux.die.net/man/7/packet): *Packet sockets are used to receive or send raw packets at the device driver (OSI Layer 2) level.* – brice Mar 27 '13 at 13:38
  • Shouldn't it be `sendto()` instead of `send()`? – Ricky Robinson Sep 23 '13 at 11:00
  • I get the error: `Destination: 00:00:5e:00:01:01` `sock.send(dst_addr+src_addr+ethertype+payload+checksum)` `Address: 00:00:5e:00:01:01` `TypeError: a bytes-like object is required, not 'str'` . Could you help ? – Ahmed Hussein Feb 24 '19 at 13:01
  • 1
    @AhmedHussein This code was written in Python 2.7. You're using Python 3.x, which handles strings and bytes differently. See https://docs.python.org/3/howto/pyporting.html – brice Feb 25 '19 at 16:20
  • I can't test the Python3 version right now, I'm on Mac which doesn't have AF_PACKET. – brice Feb 25 '19 at 16:26
10

Sockets system calls (or Winsocks, on Windows), are already wrapped in the standard module socket: intro, reference.

I've never used raw sockets but it looks like they can be used with this module:

The last example shows how to write a very simple network sniffer with raw sockets on Windows. The example requires administrator privileges to modify the interface:

import socket

# the public network interface
HOST = socket.gethostbyname(socket.gethostname())

# create a raw socket and bind it to the public interface
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
s.bind((HOST, 0))

# Include IP headers
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

# receive all packages
s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

# receive a package
print s.recvfrom(65565)

# disabled promiscuous mode
s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
Bastien Léonard
  • 60,478
  • 20
  • 78
  • 95
  • This is exactly the code example unwind links to in his answer. It's a good one, but I need to know how I send raw data. In all my attempts based on these examples, the raw data I built simply wasn't sent. Even when I tried sending raw sampled packets, that did work instead of building my own. – Avihu Turzion Jul 13 '09 at 06:59
  • With raw sockets, you would also have to build the IP & TCP or UDP header yourself. Maybe you are just trying to send your application data and hoping the OS would do the rest for you. Once, you use raw sockets, all bets are off. Everything has to be done by you. – Aditya Sehgal Jul 13 '09 at 17:24
  • and when I said TCP or UDP, I really meant any protocol on top of IP that you want to build yourself. – Aditya Sehgal Jul 13 '09 at 17:24
  • 1
    @Amey: you should write this in a new question. Personally, I can't help you. – Bastien Léonard Jul 13 '11 at 00:14
2

Is this the old code you mentioned finding? It looks sensible to me, but I haven't tested it myself (or used raw sockets much). This example from the documentation shows how to use raw sockets to sniff packets, and it looks similar enough.

unwind
  • 391,730
  • 64
  • 469
  • 606
  • 1
    **This is exactly the code example I was talking about!**. It's problem is that it's use of initilizations of the socket, and protocols are completely deprecated. Moreover, I think it's a sort of mish-mash of server and client sides into one concept app. – Avihu Turzion Jul 13 '09 at 06:55
  • Right, it seems the AF_PACKET address family is no longer around. The second example (reproduced in Bastien's answer) uses AF_INET, I guess that might make sense. If all you want to test is sending data (to check for e.g. pattern-based corruption or whatever), the addressing shouldn't matter. I think the first example "mixes concepts" by doing a totally synchronous send followed by receive call. Not very common or nice, but should be legal. – unwind Jul 13 '09 at 07:03
2

Eventually the best solution for this case was to write the entire thing in C, because it's not a big application, so it would've incurred greater penalty to write such a small thing in more than 1 language.

After much toying with both the C and python RAW sockets, I eventually preferred the C RAW sockets. RAW sockets require bit-level modifications of less than 8 bit groups for writing the packet headers. Sometimes writing only 4 bits or less. python defines no assistance to this, whereas Linux C has a full API for this.

But I definitely believe that if only this little bit of header initialization was handled conveniently in python, I would've never used C here.

Avihu Turzion
  • 3,284
  • 4
  • 25
  • 34
  • 10
    This doesn't answer the question. – Humphrey Bogart Mar 15 '10 at 23:45
  • I agree that as an overall point of view of whether Python RAW sockets are preferred to C RAW sockets, this doesn't answer the question. But eventually this related to the problem I was dealing with at the time, and this solved the issue, and hence was the correct answer as far as I'm concerned. – Avihu Turzion Mar 17 '10 at 08:07
  • Nope, have a look at [this question](http://stackoverflow.com/questions/6329583/how-to-reliably-generate-ethernet-frame-errors-in-software) – brice Jun 16 '11 at 15:54
1
s = socket(AF_PACKET, SOCK_RAW)
s = socket(PF_PACKET, SOCK_RAW)

result:

[root@localhost python]# tcpdump -i eth0

capture size 96 bytes
11:01:46.850438 

01:02:03:04:05:06 (oui Unknown) > 01:02:03:04:05:06 (oui Unknown), ethertype Unknown (0x0801), length 85:

        0x0000:  5b5b 5b5b 5b5b 5b5b 5b5b 5b5b 5b5b 5b5b  [[[[[[[[[[[[[[[[
        0x0010:  5b5b 5b5b 5b5b 5b5b 5b5b 5b5b 5b5b 5041  [[[[[[[[[[[[[[PA
        0x0020:  594c 4f41 445d 5d5d 5d5d 5d5d 5d5d 5d5d  YLOAD]]]]]]]]]]]
        0x0030:  5d5d 5d5d 5d5d 5d5d 5d5d 5d5d 5d5d 5d5d  ]]]]]]]]]]]]]]]]
        0x0040:  5d5d 5d00 0000 00                        ]]]....
ACC
  • 2,488
  • 6
  • 35
  • 61
gteng
  • 11
  • 1
0

The socket class should help. If not you'll need to either write a Python module in C or just use C. See http://mail.python.org/pipermail/python-list/2001-April/077454.html.

Basic Googling found that.

I actually tried the code example which "unwind" has pointed out. AF_PACKET did work for me in python 2.7.4

Clay Bridges
  • 11,602
  • 10
  • 68
  • 118
  • 4
    I have reviewed this post on my first googling escapades, and didn't find that very helpful. Most the discussion is there is very out-dated. `AF_PACKET` no longer appears in Python. – Avihu Turzion Jul 13 '09 at 06:53
  • Note that AF_PACKET may not be defined for python 2.7, but it is defined in python 3. – Generic Ratzlaugh Aug 25 '20 at 21:38
0

You can use this Python library: rawsocketpy it allows using raw sockets on Layer 2 => No IP/TCP/UDP headers.

#!/usr/bin/env python
from rawsocketpy import RawSocket

sock = RawSocket("wlp2s0", 0xEEFA)
sock.send("some data")
sock.send("personal data", dest="\xAA\xBB\xCC\xDD\xEE\xFF")

or the server form:

#!/usr/bin/env python
from rawsocketpy import RawRequestHandler, RawAsyncServerCallback
import time

def callback(handler, server):
    print("Testing")
    handler.setup()
    handler.handle()
    handler.finish()

class LongTaskTest(RawRequestHandler):
    def handle(self):
        time.sleep(1)
        print(self.packet)

    def finish(self):
        print("End")

    def setup(self):
        print("Begin") 

def main():
    rs = RawAsyncServerCallback("wlp2s0", 0xEEFA, LongTaskTest, callback)
    rs.spin()

if __name__ == '__main__':
    main()
Alexis Paques
  • 1,885
  • 15
  • 29
0

FTR, if you want level 2 access (Ethernet, RadioTap...), that won't be possible natively on Windows (as of today).

If you want to access those with a cross platform method, the go-to choice is libpcap and its Python bindings (as it will use Npcap/WinPcap to work on Windows).

You have a variety of Python bindings available for libpcap, at all sorts of levels (very high or very low).

My advice would be to use the Sockets of scapy (even if you're not using it to dissect), that implement both Native and Libpcap calls, (and let you chose against them with conf.use_pcap = True)

from scapy.all import conf
# conf.use_pcap = True (will be automatic if required)
socket = conf.L2socket(iface="eth0")
# On any platforms, you have `get_if_list()` in `scapy.all` available, to see the ifaces available. You could also ignore it to use the default one
Cukic0d
  • 5,111
  • 2
  • 19
  • 48