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

TCP support #1

Open
piegamesde opened this issue Sep 15, 2021 · 15 comments
Open

TCP support #1

piegamesde opened this issue Sep 15, 2021 · 15 comments

Comments

@piegamesde
Copy link

This crate is great, but my application requires to use TCP instead :(

If I understand the RFC correctly, one should be able to swap out the UDP socket (and respective connection logic) with a TCP socket and the rest of the could could still be the same.

@vi
Copy link
Owner

vi commented Sep 15, 2021

Is your application a custom STUN server that only uses TCP? Or do you need to forward STUN requests over some TCP-only channel?
Do you use any STUN extensions such as TURN?

@vi
Copy link
Owner

vi commented Sep 15, 2021

Dealing with TCP = dealing with short reads and properly accumulating incoming chunks until full STUN message gets buffered.
Also retrying logic would probably be different (e.g. nonexistent) for TCP.

So this change would definitely be more complicated than just swapping the socket type.

@piegamesde
Copy link
Author

Is your application a custom STUN server that only uses TCP? Or do you need to forward STUN requests over some TCP-only channel?

I need it because the underlying protocol that I want to speak once the connection is set up is TCP-only.

Do you use any STUN extensions such as TURN?

No, I just need to get some public IP for a socket. We have some kind-of self-made TURN server for TCP connections but I don't think that is relevant to this issue.

Also retrying logic would probably be different

As you said, that code can probably all go away :)

@vi
Copy link
Owner

vi commented Sep 16, 2021

I need it because the underlying protocol that I want to speak once the connection is set up is TCP-only.

If TCP connection would be established to STUN server then it cannot be redirected elsewhere. And if just create new TCP connection from the same source port, would typical a NAT preserve it's external source port or consider it a new, unrelated allocation?

If closing the socket, then opening another with the same local port works, then API should be designed to aid it (either doing close&reopen itself or exposing just the port number).

Also, if this is TCP hole punching, I'm not sure how one would both connect from a local TCP port and simultaneously accept incoming connections to the same TCP port using portable APIs.

@piegamesde
Copy link
Author

If you're interested in the gory details of my endeavor, I recommend reading https://bford.info/pub/net/p2pnat/. At the moment, I am prototyping an implementation of the algorithm presented in that paper (albeit with a few small variations).

@piegamesde
Copy link
Author

I gave it a try and the basic code is ridiculously simple:

    let bytes = get_binding_request()?;
    let server_addr = "stun.stunprotocol.org:3478".to_socket_addrs().unwrap().find(|x|x.is_ipv6()).unwrap();
    let mut socket = std::net::TcpStream::connect(server_addr)?;
    socket.write_all(&bytes[..])?;

    let mut buf = [0u8; 256];
    let len = socket.read(&mut buf[..])?;
    let buf = &buf[0..len];
    let external_addr = decode_address(buf);

Of course this doesn't account for async and socket reuse and all that stuff. Also note that the default google server does not appear to host a STUN/TCP service.

@vi
Copy link
Owner

vi commented Sep 16, 2021

What if for some reason the first socket.read returns only e.g. 8 bytes, inviting to continue reading further reply bytes?

@piegamesde
Copy link
Author

I also asked this myself and I honestly don't know. STUN probably has some framing mechanism that I don't know of. I just read https://datatracker.ietf.org/doc/html/rfc5389#section-7.2.2 and it says that unless the STUN is mixed into some other protocol, no additional framing is needed [compared to UDP].

@vi
Copy link
Owner

vi commented Sep 16, 2021

Even if STUN server itself won't (deliberately) fragment packet, networking equipment may.

I expect this simple scheme to work in wast majority of cases, but there will always be lurking some chance that in some ugly network it would break down. It's like undefined behaviour that "always just works in practice".

@vi
Copy link
Owner

vi commented Sep 16, 2021

is_ipv6

Isn't IPv6 supposed to be a Better World where addresses don't need to be mapped, and one can just query socket address and assume it's the external, globally routable address?

@piegamesde
Copy link
Author

Isn't IPv6 supposed to be a Better World where addresses don't need to be mapped, and one can just query socket address and assume it's the external, globally routable address?

Yeah, I was just doing some experiments and forgot to change it back. We definitely won't NAT66, but in the case some people do this regardless STUN/IPv6 got us covered.

[Off-topic] Speaking of which, you shouldn't bind to 0.0.0.0:0 but to [::]:0 instead. It is fully backwards compatible but also allows the socket to be used for IPv6 packets.


I found the appropriate section: https://datatracker.ietf.org/doc/html/rfc5389#section-6. Maybe stun_codec some header parsing API, but basically one should read the third and the fourth bytes and then the rest of the packet. Actually, this kind of applies to the UDP implementation as well…

@vi
Copy link
Owner

vi commented Sep 16, 2021

Actually, this kind of applies to the UDP implementation as well...

How would that apply to UDP?

The only thing that comes to mind is validating that UDP packet does not contain extra trailing bytes.

@vi
Copy link
Owner

vi commented Sep 16, 2021

you shouldn't bind to 0.0.0.0:0 but to [::]:0 instead.

Wouldn't it break support of system where AF_INET6 is not available in kernel? Maybe something embedded omits it to save bytes.

@piegamesde
Copy link
Author

How would that apply to UDP?

As far as I can tell from the RFC (disclaimer: I maybe read at most 20% by now), it doesn't specify a mapping from messages to UDP packets. Thus, they could be fragmented as well. In that case, the safest way to read would be to always check the header and then wait for additional packets if necessary until the required length has been reached.

Wouldn't it break support of system where AF_INET6 is not available in kernel?

Yeah, probably. Do I care about people deliberately not supporting IPv6 on machines they control? Nope

@vi
Copy link
Owner

vi commented Sep 16, 2021

Thus, they could be fragmented as well. In that case, the safest way to read would be to always check the header and then wait for additional packets if necessary until the required length has been reached.

UDP packets may be reordered and selectively dropped, so it is generally impossible to recover UDP message from headerless chunks.

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

No branches or pull requests

2 participants