dhcp4r-0.2.0/.gitignore010064400017500001750000000000221355262250500131050ustar0000000000000000target Cargo.lock dhcp4r-0.2.0/.travis.yml010066400017500001750000000001671355307272000132410ustar0000000000000000language: rust rust: - stable - beta - nightly matrix: allow_failures: - rust: nightly fast_finish: true dhcp4r-0.2.0/Cargo.toml.orig010066400017500001750000000012251355307357300140220ustar0000000000000000[package] name = "dhcp4r" version = "0.2.0" authors = ["Richard Warburton "] description = "IPv4 DHCP library with working server example." edition = "2018" # These URLs point to more information about the repository. #documentation = "..." #homepage = "..." repository = "https://github.com/krolaw/dhcp4r" # This points to a file in the repository (relative to this `Cargo.toml`). The # contents of this file are stored and indexed in the registry. # readme = "..." keywords = ["dhcp", "server", "monitor", "client"] license = "BSD-3-Clause" [dependencies] enum-primitive-derive = "^0.1" num-traits = "^0.1" time = "0.1" nom = "5.0.1" dhcp4r-0.2.0/Cargo.toml0000644000000017510000000000000102600ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "dhcp4r" version = "0.2.0" authors = ["Richard Warburton "] description = "IPv4 DHCP library with working server example." keywords = ["dhcp", "server", "monitor", "client"] license = "BSD-3-Clause" repository = "https://github.com/krolaw/dhcp4r" [dependencies.enum-primitive-derive] version = "^0.1" [dependencies.nom] version = "5.0.1" [dependencies.num-traits] version = "^0.1" [dependencies.time] version = "0.1" dhcp4r-0.2.0/LICENSE010064400017500001750000000027201355262250500121310ustar0000000000000000Copyright (c) 2016 Richard Warburton. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Richard Warburton nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. dhcp4r-0.2.0/README.md010064400017500001750000000004221355307346000124010ustar0000000000000000# DHCP4r - A DHCP library written in Rust. ## Author http://richard.warburton.it/ ## Quick Start See examples/server.rs for how to use this library to create a basic server. If you have cargo installed, you can run the example server: ``` $ cargo run --example server ``` dhcp4r-0.2.0/examples/monitor.rs010066400017500001750000000022631355307235200150030ustar0000000000000000extern crate dhcp4r; extern crate time; use std::net::{UdpSocket,Ipv4Addr}; use dhcp4r::{packet, options, server}; fn main() { server::Server::serve(UdpSocket::bind("0.0.0.0:67").unwrap(), Ipv4Addr::new(0,0,0,0), MyServer{}); } struct MyServer {} impl server::Handler for MyServer { fn handle_request(&mut self, _: &server::Server, in_packet: packet::Packet) { match in_packet.message_type() { Ok(options::MessageType::Request) => { let req_ip = match in_packet.option(options::REQUESTED_IP_ADDRESS) { Some(options::DhcpOption::RequestedIpAddress(x)) => { x.clone() }, _ => in_packet.ciaddr, }; println!("{}\t{}\t{}\tOnline", time::now().strftime("%Y-%m-%dT%H:%M:%S").unwrap(), chaddr(&in_packet.chaddr), Ipv4Addr::from(req_ip)); } _ => {} } } } /// Formats byte array machine address into hex pairs separated by colons. /// Array must be at least one byte long. fn chaddr(a: &[u8]) -> String { a[1..].iter().fold(format!("{:02x}",a[0]), |acc, &b| format!("{}:{:02x}", acc, &b)) } dhcp4r-0.2.0/examples/server.rs010066400017500001750000000130021355307235200146130ustar0000000000000000#[macro_use(u32_bytes, bytes_u32)] extern crate dhcp4r; use std::net::{UdpSocket, Ipv4Addr}; use std::time::{Duration, Instant}; use std::collections::HashMap; use std::ops::Add; use dhcp4r::{packet, options, server}; // Server configuration const SERVER_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 0, 76); const IP_START: [u8; 4] = [192, 168, 0, 180]; const SUBNET_MASK: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0); const DNS_IPS: [Ipv4Addr; 2] = [ // Google DNS servers Ipv4Addr::new(8, 8, 8, 8), Ipv4Addr::new(4, 4, 4, 4), ]; const ROUTER_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 0, 254); const LEASE_DURATION_SECS: u32 = 7200; const LEASE_NUM: u32 = 100; // Derived constants const IP_START_NUM: u32 = bytes_u32!(IP_START); fn main() { let socket = UdpSocket::bind("0.0.0.0:67").unwrap(); socket.set_broadcast(true).unwrap(); let ms = MyServer { leases: HashMap::new(), last_lease: 0, lease_duration: Duration::new(LEASE_DURATION_SECS as u64, 0), }; server::Server::serve(socket, SERVER_IP, ms); } struct MyServer { leases: HashMap, last_lease: u32, lease_duration: Duration, } impl server::Handler for MyServer { fn handle_request(&mut self, server: &server::Server, in_packet: packet::Packet) { match in_packet.message_type() { Ok(options::MessageType::Discover) => { // Prefer client's choice if available if let Some(options::DhcpOption::RequestedIpAddress(addr)) = in_packet.option(options::REQUESTED_IP_ADDRESS) { let addr = *addr; if self.available(&in_packet.chaddr, &addr) { reply(server, options::MessageType::Offer, in_packet, &addr); return; } } // Otherwise prefer existing (including expired if available) if let Some(ip) = self.current_lease(&in_packet.chaddr) { reply(server, options::MessageType::Offer, in_packet, &ip); return; } // Otherwise choose a free ip if available for _ in 0..LEASE_NUM { self.last_lease = (self.last_lease + 1) % LEASE_NUM; if self.available(&in_packet.chaddr, &((IP_START_NUM + &self.last_lease).into())) { reply(server, options::MessageType::Offer, in_packet, &((IP_START_NUM + &self.last_lease).into())); break; } } } Ok(options::MessageType::Request) => { // Ignore requests to alternative DHCP server if !server.for_this_server(&in_packet) { return; } let req_ip = match in_packet.option(options::REQUESTED_IP_ADDRESS) { Some(options::DhcpOption::RequestedIpAddress(x)) => *x, _ => in_packet.ciaddr, }; if !&self.available(&in_packet.chaddr, &req_ip) { nak(server, in_packet, "Requested IP not available"); return; } self.leases.insert(req_ip, (in_packet.chaddr, Instant::now().add(self.lease_duration))); reply(server, options::MessageType::Ack, in_packet, &req_ip); } Ok(options::MessageType::Release) | Ok(options::MessageType::Decline) => { // Ignore requests to alternative DHCP server if !server.for_this_server(&in_packet) { return; } if let Some(ip) = self.current_lease(&in_packet.chaddr) { self.leases.remove(&ip); } } // TODO - not necessary but support for dhcp4r::INFORM might be nice _ => {} } } } impl MyServer { fn available(&self, chaddr: &[u8; 6], addr: &Ipv4Addr) -> bool { let pos: u32 = (*addr).into(); pos >= IP_START_NUM && pos < IP_START_NUM + LEASE_NUM && match self.leases.get(addr) { Some(x) => x.0 == *chaddr || Instant::now().gt(&x.1), None => true, } } fn current_lease(&self, chaddr: &[u8; 6]) -> Option { for (i, v) in &self.leases { if &v.0 == chaddr { return Some(*i); } } return None; } } fn reply(s: &server::Server, msg_type: options::MessageType, req_packet: packet::Packet, offer_ip: &Ipv4Addr) { let _ = s.reply( msg_type, vec![ options::DhcpOption::IpAddressLeaseTime(LEASE_DURATION_SECS), options::DhcpOption::SubnetMask(SUBNET_MASK), options::DhcpOption::Router(vec![ROUTER_IP]), options::DhcpOption::DomainNameServer(DNS_IPS.to_vec()), ], *offer_ip, req_packet); } fn nak(s: &server::Server, req_packet: packet::Packet, message: &str) { let _ = s.reply( options::MessageType::Nak, vec![options::DhcpOption::Message(message.to_string())], Ipv4Addr::new(0, 0, 0, 0), req_packet ); } dhcp4r-0.2.0/src/lib.rs010064400017500001750000000011241355262250500130240ustar0000000000000000#[macro_use] extern crate enum_primitive_derive; extern crate num_traits; pub mod options; pub mod packet; pub mod server; /// Converts a u32 to 4 bytes (Big endian) #[macro_export] macro_rules! u32_bytes { ( $x:expr ) => { [($x >> 24) as u8, ($x >> 16) as u8, ($x >> 8) as u8, $x as u8] }; } /// Converts 4 bytes to a u32 (Big endian) #[macro_export] macro_rules! bytes_u32 { ( $x:expr ) => { ($x[0] as u32) * (1 << 24) + ($x[1] as u32) * (1 << 16) + ($x[2] as u32) * (1 << 8) + ($x[3] as u32) }; } #[cfg(test)] mod tests { #[test] fn it_works() {} } dhcp4r-0.2.0/src/options.rs010066400017500001750000000327661355307235200137730ustar0000000000000000use std::net::Ipv4Addr; use num_traits::FromPrimitive; #[derive(PartialEq, Clone)] pub struct RawDhcpOption { pub code: u8, pub data: Vec, } #[derive(PartialEq)] pub enum DhcpOption { DhcpMessageType(MessageType), ServerIdentifier(Ipv4Addr), ParameterRequestList(Vec), RequestedIpAddress(Ipv4Addr), HostName(String), Router(Vec), DomainNameServer(Vec), IpAddressLeaseTime(u32), SubnetMask(Ipv4Addr), Message(String), Unrecognized(RawDhcpOption), } impl DhcpOption { pub fn to_raw(&self) -> RawDhcpOption { match self { Self::DhcpMessageType(mtype) => RawDhcpOption{ code: DHCP_MESSAGE_TYPE, data: vec![*mtype as u8], }, Self::ServerIdentifier(addr) => RawDhcpOption{ code: SERVER_IDENTIFIER, data: addr.octets().to_vec(), }, Self::ParameterRequestList(prl) => RawDhcpOption{ code: PARAMETER_REQUEST_LIST, data: prl.clone(), }, Self::RequestedIpAddress(addr) => RawDhcpOption{ code: REQUESTED_IP_ADDRESS, data: addr.octets().to_vec(), }, Self::HostName(name) => RawDhcpOption{ code: HOST_NAME, data: name.as_bytes().to_vec(), }, Self::Router(addrs) => RawDhcpOption{ code: ROUTER, data: { let mut v = vec!(); for a in addrs { v.extend(a.octets().iter()); } v } }, Self::DomainNameServer(addrs) => RawDhcpOption{ code: DOMAIN_NAME_SERVER, data: { let mut v = vec!(); for a in addrs { v.extend(a.octets().iter()); } v } }, Self::IpAddressLeaseTime(secs) => RawDhcpOption{ code: IP_ADDRESS_LEASE_TIME, data: [ (secs & 0xFF) as u8, ((secs >> 8) & 0xFFu32) as u8, ((secs >> 16) & 0xFFu32) as u8, ((secs >> 24) & 0xFFu32) as u8, ].to_vec(), }, Self::SubnetMask(mask) => RawDhcpOption{ code: SUBNET_MASK, data: mask.octets().to_vec(), }, Self::Message(msg) => RawDhcpOption{ code: MESSAGE, data: msg.as_bytes().to_vec(), }, Self::Unrecognized(raw) => raw.clone(), } } pub fn code(&self) -> u8 { match self { Self::DhcpMessageType(_) => DHCP_MESSAGE_TYPE, Self::ServerIdentifier(_) => SERVER_IDENTIFIER, Self::ParameterRequestList(_) => PARAMETER_REQUEST_LIST, Self::RequestedIpAddress(_) => REQUESTED_IP_ADDRESS, Self::HostName(_) => HOST_NAME, Self::Router(_) => ROUTER, Self::DomainNameServer(_) => DOMAIN_NAME_SERVER, Self::IpAddressLeaseTime(_) => IP_ADDRESS_LEASE_TIME, Self::SubnetMask(_) => SUBNET_MASK, Self::Message(_) => MESSAGE, Self::Unrecognized(x) => x.code, } } } // DHCP Options; pub const SUBNET_MASK: u8 = 1; pub const TIME_OFFSET: u8 = 2; pub const ROUTER: u8 = 3; pub const TIME_SERVER: u8 = 4; pub const NAME_SERVER: u8 = 5; pub const DOMAIN_NAME_SERVER: u8 = 6; pub const LOG_SERVER: u8 = 7; pub const COOKIE_SERVER: u8 = 8; pub const LPR_SERVER: u8 = 9; pub const IMPRESS_SERVER: u8 = 10; pub const RESOURCE_LOCATION_SERVER: u8 = 11; pub const HOST_NAME: u8 = 12; pub const BOOT_FILE_SIZE: u8 = 13; pub const MERIT_DUMP_FILE: u8 = 14; pub const DOMAIN_NAME: u8 = 15; pub const SWAP_SERVER: u8 = 16; pub const ROOT_PATH: u8 = 17; pub const EXTENSIONS_PATH: u8 = 18; // IP LAYER PARAMETERS PER HOST; pub const IP_FORWARDING_ENABLE_DISABLE: u8 = 19; pub const NON_LOCAL_SOURCE_ROUTING_ENABLE_DISABLE: u8 = 20; pub const POLICY_FILTER: u8 = 21; pub const MAXIMUM_DATAGRAM_REASSEMBLY_SIZE: u8 = 22; pub const DEFAULT_IP_TIME_TO_LIVE: u8 = 23; pub const PATH_MTU_AGING_TIMEOUT: u8 = 24; pub const PATH_MTU_PLATEAU_TABLE: u8 = 25; // IP LAYER PARAMETERS PER INTERFACE; pub const INTERFACE_MTU: u8 = 26; pub const ALL_SUBNETS_ARE_LOCAL: u8 = 27; pub const BROADCAST_ADDRESS: u8 = 28; pub const PERFORM_MASK_DISCOVERY: u8 = 29; pub const MASK_SUPPLIER: u8 = 30; pub const PERFORM_ROUTER_DISCOVERY: u8 = 31; pub const ROUTER_SOLICITATION_ADDRESS: u8 = 32; pub const STATIC_ROUTE: u8 = 33; // LINK LAYER PARAMETERS PER INTERFACE; pub const TRAILER_ENCAPSULATION: u8 = 34; pub const ARP_CACHE_TIMEOUT: u8 = 35; pub const ETHERNET_ENCAPSULATION: u8 = 36; // TCP PARAMETERS; pub const TCP_DEFAULT_TTL: u8 = 37; pub const TCP_KEEPALIVE_INTERVAL: u8 = 38; pub const TCP_KEEPALIVE_GARBAGE: u8 = 39; // APPLICATION AND SERVICE PARAMETERS; pub const NETWORK_INFORMATION_SERVICE_DOMAIN: u8 = 40; pub const NETWORK_INFORMATION_SERVERS: u8 = 41; pub const NETWORK_TIME_PROTOCOL_SERVERS: u8 = 42; pub const VENDOR_SPECIFIC_INFORMATION: u8 = 43; pub const NETBIOS_OVER_TCPIP_NAME_SERVER: u8 = 44; pub const NETBIOS_OVER_TCPIP_DATAGRAM_DISTRIBUTION_SERVER: u8 = 45; pub const NETBIOS_OVER_TCPIP_NODE_TYPE: u8 = 46; pub const NETBIOS_OVER_TCPIP_SCOPE: u8 = 47; pub const XWINDOW_SYSTEM_FONT_SERVER: u8 = 48; pub const XWINDOW_SYSTEM_DISPLAY_MANAGER: u8 = 49; pub const NETWORK_INFORMATION_SERVICEPLUS_DOMAIN: u8 = 64; pub const NETWORK_INFORMATION_SERVICEPLUS_SERVERS: u8 = 65; pub const MOBILE_IP_HOME_AGENT: u8 = 68; pub const SIMPLE_MAIL_TRANSPORT_PROTOCOL: u8 = 69; pub const POST_OFFICE_PROTOCOL_SERVER: u8 = 70; pub const NETWORK_NEWS_TRANSPORT_PROTOCOL: u8 = 71; pub const DEFAULT_WORLD_WIDE_WEB_SERVER: u8 = 72; pub const DEFAULT_FINGER_SERVER: u8 = 73; pub const DEFAULT_INTERNET_RELAY_CHAT_SERVER: u8 = 74; pub const STREETTALK_SERVER: u8 = 75; pub const STREETTALK_DIRECTORY_ASSISTANCE: u8 = 76; pub const RELAY_AGENT_INFORMATION: u8 = 82; // DHCP EXTENSIONS pub const REQUESTED_IP_ADDRESS: u8 = 50; pub const IP_ADDRESS_LEASE_TIME: u8 = 51; pub const OVERLOAD: u8 = 52; pub const DHCP_MESSAGE_TYPE: u8 = 53; pub const SERVER_IDENTIFIER: u8 = 54; pub const PARAMETER_REQUEST_LIST: u8 = 55; pub const MESSAGE: u8 = 56; pub const MAXIMUM_DHCP_MESSAGE_SIZE: u8 = 57; pub const RENEWAL_TIME_VALUE: u8 = 58; pub const REBINDING_TIME_VALUE: u8 = 59; pub const VENDOR_CLASS_IDENTIFIER: u8 = 60; pub const CLIENT_IDENTIFIER: u8 = 61; pub const TFTP_SERVER_NAME: u8 = 66; pub const BOOTFILE_NAME: u8 = 67; pub const USER_CLASS: u8 = 77; pub const CLIENT_ARCHITECTURE: u8 = 93; pub const TZ_POSIX_STRING: u8 = 100; pub const TZ_DATABASE_STRING: u8 = 101; pub const CLASSLESS_ROUTE_FORMAT: u8 = 121; /// Returns title of DHCP Option code, if known. pub fn title(code: u8) -> Option<&'static str> { Some(match code { SUBNET_MASK => "Subnet Mask", TIME_OFFSET => "Time Offset", ROUTER => "Router", TIME_SERVER => "Time Server", NAME_SERVER => "Name Server", DOMAIN_NAME_SERVER => "Domain Name Server", LOG_SERVER => "Log Server", COOKIE_SERVER => "Cookie Server", LPR_SERVER => "LPR Server", IMPRESS_SERVER => "Impress Server", RESOURCE_LOCATION_SERVER => "Resource Location Server", HOST_NAME => "Host Name", BOOT_FILE_SIZE => "Boot File Size", MERIT_DUMP_FILE => "Merit Dump File", DOMAIN_NAME => "Domain Name", SWAP_SERVER => "Swap Server", ROOT_PATH => "Root Path", EXTENSIONS_PATH => "Extensions Path", // IP LAYER PARAMETERS PER HOST", IP_FORWARDING_ENABLE_DISABLE => "IP Forwarding Enable/Disable", NON_LOCAL_SOURCE_ROUTING_ENABLE_DISABLE => "Non-Local Source Routing Enable/Disable", POLICY_FILTER => "Policy Filter", MAXIMUM_DATAGRAM_REASSEMBLY_SIZE => "Maximum Datagram Reassembly Size", DEFAULT_IP_TIME_TO_LIVE => "Default IP Time-to-live", PATH_MTU_AGING_TIMEOUT => "Path MTU Aging Timeout", PATH_MTU_PLATEAU_TABLE => "Path MTU Plateau Table", // IP LAYER PARAMETERS PER INTERFACE", INTERFACE_MTU => "Interface MTU", ALL_SUBNETS_ARE_LOCAL => "All Subnets are Local", BROADCAST_ADDRESS => "Broadcast Address", PERFORM_MASK_DISCOVERY => "Perform Mask Discovery", MASK_SUPPLIER => "Mask Supplier", PERFORM_ROUTER_DISCOVERY => "Perform Router Discovery", ROUTER_SOLICITATION_ADDRESS => "Router Solicitation Address", STATIC_ROUTE => "Static Route", // LINK LAYER PARAMETERS PER INTERFACE", TRAILER_ENCAPSULATION => "Trailer Encapsulation", ARP_CACHE_TIMEOUT => "ARP Cache Timeout", ETHERNET_ENCAPSULATION => "Ethernet Encapsulation", // TCP PARAMETERS", TCP_DEFAULT_TTL => "TCP Default TTL", TCP_KEEPALIVE_INTERVAL => "TCP Keepalive Interval", TCP_KEEPALIVE_GARBAGE => "TCP Keepalive Garbage", // APPLICATION AND SERVICE PARAMETERS", NETWORK_INFORMATION_SERVICE_DOMAIN => "Network Information Service Domain", NETWORK_INFORMATION_SERVERS => "Network Information Servers", NETWORK_TIME_PROTOCOL_SERVERS => "Network Time Protocol Servers", VENDOR_SPECIFIC_INFORMATION => "Vendor Specific Information", NETBIOS_OVER_TCPIP_NAME_SERVER => "NetBIOS over TCP/IP Name Server", NETBIOS_OVER_TCPIP_DATAGRAM_DISTRIBUTION_SERVER => { "NetBIOS over TCP/IP Datagram Distribution Server" } NETBIOS_OVER_TCPIP_NODE_TYPE => "NetBIOS over TCP/IP Node Type", NETBIOS_OVER_TCPIP_SCOPE => "NetBIOS over TCP/IP Scope", XWINDOW_SYSTEM_FONT_SERVER => "X Window System Font Server", XWINDOW_SYSTEM_DISPLAY_MANAGER => "X Window System Display Manager", NETWORK_INFORMATION_SERVICEPLUS_DOMAIN => "Network Information Service+ Domain", NETWORK_INFORMATION_SERVICEPLUS_SERVERS => "Network Information Service+ Servers", MOBILE_IP_HOME_AGENT => "Mobile IP Home Agent", SIMPLE_MAIL_TRANSPORT_PROTOCOL => "Simple Mail Transport Protocol (SMTP) Server", POST_OFFICE_PROTOCOL_SERVER => "Post Office Protocol (POP3) Server", NETWORK_NEWS_TRANSPORT_PROTOCOL => "Network News Transport Protocol (NNTP) Server", DEFAULT_WORLD_WIDE_WEB_SERVER => "Default World Wide Web (WWW) Server", DEFAULT_FINGER_SERVER => "Default Finger Server", DEFAULT_INTERNET_RELAY_CHAT_SERVER => "Default Internet Relay Chat (IRC) Server", STREETTALK_SERVER => "StreetTalk Server", STREETTALK_DIRECTORY_ASSISTANCE => "StreetTalk Directory Assistance (STDA) Server", RELAY_AGENT_INFORMATION => "Relay Agent Information", // DHCP EXTENSIONS REQUESTED_IP_ADDRESS => "Requested IP Address", IP_ADDRESS_LEASE_TIME => "IP Address Lease Time", OVERLOAD => "Overload", DHCP_MESSAGE_TYPE => "DHCP Message Type", SERVER_IDENTIFIER => "Server Identifier", PARAMETER_REQUEST_LIST => "Parameter Request List", MESSAGE => "Message", MAXIMUM_DHCP_MESSAGE_SIZE => "Maximum DHCP Message Size", RENEWAL_TIME_VALUE => "Renewal (T1) Time Value", REBINDING_TIME_VALUE => "Rebinding (T2) Time Value", VENDOR_CLASS_IDENTIFIER => "Vendor class identifier", CLIENT_IDENTIFIER => "Client-identifier", // Find below TFTP_SERVER_NAME => "TFTP server name", BOOTFILE_NAME => "Bootfile name", USER_CLASS => "User Class", CLIENT_ARCHITECTURE => "Client Architecture", TZ_POSIX_STRING => "TZ-POSIX String", TZ_DATABASE_STRING => "TZ-Database String", CLASSLESS_ROUTE_FORMAT => "Classless Route Format", _ => return None, }) } /// /// DHCP Message Type. /// /// # Standards /// /// The semantics of the various DHCP message types are described in RFC 2131 (see Table 2). /// Their numeric values are described in Section 9.6 of RFC 2132, which begins: /// /// > This option is used to convey the type of the DHCP message. The code for this option is 53, /// > and its length is 1. /// #[derive(Primitive, Copy, Clone, PartialEq)] pub enum MessageType { /// Client broadcast to locate available servers. Discover = 1, /// Server to client in response to DHCPDISCOVER with offer of configuration parameters. Offer = 2, /// Client message to servers either (a) requesting offered parameters from one server and /// implicitly declining offers from all others, (b) confirming correctness of previously /// allocated address after, e.g., system reboot, or (c) extending the lease on a particular /// network address. Request = 3, /// Client to server indicating network address is already in use. Decline = 4, /// Server to client with configuration parameters, including committed network address. Ack = 5, /// Server to client indicating client's notion of network address is incorrect (e.g., client /// has moved to new subnet) or client's lease as expired. Nak = 6, /// Client to server relinquishing network address and cancelling remaining lease. Release = 7, /// Client to server, asking only for local configuration parameters; client already has /// externally configured network address. Inform = 8, } impl MessageType { pub fn from(val: u8) -> Result { MessageType::from_u8(val).ok_or_else(|| format!["Invalid DHCP Message Type: {:?}", val]) } } dhcp4r-0.2.0/src/packet.rs010066400017500001750000000165541355307235200135440ustar0000000000000000use crate::options::*; use std::net::Ipv4Addr; use nom::bytes::complete::{tag, take}; use nom::number::complete::{be_u16, be_u32, be_u8}; use nom::multi::{many0, many_till}; pub enum Err { NomError(nom::Err<(I,nom::error::ErrorKind)>), NonUtf8String, UnrecognizedMessageType, InvalidHlen, } impl nom::error::ParseError for Err { fn from_error_kind(input: I, kind: nom::error::ErrorKind) -> Self { Err::NomError(nom::Err::Error((input, kind))) } fn append(_input: I, _kind: nom::error::ErrorKind, other: Self) -> Self { other } } type IResult = nom::IResult>; /// DHCP Packet Structure pub struct Packet { pub reply: bool, // false = request, true = reply pub hops: u8, pub xid: u32, // Random identifier pub secs: u16, pub broadcast: bool, pub ciaddr: Ipv4Addr, pub yiaddr: Ipv4Addr, pub siaddr: Ipv4Addr, pub giaddr: Ipv4Addr, pub chaddr: [u8; 6], pub options: Vec, } fn decode_reply(input: &[u8]) -> IResult<&[u8], bool> { let (input, reply) = take(1u8)(input)?; Ok((input, match reply[0] { BOOT_REPLY => true, BOOT_REQUEST => false, _ => { // @TODO: Throw an error false } })) } fn decode_ipv4(p: &[u8]) -> IResult<&[u8], Ipv4Addr> { let (input, addr) = take(4u8)(p)?; Ok((input, Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3]))) } pub fn decode_option(input: &[u8]) -> IResult<&[u8], DhcpOption> { let (input, code) = be_u8(input)?; assert!(code != END); let (input, len) = be_u8(input)?; let (input, data) = take(len)(input)?; let option = match code { DHCP_MESSAGE_TYPE => DhcpOption::DhcpMessageType( match MessageType::from(be_u8(data)?.1) { Ok(x) => x, Err(_) => return Err(nom::Err::Error(Err::UnrecognizedMessageType)), } ), SERVER_IDENTIFIER => DhcpOption::ServerIdentifier( decode_ipv4(data)?.1 ), PARAMETER_REQUEST_LIST => DhcpOption::ParameterRequestList( data.to_vec() ), REQUESTED_IP_ADDRESS => DhcpOption::RequestedIpAddress( decode_ipv4(data)?.1 ), HOST_NAME => DhcpOption::HostName( match std::str::from_utf8(data) { Ok(s) => s.to_string(), Err(_) => return Err(nom::Err::Error(Err::NonUtf8String)), } ), ROUTER => DhcpOption::Router( many0(decode_ipv4)(data)?.1 ), DOMAIN_NAME_SERVER => DhcpOption::DomainNameServer( many0(decode_ipv4)(data)?.1 ), IP_ADDRESS_LEASE_TIME => DhcpOption::IpAddressLeaseTime( be_u32(data)?.1 ), MESSAGE => DhcpOption::Message( match std::str::from_utf8(data) { Ok(s) => s.to_string(), Err(_) => return Err(nom::Err::Error(Err::NonUtf8String)), } ), _ => DhcpOption::Unrecognized(RawDhcpOption{ code: code, data: data.to_vec(), }) }; Ok((input, option)) } /// Parses Packet from byte array fn decode(input: &[u8]) -> IResult<&[u8], Packet> { let (options_input, input) = take(236u32)(input)?; let (input, reply) = decode_reply(input)?; let (input, _htype) = take(1u8)(input)?; let (input, hlen) = be_u8(input)?; let (input, hops) = be_u8(input)?; let (input, xid) = be_u32(input)?; let (input, secs) = be_u16(input)?; let (input, flags) = be_u16(input)?; let (input, ciaddr) = decode_ipv4(input)?; let (input, yiaddr) = decode_ipv4(input)?; let (input, siaddr) = decode_ipv4(input)?; let (input, giaddr) = decode_ipv4(input)?; if hlen != 6 { return Err(nom::Err::Error(Err::InvalidHlen)); } let (_, chaddr) = take(6u8)(input)?; let input = options_input; let (input, _) = tag(COOKIE)(input)?; let (input, (options, _)) = many_till(decode_option, tag(&[END]))(input)?; Ok((input, Packet{ reply: reply, hops: hops, secs: secs, broadcast: flags & 128 == 128, ciaddr: ciaddr, yiaddr: yiaddr, siaddr: siaddr, giaddr: giaddr, options: options, chaddr: [chaddr[0], chaddr[1], chaddr[2], chaddr[3], chaddr[4], chaddr[5]], xid: xid, })) } impl Packet { pub fn from(input: &[u8]) -> Result>> { Ok(decode(input)?.1) } /// Extracts requested option payload from packet if available pub fn option<'a>(&'a self, code: u8) -> Option<&'a DhcpOption> { for option in &self.options { if option.code() == code { return Some(&option); } } None } /// Convenience function for extracting a packet's message type. pub fn message_type(&self) -> Result { match self.option(DHCP_MESSAGE_TYPE) { Some(DhcpOption::DhcpMessageType(msgtype)) => Ok(*msgtype), Some(_) => Err(format!["Got wrong enum type for DHCP_MESSAGE_TYPE"]), None => Err(format!["Packet does not have MessageType option"]), } } /// Creates byte array DHCP packet pub fn encode<'c>(&'c self, p: &'c mut [u8]) -> &[u8] { p[..12].clone_from_slice(&[(if self.reply { BOOT_REPLY } else { BOOT_REQUEST }), 1, 6, self.hops, ((self.xid >> 24) & 0xFF) as u8, ((self.xid >> 16) & 0xFF) as u8, ((self.xid >> 8) & 0xFF) as u8, (self.xid & 0xFF) as u8, (self.secs >> 8) as u8, (self.secs & 255) as u8, (if self.broadcast { 128 } else { 0 }), 0]); p[12..16].clone_from_slice(&self.ciaddr.octets()); p[16..20].clone_from_slice(&self.yiaddr.octets()); p[20..24].clone_from_slice(&self.siaddr.octets()); p[24..28].clone_from_slice(&self.giaddr.octets()); p[28..34].clone_from_slice(&self.chaddr); p[34..236].clone_from_slice(&[0; 202]); p[236..240].clone_from_slice(&COOKIE); let mut length: usize = 240; for option in &self.options { let option = option.to_raw(); p[length] = option.code; p[length + 1] = option.data.len() as u8; p[length + 2..length + 2 + option.data.len()].clone_from_slice(&option.data); length += 2 + option.data.len(); } p[length] = END; length += 1; if length < 272 { // Pad to min size p[length..272].clone_from_slice(&[PAD; 32][..272 - length]); length = 272 } &p[..length] } } const COOKIE: [u8; 4] = [99, 130, 83, 99]; const BOOT_REQUEST: u8 = 1; // From Client; const BOOT_REPLY: u8 = 2; // From Server; const END: u8 = 255; const PAD: u8 = 0; dhcp4r-0.2.0/src/server.rs010066400017500001750000000111211355307235200135640ustar0000000000000000use std::net::{UdpSocket, SocketAddr, Ipv4Addr, IpAddr}; use std; use std::cell::Cell; use crate::options::{DhcpOption, MessageType}; use crate::packet::*; use crate::options; ///! This is a convenience module that simplifies the writing of a DHCP server service. pub struct Server { out_buf: Cell<[u8; 1500]>, socket: UdpSocket, src: SocketAddr, server_ip: Ipv4Addr, } pub trait Handler { fn handle_request(&mut self, server: &Server, in_packet: Packet); } /// Orders and filters options based on PARAMETER_REQUEST_LIST received from client. /// DHCP_MESSAGE_TYPE and SERVER_IDENTIFIER are always first and always retained. /// This function is called by Reply. pub fn filter_options_by_req(opts: &mut Vec, req_params: &[u8]) { let mut pos = 0; let h = &[options::DHCP_MESSAGE_TYPE as u8, options::SERVER_IDENTIFIER as u8, options::IP_ADDRESS_LEASE_TIME as u8] as &[u8]; for z in [h, req_params].iter() { for r in z.iter() { let mut found = false; let mut at = 0; for (i, o) in opts[pos..].iter().enumerate() { if o.code() == *r { found = true; at = i + pos; break; } } if found { opts.swap(pos, at); pos = pos + 1; } } } opts.truncate(pos); } impl Server { pub fn serve(udp_soc: UdpSocket, server_ip: Ipv4Addr, mut handler: H) -> std::io::Error { let mut in_buf: [u8; 1500] = [0; 1500]; let mut s = Server { out_buf: Cell::new([0; 1500]), socket: udp_soc, server_ip: server_ip, src: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0), }; loop { match s.socket.recv_from(&mut in_buf) { Err(e) => return e, Ok((l, src)) => { if let Ok(p) = Packet::from(&in_buf[..l]) { s.src = src; handler.handle_request(&s, p); } } } } } /// Constructs and sends a reply packet back to the client. /// additional_options should not include DHCP_MESSAGE_TYPE nor SERVER_IDENTIFIER as these /// are added automatically. pub fn reply(&self, msg_type: MessageType, additional_options: Vec, offer_ip: Ipv4Addr, req_packet: Packet) -> std::io::Result { let ciaddr = match msg_type { MessageType::Nak => Ipv4Addr::new(0, 0, 0, 0), _ => req_packet.ciaddr, }; //let mt = &[msg_type as u8]; let mut opts: Vec = Vec::with_capacity(additional_options.len() + 2); opts.push(DhcpOption::DhcpMessageType(msg_type)); opts.push(DhcpOption::ServerIdentifier(self.server_ip)); /*opts.push(DhcpOption { code: options::DHCP_MESSAGE_TYPE, data: mt, }); opts.push(DhcpOption { code: options::SERVER_IDENTIFIER, data: &self.server_ip, });*/ opts.extend(additional_options); if let Some(DhcpOption::ParameterRequestList(prl)) = req_packet.option(options::PARAMETER_REQUEST_LIST) { filter_options_by_req(&mut opts, &prl); } self.send(Packet { reply: true, hops: 0, xid: req_packet.xid, secs: 0, broadcast: req_packet.broadcast, ciaddr: ciaddr, yiaddr: offer_ip, siaddr: Ipv4Addr::new(0, 0, 0, 0), giaddr: req_packet.giaddr, chaddr: req_packet.chaddr, options: opts, }) } /// Checks the packet see if it was intended for this DHCP server (as opposed to some other also on the network). pub fn for_this_server(&self, packet: &Packet) -> bool { match packet.option(options::SERVER_IDENTIFIER) { Some(DhcpOption::ServerIdentifier(x)) => (x == &self.server_ip), _ => false, } } /// Encodes and sends a DHCP packet back to the client. pub fn send(&self, p: Packet) -> std::io::Result { let mut addr = self.src; if p.broadcast || addr.ip() == IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) { addr.set_ip(IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255))); } self.socket.send_to(p.encode(&mut self.out_buf.get()), addr) } } dhcp4r-0.2.0/.cargo_vcs_info.json0000644000000001120000000000000122500ustar00{ "git": { "sha1": "f0eb6ce2b358d2734952832b506e7071298cb9ab" } } dhcp4r-0.2.0/Cargo.lock0000644000000226630000000000000102420ustar00# This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "arrayvec" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "autocfg" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "dhcp4r" version = "0.2.0" dependencies = [ "enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "enum-primitive-derive" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "lexical-core" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "libc" version = "0.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "memchr" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "nodrop" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "nom" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lexical-core 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "quote" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "redox_syscall" version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "rustc_version" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ryu" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "semver" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "static_assertions" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "syn" version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "synom" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "time" version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "unicode-xid" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "version_check" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" "checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b90e520ec62c1864c8c78d637acbfe8baf5f63240f2fb8165b8325c07812dd" "checksum lexical-core 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2304bccb228c4b020f3a4835d247df0a02a7c4686098d4167762cfbbe4c5cb14" "checksum libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)" = "74dfca3d9957906e8d1e6a0b641dc9a59848e793f1da2165889fd4f62d10d79c" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" "checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" "checksum nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c618b63422da4401283884e6668d39f819a106ef51f5f59b81add00075da35ca" "checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"