~zethra/public-inbox

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
4 3

[PATCH stargazer] Add proxy protocol support

Details
Message ID
<20240806172540.633245-1-bj@benjaminja.com>
DKIM signature
pass
Download raw message
Patch: +541 -11
---
 Cargo.lock              |  77 ++++++-
 Cargo.toml              |   5 +
 doc/stargazer-ini.scd   |   9 +
 doc/stargazer.1.txt     |   2 +-
 doc/stargazer.ini.5.txt |  12 +-
 src/config.rs           |   6 +
 src/main.rs             | 441 +++++++++++++++++++++++++++++++++++++++-
 7 files changed, 541 insertions(+), 11 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index de04070..d933cb2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -41,7 +41,7 @@ checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "syn 2.0.60",
 "synstructure",
]

@@ -53,7 +53,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "syn 2.0.60",
]

[[package]]
@@ -271,6 +271,12 @@ dependencies = [
 "serde",
]

[[package]]
name = "bytes"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"

[[package]]
name = "cc"
version = "1.0.96"
@@ -430,7 +436,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "syn 2.0.60",
]

[[package]]
@@ -442,6 +448,12 @@ dependencies = [
 "const-random",
]

[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"

[[package]]
name = "errno"
version = "0.3.8"
@@ -799,6 +811,15 @@ dependencies = [
 "hashbrown",
]

[[package]]
name = "osrandom"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3347b4fd9db5f924740f8101beec9a63d0b48161421e94217b0647818b140df"
dependencies = [
 "cc",
]

[[package]]
name = "parking"
version = "2.2.0"
@@ -884,6 +905,16 @@ dependencies = [
 "unicode-ident",
]

[[package]]
name = "proxy-protocol"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e50c72c21c738f5c5f350cc33640aee30bf7cd20f9d9da20ed41bce2671d532"
dependencies = [
 "bytes",
 "snafu",
]

[[package]]
name = "quote"
version = "1.0.36"
@@ -1067,7 +1098,7 @@ checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "syn 2.0.60",
]

[[package]]
@@ -1121,6 +1152,27 @@ dependencies = [
 "autocfg",
]

[[package]]
name = "snafu"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7"
dependencies = [
 "doc-comment",
 "snafu-derive",
]

[[package]]
name = "snafu-derive"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 1.0.109",
]

[[package]]
name = "socket2"
version = "0.4.10"
@@ -1173,7 +1225,9 @@ dependencies = [
 "log",
 "mime_guess",
 "once_cell",
 "osrandom",
 "percent-encoding",
 "proxy-protocol",
 "rcgen",
 "regex",
 "rust-ini",
@@ -1194,6 +1248,17 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"

[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-ident",
]

[[package]]
name = "syn"
version = "2.0.60"
@@ -1213,7 +1278,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "syn 2.0.60",
]

[[package]]
@@ -1233,7 +1298,7 @@ checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "syn 2.0.60",
]

