4

I am an experienced Linux socket programmer and am writing a server application which has many outgoing interfaces. Now server socket binds to a random source port in the start of the process along with INADDR_ANY.

Later at some point when submitting response to a specific node, i need to assign a fixed source ip address. The standard way to do this is calling bind. However, bind is called once for the port number, successive calls fail with invalid argument error.

Creating a new socket is not really a good choice since i will have to be doing this very often upon responding to some clients.

I have also explored SO and a lot of socket options such as IP_FREEBIND, but it doesn't quite suite my scenario.

Perhaps using IP_PKT_INFO and setting source address might work unless it suffers the same problem i.e. not allowing a socket once bound to INADDRANY to rebind to a fixed source ip latter.

Is there a way to unbind an existing socket or an alternate way to setting source ip address in outgoing packet?

    int sock = socket(AF_INET, SOCK_DGRAM, 0);

    if(sock < 0)
        printf("Failed creating socket\n");

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(1500);
    addr.sin_addr.s_addr = INADDR_ANY;

    // first bind succeeds
    if ( (status = bind(sock, (struct sockaddr *) &addr, sizeof(addr))) < 0)
        printf("bind error with port %s\n", strerror(errno));  

    struct sockaddr_in src_addr;
    memset(&src_addr, 0, sizeof(struct sockaddr_in));
    src_addr.sin_family = AF_INET;
    if (inet_aton("10.0.2.17", &(src_addr.sin_addr)) == 0)
        printf("Failed copying address\n");

    // second bind fails
    if((status = bind(sock, (struct sockaddr *)&src_addr, sizeof(src_addr))) < 0)
        printf("re bind error with ip %s\n", strerror(errno));

Any ideas in this regard will be highly appreciated. I have gone through considerable material on sockets, SO etc. but no success yet.

fkl
  • 5,412
  • 4
  • 28
  • 68
  • Does it not bind to the interface that received to the packet after recvfrom? – CrazyCasta Oct 03 '12 at 18:56
  • If there are multiple interfaces on the same LAN then create a socket pool with one for each NIC. A bit more detail is appreciated. – Steve-o Oct 03 '12 at 18:57
  • Thank you! Separate sockets are being used for sending and receiving so binding to interface as a result of recvfrom has no impact. And i actually have a socket pool too. But still any one of them could be required to choose an externally provided source ip. This is application requirement. – fkl Oct 03 '12 at 19:01
  • @fayyazkl "But still any one of them could be required to choose an externally provided source ip." Are you saying you need to handle source ips other than those already associated with network interfaces? – CrazyCasta Oct 04 '12 at 03:39
  • @CrazyCasta. No it is one of those on the interfaces. The reason i mentioned "externally provided" was that a configuration list is specified in the form of destination ip: source ip. If current outgoing packet destination is one of the destination ip's, we pick the corresponding source ip from list and use it as socket's source. Otherwise, we use SOBINDTODEVICE for attaching it to a pre configured interface and send out. – fkl Oct 04 '12 at 04:13
  • Well, a lot of people view this, but no one comments, up or down votes or does any thing. This was probably my longest piece of code on SO and a pretty hard found one. – fkl Oct 15 '13 at 09:19

3 Answers3

7

I finally found the solution myself so accepting my own answer (shameless but correct plugin), supplemented with code sample.

I originally wanted to rewrite source address of an outgoing packet without creating the socket again where the socket was already bound. Calling bind multiple times fail for this case, and (in my particular situation), i was not able to just have separate sockets for each source ip and use it.

I found some references in IP_PACKET_INFO but it was a pain to get it to work correctly. Following reference was helpful.

Setting source of udp socket

Sample Code

Here is a trivial application which creates a udp socket, binds it to a local port, then before sending a particular message, it appends the outgoing source ip address. Keeping in mind that in my case, i created a sudo interface and assigned it another ip. The send call will fail if this is not the case.

int status=-1;
int sock = socket(AF_INET, SOCK_DGRAM, 0);

if(sock < 0)
    printf("Failed creating socket\n");

int opt = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

struct sockaddr_in bind_addr;
memset(&bind_addr, 0, sizeof(struct sockaddr_in));
bind_addr.sin_family = AF_INET;
bind_addr.sin_port = htons(44000); // locally bound port

if((status = bind(sock, (struct sockaddr *)&bind_addr, sizeof(bind_addr))) < 0)
    printf("bind error with port %s\n", strerror(errno));

// currently using addr as destination
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(80); // destination port
if (inet_aton("74.125.236.35", &(addr.sin_addr)) == 0)
    printf("Failed copying remote address\n");
else
    printf("Success copying remote address\n");

