Packet sniffer in Python - python

I want to do a packet sniffer in Python 3.5 which captures UDP, TCP and ICMP. This is a short example of it:
import socket
import struct
# 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
n=1
while(n<=400):
print('Number ', n)
data=s.recvfrom(65565)
packet=data[0]
address= data[1]
header=struct.unpack('!BBHHHBBHBBBBBBBB', packet[:20])
if(header[6]==6): #header[6] is the field of the Protocol
print("Protocol = TCP")
elif(header[6]==17):
print("Protocol = UDP")
elif(header[5]==1):
print("Protocol = ICMP")
n=n+1
The problem is that it only captures UDP packets :(
Output:
Number 1 Protocol = UDP Number 2 Protocol = UDP Number 3 Protocol = UDP Number 4 Protocol = UDP Number 5 Protocol = UDP Number 6 Protocol = UDP Number 7
There are 2 options:
The sniffer can only capture UDP packets.
I'm just receiving UDP packets.
I think that the most logical answer is my sniffer doesn't work correctly and it's just capturing UDP. Any idea?

I myself am in the stage of creating a python packet parser/sniffer and in my research I found that, to be able parse all the incoming packets like TCP, ICMP, UDP, ARP ..etc., you must not use the below socket type because socket.IPPROTO_IP gives out only IP packets and is a dummy protocol
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
rather you must use this and works best with Linux systems
s = socket.socket( socket.AF_PACKET , socket.SOCK_RAW , socket.ntohs(0x0003))

You are using "gethostbyname" and this method obtains one between all ip adresses in your computer.
In place of this method you must use "gethostbyname_ex" that obtains a list with several addreses. You must select the address used for the browser, and you will capure TCP, UDP and ICMP packets.

Related

Python Socket does not receive UDP packets

I am currently setting up a python socket for receiving UDP packets via ethernet and have problems with configuring the socket for obtaining data.
The ethernet adapter is configured with a fixed IP address. Using tcpdump, I am able to view the incoming UDP packets (as seen here) on port 4098, IP 192.168.33.30.
I am using the following example code for the socket:
import socket
UDP_IP = "192.168.33.30"
UDP_PORT = 4098
sock = socket.socket(socket.AF_INET, # Internet
socket.SOCK_DGRAM) # UDP
sock.bind((UDP_IP, UDP_PORT))
while True:
data, addr = sock.recvfrom(4096)
print("received message: %s" % data)
Unfortunately, there is no data return, even if the UDP packets are constantly send to the device. I am using a NVIDIA Jetson NX with Ubuntu 18.04.
So far I have checked the firewall and disabled it completely, but things did not change.
I am thankful for every advice on further troubleshooting!

Windows doesn't receive multicast IPv6 packets from all interfaces

I am trying to receive IPv6 multicast packets (sent to the ff02::1 address) on Windows using this python 2.7 code-
import socket
import win_inet_pton
import struct
socket.IPPROTO_IPV6=41 #because using python 2.7 on wondows
PORT = 1234
UDP_BROADCAST_IPv6 = "ff02::1"
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("",PORT)) # not working with "::" either
# Join multicast group
addrinfo = socket.getaddrinfo(UDP_BROADCAST_IPv6, None)[0]
group = socket.inet_pton(addrinfo[0], addrinfo[4][0])
mreq = group + struct.pack('#I', 0)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
while True:
msg=sock.recv(1024)
print msg
I send packets from another computer that is connected to my computer via Ethernet; in addition, my computer also has a WiFi interface. Although I'm able to see the relevant packets when sniffing the Ethernet connection with Wireshark, the packets are not received by this code.
However, when I disable the WiFi network card, the packets are received.
This makes me think that while the WiFi interface is enabled the code listens only to packets from that interface.
I read that binding to "" should enable receiving packets from all network interfaces, but for some reason it doesn't work for me.
Does anyone have any idea to something that I have forgotten to do? or a different way to solve this?
Thanks!
Solved it :)
So apparently IPv6 doesn't listen to multicast from all interfaces. This syntax
mreq = group + struct.pack('#I', 0)
was wrong. According to this, mreq is composed of the group id and the interface id, where 0 is the default interface (in my case- WiFi). In order to listen to multicast from other interfaces, the network interface index should be specified.
The network interface index is the number thet appears after the % in the ipv6 address when running ipconfig, and can also be found running "route print" in cmd.
I used this code to find it on python:
import netifaces as ni
import _winreg as wr # use "winreg" in python3
def get_ethernet_ipv6_ifindex():
x=ni.interfaces()
con_names=get_connection_name_from_guid(x)
ethernet_index= con_names.index('Ethernet')
addresses= ni.ifaddresses(x[ethernet_index])
brod_addr=addresses[socket.AF_INET6][-1]["broadcast"]
return int(brod_addr[brod_addr.find("%")+1:])
"""
Taken from the very helpful https://stackoverflow.com/questions/29913516/how-to-get-meaningful-network-interface-names-instead-of-guids-with-netifaces-un
"""
def get_connection_name_from_guid(iface_guids):
iface_names = ['(unknown)' for i in range(len(iface_guids))]
reg = wr.ConnectRegistry(None, wr.HKEY_LOCAL_MACHINE)
reg_key = wr.OpenKey(reg, r'SYSTEM\CurrentControlSet\Control\Network\{4d36e972-e325-11ce-bfc1-08002be10318}')
for i in range(len(iface_guids)):
try:
reg_subkey = wr.OpenKey(reg_key, iface_guids[i] + r'\Connection')
iface_names[i] = wr.QueryValueEx(reg_subkey, 'Name')[0]
except WindowsError:
pass
return iface_names
And then-
mreq = group + struct.pack('#I', get_ethernet_ipv6_ifindex())

