I'm just trying out zeromq for a project in python. I'm trying to bind the 'client' side and connect the server to the fixed location. I have a simple REQ/REP setup that works fine locally but seems to do nothing over a network. If I reverse the binding, then this also works over the network.
The relevant code is:
def respond(sock):
message = sock.recv()
response = "world"
sock.send(response)
print("Received '{0:s}', sent '{1:s}'.".format(message, response) )
def request(sock):
message = "Hello"
sock.send(message)
response = sock.recv()
print("Sent '{0:s}', recieved '{1:s}'".format(message, response) )
def main():
opts = get_opts()
if opts.client:
sock = CONTEXT.socket(zmq.REQ)
sock.bind("tcp://*:{0:d}".format(opts.port) )
request(sock)
if opts.server:
sock = CONTEXT.socket(zmq.REP)
sock.connect("tcp://{0:s}:{1:d}".format(opts.address, opts.port) )
while True:
respond(sock)
And a (non-)working example is here: https://gist.github.com/4071783
When connecting to a remote address, nothing seems to happen. If I check with tcpdump, I can certainly see activity on the port:
12:20:18.846927 IP server.58387 > client.5555: Flags [.], ack 1, win 3650, options [nop,nop,TS val 718051 ecr 46170252], length 0
12:20:18.847156 IP client.5555 > server.58387: Flags [P.], seq 1:3, ack 1, win 227, options [nop,nop,TS val 46170252 ecr 718051], length 2
12:20:18.847349 IP server.58387 > client.5555: Flags [P.], seq 1:3, ack 1, win 3650, options [nop,nop,TS val 718051 ecr 46170252], length 2
12:20:18.847373 IP client.5555 > server.58387: Flags [.], ack 3, win 227, options [nop,nop,TS val 46170252 ecr 718051], length 0
12:20:18.847553 IP client.5555 > server.58387: Flags [P.], seq 3:16, ack 3, win 227, options [nop,nop,TS val 46170252 ecr 718051], length 13
12:20:18.847645 IP server.58387 > client.5555: Flags [.], ack 3, win 3650, options [nop,nop,TS val 718051 ecr 46170252], length 0
12:20:18.848286 IP server.58387 > client.5555: Flags [.], ack 16, win 3650, options [nop,nop,TS val 718051 ecr 46170252], length 0
But the send() and recv() are still blocked as if waiting for a connection. Does anyone know what this is or could suggest how to debug it?
Can you ping the remote address? 0MQ is just using TCP at this level so if the connect fails, it's because the address you're trying to connect to is not reachable.
Related
I'm following Black Hat Python (2ed.), in which I'm writing a network scanning tool. The tool is in theory supposed to send UDP packets out to a given subnet, and if a host is up on that subnet, the response packet is decoded, found to contain the message in the original datagram, and used to indicate the host is up. This seems to generally be working well to capture packets; I can go to a website, or ping another host, and the tool reliably provides the correct source and destination addresses for those cases.
Here is the meat of the code (I have not included the class creation, or the passing of the host argument for brevity, but the host is 192.168.10.85).
class IP:
"""layer 3 (IP) packet header decoder"""
def __init__(self, buff=None):
header = struct.unpack('<BBHHHBBH4s4s', buff)
self.ver = header[0] >> 4
self.ihl = header[0] & 0xF
self.tos = header[1]
self.len = header[2]
self.id = header[3]
self.offset = header[4]
self.ttl = header[5]
self.protocol_num = header[6]
self.sum = header[7]
self.src = header[8]
self.dst = header[9]
# make IP addrs human readable
self.src_address = ipaddress.ip_address(self.src)
self.dst_address = ipaddress.ip_address(self.dst)
# the protocol_num is actually a code for the protocol name
self.protocol_name = {1: 'ICMP', 6: 'TCP', 17: 'UDP'}
# try to provide the human version of the protocol, otherwise just give the code
try:
self.protocol = self.protocol_name[self.protocol_num]
except KeyError as error:
self.protocol = self.protocol_num
print(f'Protocol is unrecognized, try googling "IP protocol {self.protocol_num}"')
class ICMP:
"""layer 4 (ICMP) packet header decoder"""
def __init__(self, buff):
header = struct.unpack('<BBHHH', buff)
self.type = header[0]
self.code = header[1]
self.checksum = header[2]
self.ident = header[3]
self.seq_num = header[4]
def udp_sender():
# blasts udp packets into the network to solicit responses
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sender:
for ip in ipaddress.ip_network(SUBNET).hosts():
# time.sleep(1)
print(f'sending a test message to {ip}')
# send our test message out to port 65212 on the destination
sender.sendto(bytes(MESSAGE, 'utf8'), (str(ip), 65212))
class Scanner:
def __init__(self, host):
self.host = host
# create raw socket, bind to public interface
# if windows:
if os.name == 'nt':
socket_protocol = socket.IPPROTO_IP
# if linux/mac:
else:
socket_protocol = socket.IPPROTO_ICMP
self.socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)
self.socket.bind((host, 0))
# socket options, include header
self.socket.setsockopt(socket_protocol, socket.IP_HDRINCL, 1)
# enable promiscuous mode for windows
if os.name == 'nt':
self.socket.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
def sniff(self):
# set of all hosts that are up (respond to our ICMP message)
hosts_up = {f'{str(self.host)} *'}
try:
while True:
# read a packet, and parse the IP header
raw_buffer = self.socket.recvfrom(65535)[0]
# create IP header from the first 20 bytes
ip_header = IP(raw_buffer[0:20])
# if the protocol is ICMP, do some additional things
# print(f'src={ip_header.src_address}, dst={ip_header.dst_address}, prot_name={ip_header.protocol}')
if ip_header.protocol == 'ICMP':
# calculate where the ICMP packet starts
offset = ip_header.ihl * 4
buf = raw_buffer[offset:offset + 8]
# create ICMP structure
icmp_header = ICMP(buf)
print(f'type: {icmp_header.type}, code: {icmp_header.code}')
print(f'src={ip_header.src_address}, dst={ip_header.dst_address}, prot_name={ip_header.protocol}')
if icmp_header.type == 3 and icmp_header.code == 3:
print(f'type: {icmp_header.type}, code: {icmp_header.code}')
print(f'src={ip_header.src_address}, dst={ip_header.dst_address}, prot_name={ip_header.protocol}')
if ipaddress.ip_address(ip_header.src_address) in ipaddress.IPv4Network(SUBNET):
# make sure the packet has our test message
if raw_buffer[len(raw_buffer) - len(MESSAGE):] == bytes(MESSAGE, 'utf8'):
tgt = str(ip_header.src_address)
if tgt != self.host and tgt not in hosts_up:
hosts_up.add(str(ip_header.src_address))
print(f'Host Up: {tgt}')
However, when receiving the ICMP responses as a result of my datagram, the tool reports that the source and destination addresses are the same (my host, 192.168.10.85). Furthermore, while I should be receiving responses with Type 3 and Code 3 (destination unreachable, and port unreachable), but I am receiving (in my program) Type 3 and Code 1.
Here is an example of the output when I issue a ping command while the scanner is running, which seems correct:
src=192.168.10.85, dst=192.168.10.200, prot_name=ICMP type: 0, code: 0 src=192.168.10.200, dst=192.168.10.85, prot_name=ICMP type: 8, code: 0
Here is an example of the output to what I am assuming is the UDP packet response, which seems incorrect):
src=192.168.10.85, dst=192.168.10.85, prot_name=ICMP type: 3, code: 1
If I open wireshark while I'm running my code, I can correctly see the ICMP Type 3/Code 3 responses, so I know they are going through, here is a screen grab of one host on the target subnet as an example:
Why is my scanner not seeing these responses that are in wireshark?
I've tried running wireshark alongside my program, to see if the packets are being correctly decoded, and that the message in the UDP packet is properly in place. All signs indicate that the packets are going out to the hosts I'm trying to detect, and the correct responses are coming back, but my scanner refuses to find them.
I have threaded Python 2.7.12 code that waits for connections from external devices, and then spins off a thread to work with each device. 80% of the time this works fine, but every once in a while I get unwanted RST from the OS or Python, I'm not sure which.
The TCP connection starts off normally and then abrubptly is reset.
SYN, SYN/ACK, ACK, RST
The server initiates the RST. The timing is also extremely tight so I feel like this is probably Python's socket bugging out or the OS taking over for some reason. How could I debug this? Since I'm not seeing any errors in my Python code and logging itself, is there a ways to debug the sockets code directly used by Python?
Here is the code:
while True:
host = socket.gethostbyname(socket.gethostname())
port = 3001
try:
tcpsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcpsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcpsock.bind((host,port))
tcpsock.listen(5)
print ("\nListening for incoming connections...")
(clientsock, (ip, port)) = tcpsock.accept()
newthread = ClientThread(ip, port, clientsock)
newthread.start()
threads = 0
for t in threading.enumerate():
threads += 1
logger.info('######################## THREADS = %s' % (threads))
except Exception as e:
logger.critical('Exception: %s. Error initializing thread' % (e))
EDIT1 - Adding ClientThread code:
class ClientThread(threading.Thread):
def __init__(self,ip,port,clientsocket):
threading.Thread.__init__(self)
self.ip = ip
self.port = port
self.csocket = clientsocket
The rest of the code just starts working with the device.
The RST packet means "I don't have a connection open for the packet you just sent me". You're right, if you follow the simple examples on python.org, then Python will generate RST packets. For example:
#!/usr/bin/python2.7
import socket, sys, time
# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Bind the socket to the port
server_port = 10000
print 'starting up on port %s' % server_port
sock.bind(('', server_port))
# Listen for incoming connections
sock.listen(1)
print 'waiting for a connection'
conn, cli_addr = sock.accept()
print 'connection from', cli_addr
data = "Hello, World!\n" \
+ "Your IP is " + cli_addr[0] + "\n"
HTTP = "HTTP/1.1 200 OK\nContent-Type: text/html\nContent-Length:"
conn.sendall(HTTP + str(len(data)) + "\n\n" + data);
conn.close()
tcpdump then says:
10:59:48.847443 IP 127.0.0.1.38330 > 127.0.0.1.10000: Flags [S], seq 1326297161, win 43690, options [mss 65495,sackOK,TS val 3126487416 ecr 0,nop,wscale 7], length 0
10:59:48.847462 IP 127.0.0.1.10000 > 127.0.0.1.38330: Flags [S.], seq 1677934801, ack 1326297162, win 43690, options [mss 65495,sackOK,TS val 3126487416 ecr 3126487416,nop,wscale 7], length 0
10:59:48.847475 IP 127.0.0.1.38330 > 127.0.0.1.10000: Flags [.], ack 1, win 342, options [nop,nop,TS val 3126487416 ecr 3126487416], length 0
10:59:48.847547 IP 127.0.0.1.38330 > 127.0.0.1.10000: Flags [P.], seq 1:80, ack 1, win 342, options [nop,nop,TS val 3126487416 ecr 3126487416], length 79
10:59:48.847557 IP 127.0.0.1.10000 > 127.0.0.1.38330: Flags [.], ack 80, win 342, options [nop,nop,TS val 3126487416 ecr 3126487416], length 0
10:59:48.847767 IP 127.0.0.1.10000 > 127.0.0.1.38330: Flags [P.], seq 1:95, ack 80, win 342, options [nop,nop,TS val 3126487416 ecr 3126487416], length 94
10:59:48.847790 IP 127.0.0.1.38330 > 127.0.0.1.10000: Flags [.], ack 95, win 342, options [nop,nop,TS val 3126487416 ecr 3126487416], length 0
10:59:48.847861 IP 127.0.0.1.10000 > 127.0.0.1.38330: Flags [R.], seq 95, ack 80, win 342, options [nop,nop,TS val 3126487416 ecr 3126487416], length 0
10:59:48.847862 IP 127.0.0.1.38330 > 127.0.0.1.10000: Flags [F.], seq 80, ack 95, win 342, options [nop,nop,TS val 3126487416 ecr 3126487416], length 0
10:59:48.847878 IP 127.0.0.1.10000 > 127.0.0.1.38330: Flags [R], seq 1677934896, win 0, length 0
The problem is that conn.close() destroys the socket data structure, so the OS doesn't recognize the next packet. Notice that the client (curl) is sending a nice FIN packet, but the server (Python code above) is not.
To solve this, you need to send a FIN packet by calling conn.shutdown(socket.SHUT_WR) and then leave the socket structure around long enough to catch any straggling packets (you could do this in another thread). So instead of conn.close(), do:
conn.shutdown(socket.SHUT_WR)
time.sleep(1)
conn.close()
With this change, both sides send FIN, and the RST are missing from tcpdump:
11:05:50.201352 IP 127.0.0.1.38338 > 127.0.0.1.10000: Flags [S], seq 4249130338, win 43690, options [mss 65495,sackOK,TS val 3126848770 ecr 0,nop,wscale 7], length 0
11:05:50.201368 IP 127.0.0.1.10000 > 127.0.0.1.38338: Flags [S.], seq 1410528158, ack 4249130339, win 43690, options [mss 65495,sackOK,TS val 3126848770 ecr 3126848770,nop,wscale 7], length 0
11:05:50.201381 IP 127.0.0.1.38338 > 127.0.0.1.10000: Flags [.], ack 1, win 342, options [nop,nop,TS val 3126848770 ecr 3126848770], length 0
11:05:50.201441 IP 127.0.0.1.38338 > 127.0.0.1.10000: Flags [P.], seq 1:80, ack 1, win 342, options [nop,nop,TS val 3126848770 ecr 3126848770], length 79
11:05:50.201450 IP 127.0.0.1.10000 > 127.0.0.1.38338: Flags [.], ack 80, win 342, options [nop,nop,TS val 3126848770 ecr 3126848770], length 0
11:05:50.201568 IP 127.0.0.1.10000 > 127.0.0.1.38338: Flags [P.], seq 1:95, ack 80, win 342, options [nop,nop,TS val 3126848770 ecr 3126848770], length 94
11:05:50.201594 IP 127.0.0.1.38338 > 127.0.0.1.10000: Flags [.], ack 95, win 342, options [nop,nop,TS val 3126848770 ecr 3126848770], length 0
11:05:50.201643 IP 127.0.0.1.10000 > 127.0.0.1.38338: Flags [F.], seq 95, ack 80, win 342, options [nop,nop,TS val 3126848770 ecr 3126848770], length 0
11:05:50.201691 IP 127.0.0.1.38338 > 127.0.0.1.10000: Flags [F.], seq 80, ack 96, win 342, options [nop,nop,TS val 3126848770 ecr 3126848770], length 0
11:05:50.201707 IP 127.0.0.1.10000 > 127.0.0.1.38338: Flags [.], ack 81, win 342, options [nop,nop,TS val 3126848770 ecr 3126848770], length 0
I would like to send IGMP packets using scapy, specifically IGMP Leave, IGMP Membership report. Is it possible to do so?
UPDATE:
I was able to eventually generate them. Had to do the following:
1) Install scapy v.2.2.0 as it's described here (including minor alteration in setup.py):
scapy's contrib is missing after installing scapy on both windows and fedora
2) You need to use file from contribution package (features not added to the core of scapy):
import scapy.contrib.igmp
igmpPacket = scapy.contrib.igmp.IGMP()
Yes, it is possible to send IGMP packets. After googling a bit, I came up with some useful links that can help you in some direction.
On github there exists a IGMP and IGMPv3 implementation in scapy. Here is an interesting mailing list too. Also, this post has an other interesting stuff related to IGMP.
With this method, you can send out IGMP version 2 (RFC2236) Membership Query message, not IGMP version 3.
Here are complete code and tcpdump:
>>> from scapy.all import *
>>> import scapy.contrib.igmp
>>> p = IP(dst="62.22.14.4")/scapy.contrib.igmp.IGMP()
>>> send(p)
.
Sent 1 packets.
>>>
# tcpdump -ni cplane0 igmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on cplane0, link-type EN10MB (Ethernet), capture size 262144 bytes
18:42:01.045618 IP 44.60.11.3 > 62.22.14.4: igmp query v2 [max resp time 20]
18:42:01.045631 IP 44.60.11.3 > 62.22.14.4: igmp query v2 [max resp time 20]
18:42:01.046470 IP 44.60.11.3 > 62.22.14.4: igmp query v2 [max resp time 20]
18:42:01.046476 IP 44.60.11.3 > 62.22.14.4: igmp query v2 [max resp time 20]
18:42:01.959331 IP 62.22.14.4 > 224.1.1.1: igmp v2 report 224.1.1.1
Update:
Since IGMPv3 is under construction. Here is a way to send IGMP version 3 Membership Query:
>>> from scapy.all import *
>>>
>>> class IGMP3(Packet):
... name = "IGMP3"
... fields_desc = [ ByteField("type", 0x11),
... ByteField("mrtime", 20),
... XShortField("chksum", None),
... IPField("gaddr", "0.0.0.0"),
... IntField("others", 0x0)]
... def post_build(self, p, pay):
... p += pay
... if self.chksum is None:
... ck = checksum(p)
... p = p[:2]+chr(ck>>8)+chr(ck&0xff)+p[4:]
... return p
...
>>> bind_layers( IP, IGMP3, frag=0, proto=2)
>>> p = IP(dst="62.21.20.21")/IGMP3()
>>> send(p)
.
Sent 1 packets.
>>>
# tcpdump -ni cplane0 igmp -v
tcpdump: listening on cplane0, link-type EN10MB (Ethernet), capture size 262144 bytes
17:24:35.013987 IP (tos 0x0, ttl 62, id 1, offset 0, flags [none], proto IGMP (2), length 32)
44.60.11.3 > 62.21.20.21: igmp query v3 [max resp time 2.0s]
17:24:35.014000 IP (tos 0x0, ttl 62, id 1, offset 0, flags [none], proto IGMP (2), length 32)
44.60.11.3 > 62.21.20.21: igmp query v3 [max resp time 2.0s]
17:24:35.014476 IP (tos 0x0, ttl 62, id 1, offset 0, flags [none], proto IGMP (2), length 32)
44.60.11.3 > 62.21.20.21: igmp query v3 [max resp time 2.0s]
17:24:35.014482 IP (tos 0x0, ttl 62, id 1, offset 0, flags [none], proto IGMP (2), length 32)
44.60.11.3 > 62.21.20.21: igmp query v3 [max resp time 2.0s]
17:24:35.218208 IP (tos 0xc0, ttl 1, id 0, offset 0, flags [DF], proto IGMP (2), length 40, options (RA))
62.21.20.21 > 224.0.0.22: igmp v3 report, 1 group record(s) [gaddr 239.1.1.1 is_ex, 0 source(s)]
Similar to Setting TCP receive window in C and working with tcpdump in Linux and Why changing value of SO_RCVBUF doesn't work?, I'm a unable to increase the initial tcp receive window greater than 5888 on ubuntu linux 2.6.32-45
#!/usr/bin/python
from socket import socket, SOL_SOCKET, SO_RCVBUF, TCP_WINDOW_CLAMP
sock = socket()
sock.setsockopt(SOL_SOCKET, SO_RCVBUF, 65536)
sock.setsockopt(SOL_SOCKET, TCP_WINDOW_CLAMP, 32768)
sock.connect(('google.com', 80))
tcpdump says:
me > google: Flags [S], seq 3758517838, win 5840, options [mss 1460,sackOK,TS val 879735044 ecr 0,nop,wscale 6], length 0
google > me: Flags [S.], seq 597037042, ack 3758517839, win 62392, options [mss 1430,sackOK,TS val 541301157 ecr 879735044,nop,wscale 6], length 0
me > google: Flags [.], ack 1, win 92, options [nop,nop,TS val 879735051 ecr 541301157], length 0
sysctl -a | grep net.*mem says:
net.core.wmem_max = 131071
net.core.rmem_max = 131071
net.core.wmem_default = 112640
net.core.rmem_default = 112640
net.core.optmem_max = 10240
net.ipv4.igmp_max_memberships = 20
net.ipv4.tcp_mem = 77376 103168 154752
net.ipv4.tcp_wmem = 4096 16384 3301376
net.ipv4.tcp_rmem = 4096 87380 3301376
net.ipv4.udp_mem = 77376 103168 154752
net.ipv4.udp_rmem_min = 4096
net.ipv4.udp_wmem_min = 4096
Could there be something else putting a receive window limit on my connection?
That looks like the effects of TCP slow-start. This kernel thread from 2008 offers up a good explanation (I've linked to the last response in the thread):
SO_RCVBUF doesn't change receiver advertised window
If you keep watching the stream, the window size should increase, up to the maximum you set.
I have a raw Python socket initialized like so:
mySocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
mySocket.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
My problem is that I can't send any packet from the socket where the protocol field in my IP header is specified as '1' (ICMP). If this field is anything else it'll work just fine, including '17' (UDP) for example. I don't get any errors, the packet just doesn't display in Wireshark, and I can't receive it on my server.
I'm generating the IP header like so:
def getIPHeader(src, dst, proto):
version = 4
IHL = 5
DSCP = 0
ECN = 0
totalLength = 40
identification = 333
flags = 0
fragmentOffset = 0
timeToLive = 128
protocol = proto
headerChecksum = 0
sourceIP = socket.inet_aton(src)
destIP = socket.inet_aton(dst)
options = 0
version_IHL = (version << 4) | IHL
DSCP_ECN = (DSCP << 2) | ECN
flags_fragmentOffset = (flags << 13) | fragmentOffset
# The '!' ensures all arguments are converted to network byte order
header = struct.pack("!BBHHHBBH4s4s", version_IHL, DSCP_ECN, totalLength, identification, flags_fragmentOffset, timeToLive, protocol, headerChecksum, sourceIP, destIP)
return header
And sending like so:
icmpHeader = struct.pack("!bbHHh", 8, 0, 0, 666, 0)
packet = getIPHeader("192.168.2.15", "8.8.8.8", 1) + icmpHeader
mySocket.sendto(packet, ("8.8.8.8", 0))
Sending with UDP protocol set works (obviously malformed):
icmpHeader = struct.pack("!bbHHh", 8, 0, 0, 666, 0)
packet = getIPHeader("192.168.2.15", "8.8.8.8", 17) + icmpHeader
mySocket.sendto(packet, ("8.8.8.8", 0))
The worst part is if I send a ping from the command line, then copy the exact hex from Wireshark of the IP and ICMP data into my code, it STILL doesn't work, like so:
packet = b'\x45\x00\x00\x3c\x24\xad\x00\x00\x80\x01\x43\x4d\xc0\xa8\x02\x0f\x08\x08\x08\x08\x08\x00\x4d\x44\x00\x01\x00\x17\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x61\x62\x63\x64\x65\x66\x67\x68\x69'
mySocket.sendto(packet, ("8.8.8.8", 0))
However, as before, if I change the 10th byte of the hard coded data to '\x11' (17 decimal, UDP), I can then see it in Wireshark (obviously with a malformed UDP section as the rest of the packet is ICMP).
I'm running Windows 10, Python 3.4.1, and my firewall is off. Any ideas?
UPDATE: I've tried this on two different computers, a Windows 8 and a Windows 10 machine, both worked, so it's something to do with my computer, the plot thickens...