added port privacy
This commit is contained in:
parent
67529fdb0d
commit
5941fe4728
27
README.md
27
README.md
|
@ -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.
|
||||
|
|
80
sunbeam.rs
80
sunbeam.rs
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue