added address blocking, expanded readme

This commit is contained in:
Ellie D 2019-06-01 02:16:28 -05:00
parent 2b0178773c
commit 0a9a247549
2 changed files with 65 additions and 9 deletions

View File

@ -7,6 +7,9 @@ Sunbeam is a simple and fast multi-endpoint TCP relay for
for um
we swear we had a perfectly reasonable use in mind, but we honestly can't remember it now.
### What it does
Sunbeam is a little tiny server that passes data between TCP connections.
Run at the command line, Sunbeam takes any number of local ports (numbers between 1 and 65535)
and remote addresses (strings like [ip address]:[port]). For each address argument passed,
Sunbeam attempts to connect to that address, and for each port argument, it listens for incoming
@ -17,9 +20,9 @@ promptly retransmitted to all other connections, save for two exceptions:
- Data is not passed between servers that Sunbeam connects to.
For example, Sunbeam can be used as a relay for an audio stream by giving it the address of the
stream server and a local port. Clients connecting to the local port will receive the stream data,
but if one client sends data back to Sunbeam, it will not interfere with other clients' streams
(it will, however, be relayed back to the source).
stream server and a local port. Clients connecting to the local port will receive the stream
data, but if one client sends data back to Sunbeam, it will not interfere with other clients'
streams (it will, however, be relayed back to the source).
This behavior can be overridden with "loopback mode" in which Sunbeam always relays data to all
clients and servers except the one which originated the data. Sunbeam automatically goes into
loopback mode if no ports are specified, or if only one port and no addresses are specified. You
@ -28,6 +31,23 @@ can also pass the `-l` flag to require Sunbeam to run in loopback mode.
General usage looks like the following:
`sunbeam [-l] [local port] [remote address]:[port]`
### How to block IP addresses
It's 2019, and a server that can't filter incoming connections is a server that belongs to The
Enemy. That's why Sunbeam, minimal as it is, includes this functionality nonetheless.
You can create the file `.nosunbeam` in the directory where Sunbeam runs. Sunbeam will read it,
parse each line as an IP address (v4 or v6 are both fine), and immediately drop any incoming
connections from those addresses.
### Why you can't give it domain names
Rust has a minimal stdlib. One consequence of this is the fact that there's no way to resolve a
domain name using only stdlib, without writing and entire DNS client yourself. We admit we don't
really understand how it all works, but it seems we only have two choices: live without domain
name resolution, or pull in some enormous DNS crate with 37 unstable dependencies. We choose the
former. As a result, you'll have to do your DNS resolution some other way, like by running `dig`
at the command line to look up the IP address for the domain you want. Yes, it's clunky, but
ergonomics are not really the biggest problem this program has either.
### Why it's called that
This program is called "Sunbeam" because of a particular plot device that we thought was clever
in the sci-fi novel *The Three Body Problem* by Cixin Liu. A character discovers that, due to a
particular plasma-driven mechanism inside the sun, it's possible to use the sun as a gigantic

View File

@ -1,8 +1,9 @@
use std::collections::VecDeque;
use std::collections::{VecDeque,HashSet};
use std::env::args;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::net::{TcpListener,TcpStream,SocketAddr};
use std::net::{TcpListener,TcpStream,SocketAddr,IpAddr};
use std::process::exit;
use std::sync::mpsc;
use std::thread;
@ -12,6 +13,7 @@ static IDLE_THRESH:usize = 65536;
static BUFFERSIZE_INITIAL:usize = 511;
static LOOPBACK_FLAG:&str = "-l";
static BLOCKFILE_NAME:&str = ".nosunbeam";
fn main() {
let argv = args().collect::<Vec<String>>();
@ -43,6 +45,36 @@ fn main() {
if loopbackmode {
println!("-i- running in loopback mode (relaying data between all connections)");
}
let mut blocklist:HashSet<IpAddr> = HashSet::new();
match File::open(&BLOCKFILE_NAME) {
Err(why) => match why.kind() {
io::ErrorKind::NotFound => (),
_ => {
eprintln!("-!- failed to open {} to read IP address blocking rules: {}",BLOCKFILE_NAME,why);
exit(1);
},
},
Ok(mut file) => {
let mut blockstring = String::new();
match file.read_to_string(&mut blockstring) {
Err(why) => {
eprintln!("-!- failed to read {} for IP address blocking rules: {}",BLOCKFILE_NAME,why);
exit(1);
},
Ok(_) => (),
};
for line in blockstring.lines() {
match line.parse::<IpAddr>() {
Err(_why) => {
eprintln!("-!- could not parse '{}' in {} as an IP address",line,BLOCKFILE_NAME);
exit(1);
},
Ok(address) => blocklist.insert(address),
};
}
},
};
let mut connectors:VecDeque<(u16,TcpListener,VecDeque<(TcpStream,SocketAddr)>)> = VecDeque::new();
for port in ports.iter() {
@ -126,10 +158,14 @@ fn main() {
},
},
Ok((stream,address)) => {
println!("--> connection opened by {} on port {}",address,port);
stream.set_nonblocking(true).expect("cannot set stream to nonblocking");
stream.set_nodelay(true).expect("cannot set stream to nodelay");
connections.push_back((stream,address));
if blocklist.contains(&address.ip()) {
println!("-x- refusing connection to {} (listed in {})",address,BLOCKFILE_NAME);
} else {
println!("--> connection opened by {} on port {}",address,port);
stream.set_nonblocking(true).expect("cannot set stream to nonblocking");
stream.set_nodelay(true).expect("cannot set stream to nodelay");
connections.push_back((stream,address));
}
},
};
if let Some((mut stream,address)) = connections.pop_front() {