Is it possible to send and receive packets through

2019-08-19 02:23发布

问题:

I googled for a while and read related questions here about sending and receiving through sockets, but couldn't find an answer to my question:

Can I send and receive packets through different sockets?

I would like to implement something like this:

HOST='127.0.0.1'
PORT=10000
recvSock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
sendSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

sendSock.sendto("",(HOST,PORT))
response,addr = recvSock.recvfrom(65535)

Basically, I'd like to send a message to an echo server through sendSock which is UDP socket, and receive the answer from the server through recvSock which is a raw socket.

Is it possible? If yes, then how? the code about doesn't work.

回答1:

Let's back up a bit, because as written your question doesn't make much sense, but I think I know what you're trying to do.

When you created a UDP socket and sent a message on it, this did two things: First, it picked an appropriate source address, with an arbitrary port from the ephemeral range, and bound your socket to that address. (A UDP address is a host IP and a port.) Then it sent a message from that source address to the destination address.

When the remote side responds, it will send a message to that source address. The kernel receives that message and sees that it's aimed at the address your UDP socket is bound to. So it delivers the message to that socket.

That means recvfrom will only work on that socket, not another one. Just calling recvfrom on a different socket that isn't bound to anything won't do any good.

What if you could bind two sockets to the same address? Well, you can, by using the socket options SO_REUSEADDR and/or SO_REUSEPORT. See this question for some (highly platform-specific) details on when it's allowed with each option. But, even when it's allowed, what happens when a message is received for an address that has two sockets bound to it? Well, that's also highly platform-specific. In theory, the message is delivered to one of the sockets arbitrarily. In practice, on some platforms, the kernel will remember who most recently sent to source address of the response, or to any address on the same network as the source address, or who bound most recently, or some other rule, and deliver to that one. So, depending on your platform, that may or may not help you—you may be able to bind one socket, use it to sendto, then bind the second socket, then recvfrom on the second and get the response. Or it may not. And if it doesn't do what you want, don't try to fight with your kernel; there are better ways to go if you want to get under the covers.

What if you bind to a more inclusive address? For example, you could bind the first socket to ('127.0.0.1', 12345) and then bind the second to ('0.0.0.0', 12345), and those are different addresses, right? Well, that's basically interpreted similarly to binding them both to the same sockets—the platform-specific differences are explained in the same answer I linked above, and the behavior if you're allowed to do it will be the same as if you'd used the same address. And on most platforms, this is true even if you bind a (raw) socket to an interface rather than an address.

On top of this, classic BSD raw sockets can't even receive UDP (or TCP) packets. Unless you build a packet filter to reroute them, they're always handled by the kernel and delivered to a UDP or TCP socket (or to the firewall, if there is none). The Raw IP Networking FAQ explains exactly what you can and can't receive according to the BSD raw socket protocol as originally defined. Fortunately, most modern platforms go beyond the original BSD socket protocol—unfortunately, they all do so in different ways.

You can solve both of these problems by putting the socket into promiscuous mode, which means it will get all packets on its interface before they get routed. (The way to do this is also different on each platform.) It's up to you to then figure out which ones are going to get routed to your UDP socket and process them appropriately. (This isn't too hard with UDP; with TCP it gets more complicated because of the fact that TCP packets are stored and put back in order before being delivered to the socket.)

An easier solution is to use your platform's packet filter interface.

Or, even easier, use something like libpcap, which has cross-platform wrappers for packet capturing.

Or, even easier, use something like scapy, which has Python wrappers around libpcap and everything else necessary, or wireshark, a separate program which you can automate from Python. Going this way makes it trivial to capture exactly the packets that will be delivered to your socket and parse the headers and everything else you want to do.

Or, some platforms provide a way to get the headers on all packets on UDP sockets. This requires using recvmsg instead of recvfrom, because the headers are delivered as "ancillary data" rather than as part of the main receive buffer. Python 3.3 has recvmsg; earlier versions do not, and you will have to use ctypes or some third-party wrapper (or build your own wrapper).