1#![allow(unreachable_patterns)]
2
3use std::io::{self, ErrorKind};
4use std::net::IpAddr;
5
6use ftth_common::channel::{AsyncWorldClient, AsyncWorldServer};
7use log::warn;
8use netlink_packet_route::neighbour::{NeighbourAddress, NeighbourAttribute, NeighbourMessage};
9use netlink_packet_route::{AddressFamily, route::RouteType};
10
11pub use netlink_packet_route::neighbour::{NeighbourFlags, NeighbourState};
12
13pub(crate) type Client = AsyncWorldClient<RtnlNeighborRequest, RtnlNeighborResponse>;
14pub(crate) type Server = AsyncWorldServer<RtnlNeighborRequest, RtnlNeighborResponse>;
15
16#[derive(Debug, Clone, PartialEq)]
17pub struct NeighborEntry {
18 pub if_id: u32,
19 pub destination: IpAddr,
20 pub link_address: Option<Vec<u8>>,
21 pub state: Option<NeighbourState>,
22 pub flags: Option<NeighbourFlags>,
23}
24
25#[derive(Debug, Clone, PartialEq)]
26pub struct NeighborDelete {
27 pub if_id: u32,
28 pub destination: IpAddr,
29 pub link_address: Option<Vec<u8>>,
30 pub state: Option<NeighbourState>,
31 pub flags: Option<NeighbourFlags>,
32}
33
34#[derive(Debug, Clone, PartialEq)]
35#[non_exhaustive]
36pub enum RtnlNeighborRequest {
37 Add(NeighborEntry),
38 Change(NeighborEntry),
39 Delete(NeighborDelete),
40}
41
42#[derive(Debug, Clone, PartialEq)]
43#[non_exhaustive]
44pub enum RtnlNeighborResponse {
45 Success,
46 Failed,
47 NotImplemented,
48 NotFound,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Hash)]
52pub struct RtnlNeighborClient {
53 client: Client,
54}
55
56impl RtnlNeighborClient {
57 pub(crate) fn new(client: Client) -> Self {
58 Self { client }
59 }
60
61 pub fn add(&self, entry: NeighborEntry) -> io::Result<()> {
62 let res = self.client.send_request(RtnlNeighborRequest::Add(entry))?;
63 handle_neighbor_response("Neighbor add", res, false)
64 }
65
66 pub fn change(&self, entry: NeighborEntry) -> io::Result<()> {
67 let res = self
68 .client
69 .send_request(RtnlNeighborRequest::Change(entry))?;
70 handle_neighbor_response("Neighbor change", res, false)
71 }
72
73 pub fn delete(&self, entry: NeighborDelete) -> io::Result<()> {
74 let res = self
75 .client
76 .send_request(RtnlNeighborRequest::Delete(entry))?;
77 handle_neighbor_response("Neighbor delete", res, false)
78 }
79}
80
81pub(crate) async fn run_server(mut server: Server, handle: rtnetlink::NeighbourHandle) {
82 while let Some((req, respond)) = server.accept().await {
83 let response = match req {
84 RtnlNeighborRequest::Add(entry) => add_or_change_neighbor(&handle, entry, false).await,
85 RtnlNeighborRequest::Change(entry) => {
86 add_or_change_neighbor(&handle, entry, true).await
87 }
88 RtnlNeighborRequest::Delete(entry) => delete_neighbor(&handle, entry).await,
89 };
90 respond(response);
91 }
92}
93
94fn handle_neighbor_response(
95 operation: &str,
96 response: RtnlNeighborResponse,
97 allow_not_found: bool,
98) -> io::Result<()> {
99 match response {
100 RtnlNeighborResponse::Success => Ok(()),
101 RtnlNeighborResponse::NotFound if allow_not_found => Ok(()),
102 RtnlNeighborResponse::NotFound => Err(io::Error::new(
103 ErrorKind::NotFound,
104 format!("{}: entry not found", operation),
105 )),
106 RtnlNeighborResponse::Failed => Err(io::Error::other(format!("{} failed", operation))),
107 RtnlNeighborResponse::NotImplemented => Err(io::Error::new(
108 ErrorKind::Unsupported,
109 format!("{} is not implemented", operation),
110 )),
111 }
112}
113
114async fn add_or_change_neighbor(
115 handle: &rtnetlink::NeighbourHandle,
116 entry: NeighborEntry,
117 replace: bool,
118) -> RtnlNeighborResponse {
119 let mut request = handle.add(entry.if_id, entry.destination);
120
121 if let Some(ref link_address) = entry.link_address {
122 request = request.link_local_address(link_address);
123 }
124
125 if let Some(state) = entry.state {
126 request = request.state(state);
127 }
128
129 if let Some(flags) = entry.flags {
130 request = request.flags(flags);
131 }
132
133 if replace {
134 request = request.replace();
135 }
136
137 match request.execute().await {
138 Ok(()) => RtnlNeighborResponse::Success,
139 Err(rtnetlink::Error::NetlinkError(err_msg)) => {
140 let io_err = err_msg.to_io();
141 match io_err.kind() {
142 ErrorKind::NotFound => RtnlNeighborResponse::NotFound,
143 ErrorKind::AlreadyExists => {
144 warn!("Neighbor operation failed (already exists): {}", io_err);
145 RtnlNeighborResponse::Failed
146 }
147 _ => {
148 warn!("Neighbor operation failed: {}", io_err);
149 RtnlNeighborResponse::Failed
150 }
151 }
152 }
153 Err(err) => {
154 warn!("Neighbor operation failed: {}", err);
155 RtnlNeighborResponse::Failed
156 }
157 }
158}
159
160async fn delete_neighbor(
161 handle: &rtnetlink::NeighbourHandle,
162 entry: NeighborDelete,
163) -> RtnlNeighborResponse {
164 let message = build_delete_message(&entry);
165 match handle.del(message).execute().await {
166 Ok(()) => RtnlNeighborResponse::Success,
167 Err(rtnetlink::Error::NetlinkError(err_msg)) => {
168 let io_err = err_msg.to_io();
169 match io_err.kind() {
170 ErrorKind::NotFound => RtnlNeighborResponse::NotFound,
171 _ => {
172 warn!("Neighbor delete failed: {}", io_err);
173 RtnlNeighborResponse::Failed
174 }
175 }
176 }
177 Err(err) => {
178 warn!("Neighbor delete failed: {}", err);
179 RtnlNeighborResponse::Failed
180 }
181 }
182}
183
184fn build_delete_message(entry: &NeighborDelete) -> NeighbourMessage {
185 let mut message = NeighbourMessage::default();
186 message.header.family = match entry.destination {
187 IpAddr::V4(_) => AddressFamily::Inet,
188 IpAddr::V6(_) => AddressFamily::Inet6,
189 };
190 message.header.ifindex = entry.if_id;
191 message.header.kind = RouteType::Unspec;
192
193 if let Some(state) = entry.state {
194 message.header.state = state;
195 }
196
197 if let Some(flags) = entry.flags {
198 message.header.flags = flags;
199 }
200
201 let destination = match entry.destination {
202 IpAddr::V4(addr) => NeighbourAddress::Inet(addr),
203 IpAddr::V6(addr) => NeighbourAddress::Inet6(addr),
204 };
205
206 message
207 .attributes
208 .push(NeighbourAttribute::Destination(destination));
209
210 if let Some(ref link_address) = entry.link_address {
211 message
212 .attributes
213 .push(NeighbourAttribute::LinkLocalAddress(link_address.clone()));
214 }
215
216 message
217}