Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TCP port information to NetAddr #12

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

bgola
Copy link

@bgola bgola commented Jul 20, 2020

Currently it is not possible to directly access an open TCP socket from sclang. This RFC proposes a way to access the metadata of the socket created when calling NetAddr.connect method so the user can listen to messages coming back from that socket.


# Motivation

One of the differences between TCP and UDP is that in UDP the connecton is usually not kept alive. The sender of a message simply sends the UDP package and does not bother if the package arrives, is actually sent, or even if the host exists. In TCP a connection between both points must be made before any packages are sent, and this connection is kept open until one of the ends closes it. This usually means that the sender opens a random port locally to receive incoming data from the other end of the TCP connection. One of the advantages of TCP is that a host behind a NAT or a complex network routing can open a connection to a public IP on the internet and, because the connection is two-way, the host receiving the connection can send messages back through the opened channel without the need to know the route or how to solve NAT routing and so on. This is not possible with UDP, when both ends need to know the IP of each other to exchange messages.
Copy link
Member

@telephon telephon Jul 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When receiving a UDP message, we also receive the sender address:

OSCdef(\x, { |msg, time, addr| addr.postln }, '/test').add;
NetAddr("127.0.0.1", NetAddr.langPort).sendMsg('/test');

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the problem is if both peers are not in the same network or if the IP of the sender is not directly accessible. example:

I'm behind a router with internet IP 194.42.123.98, my computer local IP is 192.168.8.42 and I send a UDP package to a public IP / host (for example if there is a server listening on scsynth.org port 57120).

The package arrives there with my router IP (194.42.123.98) the software running on scsynth.org tries to send me an UDP message back using that IP, but my router has no idea about where it should send this package to inside the network unless I configure Port Forward in the router (telling it that any packages arriving on 57120 should be sent to 192.168.8.42).

With TCP the connection is kept on and the route between scsynth.org and my computer inside the network is still reachable.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest not making assumptions about peoples NAT situation. As an aside, in normally working NAT replies should be correctly delivered because of the routing table. It's making initial connections to computers behind NAT that usually requires configuration.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but a UDP reply (as in the scenario I described) is basically making an initial connection to a computer behind NAT.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, normally when an internal address and port sends to an external one, replies are expected and routed accordingly. Otherwise no external to internal messages would be possible.

We did a remote recording for NIME this week with a shared server and Utopia Registrar that worked exactly like this. Port forwarding was only needed on the NAT at the server end as all clients sent out a /notify message. It's possible something else is going on in your situation (like a very short translation timeout), and I know there are some exotic forms of NAT, but normally the translation table just takes care of this.

TCP on its own of course will not solve the NAT problem.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are correct @muellmusik that the translation will occur and I didn't know that sendMsg will open that port locally. So I tried now and it works:

OSCdef(\x, { |msg, time, addr| addr.postln }, '/test').add

and then on my machine:

NetAddr("bgo.la", 57120).sendMsg('/test');

and on bgo.la I get:

a NetAddr(95.91.208.191, 9435)

If I send back from bgo.la to that address in port 9435 I am able to get it back on my machine. So, somehow NetAddr is opening the port to listen. I guess the issue with TCP then is that once you do NetAddr.connect it won't register OSCFunc/Def to that TCP port?

@muellmusik
Copy link

I'm not so sure about the semantic drift of this. A NetAddr broadly models a particular endpoint, not a connection. (I admit it's slightly messy, but I'm not sure we should make it more so) Alternative approaches:

  1. connect() takes a connectHandler argument, which is passed the port on success.
  2. Add the ability to query or receive the socket after connecting, returning some sort of Socket object. (currently it just stores a pointer in that field). This might make sense if there's other behaviour/introspection we'd like.

@bgola
Copy link
Author

bgola commented Jul 20, 2020

@muellmusik agreed.

