feat(server): add support of IPv6 (no dual stack)
This commit is contained in:
+1
-1
@@ -9,7 +9,7 @@ Most runtime variables use the `OXICLOUD_` prefix. A few build-time or allocator
|
||||
| `OXICLOUD_STORAGE_PATH` | `./storage` | Root storage directory |
|
||||
| `OXICLOUD_STATIC_PATH` | `./static` | Static files directory |
|
||||
| `OXICLOUD_SERVER_PORT` | `8086` | Server port |
|
||||
| `OXICLOUD_SERVER_HOST` | `127.0.0.1` | Server bind address |
|
||||
| `OXICLOUD_SERVER_HOST` | `127.0.0.1` | Server bind addressi (IPv4 or IPv6 allowed) |
|
||||
| `OXICLOUD_BASE_URL` | (auto) | Public base URL for share links; defaults to `http://{host}:{port}` |
|
||||
| `OXICLOUD_MAX_UPLOAD_SIZE` | `10737418240` | Maximum upload size in bytes (10 GB on 64-bit, 1 GB on 32-bit) |
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ OXICLOUD_SERVER_PORT=8086
|
||||
|
||||
# Server bind address (default: 127.0.0.1)
|
||||
# Use 0.0.0.0 to bind to all interfaces in Docker
|
||||
# IPv6 format allowed, either ::1 or [::1]
|
||||
OXICLOUD_SERVER_HOST=127.0.0.1
|
||||
|
||||
# Public base URL for generating share links and external URLs
|
||||
|
||||
+62
-20
@@ -54,6 +54,65 @@ use interfaces::{
|
||||
create_api_routes, create_health_routes, create_public_api_routes, web::create_web_routes,
|
||||
};
|
||||
|
||||
fn parse_addr(host: &str, port: u16) -> Result<SocketAddr, String> {
|
||||
// Strip surrounding brackets from IPv6: [::1] -> ::1
|
||||
let host = host.trim();
|
||||
let host = host
|
||||
.strip_prefix('[')
|
||||
.and_then(|h| h.strip_suffix(']'))
|
||||
.unwrap_or(host);
|
||||
|
||||
// Try parsing as IPv6 first, then IPv4
|
||||
// and format the address string accordingly
|
||||
// - IPv6: "[::1]:8080"
|
||||
// - IPv4: "127.0.0.1:8080"
|
||||
let addr_str = if host.contains(':') {
|
||||
format!("[{host}]:{port}") // IPv6
|
||||
} else {
|
||||
format!("{host}:{port}") // IPv4
|
||||
};
|
||||
|
||||
addr_str
|
||||
.parse::<SocketAddr>()
|
||||
.map_err(|e| format!("Invalid address '{}': {}", addr_str, e))
|
||||
}
|
||||
|
||||
fn make_socket(addr: &SocketAddr) -> std::io::Result<Socket> {
|
||||
let domain = if addr.is_ipv6() {
|
||||
Domain::IPV6
|
||||
} else {
|
||||
Domain::IPV4
|
||||
};
|
||||
let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?;
|
||||
socket.set_reuse_address(true)?;
|
||||
// Allow multiple workers on the same port (future-ready)
|
||||
#[cfg(not(windows))]
|
||||
socket.set_reuse_port(true)?;
|
||||
// Disable Nagle's algorithm — send small responses (JSON, PROPFIND)
|
||||
// immediately instead of waiting up to 40ms for coalescing.
|
||||
socket.set_tcp_nodelay(true)?;
|
||||
// Detect dead connections within 60s instead of hours
|
||||
socket.set_keepalive(true)?;
|
||||
socket.set_tcp_keepalive(
|
||||
&TcpKeepalive::new()
|
||||
.with_time(Duration::from_secs(60))
|
||||
.with_interval(Duration::from_secs(10)),
|
||||
)?;
|
||||
socket.set_nonblocking(true)?;
|
||||
|
||||
// For IPv6: disable dual-stack to be explicit about what you're binding
|
||||
// (set true to restrict to IPv6-only, false to also accept IPv4-mapped)
|
||||
if addr.is_ipv6() {
|
||||
socket.set_only_v6(true)?; // explicit: one socket = one protocol
|
||||
}
|
||||
|
||||
socket.bind(&(*addr).into())?;
|
||||
// High backlog for connection bursts (WebDAV clients open many parallel connections)
|
||||
socket.listen(2048)?;
|
||||
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Load .env file if present (for local development)
|
||||
@@ -503,28 +562,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
// Start server — tuned socket for low-latency responses
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], config.server_port));
|
||||
// TODO: suport multiple addresses ?
|
||||
let addr = parse_addr(&config.server_host, config.server_port)?;
|
||||
tracing::info!("Starting OxiCloud server on http://{}", addr);
|
||||
|
||||
let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP))?;
|
||||
socket.set_reuse_address(true)?;
|
||||
// Allow multiple workers on the same port (future-ready)
|
||||
#[cfg(not(windows))]
|
||||
socket.set_reuse_port(true)?;
|
||||
// Disable Nagle's algorithm — send small responses (JSON, PROPFIND)
|
||||
// immediately instead of waiting up to 40ms for coalescing.
|
||||
socket.set_tcp_nodelay(true)?;
|
||||
// Detect dead connections within 60s instead of hours
|
||||
socket.set_keepalive(true)?;
|
||||
socket.set_tcp_keepalive(
|
||||
&TcpKeepalive::new()
|
||||
.with_time(Duration::from_secs(60))
|
||||
.with_interval(Duration::from_secs(10)),
|
||||
)?;
|
||||
socket.set_nonblocking(true)?;
|
||||
socket.bind(&addr.into())?;
|
||||
// High backlog for connection bursts (WebDAV clients open many parallel connections)
|
||||
socket.listen(2048)?;
|
||||
let socket = make_socket(&addr)?;
|
||||
|
||||
let listener = tokio::net::TcpListener::from_std(socket.into())?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user