@@ -3,6 +3,7 @@ use crate::networking::messages::{AttachTap, DetachTap};
33use futures_util:: { StreamExt , TryStreamExt } ;
44
55use kameo:: { message:: Context , prelude:: * } ;
6+ use nftnl:: { Batch , FinalizedBatch , Hook , MsgType , ProtoFamily , Rule , Table , nft_expr} ;
67use rtnetlink:: { Error as NetlinkError , Handle , LinkBridge , LinkUnspec } ;
78use stable_eyre:: Report ;
89use 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 ) ]
8689pub 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
9599impl 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
112175impl 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