Python TCP raw socket not listening on lo (localhost/ 127.0.0.1)

I created a simple packet sniffer using raw socket in Python.
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
while True:
print s.recvfrom(1600)
The internet traffic it's showing. But when I turn the primary network interface down and send syn packets using scapy through the lo interface (source and destination 127.0.0.1), There's nothing printed.
Basically I create and send 10 syn packets using scapy whose source and destination is 127.0.0.1, which is visible in wireshark. But not in this sniffer. I thought there might be a problem of the length. So I set the buffer size to a syn packet's size i.e. 74 (s.recvfrom(74)), but still nothing. As soon as I turn the primary network interface up again, it shows all the TCP traffic.
I need to turn off the network interface so that I won't receive any other traffic other than my own created one.
Where I'm going wrong with this?
On Linux :
soc = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(3))
soc.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2**30)
soc.bind(("eth0",0x0003))
Need to open RAW not TCP.
Edit for comment :
a = soc.recvform(65565)[0]
h = binascii.hexlify(a)
if h[24:30] == "080045" and h[46:48] == "06":
# h[24:30] == "080045" Means IP (Type field of Ethernet Header
# combined with IP Version and IP header length)
# h[46:48] == "06" Means TCP (Ip Protocol field of IP Header)
#do something with TCP packet
"080045" mean :
0800 = IP
4 = IP version (IPv4) 5 = Header length (5 words of 4 bytes each)

Raw sockets in python3

I want to write a packet sniffer that sniffs all incoming TCP packets.In one of the examples that I was looking instead of using SOCK_RAW instead of SOCK_STREAM?
try:
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
except socket.error as e:
print('Socket creation failed. Error Code {} Message {}'.format(str(e[0]),str(e[1])))
sys.exit()
#Include IP headers
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
packet = s.recvfrom(65565)
1) In the above case can I use SOCK_STREAM instead of SOCK_RAW.
2) What does recvfrom(65565) mean ?
Does it mean recvfrom all TCP ports instead of a specific TCP port?
If you use SOCK_STREAM instead of SOCK_RAW you won't be able to read the protocols headers, but only the data transmitted via TCP. In the other hand, SOCK_RAW will give you access to the full packet headers. In your case, as you want to build your own protocol analyzer (sniffer), SOCK_RAW should be your choice.
The method definition for recvfrom is:
socket.recvfrom(bufsize[, flags])
Receive data from the socket. The return value is a pair (string,
address) where
string is a string representing the data received and address is the address of the
socket sending the data
This method simply receives maximum bufsize bytes from the socket.

How can I send UDP broadcast when I have two ethernet card?

I have two ethernet cards, and I would like to send out UDP broadcast message on the local network(192.168.0.255) but it seams that the UDP message send out only the first ethernet card.
ip ="255.255.255.255"
UDPSocket = socket.socket( socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP )
UDPSocket.setsockopt( socket.SOL_SOCKET, socket.SO_BROADCAST, True )
UDPSocket.settimeout( timeout )
UDPSocket.sendto( msg.xmlmsg, ( ip, UDPport ) )
UDPSocket.close()
How can I specified that witch subnet would I send the message?
You have to bind the socket to a local address.
For a non-broadcast UDP socket you usually bind to the any address (0.0.0.0). You can also choose a particular port or let the system choose one for you (port 0). If you don't bind the socket explicitly it will be bound automatically to 0.0.0.0:0 on the first send.
To do broadcast is always recommended to do the bind explicitly and specify your own local address of the selected interface. If you do not need a particular port you can use port 0.
The accepted answer doesn't work for me, trying to send ICMP ECHO requests on a raw socket. I had to do this:
target = '8.8.8.8'
packet = bytearray(...)
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
sock.setsockopt(socket.SOL_SOCKET, 25, 'eth0') # or whatever the name of the interface is
# 25 = SO_BINDTODEVICE
# Some Python installations will have the constant IN.SO_BINDTODEVICE which you can use here, YMMV
sock.sendto(packet, (target, 1))

Categories