-
Notifications
You must be signed in to change notification settings - Fork 135
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
feat: add upnp support #1022
feat: add upnp support #1022
Conversation
The guy at nimbus who was pushing for 4444s told me why aren't you guys just using upnp, so I think this is a good first direction. As Qi said on discord in general channel we will probably end up supporting
In that order of priority. This will also majorly support our effort to having a large network. Thank you Qi for looking into this I believe this PR solves or at least work towards #22 Carver can correct me if I am wrong on that I guess I am saying I support this change |
@@ -170,6 +177,7 @@ impl Default for TrinConfig { | |||
bootnodes: Bootnodes::Default, | |||
external_addr: None, | |||
no_stun: false, | |||
no_upnp: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this means we're enabling upnp by default? Is that the desired behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The behavior is borrowed from geth's default behavior, where UPnP is used by default (and there is no flag to disable it). Feel free to let me know if you have better thoughts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe enable by default is the desired behavior
Co-authored-by: Nick Gheorghita <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some questions/comments. I think we should also remove the port mapping on ctr-c
or panic. I don't think we have a panic hooks right now for the main Trin process but adding a remove port mapping function and a call in main.rs
should be ok for now.
"new_port", | ||
) { | ||
Ok(()) => { | ||
thread::spawn(move || loop { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason for spawning this thread and the loop in the background? If gateway.add_port
returns Ok(()), the mapping should be successfull ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This thread is used to renew the port periodically so that Trin can own the port as long as it is alive, while the port will be released in < 60 mins if Trin quits in any matter (crash or gracefully shutdown).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, it makes sense. It will be nice to have some comments with a short description of what is happening here.
Let's use tokio to spawn the thread with tokio sleep:
thread::spawn(move || loop { | |
tokio::spawn(async move { loop { | |
tokio::time::sleep(time::Duration::from_secs(UPNP_MAPPING_TIMEOUT)).await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A comment is added.
I did some study on tokio::spawn
, and I believe that it may not be a good way because gateway.add_port
into the UPnP library is a blocking IO, which may block other async tasks. I tried to use tokio::tasks::spawn_blocking
, but I encountered an issue that SIGINT (ctrl + c) cannot exit Trin gracefully. As a result, I have to revert to the dedicated thread method for this purpose.
Please let me know if you have better ideas.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I didn't know that gateway.add_port
is a blocking IO. In this case we have two options tokio spawn_blocking or a dedicated thread but because we expect this thread to run forever, I think std::thread::spawn
is the way to go 👍 .
use tracing::{debug, info, warn}; | ||
|
||
// This stun server is part of the testnet infrastructure. | ||
// If you are unable to connect, please create an issue. | ||
const STUN_SERVER: &str = "159.223.0.83:3478"; | ||
|
||
/// The duration in seconds of an external port mapping by UPnP. | ||
const UPNP_MAPPING_DURATION: u32 = 3600; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we mapping UPnP for only 60 minutes? I think we want to use an infinite value here, which in this case should be 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am slightly in the camp of having a limit unless we're absolutely sure that these will expire after a period of disuse. We don't want to be opening ports up on people's home networks indefinitely. Seems worth the effort to implement periodic renewal of the mapping to make sure we aren't being bad citizens on user machines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am slightly in the camp of having a limit unless we're absolutely sure that these will expire after a period of disuse. We don't want to be opening ports up on people's home networks indefinitely. Seems worth the effort to implement periodic renewal of the mapping to make sure we aren't being bad citizens on user machines.
Agree. The current design will initially map the port for 60 minutes, and then periodically (every 30 minutes) renew the port for another 60 minutes. That said if Trin is down for no matter what reasons (crash or gracefully closed), the port reserved by Trin will be automatically released in < 60 minutes.
} | ||
}; | ||
|
||
let local_ip = match TcpStream::connect(gateway.addr) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if I'm in love with the idea of establishing a TCP stream to the gateway and getting the local IP address from there.
I don't know if https://docs.rs/local-ip-address/0.5.6/local_ip_address/fn.local_ip.html guarantees to return the correct IP address if more than one default interface is enabled. (if it is comparing the metric
value from the routing table info, should be ok).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, the previous version of the PR uses local_ip and local_ip_v6 to find the local IP address as below:
local_addr = match local_ip_address::local_ip().or(local_ip_address::local_ipv6()) ...
However, as @carver points out that this may be problematic if multiple interfaces exist. That is why I switch to a TCP way to detect the local IP.
Note that the UPnP protocol also uses an HTTP/TCP connection to the gateway. Unfortunately, the UPnP library does not return which local IP address is used to talk to the gateway. So, without modifying UPnP library, making a TCP connection to the gateway is an easy solution to determine the local IP address
group = "external-ips", | ||
help = "Do not use UPnP to determine an external port." | ||
)] | ||
pub no_upnp: bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer allow_upnp
with the default value true
, I think it is less mentally overwhelming that way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to change it to allow_upnp
, but looks like clap does not support setting a boolean flag to false
. Below is the error message I got
$ trin --allow-upnp=false
error: unexpected value 'false' for '--allow-upnp' found; no more were expected
Usage: trin --allow-upnp
For more information, try '--help'.
Co-authored-by: Ognyan Genev <[email protected]>
Co-authored-by: Ognyan Genev <[email protected]>
Co-authored-by: Ognyan Genev <[email protected]>
Co-authored-by: Ognyan Genev <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
Co-authored-by: Ognyan Genev <[email protected]>
What was wrong?
Currently, Trin cannot support a private node running behind a firewall/gateway. Inspired by libp2p, this PR adds UPnP support, which is the default NAT used in libp2p.
How was it fixed?
UPnP protocol will ask the gateway to map a private IP/port to an external IP/port and return the external address to Trin. By encoding the external address in ENR, Trin will allow any node to access a Trin node running behind the gateway.
Test:
To-Do