|
5 | 5 | // Copyright 2026 Oxide Computer Company |
6 | 6 |
|
7 | 7 | use crate::packet::{Icmp6RouterAdvertisement, Icmp6RouterSolicitation}; |
8 | | -use libc::{c_int, socklen_t}; |
9 | 8 | use slog::{Logger, error}; |
10 | 9 | use socket2::{Domain, Protocol, Socket, Type}; |
11 | 10 | use std::{ |
12 | | - ffi::c_void, |
13 | 11 | net::{Ipv6Addr, SocketAddrV6}, |
14 | | - os::fd::AsRawFd, |
| 12 | + num::NonZeroU32, |
15 | 13 | thread::sleep, |
16 | 14 | time::{Duration, Instant}, |
17 | 15 | }; |
@@ -70,8 +68,15 @@ pub enum ListeningSocketError { |
70 | 68 | #[error("set read timeout error: {0}")] |
71 | 69 | SetReadTimeoutError(std::io::Error), |
72 | 70 |
|
| 71 | + #[cfg(any(target_os = "linux", target_os = "illumos"))] |
73 | 72 | #[error("failed to set ipv6 min hop count: {0}")] |
74 | 73 | SetIpv6MinHopCount(std::io::Error), |
| 74 | + |
| 75 | + #[error("failed to bind socket to interface: {0}")] |
| 76 | + SetBoundIf(std::io::Error), |
| 77 | + |
| 78 | + #[error("interface index must be non-zero")] |
| 79 | + InvalidInterfaceIndex, |
75 | 80 | } |
76 | 81 |
|
77 | 82 | pub fn send_ra( |
@@ -205,28 +210,52 @@ pub fn create_socket(index: u32) -> Result<Socket, ListeningSocketError> { |
205 | 210 | s.join_multicast_v6(&ALL_ROUTERS_MCAST, index) |
206 | 211 | .map_err(E::JoinAllRoutersMulticast)?; |
207 | 212 |
|
| 213 | + let ifindex = NonZeroU32::new(index).ok_or(E::InvalidInterfaceIndex)?; |
| 214 | + s.bind_device_by_index_v6(Some(ifindex)) |
| 215 | + .map_err(E::SetBoundIf)?; |
| 216 | + |
208 | 217 | s.bind(&sa).map_err(ListeningSocketError::Bind)?; |
209 | 218 |
|
210 | 219 | s.set_read_timeout(Some(READ_TIMEOUT)) |
211 | 220 | .map_err(E::SetReadTimeoutError)?; |
212 | 221 |
|
213 | | - unsafe { |
214 | | - // from <netinet/in.h> |
215 | | - const IPV6_MINHOPCOUNT: c_int = 0x2f; |
216 | | - let min_hops: c_int = 255; |
217 | | - let rc = libc::setsockopt( |
| 222 | + // Per RFC 4861 Section 6.1.2: a valid RA must have a hop limit of 255 |
| 223 | + set_min_hopcount(&s)?; |
| 224 | + |
| 225 | + Ok(s) |
| 226 | +} |
| 227 | + |
| 228 | +#[cfg(any(target_os = "linux", target_os = "illumos"))] |
| 229 | +fn set_min_hopcount(s: &Socket) -> Result<(), ListeningSocketError> { |
| 230 | + use std::os::fd::AsRawFd; |
| 231 | + |
| 232 | + // illumos does not export IPV6_MINHOPCOUNT via libc; value from |
| 233 | + // <netinet/in.h>. Linux provides it. |
| 234 | + #[cfg(target_os = "illumos")] |
| 235 | + const IPV6_MINHOPCOUNT: libc::c_int = 0x2f; |
| 236 | + #[cfg(target_os = "linux")] |
| 237 | + use libc::IPV6_MINHOPCOUNT; |
| 238 | + |
| 239 | + let min_hops: libc::c_int = 255; |
| 240 | + // SAFETY: setsockopt with a correctly-sized pointer to an integer option. |
| 241 | + let rc = unsafe { |
| 242 | + libc::setsockopt( |
218 | 243 | s.as_raw_fd(), |
219 | 244 | libc::IPPROTO_IPV6, |
220 | 245 | IPV6_MINHOPCOUNT, |
221 | | - &min_hops as *const _ as *const c_void, |
222 | | - std::mem::size_of::<libc::c_int>() as socklen_t, |
223 | | - ); |
224 | | - if rc < 0 { |
225 | | - return Err(ListeningSocketError::SetIpv6MinHopCount( |
226 | | - std::io::Error::last_os_error(), |
227 | | - )); |
228 | | - } |
| 246 | + &min_hops as *const _ as *const libc::c_void, |
| 247 | + std::mem::size_of::<libc::c_int>() as libc::socklen_t, |
| 248 | + ) |
| 249 | + }; |
| 250 | + if rc < 0 { |
| 251 | + return Err(ListeningSocketError::SetIpv6MinHopCount( |
| 252 | + std::io::Error::last_os_error(), |
| 253 | + )); |
229 | 254 | } |
| 255 | + Ok(()) |
| 256 | +} |
230 | 257 |
|
231 | | - Ok(s) |
| 258 | +#[cfg(not(any(target_os = "linux", target_os = "illumos")))] |
| 259 | +fn set_min_hopcount(_s: &Socket) -> Result<(), ListeningSocketError> { |
| 260 | + Ok(()) |
232 | 261 | } |
0 commit comments