11use std:: fs;
22use std:: io:: Write ;
3- use std:: os :: unix :: process :: ExitStatusExt as _ ;
4- use std:: process :: Command ;
3+ use std:: net :: { UdpSocket , Ipv4Addr } ;
4+ use std:: time :: Duration ;
55
6- use anyhow:: { anyhow, Context , Result } ;
7- use log:: debug;
6+ use anyhow:: { Context , Result } ;
87use rustix:: system:: sethostname;
98
10- use crate :: utils:: env:: find_in_path;
11- use crate :: utils:: fs:: find_executable;
9+ use neli:: {
10+ consts:: {
11+ nl:: NlmF ,
12+ rtnl:: { Arphrd , Ifa , IfaF , Iff , Rta , RtAddrFamily , Rtm , RtmF , Rtn ,
13+ Rtprot , RtScope , RtTable } ,
14+ socket:: NlFamily ,
15+ } ,
16+ nl:: { NlPayload , Nlmsghdr } ,
17+ router:: synchronous:: { NlRouter , NlRouterReceiverHandle } ,
18+ rtnl:: { Ifaddrmsg , IfaddrmsgBuilder , Ifinfomsg , IfinfomsgBuilder ,
19+ RtattrBuilder , Rtmsg , RtmsgBuilder } ,
20+ utils:: Groups ,
21+ types:: RtBuffer ,
22+ } ;
23+
24+ /// Set interface flags for eth0 (interface index 2) with a given mask
25+ fn flags_eth0 ( rtnl : & NlRouter , mask : Iff , set : Iff ) -> Result < ( ) > {
26+ let ifinfomsg = IfinfomsgBuilder :: default ( )
27+ . ifi_family ( RtAddrFamily :: Unspecified )
28+ . ifi_type ( Arphrd :: Ether ) . ifi_index ( 2 )
29+ . ifi_change ( mask) . ifi_flags ( set)
30+ . build ( ) ?;
31+
32+ let _: NlRouterReceiverHandle < Rtm , Ifinfomsg > =
33+ rtnl. send ( Rtm :: Newlink , NlmF :: REQUEST , NlPayload :: Payload ( ifinfomsg) ) ?;
34+
35+ Ok ( ( ) )
36+ }
37+
38+ /// Add or delete IPv4 routes for eth0 (interface index 2)
39+ fn route4_eth0 ( rtnl : & NlRouter , what : Rtm , gw : Ipv4Addr ) -> Result < ( ) > {
40+ let rtmsg = RtmsgBuilder :: default ( )
41+ . rtm_family ( RtAddrFamily :: Inet )
42+ . rtm_dst_len ( 0 ) . rtm_src_len ( 0 ) . rtm_tos ( 0 )
43+ . rtm_table ( RtTable :: Main ) . rtm_protocol ( Rtprot :: Boot )
44+ . rtm_scope ( RtScope :: Universe ) . rtm_type ( Rtn :: Unicast )
45+ . rtm_flags ( RtmF :: empty ( ) )
46+ . rtattrs ( RtBuffer :: from_iter ( [
47+ RtattrBuilder :: default ( )
48+ . rta_type ( Rta :: Oif )
49+ . rta_payload ( 2 )
50+ . build ( ) ?,
51+ RtattrBuilder :: default ( )
52+ . rta_type ( Rta :: Dst )
53+ . rta_payload ( Ipv4Addr :: UNSPECIFIED . octets ( ) . to_vec ( ) )
54+ . build ( ) ?,
55+ RtattrBuilder :: default ( )
56+ . rta_type ( Rta :: Gateway )
57+ . rta_payload ( gw. octets ( ) . to_vec ( ) )
58+ . build ( ) ?
59+ ] ) )
60+ . build ( ) ?;
61+
62+ let _: NlRouterReceiverHandle < Rtm , Rtmsg > =
63+ rtnl. send ( what, NlmF :: CREATE | NlmF :: REQUEST ,
64+ NlPayload :: Payload ( rtmsg) ) ?;
65+
66+ Ok ( ( ) )
67+ }
68+
69+ /// Add or delete IPv4 addresses for eth0 (interface index 2)
70+ fn addr4_eth0 ( rtnl : & NlRouter , what : Rtm , addr : Ipv4Addr , prefix_len : u8 )
71+ -> Result < ( ) > {
72+ let ifaddrmsg = IfaddrmsgBuilder :: default ( )
73+ . ifa_family ( RtAddrFamily :: Inet )
74+ . ifa_prefixlen ( prefix_len)
75+ . ifa_scope ( RtScope :: Universe )
76+ . ifa_index ( 2 )
77+ . rtattrs ( RtBuffer :: from_iter ( [
78+ RtattrBuilder :: default ( )
79+ . rta_type ( Ifa :: Local )
80+ . rta_payload ( addr. octets ( ) . to_vec ( ) )
81+ . build ( ) ?,
82+ RtattrBuilder :: default ( )
83+ . rta_type ( Ifa :: Address )
84+ . rta_payload ( addr. octets ( ) . to_vec ( ) )
85+ . build ( ) ?,
86+ ] ) )
87+ . build ( ) ?;
88+
89+ let _: NlRouterReceiverHandle < Rtm , Ifaddrmsg > =
90+ rtnl. send ( what, NlmF :: CREATE | NlmF :: REQUEST ,
91+ NlPayload :: Payload ( ifaddrmsg) ) ?;
92+
93+ Ok ( ( ) )
94+ }
1295
1396pub fn configure_network ( ) -> Result < ( ) > {
1497 // Allow unprivileged users to use ping, as most distros do by default.
@@ -33,63 +116,135 @@ pub fn configure_network() -> Result<()> {
33116 sethostname ( hostname. as_bytes ( ) ) . context ( "Failed to set hostname" ) ?;
34117 }
35118
36- let dhcpcd_path = find_in_path ( "dhcpcd" ) . context ( "Failed to check existence of `dhcpcd`" ) ?;
37- let dhcpcd_path = if let Some ( dhcpcd_path) = dhcpcd_path {
38- Some ( dhcpcd_path)
39- } else {
40- find_executable ( "/sbin/dhcpcd" ) . context ( "Failed to check existence of `/sbin/dhcpcd`" ) ?
41- } ;
42- if let Some ( dhcpcd_path) = dhcpcd_path {
43- let output = Command :: new ( dhcpcd_path)
44- . args ( [ "-M" , "--nodev" , "eth0" ] )
45- . output ( )
46- . context ( "Failed to execute `dhcpcd` as child process" ) ?;
47- debug ! ( output: ?; "dhcpcd output" ) ;
48- if !output. status . success ( ) {
49- let err = if let Some ( code) = output. status . code ( ) {
50- anyhow ! ( "`dhcpcd` process exited with status code: {code}" )
51- } else {
52- anyhow ! (
53- "`dhcpcd` process terminated by signal: {}" ,
54- output
55- . status
56- . signal( )
57- . expect( "either one of status code or signal should be set" )
58- )
59- } ;
60- Err ( err) ?;
61- }
119+ let ( rtnl, _) = NlRouter :: connect ( NlFamily :: Route , None , Groups :: empty ( ) ) ?;
120+ rtnl. enable_strict_checking ( true ) ?;
62121
63- return Ok ( ( ) ) ;
122+ // Disable neighbour solicitations (dodge DAD), bring up link to start SLAAC
123+ {
124+ // IFF_NOARP | IFF_UP in one shot delays router solicitations, avoid it
125+ flags_eth0 ( & rtnl, Iff :: NOARP , Iff :: NOARP ) ?;
126+ flags_eth0 ( & rtnl, Iff :: UP , Iff :: UP ) ?;
64127 }
65128
66- let dhclient_path =
67- find_in_path ( "dhclient" ) . context ( "Failed to check existence of `dhclient`" ) ?;
68- let dhclient_path = if let Some ( dhclient_path) = dhclient_path {
69- Some ( dhclient_path)
70- } else {
71- find_executable ( "/sbin/dhclient" )
72- . context ( "Failed to check existence of `/sbin/dhclient`" ) ?
73- } ;
74- let dhclient_path =
75- dhclient_path. ok_or_else ( || anyhow ! ( "could not find required `dhcpcd` or `dhclient`" ) ) ?;
76- let output = Command :: new ( dhclient_path)
77- . output ( )
78- . context ( "Failed to execute `dhclient` as child process" ) ?;
79- debug ! ( output: ?; "dhclient output" ) ;
80- if !output. status . success ( ) {
81- let err = if let Some ( code) = output. status . code ( ) {
82- anyhow ! ( "`dhclient` process exited with status code: {code}" )
129+ // Configure IPv4 using DHCP with Rapid Commit (DISCOVER -> ACK)
130+ {
131+ // Temporary link-local address and route avoid the need for raw sockets
132+ route4_eth0 ( & rtnl, Rtm :: Newroute , Ipv4Addr :: UNSPECIFIED ) ?;
133+ addr4_eth0 ( & rtnl, Rtm :: Newaddr , Ipv4Addr :: new ( 169 , 254 , 1 , 1 ) , 16 ) ?;
134+
135+ // Send request (DHCPDISCOVER)
136+ let socket = UdpSocket :: bind ( "0.0.0.0:68" ) . expect ( "Failed to bind" ) ;
137+ let mut buf = [ 0 ; 576 /* RFC 2131, Section 2 */ ] ;
138+
139+ const REQUEST : [ u8 ; 300 /* From RFC 951: >= 60 B of options */ ] = [
140+ 1 /* REQUEST */ , 0x1 /* Ethernet */ , 6 /* hlen */ , 0 /* Hops */ ,
141+ 1 , 2 , 3 , 4 /* XID */ , 0 , 0 /* Seconds */ , 0x80 , 0x0 /* Flags */ ,
142+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , /* All-zero (four) addresses */
143+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , /* 16B HW address: who cares */
144+ /* 32 bytes per row: 64B 'sname', plus 128B 'file' (RFC 1531) */
145+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
146+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
147+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
148+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
149+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
150+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
151+ 0x63 , 0x82 , 0x53 , 0x63 , /* DHCP (magic) cookie, then options: */
152+ 53 , 1 , 1 /* DISCOVER */ , 80 , 0 /* Rapid commit */ , 0xff , // Done
153+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
154+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 /* 54B paaaadding */
155+ ] ;
156+
157+ socket. set_broadcast ( true ) ?;
158+ socket. send_to ( & REQUEST , "255.255.255.255:67" ) ?;
159+
160+ // Keep IPv6-only fast
161+ let _ = socket. set_read_timeout ( Some ( Duration :: from_millis ( 100 ) ) ) ;
162+
163+ // Get and process response (DHCPACK) if any
164+ if let Ok ( ( len, _) ) = socket. recv_from ( & mut buf) {
165+ let msg = & mut buf[ ..len] ;
166+
167+ let addr = Ipv4Addr :: new ( msg[ 16 ] , msg[ 17 ] , msg[ 18 ] , msg[ 19 ] ) ;
168+ let mut netmask = Ipv4Addr :: UNSPECIFIED ;
169+ let mut router = Ipv4Addr :: UNSPECIFIED ;
170+ let mut p: usize = 240 ;
171+
172+ while p < len {
173+ let o = msg[ p] ;
174+ let mut l: u8 ;
175+ l = msg[ p + 1 ] ;
176+
177+ if o == 1 { // Option 1: Subnet Mask
178+ netmask = Ipv4Addr :: new ( msg[ p + 2 ] , msg[ p + 3 ] ,
179+ msg[ p + 4 ] , msg[ p + 5 ] ) ;
180+ } else if o == 3 { // Option 3: Router
181+ router = Ipv4Addr :: new ( msg[ p + 2 ] , msg[ p + 3 ] ,
182+ msg[ p + 4 ] , msg[ p + 5 ] ) ;
183+ } else if o == 0xff { // Option 255: End (of options)
184+ break ;
185+ }
186+
187+ l += 2 ; // Length doesn't include code and length field itself
188+ p += l as usize ;
189+ }
190+
191+ let prefix_len : u8 = netmask. to_bits ( ) . leading_ones ( ) as u8 ;
192+
193+ // Drop temporary address and route, configure what we got instead
194+ route4_eth0 ( & rtnl, Rtm :: Delroute , Ipv4Addr :: UNSPECIFIED ) ?;
195+ addr4_eth0 ( & rtnl, Rtm :: Deladdr , Ipv4Addr :: new ( 169 , 254 , 1 , 1 ) , 16 ) ?;
196+
197+ addr4_eth0 ( & rtnl, Rtm :: Newaddr , addr, prefix_len) ?;
198+ route4_eth0 ( & rtnl, Rtm :: Newroute , router) ?;
83199 } else {
84- anyhow ! (
85- "`dhclient` process terminated by signal: {}" ,
86- output
87- . status
88- . signal( )
89- . expect( "either one of status code or signal should be set" )
90- )
91- } ;
92- Err ( err) ?;
200+ // Clean up: we're clearly too cool for IPv4
201+ route4_eth0 ( & rtnl, Rtm :: Delroute , Ipv4Addr :: UNSPECIFIED ) ?;
202+ addr4_eth0 ( & rtnl, Rtm :: Deladdr , Ipv4Addr :: new ( 169 , 254 , 1 , 1 ) , 16 ) ?;
203+ }
204+ }
205+
206+ // Wait for SLAAC to complete or fail: we're done only once network is ready
207+ {
208+ let mut global_seen = false ;
209+ let mut global_wait = true ;
210+ let mut ll_seen = false ;
211+
212+ // Busy-netlink-loop until we see a link-local address, and a global
213+ // unicast address as long as we might expect one (see below)
214+ while !ll_seen || ( global_wait && !global_seen) {
215+ let ifaddrmsg = IfaddrmsgBuilder :: default ( )
216+ . ifa_family ( RtAddrFamily :: Inet6 )
217+ . ifa_prefixlen ( 0 )
218+ . ifa_scope ( RtScope :: Universe )
219+ . ifa_index ( 2 )
220+ . build ( ) ?;
221+
222+ let recv = rtnl. send ( Rtm :: Getaddr , NlmF :: ROOT ,
223+ NlPayload :: Payload ( ifaddrmsg) ) ?;
224+
225+ for response in recv {
226+ let header: Nlmsghdr < Rtm , Ifaddrmsg > = response?;
227+ if let NlPayload :: Payload ( p) = header. nl_payload ( ) {
228+ if p. ifa_scope ( ) == & RtScope :: Link {
229+ // A non-tentative link-local address implies we sent a
230+ // router solicitation that didn't get any response
231+ // (IPv4-only)? Stop waiting for the router in that case
232+ if * p. ifa_flags ( ) & IfaF :: TENTATIVE != IfaF :: TENTATIVE {
233+ global_wait = false ;
234+ }
235+
236+ ll_seen = true ;
237+ } else if p. ifa_scope ( ) == & RtScope :: Universe {
238+ global_seen = true ;
239+ }
240+ }
241+ }
242+ }
243+ }
244+
245+ // Re-enable neighbour solicitations and ARP requests
246+ {
247+ flags_eth0 ( & rtnl, Iff :: NOARP , Iff :: empty ( ) ) ?;
93248 }
94249
95250 Ok ( ( ) )
0 commit comments