[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 22779c3..3c86871 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -53,6 +53,7 @@ mime_guess = "2.0.4"
socket2 = "0.5.7"
percent-encoding = "2.3.1"
uriparse = "0.6"
proxy-protocol = "0.5.0"

anyhow = "1.0.82"
once_cell = "1.19.0"
@@ -89,3 +90,7 @@ features = ["std"]
[profile.release]
lto = "fat"
codegen-units = 1

[dev-dependencies]
osrandom = "0.1.0"

diff --git a/doc/stargazer-ini.scd b/doc/stargazer-ini.scd
index 2c23867..c03cfa4 100644
--- a/doc/stargazer-ini.scd
+++ b/doc/stargazer-ini.scd
@@ -81,6 +81,15 @@ that *stargazer* will server requests to. At least one route must be specified.
	default. *Warning*, if this is set, large files and cgi scripts may be cut
	off before their response finishes.

*proxy-protocol*
	Whether or not to expect a proxy protocol header before the tls connection.
	It is generally used with a reverse proxy in situations where you need to
	know the real IP address of the client. Off by default.

	See the formal specification of proxy protocol:

	http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt

## TLS KEYS

The following keys are accepted under the *[:tls]* section:
diff --git a/doc/stargazer.1.txt b/doc/stargazer.1.txt
index 1f24b10..4a190f0 100644
--- a/doc/stargazer.1.txt
+++ b/doc/stargazer.1.txt
@@ -51,4 +51,4 @@ AUTHORS
       bugs/patches  can  be  submitted by email to ~zethra/public-in‐
       box@lists.sr.ht with the prefix stargazer.

                              2024-08-03                  stargazer(1)
                              2024-08-05                  stargazer(1)
diff --git a/doc/stargazer.ini.5.txt b/doc/stargazer.ini.5.txt
index 826bff3..fa5c67e 100644
--- a/doc/stargazer.ini.5.txt
+++ b/doc/stargazer.ini.5.txt
@@ -83,6 +83,16 @@ CONFIGURATION KEYS
           Warning, if this is set, large files and cgi scripts may be
           cut off before their response finishes.

       proxy-protocol
           Whether or not to expect a proxy protocol header before the
           tls connection. It is designed for use with a reverse proxy
           in  situations where you need to know the IP address of the
           client. Off by default.

           See the formal specification of proxy protocol:

           http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt

   TLS KEYS
       The following keys are accepted under the [:tls] section:

@@ -410,4 +420,4 @@ AUTHORS
       bugs/patches can be submitted by  email  to  ~zethra/public-in‐
       box@lists.sr.ht with the prefix stargazer.

                              2024-08-03              stargazer.ini(5)
                              2024-08-05              stargazer.ini(5)
diff --git a/src/config.rs b/src/config.rs
index 7f1196b..5d5cc25 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -60,6 +60,8 @@ pub struct Config {
    pub cert_lifetime: u64,
    /// Should stargazer log full or partial IPs or not at all
    pub ip_log: IpLogAmount,
    /// Should stargazer expect a proxy protocol header
    pub proxy_protocol: bool,
}

#[derive(Debug, Clone, Copy)]
@@ -122,6 +124,8 @@ pub fn load(config_path: impl AsRef<Path>) -> Result<Config> {
                "log-ip and log-ip-partial can't both be turned on at once"
            ),
        };

        let proxy_protocol = general.remove_yn("proxy-protocol");
        check_section_empty("general", general)?;

        let tls_section = conf
