1use std::net::Ipv6Addr;
7use tracing::{debug, error, info, warn};
8
9pub fn check_ipv6_forwarding() {
14 match std::fs::read_to_string("/proc/sys/net/ipv6/conf/all/forwarding") {
15 Ok(val) if val.trim() == "1" => {
16 debug!("IPv6 forwarding is enabled");
17 }
18 Ok(_) => {
19 error!(
20 "IPv6 forwarding is disabled. Enable with: \
21 sysctl -w net.ipv6.conf.all.forwarding=1"
22 );
23 std::process::exit(1);
24 }
25 Err(e) => {
26 error!(error = %e, "Could not check IPv6 forwarding state");
27 std::process::exit(1);
28 }
29 }
30}
31
32pub async fn check_interface_exists(name: &str) -> Result<u32, std::io::Error> {
34 let index = rustables::iface_index(name)
35 .map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, e.to_string()))?;
36 debug!(interface = %name, index, "Interface found");
37 Ok(index)
38}
39
40pub struct NetSetup {
42 lan_interface: String,
43 proxy_entries: Vec<Ipv6Addr>,
45 route_added: bool,
47 pool_cidr: String,
48}
49
50impl NetSetup {
51 pub fn new(lan_interface: String, pool_cidr: String) -> Self {
53 Self {
54 lan_interface,
55 proxy_entries: Vec::new(),
56 route_added: false,
57 pool_cidr,
58 }
59 }
60
61 pub async fn add_pool_route(&mut self) -> Result<(), std::io::Error> {
69 let output = tokio::process::Command::new("ip")
70 .args(["-6", "route", "add", "local", &self.pool_cidr, "dev", "lo"])
71 .output()
72 .await?;
73
74 if !output.status.success() {
75 let stderr = String::from_utf8_lossy(&output.stderr);
76 if stderr.contains("File exists") {
78 debug!(cidr = %self.pool_cidr, "Pool route already exists");
79 return Ok(());
80 }
81 return Err(std::io::Error::other(format!(
82 "Failed to add pool route: {stderr}"
83 )));
84 }
85
86 self.route_added = true;
87 info!(cidr = %self.pool_cidr, "Added local pool route");
88 Ok(())
89 }
90
91 pub async fn add_proxy_ndp(&mut self, addr: Ipv6Addr) -> Result<(), std::io::Error> {
93 let addr_str = addr.to_string();
94 let output = tokio::process::Command::new("ip")
95 .args([
96 "-6",
97 "neigh",
98 "add",
99 "proxy",
100 &addr_str,
101 "dev",
102 &self.lan_interface,
103 ])
104 .output()
105 .await?;
106
107 if !output.status.success() {
108 let stderr = String::from_utf8_lossy(&output.stderr);
109 if stderr.contains("File exists") {
110 debug!(addr = %addr, "Proxy NDP entry already exists");
111 return Ok(());
112 }
113 return Err(std::io::Error::other(format!(
114 "Failed to add proxy NDP: {stderr}"
115 )));
116 }
117
118 self.proxy_entries.push(addr);
119 debug!(addr = %addr, iface = %self.lan_interface, "Added proxy NDP entry");
120 Ok(())
121 }
122
123 pub async fn remove_proxy_ndp(&mut self, addr: Ipv6Addr) -> Result<(), std::io::Error> {
125 let addr_str = addr.to_string();
126 let output = tokio::process::Command::new("ip")
127 .args([
128 "-6",
129 "neigh",
130 "del",
131 "proxy",
132 &addr_str,
133 "dev",
134 &self.lan_interface,
135 ])
136 .output()
137 .await?;
138
139 if !output.status.success() {
140 let stderr = String::from_utf8_lossy(&output.stderr);
141 if !stderr.contains("No such file") {
143 warn!(addr = %addr, error = %stderr.trim(), "Failed to remove proxy NDP");
144 }
145 }
146
147 self.proxy_entries.retain(|a| *a != addr);
148 Ok(())
149 }
150
151 pub async fn cleanup(&mut self) {
153 let entries: Vec<Ipv6Addr> = self.proxy_entries.clone();
155 for addr in entries {
156 let _ = self.remove_proxy_ndp(addr).await;
157 }
158
159 if self.route_added {
161 let output = tokio::process::Command::new("ip")
162 .args(["-6", "route", "del", "local", &self.pool_cidr, "dev", "lo"])
163 .output()
164 .await;
165
166 match output {
167 Ok(o) if o.status.success() => {
168 info!(cidr = %self.pool_cidr, "Removed pool route");
169 }
170 Ok(o) => {
171 let stderr = String::from_utf8_lossy(&o.stderr);
172 warn!(error = %stderr.trim(), "Failed to remove pool route");
173 }
174 Err(e) => {
175 warn!(error = %e, "Failed to run ip route del");
176 }
177 }
178 self.route_added = false;
179 }
180 }
181}