Hi osmo_sock experts,
I'm currently looking at making the osmo-hnbgw interface addresses configurable, an I came across a detail. This is not really pressing, but while I'm at it I might as well cover all of the addresses.
So far I see in osmo-iuh's hnbgw.c the local address osmo-hnbgw uses to accept Iuh connections from a 3G cell, and the remote addresses of IuCS and IuPS, each passed to osmo_sock_init():
3G cell -------> 1.2.3.4 OSMO-HNBGW IuPS-local? -------> 10.9.8.7 OSMO-SGSN
So we tell one socket to listen on local 1.2.3.4 for Iuh. We tell another to send IuPS to 10.9.8.7.
But how would I tell the IuPS to use a given local interface 1.2.3.5 to contact the SGSN's address? Would I bind() to a given local IP address and connect() to the remote one? I must admit that the details are not 100% clear to me. AFAIK it can be important to set a local IP address and port to send from. So if there's something I don't understand yet I would appreciate a hint.
Anyway, in osmo_sock_init, I see:
if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) == (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) { fprintf(stderr, "invalid: both bind and connect flags set:" " %s:%u\n", host, port); return -EINVAL; }
Should we have another osmo_sock_init that can set up a socket like this code I found somewhere at sysmocom to define both ends of a connection?
fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { printf("socket() failed: %s\n", strerror(errno)); return -1; }
memset(&bindaddr, 0, sizeof(bindaddr)); bindaddr.sin_family = AF_INET; bindaddr.sin_port = htons(lport); rc = inet_pton(AF_INET, baddr, &(bindaddr.sin_addr)); if (rc != 1) { printf("inet_pton() failed with %i: %s\n", rc, strerror(errno)); return -1; }
rc = bind(fd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)); if (rc < 0) { printf("bind() failed: %s\n", strerror(errno)); return -1; }
memset(&destaddr, 0, sizeof(destaddr)); destaddr.sin_family = AF_INET; destaddr.sin_port = htons(dport); rc = inet_pton(AF_INET, dest, &(destaddr.sin_addr)); if (rc != 1) { printf("inet_pton() failed with %i: %s\n", rc, strerror(errno)); return -1; }
rc = connect(fd, (const struct sockaddr *)&destaddr, sizeof(destaddr)); if (rc < 0) { printf("connect() failed: %s\n", strerror(errno)); return -1; }
rc = send(fd, buf, len, 0); if (rc != len) { printf("send() failed\n"); return -1; }
Thanks,
~Neels
On Thu, Oct 13, 2016 at 11:39:17PM +0200, Neels Hofmeyr wrote:
But how would I tell the IuPS to use a given local interface 1.2.3.5 to contact the SGSN's address?
Please note that on Linux, addresses are always addresses of the overall host / IP stack and not of a specific interface. The routing table is used to determine the local address that should be used for a given destination.
So for (almost?) all reasonably configured systems, it should simply work based on what the system administrator has set.
Would I bind() to a given local IP address and connect() to the remote one?
yes.
AFAIK it can be important to set a local IP address and port to send from.
I think in general this would mostly be a work-around for broken setups.
At least, when talking about TCP clients here. Binding UDP or TCP server sockets is of course a different story
Anyway, in osmo_sock_init, I see:
if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) == (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) { fprintf(stderr, "invalid: both bind and connect flags set:" " %s:%u\n", host, port); return -EINVAL; }
well, because we only pass one pair of host+port parameters into osmo_sock_init(), you cannot bind and connect to that same host:port, unless you insist to only talk to yourself.
Should we have another osmo_sock_init that can set up a socket like this code I found somewhere at sysmocom to define both ends of a connection?
I'm not sure if it should be a new function for both connect + bind at the same time, or we simply bind using osmo_sock_init() and then have another call for the connect?
In any case, this is one of the things I wouldn't bother unless somebody really indicates he needs it.
On Fri, Oct 14, 2016 at 04:41:01PM +0200, Harald Welte wrote:
Would I bind() to a given local IP address and connect() to the remote one?
yes.
In any case, this is one of the things I wouldn't bother unless somebody really indicates he needs it.
Ok, will not bother then.
Out of curiosity...
So normally when I send something, the routing table (ip r show) determines which local interface the remote side sees as sender.
If I connect() and bind() at the same time, would that bypass the routing table and the bind() address would be the sender?
And that's why we don't really need it, because it doesn't make sense to send from a different interface than indicated by the routing table?
On Sun, Oct 16, 2016 at 11:55:33PM +0200, Neels Hofmeyr wrote:
So normally when I send something, the routing table (ip r show) determines which local interface the remote side sees as sender.
Yes. Please note 'ip r show' will only show you 'table main'. Since Linux 2.2.x (yes, ages ago) Linux supports and uses multiple routin tables. See 'ip route show table all' to see all routes that exist in the stack.
If you didn't bind a socket and send a packet, it works more or less like this:
* The kernel performs a route lookup and finds an outbound route for the destination. If the route contains the src parameter, the kernel selects this IP address for the outbound packet.
* If no 'src' parameter is in the route, the kernel will choose the first address configured on the interface which falls in the same network as the destination address or the nexthop router.
See http://linux-ip.net/html/routing-saddr-selection.html
If I connect() and bind() at the same time, would that bypass the routing table and the bind() address would be the sender?
yes, if you explicitly bind before using the socket, you will bypass the regular automatic source address selection of the kernel.
And that's why we don't really need it, because it doesn't make sense to send from a different interface than indicated by the routing table?
well, it sometimes makes sense in more complex scenarios with multi-homed machines, but I think we can ignore that until somebody requests it.
Also, in a lot of cases it makes sense to store such configuration in the routing table itself, rather than inside the applicatin config. Chances are that other applications/services wanting to reach the same destination address/network will have to choose the same source address.
So something like 'ip route add 8.8.8.8/32 via 192.168.100.1 src 1.2.3.4' will teach the routing table to choose the local source address 1.2.3.4 whenever sending packets to 8.8.8.8 and route them via the gateway 192.168.100.1. This configuration is valid system-wide, for all applications/sockets. Please note that 1.2.3.4 must be a valid local address (i.e. one that has been added using 'ip addr add 1.2.3.4 ...' before.
Regards, Harald
On Mon, Oct 17, 2016 at 12:13:19AM +0200, Harald Welte wrote:
On Sun, Oct 16, 2016 at 11:55:33PM +0200, Neels Hofmeyr wrote:
So normally when I send something, the routing table (ip r show) determines which local interface the remote side sees as sender.
Yes. Please note 'ip r show' will only show you 'table main'. Since Linux 2.2.x (yes, ages ago) Linux supports and uses multiple routin tables. See 'ip route show table all' to see all routes that exist in the stack.
yep, that was a gotcha when I started to get used to Freifunk -- scratching my head at the main table until I saw a hundred more routes in tables 'olsr' and 'olsr-default' (and having to fix the table priorities...)
[...]
[...]
So something like 'ip route add 8.8.8.8/32 via 192.168.100.1 src 1.2.3.4' will teach the routing table to choose the local source address 1.2.3.4 whenever sending packets to 8.8.8.8 and route them via the gateway 192.168.100.1. This configuration is valid system-wide, for all applications/sockets. Please note that 1.2.3.4 must be a valid local address (i.e. one that has been added using 'ip addr add 1.2.3.4 ...' before.
Thanks for the excellent explanation and link! It's very clear to me now.