Skip to content

Commit 137984e

Browse files
committed
use nftables to write routing rules
1 parent 5067c9b commit 137984e

1 file changed

Lines changed: 113 additions & 21 deletions

File tree

odorobo-agent/src/networking/actor.rs

Lines changed: 113 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::networking::messages::{AttachTap, DetachTap};
33
use futures_util::{StreamExt, TryStreamExt};
44

55
use kameo::{message::Context, prelude::*};
6+
use nftnl::{Batch, FinalizedBatch, Hook, MsgType, ProtoFamily, Rule, Table, nft_expr};
67
use rtnetlink::{Error as NetlinkError, Handle, LinkBridge, LinkUnspec};
78
use stable_eyre::Report;
89
use stable_eyre::eyre::{Context as EyreContext, eyre};
@@ -21,6 +22,7 @@ impl Actor for DhcpActor {
2122
type Args = DhcpConfig;
2223
type Error = Report;
2324
async fn on_start(args: Self::Args, _actor_ref: ActorRef<Self>) -> Result<Self, Self::Error> {
25+
// todo: actually run dnsmasq on startup
2426
Ok(Self { config: args })
2527
}
2628
}
@@ -82,6 +84,7 @@ async fn ensure_address(
8284
}),
8385
}
8486
}
87+
8588
#[derive(RemoteActor)]
8689
pub struct NetworkAgentActor {
8790
pub config: NetworkConfig,
@@ -90,6 +93,7 @@ pub struct NetworkAgentActor {
9093
// netlink_handle:
9194
netlink_thread: tokio::task::JoinHandle<()>,
9295
netlink_handle: Handle,
96+
nft_socket: mnl::Socket,
9397
}
9498

9599
impl NetworkAgentActor {
@@ -107,6 +111,65 @@ impl NetworkAgentActor {
107111
.ok_or_else(|| eyre!("link {} not found", link_name))?
108112
.wrap_err_with(|| format!("failed to query link {}", link_name))
109113
}
114+
115+
fn send_nft_batch(&mut self, batch: &FinalizedBatch) -> Result<(), Report> {
116+
let portid = self.nft_socket.portid();
117+
118+
self.nft_socket
119+
.send_all(batch)
120+
.wrap_err("failed to send nftables batch to netfilter")?;
121+
122+
let mut buffer = vec![0; nftnl::nft_nlmsg_maxsize() as usize];
123+
let mut expected_seqs = batch.sequence_numbers();
124+
125+
while !expected_seqs.is_empty() {
126+
for message in self
127+
.nft_socket
128+
.recv(&mut buffer[..])
129+
.wrap_err("failed to receive nftables netlink acknowledgement")?
130+
{
131+
let message = message.wrap_err("failed to decode nft ack message")?;
132+
let expected_seq = expected_seqs
133+
.next()
134+
.ok_or_else(|| eyre!("received unexpected nftables acknowledgement"))?;
135+
136+
mnl::cb_run(message, expected_seq, portid)
137+
.wrap_err("nftables batch acknowledgement failed")?;
138+
}
139+
}
140+
141+
Ok(())
142+
}
143+
144+
fn ensure_nat_rules(
145+
&mut self,
146+
_bridge: &str,
147+
_subnet: &str,
148+
upstream_iface: &str,
149+
) -> Result<(), Report> {
150+
let table = Table::new(c"nat", ProtoFamily::Ipv4);
151+
152+
let mut postrouting_chain = nftnl::Chain::new(c"postrouting", &table);
153+
postrouting_chain.set_type(nftnl::ChainType::Nat);
154+
postrouting_chain.set_hook(Hook::PostRouting, 100);
155+
156+
let mut batch = Batch::new();
157+
batch.add(&table, MsgType::Add);
158+
batch.add(&postrouting_chain, MsgType::Add);
159+
160+
let mut postrouting_rule = Rule::new(&postrouting_chain);
161+
postrouting_rule.add_expr(&nft_expr!(meta oifname));
162+
postrouting_rule.add_expr(&nft_expr!(cmp == upstream_iface));
163+
postrouting_rule.add_expr(&nft_expr!(masquerade));
164+
batch.add(&postrouting_rule, MsgType::Add);
165+
166+
let finalized = batch.finalize();
167+
self.send_nft_batch(&finalized).wrap_err_with(|| {
168+
format!("failed to apply nftables postrouting masquerade for {upstream_iface}")
169+
})?;
170+
171+
Ok(())
172+
}
110173
}
111174

112175
impl Actor for NetworkAgentActor {
@@ -117,6 +180,8 @@ impl Actor for NetworkAgentActor {
117180

118181
let (connection, handle, _) = rtnetlink::new_connection()?;
119182
let netlink_thread = tokio::spawn(connection);
183+
let nft_socket = mnl::Socket::new(mnl::Bus::Netfilter)
184+
.wrap_err("failed to create netfilter netlink socket")?;
120185

121186
let common = match args.network_mode.clone() {
122187
NetworkMode::HostonlyNat {
@@ -168,22 +233,63 @@ impl Actor for NetworkAgentActor {
168233
})?
169234
};
170235

171-
match args.network_mode.clone() {
236+
let dhcp_actor = if let Some(dhcp_config) = &args.dhcp_config {
237+
Some(DhcpActor::spawn_link(&actor_ref, dhcp_config.clone()).await)
238+
} else {
239+
None
240+
};
241+
242+
let mut actor = Self {
243+
config: args,
244+
common,
245+
dhcp_actor,
246+
netlink_thread,
247+
netlink_handle: handle,
248+
nft_socket,
249+
};
250+
251+
match actor.config.network_mode.clone() {
172252
NetworkMode::HostonlyNat {
173253
bridge: _,
174254
subnet,
175255
gateway,
176-
upstream_iface: _,
256+
upstream_iface,
257+
} => {
258+
ensure_address(
259+
&actor.netlink_handle,
260+
bridge.header.index,
261+
&actor.common.bridge,
262+
gateway,
263+
subnet.prefix_len(),
264+
)
265+
.await
266+
.wrap_err_with(|| {
267+
format!(
268+
"failed to ensure gateway {}/{} exists on bridge {}",
269+
gateway,
270+
subnet.prefix_len(),
271+
actor.common.bridge
272+
)
273+
})?;
274+
275+
actor
276+
.ensure_nat_rules(&actor.common.bridge, &actor.common.subnet, &upstream_iface)
277+
.wrap_err_with(|| {
278+
format!(
279+
"failed to ensure nftables NAT rules for bridge {} and upstream {}",
280+
actor.common.bridge, upstream_iface
281+
)
282+
})?;
177283
}
178-
| NetworkMode::Bridged {
284+
NetworkMode::Bridged {
179285
bridge: _,
180286
subnet,
181287
gateway,
182288
} => {
183289
ensure_address(
184-
&handle,
290+
&actor.netlink_handle,
185291
bridge.header.index,
186-
&common.bridge,
292+
&actor.common.bridge,
187293
gateway,
188294
subnet.prefix_len(),
189295
)
@@ -193,27 +299,13 @@ impl Actor for NetworkAgentActor {
193299
"failed to ensure gateway {}/{} exists on bridge {}",
194300
gateway,
195301
subnet.prefix_len(),
196-
common.bridge
302+
actor.common.bridge
197303
)
198304
})?;
199305
}
200306
}
201307

202-
// let link_bridge = LinkBridge::new(arg)
203-
204-
let dhcp_actor = if let Some(dhcp_config) = &args.dhcp_config {
205-
Some(DhcpActor::spawn_link(&actor_ref, dhcp_config.clone()).await)
206-
} else {
207-
None
208-
};
209-
210-
Ok(Self {
211-
config: args,
212-
common,
213-
dhcp_actor,
214-
netlink_thread,
215-
netlink_handle: handle,
216-
})
308+
Ok(actor)
217309
}
218310

219311
async fn on_stop(

0 commit comments

Comments
 (0)