From 48897db9d10617fddc1b8bea79eaf69e5a9d632c Mon Sep 17 00:00:00 2001 From: chenwanqq Date: Tue, 5 Dec 2023 11:26:26 +0800 Subject: [PATCH 1/5] update v6 message validation --- plugins/message-type/src/lib.rs | 160 +++++++++++++++++++++++++++++++- 1 file changed, 155 insertions(+), 5 deletions(-) diff --git a/plugins/message-type/src/lib.rs b/plugins/message-type/src/lib.rs index bc9d960..ee138b2 100644 --- a/plugins/message-type/src/lib.rs +++ b/plugins/message-type/src/lib.rs @@ -338,6 +338,29 @@ pub mod util { use dora_core::server::msg::SerialMsg; use unix_udp_sock::RecvMeta; + pub fn blank_ctx_v6( + recv_addr: SocketAddr, + ifindex: u32, + msg_type: v6::MessageType, + ) -> Result> { + let msg = dhcproto::v6::Message::new(msg_type); + let buf = msg.to_vec().unwrap(); + let meta = RecvMeta { + addr: recv_addr, + len: buf.len(), + ifindex, + // recv addr copied here + dst_ip: Some(recv_addr.ip()), + ..RecvMeta::default() + }; + let ctx: MsgContext = MsgContext::new( + SerialMsg::new(buf.into(), recv_addr), + meta, + Arc::new(State::new(10)), + )?; + Ok(ctx) + } + /// for testing pub fn blank_ctx( recv_addr: SocketAddr, @@ -433,6 +456,7 @@ impl Plugin for MsgType { // TODO RelayForw type // TODO: make sure we handle client ids as specified - https://www.rfc-editor.org/rfc/rfc8415#section-16.1 let req_sid = req.opts().get(v6::OptionCode::ServerId); + let req_cid = req.opts().get(v6::OptionCode::ClientId); // if the request includes a server id, it must match our server id if matches!(req_sid, Some(v6::DhcpOption::ServerId(id)) if *id != server_id) { debug!(?server_id, "server identifier in msg doesn't match"); @@ -443,12 +467,58 @@ impl Plugin for MsgType { .insert(v6::DhcpOption::ServerId(server_id.to_vec())); match msg_type { - // discard if it has these types but NO server id - // https://www.rfc-editor.org/rfc/rfc8415#section-16.6 - Request | Renew | Decline | Release if req_sid.is_none() => { - return Ok(Action::NoResponse); + Solicit => { + //https://datatracker.ietf.org/doc/html/rfc8415#section-16.2 + if req_sid.is_some() || req_cid.is_none() { + return Ok(Action::NoResponse); + } + } + Request => { + // https://datatracker.ietf.org/doc/html/rfc8415#section-16.4 + if req_sid.is_none() || req_cid.is_none() { + return Ok(Action::NoResponse); + } + } + Confirm => { + // https://datatracker.ietf.org/doc/html/rfc8415#section-16.5 + if req_sid.is_some() || req_cid.is_none() { + return Ok(Action::NoResponse); + } + } + Renew => { + // https://datatracker.ietf.org/doc/html/rfc8415#section-16.6 + if req_sid.is_none() || req_cid.is_none() { + return Ok(Action::NoResponse); + } + } + Rebind => { + // https://datatracker.ietf.org/doc/html/rfc8415#section-16.7 + if req_sid.is_some() || req_cid.is_none() { + return Ok(Action::NoResponse); + } + } + Decline => { + // https://datatracker.ietf.org/doc/html/rfc8415#section-16.8 + if req_sid.is_none() || req_cid.is_none() { + return Ok(Action::NoResponse); + } + } + Release => { + // https://datatracker.ietf.org/doc/html/rfc8415#section-16.9 + if req_sid.is_none() || req_cid.is_none() { + return Ok(Action::NoResponse); + } } InformationRequest => { + // discard if req has IA option + // https://datatracker.ietf.org/doc/html/rfc8415#section-16.12 + if req.opts().get(v6::OptionCode::IANA).is_some() + || req.opts().get(v6::OptionCode::IATA).is_some() + || req.opts().get(v6::OptionCode::IAPD).is_some() + { + return Ok(Action::NoResponse); + } + if let Some(opts) = self.cfg.v6().get_opts(meta.ifindex) { ctx.set_resp_msg(resp); ctx.populate_opts(opts); @@ -460,6 +530,7 @@ impl Plugin for MsgType { "couldn't match any options with INFORMATION-REQUEST message" ); } + RelayForw => {} _ => { debug!("currently unsupported message type"); return Ok(Action::NoResponse); @@ -477,14 +548,19 @@ pub struct MatchedClasses(pub Vec); #[cfg(test)] mod tests { + use config::v6::is_unicast_link_local; use util::get_server_id_override; - use dora_core::dhcproto::v4::{self, relay}; + use dora_core::dhcproto::{ + v4::{self, relay}, + v6::ORO, + }; use tracing_test::traced_test; use super::*; static SAMPLE_YAML: &str = include_str!("../../../libs/config/sample/config.yaml"); + static V6_EXAMPLE_YAML: &str = include_str!("../../../libs/config/sample/config_v6.yaml"); #[tokio::test] #[traced_test] @@ -629,4 +705,78 @@ mod tests { assert_eq!(res, Action::NoResponse); Ok(()) } + + /// for testing + fn find_interface_with_unicast_link_local(cfg: &DhcpConfig) -> Option { + let interfaces = cfg.v6().interfaces(); + //find index of first interface that has a unicast address + interfaces.iter().find_map(|int| { + int.ips.iter().find_map(|ip| match ip { + IpNetwork::V6(ip) => { + if is_unicast_link_local(&ip.ip()) { + Some(int.index) + } else { + None + } + } + _ => None, + }) + }) + } + + ///test that we respond to an information request + #[tokio::test] + #[traced_test] + async fn test_information_request() -> Result<()> { + let cfg = DhcpConfig::parse_str(V6_EXAMPLE_YAML).unwrap(); + //find index of first interface that has a unicast address + let ifindex = find_interface_with_unicast_link_local(&cfg) + .context("no interface with unicast link local address")?; + let plugin = MsgType::new(Arc::new(cfg.clone()))?; + let mut ctx = util::blank_ctx_v6( + "[2001:db8::1]:546".parse()?, + ifindex as u32, + v6::MessageType::InformationRequest, + )?; + //according to https://datatracker.ietf.org/doc/html/rfc8415#section-18.2.6, Information-request Messages might not include Client Identifier option, so here we ignore it. + //add elapsed time option + ctx.msg_mut() + .opts_mut() + .insert(v6::DhcpOption::ElapsedTime(60)); + let oro = ORO { + opts: vec![ + v6::OptionCode::InfMaxRt, + v6::OptionCode::InformationRefreshTime, + v6::OptionCode::DomainNameServers, + ], + }; + //add option request + ctx.msg_mut().opts_mut().insert(v6::DhcpOption::ORO(oro)); + let res = plugin.handle(&mut ctx).await?; + let resp = ctx.resp_msg().unwrap(); + println!("{:?}", resp); + assert_eq!(res, Action::Respond); + Ok(()) + } + + #[tokio::test] + #[traced_test] + async fn test_solicit() -> Result<()> { + let cfg = DhcpConfig::parse_str(V6_EXAMPLE_YAML).unwrap(); + //find index of first interface that has a unicast address + let ifindex = find_interface_with_unicast_link_local(&cfg) + .context("no interface with unicast link local address")?; + let plugin = MsgType::new(Arc::new(cfg.clone()))?; + let mut ctx = util::blank_ctx_v6( + "[2001:db8::1]:546".parse()?, + ifindex as u32, + v6::MessageType::Solicit, + )?; + + let res = plugin.handle(&mut ctx).await?; + let resp = ctx.resp_msg().unwrap(); + println!("{:?}", resp); + assert_eq!(res, Action::Continue); + Ok(()) + } } From c6f56d40e523791798bb9fab0468097b71f8f316 Mon Sep 17 00:00:00 2001 From: chenwanqq Date: Sat, 9 Dec 2023 20:54:20 +0800 Subject: [PATCH 2/5] fix a bug of generate_random_bytes. continue woring on DHCPv6 --- libs/config/src/lib.rs | 2 +- plugins/leases/src/lib.rs | 21 ++++++++++++++++ plugins/message-type/src/lib.rs | 43 +++++++++++++++++++++++++++------ 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/libs/config/src/lib.rs b/libs/config/src/lib.rs index dde8b1b..63191dc 100644 --- a/libs/config/src/lib.rs +++ b/libs/config/src/lib.rs @@ -184,7 +184,7 @@ pub fn rebind(t: Duration) -> Duration { } pub fn generate_random_bytes(len: usize) -> Vec { - let mut ident = Vec::with_capacity(len); + let mut ident = vec![0;len]; rand::thread_rng().fill_bytes(&mut ident); ident } diff --git a/plugins/leases/src/lib.rs b/plugins/leases/src/lib.rs index 5b50038..fab9d81 100644 --- a/plugins/leases/src/lib.rs +++ b/plugins/leases/src/lib.rs @@ -23,6 +23,7 @@ use dora_core::{ anyhow::anyhow, chrono::{DateTime, SecondsFormat, Utc}, dhcproto::v4::{DhcpOption, Message, MessageType, OptionCode}, + dhcproto::v6::OptionCode as v6OptionCode, metrics, prelude::*, tracing::warn, @@ -163,6 +164,26 @@ where } } +#[async_trait] +impl Plugin for Leases +where + S: Storage + Send + Sync + 'static, +{ + #[instrument(level = "debug", skip_all)] + async fn handle(&self, ctx: &mut MsgContext) -> Result { + let req = ctx.msg(); + let meta = ctx.meta(); + let client_id = self + .cfg + .v6() + .get_opts(meta.ifindex) + .context("can not get dhcp options")? + .get(v6OptionCode::ClientId) + .context("no client id")?; + //TODO unfinish + } +} + impl Leases where S: Storage, diff --git a/plugins/message-type/src/lib.rs b/plugins/message-type/src/lib.rs index ee138b2..e3f91f3 100644 --- a/plugins/message-type/src/lib.rs +++ b/plugins/message-type/src/lib.rs @@ -452,6 +452,8 @@ impl Plugin for MsgType { // create initial response with reply type let mut resp = v6::Message::new_with_id(Reply, req.xid()); + let rapid_commit = ctx.msg().opts().get(v6::OptionCode::RapidCommit).is_some() + && self.cfg.v4().rapid_commit(); let server_id = self.cfg.v6().server_id(); // TODO RelayForw type // TODO: make sure we handle client ids as specified - https://www.rfc-editor.org/rfc/rfc8415#section-16.1 @@ -472,42 +474,54 @@ impl Plugin for MsgType { if req_sid.is_some() || req_cid.is_none() { return Ok(Action::NoResponse); } + if rapid_commit { + resp.set_msg_type(v6::MessageType::Reply); + } else { + resp.set_msg_type(v6::MessageType::Advertise); + } + //TODO: discard if req not fulfill administrative policy } Request => { // https://datatracker.ietf.org/doc/html/rfc8415#section-16.4 if req_sid.is_none() || req_cid.is_none() { return Ok(Action::NoResponse); } + resp.set_msg_type(v6::MessageType::Reply); } Confirm => { // https://datatracker.ietf.org/doc/html/rfc8415#section-16.5 if req_sid.is_some() || req_cid.is_none() { return Ok(Action::NoResponse); } + resp.set_msg_type(v6::MessageType::Reply); } Renew => { // https://datatracker.ietf.org/doc/html/rfc8415#section-16.6 if req_sid.is_none() || req_cid.is_none() { return Ok(Action::NoResponse); } + resp.set_msg_type(v6::MessageType::Reply); } Rebind => { // https://datatracker.ietf.org/doc/html/rfc8415#section-16.7 if req_sid.is_some() || req_cid.is_none() { return Ok(Action::NoResponse); } + resp.set_msg_type(v6::MessageType::Reply); } Decline => { // https://datatracker.ietf.org/doc/html/rfc8415#section-16.8 if req_sid.is_none() || req_cid.is_none() { return Ok(Action::NoResponse); } + resp.set_msg_type(v6::MessageType::Reply); } Release => { // https://datatracker.ietf.org/doc/html/rfc8415#section-16.9 if req_sid.is_none() || req_cid.is_none() { return Ok(Action::NoResponse); } + resp.set_msg_type(v6::MessageType::Reply); } InformationRequest => { // discard if req has IA option @@ -518,7 +532,7 @@ impl Plugin for MsgType { { return Ok(Action::NoResponse); } - + resp.set_msg_type(v6::MessageType::Reply); if let Some(opts) = self.cfg.v6().get_opts(meta.ifindex) { ctx.set_resp_msg(resp); ctx.populate_opts(opts); @@ -530,7 +544,7 @@ impl Plugin for MsgType { "couldn't match any options with INFORMATION-REQUEST message" ); } - RelayForw => {} + //RelayForw => {} _ => { debug!("currently unsupported message type"); return Ok(Action::NoResponse); @@ -548,12 +562,12 @@ pub struct MatchedClasses(pub Vec); #[cfg(test)] mod tests { - use config::v6::is_unicast_link_local; + use config::{generate_random_bytes, v6::is_unicast_link_local}; use util::get_server_id_override; use dora_core::dhcproto::{ v4::{self, relay}, - v6::ORO, + v6::{duid::Duid, ORO}, }; use tracing_test::traced_test; @@ -724,6 +738,13 @@ mod tests { }) } + // create a uuid type duid + fn generate_duid() -> Result { + let bytes = generate_random_bytes(16); + println!("!!!{:?}",bytes); + let duid = Duid::uuid(&bytes); + Ok(duid) + } ///test that we respond to an information request #[tokio::test] #[traced_test] @@ -767,16 +788,22 @@ mod tests { let ifindex = find_interface_with_unicast_link_local(&cfg) .context("no interface with unicast link local address")?; let plugin = MsgType::new(Arc::new(cfg.clone()))?; + let mut ctx = util::blank_ctx_v6( "[2001:db8::1]:546".parse()?, ifindex as u32, v6::MessageType::Solicit, )?; - + let client_id = generate_duid().unwrap().as_ref().to_vec(); + ctx.msg_mut() + .opts_mut() + .insert(v6::DhcpOption::ClientId(client_id)); + //ctx.msg_mut().opts_mut().insert(v6::DhcpOption::ClientId()); let res = plugin.handle(&mut ctx).await?; - let resp = ctx.resp_msg().unwrap(); - println!("{:?}", resp); - assert_eq!(res, Action::Continue); + println!("{:?}", res); + //let resp = ctx.resp_msg().unwrap(); + //println!("{:?}", resp); + //assert_eq!(res, Action::Continue); Ok(()) } } From d08cbf838ef29038086c0327cf7a3305f59b091c Mon Sep 17 00:00:00 2001 From: chenwanqq Date: Tue, 12 Dec 2023 09:45:20 +0800 Subject: [PATCH 3/5] small update --- example.yaml | 4 ++- plugins/leases/src/lib.rs | 64 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/example.yaml b/example.yaml index 0702ba5..471eba7 100644 --- a/example.yaml +++ b/example.yaml @@ -244,7 +244,9 @@ v6: value: - 2001:db8::1 - 2001:db8::2 - +#TODO: +# 1. ipv6 static addr; +# 2. server preference(https://datatracker.ietf.org/doc/html/rfc8415#section-21.8) # Example Client Classifier # # We define in the dora config a client_classes section, where each class has a predicate whose syntax diff --git a/plugins/leases/src/lib.rs b/plugins/leases/src/lib.rs index fab9d81..0b7204d 100644 --- a/plugins/leases/src/lib.rs +++ b/plugins/leases/src/lib.rs @@ -23,6 +23,7 @@ use dora_core::{ anyhow::anyhow, chrono::{DateTime, SecondsFormat, Utc}, dhcproto::v4::{DhcpOption, Message, MessageType, OptionCode}, + dhcproto::v6, dhcproto::v6::OptionCode as v6OptionCode, metrics, prelude::*, @@ -40,6 +41,7 @@ use ip_manager::{IpError, IpManager, IpState, Storage}; #[derive(Register)] #[register(msg(Message))] +#[register(msg(v6::Message))] #[register(plugin(StaticAddr))] pub struct Leases where @@ -181,6 +183,68 @@ where .get(v6OptionCode::ClientId) .context("no client id")?; //TODO unfinish + let rapid_commit = ctx.msg().opts().get(v6::OptionCode::RapidCommit).is_some() + && self.cfg.v4().rapid_commit(); + let network = self + .cfg + .v6() + .get_network(meta.ifindex) + .context("no network")?; + match req.msg_type() { + v6::MessageType::Solicit => { + //self.solicit() + todo!() + } + v6::MessageType::Request => todo!(), + v6::MessageType::Confirm => todo!(), + v6::MessageType::Renew => todo!(), + v6::MessageType::Rebind => todo!(), + v6::MessageType::Reply => todo!(), + v6::MessageType::Release => todo!(), + v6::MessageType::Decline => todo!(), + v6::MessageType::InformationRequest => todo!(), + v6::MessageType::RelayForw => todo!(), + _ => { + debug!("unsupported message type"); + return Ok(Action::NoResponse); + } + } + } +} + +//v6 related +impl Leases +where + S: Storage, +{ + async fn solicit( + &self, + ctx: &mut MsgContext, + server_id: &[u8], + client_id: &[u8], + network: &Network, + rapid_commit: bool, + ) -> Result { + if rapid_commit { + todo!() + } else { + ctx.resp_msg_mut() + .unwrap() + .opts_mut() + .insert(v6::DhcpOption::ServerId(server_id.to_vec())); + ctx.resp_msg_mut() + .unwrap() + .opts_mut() + .insert(v6::DhcpOption::ClientId(client_id.to_vec())); + //TODO: Preference option + //TODO: Reconfigure Accept option + // fill informations that requested in ORO + if let Some(opts) = self.cfg.v6().get_opts(meta.ifindex) { + ctx.populate_opts(opts); + } + //TODO: IA related and leases + + } } } From 0ed3e9d15eb878769bc6a19dfec152a40bb3a2d8 Mon Sep 17 00:00:00 2001 From: chenwanqq Date: Tue, 12 Dec 2023 10:39:11 +0800 Subject: [PATCH 4/5] WIP --- plugins/leases/src/lib.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/plugins/leases/src/lib.rs b/plugins/leases/src/lib.rs index 0b7204d..14aff40 100644 --- a/plugins/leases/src/lib.rs +++ b/plugins/leases/src/lib.rs @@ -243,7 +243,21 @@ where ctx.populate_opts(opts); } //TODO: IA related and leases - + // IANA + /* */ + if let Some(ia_na) = ctx.msg().opts().get(v6::OptionCode::IANA) { + let iana = match ia_na { + v6::DhcpOption::IANA(iana) => iana, + _ => unreachable!(), + }; + let iaid = iana.iaid(); + let t1 = iana.t1(); + let t2 = iana.t2(); + let iana = v6::IANA::new(iaid, t1, t2); + //TODO: generate v6 lease and addr + let iana = v6::DhcpOption::IANA(iana); + ctx.resp_msg_mut().unwrap().opts_mut().insert(iana); + } } } } From c961c44e14929ece41bfa6dbfb4db0c523774846 Mon Sep 17 00:00:00 2001 From: chenwanqq Date: Tue, 12 Dec 2023 10:56:08 +0800 Subject: [PATCH 5/5] wip --- plugins/leases/src/lib.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/plugins/leases/src/lib.rs b/plugins/leases/src/lib.rs index 14aff40..988fe08 100644 --- a/plugins/leases/src/lib.rs +++ b/plugins/leases/src/lib.rs @@ -239,26 +239,31 @@ where //TODO: Preference option //TODO: Reconfigure Accept option // fill informations that requested in ORO - if let Some(opts) = self.cfg.v6().get_opts(meta.ifindex) { + if let Some(opts) = self.cfg.v6().get_opts(ctx.meta().ifindex) { ctx.populate_opts(opts); } //TODO: IA related and leases // IANA - /* */ if let Some(ia_na) = ctx.msg().opts().get(v6::OptionCode::IANA) { let iana = match ia_na { v6::DhcpOption::IANA(iana) => iana, _ => unreachable!(), }; - let iaid = iana.iaid(); - let t1 = iana.t1(); - let t2 = iana.t2(); - let iana = v6::IANA::new(iaid, t1, t2); + let iaid = iana.id; + let t1 = iana.t1; + let t2 = iana.t2; //TODO: generate v6 lease and addr + let iana = v6::IANA { + id: iaid, + t1: t1, + t2: t2, + opts: todo!(), + }; let iana = v6::DhcpOption::IANA(iana); ctx.resp_msg_mut().unwrap().opts_mut().insert(iana); } } + Ok(Action::Continue) } }