Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion libs/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ pub fn rebind(t: Duration) -> Duration {
}

pub fn generate_random_bytes(len: usize) -> Vec<u8> {
let mut ident = Vec::with_capacity(len);
let mut ident = vec![0;len];
rand::thread_rng().fill_bytes(&mut ident);
ident
}
Expand Down
104 changes: 104 additions & 0 deletions plugins/leases/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ use dora_core::{
anyhow::anyhow,
chrono::{DateTime, SecondsFormat, Utc},
dhcproto::v4::{DhcpOption, Message, MessageType, OptionCode},
dhcproto::v6,
dhcproto::v6::OptionCode as v6OptionCode,
metrics,
prelude::*,
tracing::warn,
Expand All @@ -39,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<S>
where
Expand Down Expand Up @@ -163,6 +166,107 @@ where
}
}

#[async_trait]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move this impl to a v6 module in leases ?

impl<S> Plugin<v6::Message> for Leases<S>
where
S: Storage + Send + Sync + 'static,
{
#[instrument(level = "debug", skip_all)]
async fn handle(&self, ctx: &mut MsgContext<v6::Message>) -> Result<Action> {
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
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<S> Leases<S>
where
S: Storage,
{
async fn solicit(
&self,
ctx: &mut MsgContext<v6::Message>,
server_id: &[u8],
client_id: &[u8],
network: &Network,
rapid_commit: bool,
) -> Result<Action> {
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(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.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)
}
}

impl<S> Leases<S>
where
S: Storage,
Expand Down
187 changes: 182 additions & 5 deletions plugins/message-type/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<MsgContext<dhcproto::v6::Message>> {
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<dhcproto::v6::Message> = 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,
Expand Down Expand Up @@ -429,10 +452,13 @@ impl Plugin<v6::Message> 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
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");
Expand All @@ -443,12 +469,70 @@ impl Plugin<v6::Message> 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);
}
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
// 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);
}
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);
Expand All @@ -460,6 +544,7 @@ impl Plugin<v6::Message> for MsgType {
"couldn't match any options with INFORMATION-REQUEST message"
);
}
//RelayForw => {}
_ => {
debug!("currently unsupported message type");
return Ok(Action::NoResponse);
Expand All @@ -477,14 +562,19 @@ pub struct MatchedClasses(pub Vec<String>);

#[cfg(test)]
mod tests {
use config::{generate_random_bytes, 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::{duid::Duid, 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]
Expand Down Expand Up @@ -629,4 +719,91 @@ mod tests {
assert_eq!(res, Action::NoResponse);
Ok(())
}

/// for testing
fn find_interface_with_unicast_link_local(cfg: &DhcpConfig) -> Option<u32> {
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,
})
})
}

// create a uuid type duid
fn generate_duid() -> Result<Duid, ()> {
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]
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 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?;
println!("{:?}", res);
//let resp = ctx.resp_msg().unwrap();
//println!("{:?}", resp);
//assert_eq!(res, Action::Continue);
Ok(())
}
}