struct sockaddr_in src_addr;
memset(&src_addr, 0, sizeof(struct sockaddr_in));
src_addr.sin_family = AF_INET;
if (inet_aton("10.0.2.17", &(src_addr.sin_addr)) == 0)
    printf("Failed copying src address\n");
else
    printf("Success copying src address\n");

char cmbuf[CMSG_SPACE(sizeof(struct in_pktinfo))];

char msg[10] = "hello";
int len = strlen(msg);

struct msghdr mh;
memset(&mh, 0, sizeof(mh));

struct cmsghdr *cmsg;
struct in_pktinfo *pktinfo;

struct iovec iov[1];
iov[0].iov_base = msg;
iov[0].iov_len = len;

mh.msg_name = &addr; // destination address of packet
mh.msg_namelen = sizeof(addr);
mh.msg_control = cmbuf;
mh.msg_controllen = sizeof(cmbuf);
mh.msg_flags = 0;
mh.msg_iov = iov;
mh.msg_iovlen = 1;

// after initializing msghdr & control data to 
// CMSG_SPACE(sizeof(struct in_pktinfo))
cmsg = CMSG_FIRSTHDR(&mh);
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg);

//src_interface_index 0 allows choosing interface of the source ip specified
pktinfo->ipi_ifindex = 0;
pktinfo->ipi_spec_dst = src_addr.sin_addr;

int rc = sendmsg(sock, &mh, 0);
printf("Result %d\n", rc);

The key statement is

pktinfo->ipi_spec_dst = src_addr.sin_addr;

where we are specifying the source ip address to be used. The rest of things like cmsg struct etc. are merely used in order to be able to write ipoktinfo struct ourselves

fkl
  • 5,412
  • 4
  • 28
  • 68
  • 1
    Well found! I had not realized that was possible, even though the `man 7 ip` man page does describe the option. You don't really need superuser rights: the `CAP_NET_ADMIN` capability is enough, i.e. `sudo setcap cap_net_admin=pe binary` is enough, no need to set it setuid/setgid root. However, is there a reason you do not just send the response using a new socket, bound to the desired address? With UDP, you do not need to send the response using the same socket the request was received on, as there is no connection, just packets. Or does the source port of the response matter to the clients? – Nominal Animal Oct 10 '12 at 03:55
  • Thank you, for one, the source port matters. Secondly, in my scenario it is possible that the socket bound to that address might be servicing another direct request while being bound to one of the interfaces say eth2. Where as this particular request needs to go out some other interface. upvote? :) – fkl Oct 10 '12 at 04:50
  • Because there are no connections, just datagrams, you can use `sendto()` to send a response to any address and port from a bound socket. You can use a single socket to send responses to different clients, even simultaneously. Responses do not need to use the same socket the request was received to. I still do not understand why you don't have a fixed set of sockets bound to the desired source addresses and ports. Perhaps it would be easier to see what I mean if I wrote simple example server/client as a new answer? – Nominal Animal Oct 10 '12 at 09:04
  • Thank you. I perfectly understand your suggestion and don't need reference code. Perhaps my explanation is not up to par. Primarily, i receive requests on a single request listening socket. Against each request there is a corresponding struct allocated, which contains about 3 outgoing sockets each of which is bound to a different source port. For the life of this request, these 3 outgoing sockets might have to send messages to several destinations and receive status responses (not to be confused with plain requests). Now i have like 10-15 interfaces. I cannot have 15 sockets per query – fkl Oct 11 '12 at 06:04
  • with each bound to one of the interfaces. I need this per query because source port for all 3 sockets are different for each query. So a single socket bound to one source ip cannot be used by multiple queries. Lastly, even if this was a viable solution, it is a completed product. Modifying primary query struct and how it handles sockets is going to take a month or so of development and about the same amount of time refining. And even that still doesn't get around the "dynamic source port for each request's response" issue. Thanks again for injecting the energy. Appreciate that. – fkl Oct 11 '12 at 06:04
4

