diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/error.rs | 27 | ||||
-rw-r--r-- | src/lib.rs | 4 | ||||
-rw-r--r-- | src/main.rs | 151 | ||||
-rw-r--r-- | src/util.rs | 33 |
4 files changed, 213 insertions, 2 deletions
diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..c35ff38 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,27 @@ +use std::{ffi, io, net}; + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("incomplete transmission")] + PartialSend, + + #[error("parse address: {0}")] + AddrParse(#[from] net::AddrParseError), + #[error("io: {0}")] + Io(#[from] io::Error), + #[error("nul: {0}")] + Nul(#[from] ffi::NulError), + + #[error("dhcproto decode: {0}")] + DhcprotoDecode(#[from] dhcproto::error::DecodeError), + #[error("dhcproto encode: {0}")] + DhcprotoEncode(#[from] dhcproto::error::EncodeError), + #[error("rsdsl_netlinkd: {0}")] + RsdslNetlinkd(#[from] rsdsl_netlinkd::error::Error), + #[error("serde_json: {0}")] + SerdeJson(#[from] serde_json::Error), +} + +pub type Result<T> = std::result::Result<T, Error>; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e708ecb --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,4 @@ +mod error; +pub use error::*; + +pub mod util; diff --git a/src/main.rs b/src/main.rs index e7a11a9..15db460 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,150 @@ -fn main() { - println!("Hello, world!"); +use std::ffi::CString; +use std::fs::File; +use std::mem::MaybeUninit; +use std::net::{SocketAddr, SocketAddrV6}; +use std::os::fd::AsRawFd; +use std::str::FromStr; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; + +use dhcproto::v6::{DhcpOption, Message, MessageType, IAPD}; +use dhcproto::{Decodable, Decoder, Encodable, Encoder}; +use rsdsl_dhcp6::util::setsockopt; +use rsdsl_dhcp6::{Error, Result}; +use rsdsl_ip_config::DsConfig; +use rsdsl_netlinkd::link; +use socket2::{Domain, SockAddr, Socket, Type}; + +const BUFSIZE: usize = 1500; + +#[derive(Clone, Debug, Eq, PartialEq)] +enum State { + Solicit(Vec<u8>), + Request, + Active, + Renew, +} + +impl Default for State { + fn default() -> Self { + Self::Solicit(rand::random::<u128>().to_be_bytes().to_vec()) + } +} + +fn main() -> Result<()> { + println!("wait for up ppp0"); + link::wait_up("ppp0".into())?; + + let mut file = File::open("/data/pppoe.ip_config")?; + let ds_config: DsConfig = serde_json::from_reader(&mut file)?; + + if ds_config.v6.is_none() { + println!("ignore incapable ppp0"); + } + + println!("init ppp0"); + + let state = Arc::new(Mutex::new(State::default())); + + let sock = Socket::new(Domain::IPV6, Type::DGRAM, None)?; + + sock.set_only_v6(true)?; + sock.set_reuse_port(true)?; + sock.set_reuse_address(true)?; + + // Bind socket to interface. + unsafe { + let link_index = CString::new("ppp0")?.into_raw(); + + setsockopt( + sock.as_raw_fd(), + libc::SOL_SOCKET, + libc::SO_BINDTODEVICE, + link_index, + "ppp0".len() as i32, + )?; + + // Prevent memory leak. + let _ = CString::from_raw(link_index); + } + + let address = SocketAddr::from_str("[::]:546")?; + sock.bind(&address.into())?; + + let sock2 = sock.try_clone()?; + thread::spawn(move || loop { + match tick(&sock2, state.clone()) { + Ok(_) => thread::sleep(Duration::from_secs(3)), + Err(e) => println!("can't tick on ppp0: {}", e), + } + }); + + loop { + let mut buf = [MaybeUninit::new(0); BUFSIZE]; + let (n, remote) = sock.recv_from(&mut buf)?; + let buf = &buf + .iter() + .take(n) + .map(|p| unsafe { p.assume_init() }) + .collect::<Vec<u8>>(); + + let remote = remote.as_socket_ipv6().unwrap(); + + match handle_response(&sock, buf) { + Ok(_) => {} + Err(e) => println!("can't handle pkt from {} on ppp0: {}", remote, e), + } + } +} + +fn handle_response(sock: &Socket, buf: &[u8]) -> Result<()> { + // let dst: SocketAddrV6 = "[ff02::1:2]:547".parse()?; + + // let msg = Message::decode(&mut Decoder::new(buf))?; + + // let typ = msg.msg_type(); + // match typ { + // MessageType::Advertise => {} + // } + + Ok(()) +} + +fn tick(sock: &Socket, state: Arc<Mutex<State>>) -> Result<()> { + let dst: SocketAddrV6 = "[ff02::1:2]:547".parse()?; + + let state = state.lock().expect("state mutex is poisoned"); + match *state { + State::Solicit(ref client_id) => { + let mut req = Message::new(MessageType::Solicit); + let opts = req.opts_mut(); + + opts.insert(DhcpOption::ClientId(client_id.clone())); + opts.insert(DhcpOption::IAPD(IAPD { + id: 1, + t1: 0, + t2: 0, + opts: Default::default(), + })); + + let mut req_buf = Vec::new(); + req.encode(&mut Encoder::new(&mut req_buf))?; + + sock.send_to(&req_buf, &dst.into())?; + + println!("solicit pd 1 aftr"); + Ok(()) + } + _ => todo!(), + } +} + +fn send_to_exact(sock: &Socket, buf: &[u8], dst: &SockAddr) -> Result<()> { + let n = sock.send_to(buf, dst)?; + if n != buf.len() { + Err(Error::PartialSend) + } else { + Ok(()) + } } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..245f755 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,33 @@ +use std::ffi::{c_char, c_int}; +use std::io; + +/// Helper macro to execute a system call that returns an `io::Result`. +macro_rules! syscall { + ($fn: ident ( $($arg: expr),* $(,)* ) ) => {{ + #[allow(unused_unsafe)] + let res = unsafe { libc::$fn($($arg, )*) }; + if res == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(res) + } + }}; +} + +#[allow(clippy::missing_safety_doc)] +pub unsafe fn setsockopt( + fd: c_int, + opt: c_int, + val: c_int, + payload: *const c_char, + optlen: c_int, +) -> io::Result<()> { + syscall!(setsockopt( + fd, + opt, + val, + payload.cast(), + optlen as libc::socklen_t + )) + .map(|_| ()) +} |