parent
69bc2de64c
commit
49f638e2d2
|
@ -19,6 +19,8 @@ matrix:
|
||||||
env: FEATURES='std proto-ipv4 proto-igmp socket-raw' MODE='test'
|
env: FEATURES='std proto-ipv4 proto-igmp socket-raw' MODE='test'
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
env: FEATURES='std proto-ipv4 socket-udp socket-tcp' MODE='test'
|
env: FEATURES='std proto-ipv4 socket-udp socket-tcp' MODE='test'
|
||||||
|
- rust: nightly
|
||||||
|
env: FEATURES='std proto-ipv4 proto-dhcpv4 socket-udp' MODE='test'
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
env: FEATURES='std proto-ipv6 socket-udp' MODE='test'
|
env: FEATURES='std proto-ipv6 socket-udp' MODE='test'
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
|
@ -35,7 +37,7 @@ matrix:
|
||||||
env: FEATURES='proto-ipv4 proto-ipv6 socket-raw socket-udp socket-tcp socket-icmp alloc'
|
env: FEATURES='proto-ipv4 proto-ipv6 socket-raw socket-udp socket-tcp socket-icmp alloc'
|
||||||
MODE='test'
|
MODE='test'
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
env: FEATURES='proto-ipv4 proto-ipv6 proto-igmp socket-raw socket-udp socket-tcp socket-icmp'
|
env: FEATURES='proto-ipv4 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp'
|
||||||
MODE='build'
|
MODE='build'
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
env: MODE='fuzz run' ARGS='packet_parser -- -max_len=1536 -max_total_time=30'
|
env: MODE='fuzz run' ARGS='packet_parser -- -max_len=1536 -max_total_time=30'
|
||||||
|
|
|
@ -35,12 +35,12 @@ verbose = []
|
||||||
"phy-tap_interface" = ["std", "libc"]
|
"phy-tap_interface" = ["std", "libc"]
|
||||||
"proto-ipv4" = []
|
"proto-ipv4" = []
|
||||||
"proto-igmp" = ["proto-ipv4"]
|
"proto-igmp" = ["proto-ipv4"]
|
||||||
|
"proto-dhcpv4" = ["proto-ipv4", "socket-raw"]
|
||||||
"proto-ipv6" = []
|
"proto-ipv6" = []
|
||||||
"socket-raw" = []
|
"socket-raw" = []
|
||||||
"socket-udp" = []
|
"socket-udp" = []
|
||||||
"socket-tcp" = []
|
"socket-tcp" = []
|
||||||
"socket-icmp" = []
|
"socket-icmp" = []
|
||||||
"proto-dhcpv4" = ["proto-ipv4"]
|
|
||||||
default = [
|
default = [
|
||||||
"std", "log", # needed for `cargo test --no-default-features --features default` :/
|
"std", "log", # needed for `cargo test --no-default-features --features default` :/
|
||||||
"phy-raw_socket", "phy-tap_interface",
|
"phy-raw_socket", "phy-tap_interface",
|
||||||
|
@ -88,5 +88,9 @@ required-features = ["std", "phy-tap_interface", "proto-ipv4", "proto-igmp", "so
|
||||||
name = "benchmark"
|
name = "benchmark"
|
||||||
required-features = ["std", "phy-tap_interface", "proto-ipv4", "socket-raw", "socket-udp"]
|
required-features = ["std", "phy-tap_interface", "proto-ipv4", "socket-raw", "socket-udp"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "dhcp_client"
|
||||||
|
required-features = ["std", "phy-tap_interface", "proto-ipv4", "proto-dhcpv4", "socket-raw"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = 2
|
debug = 2
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
extern crate env_logger;
|
||||||
|
extern crate getopts;
|
||||||
|
extern crate smoltcp;
|
||||||
|
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
use smoltcp::phy::wait as phy_wait;
|
||||||
|
use smoltcp::wire::{EthernetAddress, Ipv4Address, IpCidr, Ipv4Cidr};
|
||||||
|
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, Routes};
|
||||||
|
use smoltcp::socket::{SocketSet, RawSocketBuffer, RawPacketMetadata};
|
||||||
|
use smoltcp::time::Instant;
|
||||||
|
use smoltcp::dhcp::Dhcpv4Client;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
#[cfg(feature = "log")]
|
||||||
|
utils::setup_logging("");
|
||||||
|
|
||||||
|
let (mut opts, mut free) = utils::create_options();
|
||||||
|
utils::add_tap_options(&mut opts, &mut free);
|
||||||
|
utils::add_middleware_options(&mut opts, &mut free);
|
||||||
|
|
||||||
|
let mut matches = utils::parse_options(&opts, free);
|
||||||
|
let device = utils::parse_tap_options(&mut matches);
|
||||||
|
let fd = device.as_raw_fd();
|
||||||
|
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/false);
|
||||||
|
|
||||||
|
let neighbor_cache = NeighborCache::new(BTreeMap::new());
|
||||||
|
let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
|
||||||
|
let ip_addrs = [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 0)];
|
||||||
|
let mut routes_storage = [None; 1];
|
||||||
|
let routes = Routes::new(&mut routes_storage[..]);
|
||||||
|
let mut iface = EthernetInterfaceBuilder::new(device)
|
||||||
|
.ethernet_addr(ethernet_addr)
|
||||||
|
.neighbor_cache(neighbor_cache)
|
||||||
|
.ip_addrs(ip_addrs)
|
||||||
|
.routes(routes)
|
||||||
|
.finalize();
|
||||||
|
|
||||||
|
let mut sockets = SocketSet::new(vec![]);
|
||||||
|
let dhcp_rx_buffer = RawSocketBuffer::new(
|
||||||
|
[RawPacketMetadata::EMPTY; 1],
|
||||||
|
vec![0; 900]
|
||||||
|
);
|
||||||
|
let dhcp_tx_buffer = RawSocketBuffer::new(
|
||||||
|
[RawPacketMetadata::EMPTY; 1],
|
||||||
|
vec![0; 600]
|
||||||
|
);
|
||||||
|
let mut dhcp = Dhcpv4Client::new(&mut sockets, dhcp_rx_buffer, dhcp_tx_buffer, Instant::now());
|
||||||
|
let mut prev_cidr = Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0);
|
||||||
|
loop {
|
||||||
|
let timestamp = Instant::now();
|
||||||
|
iface.poll(&mut sockets, timestamp)
|
||||||
|
.map(|_| ())
|
||||||
|
.unwrap_or_else(|e| println!("Poll: {:?}", e));
|
||||||
|
let config = dhcp.poll(&mut iface, &mut sockets, timestamp)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
println!("DHCP: {:?}", e);
|
||||||
|
None
|
||||||
|
});
|
||||||
|
config.map(|config| {
|
||||||
|
println!("DHCP config: {:?}", config);
|
||||||
|
match config.address {
|
||||||
|
Some(cidr) => if cidr != prev_cidr {
|
||||||
|
iface.update_ip_addrs(|addrs| {
|
||||||
|
addrs.iter_mut().nth(0)
|
||||||
|
.map(|addr| {
|
||||||
|
*addr = IpCidr::Ipv4(cidr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
prev_cidr = cidr;
|
||||||
|
println!("Assigned a new IPv4 address: {}", cidr);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.router.map(|router| iface.routes_mut()
|
||||||
|
.add_default_ipv4_route(router.into())
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
iface.routes_mut()
|
||||||
|
.update(|routes_map| {
|
||||||
|
routes_map.get(&IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 0))
|
||||||
|
.map(|default_route| {
|
||||||
|
println!("Default gateway: {}", default_route.via_router);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if config.dns_servers.iter().any(|s| s.is_some()) {
|
||||||
|
println!("DNS servers:");
|
||||||
|
for dns_server in config.dns_servers.iter().filter_map(|s| *s) {
|
||||||
|
println!("- {}", dns_server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut timeout = dhcp.next_poll(timestamp);
|
||||||
|
iface.poll_delay(&sockets, timestamp)
|
||||||
|
.map(|sockets_timeout| timeout = sockets_timeout);
|
||||||
|
phy_wait(fd, Some(timeout))
|
||||||
|
.unwrap_or_else(|e| println!("Wait: {:?}", e));;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,435 @@
|
||||||
|
use {Result, Error};
|
||||||
|
use wire::{IpVersion, IpProtocol, IpEndpoint, IpAddress,
|
||||||
|
Ipv4Cidr, Ipv4Address, Ipv4Packet, Ipv4Repr,
|
||||||
|
UdpPacket, UdpRepr,
|
||||||
|
DhcpPacket, DhcpRepr, DhcpMessageType};
|
||||||
|
use wire::dhcpv4::field as dhcpv4_field;
|
||||||
|
use socket::{SocketSet, SocketHandle, RawSocket, RawSocketBuffer};
|
||||||
|
use phy::{Device, ChecksumCapabilities};
|
||||||
|
use iface::EthernetInterface as Interface;
|
||||||
|
use time::{Instant, Duration};
|
||||||
|
use super::{UDP_SERVER_PORT, UDP_CLIENT_PORT};
|
||||||
|
|
||||||
|
const DISCOVER_TIMEOUT: u64 = 10;
|
||||||
|
const REQUEST_TIMEOUT: u64 = 1;
|
||||||
|
const REQUEST_RETRIES: u16 = 15;
|
||||||
|
const RENEW_INTERVAL: u64 = 60;
|
||||||
|
const RENEW_RETRIES: u16 = 3;
|
||||||
|
const PARAMETER_REQUEST_LIST: &[u8] = &[
|
||||||
|
dhcpv4_field::OPT_SUBNET_MASK,
|
||||||
|
dhcpv4_field::OPT_ROUTER,
|
||||||
|
dhcpv4_field::OPT_DOMAIN_NAME_SERVER,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// IPv4 configuration data returned by `client.poll()`
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
pub address: Option<Ipv4Cidr>,
|
||||||
|
pub router: Option<Ipv4Address>,
|
||||||
|
pub dns_servers: [Option<Ipv4Address>; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct RequestState {
|
||||||
|
retry: u16,
|
||||||
|
endpoint_ip: Ipv4Address,
|
||||||
|
server_identifier: Ipv4Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct RenewState {
|
||||||
|
retry: u16,
|
||||||
|
endpoint_ip: Ipv4Address,
|
||||||
|
server_identifier: Ipv4Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ClientState {
|
||||||
|
/// Discovering the DHCP server
|
||||||
|
Discovering,
|
||||||
|
/// Requesting an address
|
||||||
|
Requesting(RequestState),
|
||||||
|
/// Having an address, refresh it periodically
|
||||||
|
Renew(RenewState),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Client {
|
||||||
|
state: ClientState,
|
||||||
|
raw_handle: SocketHandle,
|
||||||
|
/// When to send next request
|
||||||
|
next_egress: Instant,
|
||||||
|
transaction_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DHCP client with a RawSocket.
|
||||||
|
///
|
||||||
|
/// To provide memory for the dynamic IP address, configure your
|
||||||
|
/// `Interface` with one of `ip_addrs` and the `ipv4_gateway` being
|
||||||
|
/// `Ipv4Address::UNSPECIFIED`. You must also assign this `0.0.0.0/0`
|
||||||
|
/// while the client's state is `Discovering`. Hence, the `poll()`
|
||||||
|
/// method returns a corresponding `Config` struct in this case.
|
||||||
|
///
|
||||||
|
/// You must call `dhcp_client.poll()` after `iface.poll()` to send
|
||||||
|
/// and receive DHCP packets.
|
||||||
|
impl Client {
|
||||||
|
/// # Usage
|
||||||
|
/// ```rust
|
||||||
|
/// use smoltcp::socket::{SocketSet, RawSocketBuffer, RawPacketMetadata};
|
||||||
|
/// use smoltcp::dhcp::Dhcpv4Client;
|
||||||
|
/// use smoltcp::time::Instant;
|
||||||
|
///
|
||||||
|
/// let mut sockets = SocketSet::new(vec![]);
|
||||||
|
/// let dhcp_rx_buffer = RawSocketBuffer::new(
|
||||||
|
/// [RawPacketMetadata::EMPTY; 1],
|
||||||
|
/// vec![0; 600]
|
||||||
|
/// );
|
||||||
|
/// let dhcp_tx_buffer = RawSocketBuffer::new(
|
||||||
|
/// [RawPacketMetadata::EMPTY; 1],
|
||||||
|
/// vec![0; 600]
|
||||||
|
/// );
|
||||||
|
/// let mut dhcp = Dhcpv4Client::new(
|
||||||
|
/// &mut sockets,
|
||||||
|
/// dhcp_rx_buffer, dhcp_tx_buffer,
|
||||||
|
/// Instant::now()
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
pub fn new<'a, 'b, 'c>(sockets: &mut SocketSet<'a, 'b, 'c>, rx_buffer: RawSocketBuffer<'b, 'c>, tx_buffer: RawSocketBuffer<'b, 'c>, now: Instant) -> Self
|
||||||
|
where 'b: 'c,
|
||||||
|
{
|
||||||
|
let raw_socket = RawSocket::new(IpVersion::Ipv4, IpProtocol::Udp, rx_buffer, tx_buffer);
|
||||||
|
let raw_handle = sockets.add(raw_socket);
|
||||||
|
|
||||||
|
Client {
|
||||||
|
state: ClientState::Discovering,
|
||||||
|
raw_handle,
|
||||||
|
next_egress: now,
|
||||||
|
transaction_id: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When to send next packet
|
||||||
|
///
|
||||||
|
/// Useful for suspending execution after polling.
|
||||||
|
pub fn next_poll(&self, now: Instant) -> Duration {
|
||||||
|
self.next_egress - now
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process incoming packets on the contained RawSocket, and send
|
||||||
|
/// DHCP requests when timeouts are ready.
|
||||||
|
///
|
||||||
|
/// Applying the obtained network configuration is left to the
|
||||||
|
/// user. You must configure the new IPv4 address from the
|
||||||
|
/// returned `Config`. Otherwise, DHCP will not work.
|
||||||
|
///
|
||||||
|
/// A Config can be returned from any valid DHCP reply. The client
|
||||||
|
/// performs no bookkeeping on configuration or their changes.
|
||||||
|
pub fn poll<DeviceT>(&mut self,
|
||||||
|
iface: &mut Interface<DeviceT>, sockets: &mut SocketSet,
|
||||||
|
now: Instant
|
||||||
|
) -> Result<Option<Config>>
|
||||||
|
where
|
||||||
|
DeviceT: for<'d> Device<'d>,
|
||||||
|
{
|
||||||
|
let checksum_caps = ChecksumCapabilities::default(); // ?
|
||||||
|
let mut raw_socket = sockets.get::<RawSocket>(self.raw_handle);
|
||||||
|
|
||||||
|
// Process incoming
|
||||||
|
let config = {
|
||||||
|
match raw_socket.recv()
|
||||||
|
.and_then(|packet| parse_udp(packet, &checksum_caps)) {
|
||||||
|
Ok((IpEndpoint {
|
||||||
|
addr: IpAddress::Ipv4(src_ip),
|
||||||
|
port: UDP_SERVER_PORT,
|
||||||
|
}, IpEndpoint {
|
||||||
|
addr: _,
|
||||||
|
port: UDP_CLIENT_PORT,
|
||||||
|
}, payload)) =>
|
||||||
|
self.ingress(iface, now, payload, &src_ip),
|
||||||
|
Ok(_) =>
|
||||||
|
return Err(Error::Unrecognized),
|
||||||
|
Err(Error::Exhausted) =>
|
||||||
|
None,
|
||||||
|
Err(e) =>
|
||||||
|
return Err(e),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if config.is_some() {
|
||||||
|
// Return a new config immediately so that addresses can
|
||||||
|
// be configured that are required by egress().
|
||||||
|
Ok(config)
|
||||||
|
} else {
|
||||||
|
// Send requests
|
||||||
|
if raw_socket.can_send() && now >= self.next_egress {
|
||||||
|
self.egress(iface, &mut *raw_socket, &checksum_caps, now)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ingress<DeviceT>(&mut self,
|
||||||
|
iface: &mut Interface<DeviceT>, now: Instant,
|
||||||
|
data: &[u8], src_ip: &Ipv4Address
|
||||||
|
) -> Option<Config>
|
||||||
|
where
|
||||||
|
DeviceT: for<'d> Device<'d>,
|
||||||
|
{
|
||||||
|
let dhcp_packet = match DhcpPacket::new_checked(data) {
|
||||||
|
Ok(dhcp_packet) => dhcp_packet,
|
||||||
|
Err(e) => {
|
||||||
|
net_debug!("DHCP invalid pkt from {}: {:?}", src_ip, e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let dhcp_repr = match DhcpRepr::parse(&dhcp_packet) {
|
||||||
|
Ok(dhcp_repr) => dhcp_repr,
|
||||||
|
Err(e) => {
|
||||||
|
net_debug!("DHCP error parsing pkt from {}: {:?}", src_ip, e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mac = iface.ethernet_addr();
|
||||||
|
if dhcp_repr.client_hardware_address != mac { return None }
|
||||||
|
if dhcp_repr.transaction_id != self.transaction_id { return None }
|
||||||
|
let server_identifier = match dhcp_repr.server_identifier {
|
||||||
|
Some(server_identifier) => server_identifier,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
net_debug!("DHCP recv {:?} from {} ({})", dhcp_repr.message_type, src_ip, server_identifier);
|
||||||
|
|
||||||
|
let config = if (dhcp_repr.message_type == DhcpMessageType::Offer ||
|
||||||
|
dhcp_repr.message_type == DhcpMessageType::Ack) &&
|
||||||
|
dhcp_repr.your_ip != Ipv4Address::UNSPECIFIED {
|
||||||
|
let address = dhcp_repr.subnet_mask
|
||||||
|
.and_then(|mask| IpAddress::Ipv4(mask).to_prefix_len())
|
||||||
|
.map(|prefix_len| Ipv4Cidr::new(dhcp_repr.your_ip, prefix_len));
|
||||||
|
let router = dhcp_repr.router;
|
||||||
|
let dns_servers = dhcp_repr.dns_servers
|
||||||
|
.unwrap_or([None; 3]);
|
||||||
|
Some(Config { address, router, dns_servers })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.state {
|
||||||
|
ClientState::Discovering
|
||||||
|
if dhcp_repr.message_type == DhcpMessageType::Offer =>
|
||||||
|
{
|
||||||
|
self.next_egress = now;
|
||||||
|
let r_state = RequestState {
|
||||||
|
retry: 0,
|
||||||
|
endpoint_ip: *src_ip,
|
||||||
|
server_identifier,
|
||||||
|
};
|
||||||
|
Some(ClientState::Requesting(r_state))
|
||||||
|
}
|
||||||
|
ClientState::Requesting(ref r_state)
|
||||||
|
if dhcp_repr.message_type == DhcpMessageType::Ack &&
|
||||||
|
server_identifier == r_state.server_identifier =>
|
||||||
|
{
|
||||||
|
self.next_egress = now + Duration::from_secs(RENEW_INTERVAL);
|
||||||
|
let p_state = RenewState {
|
||||||
|
retry: 0,
|
||||||
|
endpoint_ip: *src_ip,
|
||||||
|
server_identifier,
|
||||||
|
};
|
||||||
|
Some(ClientState::Renew(p_state))
|
||||||
|
}
|
||||||
|
ClientState::Renew(ref mut p_state)
|
||||||
|
if dhcp_repr.message_type == DhcpMessageType::Ack &&
|
||||||
|
server_identifier == p_state.server_identifier =>
|
||||||
|
{
|
||||||
|
self.next_egress = now + Duration::from_secs(RENEW_INTERVAL);
|
||||||
|
p_state.retry = 0;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
_ => None
|
||||||
|
}.map(|new_state| self.state = new_state);
|
||||||
|
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
fn egress<DeviceT: for<'d> Device<'d>>(&mut self, iface: &mut Interface<DeviceT>, raw_socket: &mut RawSocket, checksum_caps: &ChecksumCapabilities, now: Instant) -> Result<Option<Config>> {
|
||||||
|
// Reset after maximum amount of retries
|
||||||
|
let retries_exceeded = match self.state {
|
||||||
|
ClientState::Requesting(ref mut r_state) if r_state.retry >= REQUEST_RETRIES => {
|
||||||
|
net_debug!("DHCP request retries exceeded, restarting discovery");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
ClientState::Renew(ref mut r_state) if r_state.retry >= RENEW_RETRIES => {
|
||||||
|
net_debug!("DHCP renew retries exceeded, restarting discovery");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
if retries_exceeded {
|
||||||
|
self.reset(now);
|
||||||
|
// Return a config now so that user code assigns the
|
||||||
|
// 0.0.0.0/0 address, which will be used sending a DHCP
|
||||||
|
// discovery packet in the next call to egress().
|
||||||
|
return Ok(Some(Config {
|
||||||
|
address: Some(Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0)),
|
||||||
|
router: None,
|
||||||
|
dns_servers: [None; 3],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare sending next packet
|
||||||
|
self.transaction_id += 1;
|
||||||
|
let mac = iface.ethernet_addr();
|
||||||
|
|
||||||
|
let mut dhcp_repr = DhcpRepr {
|
||||||
|
message_type: DhcpMessageType::Discover,
|
||||||
|
transaction_id: self.transaction_id,
|
||||||
|
client_hardware_address: mac,
|
||||||
|
client_ip: Ipv4Address::UNSPECIFIED,
|
||||||
|
your_ip: Ipv4Address::UNSPECIFIED,
|
||||||
|
server_ip: Ipv4Address::UNSPECIFIED,
|
||||||
|
router: None,
|
||||||
|
subnet_mask: None,
|
||||||
|
relay_agent_ip: Ipv4Address::UNSPECIFIED,
|
||||||
|
broadcast: true,
|
||||||
|
requested_ip: None,
|
||||||
|
client_identifier: Some(mac),
|
||||||
|
server_identifier: None,
|
||||||
|
parameter_request_list: None,
|
||||||
|
dns_servers: None,
|
||||||
|
};
|
||||||
|
let mut send_packet = |iface, endpoint, dhcp_repr| {
|
||||||
|
send_packet(iface, raw_socket, &endpoint, &dhcp_repr, checksum_caps)
|
||||||
|
.map(|()| None)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
match self.state {
|
||||||
|
ClientState::Discovering => {
|
||||||
|
self.next_egress = now + Duration::from_secs(DISCOVER_TIMEOUT);
|
||||||
|
|
||||||
|
let endpoint = IpEndpoint {
|
||||||
|
addr: Ipv4Address::BROADCAST.into(),
|
||||||
|
port: UDP_SERVER_PORT,
|
||||||
|
};
|
||||||
|
net_trace!("DHCP send discover to {}: {:?}", endpoint, dhcp_repr);
|
||||||
|
send_packet(iface, endpoint, dhcp_repr)
|
||||||
|
}
|
||||||
|
ClientState::Requesting(ref mut r_state) => {
|
||||||
|
r_state.retry += 1;
|
||||||
|
self.next_egress = now + Duration::from_secs(REQUEST_TIMEOUT);
|
||||||
|
|
||||||
|
let endpoint = IpEndpoint {
|
||||||
|
addr: Ipv4Address::BROADCAST.into(),
|
||||||
|
port: UDP_SERVER_PORT,
|
||||||
|
};
|
||||||
|
let requested_ip = match iface.ipv4_addr() {
|
||||||
|
Some(addr) if !addr.is_unspecified() =>
|
||||||
|
Some(addr),
|
||||||
|
_ =>
|
||||||
|
None,
|
||||||
|
};
|
||||||
|
dhcp_repr.message_type = DhcpMessageType::Request;
|
||||||
|
dhcp_repr.broadcast = false;
|
||||||
|
dhcp_repr.requested_ip = requested_ip;
|
||||||
|
dhcp_repr.server_identifier = Some(r_state.server_identifier);
|
||||||
|
dhcp_repr.parameter_request_list = Some(PARAMETER_REQUEST_LIST);
|
||||||
|
net_trace!("DHCP send request to {} = {:?}", endpoint, dhcp_repr);
|
||||||
|
send_packet(iface, endpoint, dhcp_repr)
|
||||||
|
}
|
||||||
|
ClientState::Renew(ref mut p_state) => {
|
||||||
|
p_state.retry += 1;
|
||||||
|
self.next_egress = now + Duration::from_secs(RENEW_INTERVAL);
|
||||||
|
|
||||||
|
let endpoint = IpEndpoint {
|
||||||
|
addr: p_state.endpoint_ip.into(),
|
||||||
|
port: UDP_SERVER_PORT,
|
||||||
|
};
|
||||||
|
let client_ip = iface.ipv4_addr().unwrap_or(Ipv4Address::UNSPECIFIED);
|
||||||
|
dhcp_repr.message_type = DhcpMessageType::Request;
|
||||||
|
dhcp_repr.client_ip = client_ip;
|
||||||
|
dhcp_repr.broadcast = false;
|
||||||
|
net_trace!("DHCP send renew to {}: {:?}", endpoint, dhcp_repr);
|
||||||
|
send_packet(iface, endpoint, dhcp_repr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset state and restart discovery phase.
|
||||||
|
///
|
||||||
|
/// Use this to speed up acquisition of an address in a new
|
||||||
|
/// network if a link was down and it is now back up.
|
||||||
|
///
|
||||||
|
/// You *must* configure a `0.0.0.0` address on your interface
|
||||||
|
/// before the next call to `poll()`!
|
||||||
|
pub fn reset(&mut self, now: Instant) {
|
||||||
|
net_trace!("DHCP reset");
|
||||||
|
self.state = ClientState::Discovering;
|
||||||
|
self.next_egress = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_packet<DeviceT: for<'d> Device<'d>>(iface: &mut Interface<DeviceT>, raw_socket: &mut RawSocket, endpoint: &IpEndpoint, dhcp_repr: &DhcpRepr, checksum_caps: &ChecksumCapabilities) -> Result<()> {
|
||||||
|
let mut dhcp_payload_buf = [0; 320];
|
||||||
|
assert!(dhcp_repr.buffer_len() <= dhcp_payload_buf.len());
|
||||||
|
let dhcp_payload = &mut dhcp_payload_buf[0..dhcp_repr.buffer_len()];
|
||||||
|
{
|
||||||
|
let mut dhcp_packet = DhcpPacket::new_checked(&mut dhcp_payload[..])?;
|
||||||
|
dhcp_repr.emit(&mut dhcp_packet)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let udp_repr = UdpRepr {
|
||||||
|
src_port: UDP_CLIENT_PORT,
|
||||||
|
dst_port: endpoint.port,
|
||||||
|
payload: dhcp_payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
let src_addr = iface.ipv4_addr().unwrap();
|
||||||
|
let dst_addr = match endpoint.addr {
|
||||||
|
IpAddress::Ipv4(addr) => addr,
|
||||||
|
_ => return Err(Error::Illegal),
|
||||||
|
};
|
||||||
|
let ipv4_repr = Ipv4Repr {
|
||||||
|
src_addr,
|
||||||
|
dst_addr,
|
||||||
|
protocol: IpProtocol::Udp,
|
||||||
|
payload_len: udp_repr.buffer_len(),
|
||||||
|
hop_limit: 64,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut packet = raw_socket.send(
|
||||||
|
ipv4_repr.buffer_len() + udp_repr.buffer_len()
|
||||||
|
)?;
|
||||||
|
{
|
||||||
|
let mut ipv4_packet = Ipv4Packet::new_unchecked(&mut packet);
|
||||||
|
ipv4_repr.emit(&mut ipv4_packet, &checksum_caps);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let mut udp_packet = UdpPacket::new_unchecked(
|
||||||
|
&mut packet[ipv4_repr.buffer_len()..]
|
||||||
|
);
|
||||||
|
udp_repr.emit(&mut udp_packet,
|
||||||
|
&src_addr.into(), &dst_addr.into(),
|
||||||
|
checksum_caps);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_udp<'a>(data: &'a [u8], checksum_caps: &ChecksumCapabilities) -> Result<(IpEndpoint, IpEndpoint, &'a [u8])> {
|
||||||
|
let ipv4_packet = Ipv4Packet::new_checked(data)?;
|
||||||
|
let ipv4_repr = Ipv4Repr::parse(&ipv4_packet, &checksum_caps)?;
|
||||||
|
let udp_packet = UdpPacket::new_checked(ipv4_packet.payload())?;
|
||||||
|
let udp_repr = UdpRepr::parse(
|
||||||
|
&udp_packet,
|
||||||
|
&ipv4_repr.src_addr.into(), &ipv4_repr.dst_addr.into(),
|
||||||
|
checksum_caps
|
||||||
|
)?;
|
||||||
|
let src = IpEndpoint {
|
||||||
|
addr: ipv4_repr.src_addr.into(),
|
||||||
|
port: udp_repr.src_port,
|
||||||
|
};
|
||||||
|
let dst = IpEndpoint {
|
||||||
|
addr: ipv4_repr.dst_addr.into(),
|
||||||
|
port: udp_repr.dst_port,
|
||||||
|
};
|
||||||
|
let data = udp_repr.payload;
|
||||||
|
Ok((src, dst, data))
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
pub const UDP_SERVER_PORT: u16 = 67;
|
||||||
|
pub const UDP_CLIENT_PORT: u16 = 68;
|
||||||
|
|
||||||
|
mod clientv4;
|
||||||
|
pub use self::clientv4::Client as Dhcpv4Client;
|
|
@ -433,6 +433,16 @@ impl<'b, 'c, 'e, DeviceT> Interface<'b, 'c, 'e, DeviceT>
|
||||||
self.inner.ip_addrs.as_ref()
|
self.inner.ip_addrs.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the first IPv4 address if present.
|
||||||
|
#[cfg(feature = "proto-ipv4")]
|
||||||
|
pub fn ipv4_addr(&self) -> Option<Ipv4Address> {
|
||||||
|
self.ip_addrs().iter()
|
||||||
|
.filter_map(|cidr| match cidr.address() {
|
||||||
|
IpAddress::Ipv4(addr) => Some(addr),
|
||||||
|
_ => None,
|
||||||
|
}).next()
|
||||||
|
}
|
||||||
|
|
||||||
/// Update the IP addresses of the interface.
|
/// Update the IP addresses of the interface.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
|
@ -698,7 +708,7 @@ impl<'b, 'c, 'e> InterfaceInner<'b, 'c, 'e> {
|
||||||
|
|
||||||
fn check_ip_addrs(addrs: &[IpCidr]) {
|
fn check_ip_addrs(addrs: &[IpCidr]) {
|
||||||
for cidr in addrs {
|
for cidr in addrs {
|
||||||
if !cidr.address().is_unicast() {
|
if !cidr.address().is_unicast() && !cidr.address().is_unspecified() {
|
||||||
panic!("IP address {} is not unicast", cidr.address())
|
panic!("IP address {} is not unicast", cidr.address())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,8 @@ pub mod wire;
|
||||||
pub mod iface;
|
pub mod iface;
|
||||||
pub mod socket;
|
pub mod socket;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
|
#[cfg(feature = "proto-dhcpv4")]
|
||||||
|
pub mod dhcp;
|
||||||
|
|
||||||
/// The error type for the networking stack.
|
/// The error type for the networking stack.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
|
@ -765,7 +765,8 @@ impl<'a> Repr<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emit a high-level representation into a Transmission Control Protocol packet.
|
/// Emit a high-level representation into a Dynamic Host
|
||||||
|
/// Configuration Protocol packet.
|
||||||
pub fn emit<T>(&self, packet: &mut Packet<&mut T>) -> Result<()>
|
pub fn emit<T>(&self, packet: &mut Packet<&mut T>) -> Result<()>
|
||||||
where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized {
|
where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized {
|
||||||
packet.set_sname_and_boot_file_to_zero();
|
packet.set_sname_and_boot_file_to_zero();
|
||||||
|
|
Loading…
Reference in New Issue