There is no way to unbind and rebind an existing socket.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • Thank you. I will get back on this with in a day. That was helpful instead of me still thinking there might be a way. – fkl Oct 04 '12 at 03:23
  • By the way isn't it ironic that i can still use SOBINDTODEVICE repeatedly and effectively bind it to any outgoing interface? I am actually doing it in my application successfully but cannot call bind() again. – fkl Oct 04 '12 at 06:44
  • 1
    Take a look at the solution i found using IP_PACKETINFO and sendmsg. It is not precisely rebind call, but does the job i.e. i can rewrite my outgoing ip address on every packet. – fkl Oct 07 '12 at 17:58
  • Thought of adding tag for the author to be notified i.e. @EJP – fkl Oct 11 '12 at 06:14
  • I was able to change src ip address of outgoing packet. Have posted the solution. So i have accepted my own answer instead. @EJP i would certainly welcome if you or any one else has any criticism. – fkl Oct 13 '12 at 10:44
  • 2
    Just for your information: Some systems do allow UDP sockets to be unbound. Just bind them to an address of type AF_UNSPEC. While this returns an error, the man page says it may be ignored. Afterwards the socket is unbound and can be rebound. Of course this is not and officially standardized behavior and thus not useful for portable code. – Mecki Jan 22 '13 at 22:59
  • Thanks @Mecki. I some how misesd the comment and only read it again after a long time. But basically i found a more standard way and the application is quite portable across many linux distrubutions as well as freebsd, so i am contended with it. Take a look at my answer below http://stackoverflow.com/a/12771043/986760 – fkl Jul 11 '13 at 08:56
  • Just clarifying again, THE ABOVE ANSWER DOES NOT HOLD, since we cannot unbind an existing socket, but we CAN MODIFY the outgoing source ip. Example code is in my solution. – fkl Oct 15 '13 at 09:21
  • 1
    The above answer DOES HOLD because there is no way to rebind an already bound socket [other than on the systems referred to by @Mecki], and DOES NOT STATE that there is no alternative way to modify the outgoing IP address. And PLEASE LAY OFF THE SHOUTING. – user207421 Jul 15 '16 at 18:01
  • 1
    OP wrote: _Is there a way to unbind an existing socket or an alternate way to setting source ip address in outgoing packet?_ Though your answer is true, it does not answer this exact question. – Armali Sep 02 '16 at 06:23
1

Why don't you create a socket for each interface instead? Since the UDP/IP protocol is connectionless, you can choose the source IP address by choosing which socket you use to send the reply with; there is no need to use the same socket the incoming datagram was received on.

The downsides are that you can no longer bind to the wildcard address, and you must use select(), poll(), multiple threads, or some other mechanism to receive datagrams from multiple sources concurrently. You'll also need some logic to efficiently pick the socket based on the client IP address.

In most cases, I suspect that adding a few route entries to route each remote IP address to the desired host IP address, and using a separate socket for each host IP address and port combination, solves the issues perfectly -- and using the very efficient kernel functionality to do so. While the behaviour may be an application requirement, I suspect it is better solved using the network interface configuration instead. Unfortunately, often the requirements are written by semi-functional idiots better suited for manual labor, and your hands are tied.. if so, I commiserate.

If you have a test network with workstations having multiple physical network interfaces, I can provide a simple example C99 test program you can use to verify the design works.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • Thanks a lot for another opinion. Originally i went on with socket per interface strategy. However, there are protocol / application specific requirements preventing this. To give an idea, since i can only elaborate here to some general limit, For the sake of robustness, the server which further has to query a list of destinations, has to do it in parallel i.e. at least 3 requests from server are sent sequentially (without waiting for a response for any one of them). Now each of these requires a separate socket, which is there (where as there is another listening socket for incoming resp. – fkl Oct 04 '12 at 18:30
  • Each of these separate sockets might need to be bound to any of the given interfaces and this keeps changing. Say first 3 requests went out and failed. Now there is a sorted list from which i will pick the next 3 destinations each of which could be bound to a different interface. In practice this server runs on a machine which has about 10-12 interfaces and for each incoming request, i allocate 3 sockets which live for the life of that particular request although they might have to bound to any of the interfaces at random. – fkl Oct 04 '12 at 18:32
  • To add insult to injury, it is working and running in production actually until this simple looking requirement came i.e. apart from all 3 simultaneous queries and dynamic binding to interfaces, if configuration specifies a destination ip: src ip list, where your current dest matches one of the list entries, then use src ip as outgoing ip. So there is a limit to what i can change in a well developed and production tested application. The requirements guys thought it was as simple as calling another bind before releasing the packet once it is identified. So much for simple requirements. – fkl Oct 04 '12 at 18:35
  • @fayaazkl: I think I understand. Yes, a typical "simple requirement".. As I see it, the dest-ip:source-ip list should really be configured in the routing table, instead of the application. That way your service binds to the wildcard address, and the clients see the correct source IP address. See `man 8 route` for details. The routing table can be updated in real time, even under control of your application, if so desired. I wouldn't, though. – Nominal Animal Oct 04 '12 at 21:03
  • I found details of IP_PACKETINFO and sendmsg and got it working. Please take a look at following code when you can. Any opinions are really appreciated. Thanks – fkl Oct 07 '12 at 17:56