Как я могу отправить через определенный сетевой интерфейс?

Мне нужно динамически отправлять сообщения через разные шлюзы. Как это сделать и каким должен быть мой первый шаг в этом направлении?

На моем сервере у меня есть два соединения: одно прямое и вторичное через VPN. Маршрут по умолчанию — прямое соединение, но мне нужно динамически менять соединение с VPN.

В настоящее время я пытаюсь построить сокет из libc::bind(), он работает, но не ожидал эффекта.

Изменение исходящего IP-адреса не является решением для определения интерфейса.


person Gedweb    schedule 10.09.2018    source источник
comment
См. stackoverflow.com/questions/50408040/ .   -  person starblue    schedule 10.09.2018
comment
@starblue, я видел. Проблема не была принята как решенная и является несколько другим вопросом.   -  person Gedweb    schedule 10.09.2018
comment
У ржавчины нет API на таком низком уровне сети, смотрите документацию по вашей ОС.   -  person Stargateur    schedule 10.09.2018


Ответы (2)


Как было предложено в комментарии, мы должны использовать 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)
    }
}
person Gedweb    schedule 12.09.2018

Вы можете использовать разные исходные IP-адреса. Я предполагаю, что ваша система настроена на маршрутизацию разных исходных IP-адресов на разные шлюзы (если нет, это проблема оператора, а не программиста). Вы можете указать другой IP-адрес источника в функции bind для сокета. Обычно вы передаете их значение «по умолчанию» (0.0.0.0), что означает «все, что ОС считает разумным», но вы можете указать точный IP-адрес источника для своей задачи.

Подпись C bind:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

addr может содержать конкретный адрес.

person George Shuklin    schedule 10.09.2018
comment
Поскольку этот ответ совпадает с ответом на Как отправить пакет UDP с определенного интерфейса в Linux?, это безопасно. предположить, что вы согласны, что вопросы дублируются? - person Shepmaster; 11.09.2018
comment
Может быть. Честно говоря, эта тема требует подробного объяснения на уровне оператора, включая gai.conf, таблицы маршрутизации, маршрутизацию на основе политик и т. д. Множественная адресация — сложная тема, особенно в мире ipv4. - person George Shuklin; 11.09.2018