1

For context, my group and I are attempting to build a simple p2p messaging application in Rust with minimal library use. (We DID attempt using libp2p early on, but unfortunately it's incompatible with the key exchange algorithm we're using.)

The users on each end are required to send one another a public key through a third party messaging service before connecting, and we are able to encode the public IP address and the listening port of the program within this public key. (Meaning that the public IP and listening port of the other party will be known by the program at runtime.)

Since we are able to communicate the router's public IP address and the listening port of the program, would it be possible to establish a p2p connection without the need for an external server or port forwarding? If so, is there a simple solution we're not seeing using only the standard library? Currently we're attempting to check for incoming connections using TcpListener (see test code below) and are able to detect connections to localhost on the specified port, but have no access over the network.

We're all college students who are new to networking, so any explanation for what technology we're looking for would be greatly appreciated. We've tried researching hole punching, but to our understanding that requires a third server with a known open port. We were hoping that by broadcasting the IP and listening port directly we could bypass this. We're operating on the school's network, so UPnP is disabled.

use std::net::{TcpListener, TcpStream};

// Simple test script for TcpListener - attempting to listen over the network instead of locally.

// Handle new connections - test program just prints info and allows the connection to close.
fn handle_connection(stream: TcpStream) {
    println!("New Client: {}", stream.peer_addr().unwrap());
}

fn main() -> std::io::Result<()> {
    // Listen on any IP and let the OS choose a port - works with localhost and local address 
    // shown from "ipconfig". Does not work with public IP shown in web browsers.
    // (This is expected - no open port or specialized incoming communication yet.)
    let listener = TcpListener::bind("0.0.0.0:0").unwrap();

    // Show listening port for testing purposes.
    println!("Listening on: {}", listener.local_addr().unwrap());

    // Attempt to connect to all incoming streams.
    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                handle_connection(stream);
            }
            Err(_) => {
                eprintln!("Connection Error");
            }
        }
    }
    Ok(())
}
pelleg96
  • 11
  • 3
  • You absolutely can connect directly. You may have issues navigating NAT on the listening router, unless you use something (unsafe) like upnp. A server could act as a proxy to prevent this (outbound connections are usually allowed on home networks), but that comes with an obvious price tag. Ultimately it depends on who your end users will be: how tech savvy they are, if they have network administration access, running on a hardened network etc. – MeetTitan Oct 20 '22 at 18:34
  • I'm not sure how easy it is to do this. Usually port forwarding / IPV6 traffic forwarding is the way to go. I know there are ways to achieve this anyway, like [UDP hole punching](https://en.wikipedia.org/wiki/UDP_hole_punching), but it's not trivial. – Finomnis Oct 20 '22 at 18:55
  • @MeetTitan OP said *"without the need for an external server or port forwarding"*. I don't think it's as easy as you make it seem given those limitations. – Finomnis Oct 20 '22 at 18:57
  • Note that this isn't really a Rust-specific question, this is more of a networking question, any answers here would be generally applicable to any programming language. – effect Oct 20 '22 at 19:11
  • Something that isn't clear, is the intent of this messaging application to work just in a local network, or to communicate over the internet? I'm assuming over the internet? – effect Oct 20 '22 at 19:27
  • You can do this, but it's pretty much required to have a server as an intermediary. You can choose a specific local port, but that port might be being used by some other obscure program. The way to go generally is just to send a request and let the OS choose the port. Additionally, this will likely not work for everyone. It does work on most standard household routers, but there are some that will filter requests like this making hole punching impossible. For this reason, even programs that do use this method almost always have a backup server to use as an intermediary. – Jesse Oct 20 '22 at 21:08
  • [Here is a good answer explaining how to implement UDP hole punching](https://stackoverflow.com/a/11377330/10601203) – Jesse Oct 20 '22 at 21:14
  • In response to @effect: The intent of the application IS to work over the internet. The goal is for the user not to need to do any setup. Just type the other user's key and hit "enter". Then have the program keep trying to connect until either it's closed or a connection is successful. Hole punching seems like the way to go, I'm just curious if it's possible to do without an external server since the public address and listening port of each client are both initially communicated to the other with the key. (Also, thanks for the advice, I removed the "Rust" tag to make it more network-focused.) – pelleg96 Oct 20 '22 at 21:16
  • Thanks @Jesse, I'll give that answer a look. The OS is given the task of choosing the port for each client whenever the program runs, and the port it's currently listening on (as well as the public IP of the client router) is given to the other peer along with the user's public key. – pelleg96 Oct 20 '22 at 21:18

0 Answers0