diff options
-rw-r--r-- | src/main.rs | 315 |
1 files changed, 89 insertions, 226 deletions
diff --git a/src/main.rs b/src/main.rs index 690e4da..324a55f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,6 @@ use socket2::{Domain, Socket, Type}; use trust_dns_proto::serialize::binary::BinDecodable; const DUID_LOCATION: &str = "/data/dhcp6.duid"; -const TICK_INTERVAL: u64 = 5; const ALL_DHCPV6_SERVERS: SocketAddrV6 = SocketAddrV6::new(Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 1, 2), 547, 0, 0); @@ -25,6 +24,11 @@ const ALL_DHCPV6_SERVERS: SocketAddrV6 = struct Dhcp6 { duid: Duid, lease: Option<PdConfig>, + + xid: [u8; 3], + server_id: Vec<u8>, + last_packet: Packet, + iapd: IAPD, } impl Dhcp6 { @@ -32,6 +36,16 @@ impl Dhcp6 { Ok(Self { duid: load_or_generate_duid()?, lease: load_lease_optional(), + + xid: [0; 3], + server_id: Vec::default(), + last_packet: Packet::Reply, // Can never occur naturally, forces XID generation. + iapd: IAPD { + id: 1, + t1: 0, + t2: 0, + opts: Default::default(), + }, }) } } @@ -73,41 +87,51 @@ async fn main() -> Result<()> { sock.bind_device(Some("ppp0".as_bytes()))?; - // If a valid lease is present on disk, inform netlinkd and dslite immediately. - if dhcp6.lease.is_some() { - inform(); - } - - let mut interval = time::interval(Duration::from_secs(TICK_INTERVAL)); - let mut buf = [0; 1500]; loop { tokio::select! { - result = sock.recv_from(&mut buf) => { - let (n, raddr) = result?; - let buf = &buf[..n]; + biased; + + packet = dhcp6c.to_send() => send_dhcp6(&mut dhcp6, &sock, packet).await?, + + result = dhcp6c_rx.changed() => { + result?; - logged_handle(&mut dhcp6, buf, raddr); - logged_tick(&mut dhcp6, &sock).await; + let is_opened = *dhcp6c_rx.borrow_and_update(); + if is_opened { + todo!("write lease + inform") + } else { + todo!("del lease + inform") + } } - _ = interval.tick() => { - logged_tick(&mut dhcp6, &sock).await; + + Ok(result) = sock.recv_from(&mut buf) => { + let (n, raddr) = result; + let buf = &buf[..n]; + + logged_handle(&mut dhcp6c, buf); } } } } -async fn logged_tick(dhcp6: &mut Dhcp6, sock: &UdpSocket) { - match tick(dhcp6, sock).await { +fn logged_handle(dhcp6c: &mut Dhcp6c, buf: &[u8]) { + match handle(dhcp6c, buf) { Ok(_) => {} - Err(e) => println!("[warn] tick: {}", e), + Err(e) => println!("[warn] {}", e), } } -async fn tick(dhcp6: &mut Dhcp6, sock: &UdpSocket) -> Result<()> { - match &mut dhcp6.lease { - None => { - let mut solicit = Message::new(MessageType::Solicit); +fn handle(dhcp6c: &mut Dhcp6c, buf: &[u8]) -> Result<()> {} + +async fn send_dhcp6(dhcp6: &mut Dhcp6, sock: &UdpSocket, packet: Packet) -> Result<()> { + if packet != dhcp6.last_packet { + dhcp6.xid = rand::random(); + } + + match packet { + Packet::Solicit => { + let mut solicit = Message::new_with_id(MessageType::Solicit, dhcp6.xid); let opts = solicit.opts_mut(); opts.insert(DhcpOption::ClientId(dhcp6.duid.as_ref().to_vec())); @@ -128,225 +152,64 @@ async fn tick(dhcp6: &mut Dhcp6, sock: &UdpSocket) -> Result<()> { send_to_exact(sock, &buf, ALL_DHCPV6_SERVERS).await?; println!("[info] -> solicit"); - Ok(()) - } - Some(lease) => { - if expired(lease) { - dhcp6.lease = None; - - // Inexistent lease causes deconfiguration. - fs::remove_file(rsdsl_pd_config::LOCATION)?; - inform(); - - println!("[info] lease expired"); - } else if needs_rebind(lease) { - let mut rebind = Message::new(MessageType::Rebind); - let opts = rebind.opts_mut(); - - opts.insert(DhcpOption::ClientId(dhcp6.duid.as_ref().to_vec())); - opts.insert(DhcpOption::IAPD(IAPD { - id: 1, - t1: 0, - t2: 0, - opts: vec![DhcpOption::IAPrefix(IAPrefix { - prefix_ip: lease.prefix, - prefix_len: lease.len, - preferred_lifetime: lease.preflft, - valid_lifetime: lease.validlft, - opts: Default::default(), - })] - .into_iter() - .collect(), - })); - opts.insert(DhcpOption::ORO(ORO { - opts: vec![OptionCode::AftrName, OptionCode::DomainNameServers], - })); - - let mut buf = Vec::new(); - rebind.encode(&mut Encoder::new(&mut buf))?; - - send_to_exact(sock, &buf, ALL_DHCPV6_SERVERS).await?; - - println!("[info] -> rebind"); - } else if needs_renewal(lease) { - let mut renew = Message::new(MessageType::Renew); - let opts = renew.opts_mut(); - - opts.insert(DhcpOption::ClientId(dhcp6.duid.as_ref().to_vec())); - opts.insert(DhcpOption::ServerId(lease.server_id.clone())); - opts.insert(DhcpOption::IAPD(IAPD { - id: 1, - t1: 0, - t2: 0, - opts: vec![DhcpOption::IAPrefix(IAPrefix { - prefix_ip: lease.prefix, - prefix_len: lease.len, - preferred_lifetime: lease.preflft, - valid_lifetime: lease.validlft, - opts: Default::default(), - })] - .into_iter() - .collect(), - })); - opts.insert(DhcpOption::ORO(ORO { - opts: vec![OptionCode::AftrName, OptionCode::DomainNameServers], - })); - - let mut buf = Vec::new(); - renew.encode(&mut Encoder::new(&mut buf))?; - - send_to_exact(sock, &buf, lease.server).await?; - - println!("[info] -> renew"); - } - - Ok(()) } - } -} + Packet::Request => { + let mut request = Message::new_with_id(MessageType::Request, dhcp6.xid); + let opts = request.opts_mut(); -fn logged_handle(dhcp6: &mut Dhcp6, buf: &[u8], raddr: SocketAddr) { - match handle(dhcp6, buf, raddr) { - Ok(_) => {} - Err(e) => println!("[warn] handle from {}: {}", raddr, e), - } -} - -fn handle(dhcp6: &mut Dhcp6, buf: &[u8], raddr: SocketAddr) -> Result<()> { - let msg = Message::decode(&mut Decoder::new(buf))?; - let opts = msg.opts(); + opts.insert(DhcpOption::ClientId(dhcp6.duid.as_ref().to_vec())); + opts.insert(DhcpOption::ServerId(dhcp6.server_id.clone())); + opts.insert(DhcpOption::IAPD(dhcp6.iapd.clone())); + opts.insert(DhcpOption::ORO(ORO { + opts: vec![OptionCode::AftrName, OptionCode::DomainNameServers], + })); - let client_id = match opts.get(OptionCode::ClientId).ok_or(Error::NoClientId)? { - DhcpOption::ClientId(client_id) => client_id, - _ => unreachable!(), - }; + let mut buf = Vec::new(); + request.encode(&mut Encoder::new(&mut buf))?; - if client_id != dhcp6.duid.as_ref() { - println!("[warn] <- [{}] client id mismatch", raddr); - return Ok(()); - } + send_to_exact(sock, &buf, ALL_DHCPV6_SERVERS).await?; - match msg.msg_type() { - MessageType::Reply => { - let server_id = match opts.get(OptionCode::ServerId).ok_or(Error::NoServerId)? { - DhcpOption::ServerId(server_id) => server_id, - _ => unreachable!(), - }; - - // Regular renewal, make sure the server ID matches. - if dhcp6 - .lease - .as_ref() - .is_some_and(|lease| !needs_rebind(lease) && *server_id != lease.server_id) - { - println!("[warn] <- [{}] server id mismatch", raddr); - return Ok(()); - } + println!("[info] -> request"); + } + Packet::Renew => { + let mut renew = Message::new_with_id(MessageType::Renew, dhcp6.xid); + let opts = renew.opts_mut(); - let aftr = opts.get(OptionCode::AftrName).map(|v| match v { - DhcpOption::Unknown(unk) => { - Name::from_bytes(unk.data()).expect("invalid aftr name format") - } - _ => unreachable!(), - }); - - let ia_pd = match opts.get(OptionCode::IAPD).ok_or(Error::NoIAPD)? { - DhcpOption::IAPD(ia_pd) => ia_pd, - _ => unreachable!(), - }; - - let ia_prefix = match ia_pd - .opts - .get(OptionCode::IAPrefix) - .ok_or(Error::NoIAPrefix)? - { - DhcpOption::IAPrefix(ia_prefix) => ia_prefix, - _ => unreachable!(), - }; - - let dnss = match opts - .get(OptionCode::DomainNameServers) - .ok_or(Error::NoDns)? - { - DhcpOption::DomainNameServers(dnss) => dnss, - _ => unreachable!(), - }; - - if dnss.len() < 2 { - return Err(Error::TooFewDns(dnss.len())); - } + opts.insert(DhcpOption::ClientId(dhcp6.duid.as_ref().to_vec())); + opts.insert(DhcpOption::ServerId(dhcp6.server_id.clone())); + opts.insert(DhcpOption::IAPD(dhcp6.iapd.clone())); + opts.insert(DhcpOption::ORO(ORO { + opts: vec![OptionCode::AftrName, OptionCode::DomainNameServers], + })); - let aftr = aftr.map(|v| v.to_utf8()); - - let new_lease = PdConfig { - timestamp: SystemTime::now(), - server: raddr, - server_id: server_id.to_vec(), - t1: ia_pd.t1, - t2: ia_pd.t2, - prefix: ia_prefix.prefix_ip, - len: ia_prefix.prefix_len, - preflft: ia_prefix.preferred_lifetime, - validlft: ia_prefix.valid_lifetime, - dns1: dnss[0], - dns2: dnss[1], - aftr: aftr.clone(), - }; - - let should_inform = dhcp6.lease.is_none(); - - // Are we renewing an existing lease? - match &mut dhcp6.lease { - Some(lease) => { - *lease = new_lease; - println!("[info] <- renewal/rebind successful"); - } - None => { - dhcp6.lease = Some(new_lease); - println!( - "[info] <- [{}] obtain from {}: {}/{}, valid {}, pref {}, dns1 {}, dns2 {}, aftr {}", - raddr, - hexdump(server_id)?, - ia_prefix.prefix_ip, - ia_prefix.prefix_len, - ia_prefix.valid_lifetime, - ia_prefix.preferred_lifetime, - dnss[0], - dnss[1], - aftr.unwrap_or("unset".into()) - ); - } - } + let mut buf = Vec::new(); + renew.encode(&mut Encoder::new(&mut buf))?; - let mut file = File::create(rsdsl_pd_config::LOCATION)?; - serde_json::to_writer_pretty(&mut file, &dhcp6.lease)?; + send_to_exact(sock, &buf, ALL_DHCPV6_SERVERS).await?; - // If this is a new lease, inform netlinkd and dslite. - if should_inform { - inform(); - } + println!("[info] -> renew"); } - MessageType::Decline => { - // Declined solicitations don't have an impact, logging them is enough. - // If a renewal or rebind is declined, start over by soliciting. - // Inform netlinkd and dslite of the validity loss. + Packet::Rebind => { + let mut rebind = Message::new_with_id(MessageType::Rebind, dhcp6.xid); + let opts = rebind.opts_mut(); + + opts.insert(DhcpOption::ClientId(dhcp6.duid.as_ref().to_vec())); + opts.insert(DhcpOption::IAPD(dhcp6.iapd.clone())); + opts.insert(DhcpOption::ORO(ORO { + opts: vec![OptionCode::AftrName, OptionCode::DomainNameServers], + })); - if dhcp6.lease.is_some() { - dhcp6.lease = None; + let mut buf = Vec::new(); + rebind.encode(&mut Encoder::new(&mut buf))?; - // Inexistent lease causes deconfiguration. - fs::remove_file(rsdsl_pd_config::LOCATION)?; - inform(); - } + send_to_exact(sock, &buf, ALL_DHCPV6_SERVERS).await?; - println!("[info] <- [{}] decline", raddr); + println!("[info] -> rebind"); } - _ => println!( - "[warn] <- [{}] unhandled message type {:?}", - raddr, - msg.msg_type() - ), + _ => println!("[warn] -> can't send unsupported packet type"), } + dhcp6.last_packet = packet; + Ok(()) } |