@@ -332,6 +336,7 @@ pub fn load(config_path: impl AsRef<Path>) -> Result<Config> {
            regen_certs,
            cert_lifetime,
            ip_log,
            proxy_protocol,
        })
    })();
    res.with_context(|| {
@@ -381,6 +386,7 @@ pub fn dev_config() -> Result<Config> {
        regen_certs: true,
        cert_lifetime: 0,
        ip_log: IpLogAmount::Full,
        proxy_protocol: true,
    };
    fs::create_dir_all(&conf.store).with_context(|| {
        format!(
diff --git a/src/main.rs b/src/main.rs
index b0fa9ba..45a86b4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -35,12 +35,14 @@ use futures_rustls::{server::TlsStream, TlsAcceptor};
use get_file::get_file;
use log::{debug, error};
use once_cell::sync::{Lazy, OnceCell};
use proxy_protocol::ProxyHeader;
use router::{route, Request, Route};
use std::clone::Clone;
use std::convert::TryFrom;
use std::net::SocketAddr;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::panic::catch_unwind;
use std::slice;
use std::sync::Arc;
use std::{process::exit, str, thread, time::Duration};

@@ -234,15 +236,129 @@ async fn exit_on_sig() -> Result<()> {
    Ok(future::pending::<()>().await)
}

/// Parse the proxy protocol given a peek buffer and a stream.
///
/// It is separated out from handle_proxy_protocol for testing purposes.
async fn parse_proxy_protocol<R: AsyncReadExt + Unpin>(
    peek: &[u8],
    stream: &mut R,
) -> anyhow::Result<Option<ProxyHeader>> {
    if peek.starts_with(b"PROXY ") {
        // V1 - Read until \r\n
        let mut buf = Vec::<u8>::with_capacity(peek.len());
        loop {
            let mut b = 0u8;
            stream.read_exact(slice::from_mut(&mut b)).await?;
            buf.push(b);
            if b == b'\n' {
                break;
            }
        }
        let mut buf = buf.as_slice();

        Ok(Some(proxy_protocol::parse(&mut buf)?))
    } else if peek.starts_with(&[
        0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A,
    ]) {
        // V2 - Read header length bytes after the first 16 at [14..16]

        let mut header = [0u8; 16];
        stream.read_exact(&mut header).await?;
        let len = u16::from_be_bytes([header[14], header[15]]);

        let mut buf = vec![0u8; 16 + len as usize];
        let buf_slice = buf.as_mut_slice();
        buf_slice[..16].copy_from_slice(&header);
        stream.read_exact(&mut buf_slice[16..]).await?;
        let mut buf = buf.as_slice();

        Ok(Some(proxy_protocol::parse(&mut buf)?))
    } else {
        Ok(None)
    }
}

/// Try to parse and apply a proxy protocol header.
///
/// * If the header is not found, then no data is read from the stream and no
///     action is taken.
/// * If the header is found: `remote_addr` will be swapped with the header's
///     source addr.
/// * If the header is found, but contains invalid data: no action is taken.
/// * If the header is found, but could not be parsed: an error is returned and
///     the connection should be closed.
async fn handle_proxy_protocol(
    stream: &mut TcpStream,
    remote_addr: &mut SocketAddr,
) -> anyhow::Result<()> {
    use proxy_protocol::{
        version1::ProxyAddresses as ProxyAddrV1,
        version2::ProxyAddresses as ProxyAddrV2,
    };
    // proxy-protocol doesn't support async streams, so in order to parse the
    // proxy protocol, we need to pre-read the header before passing it in
    // without reading into tls data.
    //
    // First, check if there is a proxy protocol header with the first 12 bytes.
    // Second, read the whole proxy protocol header.
    // Third, pass the raw data into proxy-protocol to get parsed.

    let mut header = [0u8; 12];
    let n = stream.peek(&mut header).await?;
    let header = &header[..n];

    match parse_proxy_protocol(header, stream).await? {
        Some(ProxyHeader::Version1 { addresses }) => match addresses {
            ProxyAddrV1::Ipv4 {
                source,
                destination: _,
            } => *remote_addr = SocketAddr::V4(source),
            ProxyAddrV1::Ipv6 {
                source,
                destination: _,
            } => *remote_addr = SocketAddr::V6(source),
            _ => {}
        },
        Some(ProxyHeader::Version2 {
            command: _,
            transport_protocol: _,
            addresses,
        }) => match addresses {
            ProxyAddrV2::Ipv4 {
                source,
                destination: _,
            } => *remote_addr = SocketAddr::V4(source),
            ProxyAddrV2::Ipv6 {
                source,
                destination: _,
            } => *remote_addr = SocketAddr::V6(source),
            _ => {}
        },
        _ => {}
    }
    Ok(())
}

// This is a separate function for ease of error handling
async fn start_task(
    stream: TcpStream,
    mut stream: TcpStream,
    acceptor: TlsAcceptor,
    remote_addr: SocketAddr,
    mut remote_addr: SocketAddr,
    server_port: u16,
) {
    use anyhow::Context;
    // Accept tsl connection

    if CONF.proxy_protocol {
        if let Err(e) = handle_proxy_protocol(&mut stream, &mut remote_addr)
            .await
            .context("Error parsing proxy protocol")
        {
            debug!("{:#}", e);
            return;
        }
    }

    // Accept tls connection
    match acceptor
        .accept(stream)
        .await
@@ -593,3 +709,322 @@ impl From<GemError> for ErrorLogInfo {
        }
    }
}

#[cfg(test)]
mod test {
    use std::time::{Duration, Instant};

    use super::*;

    use async_net::SocketAddrV4;
    use proxy_protocol::{
        version1::ProxyAddresses as ProxyAddrV1,
        version2::{
            ProxyAddresses as ProxyAddrV2, ProxyCommand, ProxyTransportProtocol,
        },
        ParseError, ProxyHeader,
    };

    fn parse_proxy(body: &[u8]) -> anyhow::Result<Option<ProxyHeader>> {
        parse_proxy_len(body).0
    }
    fn parse_proxy_len(
        body: &[u8],
    ) -> (anyhow::Result<Option<ProxyHeader>>, usize) {
        async_io::block_on(async {
            let peek = &body[..body.len().min(12)];
            let mut stream = body;
            let output = parse_proxy_protocol(peek, &mut stream).await;
            (output, body.len() - stream.len())
        })
    }

    fn random_u8() -> u8 {
        let rng: [u8; 1] = osrandom::to_array().unwrap();
        rng[0]
    }
    #[inline]
    fn random_bool() -> bool {
        random_u8() & 1 == 0
    }

    fn random_addr_v4() -> SocketAddrV4 {
        let addr = u32::from_le_bytes(osrandom::to_array().unwrap());
        let port = u16::from_le_bytes(osrandom::to_array().unwrap());
        SocketAddrV4::new(Ipv4Addr::from_bits(addr), port)
    }
    fn random_addr_v6() -> SocketAddrV6 {
        let addr = u128::from_le_bytes(osrandom::to_array().unwrap());
        let port = u16::from_le_bytes(osrandom::to_array().unwrap());
        SocketAddrV6::new(Ipv6Addr::from_bits(addr), port, 0, 0)
    }

    fn random_header() -> ProxyHeader {
        if random_bool() {
            let addresses = if random_bool() {
                ProxyAddrV1::Ipv4 {
                    source: random_addr_v4(),
                    destination: random_addr_v4(),
                }
            } else {
                ProxyAddrV1::Ipv6 {
                    source: random_addr_v6(),
                    destination: random_addr_v6(),
                }
            };
            ProxyHeader::Version1 { addresses }
        } else {
            let addresses = match random_u8() & 0b11 {
                0 => ProxyAddrV2::Unspec,
                1 => ProxyAddrV2::Ipv4 {
                    source: random_addr_v4(),
                    destination: random_addr_v4(),
                },
                2 => ProxyAddrV2::Ipv6 {
                    source: random_addr_v6(),
                    destination: random_addr_v6(),
                },
                3 => ProxyAddrV2::Unix {
                    source: osrandom::to_array().unwrap(),
                    destination: osrandom::to_array().unwrap(),
                },
                _ => unreachable!(),
            };
            ProxyHeader::Version2 {
                command: match random_bool() {
                    true => ProxyCommand::Local,
                    false => ProxyCommand::Proxy,
                },
                transport_protocol: match random_u8() & 0b11 {
                    0 => ProxyTransportProtocol::Unspec,
                    1 => ProxyTransportProtocol::Stream,
                    2 => ProxyTransportProtocol::Datagram,
                    3 => ProxyTransportProtocol::Stream,
                    _ => unreachable!(),
                },
                addresses,
            }
        }
    }

    /// Perform a simple check that v1 headers parse properly
    #[test]
    fn test_proxy_proto_v1() {
        assert_eq!(parse_proxy(b"No header here").unwrap(), None);

        let hdr = ProxyHeader::Version1 {
            addresses: ProxyAddrV1::Ipv4 {
                source: random_addr_v4(),
                destination: random_addr_v4(),
            },
        };
        let data = proxy_protocol::encode(hdr.clone()).unwrap();
        assert_eq!(parse_proxy(&data).unwrap(), Some(hdr));

        let hdr = ProxyHeader::Version1 {
            addresses: ProxyAddrV1::Ipv6 {
                source: random_addr_v6(),
                destination: random_addr_v6(),
            },
        };
        let data = proxy_protocol::encode(hdr.clone()).unwrap();
        assert_eq!(parse_proxy(&data).unwrap(), Some(hdr));
    }

    /// Perform a simple check that v2 headers parse properly
    #[test]
    fn test_proxy_proto_v2() {
        let hdr = ProxyHeader::Version2 {
            command: ProxyCommand::Proxy,
            transport_protocol: ProxyTransportProtocol::Stream,
            addresses: ProxyAddrV2::Ipv4 {
                source: random_addr_v4(),
                destination: random_addr_v4(),
            },
        };
        let data = proxy_protocol::encode(hdr.clone()).unwrap();
        assert_eq!(parse_proxy(&data).unwrap(), Some(hdr));

        let hdr = ProxyHeader::Version2 {
            command: ProxyCommand::Proxy,
            transport_protocol: ProxyTransportProtocol::Stream,
            addresses: ProxyAddrV2::Ipv6 {
                source: random_addr_v6(),
                destination: random_addr_v6(),
            },
        };
        let data = proxy_protocol::encode(hdr.clone()).unwrap();
        assert_eq!(parse_proxy(&data).unwrap(), Some(hdr));
    }

    /// Check against as many as possible inputs to make sure that all valid headers do not error
    /// and all invalid headers match the error received by proxy_protocol
    ///
    /// This will always run for `duration` seconds
    #[test]
    fn test_proxy_proto_fuzz() {
        let duration = Duration::from_secs(15);
        let deadline = Instant::now() + duration;

        let mut count = 0;
        let mut v1_count = 0;
        let mut v2_count = 0;

        while Instant::now() < deadline {
            // Create a random header and verify that it parses correctly
            let header = random_header();
            let mut data = proxy_protocol::encode(header.clone()).unwrap();
            let len = data.len();
            let extra: [u8; 64] = osrandom::to_array().unwrap();
            data.extend_from_slice(&extra);

            let mut data = data.to_vec();
            count += 1;

            assert_eq!(parse_proxy(&data).unwrap(), Some(header));

            let is_ascii = data.starts_with(b"PROXY ");
            if is_ascii {
                v1_count += 1 + len * 8;
            } else {
                v2_count += 1 + len * 8;
            }

            // perform N permutations, corrupting the header 1 random bit at a time where N is the
            // number of bits in the header
            for _ in 0..len * 8 {
                let i = random_u8() as usize % len;
                let o = random_u8() & 0b111;
                data[i] = data[i] ^ (1 << o);

                count += 1;

                let genbuf = || {
                    if is_ascii {
                        data[..len]
                            .iter()
                            .map(|c| match *c >= 0x20 && *c <= 0x7F {
                                true => (*c as char).to_string(),
                                false => format!("\\{c:X}"),
                            })
                            .collect::<Vec<String>>()
                            .join("")
                    } else {
                        data[..len]
                            .iter()
                            .map(|c| format!("{c:02X}"))
                            .collect::<Vec<_>>()
                            .join(" ")
                    }
                };

                let (output, read) = parse_proxy_len(&data);
                if let Err(e) = &output {
                    if let Some(_) = e.downcast_ref::<io::Error>() {
                        // This is an error with the length of the data.
                        // If we reach this point, then the input is absolutely corrupted and we
                        // cannot continue.
                        continue;
                    }
                }
                if let Ok(None) = &output {
                    if read != 0 {
                        let buf = genbuf();
                        panic!("{buf}\nThere no header, but {read} bytes were read")
                    }
                }

                let mut expected_slice = data.as_slice();
                let expected = match proxy_protocol::parse(&mut expected_slice)
                {
                    Ok(output) => Ok(Some(output)),
                    Err(ParseError::NotProxyHeader) => Ok(None),
                    Err(err) => Err(err),
                };
                let expected_read = data.len() - expected_slice.len();

                // Compare the outputs of the expected behaviour and the actual behaviour.
                // There are some edge cases because of the way we parse the data
                match (output, expected) {
                    (Ok(a), Ok(b)) => {
                        if a != b {
                            let buf = genbuf();
                            assert_eq!(a, b, "{buf}");
                        }
                        if read != expected_read {
                            let buf = genbuf();
                            assert_eq!(read, expected_read, "{buf}");
                        }
                    }
                    (Err(a), Err(b)) => {
                        // This is where results may differ. We should at least verify that the
                        // kinds of errors are the same.
                        match (a.downcast::<ParseError>().unwrap(), b) {
                            (ParseError::NotProxyHeader, _) => {
                                let buf = genbuf();
                                panic!("{buf}\nIt should not be possible for a to return a NotProxyHeader error")
                            }
                            (
                                ParseError::InvalidVersion { version: a },
                                ParseError::InvalidVersion { version: b },
                            ) => {
                                let buf = genbuf();
                                assert_eq!(
                                    ParseError::InvalidVersion { version: a }
                                        .to_string(),
                                    ParseError::InvalidVersion { version: b }
                                        .to_string(),
                                    "{buf}"
                                );
                            }
                            (
                                ParseError::Version1 { source: _ },
                                ParseError::Version1 { source: _ },
                            ) => {}
                            (
                                ParseError::Version2 { source: _ },
                                ParseError::Version2 { source: _ },
                            ) => {}
                            (a, b) => {
                                let buf = genbuf();
                                assert_eq!(
                                    a.to_string(),
                                    b.to_string(),
                                    "{buf}"
                                );
                            }
                        }
                    }
                    (Err(a), b) => match a.downcast::<ParseError>().unwrap() {
                        ParseError::Version1 { source } => {
                            match source {
                                // (V1 error - unexpected eof, an okay result)
                                // This is a rare occurrence, but it is possible that a technically
                                // valid header gets read as invalid because there is a \n hidden
                                // inside the header. This is okay so should not panic.
                                proxy_protocol::version1::ParseError::UnexpectedEof => {},
                                a => {
                                    let buf = genbuf();
                                    panic!("{buf}\n{a:?} != {b:?}")
                                }
                            }
                        }
                        a => {
                            let buf = genbuf();
                            panic!("{buf}\n{a:?} != {b:?}")
                        }
                    },
                    (a, b) => {
                        let buf = genbuf();
                        panic!("{buf}\n{a:?} != {b:?}")
                    }
                }
            }
        }
        println!(
            "Checked {count} headers ({}% v1 headers and {}% v2 headers)",
            (v1_count as f64 / count as f64 * 100.0).round(),
            (v2_count as f64 / count as f64 * 100.0).round()
        )
    }
}
-- 
2.30.2

[stargazer/patches] build success

builds.sr.ht <builds@sr.ht>
Details
Message ID
<D3909JQOM8OO.2QMD2T6N0RN0T@fra01>
In-Reply-To
<20240806172540.633245-1-bj@benjaminja.com> (view parent)
DKIM signature
missing
Download raw message
stargazer/patches: SUCCESS in 3m29s

[Add proxy protocol support][0] from [Benjamin Jacobs][1]

[0]: https://lists.sr.ht/~zethra/public-inbox/patches/54318
[1]: bj@benjaminja.com

✓ #1295221 SUCCESS stargazer/patches/linux.yml   https://builds.sr.ht/~zethra/job/1295221
✓ #1295220 SUCCESS stargazer/patches/freebsd.yml https://builds.sr.ht/~zethra/job/1295220
Details
Message ID
<D390CSTX3GO0.1W0L2LXLHCE1K@benjaminja.com>
In-Reply-To
<20240806172540.633245-1-bj@benjaminja.com> (view parent)
DKIM signature
pass
Download raw message
This patch adds support for proxy protocol headers. It allows for proxy
servers to pass the real client IP address to the server. The header is
injected before the tls connection by the proxy server so that the tls
connection can be passed through unmodified.

Here is a pretty good article that describes the proxy protocol.
https://seriousben.com/posts/2020-02-exploring-the-proxy-protocol/

I have a unique use case where I have two separate gemini servers running
on the same machine that needs to be accessible from port 1965 and some
of the services use the peer address as a part of session management. To
solve this, I'm using a tcp proxy that checks the tls sni to see where
the traffic should be redirected to. The proxy protocol header is then
injected into the proxied request so the server will know where the
traffic came from.

The feature is disabled by default and can be enabled with the
'proxy-protocol' setting. I have also added some docs in stargazer.ini. I
don't know if the documentation is adequate though.

The patch is live with `proxy-protocol = on` on my server at
gemini://benjaminja.com
Details
Message ID
<D3973H9ZCI2S.1JT33AEELZVZD@noraa.gay>
In-Reply-To
<20240806172540.633245-1-bj@benjaminja.com> (view parent)
DKIM signature
pass
Download raw message
Hello,

Thank you for contributing to stargazer! You patch mostly looks good
but, there are a few issues. The two main ones are with the tests you
provided. 

First, you are using the `from_bits` IP addr functions, which are
unstable. Please only use code that compiles on stable Rust, preferably
code that compile on the currently documented MSRV. Second please use a
different random number library than osrandom. The C component won't
compile on my system in debug mode because it throws a warning
(`_FORTIFY_SOURCE requires compiling with optimization`) and it has
warnings as errors set. Additionally, it appears to be unmaintained. It
has one version uploaded 2 years ago and the GitHub repo linked on it's
crates.io page has been deleted.

Separately, I'm also a little worried this loop is inefficient.

> loop {
> 	let mut b = 0u8;
> 	stream.read_exact(slice::from_mut(&mut b)).await?;
> 	buf.push(b);
> 	if b == b'\n' {
> 		break;
> 	}
> }

I'm okay accepting this as is, but using a buffer reader might be
better. Some to experiment with if you'd like to.

All the best,
Sashanoraa
Details
Message ID
<D397WLK8YE9K.1NZ607FF94L1M@benjaminja.com>
In-Reply-To
<D3973H9ZCI2S.1JT33AEELZVZD@noraa.gay> (view parent)
DKIM signature
pass
Download raw message
> First, you are using the `from_bits` IP addr functions, which are
> unstable. Please only use code that compiles on stable Rust, preferably
> code that compile on the currently documented MSRV.

Okay, I'll make sure of it.

> Second please use a different random number library than osrandom. The
> C component won't compile on my system in debug mode because it throws a
> warning (`_FORTIFY_SOURCE requires compiling with optimization`) and it
> has warnings as errors set. Additionally, it appears to be unmaintained.
> It has one version uploaded 2 years ago and the GitHub repo linked on
> it's crates.io page has been deleted.

Oh shoot, I didn't realize it was dead. I'll switch to rand instead.

> Separately, I'm also a little worried this loop is inefficient.
> I'm okay accepting this as is, but using a buffer reader might be
> better. Some to experiment with if you'd like to.

This was the best I could do without changing to a buffered reader. I'll
see what I can do to make it more efficient, but I'll probably just end
up switching to a buffered reader.

Thanks,
Ben
Reply to thread Export thread (mbox)