views:

390

answers:

2

Pyzor uses UDP/IP as the communication protocol. We recently switched the public server to a new machine, and started getting reports of many timeouts. I discovered that I could fix the problem if I changed the IP that was queried from eth0:1 to eth0.

I can reproduce this problem with a simple example:

This is the server code:

#! /usr/bin/env python

import SocketServer

class RequestHandler(SocketServer.DatagramRequestHandler):
    def handle(self):
        print self.packet
        self.wfile.write("Pong")

s = SocketServer.UDPServer(("0.0.0.0", 24440), RequestHandler)
s.serve_forever()

This is the client code (188.40.77.206 is eth0. 188.40.77.236 is the same server, but is eth0:1):

>>> import socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
>>> s.sendto('ping', 0, ("188.40.77.206", 24440))
4
>>> s.recvfrom(1024)
('Pong', ('188.40.77.206', 24440))
>>> s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
>>> s.sendto('ping', 0, ("188.40.77.236", 24440))
4
>>> s.recvfrom(1024)
[never gets anything]

The server gets the "ping" packet in both cases (and therefore sends the "pong" packet in both cases).

Oddly, this does work from some places (i.e. I'll get a response from both IPs). For example, it works from 188.40.37.137 (same network/datacenter, different server), but also from 89.18.189.160 (different datacenter). In those cases, the recvfrom response does have the eth0 IP, rather than the one that was connected to.

Is this just a rule of UDP? Is this a problem/limitation with the Python UDPServer class? Is it something I'm doing incorrectly? Is there any way that I can have this work apart from simply connecting to the eth0 IP (or listening on the specific IP rather than 0.0.0.0)?

+2  A: 

I came across this with a TFTP server. My server had two IP addresses facing the same network. Because UDP is connectionless, there can be issues with IP addresses not being set as expected in that situation. The sequence I had was:

  1. Client sends the initial packet to the server at a particular IP address
  2. Server reads the client's source address from the incoming packet, and sends a response.
    1. However, in the response, the server's "source address" is set according to the routing tables, and it gets set to the other IP address.
    2. It wasn't possible to control the server's "source" IP address because the OS didn't tell us which IP address the request came in through.
  3. The client gets a response from the "other" IP address, and rejects it.

The solution in my case was to specifically bind the TFTP server to the IP address that I wanted to listen to, rather than binding to all interfaces.

Edit: found some text that may be relevant in a Linux man page for tftpd (TFTP server). Here it is:

 Unfortunately, on multi-homed systems, it is impossible for tftpd to
 determine the address on which a packet was received. As a result, tftpd
 uses two different mechanisms to guess the best source address to use for
 replies. If the socket that inetd(8) passed to tftpd is bound to a par‐
 ticular address, tftpd uses that address for replies. Otherwise, tftpd
 uses ‘‘UDP connect’’ to let the kernel choose the reply address based on
 the destination of the replies and the routing tables. This means that
 most setups will work transparently, while in cases where the reply
 address must be fixed, the virtual hosting feature of inetd(8) can be
 used to ensure that replies go out from the correct address.  These con‐
 siderations are important, because most tftp clients will reject reply
 packets that appear to come from an unexpected address.
Craig McQueen
That sounds like exactly what is happening here. I wrote a C client that did the same thing as the Python above, and I still get the same results, so the "reject response from other IP" must be happening at the C library level (which effectively means I'm stuck with it).Looks like I'll need to bind to the specific address also. Thanks for the help!
Tony Meyer
+1  A: 

Is this just a rule of UDP?

No.

Is this a problem/limitation with the Python UDPServer class?

Doubtful.

Is it something I'm doing incorrectly?

Your program looks correct.

There are any number of reasons why the datagram isn't getting to the server. UDP is connectionless so your client just sends the ping off into the ether without knowing if anyone receives it.

See if you are allowed to bind to that address. There's a great little program called netcat that works great for low level network access. It's not always available on every system but it's easy to download and compile.

nc -l -s 188.40.77.236 -p 24440 -u

If you run your client program just like before, you should see "Ping" printed on your terminal. (You can type Pong and set it back to your client. It's kinda fun to play with.) If you get the ping, the networking issues aren't the problem and something is wrong with the Python server program or libraries. If you don't get the ping, you can't make the connection. "Contact your network administrator for assistance."

Things to check would include...

  1. Firewall problems?
  2. Configuration issues with aliased network interfaces.
  3. User permission problems.
Steve K
The problem isn't binding to the address, because the server gets the 'ping' - but the client doesn't get the 'pong'.Running netcat bound to that IP works (but so does SocketServer). Running netcat bound to 0.0.0.0 **doesn't** work.
Tony Meyer
I edited the question to make it clearer that the problem is with the response, not the server receiving the client's packet.
Tony Meyer