better-socks 0.1.2

Asynchronous SOCKS proxy support for Rust
Documentation
# better-socks architecture

Asynchronous SOCKS5 client for Tokio. CONNECT, BIND, and UDP ASSOCIATE
(RFC 1928) plus username/password auth (RFC 1929, cleartext). Optional `tor`
feature adds Tor `RESOLVE` / `RESOLVE_PTR`. No TLS-to-proxy, no SOCKS4, no
GSSAPI.

# Layers

```mermaid
flowchart TB
    subgraph consumers ["consumers"]
        app["app TcpStream / UnixStream"]
        chain["chainproxy example"]
        tor["tor / socket examples"]
    end

    subgraph rust ["src — Rust API"]
        lib["lib.rs — ToProxyAddrs, TargetAddr, Authentication"]
        tcp["tcp.rs — SocksConnector, Socks5Stream, Socks5Listener"]
        udp["udp.rs — Socks5UdpFramed, Socks5UdpCodec"]
        err["error.rs — Error"]
    end

    subgraph tokio ["Tokio"]
        dns["lookup_host"]
        tcpio["TcpStream AsyncRead / AsyncWrite"]
        udpio["UdpSocket + UdpFramed"]
    end

    app --> tcp
    chain --> tcp
    tor --> tcp
    lib --> tcp
    lib --> udp
    tcp --> err
    udp --> tcp
    udp --> err
    lib -->|"str / host:port"| dns
    tcp --> tcpio
    udp --> udpio
    udp --> tcpio
```

# Public types

```mermaid
flowchart TB
    subgraph lib_t ["lib.rs"]
        TPA[ToProxyAddrs]
        PAS[ProxyAddrsStream]
        ITA[IntoTargetAddr]
        TA["TargetAddr Ip / Domain"]
        Auth["Authentication None / Password"]
        E[Error]
        R["Result T"]
    end

    subgraph tcp_t ["tcp.rs"]
        SC[SocksConnector]
        S5S[Socks5Stream S]
        S5L[Socks5Listener S]
        Cmd["Command Connect Bind Associate TorResolve TorResolvePtr"]
    end

    subgraph udp_t ["udp.rs"]
        SUF[Socks5UdpFramed]
        SUC[Socks5UdpCodec]
        SUM[Socks5UdpMessage]
    end

    TPA --> PAS
    ITA --> TA
    TPA --> SC
    ITA --> SC
    Auth --> SC
    Cmd --> SC
    SC --> S5S
    S5S --> S5L
    S5S --> SUF
    SUC --> SUF
    SUC --> SUM
    E --> R
```

# Address conversion

```mermaid
flowchart TB
    subgraph proxy ["ToProxyAddrs — resolve proxy"]
        sa["SocketAddr / IpAddr,u16 / V4 / V6"]
        slice["&[SocketAddr]"]
        host["str / &str,u16"]
        sa -->|"Once Ready Ok"| once[Once stream]
        slice -->|"Iter"| pas[ProxyAddrsStream]
        host -->|"tokio lookup_host"| pas
        pas -->|"Lookup then Iter / Done"| addrs[SocketAddr]
    end

    subgraph target ["IntoTargetAddr — SOCKS DST"]
        ip["SocketAddr family"]
        pair["&str,u16 / String,u16"]
        s["&str / String host:port"]
        ip --> TA_ip[TargetAddr::Ip]
        pair -->|"parse IpAddr else Domain"| TA
        s -->|"parse SocketAddr else rsplit :"| TA
        TA_ip --> TA[TargetAddr]
        TA -->|"len > 255"| inv[Error::InvalidTargetAddress]
        TA_ip -->|"try_from"| sock[SocketAddr]
        TA -->|"Domain try_from"| inv
        TA -.->|"ToSocketAddrs local DNS"| leak["leaks query; prefer Domain to proxy"]
    end
```

# CONNECT lifecycle

