Search code examples
dockerrustlibc

libc::recv does not return on docker


I wrote a simple packet capture in Rust on docker hosted by macOS. However, libc::recv doesn't return forever.

src/main.rs

use std::io;

fn main() -> io::Result<()> {
    let sock = unsafe {
        match libc::socket(
            libc::AF_PACKET,
            libc::SOCK_RAW,
            libc::ETH_P_ALL.to_be() as _,
        ) {
            -1 => Err(io::Error::last_os_error()),
            fd => Ok(fd),
        }
    }?;
    println!("sock: {}", sock);

    let mut buf = [0u8; 1024];
    loop {
        let len = unsafe {
            libc::recv(
                sock,
                (&mut buf[..]).as_mut_ptr() as *mut libc::c_void,
                buf.len(),
                0,
            )
        };
        println!("len: {}", len);
    }
}

Dockerfile


RUN apt-get update && apt-get install -y tcpdump

WORKDIR /app

COPY . .

ENTRYPOINT ["cargo", "run"]

run command

$ docker build . -t rust_cap && docker run -p 127.0.0.1:15006:15006/udp -it --rm --name="rust_cap" rust_cap

I try to check to send any packets into the container by tcpdump and check to call system calls by strace, they seem correct.

Second, I wrote code as same as above in C such like:

main.c

#include <stdio.h>
#include <sys/socket.h>
#include <net/ethernet.h>

int main(int argc, char **argv) {
    int sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (sock < 0) {
        perror("socket");
        exit(1);
    }
    printf("sock: %d\n", sock);

    u_char buf[1024];
    while (1) {
        int len = recv(sock, buf, sizeof(buf), 0);
        printf("len: %d\n", len);
    }
}

Dockerfile

FROM ubuntu:20.04

RUN apt-get update && apt-get install -y build-essential

COPY . .
RUN cc -o main main.c

ENTRYPOINT ["./main"]

run command

$ docker build . -t c_cap && docker run -p 127.0.0.1:15007:15007/udp -it --rm --name="c_cap" c_cap

This C code is run correctly. (I can see len: xxx on stdout each message sent by me)

Why recv doesn't return in Rust code? What am I missing?


ref. strace output

in Rust

socket(AF_PACKET, SOCK_RAW, htons(0 /* ETH_P_??? */)) = 3
write(1, "sock: 3\n", 8sock: 3
)                = 8
recvfrom(3,

in C (part of receiving buffers omitted)

socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) = 3
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
brk(NULL)                               = 0x5616f5ab4000
brk(0x5616f5ad5000)                     = 0x5616f5ad5000
write(1, "sock: 3\n", 8sock: 3
)                = 8
recvfrom(3,

FYI, I ran strace by the following way.

$ docker run -it --cap-add sys_ptrace --entrypoint="/bin/bash" $IMAGE
$ cargo build && strace /app/target/debug/xxx
# or strace /main 

Solution

  • I solved the problem myself.

    A type of libc::ETH_P_ALL passing to libc::socket is c_int aliasing to i32.

    Converting libc::ETH_P_ALL as i32 to big endian is incorrect as libc::socket parameter.

    Instead, it must to convert libc::ETH_P_ALL as u16 to big endian.

    use std::io;
    
    fn main() -> io::Result<()> {
        let sock = unsafe {
            match libc::socket(
                libc::AF_PACKET,
                libc::SOCK_RAW,
    -            libc::ETH_P_ALL.to_be() as _,
    +            (libc::ETH_P_ALL as u16).to_be() as _,
            ) {
                -1 => Err(io::Error::last_os_error()),
                fd => Ok(fd),
            }
        }?;
        println!("sock: {}", sock);
    
        let mut buf = [0u8; 1024];
        loop {
            let len = unsafe {
                libc::recv(
                    sock,
                    (&mut buf[..]).as_mut_ptr() as *mut libc::c_void,
                    buf.len(),
                    0,
                )
            };
            println!("len: {}", len);
        }
    }
    

    FYI,

    use libc;
    
    fn main() {
        assert_eq!(0x0003, libc::ETH_P_ALL);
        assert_eq!(0x3000000, libc::ETH_P_ALL.to_be());  // <- incorrect in the question
        assert_eq!(0x0003, libc::ETH_P_ALL as u16);
        assert_eq!(0x300, (libc::ETH_P_ALL as u16).to_be());  // <- correct
    }