Skip to content

Commit

Permalink
some bug fixes
Browse files Browse the repository at this point in the history
- check interface is on subnet before inserting netmask/router into
  response
- dont always give back broadcast option (28)
- if config was changed but db contains an old lease, we delete
the expired lease before inserting a new one
- dont set broadcast flag on relayed ACK responses
  • Loading branch information
leshow committed Oct 11, 2023
1 parent 9037bd6 commit 2c7d201
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 32 deletions.
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ RUN apt-get -qq update; \
apt-get -qq --no-install-recommends install \
dumb-init \
isc-dhcp-server \
iputils-ping \
iproute2 \
ca-certificates \
wget \
sudo;
Expand Down
33 changes: 25 additions & 8 deletions dora-core/src/server/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,7 @@ impl MsgContext<v4::Message> {
/// these will be overwritten.
pub fn populate_opts(&mut self, param_opts: &v4::DhcpOptions) -> Option<()> {
use dhcproto::v4::{DhcpOption, OptionCode};
let subnet = self.subnet();
// https://datatracker.ietf.org/doc/html/rfc3046#section-2.2
// copy opt 82 (relay agent) into response
let resp = self.resp_msg.as_mut()?;
Expand All @@ -614,21 +615,37 @@ impl MsgContext<v4::Message> {
if let Some(id) = self.msg.opts().get(OptionCode::ClientIdentifier) {
resp.opts_mut().insert(id.clone());
}

// insert router & net mask & broadcast from interface
let mut interface_match = false;
// insert router/netmask
// if the config provides these also, they will be overwritten
if let Some(IpNetwork::V4(interface)) = self.interface {
resp.opts_mut()
.insert(DhcpOption::Router(vec![interface.ip()]));
resp.opts_mut()
.insert(DhcpOption::SubnetMask(interface.mask()));
resp.opts_mut()
.insert(DhcpOption::BroadcastAddr(interface.broadcast()));
// if we populate from interface, interface must be on same subnet as packet (local)
if matches!(subnet, Ok(subnet) if interface.contains(subnet)) {
resp.opts_mut()
.insert(DhcpOption::Router(vec![interface.ip()]));
resp.opts_mut()
.insert(DhcpOption::SubnetMask(interface.mask()));
interface_match = true;
}
// configured router/netmask will override interface
if let Some(v) = param_opts.get(OptionCode::Router) {
resp.opts_mut().insert(v.clone());
}
if let Some(v) = param_opts.get(OptionCode::SubnetMask) {
resp.opts_mut().insert(v.clone());
}
}

if let Some(DhcpOption::ParameterRequestList(requested)) =
self.msg.opts().get(OptionCode::ParameterRequestList)
{
// if broadcast addr is requested, try to fill from interface
if let Some(IpNetwork::V4(interface)) = self.interface {
if requested.contains(&v4::OptionCode::BroadcastAddr) && interface_match {
resp.opts_mut()
.insert(DhcpOption::BroadcastAddr(interface.broadcast()));
}
}
// look in the requested list of params
for code in requested {
// if we have that option, add it to the response
Expand Down
42 changes: 27 additions & 15 deletions libs/ip-manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,19 @@ where
expires_at: SystemTime,
state: Option<IpState>,
) -> Result<IpAddr, IpError<T::Error>> {
const MAX_ATTEMPTS: usize = 2;
let subnet = network.subnet().into();
// unfortunately the sqlite connection is sometimes unreliable under high contention, meaning
// we need to make a few attempts to get an address.
let mut attempts = 0;
loop {
let ip_range = range.start().into()..=range.end().into();
if attempts > MAX_ATTEMPTS {
return Err(IpError::MaxAttempts {
range: ip_range,
attempts,
});
}
// find the min expired IP or where id matches
let ip = match self
.store
Expand All @@ -286,25 +293,19 @@ where
)
.await
{
Ok(ip) => ip.ok_or(IpError::RangeError { range: ip_range })?,
Ok(ip) => ip.ok_or(IpError::RangeError {
range: ip_range.clone(),
})?,
Err(err) => {
attempts += 1;
if attempts <= 1 {
warn!("error grabbing new IP-- retrying");
continue;
} else {
return Err(IpError::DbError(err));
}
warn!(?err, "error grabbing new IP-- retrying");
continue;
}
},
Err(err) => {
attempts += 1;
if attempts <= 1 {
warn!("error grabbing next expired IP-- retrying");
continue;
} else {
return Err(IpError::DbError(err));
}
warn!(?err, "error grabbing next expired IP-- retrying");
continue;
}
};
match ip {
Expand All @@ -327,6 +328,7 @@ where
.update_ip(ip, IpState::Probate, None, probation_time)
.await
{
attempts += 1;
error!(?err, "failed to probate IP on ping success");
// not returning error because we must give client an IP
} else {
Expand All @@ -336,11 +338,16 @@ where
}
}
} else {
error!(
attempts += 1;
warn!(
?range,
?ipv4,
"IP returned from leases table is outside of network range"
"IP for client id returned from leases table is outside of network range"
);
// entry for ip/id but the range doesn't match, remove the old entry
if let Err(err) = self.store.release_ip(ip, id).await {
error!(?err, "failed to delete entry");
}
continue;
}
}
Expand Down Expand Up @@ -530,6 +537,11 @@ pub enum IpError<E> {
AddrInUse(IpAddr),
#[error("error getting next IP in range {range:?}")]
RangeError { range: RangeInclusive<IpAddr> },
#[error("error getting next IP in range {range:?} inside attempts {attempts:?}")]
MaxAttempts {
range: RangeInclusive<IpAddr>,
attempts: usize,
},
}

#[cfg(test)]
Expand Down
14 changes: 7 additions & 7 deletions libs/ip-manager/src/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,11 +450,11 @@ mod util {
now: i64,
) -> Result<Option<IpAddr>, sqlx::Error> {
Ok(sqlx::query!(
"SELECT ip
FROM
leases
WHERE
client_id = ?1 AND expires_at > ?2
"SELECT ip
FROM
leases
WHERE
client_id = ?1 AND expires_at > ?2
LIMIT 1",
id,
now
Expand Down Expand Up @@ -575,7 +575,7 @@ mod util {
SELECT ip
FROM leases
WHERE
((client_id = ?2 AND ip = ?3)
((client_id = ?2 AND ip = ?3)
OR (expires_at < ?1 AND ip = ?3))
ORDER BY ip LIMIT 1
)
Expand Down Expand Up @@ -671,7 +671,7 @@ mod util {
UPDATE leases
SET
client_id = ?2, expires_at = ?3, leased = ?4, probation = ?5
WHERE
WHERE
ip = ?1
RETURNING *
"#,
Expand Down
3 changes: 2 additions & 1 deletion plugins/leases/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use dora_core::{
dhcproto::v4::{DhcpOption, Message, MessageType, OptionCode},
metrics,
prelude::*,
tracing::warn,
};
use message_type::MatchedClasses;
use register_derive::Register;
Expand Down Expand Up @@ -262,7 +263,7 @@ where
}
}
}
debug!("leases plugin did not assign ip");
warn!("leases plugin did not assign ip, check configuration or try clearing leases table. submit bugs to: github.com/bluecatengineering/dora");
Ok(Action::NoResponse)
}

Expand Down
2 changes: 1 addition & 1 deletion plugins/message-type/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ impl Plugin<Message> for MsgType {
.insert(DhcpOption::MessageType(MessageType::Offer));
}
Some(MessageType::Request) => {
if !req.giaddr().is_unspecified() {
if req.giaddr().is_unspecified() {
resp.set_flags(req.flags().set_broadcast());
}
resp.opts_mut()
Expand Down

0 comments on commit 2c7d201

Please sign in to comment.