Compare commits

...

7 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
25719ecd0d Improve code quality - module-level constant and better error handling
Co-authored-by: rustdesk <71636191+rustdesk@users.noreply.github.com>
2026-02-05 03:55:09 +00:00
copilot-swe-agent[bot]
5d490b2bdd Address code review feedback - remove unused imports and use const for backlog
Co-authored-by: rustdesk <71636191+rustdesk@users.noreply.github.com>
2026-02-05 03:54:00 +00:00
copilot-swe-agent[bot]
7de2390d23 Add documentation for network interface binding feature
Co-authored-by: rustdesk <71636191+rustdesk@users.noreply.github.com>
2026-02-05 03:52:52 +00:00
copilot-swe-agent[bot]
16dde51e23 Add unit test for bind interface IP address parsing
Co-authored-by: rustdesk <71636191+rustdesk@users.noreply.github.com>
2026-02-05 03:52:03 +00:00
copilot-swe-agent[bot]
780f99bd32 Implement bind-interface option for network binding without modifying submodule
Co-authored-by: rustdesk <71636191+rustdesk@users.noreply.github.com>
2026-02-05 03:50:48 +00:00
copilot-swe-agent[bot]
1b87c33fdc Add bind-interface configuration option for network binding
Co-authored-by: rustdesk <71636191+rustdesk@users.noreply.github.com>
2026-02-05 03:49:20 +00:00
copilot-swe-agent[bot]
ef59778720 Initial plan 2026-02-05 03:43:51 +00:00
2 changed files with 169 additions and 2 deletions

109
docs/NETWORK_BINDING.md Normal file
View File

@@ -0,0 +1,109 @@
# Network Interface Binding
RustDesk can be configured to bind to a specific network interface by IP address.
## Configuration
To bind RustDesk to a specific network interface, set the `bind-interface` option in your configuration.
### Option Name
`bind-interface`
### Supported Values
- Empty string (default): Bind to all available interfaces (0.0.0.0 for IPv4, :: for IPv6)
- IPv4 address: e.g., `192.168.1.100`, `10.0.0.1`
- IPv6 address: e.g., `::1`, `fe80::1`, `2001:db8::1`
## Usage Examples
### Bind to a specific IPv4 address
To bind RustDesk to only listen on interface with IP address `192.168.1.100`:
```json
{
"options": {
"bind-interface": "192.168.1.100"
}
}
```
### Bind to IPv6 localhost
```json
{
"options": {
"bind-interface": "::1"
}
}
```
### Bind to all interfaces (default)
```json
{
"options": {
"bind-interface": ""
}
}
```
Or simply omit the `bind-interface` option entirely.
## How It Works
When the `bind-interface` option is set:
1. RustDesk reads the configuration when starting the direct server
2. If `bind-interface` is empty or not set, it binds to all available network interfaces
3. If `bind-interface` contains a valid IP address:
- RustDesk validates the IP address format (supports both IPv4 and IPv6)
- Creates a TCP socket and binds it to the specified IP address
- Starts listening for connections on that interface only
## Use Cases
### Multiple Network Interfaces
If your machine has multiple network interfaces (e.g., Ethernet, Wi-Fi, VPN), you can force RustDesk to use a specific one:
- **Example**: Force RustDesk to use the Ethernet interface at `192.168.1.100` instead of the Wi-Fi interface at `192.168.2.50`
### Security
Restrict RustDesk to listen only on internal network interfaces:
- **Example**: Bind to `10.0.0.5` (internal network) instead of listening on all interfaces including public-facing ones
### VPN/Tunneling
Force RustDesk to use a VPN or tunnel interface:
- **Example**: Bind to the VPN interface IP address to ensure all traffic goes through the VPN
## Troubleshooting
### Invalid bind address error
If you see an error like "Invalid bind interface address", check that:
- The IP address format is correct (no typos)
- The IP address is valid (e.g., not `999.999.999.999`)
- The IP address exists on one of your machine's network interfaces
### Failed to start direct server
If RustDesk fails to start with a bind error, it could be because:
- The specified IP address doesn't exist on your machine
- Another application is already using the port on that interface
- You don't have permission to bind to that address
### Finding your network interface IP addresses
**Windows:**
```cmd
ipconfig
```
**Linux/macOS:**
```bash
ip addr show # Linux
ifconfig # macOS/Linux
```
Look for the `inet` (IPv4) or `inet6` (IPv6) addresses associated with your network interfaces.
## Related Discussion
This feature was implemented to address: https://github.com/rustdesk/rustdesk/discussions/2286

