mirror of
https://github.com/telemt/telemt.git
synced 2026-04-26 15:04:09 +03:00
BINDTODEVICE for Direct Upstreams by #683
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
@@ -67,6 +67,7 @@ pub fn format_sample_line(sample: &MePingSample) -> String {
|
||||
fn format_direct_with_config(
|
||||
interface: &Option<String>,
|
||||
bind_addresses: &Option<Vec<String>>,
|
||||
bindtodevice: &Option<String>,
|
||||
) -> Option<String> {
|
||||
let mut direct_parts: Vec<String> = Vec::new();
|
||||
if let Some(dev) = interface.as_deref().filter(|v| !v.is_empty()) {
|
||||
@@ -75,6 +76,9 @@ fn format_direct_with_config(
|
||||
if let Some(src) = bind_addresses.as_ref().filter(|v| !v.is_empty()) {
|
||||
direct_parts.push(format!("src={}", src.join(",")));
|
||||
}
|
||||
if let Some(device) = bindtodevice.as_deref().filter(|v| !v.is_empty()) {
|
||||
direct_parts.push(format!("bindtodevice={device}"));
|
||||
}
|
||||
if direct_parts.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -231,8 +235,11 @@ pub async fn format_me_route(
|
||||
UpstreamType::Direct {
|
||||
interface,
|
||||
bind_addresses,
|
||||
bindtodevice,
|
||||
} => {
|
||||
if let Some(route) = format_direct_with_config(interface, bind_addresses) {
|
||||
if let Some(route) =
|
||||
format_direct_with_config(interface, bind_addresses, bindtodevice)
|
||||
{
|
||||
route
|
||||
} else {
|
||||
detect_direct_route_details(reports, prefer_ipv6, v4_ok, v6_ok)
|
||||
|
||||
@@ -158,6 +158,56 @@ pub fn create_outgoing_socket_bound(addr: SocketAddr, bind_addr: Option<IpAddr>)
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
/// Pin an outgoing socket to a specific Linux network interface via SO_BINDTODEVICE.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn bind_outgoing_socket_to_device(socket: &Socket, device: &str) -> Result<()> {
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::os::fd::AsRawFd;
|
||||
|
||||
let name = device.trim();
|
||||
if name.is_empty() {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"bindtodevice must not be empty",
|
||||
));
|
||||
}
|
||||
|
||||
// The kernel expects an interface name buffer with a trailing NUL.
|
||||
if name.len() >= libc::IFNAMSIZ {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"bindtodevice exceeds IFNAMSIZ",
|
||||
));
|
||||
}
|
||||
let mut ifname = [0u8; libc::IFNAMSIZ];
|
||||
ifname[..name.len()].copy_from_slice(name.as_bytes());
|
||||
|
||||
let rc = unsafe {
|
||||
libc::setsockopt(
|
||||
socket.as_raw_fd(),
|
||||
libc::SOL_SOCKET,
|
||||
libc::SO_BINDTODEVICE,
|
||||
ifname.as_ptr().cast::<libc::c_void>(),
|
||||
(name.len() + 1) as libc::socklen_t,
|
||||
)
|
||||
};
|
||||
if rc != 0 {
|
||||
return Err(Error::last_os_error());
|
||||
}
|
||||
debug!("Pinned outgoing socket to interface {}", name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stub for non-Linux targets where SO_BINDTODEVICE is unavailable.
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn bind_outgoing_socket_to_device(_socket: &Socket, _device: &str) -> Result<()> {
|
||||
use std::io::{Error, ErrorKind};
|
||||
Err(Error::new(
|
||||
ErrorKind::Unsupported,
|
||||
"bindtodevice is supported only on Linux",
|
||||
))
|
||||
}
|
||||
|
||||
/// Get local address of a socket
|
||||
#[allow(dead_code)]
|
||||
pub fn get_local_addr(stream: &TcpStream) -> Option<SocketAddr> {
|
||||
|
||||
@@ -26,7 +26,9 @@ use crate::stats::Stats;
|
||||
use crate::transport::shadowsocks::{
|
||||
ShadowsocksStream, connect_shadowsocks, sanitize_shadowsocks_url,
|
||||
};
|
||||
use crate::transport::socket::{create_outgoing_socket_bound, resolve_interface_ip};
|
||||
use crate::transport::socket::{
|
||||
bind_outgoing_socket_to_device, create_outgoing_socket_bound, resolve_interface_ip,
|
||||
};
|
||||
use crate::transport::socks::{connect_socks4, connect_socks5};
|
||||
|
||||
/// Number of Telegram datacenters
|
||||
@@ -928,6 +930,7 @@ impl UpstreamManager {
|
||||
UpstreamType::Direct {
|
||||
interface,
|
||||
bind_addresses,
|
||||
bindtodevice,
|
||||
} => {
|
||||
let bind_ip = Self::resolve_bind_address(
|
||||
interface,
|
||||
@@ -943,6 +946,10 @@ impl UpstreamManager {
|
||||
}
|
||||
|
||||
let socket = create_outgoing_socket_bound(target, bind_ip)?;
|
||||
if let Some(device) = bindtodevice.as_deref().filter(|value| !value.is_empty()) {
|
||||
bind_outgoing_socket_to_device(&socket, device).map_err(ProxyError::Io)?;
|
||||
debug!(bindtodevice = %device, target = %target, "Pinned socket to interface");
|
||||
}
|
||||
if let Some(ip) = bind_ip {
|
||||
debug!(bind = %ip, target = %target, "Bound outgoing socket");
|
||||
} else if interface.is_some() || bind_addresses.is_some() {
|
||||
@@ -1209,6 +1216,7 @@ impl UpstreamManager {
|
||||
UpstreamType::Direct {
|
||||
interface,
|
||||
bind_addresses,
|
||||
bindtodevice,
|
||||
} => {
|
||||
let mut direct_parts = Vec::new();
|
||||
if let Some(dev) = interface.as_deref().filter(|v| !v.is_empty()) {
|
||||
@@ -1217,6 +1225,9 @@ impl UpstreamManager {
|
||||
if let Some(src) = bind_addresses.as_ref().filter(|v| !v.is_empty()) {
|
||||
direct_parts.push(format!("src={}", src.join(",")));
|
||||
}
|
||||
if let Some(device) = bindtodevice.as_deref().filter(|v| !v.is_empty()) {
|
||||
direct_parts.push(format!("bindtodevice={device}"));
|
||||
}
|
||||
if direct_parts.is_empty() {
|
||||
"direct".to_string()
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user