```mermaid
flowchart LR
    P[P: ToProxyAddrs] --> fuse["to_proxy_addrs fuse"]
    T[T: IntoTargetAddr] --> tgt[TargetAddr]
    auth{"password?"}
    auth -->|no| none[Authentication::None]
    auth -->|yes| pw[Authentication::Password]
    none --> val[validate_auth]
    pw --> val
    val -->|len > 255| iae[Error::InvalidAuthValues]
    fuse --> SC[SocksConnector]
    tgt --> SC
    val --> SC
    SC --> exec{"socket given?"}
    exec -->|no| loop["TcpStream::connect each addr"]
    exec -->|yes| sock[execute_with_socket]
    loop -->|all fail| last["last Io / ProxyServerUnreachable"]
    loop -->|ok| sock
    sock --> hs[authenticate]
    hs --> req[prepare_send_request]
    req --> reply[receive_reply]
    reply --> rewrite["rewrite private IPv4 bind if public proxy"]
    rewrite --> S5S[Socks5Stream]
    S5S --> io["AsyncRead / AsyncWrite passthrough"]
```

# Handshake and request

```mermaid
flowchart TB
    subgraph method ["method selection RFC 1928"]
        sendm["VER 5, NMETHODS, METHODS"]
        recvm["VER, METHOD"]
        sendm --> recvm
        recvm -->|0x00| noauth[no auth]
        recvm -->|0x02| rfc1929[RFC 1929]
        recvm -->|0xff| na[Error::NoAcceptableAuthMethods]
        recvm -->|other| ua[Error::UnknownAuthMethod]
        recvm -->|"VER != 5"| irv[Error::InvalidResponseVersion]
    end

    subgraph pass ["username/password RFC 1929"]
        sendp["VER 1, ULEN, UNAME, PLEN, PASSWD"]
        recvp["VER 1, STATUS"]
        sendp --> recvp
        recvp -->|"STATUS != 0"| paf[Error::PasswordAuthFailure]
        recvp -->|"VER != 1"| irv
        none_auth["Authentication::None + METHOD 0x02"] --> ar[Error::AuthorizationRequired]
    end

    subgraph req ["request"]
        hdr["VER 5, CMD, RSV 0, ATYP"]
        a1["ATYP 1 IPv4 + port"]
        a4["ATYP 4 IPv6 + port"]
        a3["ATYP 3 domain + port"]
        hdr --> a1
        hdr --> a4
        hdr --> a3
    end

    subgraph reply ["reply"]
        rh["VER 5, REP, RSV, ATYP + BND"]
        rh -->|REP 0x00| ok[TargetAddr from BND]
        rh -->|0x01..0x08| socks_err["GeneralSocksServerFailure .. AddressTypeNotSupported"]
        rh -->|other REP| unk[Error::UnknownError]
        rh -->|"RSV != 0"| irb[Error::InvalidReservedByte]
        rh -->|"ATYP unknown"| uat[Error::UnknownAddressType]
    end

    recvm --> rfc1929
    rfc1929 --> sendp
    noauth --> hdr
    recvp --> hdr
    a1 --> rh
    a4 --> rh
    a3 --> rh
```

# BIND lifecycle

```mermaid
flowchart LR
    bind["Socks5Listener::bind / bind_with_socket"] --> cmd[Command::Bind]
    cmd --> SC[SocksConnector execute]
    SC --> L[Socks5Listener]
    L --> ba["bind_addr = first BND"]
    ba --> fwd["forward bind_addr to peer"]
    fwd --> acc["accept"]
    acc --> r2["receive_reply second BND"]
    r2 --> S5S["Socks5Stream peer via proxy"]
```

# UDP ASSOCIATE

```mermaid
flowchart TB
    subgraph setup ["Socks5UdpFramed::connect"]
        bindu{"bind_addr?"}
        bindu -->|None| any["UdpSocket 0.0.0.0:0"]
        bindu -->|Some| spec[UdpSocket::bind]
        any --> local[local_addr]
        spec --> local
        local --> assoc["Socks5Stream::associate"]
        assoc --> framed["UdpFramed Socks5UdpCodec"]
        assoc --> relay["relay_socket_addr BND"]
        relay -->|"Ip"| sa[socks_addr]
        relay -->|"Domain"| inv[Error::InvalidTargetAddress]
    end

    subgraph io ["I/O"]
        sink["Sink Bytes, TargetAddr"]
        sink --> enc["encode header + payload"]
        enc --> send["send to socks_addr"]
        stream["Stream"]
        stream --> udp_in["framed.poll_next datagram"]
        stream --> tcp_ctl["poll_read control TCP"]
        tcp_ctl -->|EOF| end["Stream None"]
        tcp_ctl -->|data| ignore["ignore, keep waiting UDP"]
        tcp_ctl -->|Io| eio[Error::Io]
        udp_in --> dec[decode Socks5UdpMessage]
    end
```