View File

@@ -11,7 +11,7 @@ use uuid::Uuid;
use hbb_common::{
allow_err,
anyhow::{self, bail},
anyhow::{self, bail, Context},
config::{
self, keys::*, option2bool, use_ws, Config, CONNECT_TIMEOUT, REG_INTERVAL, RENDEZVOUS_PORT,
},
@@ -749,6 +749,9 @@ impl RendezvousMediator {
}
}
// Socket backlog value used for TCP listeners, matching the value in hbb_common::tcp
const SOCKET_BACKLOG: u32 = 128;
fn get_direct_port() -> i32 {
let mut port = Config::get_option("direct-access-port")
.parse::<i32>()
@@ -759,6 +762,37 @@ fn get_direct_port() -> i32 {
port
}
async fn listen_with_bind_interface(port: u16) -> ResultType<hbb_common::tokio::net::TcpListener> {
use hbb_common::tokio::net::{TcpListener, TcpSocket};
use std::net::{IpAddr, SocketAddr};
let bind_interface = Config::get_option("bind-interface");
if bind_interface.is_empty() {
// Use the default listen_any behavior
hbb_common::tcp::listen_any(port).await
} else {
// Parse the bind interface address
let addr: IpAddr = bind_interface.parse()
.with_context(|| format!("Invalid bind interface address: {}", bind_interface))?;
let socket_addr = SocketAddr::new(addr, port);
// Create and bind socket
let socket = match socket_addr {
SocketAddr::V4(..) => TcpSocket::new_v4()?,
SocketAddr::V6(..) => TcpSocket::new_v6()?,
};
// Set socket options
#[cfg(all(unix, not(target_os = "illumos")))]
allow_err!(socket.set_reuseport(true));
allow_err!(socket.set_reuseaddr(true));
socket.bind(socket_addr)?;
Ok(socket.listen(SOCKET_BACKLOG)?)
}
}
async fn direct_server(server: ServerPtr) {
let mut listener = None;
let mut port = 0;
@@ -769,7 +803,7 @@ async fn direct_server(server: ServerPtr) {
) || option2bool("stop-service", &Config::get_option("stop-service"));
if !disabled && listener.is_none() {
port = get_direct_port();
match hbb_common::tcp::listen_any(port as _).await {
match listen_with_bind_interface(port as _).await {
Ok(l) => {
listener = Some(l);
log::info!(
@@ -904,3 +938,27 @@ async fn udp_nat_listen(
})?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::IpAddr;
#[test]
fn test_bind_interface_parsing() {
// Test valid IPv4 addresses
assert!("192.168.1.100".parse::<IpAddr>().is_ok());
assert!("10.0.0.1".parse::<IpAddr>().is_ok());
assert!("127.0.0.1".parse::<IpAddr>().is_ok());
// Test valid IPv6 addresses
assert!("::1".parse::<IpAddr>().is_ok());
assert!("fe80::1".parse::<IpAddr>().is_ok());
assert!("2001:db8::1".parse::<IpAddr>().is_ok());
// Test invalid addresses
assert!("invalid".parse::<IpAddr>().is_err());
assert!("999.999.999.999".parse::<IpAddr>().is_err());
assert!("".parse::<IpAddr>().is_err());
}
}