-
Notifications
You must be signed in to change notification settings - Fork 1
feat(cli): connection flags + DX (quiet logging, random port, best-effort TLS) #397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
442c2cd
e1c5fd6
6ac4806
e13f5b8
c928bdf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,16 +51,35 @@ pub async fn database(config: &DatabaseConfig) -> Result<Client, Error> { | |
| Ok(client) | ||
| } | ||
|
|
||
| pub async fn bind_with_retry(server: &ServerConfig) -> TcpListener { | ||
| let address = &server.to_socket_address(); | ||
| pub async fn bind_with_retry(server: &ServerConfig, allow_random_fallback: bool) -> TcpListener { | ||
| let address = server.to_socket_address(); | ||
| let mut retry_count = 0; | ||
|
|
||
| loop { | ||
| match TcpListener::bind(address).await { | ||
| match TcpListener::bind(&address).await { | ||
| Ok(listener) => { | ||
| info!(msg = "Server waiting for connections", address); | ||
| report_listening(&listener); | ||
| return listener; | ||
| } | ||
| // The configured port is in use, but it's only the default (not | ||
| // explicitly set), so fall back to an OS-assigned port and report it | ||
| // rather than failing. | ||
| Err(err) if err.kind() == std::io::ErrorKind::AddrInUse && allow_random_fallback => { | ||
| match TcpListener::bind(format!("{}:0", server.host)).await { | ||
| Ok(listener) => { | ||
| println!( | ||
| "Port {} is already in use; listening on an OS-assigned port instead.", | ||
| server.port | ||
| ); | ||
| report_listening(&listener); | ||
| return listener; | ||
| } | ||
| Err(err) => { | ||
| error!(msg = "Error binding connection", error = err.to_string()); | ||
| std::process::exit(exitcode::CONFIG); | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+67
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Inspect how ServerConfig::to_socket_address formats host:port (brackets for IPv6?)
ast-grep --pattern $'fn to_socket_address($$$) {
$$$
}'
echo "----- raw search -----"
rg -nP -C3 '\bto_socket_address\b'Repository: cipherstash/proxy Length of output: 2830 Fix IPv6-literal concern: primary bind has the same formatting issue as the fallback The fallback uses 🤖 Prompt for AI Agents |
||
| Err(err) => { | ||
| if retry_count > MAX_RETRY_COUNT { | ||
| error!( | ||
|
|
@@ -80,6 +99,21 @@ pub async fn bind_with_retry(server: &ServerConfig) -> TcpListener { | |
| } | ||
| } | ||
|
|
||
| /// Report the address the proxy is listening on. Uses `println!` so the | ||
| /// listening address (including an OS-assigned fallback port) is always visible, | ||
| /// even when logging is quiet. | ||
| fn report_listening(listener: &TcpListener) { | ||
| match listener.local_addr() { | ||
| Ok(addr) => { | ||
| println!("CipherStash Proxy listening on {addr}"); | ||
| info!(msg = "Server waiting for connections", address = %addr); | ||
| } | ||
| Err(_) => { | ||
| info!(msg = "Server waiting for connections"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub async fn connect_with_retry(addr: &str) -> Result<TcpStream, Error> { | ||
| let mut retry_count = 0; | ||
|
|
||
|
|
@@ -157,3 +191,28 @@ pub fn configure(stream: &TcpStream) { | |
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[tokio::test] | ||
| async fn bind_falls_back_to_random_port_when_default_in_use() { | ||
| // Occupy a port, then ask bind_with_retry to bind the same one. | ||
| let occupied = TcpListener::bind("127.0.0.1:0").await.unwrap(); | ||
| let occupied_port = occupied.local_addr().unwrap().port(); | ||
|
|
||
| let server = ServerConfig { | ||
| host: "127.0.0.1".to_string(), | ||
| port: occupied_port, | ||
| ..ServerConfig::default() | ||
| }; | ||
|
|
||
| // With fallback allowed, it binds a different OS-assigned port. | ||
| let listener = bind_with_retry(&server, true).await; | ||
| let bound_port = listener.local_addr().unwrap().port(); | ||
|
|
||
| assert_ne!(bound_port, occupied_port); | ||
| assert_ne!(bound_port, 0); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.