如何通过特定的网络接口发送?
How can I send through a specific network interface?
我需要通过不同的网关动态发送消息。如何做到这一点,我朝这个方向迈出的第一步必须是什么?
在我的服务器上,我有两个连接:一个是直接连接,另一个是通过 VPN 连接。默认路由是直接连接,但我需要动态更改到 VPN 的连接。
目前我尝试从 libc::bind()
构建套接字,它可以工作,但没有预期的效果。
Changing the outgoing IP不是定义接口的解决方案
您可以使用不同的源 IP。我假设您已将系统配置为将不同的源 IP 路由到不同的网关(如果没有,这是操作员的问题,而不是程序员的问题)。您可以在 bind
函数中为套接字指定不同的源 IP。通常您传递它们的 'default' 值 (0.0.0.0),这意味着 'anything OS find reasonable',但您可以为您的任务指定确切的源 IP。
A C bind
签名:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
addr
可能包含具体地址。
正如评论中所建议的,我们必须使用 SO_BINDTODEVICE
,并且无法逃避 FFI,因为它在内部使用。
这里的工作示例:
extern crate libc;
use libc as c;
use std::ffi::CString;
use std::net::{TcpStream, SocketAddr};
use std::io::{self, Write};
use std::os::unix::io::FromRawFd;
use std::mem;
#[cfg(any(target_os = "linux"))]
fn connect_dev(addr: SocketAddr, link: &str) -> io::Result<TcpStream> {
let (addr_raw, addr_len) = match addr {
SocketAddr::V4(ref a) =>
(a as *const _ as *const _, mem::size_of_val(a) as c::socklen_t),
SocketAddr::V6(ref a) =>
(a as *const _ as *const _, mem::size_of_val(a) as c::socklen_t),
};
unsafe {
let fd = check_os_error(c::socket(c::AF_INET, c::SOCK_STREAM, 0))?;
check_os_error(c::setsockopt(
fd,
c::SOL_SOCKET,
c::SO_BINDTODEVICE,
CString::new(link).expect("device name").as_ptr() as *const c::c_void,
mem::size_of::<CString>() as c::socklen_t,
))?;
check_os_error(c::connect(fd, addr_raw, addr_len))?;
Ok(TcpStream::from_raw_fd(fd))
}
}
#[cfg(any(target_os = "linux"))]
pub fn check_os_error(res: c::c_int) -> io::Result<c::c_int> {
if res == -1 {
Err(io::Error::from_raw_os_error(unsafe { *c::__errno_location() as i32 }))
} else {
Ok(res)
}
}
我需要通过不同的网关动态发送消息。如何做到这一点,我朝这个方向迈出的第一步必须是什么?
在我的服务器上,我有两个连接:一个是直接连接,另一个是通过 VPN 连接。默认路由是直接连接,但我需要动态更改到 VPN 的连接。
目前我尝试从 libc::bind()
构建套接字,它可以工作,但没有预期的效果。
Changing the outgoing IP不是定义接口的解决方案
您可以使用不同的源 IP。我假设您已将系统配置为将不同的源 IP 路由到不同的网关(如果没有,这是操作员的问题,而不是程序员的问题)。您可以在 bind
函数中为套接字指定不同的源 IP。通常您传递它们的 'default' 值 (0.0.0.0),这意味着 'anything OS find reasonable',但您可以为您的任务指定确切的源 IP。
A C bind
签名:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
addr
可能包含具体地址。
正如评论中所建议的,我们必须使用 SO_BINDTODEVICE
,并且无法逃避 FFI,因为它在内部使用。
这里的工作示例:
extern crate libc;
use libc as c;
use std::ffi::CString;
use std::net::{TcpStream, SocketAddr};
use std::io::{self, Write};
use std::os::unix::io::FromRawFd;
use std::mem;
#[cfg(any(target_os = "linux"))]
fn connect_dev(addr: SocketAddr, link: &str) -> io::Result<TcpStream> {
let (addr_raw, addr_len) = match addr {
SocketAddr::V4(ref a) =>
(a as *const _ as *const _, mem::size_of_val(a) as c::socklen_t),
SocketAddr::V6(ref a) =>
(a as *const _ as *const _, mem::size_of_val(a) as c::socklen_t),
};
unsafe {
let fd = check_os_error(c::socket(c::AF_INET, c::SOCK_STREAM, 0))?;
check_os_error(c::setsockopt(
fd,
c::SOL_SOCKET,
c::SO_BINDTODEVICE,
CString::new(link).expect("device name").as_ptr() as *const c::c_void,
mem::size_of::<CString>() as c::socklen_t,
))?;
check_os_error(c::connect(fd, addr_raw, addr_len))?;
Ok(TcpStream::from_raw_fd(fd))
}
}
#[cfg(any(target_os = "linux"))]
pub fn check_os_error(res: c::c_int) -> io::Result<c::c_int> {
if res == -1 {
Err(io::Error::from_raw_os_error(unsafe { *c::__errno_location() as i32 }))
} else {
Ok(res)
}
}