The Socket object should be a second proposal I would add in a next RFC. That is because currently sclang is only able to send / receive OSC messages, so having access to a Socket object would IMO mean a lot of extra work. I don't think it would make sense to have such object and still not be able to open local TCP port for accepting incoming connections or be able to send / receive any sort of bytes through that socket (and all other things that a Socket object should/could do). So for now having just the port and/or some other attributes would be nice.

I have no preference between having NetAddr.tcpRecvPort or receving the port as the connectHandler.. Even tho I think it adds some unnecessary complexity for users who are not familiar with callbacks (but sclang is full of callbacks anyway, so it wouldn't be such a big issue)

@muellmusik
Copy link

So for now having just the port and/or some other attributes would be nice.

I'm totally sympathetic to reluctance to add a lot of stuff just to get a particular feature for now. That does need to be balanced though against the additional technical debt and complexity of adding multiple ways of doing the same thing, especially if we think a Socket object is the ultimate way to go. There's been some enthusiastic talk of adding WebSocket support, so perhaps it would be most productive to join forces and think about a broader network implementation. For myself I'd rather see an initially incomplete version of it 'done right' (if we can agree on that :-) than 1.5 times what we ultimately need.

(but sclang is full of callbacks anyway, so it wouldn't be such a big issue)

The question I guess is how to deal with failures. Another approach could be to optionally request a port in connect() a la openUDP. We could return the port or a NetAddr if successful and nil if not. There is a small chance that this would break a small amount of code, though I suspect very little if any as it would only matter if people were relying on connect returning the instance.

@bgola
Copy link
Author

bgola commented Jul 20, 2020

@muellmusik I am totally up for a broader network implementation and also very interested in WebSocket support. So yes, would make sense to have an initial Socket object for now. But then I'm not the most experienced in SC development to propose how that should be implemented. On the other hand, I think the way this NetAddr.connect / TCP support is really incomplete so would not hurt to have this "fixed" for now until we come up with a full network implementation.

But as I said, I am happy with both. I would just like to know what port was used to send the message so I can register the proper callbacks for that message (without the need to include things on top of the application protocol like the randomId in OSCRouterClient).

Thanks for the feedback :)

@capital-G
Copy link

So, to bring this up again - I think the network stack of sclang would really benefit from some care. The approach nowadays seems to be to spin up other node/python/... scripts to take care of any network translation (e.g. UDP->websocket, ... etc) and it would be great to get any kind of improvement here - and I'd consider any movement an improvement.

Today's networking is really complex and to implement it properly on a first try is almost impossible. The only network implementation I know a bit of is from Python, see https://docs.python.org/3/library/socket.html - having a full socket implementation would be great, but I think this is a bit much to ask and to maintain.

I'd vote to go with the proposed RFC but label the whole (hopefully evolving) network stack as "unstable API" and see where we go from there and eventually reach a stable API. Thinking that 4 years ago we could have the proposed RFC implemented seems to me more desirable than the current status quo of the network stack.

Would you still be interested in providing some work on this @bgola? If you are still up for it I think we could ask if there are any oppositions to it and then you could give it a go :)

@bgola
Copy link
Author

bgola commented Dec 13, 2024

@capital-G I am definitely up to work on this. Thanks for bringing it up.

@capital-G
Copy link

capital-G commented Dec 15, 2024

Thanks for still being interested in this @bgola!

@muellmusik what is your view on it? There was some talk regarding NATing and UDP and I personally found that anytime you are confronted with NAT you are out of luck with sending UDP message to a host sitting behind a NAT. It is possible to forward a specific UDP port to a NATed host, but if your network is big (e.g. eduroam, company ...) you most likely don't get such a rule implemented within your organization - and as IPv4 addresses becomes more and more sparse (and SC is also bound to IPv4 iirc) NATing has become a default in many places, making UDP an one-way communication protocol.

Having the ability to use TCP really helps in such cases because it allows to setup a back-channel through a NATed network by using the established connection which gets translated by a NAT w/o problems b/c it is stateful.

