sergio/takana
> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…
crates/netup: red mínima Rust-nativa (Etapa C pieza 5)
1//! Netlink (NETLINK_ROUTE) mínimo y SÍNCRONO sobre libc: levantar un link, añadir una dirección IPv4 y
2//! una ruta por defecto. Hand-roll de los mensajes RTM_* (ABI estable del kernel) en vez de tirar de
3//! `rtnetlink` (async/tokio) — el workspace es 100% sync y así no agregamos deps que vendorar.
4
5use std::io;
6use std::mem::size_of;
7use std::os::unix::io::RawFd;
8
9// --- constantes de la ABI netlink (estables) ---
10const RTM_NEWLINK: u16 = 16;
11const RTM_NEWADDR: u16 = 20;
12const RTM_NEWROUTE: u16 = 24;
13const NLM_F_REQUEST: u16 = 0x001;
14const NLM_F_ACK: u16 = 0x004;
15const NLM_F_REPLACE: u16 = 0x100;
16const NLM_F_CREATE: u16 = 0x400;
17const NLMSG_ERROR: u16 = 0x2;
18const IFA_ADDRESS: u16 = 1;
19const IFA_LOCAL: u16 = 2;
20const RTA_OIF: u16 = 4;
21const RTA_GATEWAY: u16 = 5;
22const RT_SCOPE_UNIVERSE: u8 = 0;
23const RT_TABLE_MAIN: u8 = 254;
24const RTPROT_BOOT: u8 = 3;
25const RTN_UNICAST: u8 = 1;
26
27// libc trae nlmsghdr/nlmsgerr/ifinfomsg pero NO estos tres de rtnetlink; los definimos (layout ABI).
28#[repr(C)]
29struct Rtattr {
30 rta_len: u16,
31 rta_type: u16,
32}
33#[repr(C)]
34struct Ifaddrmsg {
35 ifa_family: u8,
36 ifa_prefixlen: u8,
37 ifa_flags: u8,
38 ifa_scope: u8,
39 ifa_index: u32,
40}
41#[repr(C)]
42struct Rtmsg {
43 rtm_family: u8,
44 rtm_dst_len: u8,
45 rtm_src_len: u8,
46 rtm_tos: u8,
47 rtm_table: u8,
48 rtm_protocol: u8,
49 rtm_scope: u8,
50 rtm_type: u8,
51 rtm_flags: u32,
52}
53
54fn nlmsg_align(len: usize) -> usize {
55 (len + 3) & !3
56}
57
58unsafe fn as_bytes<T>(p: &T) -> &[u8] {
59 std::slice::from_raw_parts(p as *const T as *const u8, size_of::<T>())
60}
61
62/// Socket netlink + número de secuencia para los requests.
63pub struct Netlink {
64 fd: RawFd,
65 seq: u32,
66}
67
68impl Drop for Netlink {
69 fn drop(&mut self) {
70 unsafe { libc::close(self.fd) };
71 }
72}
73
74impl Netlink {
75 pub fn open() -> io::Result<Self> {
76 let fd = unsafe {
77 libc::socket(
78 libc::AF_NETLINK,
79 libc::SOCK_RAW | libc::SOCK_CLOEXEC,
80 libc::NETLINK_ROUTE,
81 )
82 };
83 if fd < 0 {
84 return Err(io::Error::last_os_error());
85 }
86 let mut sa: libc::sockaddr_nl = unsafe { std::mem::zeroed() };
87 sa.nl_family = libc::AF_NETLINK as u16;
88 let r = unsafe {
89 libc::bind(
90 fd,
91 &sa as *const _ as *const libc::sockaddr,
92 size_of::<libc::sockaddr_nl>() as u32,
93 )
94 };
95 if r < 0 {
96 let e = io::Error::last_os_error();
97 unsafe { libc::close(fd) };
98 return Err(e);
99 }
100 Ok(Self { fd, seq: 0 })
101 }
102
103 /// Empaqueta nlmsghdr + cuerpo + atributos, lo manda al kernel y espera el ACK (NLMSG_ERROR con
104 /// error==0). `body` es el struct de familia (ifinfomsg/ifaddrmsg/rtmsg); `attrs` los rtattr.
105 fn request(&mut self, msg_type: u16, flags: u16, body: &[u8], attrs: &[(u16, Vec<u8>)]) -> io::Result<()> {
106 self.seq += 1;
107 let mut buf: Vec<u8> = Vec::with_capacity(256);
108 buf.resize(size_of::<libc::nlmsghdr>(), 0); // hueco para el header, se rellena al final
109 buf.extend_from_slice(body);
110 while buf.len() % 4 != 0 {
111 buf.push(0);
112 }
113 for (atype, data) in attrs {
114 let rta_len = (size_of::<Rtattr>() + data.len()) as u16;
115 let rta = Rtattr { rta_len, rta_type: *atype };
116 buf.extend_from_slice(unsafe { as_bytes(&rta) });
117 buf.extend_from_slice(data);
118 let pad = nlmsg_align(rta_len as usize) - rta_len as usize;
119 buf.extend(std::iter::repeat(0u8).take(pad));
120 }
121 let hdr = libc::nlmsghdr {
122 nlmsg_len: buf.len() as u32,
123 nlmsg_type: msg_type,
124 nlmsg_flags: flags,
125 nlmsg_seq: self.seq,
126 nlmsg_pid: 0,
127 };
128 buf[..size_of::<libc::nlmsghdr>()].copy_from_slice(unsafe { as_bytes(&hdr) });
129
130 let mut k: libc::sockaddr_nl = unsafe { std::mem::zeroed() };
131 k.nl_family = libc::AF_NETLINK as u16; // nl_pid 0 ⇒ el kernel
132 let sent = unsafe {
133 libc::sendto(
134 self.fd,
135 buf.as_ptr() as *const libc::c_void,
136 buf.len(),
137 0,
138 &k as *const _ as *const libc::sockaddr,
139 size_of::<libc::sockaddr_nl>() as u32,
140 )
141 };
142 if sent < 0 {
143 return Err(io::Error::last_os_error());
144 }
145 self.recv_ack()
146 }
147
148 fn recv_ack(&mut self) -> io::Result<()> {
149 let mut rbuf = [0u8; 8192];
150 let n = unsafe {
151 libc::recv(
152 self.fd,
153 rbuf.as_mut_ptr() as *mut libc::c_void,
154 rbuf.len(),
155 0,
156 )
157 };
158 if n < 0 {
159 return Err(io::Error::last_os_error());
160 }
161 let n = n as usize;
162 if n < size_of::<libc::nlmsghdr>() {
163 return Err(io::Error::new(io::ErrorKind::InvalidData, "respuesta netlink truncada"));
164 }
165 let hdr: libc::nlmsghdr = unsafe { std::ptr::read_unaligned(rbuf.as_ptr() as *const _) };
166 if hdr.nlmsg_type == NLMSG_ERROR {
167 // nlmsgerr.error: 0 = éxito, negativo = -errno.
168 let err: libc::nlmsgerr =
169 unsafe { std::ptr::read_unaligned(rbuf.as_ptr().add(size_of::<libc::nlmsghdr>()) as *const _) };
170 if err.error != 0 {
171 return Err(io::Error::from_raw_os_error(-err.error));
172 }
173 }
174 Ok(())
175 }
176
177 /// Levanta el link (equivalente a `ip link set <if> up`).
178 pub fn link_up(&mut self, ifindex: i32) -> io::Result<()> {
179 let mut ifi: libc::ifinfomsg = unsafe { std::mem::zeroed() };
180 ifi.ifi_family = libc::AF_UNSPEC as u8;
181 ifi.ifi_index = ifindex;
182 ifi.ifi_flags = libc::IFF_UP as u32;
183 ifi.ifi_change = libc::IFF_UP as u32;
184 self.request(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK, unsafe { as_bytes(&ifi) }, &[])
185 }
186
187 /// Añade una dirección IPv4 con su prefijo (equivalente a `ip addr add ip/prefix dev <if>`).
188 pub fn add_addr(&mut self, ifindex: i32, ip: [u8; 4], prefix: u8) -> io::Result<()> {
189 let mut ifa: Ifaddrmsg = unsafe { std::mem::zeroed() };
190 ifa.ifa_family = libc::AF_INET as u8;
191 ifa.ifa_prefixlen = prefix;
192 ifa.ifa_scope = RT_SCOPE_UNIVERSE;
193 ifa.ifa_index = ifindex as u32;
194 self.request(
195 RTM_NEWADDR,
196 NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_REPLACE,
197 unsafe { as_bytes(&ifa) },
198 &[(IFA_LOCAL, ip.to_vec()), (IFA_ADDRESS, ip.to_vec())],
199 )
200 }
201
202 /// Añade la ruta por defecto (equivalente a `ip route add default via <gw> dev <if>`).
203 pub fn add_default_route(&mut self, ifindex: i32, gw: [u8; 4]) -> io::Result<()> {
204 let mut rtm: Rtmsg = unsafe { std::mem::zeroed() };
205 rtm.rtm_family = libc::AF_INET as u8;
206 rtm.rtm_table = RT_TABLE_MAIN;
207 rtm.rtm_protocol = RTPROT_BOOT;
208 rtm.rtm_scope = RT_SCOPE_UNIVERSE;
209 rtm.rtm_type = RTN_UNICAST;
210 self.request(
211 RTM_NEWROUTE,
212 NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE,
213 unsafe { as_bytes(&rtm) },
214 &[(RTA_GATEWAY, gw.to_vec()), (RTA_OIF, (ifindex as u32).to_ne_bytes().to_vec())],
215 )
216 }
217}