diff --git a/Cargo.lock b/Cargo.lock index a11d68fea..d8c112fd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1568,6 +1568,7 @@ dependencies = [ "protobuf-codegen-pure", "quinn", "rand 0.8.4", + "regex", "serde 1.0.133", "serde_derive", "serde_json 1.0.74", diff --git a/libs/hbb_common/Cargo.toml b/libs/hbb_common/Cargo.toml index 4dc7dcf86..d1868f5b5 100644 --- a/libs/hbb_common/Cargo.toml +++ b/libs/hbb_common/Cargo.toml @@ -28,6 +28,7 @@ confy = { git = "https://github.com/open-trade/confy" } dirs-next = "2.0" filetime = "0.2" sodiumoxide = "0.2" +regex = "1.4" tokio-socks = { git = "https://github.com/fufesou/tokio-socks" } [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] diff --git a/libs/hbb_common/src/lib.rs b/libs/hbb_common/src/lib.rs index 38487a1a4..f84221b70 100644 --- a/libs/hbb_common/src/lib.rs +++ b/libs/hbb_common/src/lib.rs @@ -27,6 +27,7 @@ pub use anyhow::{self, bail}; pub use futures_util; pub mod config; pub mod fs; +pub use regex; pub use sodiumoxide; pub use tokio_socks; pub use tokio_socks::IntoTargetAddr; diff --git a/src/client.rs b/src/client.rs index ff4603776..29e2585b1 100644 --- a/src/client.rs +++ b/src/client.rs @@ -103,8 +103,33 @@ impl Drop for OboePlayer { impl Client { pub async fn start(peer: &str, conn_type: ConnType) -> ResultType<(Stream, bool)> { + match Self::_start(peer, conn_type).await { + Err(err) => { + let err_str = err.to_string(); + if err_str.starts_with("Failed") { + bail!(err_str + ": Please try later"); + } else { + return Err(err); + } + } + Ok(x) => Ok(x), + } + } + + async fn _start(peer: &str, conn_type: ConnType) -> ResultType<(Stream, bool)> { // to-do: remember the port for each peer, so that we can retry easier let any_addr = Config::get_any_listen_addr(); + if crate::is_ip(peer) { + return Ok(( + socket_client::connect_tcp( + crate::check_port(peer, RELAY_PORT + 1), + any_addr, + RENDEZVOUS_TIMEOUT, + ) + .await?, + true, + )); + } let rendezvous_server = crate::get_rendezvous_server(1_000).await; log::info!("rendezvous server: {}", rendezvous_server); diff --git a/src/common.rs b/src/common.rs index a29273397..13599045e 100644 --- a/src/common.rs +++ b/src/common.rs @@ -342,7 +342,7 @@ async fn test_rendezvous_server_() { futs.push(tokio::spawn(async move { let tm = std::time::Instant::now(); if socket_client::connect_tcp( - &crate::check_port(&host, RENDEZVOUS_PORT), + crate::check_port(&host, RENDEZVOUS_PORT), Config::get_any_listen_addr(), RENDEZVOUS_TIMEOUT, ) @@ -461,3 +461,10 @@ async fn _check_software_update() -> hbb_common::ResultType<()> { } Ok(()) } + +pub fn is_ip(id: &str) -> bool { + hbb_common::regex::Regex::new(r"^\d+\.\d+\.\d+\.\d+$") + .unwrap() + .is_match(id) +} + diff --git a/src/ipc.rs b/src/ipc.rs index 841dd37ef..e197d804d 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -218,6 +218,8 @@ async fn handle(data: Data, stream: &mut Connection) { value = Some(Config::get_salt()); } else if name == "rendezvous_server" { value = Some(Config::get_rendezvous_server()); + } else if name == "rendezvous_servers" { + value = Some(Config::get_rendezvous_servers().join(",")); } else { value = None; } @@ -225,6 +227,7 @@ async fn handle(data: Data, stream: &mut Connection) { } Some(value) => { if name == "id" { + Config::set_key_confirmed(false); Config::set_id(&value); } else if name == "password" { Config::set_password(&value); diff --git a/src/rendezvous_mediator.rs b/src/rendezvous_mediator.rs index b994b4b91..d46344517 100644 --- a/src/rendezvous_mediator.rs +++ b/src/rendezvous_mediator.rs @@ -31,6 +31,7 @@ lazy_static::lazy_static! { static ref SOLVING_PK_MISMATCH: Arc> = Default::default(); } static SHOULD_EXIT: AtomicBool = AtomicBool::new(false); +const REG_INTERVAL: i64 = 12_000; #[derive(Clone)] pub struct RendezvousMediator { @@ -54,6 +55,10 @@ impl RendezvousMediator { crate::common::test_nat_type(); nat_tested = true; } + let server_cloned = server.clone(); + tokio::spawn(async move { + allow_err!(direct_server(server_cloned).await); + }); loop { Config::reset_online(); if Config::get_option("stop-service").is_empty() { @@ -111,7 +116,6 @@ impl RendezvousMediator { const TIMER_OUT: Duration = Duration::from_secs(1); let mut timer = interval(TIMER_OUT); let mut last_timer = SystemTime::UNIX_EPOCH; - const REG_INTERVAL: i64 = 12_000; const REG_TIMEOUT: i64 = 3_000; const MAX_FAILS1: i64 = 3; const MAX_FAILS2: i64 = 6; @@ -246,7 +250,7 @@ impl RendezvousMediator { // old UDP socket not work any more after network recover if let Some(s) = socket_client::rebind_udp(any_addr).await? { socket = s; - }; + } last_dns_check = now; } } else if fails > MAX_FAILS1 { @@ -457,3 +461,43 @@ impl RendezvousMediator { Ok(()) } } + +async fn direct_server(server: ServerPtr) -> ResultType<()> { + let port = RENDEZVOUS_PORT + 2; + let addr = format!("0.0.0.0:{}", port); + let mut listener = None; + loop { + if !Config::get_option("direct-server").is_empty() && listener.is_none() { + listener = Some(hbb_common::tcp::new_listener(&addr, false).await?); + log::info!( + "Direct server listening on: {}", + &listener.as_ref().unwrap().local_addr()? + ); + } + if let Some(l) = listener.as_mut() { + if let Ok(Ok((stream, addr))) = hbb_common::timeout(1000, l.accept()).await { + if Config::get_option("direct-server").is_empty() { + continue; + } + log::info!("direct access from {}", addr); + let local_addr = stream.local_addr()?; + let server = server.clone(); + tokio::spawn(async move { + allow_err!( + crate::server::create_tcp_connection( + server, + hbb_common::Stream::from(stream, local_addr), + addr, + false, + ) + .await + ); + }); + } else { + sleep(0.1).await; + } + } else { + sleep(1.).await; + } + } +} diff --git a/src/server/connection.rs b/src/server/connection.rs index 482a9f654..75a9071f8 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -589,7 +589,7 @@ impl Connection { } _ => {} } - if lr.username != Config::get_id() { + if !crate::is_ip(&lr.username) && lr.username != Config::get_id() { self.send_login_error("Offline").await; } else if lr.password.is_empty() { self.try_start_cm(lr.my_id, lr.my_name, false).await; diff --git a/src/ui/common.css b/src/ui/common.css index 574fbb1a5..7e7eb6bf1 100644 --- a/src/ui/common.css +++ b/src/ui/common.css @@ -4,6 +4,7 @@ html { var(gray-bg): #eee; var(bg): white; var(border): #ccc; + var(hover-border): #999; var(text): #222; var(placeholder): #aaa; var(lighter-text): #888; @@ -52,6 +53,10 @@ button.button:active, button.active { border-color: color(accent); } +button.button:hover, button.outline:hover { + border-color: color(hover-border); +} + input[type=text], input[type=password], input[type=number] { width: *; font-size: 1.5em; diff --git a/src/ui/index.tis b/src/ui/index.tis index f8d8f4921..a69e5b014 100644 --- a/src/ui/index.tis +++ b/src/ui/index.tis @@ -62,6 +62,24 @@ function createNewConnect(id, type) { handler.new_remote(id, type); } +var direct_server; +class DirectServer: Reactor.Component { + function this() { + direct_server = this; + } + + function render() { + var text = translate("Enable Direct IP Access"); + var cls = handler.get_option("direct-server") == "Y" ? "selected" : "line-through"; + return
  • {svg_checkmark}{text}
  • ; + } + + function onClick() { + handler.set_option("direct-server", handler.get_option("direct-server") == "Y" ? "" : "Y"); + this.update(); + } +} + var myIdMenu; var audioInputMenu; class AudioInputs: Reactor.Component { @@ -138,6 +156,7 @@ class MyIdMenu: Reactor.Component {
  • {translate('Socks5 Proxy')}
  • {svg_checkmark}{translate("Enable Service")}
  • +
  • {translate('About')} {" "} {handler.get_app_name()}
  • @@ -147,6 +166,7 @@ class MyIdMenu: Reactor.Component { event click $(svg#menu) (_, me) { audioInputMenu.update({ show: true }); this.toggleMenuState(); + if (direct_server) direct_server.update(); var menu = $(menu#config-options); me.popup(menu); }