1
votes

I'm trying to implement a basic UDP protocol wherein a Sender sends a UDP datagram to a Service, which then sends a response datagram back, using the source address and source port from the incoming datagram.

Normally you'd have the Sender also listen for the response on that port. But I want the response to be picked up by a separate program (the Listener) also running on that host. So:

  1. On host A, Listener starts and binds to port 12345, and blocks on recvfrom.
  2. On host A, Sender sends datagram to Service running on host B, setting the source address and port to host A, port 12345.
  3. Service on host B sends a response to host A port 12345.
  4. Response is picked up by Listener.

Setting the source address and port is done by binding to them. So I need both Sender and Listener to bind to the same port. Setting SO_REUSEADDR in both allows this. Note that I'm not using multicast here.

But the responses aren't reliably being picked up by Listener. There are two exceptions I've observed:

I find that if the Sender closes the socket immediately after sending the first datagram, then the response will get to the Listener.

Alternatively, if the Sender is started first and binds before the Listener, the responses will get picked up by the Listener.

I've been working from examples from the internet and haven't found documentation that clearly describes what should happen. But a few places I've seen have hinted that, for Unicast, only the most recent process to bind to the port will receive datagrams sent to it.

My question is, can I send UDP datagrams so that responses (sent using the source address and port) will be picked up by another process? If the above process can't be made to work, is there a way to set the source information on an outgoing datagram without binding to that port?

A few other points:

  • Each process should be started independently and be able to be restarted without interfering with the other. So I don't think I can have one open the socket and spawn the other.
  • I don't need to receive packets from both processes. One process only sends, and the other only receives.
  • Ideally, the solution would be portable enough to run on common Unixes and Windows.
  • Finally, if it's simply not possible then I'll fall back to using a single process to perform both functions. I'm not too stressed about it but I am interested in doing it if it is possible somehow. :-)

Networking code follows...

Sender code

void run(Options *options)
{
    struct sockaddr_in si_me, si_other;
    int s;
    socklen_t slen = sizeof(si_other);
    int reuse = 1;
    struct hostent *he;

    if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
        die("socket");

    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) != 0)
        die("setsockopt");

    // Bind to the "listen port", so that outgoing datagrams have the correct source information
    memset((char *) &si_me, 0, sizeof(si_me));
    si_me.sin_family = AF_INET;
    si_me.sin_port = htons(options->listen_port);
    si_me.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(s, (struct sockaddr *) &si_me, sizeof(si_me)) != 0)
        die("bind");

    memset((char *) &si_other, 0, sizeof(si_other));
    si_other.sin_family = AF_INET;
    si_other.sin_port = htons(options->service_port);

    if (!(he = gethostbyname2(options->service_host, AF_INET)))
        die("gethostbyname2");

    memmove(&si_other.sin_addr.s_addr, he->h_addr, he->h_length);

    while (1)
    {
        int len;
        char *buf;

        // Create outgoing message in buf
        ...

        if (sendto(s, buf, len, 0, (struct sockaddr *) &si_other, slen) == -1)
            die("sendto");
    }
    close(s);
}

Listener code

static void run(Options *options)
{
    struct sockaddr_in si_me, si_other;
    int s;
    socklen_t slen = sizeof(si_other);
    char buf[BUFLEN];
    int reuse = 1;

    if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
        die("socket");

    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) != 0)
        die("setsockopt");

    // Bind to the same "listen port" to pick up responses to datagrams sent by Sender
    memset((char *) &si_me, 0, sizeof(si_me));
    si_me.sin_family = AF_INET;
    si_me.sin_port = htons(options->listen_port);
    si_me.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(s, (struct sockaddr *) &si_me, sizeof(si_me)) == -1)
        die("bind");

    while (1)
    {
        int nr;

        nr = recvfrom(s, buf, BUFLEN, 0, (struct sockaddr *) &si_other, &slen);
        if (nr == -1)
            die("recvfrom");

        // Process the received message
        ...
    }

    close(s);
}

A related question is Using netcat to send a UDP packet without binding, where one answer seems to suggest it should be possible using SO_SOCKADDR, but did not quite explain how it would work in my case.

2
Instead of having each process open its own socket, have them share a socket.David Schwartz
Hmm, how would I go about that, and can that be done if each is started independently?Edmund
Have them both started by a parent that creates the socket and allows it to be inherited.user207421
@Edmund You can use libancillary to do it. There are other ways that are more platform-specific.David Schwartz
@DavidSchwartz - thanks that looks like it might work.Edmund

2 Answers

0
votes

is there a way to set the source information on an outgoing datagram without binding to that port?

There is no portable way. A solution for Linux, using IP_PKTINFO, is this answer to How to re bind a udp socket in Linux.

-1
votes

1: You can send from different port on B

A binds 12345 sends to B:12345

B:12345 - process 1 - recv 
B:12346 - process 2 - send to A:12345

2: You can construct packet with fake back address with raw sockets

First solution is better