added port privacy

This commit is contained in:
Ellie D 2019-06-01 13:10:20 -05:00
parent 67529fdb0d
commit 5941fe4728
2 changed files with 89 additions and 18 deletions

View File

@ -10,7 +10,7 @@ we swear we had a perfectly reasonable use in mind, but we honestly can't rememb
### What it does
Sunbeam is a little tiny server that passes data between TCP connections.
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,
@ -28,7 +28,7 @@ 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
can also pass the `-l` flag to require Sunbeam to run in loopback mode.
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]`
@ -39,7 +39,24 @@ It's 2019, and a server that can't filter incoming connections is a server that
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.
connections from those addresses.
### How to make ports private
In case you want a port only to be accessible locally - e.g. if you're running a streaming server
and don't want anyone else to be able to inject data into the stream - you can mark them private
by suffixing them with the letter `p`, e.g.
`sunbeam 4444p 55555`
Additionaly, ports suffixed with `w` only accept connections from IP addresses listed in the file
`.yesunbeam`, which acts as a whitelist in a similar fashion to the `.nosunbeam` blacklist.
Entries in the blacklist take precedence over entries in the whitelist, so addresses listed in
both places will not be able to connect.
Using `w` with an empty or missing `.yesunbeam` file is nearly equivalent to using `p`, with the
difference being that `p` causes the socket to bind to `127.0.0.1` instead of the public address,
while `w` keeps the binding public but checks all incoming connections against a hash table of
allowed addresses and drops them if they are not found. `p` is likely a little bit faster, but
only `w` will print a notification to the console whenever a connection is rejected.
If a port is suffixed with both `w` and `p`, whichever is listed last will take precedence.
### Why you can't give it domain names
@ -50,7 +67,7 @@ choices: live without domain name resolution, or pull in some enormous DNS crate
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 the point of this program is to be minimal and simple and reliable, not to
be easier to use at the expense of those things.
be easier to use at the expense of those things.
### Why it's called that
@ -60,4 +77,4 @@ particular plasma-driven mechanism inside the sun, it's possible to use the sun
radio amplifier at certain frequencies - all you have to do is launch a radio beam into the
photosphere with enough intensity, and it bounces back out with orders of magnitude more power.
That's basically what this program does - you just aim your transmitter at it and start
talking, and it lets you communicate with other receivers that you normally couldn't reach.
talking, and it lets you communicate with other receivers that you normally couldn't reach.

View File

@ -14,6 +14,16 @@ static BUFFERSIZE_INITIAL:usize = 511;
static LOOPBACK_FLAG:&str = "-l";
static BLOCKFILE_NAME:&str = ".nosunbeam";
static ALLOWFILE_NAME:&str = ".yesunbeam";
#[derive(PartialEq)]
#[derive(Clone)]
#[derive(Copy)]
enum PortPrivacy {
Public,
Whitelist,
Private,
}
fn main() {
let argv = args().collect::<Vec<String>>();
@ -23,21 +33,29 @@ fn main() {
}
let mut loopbackmode:bool = false;
let mut addresses:Vec<SocketAddr> = Vec::new();
let mut ports:Vec<u16> = Vec::new();
let mut ports:Vec<(PortPrivacy,u16)> = Vec::new();
for arg in argv[1..].iter() {
if arg == LOOPBACK_FLAG {
loopbackmode = true;
} else {
match arg.parse() {
match arg.trim_end_matches(&['p','w'][..]).parse::<u16>() {
Err(_why) => {
match arg.parse() {
match arg.parse::<SocketAddr>() {
Err(_why) => {
eprintln!("-!- could not parse `{}` as a socket address or port number",arg);
},
Ok(address) => addresses.push(address),
};
},
Ok(n) => ports.push(n),
Ok(n) => if arg.ends_with("p") {
println!("-i- accepting only local connections on port {}",n);
ports.push((PortPrivacy::Private,n));
} else if arg.ends_with("w") {
println!("-i- accepting only connections whitelisted in {} on port {}",ALLOWFILE_NAME,n);
ports.push((PortPrivacy::Whitelist,n));
} else {
ports.push((PortPrivacy::Public,n));
},
};
}
}
@ -75,17 +93,51 @@ fn main() {
}
},
};
let mut allowlist:HashSet<IpAddr> = HashSet::new();
match File::open(&ALLOWFILE_NAME) {
Err(why) => match why.kind() {
io::ErrorKind::NotFound => (),
_ => {
eprintln!("-!- failed to open {} to read IP address allowing rules: {}",ALLOWFILE_NAME,why);
exit(1);
},
},
Ok(mut file) => {
let mut allowstring = String::new();
match file.read_to_string(&mut allowstring) {
Err(why) => {
eprintln!("-!- failed to read {} for IP address allowing rules: {}",ALLOWFILE_NAME,why);
exit(1);
},
Ok(_) => (),
};
for line in allowstring.lines() {
match line.parse::<IpAddr>() {
Err(_why) => {
eprintln!("-!- could not parse '{}' in {} as an IP address",line,ALLOWFILE_NAME);
exit(1);
},
Ok(address) => allowlist.insert(address),
};
}
},
};
let mut connectors:VecDeque<(u16,TcpListener,VecDeque<(TcpStream,SocketAddr)>)> = VecDeque::new();
for port in ports.iter() {
match TcpListener::bind(&format!("[::]:{}",port)) {
let mut connectors:VecDeque<(PortPrivacy,u16,TcpListener,VecDeque<(TcpStream,SocketAddr)>)> = VecDeque::new();
for (privacy,port) in ports.iter() {
let bindaddress:&str = match privacy {
PortPrivacy::Private => "127.0.0.1",
_ => "[::]",
};
match TcpListener::bind(&format!("{}:{}",bindaddress,port)) {
Err(why) => {
eprintln!("-!- failed to bind TCP listener: {}",why);
exit(1);
},
Ok(listener) => {
listener.set_nonblocking(true).expect("cannot set listener to nonblocking");
connectors.push_back((*port,listener,VecDeque::new()));
connectors.push_back((*privacy,*port,listener,VecDeque::new()));
},
};
}
@ -141,7 +193,7 @@ fn main() {
if bottle.len() > 0 {
idlecycles = 0;
}
for (_port,_listener,otherconnects) in connectors.iter_mut() {
for (_privacy,_port,_listener,otherconnects) in connectors.iter_mut() {
for (otherstream,_otheraddress) in otherconnects.iter_mut() {
let _ = otherstream.write_all(&bottle);
}
@ -149,7 +201,7 @@ fn main() {
}
bottle.clear();
if let Some((port,listener,mut connections)) = connectors.pop_front() {
if let Some((privacy,port,listener,mut connections)) = connectors.pop_front() {
match listener.accept() {
Err(why) => match why.kind() {
io::ErrorKind::WouldBlock => (),
@ -159,7 +211,9 @@ fn main() {
},
Ok((stream,address)) => {
if blocklist.contains(&address.ip()) {
println!("-x- refusing connection to {} (listed in {})",address,BLOCKFILE_NAME);
println!("-x- refusing connection to {} on port {} (listed in {})",address,port,BLOCKFILE_NAME);
} else if privacy == PortPrivacy::Whitelist && !address.ip().is_loopback() && !allowlist.contains(&address.ip()) {
println!("-x- refusing connection to {} on port {} (not listed in {})",address,port,ALLOWFILE_NAME);
} else {
println!("--> connection opened by {} on port {}",address,port);
stream.set_nonblocking(true).expect("cannot set stream to nonblocking");
@ -190,7 +244,7 @@ fn main() {
if bottle.len() > 0 {
idlecycles = 0;
}
for (_port,_listener,otherconnects) in connectors.iter_mut() {
for (_privacy,_port,_listener,otherconnects) in connectors.iter_mut() {
for (otherstream,_otheraddress) in otherconnects.iter_mut() {
let _ = otherstream.write_all(&bottle);
}
@ -199,7 +253,7 @@ fn main() {
let _ = otherstream.write_all(&bottle);
}
}
connectors.push_back((port,listener,connections));
connectors.push_back((privacy,port,listener,connections));
}
if let Some(threadaddress) = brokensources.pop_front() {