# UDP codec

```mermaid
flowchart LR
    subgraph pkt ["RFC 1928 UDP request"]
        rsv["RSV 2"]
        frag["FRAG 1"]
        atyp["ATYP"]
        dst["DST.ADDR + PORT"]
        data[DATA]
    end

    subgraph enc ["Encoder Bytes, TargetAddr"]
        e4["ATYP 1 IPv4"]
        e6["ATYP 4 IPv6"]
        ed["ATYP 3 domain"]
        ed -->|"len > 255"| ol[Error::InvalidTargetAddress]
    end

    subgraph dec ["Decoder"]
        dshort["len < 4 truncated"]
        drsv["RSV != 0 InvalidReservedByte"]
        dfrag["FRAG != 0 FragmentationNotSupported"]
        datyp["ATYP 1 / 4 / 3"]
        dunk["unknown ATYP"]
    end

    rsv --> enc
    frag --> enc
    atyp --> enc
    dst --> enc
    data --> enc
    rsv --> dec
    frag --> dec
    atyp --> dec
```

# Private IPv4 rewrite

```mermaid
flowchart TB
    exec["SocksConnector::execute after CONNECT"]
    exec --> chk{"BND is IPv4 private AND proxy addr is public V4?"}
    chk -->|yes| rw["replace BND IP with proxy public IP"]
    chk -->|no| keep[keep BND]
    sock["execute_with_socket"] -.->|"no rewrite"| keep
```

Applied so UDP ASSOCIATE / BIND traffic is sent to a reachable host when a
public proxy reports a LAN bind address. Pre-opened sockets skip the rewrite.

# Command map

```mermaid
flowchart LR
    subgraph api ["public entry"]
        c["Socks5Stream::connect"]
        cp["connect_with_password"]
        cs["connect_with_socket"]
        a["associate"]
        ap["associate_with_password"]
        b["Socks5Listener::bind"]
        tr["tor_resolve"]
        tp["tor_resolve_ptr"]
        uf["Socks5UdpFramed::connect"]
    end

    subgraph cmd ["Command"]
        C[Connect 0x01]
        B[Bind 0x02]
        A[Associate 0x03]
        TR["TorResolve 0xF0"]
        TPTR["TorResolvePtr 0xF1"]
    end

    c --> C
    cp --> C
    cs --> C
    a --> A
    ap --> A
    uf --> a
    b --> B
    tr --> TR
    tp --> TPTR
```

# Chain proxy

```mermaid
flowchart LR
    t0["TcpStream::connect proxy0"] --> s1["connect_with_socket proxy1"]
    s1 --> s2["connect_with_socket dest"]
    s2 --> bytes["AsyncRead / AsyncWrite nested Socks5Stream"]
```

Inner `Socks5Stream` is `AsyncRead + AsyncWrite`, so it is a valid socket for
the next hop. Tor Unix sockets use the same `*_with_socket` path.

# Errors

```mermaid
flowchart TB
    subgraph local ["client-side"]
        io[Io]
        inv[InvalidTargetAddress]
        iae[InvalidAuthValues]
        unreach[ProxyServerUnreachable]
        irv[InvalidResponseVersion]
        irb[InvalidReservedByte]
        uat[UnknownAddressType]
        frag[FragmentationNotSupported]
        ar[AuthorizationRequired]
    end

    subgraph auth_e ["auth"]
        na[NoAcceptableAuthMethods]
        ua[UnknownAuthMethod]
        paf[PasswordAuthFailure]
    end

    subgraph socks ["reply REP"]
        g[GeneralSocksServerFailure]
        r[ConnectionNotAllowedByRuleset]
        n[NetworkUnreachable]
        h[HostUnreachable]
        c[ConnectionRefused]
        t[TtlExpired]
        cmd[CommandNotSupported]
        at[AddressTypeNotSupported]
        unk[UnknownError]
    end
```