Is it possible to achieve a p2p connection without an external server if the public IP and listening port are known? - p2p

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(())
}

Related

Data sent over TCP not getting delivered

I'm working with node.js looking to build a chat application with p2p capabilities.
I have 2 seporate computers on 2 different wifi networks.
I'm using the module fully-connected-topology and adding users to the topology using t.add(IP:PORT)
I'm getting the IP using the public-ip module and publicIp.v4().then(ip => { (Gets external IP)
I've tried getting the port in 2 ways:
Using get-port module (Finding open ports) and getPort().then(port => {
register-multicast-dns module and hash-to-port (I think this only works for local host so I switched to looking for open ports
Here's where I'm stuck:
Computer 1: IP = IP1, PORT = PORT1
Computer 2: IP = IP2, PORT = PORT2
when I run on Computer 1 t.add(IP2:PORT2) and on Computer 2 t.add(IP1:PORT1) no connection is made.
I know this because in this code:
t.on('connection', function (socket){
console.log('Connection established')
})
the print statement never fires.
Can someone direct me to where the packet is getting lost/dropped?
It all worked when on the same network but I can't find where the error lies.

Timeout accessing Amazon EC2 machine from outside even with security group allowing everything

how are you?
I have a question regarding external access to my (dockerized or not) application.
I have a simple ping-pong socket communication using socket.io with the following code files:
server.js
let Server = require('socket.io')
const io = new Server().attach(8090)
io.on('connection', (socket) => {
console.log('connection')
socket.on('message', (msg) => {
console.log('< ' + msg)
console.log('> pong')
socket.emit('event', 'pong')
})
})
client.js
let host = process.argv[2]
let io = require("socket.io-client");
let client = io.connect(`http://${host}`)
let connected = false
client.on('connect', () => {
connected = true
})
client.on('event', (msg) => {
console.log('< ' + msg)
ping()
})
function ping () {
if (!connected) {
setTimeout(ping, 1000)
} else {
setTimeout(() => {
console.log('> ping')
client.emit('message', 'ping');
}, 1000)
}
}
ping()
with my server up and running, I can execute the client to test the communication like so:
$ node client.js localhost:8090
and the output is similar to this:
> ping
< pong
> ping
< pong
> ping
< pong
... and so on
Everything works normally when I test this (both client and server) outside my EC2 machine, but when I deploy the server.js to the cloud and run it, I can't reach it from outside.
When I bind a nginx web service on port 80 or 81 and proxy every request to the application, everything starts working fine from outside the cloud, but still does not work on other ports.
My security group is fully open (for testing purposes) (inbound and outbound allowing all traffic from and to anywhere (0.0.0.0/0, ::/0) on all ports) (screenshot attached).
Things that may be useful for troubleshooting:
When I try a NodeJS http server (without nginx) using node on the same port, it does not work either (works only on 80 and 81)
I am sure my machine is on the 'fully-open-insecure' security group with all traffic allowed.
My application is binding to all addresses because I can connect to it using the public address or localhost from inside the cloud machine.
It only works on port 80 and 81 when I proxy_pass connections from a nginx web server that is on the same machine as the NodeJS application
any help or troubleshooting suggestion will be very appreciated.
thank you very much for your time
Below is a screenshot with the security group definitions:
My problem was solved and this is embarrassing. My local network was not allowing outbound connections on the ports I mentioned.
If you have the same problem, I recommend to test your local network. Also, check your iptables rules

IPv6 multicast with Node.js

I am experimenting with IPv6 UDP multicast over a VPN. I have tried the following code:
const dgram = require('dgram');
let sock = dgram.createSocket('udp6', {
reuseAddr: true
});
sock.on('message', (data, source) => {
console.log('on message', arguments);
});
sock.bind('36912', '2620:9b::1944:e598', () => {
sock.addMembership('ff02::1:3', '2620:9b::1944:e598');
});
setInterval(() => {
let buf = Buffer.from((new Date()).toString());
sock.send(buf, 0, buf.length, 36912, 'ff02::1:3');
}, 500);
The script runs, and I see packets being sent/received with Wireshark, but neither end shows them in the console.
What am I doing wrong? What's a way to send and receive basic multicast with IPv6?
Scope id -> interface number
In IPv6, there is a concept of a scope_id of an address that is supposed to indicate a context for the IP address, and generally just means what interface it is reachable on. While scopes have OS specific names, each just translates to an interface number, with 0 usually meaning the system's default.
In IPv6 multicast, this scope_id is provided directly to IP_ADD_MEMBERSHIP and IP_MULTICAST_IF instead of providing an ip associated with the interface as IPv4 does.
wrapper smoothing of v6's differences
node (via libuv) hides this difference for you in addMembership by looking up the scope_id from the "interface address" you provide.
Unfortunately, starting from just an IP and getting a scope doesn't make a lot of sense (the whole point of the scope is that the IP could have different uses in different scopes.) So libuv is only able to fill in a scope if you explicitly provide it at the end of the address, using the %[scope] format.
Using Addresses with Explicit Scopes
The way around this seems to be:
sock.bind('36912', '::', () => {
sock.addMembership('ff02::1:3', '::%eth2');
...
sock.send(buf, 0, buf.length, 36912, 'ff02::1:3%eth2');
Where:
using :: (or no address) in bind is necessary since you are combining receive which will filter the multicast address on this with send which needs a normal address.
using %[iface#] forces the scope of this interface #.
The second argument of addMembership could really start with any address since we are forcing the scope and the rest is discarded.
Usually the send socket is separated and given a different port or an anonymous port as you are either limited in what you can configure or in danger of getting EADDRINUSE errors for having sockets that are too similar.
I followed the steps in this answer but still couldn't get IPv6 multicast to work. It turned out I was setting socket.setMulticastLoopback(false) to filter out messages coming from the node itself, which worked well for IPv4, but was blocking all messages for IPv6. Removing this fixed the issue and messages started appearing correctly, with no need to filter.

Giving public access to chat server on node.js

Now I am learning node.js and created a simple chat server using node.js.
My code:
net = require('net');
var sockets = [];
var s = net.Server(function(socket) {
sockets.push(socket);
socket.on('data', function(d) {
for (var i = 0; i < sockets.length; i++) {
if(sockets[i] == socket) continue;
sockets[i].write(d);
}
});
socket.on('end', function() {
var i = sockets.indexOf(socket);
sockets.splice(i, 1);
});
});
s.listen(8000);
How can I share this chat server on the Internet, so other people can use it?
On my local machine, I have an access through telnet:
telnet localhost 8000
If you really want to let others use this chat through telnet over the internet (which I recommend against), you will need to forward a port through your router (I assume you use a router) to port 8000 on your local machine. Give your friends your IP address and the port you mapped, and they should be able to telnet in as well.
It's hard to answer this question without more information, however. Do you use a firewall? Modem? Etc.
Its really hard to answer this question based on all the information.
It all depends if you are behind a router, firewall etc. If you are directly connected to the internet through a modem you can use ipconfig(in command prompt) to find your public ip. If you are behind a network router you will most likely have to setup port forwarding. If this is the case, just do a Google search on your router, I'm sure you will find a tutorial to set it up or you can refer to your manual.
Here is a explanation on port forwarding: http://en.wikipedia.org/wiki/Port_forwarding
Hope this helps or points you in the right direction!
EDIT 2022:
Easiest option these days would be to use NGROK (or something similar)

NodeJS TCP Socket does not show client hostname information

lets say I've created a simple node.js TCP Socket server as follows.
var server = net.createServer(function (socket) {
socket.end("goodbye\n");
});
// listen on port 80, like a webserver would, and configured to accept connections to multiple hosts, ie test.example.com, sdfsdfsdf.example.com, example2.com.
server.listen(80, function() {
address = server.address();
console.log("opened server on %j", address);
});
server.on('connection', function(socket) {
//log('connection data', socket);
log('CONNECTED SOCKET DATA', socket.address());
log('CONNECTED LOCAL, %s:%s', socket.localAddress, socket.localPort);
log('CONNECTED %s:%s', socket.remoteAddress, socket.remotePort);
//How do I find the server hostname that the client connected to?
// eg test.example.com or example2.com
});
Once a tcp connection is made, I want to parse the hostname that the client attempted to connect to. However the socket does not seem to have this information.
The following is what I got when I ran the previous code (removed ip addresses)
CONNECTED SOCKET DATA { port: 80, family: 2, address: '[SERVER IP ADDRESS]' }
CONNECTED LOCAL, undefined:undefined
CONNECTED [CLIENT IP ADDRESS]:13263
I've gone through the nodejs socket documentation but I cant find anything related to host name. The documentation actually says that the socket.localAddress will be an IP Address, with makes it useless in my case.
That information is not available at the TCP layer. You need to look at the Host header at the HTTP layer when a request arrives. This will be available as req.headers.host when a request arrives. http://nodejs.org/docs/latest/api/all.html#all_message_headers
Also based on your comment, trying to run a non-HTTP server on port 80 is asking for trouble. You are going to get connections from web spiders and botnets all the time, so make sure your program properly handles HTTP clients connecting and disconnects them or perhaps sends an HTTP error message.
Joe is correct in the comment that if you are not using HTTP, your custom protocol will need to transmit the "virtual host" name from the client to the server in the first packet/header message of your custom protocol.

Resources