shadowsocks/shadowsocks-rust · error

missing destination address in msghdr

Error message

missing destination address in msghdr

What it means

On OpenBSD, the UDP relay received a packet whose recvmsg ancillary data contained no original destination address, so get_destination_addr fails with InvalidData. The relay cannot know where to forward the packet without it.

Source

Thrown at crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/openbsd.rs:307

                    }
                    (libc::IPPROTO_IPV6, IPV6_RECVDSTPORT) => {
                        let toaddr_in = &mut *(dst_addr as *mut libc::sockaddr_in6);
                        ptr::copy_nonoverlapping(
                            libc::CMSG_DATA(cmsg),
                            &(*toaddr_in).sin6_port as *const _ as *mut _,
                            mem::size_of::<libc::in_port_t>(),
                        );
                        if addr_or_port_received {
                            return Ok(());
                        } else {
                            addr_or_port_received = true
                        }
                    }
                    _ => {}
                }
                cmsg = libc::CMSG_NXTHDR(msg, cmsg);
            }
            let err = Error::new(ErrorKind::InvalidData, "missing destination address in msghdr");
            Err(err)
        })?;

        Ok(addr.as_socket().expect("SocketAddr"))
    }
}

fn recv_dest_from(socket: &UdpSocket, buf: &mut [u8]) -> io::Result<(usize, SocketAddr, SocketAddr)> {
    unsafe {
        let mut control_buf = [0u8; 64];
        let mut src_addr: libc::sockaddr_storage = mem::zeroed();

        let mut msg: libc::msghdr = mem::zeroed();
        msg.msg_name = &mut src_addr as *mut _ as *mut _;
        msg.msg_namelen = mem::size_of_val(&src_addr) as libc::socklen_t;

        let mut iov = libc::iovec {
            iov_base: buf.as_mut_ptr() as *mut _,

View on GitHub (pinned to 8eb0f0a65b)

Solutions

  1. Add/fix the pf rule using 'divert-to <port>' so UDP packets reach the socket with original destination preserved
  2. Verify the socket enabled the destination-address receive option before recvmsg
  3. Enlarge the cmsg control buffer to avoid MSG_CTRUNC dropping the address
  4. Check that the packet actually traverses pf (route/anchor ordering)
  5. Log cmsg content on failure to confirm whether the kernel provided any destination data

Example fix

// before: pass in via rdr-to only
pass in on $ext_if proto udp rdr-to 127.0.0.1 port 1080
// after: preserve original dst
pass in on $ext_if proto udp divert-to 127.0.0.1 port 1080
Defensive patterns

Strategy: validation

Validate before calling

// verify pf rule exists before starting relay
// pfctl -sr | grep 'divert-to' must show a udp divert rule

Type guard

fn cmsg_carries_dst(control: &[u8]) -> bool { /* parse cmsg level/type for dst addr */ !control.is_empty() }

Try / catch

match relay.recv_dest_from(&mut buf).await { Err(e) if e.kind()==InvalidData => { log::warn!("no divert dst; check pf"); continue; }, r => r? }

Prevention

When it happens

Trigger: recv_dest_from when the expected cmsg (DIVERT or recv dst address) is absent — packet not diverted by pf with 'divert-to', or the socket option enabling destination reception was not set.

Common situations: pf rules missing 'divert-to' for UDP, packets arriving directly at the socket in tests, or control buffer too small causing cmsg truncation.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of shadowsocks/shadowsocks-rust@8eb0f0a65b (2026-09-09). Data as JSON: /api/errors/18dac760b8fa59d4. Report an issue: GitHub.