aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/error.rs27
-rw-r--r--src/lib.rs4
-rw-r--r--src/main.rs151
-rw-r--r--src/util.rs33
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(|_| ())
+}