Best practices around streaming between two unix domain sockets

I've written this up on StackOverflow if anyone wants the Internet points. I'm trying to find the best way to chain two UNIX domain sockets together. I want to proxy requests between each, while also inspecting the first line.

The intended use case is for Docker Engine API HTTP traffic. I want to be able to inspect the URL and short circuit the pipeline (say with a 401 response)

use std::io::{BufRead, BufReader};
use std::io::BufWriter;
use std::os::unix::net::{UnixStream,UnixListener};
use std::thread;
use std::io::Write;
use std::io;
use std::fmt::format;

pub static DOCKER_SOCKET_PATH: &'static str = "/run/docker.sock";

fn handle_client(mut proxy_stream: UnixStream) {

    let mut docker_stream = UnixStream::connect(DOCKER_SOCKET_PATH).unwrap();
    let proxy_reader = BufReader::new(&proxy_stream);
    let mut proxy_writer = BufWriter::new(&proxy_stream);
    let mut docker_reader = BufReader::new(&docker_stream);
    let mut docker_writer = BufWriter::new(&docker_stream);

    for line in proxy_reader.lines() {
        let x = line.unwrap();
        println!("{}", x);
        docker_writer.write(format!("{}\n", x).as_bytes());
        if x == "" {
            break;
        }
    }
    docker_writer.write("\n".as_bytes());
    docker_writer.flush();


    for line in docker_reader.lines() {
        let y = line.unwrap();
        println!("{}", y);
        proxy_writer.write(format!("{}\n", y).as_bytes());
    }
    //proxy_writer.write("\n".as_bytes());
    proxy_writer.flush();
}

fn main() {
    let listener = UnixListener::bind("/tmp/docker-proxy.sock").unwrap();

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                thread::spawn(|| handle_client(stream));
            }
            Err(err) => {
                println!("Error: {}", err);
                break;
            }
        }
    }
}

If I attempt the use this proxy using docker -H unix:///tmp/docker-proxy.sock ps, I see the response, but I'm obviously not handling the HTTP correctly:

docker -H unix:///tmp/docker-proxy.sock ps
2021/04/04 21:27:02 Unsolicited response received on idle HTTP channel starting with "HTTP/1.1 400 Bad Request\nContent-Type: text/plain; charset=utf-8\nConnection: close\n\n400 Bad Request\n\n"; err=<nil>
2021/04/04 21:27:02 Unsolicited response received on idle HTTP channel starting with "HTTP/1.1 400 Bad Request\nContent-Type: text/plain; charset=utf-8\nConnection: close\n\n400 Bad Request\n\n"; err=<nil>
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
970cd603429f        testcodaa           "/bin/sh"           9 days ago          Up 9 days           3000/tcp            crazy_khorana

I feel like what I'm doing is really primitive: reading lines from a buffer on the incoming request and breaking on the double new line (HTTP protocol specific), and then flipping over to the other socket to write and read. Somehow with this, I'm also getting a 400 invalid back from the server. I tried removing the \n write for the docker_writer , but if I remove either that or the flush, the entire process just hangs.

Ultimately, my goal is to create a proxy that will either continue to proxy the request or return a 401 if the first line contains an API endpoint I want to restrict. What's best practice for doing something like this in Rust? I've been looking through the API/examples and I suspect I should probably extend a class I can use as a reading/write chain between the two streams in order to short circuit them, but I'm new to Rust and not sure the best way to go about doing this.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.