Skip to main content

sozu_lib/protocol/proxy_protocol/
send.rs

1//! PROXY-v2 send state.
2//!
3//! Synthesises a PROXY-v2 header (`HeaderV2`) describing the original
4//! client and emits it on a freshly opened backend `TcpStream` before the
5//! TCP/TLS payload begins. Used when the front-end accepted a non-PROXY
6//! connection but the configured backend expects PROXY-v2 metadata.
7
8use std::{
9    cell::RefCell,
10    io::{ErrorKind, Write},
11    rc::Rc,
12};
13
14use mio::{Token, net::TcpStream};
15use rusty_ulid::Ulid;
16use sozu_command::logging::ansi_palette;
17
18use crate::metrics::names;
19use crate::{
20    BackendConnectionStatus, Protocol, Readiness, SessionMetrics, SessionResult,
21    pool::Checkout,
22    protocol::{
23        pipe::{Pipe, WebSocketContext},
24        proxy_protocol::header::{Command, HeaderV2, ProxyProtocolHeader},
25    },
26    socket::SocketHandler,
27    sozu_command::ready::Ready,
28    tcp::TcpListener,
29};
30
31/// Module-level prefix used on every log line emitted from this module when
32/// no per-session state is in scope. Produces a bold bright-white
33/// `PROXY-SEND` label (uniform across every protocol) when the logger is in
34/// colored mode.
35#[allow(unused_macros)]
36macro_rules! log_module_context {
37    () => {{
38        let (open, reset, _, _, _) = ansi_palette();
39        format!("{open}PROXY-SEND{reset}\t >>>", open = open, reset = reset)
40    }};
41}
42
43/// Per-session prefix for log lines emitted with a [`SendProxyProtocol`] in
44/// scope. Renders the canonical `\tPROXY-SEND\tSession(...)\t >>>` envelope.
45/// The send-side state has no `request_id`-keyed [`LogContext`] yet; the
46/// bracket carries the front/back tokens instead.
47macro_rules! log_context {
48    ($self:expr) => {{
49        let (open, reset, grey, gray, white) = ansi_palette();
50        format!(
51            "{open}PROXY-SEND{reset}\t{grey}Session{reset}({gray}frontend{reset}={white}{frontend}{reset}, {gray}backend{reset}={white}{backend}{reset}, {gray}front_readiness{reset}={white}{front_readiness}{reset}, {gray}back_readiness{reset}={white}{back_readiness}{reset})\t >>>",
52            open = open,
53            reset = reset,
54            grey = grey,
55            gray = gray,
56            white = white,
57            frontend = $self.frontend_token.0,
58            backend = $self.backend_token.map(|t| t.0.to_string()).unwrap_or_else(|| "<none>".to_string()),
59            front_readiness = $self.frontend_readiness,
60            back_readiness = $self.backend_readiness,
61        )
62    }};
63}
64
65pub struct SendProxyProtocol<Front: SocketHandler> {
66    cursor_header: usize,
67    pub backend_readiness: Readiness,
68    pub backend_token: Option<Token>,
69    pub backend: Option<TcpStream>,
70    pub frontend_readiness: Readiness,
71    pub frontend_token: Token,
72    pub frontend: Front,
73    pub header: Option<Vec<u8>>,
74    pub request_id: Ulid,
75}
76
77impl<Front: SocketHandler> SendProxyProtocol<Front> {
78    /// Instantiate a new SendProxyProtocol SessionState with:
79    /// - frontend_interest: HUP | ERROR
80    /// - frontend_event: EMPTY
81    /// - backend_interest: HUP | ERROR
82    /// - backend_event: EMPTY
83    pub fn new(
84        frontend: Front,
85        frontend_token: Token,
86        request_id: Ulid,
87        backend: Option<TcpStream>,
88    ) -> Self {
89        SendProxyProtocol {
90            header: None,
91            frontend,
92            request_id,
93            backend,
94            frontend_token,
95            backend_token: None,
96            frontend_readiness: Readiness {
97                interest: Ready::HUP | Ready::ERROR,
98                event: Ready::EMPTY,
99            },
100            backend_readiness: Readiness {
101                interest: Ready::HUP | Ready::ERROR,
102                event: Ready::EMPTY,
103            },
104            cursor_header: 0,
105        }
106    }
107
108    // The header is send immediately at once upon the connection is establish
109    // and prepended before any data.
110    pub fn back_writable(&mut self, metrics: &mut SessionMetrics) -> SessionResult {
111        debug!(
112            "{} trying to write proxy protocol header",
113            log_context!(self)
114        );
115
116        // Generate the proxy protocol header if not already exist.
117        if self.header.is_none()
118            && let Ok(local_addr) = self.front_socket().local_addr()
119        {
120            let Ok(frontend_addr) = self.front_socket().peer_addr() else {
121                return SessionResult::Close;
122            };
123            let v2 = HeaderV2::new(Command::Proxy, frontend_addr, local_addr);
124            let declared_len = v2.len();
125            let serialized = ProxyProtocolHeader::V2(v2).into_bytes();
126            // Send postcondition: the byte vector we will stream out is
127            // exactly the length the header model declared. The cursor
128            // logic below relies on `header.len()` being this serialized
129            // size to detect completion.
130            debug_assert_eq!(
131                serialized.len(),
132                declared_len,
133                "serialized send header length must match HeaderV2::len()"
134            );
135            debug_assert!(
136                serialized.len() >= 16,
137                "a v2 send header is at least its 16-byte fixed prefix"
138            );
139            self.header = Some(serialized);
140        };
141
142        if let Some(ref mut socket) = self.backend
143            && let Some(ref mut header) = self.header
144        {
145            loop {
146                // The cursor never overruns the serialized header: it only
147                // advances by reported write sizes and stops at `len()`.
148                debug_assert!(
149                    self.cursor_header <= header.len(),
150                    "send cursor must stay within the serialized header"
151                );
152                let remaining = header.len() - self.cursor_header;
153                match socket.write(&header[self.cursor_header..]) {
154                    Ok(sz) => {
155                        debug_assert!(
156                            sz <= remaining,
157                            "socket.write cannot send more than the unsent header tail"
158                        );
159                        let cursor_before = self.cursor_header;
160                        self.cursor_header += sz;
161                        // Strictly monotonic: the cursor tracks exactly the
162                        // bytes emitted and never passes the header length.
163                        debug_assert_eq!(
164                            self.cursor_header,
165                            cursor_before + sz,
166                            "send cursor advances by exactly the bytes written"
167                        );
168                        debug_assert!(
169                            self.cursor_header <= header.len(),
170                            "send cursor must not pass the header length"
171                        );
172                        count!(names::backend::BACK_BYTES_OUT, sz as i64);
173                        metrics.backend_bout += sz;
174
175                        if self.cursor_header == header.len() {
176                            debug!("{} proxy protocol sent, upgrading", log_context!(self));
177                            return SessionResult::Upgrade;
178                        }
179                    }
180                    Err(e) => match e.kind() {
181                        ErrorKind::WouldBlock => {
182                            self.backend_readiness.event.remove(Ready::WRITABLE);
183                            return SessionResult::Continue;
184                        }
185                        e => {
186                            incr!(names::proxy_protocol::ERRORS);
187                            debug!("{} write error: {:?}", log_context!(self), e);
188                            return SessionResult::Close;
189                        }
190                    },
191                }
192            }
193        }
194
195        error!(
196            "{} started send proxy protocol with no header or backend socket",
197            log_context!(self)
198        );
199        SessionResult::Close
200    }
201
202    pub fn front_socket(&self) -> &TcpStream {
203        self.frontend.socket_ref()
204    }
205
206    pub fn front_socket_mut(&mut self) -> &mut TcpStream {
207        self.frontend.socket_mut()
208    }
209
210    pub fn back_socket(&self) -> Option<&TcpStream> {
211        self.backend.as_ref()
212    }
213
214    pub fn back_socket_mut(&mut self) -> Option<&mut TcpStream> {
215        self.backend.as_mut()
216    }
217
218    pub fn set_back_socket(&mut self, socket: TcpStream) {
219        self.backend = Some(socket);
220    }
221
222    pub fn back_token(&self) -> Option<Token> {
223        self.backend_token
224    }
225
226    pub fn set_back_token(&mut self, token: Token) {
227        self.backend_token = Some(token);
228    }
229
230    pub fn set_back_connected(&mut self, status: BackendConnectionStatus) {
231        if status == BackendConnectionStatus::Connected {
232            self.backend_readiness.interest.insert(Ready::WRITABLE);
233        }
234    }
235
236    pub fn into_pipe(
237        mut self,
238        front_buf: Checkout,
239        back_buf: Checkout,
240        listener: Rc<RefCell<TcpListener>>,
241    ) -> Pipe<Front, TcpListener> {
242        let backend_socket = self.backend.take().unwrap();
243        let addr = self.front_socket().peer_addr().ok();
244
245        let mut pipe = Pipe::new(
246            back_buf,
247            None,
248            Some(backend_socket),
249            None,
250            None,
251            None,
252            None,
253            front_buf,
254            self.frontend_token,
255            self.frontend,
256            listener,
257            Protocol::TCP,
258            self.request_id,
259            self.request_id,
260            addr,
261            WebSocketContext::Tcp,
262        );
263
264        pipe.frontend_readiness = self.frontend_readiness;
265        pipe.backend_readiness = self.backend_readiness;
266
267        pipe.frontend_readiness.interest.insert(Ready::READABLE);
268        pipe.backend_readiness.interest.insert(Ready::READABLE);
269
270        if let Some(back_token) = self.backend_token {
271            pipe.set_back_token(back_token);
272        }
273
274        pipe
275    }
276}
277
278#[cfg(test)]
279mod send_test {
280    use std::{
281        io::Read,
282        net::{SocketAddr, TcpListener as StdTcpListener, TcpStream as StdTcpStream},
283        os::unix::io::{FromRawFd, IntoRawFd},
284        sync::{Arc, Barrier},
285        thread::{self, JoinHandle},
286    };
287
288    use mio::net::{TcpListener, TcpStream};
289    use rusty_ulid::Ulid;
290
291    use super::{
292        super::parser::parse_v2_header, BackendConnectionStatus, ErrorKind, SendProxyProtocol,
293        SessionMetrics, SessionResult, Token,
294    };
295
296    #[test]
297    fn it_should_send_a_proxy_protocol_header_to_the_upstream_backend() {
298        setup_test_logger!();
299        let addr_client: SocketAddr = "127.0.0.1:6666".parse().expect("parse address error");
300        let addr_backend: SocketAddr = "127.0.0.1:2001".parse().expect("parse address error");
301        let barrier = Arc::new(Barrier::new(3));
302        let end_barrier = Arc::new(Barrier::new(2));
303
304        start_client(addr_client, barrier.clone(), end_barrier.clone());
305        let backend = start_backend(addr_backend, barrier.clone(), end_barrier);
306        start_middleware(addr_client, addr_backend, barrier);
307
308        backend
309            .join()
310            .expect("Couldn't join on the associated backend");
311    }
312
313    // Get connection from the session and connect to the backend
314    // When connections are establish we send the proxy protocol header
315    fn start_middleware(addr_client: SocketAddr, addr_backend: SocketAddr, barrier: Arc<Barrier>) {
316        let listener = TcpListener::bind(addr_client).expect("could not accept session connection");
317
318        let client_stream;
319        barrier.wait();
320
321        loop {
322            if let Ok((stream, _addr)) = listener.accept() {
323                client_stream = stream;
324                break;
325            }
326        }
327
328        // connect in blocking first, then convert to a mio socket
329        let backend_stream =
330            StdTcpStream::connect(addr_backend).expect("could not connect to the backend");
331        let fd = backend_stream.into_raw_fd();
332        // SAFETY: `fd` was just released by `into_raw_fd` from the blocking
333        // `StdTcpStream` so it is a valid open descriptor with no other owner.
334        // Ownership transfers to the mio `TcpStream`, whose `Drop` closes it.
335        let backend_stream = unsafe { TcpStream::from_raw_fd(fd) };
336
337        let mut send_pp = SendProxyProtocol::new(
338            client_stream,
339            Token(0),
340            Ulid::generate(),
341            Some(backend_stream),
342        );
343        let mut session_metrics = SessionMetrics::new(None);
344
345        send_pp.set_back_connected(BackendConnectionStatus::Connected);
346
347        loop {
348            let result = send_pp.back_writable(&mut session_metrics);
349            if result == SessionResult::Upgrade {
350                break;
351            }
352
353            if result != SessionResult::Continue {
354                panic!("state machine error: result = {result:?}");
355            }
356        }
357    }
358
359    // Only connect to the middleware
360    fn start_client(addr: SocketAddr, barrier: Arc<Barrier>, end_barrier: Arc<Barrier>) {
361        thread::spawn(move || {
362            barrier.wait();
363
364            let _stream = StdTcpStream::connect(addr).unwrap();
365
366            end_barrier.wait();
367        });
368    }
369
370    // Get connection from the middleware read on the socket stream.
371    // We check if we receive a valid proxy protocol header
372    fn start_backend(
373        addr: SocketAddr,
374        barrier: Arc<Barrier>,
375        end_barrier: Arc<Barrier>,
376    ) -> JoinHandle<()> {
377        let listener = StdTcpListener::bind(addr).expect("could not start backend");
378
379        thread::spawn(move || {
380            barrier.wait();
381
382            let mut buf: [u8; 28] = [0; 28];
383            let (mut conn, _) = listener
384                .accept()
385                .expect("could not accept connection from light middleware");
386            println!("backend got a connection from the middleware");
387
388            let mut index = 0usize;
389            loop {
390                if index >= 28 {
391                    break;
392                }
393
394                match conn.read(&mut buf[index..]) {
395                    Err(e) => match e.kind() {
396                        ErrorKind::WouldBlock => continue,
397                        e => {
398                            end_barrier.wait();
399                            panic!("read error: {e:?}");
400                        }
401                    },
402                    Ok(sz) => {
403                        println!("backend read {sz} bytes");
404                        index += sz;
405                    }
406                }
407            }
408
409            match parse_v2_header(&buf) {
410                Ok((_, _)) => println!("complete header received"),
411                err => {
412                    end_barrier.wait();
413                    panic!("incorrect proxy protocol header received: {err:?}");
414                }
415            };
416
417            end_barrier.wait();
418        })
419    }
420}