aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main.rs315
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(())
}