Coverage Report

Created: 2025-11-16 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ztunnel/src/inpod/config.rs
Line
Count
Source
1
// Copyright Istio Authors
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
use crate::proxy::DefaultSocketFactory;
16
use crate::{config, socket};
17
use std::sync::Arc;
18
19
use super::netns::InpodNetns;
20
21
pub struct InPodConfig {
22
    cur_netns: Arc<std::os::fd::OwnedFd>,
23
    mark: Option<std::num::NonZeroU32>,
24
    reuse_port: bool,
25
    socket_config: config::SocketConfig,
26
}
27
28
impl InPodConfig {
29
0
    pub fn new(cfg: &config::Config) -> std::io::Result<Self> {
30
        Ok(InPodConfig {
31
0
            cur_netns: Arc::new(InpodNetns::current()?),
32
0
            mark: std::num::NonZeroU32::new(cfg.packet_mark.expect("in pod requires packet mark")),
33
0
            reuse_port: cfg.inpod_port_reuse,
34
0
            socket_config: cfg.socket_config,
35
        })
36
0
    }
37
0
    pub fn socket_factory(
38
0
        &self,
39
0
        netns: InpodNetns,
40
0
    ) -> Box<dyn crate::proxy::SocketFactory + Send + Sync> {
41
0
        let base = crate::proxy::DefaultSocketFactory(self.socket_config);
42
0
        let sf = InPodSocketFactory::from_cfg(base, self, netns);
43
0
        if self.reuse_port {
44
0
            Box::new(InPodSocketPortReuseFactory::new(sf))
45
        } else {
46
0
            Box::new(sf)
47
        }
48
0
    }
49
50
0
    pub fn cur_netns(&self) -> Arc<std::os::fd::OwnedFd> {
51
0
        self.cur_netns.clone()
52
0
    }
53
0
    fn mark(&self) -> Option<std::num::NonZeroU32> {
54
0
        self.mark
55
0
    }
56
}
57
58
struct InPodSocketFactory {
59
    inner: DefaultSocketFactory,
60
    netns: InpodNetns,
61
    mark: Option<std::num::NonZeroU32>,
62
}
63
64
impl InPodSocketFactory {
65
0
    fn from_cfg(
66
0
        inner: DefaultSocketFactory,
67
0
        inpod_config: &InPodConfig,
68
0
        netns: InpodNetns,
69
0
    ) -> Self {
70
0
        Self::new(inner, netns, inpod_config.mark())
71
0
    }
72
73
0
    fn new(
74
0
        inner: DefaultSocketFactory,
75
0
        netns: InpodNetns,
76
0
        mark: Option<std::num::NonZeroU32>,
77
0
    ) -> Self {
78
0
        Self { inner, netns, mark }
79
0
    }
80
81
0
    fn run_in_ns<S, F: FnOnce() -> std::io::Result<S>>(&self, f: F) -> std::io::Result<S> {
82
0
        self.netns.run(f)?
83
0
    }
84
85
0
    fn configure<S: std::os::unix::io::AsFd, F: FnOnce() -> std::io::Result<S>>(
86
0
        &self,
87
0
        f: F,
88
0
    ) -> std::io::Result<S> {
89
0
        let socket = self.netns.run(f)??;
90
91
0
        if let Some(mark) = self.mark {
92
0
            crate::socket::set_mark(&socket, mark.into())?;
93
0
        }
94
0
        Ok(socket)
95
0
    }
Unexecuted instantiation: <ztunnel::inpod::config::InPodSocketFactory>::configure::<std::net::tcp::TcpListener, <ztunnel::inpod::config::InPodSocketFactory as ztunnel::proxy::SocketFactory>::tcp_bind::{closure#0}>
Unexecuted instantiation: <ztunnel::inpod::config::InPodSocketFactory>::configure::<std::net::udp::UdpSocket, <ztunnel::inpod::config::InPodSocketFactory as ztunnel::proxy::SocketFactory>::udp_bind::{closure#0}>
Unexecuted instantiation: <ztunnel::inpod::config::InPodSocketFactory>::configure::<tokio::net::tcp::socket::TcpSocket, <ztunnel::inpod::config::InPodSocketFactory as ztunnel::proxy::SocketFactory>::new_tcp_v4::{closure#0}>
Unexecuted instantiation: <ztunnel::inpod::config::InPodSocketFactory>::configure::<tokio::net::tcp::socket::TcpSocket, <ztunnel::inpod::config::InPodSocketFactory as ztunnel::proxy::SocketFactory>::new_tcp_v6::{closure#0}>
Unexecuted instantiation: <ztunnel::inpod::config::InPodSocketFactory>::configure::<tokio::net::tcp::socket::TcpSocket, <ztunnel::inpod::config::InPodSocketPortReuseFactory as ztunnel::proxy::SocketFactory>::tcp_bind::{closure#0}>
Unexecuted instantiation: <ztunnel::inpod::config::InPodSocketFactory>::configure::<std::os::fd::owned::OwnedFd, <ztunnel::inpod::config::InPodSocketPortReuseFactory as ztunnel::proxy::SocketFactory>::udp_bind::{closure#0}>
96
}
97
98
impl crate::proxy::SocketFactory for InPodSocketFactory {
99
0
    fn new_tcp_v4(&self) -> std::io::Result<tokio::net::TcpSocket> {
100
0
        self.configure(|| self.inner.new_tcp_v4())
101
0
    }
102
103
0
    fn new_tcp_v6(&self) -> std::io::Result<tokio::net::TcpSocket> {
104
0
        self.configure(|| self.inner.new_tcp_v6())
105
0
    }
106
107
0
    fn tcp_bind(&self, addr: std::net::SocketAddr) -> std::io::Result<socket::Listener> {
108
0
        let std_sock = self.configure(|| std::net::TcpListener::bind(addr))?;
109
0
        std_sock.set_nonblocking(true)?;
110
0
        tokio::net::TcpListener::from_std(std_sock).map(socket::Listener::new)
111
0
    }
112
113
0
    fn udp_bind(&self, addr: std::net::SocketAddr) -> std::io::Result<tokio::net::UdpSocket> {
114
0
        let std_sock = self.configure(|| std::net::UdpSocket::bind(addr))?;
115
0
        std_sock.set_nonblocking(true)?;
116
0
        tokio::net::UdpSocket::from_std(std_sock)
117
0
    }
118
119
0
    fn ipv6_enabled_localhost(&self) -> std::io::Result<bool> {
120
0
        self.run_in_ns(|| self.inner.ipv6_enabled_localhost())
121
0
    }
122
}
123
124
// Same as socket factory, but sets SO_REUSEPORT
125
struct InPodSocketPortReuseFactory {
126
    sf: InPodSocketFactory,
127
}
128
129
impl InPodSocketPortReuseFactory {
130
0
    fn new(sf: InPodSocketFactory) -> Self {
131
0
        Self { sf }
132
0
    }
133
}
134
135
impl crate::proxy::SocketFactory for InPodSocketPortReuseFactory {
136
0
    fn new_tcp_v4(&self) -> std::io::Result<tokio::net::TcpSocket> {
137
0
        self.sf.new_tcp_v4()
138
0
    }
139
140
0
    fn new_tcp_v6(&self) -> std::io::Result<tokio::net::TcpSocket> {
141
0
        self.sf.new_tcp_v6()
142
0
    }
143
144
0
    fn tcp_bind(&self, addr: std::net::SocketAddr) -> std::io::Result<socket::Listener> {
145
0
        let sock = self.sf.configure(|| match addr {
146
0
            std::net::SocketAddr::V4(_) => tokio::net::TcpSocket::new_v4(),
147
0
            std::net::SocketAddr::V6(_) => tokio::net::TcpSocket::new_v6(),
148
0
        })?;
149
150
0
        if let Err(e) = sock.set_reuseport(true) {
151
0
            tracing::warn!("setting set_reuseport failed: {e} addr: {addr}");
152
0
        }
153
154
0
        sock.bind(addr)?;
155
0
        sock.listen(128).map(socket::Listener::new)
156
0
    }
157
158
0
    fn udp_bind(&self, addr: std::net::SocketAddr) -> std::io::Result<tokio::net::UdpSocket> {
159
0
        let sock = self.sf.configure(|| {
160
0
            let sock = match addr {
161
0
                std::net::SocketAddr::V4(_) => nix::sys::socket::socket(
162
0
                    nix::sys::socket::AddressFamily::Inet,
163
0
                    nix::sys::socket::SockType::Datagram,
164
0
                    nix::sys::socket::SockFlag::empty(),
165
0
                    None,
166
                )
167
0
                .map_err(|e| std::io::Error::from_raw_os_error(e as i32)),
168
0
                std::net::SocketAddr::V6(_) => nix::sys::socket::socket(
169
0
                    nix::sys::socket::AddressFamily::Inet6,
170
0
                    nix::sys::socket::SockType::Datagram,
171
0
                    nix::sys::socket::SockFlag::empty(),
172
0
                    None,
173
                )
174
0
                .map_err(|e| std::io::Error::from_raw_os_error(e as i32)),
175
0
            }?;
176
0
            Ok(sock)
177
0
        })?;
178
179
0
        let socket_ref = socket2::SockRef::from(&sock);
180
181
        // important to set SO_REUSEPORT before binding!
182
0
        socket_ref.set_reuse_port(true)?;
183
0
        let addr = socket2::SockAddr::from(addr);
184
0
        socket_ref.bind(&addr)?;
185
186
0
        let std_sock: std::net::UdpSocket = sock.into();
187
188
0
        std_sock.set_nonblocking(true)?;
189
0
        tokio::net::UdpSocket::from_std(std_sock)
190
0
    }
191
192
0
    fn ipv6_enabled_localhost(&self) -> std::io::Result<bool> {
193
0
        self.sf.ipv6_enabled_localhost()
194
0
    }
195
}
196
197
#[cfg(test)]
198
mod test {
199
    use super::*;
200
201
    use crate::inpod::test_helpers::new_netns;
202
203
    macro_rules! fixture {
204
        () => {{
205
            if !crate::test_helpers::can_run_privilged_test() {
206
                eprintln!("This test requires root; skipping");
207
                return;
208
            }
209
210
            crate::config::Config {
211
                packet_mark: Some(123),
212
                ..crate::config::parse_config().unwrap()
213
            }
214
        }};
215
    }
216
217
    #[tokio::test]
218
    async fn test_inpod_config_no_port_reuse() {
219
        let mut cfg = fixture!();
220
        cfg.inpod_port_reuse = false;
221
222
        let inpod_cfg = InPodConfig::new(&cfg).unwrap();
223
        assert_eq!(
224
            inpod_cfg.mark(),
225
            Some(std::num::NonZeroU32::new(123).unwrap())
226
        );
227
        assert!(!inpod_cfg.reuse_port);
228
229
        let sf = inpod_cfg.socket_factory(
230
            InpodNetns::new(
231
                Arc::new(crate::inpod::netns::InpodNetns::current().unwrap()),
232
                new_netns(),
233
            )
234
            .unwrap(),
235
        );
236
237
        let sock_addr: std::net::SocketAddr = "127.0.0.1:8080".parse().unwrap();
238
        {
239
            let s = sf.tcp_bind(sock_addr).unwrap().inner();
240
241
            // make sure mark nad port re-use are set
242
            let sock_ref = socket2::SockRef::from(&s);
243
            assert_eq!(
244
                sock_ref.local_addr().unwrap(),
245
                socket2::SockAddr::from(sock_addr)
246
            );
247
            assert!(!sock_ref.reuse_port().unwrap());
248
            assert_eq!(sock_ref.mark().unwrap(), 123);
249
        }
250
251
        {
252
            let s = sf.udp_bind(sock_addr).unwrap();
253
254
            // make sure mark nad port re-use are set
255
            let sock_ref = socket2::SockRef::from(&s);
256
            assert_eq!(
257
                sock_ref.local_addr().unwrap(),
258
                socket2::SockAddr::from(sock_addr)
259
            );
260
            assert!(!sock_ref.reuse_port().unwrap());
261
            assert_eq!(sock_ref.mark().unwrap(), 123);
262
        }
263
    }
264
265
    #[tokio::test]
266
    async fn test_inpod_config_port_reuse() {
267
        let cfg = fixture!();
268
269
        let inpod_cfg = InPodConfig::new(&cfg).unwrap();
270
        assert_eq!(
271
            inpod_cfg.mark(),
272
            Some(std::num::NonZeroU32::new(123).unwrap())
273
        );
274
        assert!(inpod_cfg.reuse_port);
275
276
        let sf = inpod_cfg.socket_factory(
277
            InpodNetns::new(
278
                Arc::new(crate::inpod::netns::InpodNetns::current().unwrap()),
279
                new_netns(),
280
            )
281
            .unwrap(),
282
        );
283
284
        let sock_addr: std::net::SocketAddr = "127.0.0.1:8080".parse().unwrap();
285
        {
286
            let s = sf.tcp_bind(sock_addr).unwrap().inner();
287
288
            // make sure mark nad port re-use are set
289
            let sock_ref = socket2::SockRef::from(&s);
290
            assert_eq!(
291
                sock_ref.local_addr().unwrap(),
292
                socket2::SockAddr::from(sock_addr)
293
            );
294
            assert!(sock_ref.reuse_port().unwrap());
295
            assert_eq!(sock_ref.mark().unwrap(), 123);
296
        }
297
298
        {
299
            let s = sf.udp_bind(sock_addr).unwrap();
300
301
            // make sure mark nad port re-use are set
302
            let sock_ref = socket2::SockRef::from(&s);
303
            assert_eq!(
304
                sock_ref.local_addr().unwrap(),
305
                socket2::SockAddr::from(sock_addr)
306
            );
307
            assert!(sock_ref.reuse_port().unwrap());
308
            assert_eq!(sock_ref.mark().unwrap(), 123);
309
        }
310
    }
311
312
    #[tokio::test]
313
    async fn test_inpod_config_outbound_sockets() {
314
        let cfg = fixture!();
315
316
        let inpod_cfg = InPodConfig::new(&cfg).unwrap();
317
318
        let sf = inpod_cfg.socket_factory(
319
            InpodNetns::new(
320
                Arc::new(crate::inpod::netns::InpodNetns::current().unwrap()),
321
                new_netns(),
322
            )
323
            .unwrap(),
324
        );
325
326
        {
327
            let s = sf.new_tcp_v4().unwrap();
328
            let sock_ref = socket2::SockRef::from(&s);
329
            assert!(!sock_ref.reuse_port().unwrap());
330
            assert_eq!(sock_ref.mark().unwrap(), 123);
331
        }
332
333
        {
334
            let s = sf.new_tcp_v6().unwrap();
335
            let sock_ref = socket2::SockRef::from(&s);
336
            assert!(!sock_ref.reuse_port().unwrap());
337
            assert_eq!(sock_ref.mark().unwrap(), 123);
338
        }
339
    }
340
}