Adding the opened TCP port to a NetAddr seems like a good way to separate multiple existing TCP connections. I agree that NetAddr doesn't look like a stateful connection socket, but this PR suggest to extend already existing behavior.

At some point we could deprecate NetAddr and replace it with a UDPSocket/TCPSocket/... class. But this would also require some decision on how much of a raw socket we want to expose to sclang - byte only, OSC only or some common parsed I/O like JSON? But this is for another PR/RFC.

@muellmusik
Copy link

@capital-G Hey Dennis! Generally in favour of doing something. In fairness I'm not a networking expert, just someone who's had to make a lot of telematic stuff work, haha.

That said, I'm not sure that TCP is a real solution for NAT problems, at least not a total one. If someone knows different, speak up, but my understanding is the problem is initial contact of an address behind NAT. Though not really originally intended that way, NAT acts as a security feature, since your LAN is invisible to the WAN/internet. Since LAN devices have a local rather than global address, you cannot initiate contact, you can only reply to a request. NAT keeps a table of outgoing messages, which it then uses route replies. You can't normally contact a device behind NAT, only the router. If NAT isn't expecting a reply it ignores the packet.

So while TCP might help in the WAN->LAN case providing the LAN device initiates contact, it doesn't solve the LAN <-> WAN <-> LAN case since you cross NAT twice. I have actually seen this work however by both computers spoofing replies and then keeping a steady stream of messages going once connection succeeds!

Anyways, apologies if this is all obvious, I just wanted to say that I don't think we should view this too much as a NAT solution, but there are other good reasons to do this.

@muellmusik
Copy link

Anyway, yes would be great to take this forward! As NetAddr is in a lot of code, I think a new approach leading to deprecation seems very sensible. It would be nice if this was done as part of a bigger picture rethink of networking requirements, including web sockets, as suggested above.

@capital-G
Copy link

capital-G commented Dec 16, 2024

So while TCP might help in the WAN->LAN case providing the LAN device initiates contact, it doesn't solve the LAN <-> WAN <-> LAN case since you cross NAT twice.

Indeed, but one could use ICE for this, see https://temasys.io/guides/developers/webrtc-ice-sorcery/ and https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Protocols (e.g. use https://github.com/libnice/libnice).

@bgola I think you can start working on your implementation, I don't see that a PR would hit any obstacles now :)

edit: Acutally it would be also great if we would be able to get the outbound UDP port of an OSC message as well - this (I think) would allow to implement STUN on top of OSC (with a simple custom web server which we could code and provide)

@muellmusik
Copy link

I still remember oscgroups: http://www.rossbencina.com/code/oscgroups I'm not sure if anyone runs a server anymore...

@joshpar
Copy link
Member

joshpar commented Dec 17, 2024 via email

@capital-G
Copy link

I still remember oscgroups: http://www.rossbencina.com/code/oscgroups I'm not sure if anyone runs a server anymore...

But IIUC oscgroups isn't p2p but more of a tunneling service - STUN allows you to negotiate a P2P connection with the help of a third party to which the peers can directly connect and wouldn't require a custom client.

I also tried to perform once with oscgroups within eduroam but it didn't work, so I had to quickly hack a websocket tunnel to transport the OSC messages. (btw - I am currently looking into websocket implementation within sclang)

But a quark implementing oscgroups functionality and a rewrite of the oscgroups server in rust (or else) seems like a good project :)

@joshpar
Copy link
Member

joshpar commented Dec 17, 2024 via email

@capital-G
Copy link

capital-G commented Dec 17, 2024

There was a quark… still is I think :)

Yes, but it has still a dependency upon oscgroups client https://github.com/supercollider-quarks/OscGroupClient/blob/b02a838bf7333721c50ad02e1e85c41df03c4276/OscGroup.sc#L13 :)

One could implement this natively within sclang if we'd have access to the outgoing port of an OSC message for UDP hole punching.

@adcxyz
Copy link

adcxyz commented Dec 30, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants