From 9b1d966b95c2329cdf247680fcbbaf1852265407 Mon Sep 17 00:00:00 2001 From: knutaf Date: Fri, 22 Sep 2023 11:03:05 -0700 Subject: [PATCH] Rename "broker" to "hub"; add convenience shortcut args; fix outbound connection bug The name "broker" never felt right to me. It acts more like an oldschool Ethernet hub, so I renamed it everywhere to "hub". Added short versions of a few args, in particular input modes and output modes, so you can do `-in -on` to silence input and output. Also `--eai` for `--exit-after-input`, which is a mouthful. If max inbound connections isn't set, we will now set it to one *per listen address* specified by the user, basically the number of times the user gave `-l`/`-L`. There was a bug where if any outbound connection to a target failed, the target was considered failed. The logic has been corrected to consider it failed only if *all* connections failed. Minor change to defer creating and configuring the socket for an outbound connection until after a valid source address has been found. --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 14 +++++----- src/main.rs | 78 ++++++++++++++++++++++++++--------------------------- 4 files changed, 48 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c07c066..25acb3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -443,7 +443,7 @@ dependencies = [ [[package]] name = "netcrab" -version = "0.7.0" +version = "0.8.0" dependencies = [ "bytes", "clap", diff --git a/Cargo.toml b/Cargo.toml index 9ee85a3..68ef6ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "netcrab" description = "A multi-purpose TCP/UDP listener and connector" keywords = ["tcp", "udp", "networking", "sockets"] categories = ["network-programming", "command-line-utilities"] -version = "0.7.0" +version = "0.8.0" authors = ["knutaf"] edition = "2021" repository = "https://github.com/knutaf/netcrab" diff --git a/README.md b/README.md index b75a1a1..3d17943 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ The `ADDR:PORT` syntax supports some special variants: - \*:*PORT* - uses the wildcard IPv4 and IPv6 addresses (0.0.0.0 and [::]) with the specified port. E.g. `*:5000` - \* - same as above but implicitly use port 0 -The TCP server by default only allows a single incoming connection to be active at a time, but the `-m max_inbound_clients` flag allows more than one to connect. +The TCP server by default only allows a single incoming connection to be active at a time per listening address, but the `-m max_inbound_clients` flag allows more than one to connect. As with client mode, stdin is sent to all connected sockets, and incoming data from all sockets is sent to stdout. This can be changed using the input and output mode arguments below. @@ -161,16 +161,16 @@ Now we get to the really useful parts. Netcrab can turn into a router, forwardin The first and second channels pass all the way through but don't cross the streams. If Host 1 or Host 3 disconnects an endpoint, the disconnection is "forwarded" to the other end of the channel too. If that behavior doesn't work for you, you can switch to Lingering Channels mode. -When in channels mode, max clients is automatically bumped to 10 under the assumption that the user probably wants more than one connection at a time in order to actually, you know, use the channels. This can be overridden with `-m`. +When in channels mode, max clients is automatically bumped to 10 per listening address under the assumption that the user probably wants more than one connection at a time in order to actually, you know, use the channels. This can be overridden with `-m`. -## Broker mode +## Hub mode -`netcrab --fm broker` +`netcrab --fm hub` -Broker mode is similar to channels mode, but simpler: all traffic from all network sources is forwarded back to all other sockets. You could use it to set up a chat room or something. +Hub mode is similar to channels mode, but simpler: all traffic from all network sources is forwarded back to all other sockets. You could use it to set up a chat room or something. -As in channels mode, when in broker mode, max clients is automatically bumped to 10 but can be overridden with `-m`. +As in channels mode, when in hub mode, max clients is automatically bumped to 10 per listening address but can be overridden with `-m`. ## Endless possibilities -Pretty much all of the capabilities described above can be combined and used at the same time. For example, listening on multiple addresses and connecting to multiple targets in the same session while brokering all the connections can be done. +Pretty much all of the capabilities described above can be combined and used at the same time. For example, you can listen on multiple addresses and connect to multiple targets in the same session while using hub to forward between all the connections. diff --git a/src/main.rs b/src/main.rs index cbcc046..154826b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -240,16 +240,16 @@ fn should_forward_to(args: &NcArgs, source: &SocketAddr, dest: &SocketAddr) -> b return false; } - // When brokering, any incoming traffic should be sent back to all other peers. - // When not brokering, only send to the local output or back to the source (only possible in + // When in hub mode, any incoming traffic should be sent back to all other peers. + // When not in hub mode, only send to the local output or back to the source (only possible in // echo mode). - if args.forwarding_mode != ForwardingMode::Broker + if args.forwarding_mode != ForwardingMode::Hub && (dest != source) && *dest != LOCAL_IO_PEER_ADDR && *source != LOCAL_IO_PEER_ADDR { if args.verbose { - eprintln!("Skipping forwarding to other remote endpoint due to not broker mode."); + eprintln!("Skipping forwarding to other remote endpoint due to not hub mode."); } return false; @@ -549,7 +549,7 @@ impl<'a> TcpRouter<'a> { } // If no send happened from channels earlier, try again with regular routing. We shouldn't be in - // both broker mode and channel mode, so mostly this covers making sure local input can be sent out, + // both hub mode and channel mode, so mostly this covers making sure local input can be sent out, // since it isn't associated with any channel. if !did_send { // Broadcast any incoming data back to whichever other connected sockets and/or local IO it @@ -1039,14 +1039,6 @@ async fn tcp_connect_to_candidate( source_addrs: &SockAddrSet, args: &NcArgs, ) -> std::io::Result { - let socket = if addr.is_ipv4() { - tokio::net::TcpSocket::new_v4() - } else { - tokio::net::TcpSocket::new_v6() - }?; - - configure_socket_options(&socket, addr.is_ipv4(), args)?; - // Bind the local socket to any local address that matches the address family of the destination. let source_addr = source_addrs .iter() @@ -1058,6 +1050,14 @@ async fn tcp_connect_to_candidate( eprintln!("Connecting from {} to {}", source_addr, addr); + let socket = if addr.is_ipv4() { + tokio::net::TcpSocket::new_v4() + } else { + tokio::net::TcpSocket::new_v6() + }?; + + configure_socket_options(&socket, addr.is_ipv4(), args)?; + socket.bind(*source_addr)?; let stream = socket.connect(*addr).await?; @@ -1166,7 +1166,7 @@ async fn do_tcp( // one successful connection is established, move on to the next target. Otherwise we'd end up sending duplicate // traffic to the same host. for target in targets.iter() { - let mut succeeded_all_connections = true; + let mut succeeded_any_connection = false; for addr in target.addrs.iter() { // Skip incompatible candidates from what address family the user specified. if args.af_limit.use_v4 && addr.is_ipv6() || args.af_limit.use_v6 && addr.is_ipv4() { @@ -1186,24 +1186,21 @@ async fn do_tcp( )); // Stop after first successful connection for this target. + succeeded_any_connection = true; break; } Err(e) => { eprintln!("Failed to connect to {}. Error: {}", addr, e); - succeeded_all_connections = false; } } } // Fail if we couldn't connect to any address for a given target, even if we successfully connected to another // target. - if !succeeded_all_connections { + if !succeeded_any_connection { return Err(std::io::Error::new( std::io::ErrorKind::NotConnected, - format!( - "Failed to connect to all candidates for {}", - &target.addr_string - ), + format!("Failed to connect to {}", &target.addr_string), )); } } @@ -1717,7 +1714,7 @@ async fn handle_udp_sockets( } // If no send happened from channels earlier, try again with regular routing. We shouldn't be in both - // broker mode and channel mode, so mostly this covers making sure local input can be sent out, since + // hub mode and channel mode, so mostly this covers making sure local input can be sent out, since // it isn't associated with any channel. // // Broadcast any incoming data back to whichever other connected sockets and/or local IO it should go @@ -1804,7 +1801,7 @@ struct AfLimit { #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, clap::ValueEnum)] enum InputMode { /// No input - #[value(name = "none", alias = "no")] + #[value(name = "none", alias = "n")] Null, /// Read from stdin @@ -1833,7 +1830,7 @@ enum OutputMode { Stdout, /// No output - #[value(name = "none", alias = "no")] + #[value(name = "none", alias = "n")] Null, } @@ -1843,9 +1840,9 @@ enum ForwardingMode { #[value(name = "none")] Null, - /// Broker mode: forward traffic between connected clients. Automatically sets -m 10. - #[value(name = "broker", alias = "b")] - Broker, + /// Hub mode: forward traffic between connected clients. Automatically sets -m 10. + #[value(name = "hub", alias = "h")] + Hub, /// Channel mode: automatically group pairs of remote addresses from different IP addresses into "channels" and forward traffic between the two endpoints, but not between different channels. If one end of a channel disconnects, it automatically disconnects the other end too. This disconnection part has no effect with UDP. Automatically sets -m 10. #[value(name = "channels", alias = "c")] @@ -1970,7 +1967,7 @@ pub struct NcArgs { output_mode: OutputMode, /// Exit after input completes (e.g. after stdin EOF) - #[arg(long = "exit-after-input")] + #[arg(long = "exit-after-input", alias = "eai")] should_exit_after_input_closed: bool, /// Join multicast group given by hostname (outbound UDP only) @@ -2051,18 +2048,6 @@ async fn main() -> Result<(), String> { _ => {} } - // If max_inbound_connections wasn't specified explicitly, set its value automatically. If in broker or channel - // mode, you generally want more than one incoming client at a time, or else why are you in a forwarding mode?? - // Otherwise, safely limit to just one at a time. - if args.max_inbound_connections.is_none() { - args.max_inbound_connections = Some(match args.forwarding_mode { - ForwardingMode::Null => 1, - ForwardingMode::Broker | ForwardingMode::Channels | ForwardingMode::LingerChannels => { - 10 - } - }); - } - let mut targets: Vec = vec![]; if !args.targets.is_empty() { @@ -2142,6 +2127,21 @@ async fn main() -> Result<(), String> { .await .map_err(format_io_err)?; + // If max_inbound_connections wasn't specified explicitly, set its value automatically. If in hub or channel + // mode, you generally want more than one incoming client at a time, or else why are you in a forwarding mode?? + // Otherwise, safely limit to just one per user-specified listen address at a time. + if args.max_inbound_connections.is_none() { + args.max_inbound_connections = Some( + listen_addr_strings.len() + * match args.forwarding_mode { + ForwardingMode::Null => 1, + ForwardingMode::Hub + | ForwardingMode::Channels + | ForwardingMode::LingerChannels => 10, + }, + ); + } + let result = if args.is_udp { do_udp(&listen_addrs, &outbound_source_addrs, &targets, &args).await } else {