Эх сурвалжийг харах

Merge #738

738: Allow specifying different server/client DHCP ports r=Dirbaio a=Czocher

The default linux dhclient utility allows to use DHCP with non-standard ports. This change adds support for those usecases.

Co-authored-by: Paweł Jan Czochański <pawel@czochanski.pl>
bors[bot] 2 жил өмнө
parent
commit
7b631c27ce

+ 7 - 7
src/iface/interface/ipv4.rs

@@ -90,15 +90,15 @@ impl<'a> InterfaceInner<'a> {
         #[cfg(feature = "socket-dhcpv4")]
         {
             if ipv4_repr.next_header == IpProtocol::Udp && self.hardware_addr.is_some() {
-                // First check for source and dest ports, then do `UdpRepr::parse` if they match.
-                // This way we avoid validating the UDP checksum twice for all non-DHCP UDP packets (one here, one in `process_udp`)
                 let udp_packet = check!(UdpPacket::new_checked(ip_payload));
-                if udp_packet.src_port() == DHCP_SERVER_PORT
-                    && udp_packet.dst_port() == DHCP_CLIENT_PORT
+                if let Some(dhcp_socket) = sockets
+                    .items_mut()
+                    .find_map(|i| dhcpv4::Socket::downcast_mut(&mut i.socket))
                 {
-                    if let Some(dhcp_socket) = sockets
-                        .items_mut()
-                        .find_map(|i| dhcpv4::Socket::downcast_mut(&mut i.socket))
+                    // First check for source and dest ports, then do `UdpRepr::parse` if they match.
+                    // This way we avoid validating the UDP checksum twice for all non-DHCP UDP packets (one here, one in `process_udp`)
+                    if udp_packet.src_port() == dhcp_socket.server_port
+                        && udp_packet.dst_port() == dhcp_socket.client_port
                     {
                         let (src_addr, dst_addr) = (ip_repr.src_addr(), ip_repr.dst_addr());
                         let udp_repr = check!(UdpRepr::parse(

+ 82 - 7
src/socket/dhcpv4.rs

@@ -149,6 +149,12 @@ pub struct Socket<'a> {
     /// Ignore NAKs.
     ignore_naks: bool,
 
+    /// Server port config
+    pub(crate) server_port: u16,
+
+    /// Client port config
+    pub(crate) client_port: u16,
+
     /// A buffer contains options additional to be added to outgoing DHCP
     /// packets.
     outgoing_options: &'a [DhcpOption<'a>],
@@ -186,6 +192,8 @@ impl<'a> Socket<'a> {
             receive_packet_buffer: None,
             #[cfg(feature = "async")]
             waker: WakerRegistration::new(),
+            server_port: DHCP_SERVER_PORT,
+            client_port: DHCP_CLIENT_PORT,
         }
     }
 
@@ -247,6 +255,15 @@ impl<'a> Socket<'a> {
         self.ignore_naks = ignore_naks;
     }
 
+    /// Set the server/client port
+    ///
+    /// Allows you to specify the ports used by DHCP.
+    /// This is meant to support esoteric usecases allowed by the dhclient program.
+    pub fn set_ports(&mut self, server_port: u16, client_port: u16) {
+        self.server_port = server_port;
+        self.client_port = client_port;
+    }
+
     pub(crate) fn poll_at(&self, _cx: &mut Context) -> PollAt {
         let t = match &self.state {
             ClientState::Discovering(state) => state.retry_at,
@@ -266,7 +283,7 @@ impl<'a> Socket<'a> {
         let src_ip = ip_repr.src_addr;
 
         // This is enforced in interface.rs.
-        assert!(repr.src_port == DHCP_SERVER_PORT && repr.dst_port == DHCP_CLIENT_PORT);
+        assert!(repr.src_port == self.server_port && repr.dst_port == self.client_port);
 
         let dhcp_packet = match DhcpPacket::new_checked(payload) {
             Ok(dhcp_packet) => dhcp_packet,
@@ -528,8 +545,8 @@ impl<'a> Socket<'a> {
         };
 
         let udp_repr = UdpRepr {
-            src_port: DHCP_CLIENT_PORT,
-            dst_port: DHCP_SERVER_PORT,
+            src_port: self.client_port,
+            dst_port: self.server_port,
         };
 
         let mut ipv4_repr = Ipv4Repr {
@@ -842,12 +859,24 @@ mod test {
     };
 
     const UDP_SEND: UdpRepr = UdpRepr {
-        src_port: 68,
-        dst_port: 67,
+        src_port: DHCP_CLIENT_PORT,
+        dst_port: DHCP_SERVER_PORT,
     };
     const UDP_RECV: UdpRepr = UdpRepr {
-        src_port: 67,
-        dst_port: 68,
+        src_port: DHCP_SERVER_PORT,
+        dst_port: DHCP_CLIENT_PORT,
+    };
+
+    const DIFFERENT_CLIENT_PORT: u16 = 6800;
+    const DIFFERENT_SERVER_PORT: u16 = 6700;
+
+    const UDP_SEND_DIFFERENT_PORT: UdpRepr = UdpRepr {
+        src_port: DIFFERENT_CLIENT_PORT,
+        dst_port: DIFFERENT_SERVER_PORT,
+    };
+    const UDP_RECV_DIFFERENT_PORT: UdpRepr = UdpRepr {
+        src_port: DIFFERENT_SERVER_PORT,
+        dst_port: DIFFERENT_CLIENT_PORT,
     };
 
     const DHCP_DEFAULT: DhcpRepr = DhcpRepr {
@@ -956,6 +985,17 @@ mod test {
         }
     }
 
+    fn socket_different_port() -> TestSocket {
+        let mut s = Socket::new();
+        s.set_ports(DIFFERENT_SERVER_PORT, DIFFERENT_CLIENT_PORT);
+
+        assert_eq!(s.poll(), Some(Event::Deconfigured));
+        TestSocket {
+            socket: s,
+            cx: Context::mock(),
+        }
+    }
+
     fn socket_bound() -> TestSocket {
         let mut s = socket();
         s.state = ClientState::Renewing(RenewState {
@@ -1011,6 +1051,41 @@ mod test {
         }
     }
 
+    #[test]
+    fn test_bind_different_ports() {
+        let mut s = socket_different_port();
+
+        recv!(s, [(IP_BROADCAST, UDP_SEND_DIFFERENT_PORT, DHCP_DISCOVER)]);
+        assert_eq!(s.poll(), None);
+        send!(s, (IP_RECV, UDP_RECV_DIFFERENT_PORT, dhcp_offer()));
+        assert_eq!(s.poll(), None);
+        recv!(s, [(IP_BROADCAST, UDP_SEND_DIFFERENT_PORT, DHCP_REQUEST)]);
+        assert_eq!(s.poll(), None);
+        send!(s, (IP_RECV, UDP_RECV_DIFFERENT_PORT, dhcp_ack()));
+
+        assert_eq!(
+            s.poll(),
+            Some(Event::Configured(Config {
+                server: ServerInfo {
+                    address: SERVER_IP,
+                    identifier: SERVER_IP,
+                },
+                address: Ipv4Cidr::new(MY_IP, 24),
+                dns_servers: Vec::from_slice(DNS_IPS).unwrap(),
+                router: Some(SERVER_IP),
+                packet: None,
+            }))
+        );
+
+        match &s.state {
+            ClientState::Renewing(r) => {
+                assert_eq!(r.renew_at, Instant::from_secs(500));
+                assert_eq!(r.expires_at, Instant::from_secs(1000));
+            }
+            _ => panic!("Invalid state"),
+        }
+    }
+
     #[test]
     fn test_discover_retransmit() {
         let mut s = socket();