jamulussoftware / jamulus

Jamulus enables musicians to perform real-time jam sessions over the internet.
https://jamulus.io
Other
997 stars 222 forks source link

Bind to a specific interface/IP #141

Closed marcodmb closed 3 years ago

marcodmb commented 4 years ago

Maybe useful for Jamulus to bind to a specific interface (multipath routing with iproute2, ecc...). At the moment works only (internet side) with the interface related to default gateway. Thank you and great job!

pljones commented 4 years ago

If I remember correctly...

The server just listens on 0.0.0.0:<local port>. So, no routing applicable there, really - if you want to limit connections, your firewall can do that easily enough. Are you saying you want to limit the listen to a specific local IP (i.e. the address of eth0 or whatever)?

The client routes to the target IP <remote IP>:<remote port> using whatever routing rules set up, then uses the local IP and port it gets allocated to listen for replies. You should be able to write routing for already that would ensure the right interface was used, right?

marcodmb commented 4 years ago

No, is not a firewall issue. It's a load balancing setup. My Linux server has 2 public IP addresses IP_A/subnet_A and IP_B/subnet_B. Each public IP has a gateway for the respective subnet. Via iproute2 I've set the proper source based routing rules: Packets from IP_A -> GATEWAY_A and packets from IP_B -> GATEWAY_B. The main routing table has GATEWAY_A as default (not GATEWAY_B), thus if i start a socket with any server on the internet the path is thru link A using IP_A and GATEWAY_A.

Then starting Jamulus I have: root@myserver:~# netstat -anp | grep Jamulus udp 0 0 0.0.0.0:22124 0.0.0.0:* 24720/Jamulus thus Jamulus listens on 0.0.0.0:22124.

If I run Jamulus setting IP_A:22124 as destination on client C just works. But if i run Jamulus setting IP_B:22124 as destination on client C doesn't work. UDP packets reach my server thru link_B but return thru link_A, not link_B! Adding interface/IP binding option to Jamulus should fix this issue and permit load balancing configurations (to lower latecy for example or increase availability, using multiple links and multiple Jamulus intances).

pljones commented 4 years ago

It sounds like you're managing the set up outside the server wrongly, still, to me. Either there is one service endpoint address - and that means one instance of Jamulus, as it's not sharing state - or there are two endpoint addresses, each with one discrete instance.

Client A connects to IP 1, that gets network load balanced once it hits your network - but you need to bring those multiple routes together before arriving at the Jamulus instance handling IP 1.

The server internal IP should not be seen by the client.

marcodmb commented 4 years ago

The setup I need is: Server IP_A <-> Router A <-> Client A Server IP_B <-> Router B <-> Client B Server is the same, IPs are two in this case, IP_A has GATEWAY_A as default. Clients are one, on more that one, for each link, doesn't matter. Either running one on more instance of Jamulus, Client B is unable to connect to the Server. That's it. I don't need to sum bandwidth, only to have one server and multiple gateways and n Jamulus instances that route traffic thru each link.

pljones commented 4 years ago

Have you tried containerising the instances of Jamulus using, say, docker? Each instance would then only have the one interface and you'd route appropriately. That would have the effect you're after. If it still didn't work, it would mean it's not because the binding in Jamulus isn't working.

(Sorry, yes, I now see the issue.)

marcodmb commented 4 years ago

No, I haven't tried yet, but could be a solution indeed. However... this is just "the icing on the cake" for Jamulus ;) Great job!

marcodmb commented 4 years ago

A possibile fix, to this issue, could be replying using as source IP the destination IP contained in the UDP packet sent by the client... In this case binding ip/interface setting is not more necessary.

pljones commented 4 years ago

The problem with that is that a new socket would need creating and destroying for each client, so it could bind the remote address (sendto doesn't take a source address - that's in the socket binding). That would likely introduce some overhead. I suppose the socket could be added to the information for each channel, though... Less overhead in CPU terms, that way, just more storage. I'm not certain "bind" would let you bind an IP that isn't valid on the machine being run on, either.

The client does check that packets received arrive with a source address matching the server it believes it's connected to. So putting the client IP into the source address and not mapping it in your routing would cause issues. (Assuming UDP didn't override the "unknown" source IP with a "valid" IP and put you back to the starting position.)

So not necessarily a simple fix, unfortunately.

pljones commented 4 years ago

OK, I've looked at the server code now and I'll raise a PR for Volker to look at.

pljones commented 4 years ago

OK @marcodmb - I've updated the source on a branch but I've no way to tell if it fixes your problem. Can you pull from https://github.com/pljones/jamulus/tree/feature/141-bind-to-interface-address and test it? --ifaddr 192.168.2.1 or whatever lets you specify the address.

marcodmb commented 4 years ago

Thank you... but unfortunately doesn't work:

Network Error: Cannot bind the socket (maybe the software is already running).

There isn't any running Jamulus process (killall Jamulus). I've got root privileges. [IP_PUB_B] exists and is correctly associated with a physical interface.

Command:

root@myserver:/opt/src/test/jamulus-feature-141-bind-to-interface-address# ./Jamulus -s -n --ifaddr [IP_PUB_B]
- server mode chosen
- no GUI mode chosen
- listen for connections on address : [IP_PUB_B]
Network Error: Cannot bind the socket (maybe the software is already running).
pljones commented 4 years ago

That address doesn't look valid. You need an IPv4 address. Ideally give it the dotted quad.

pljones commented 4 years ago

Nope, okay, not working (yeah, yeah, I could have at least tested with 127.0.0.1...)

pljones commented 4 years ago

Hm. Looks like it needs setsockopt to bind it to the interface address. So I'll have to try again.

pljones commented 4 years ago

And the bad news is I don't see a portable way to do it. It's messy enough on Linux (either just pass the interface name string straight in or take the address and hunt through the list of interfaces to find the name string, then pass it). I'm not finding example of how to set it on Windows at all.

Did you try containerising the instances?

elliotclee commented 4 years ago

The right and portable way to do it should be with the bind() call. Exists on Windows and Linux so probably MacOS too.

In jamulus/src/socket.cpp:

If --ifaddr is passed on the command line its value should be passed in as an optional second ifAddress parameter to CSocket::Init(). In that method, if !bIsClient, use getaddrinfo(ifAddress) to find the value to stuff into the UdpSocketInAddr.sin_addr.s_addr structure member.

https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind http://man7.org/linux/man-pages/man2/bind.2.html

pljones commented 4 years ago

Have you got a patch that works?

pljones commented 4 years ago

I guess that's a "No".

buv commented 3 years ago

I tried to create a patch in pull request #1561