stamparm/maltrail · error

unable to open a remote logging socket for

Error message

unable to open a remote logging socket for '{endpoint}'

What it means

The sensor could not bind a local UDP socket needed to send remote logging datagrams for this endpoint. Previously such bind failures were silently swallowed; now the sensor logs this error, increments remote_log_errors, and drops the event's datagram.

Solutions

  1. Check whether the required address family is available (ip addr / `ip -6 addr`); disable v6 remote logging or add v6 connectivity if is_v6 fails on a v4-only host.
  2. Check for port/address conflicts with ss -lunp and stop the conflicting process or change the configured bind address.
  3. Ensure the sensor process has network bind permission (containers: add CAP_NET_RAW/NET_BIND_SERVICE or adjust sandbox policy).
  4. Watch remote_log_errors metrics; bind failures persist until the environment is fixed since each event re-attempts the bind.
Defensive patterns

Strategy: validation

Validate before calling

# before starting the sensor, confirm both address families can bind
ip -4 addr show | grep -q inet  || echo "no IPv4"
ip -6 addr show | grep -q inet6 || echo "no IPv6 (v6 remote logging will fail)"
ss -lunp | grep "<bind-port>"   # detect address-in-use conflicts

Prevention

When it happens

Trigger: send_datagram finds self.sock4/sock6 is None for the required address family and UdpSocket::bind(bind) fails or returns None — e.g. the requested local bind address/port is unavailable, or the address family is not configured on the host (no IPv6 support while endpoint resolves to v6).

Common situations: Host lacks IPv6 (no v6 interfaces) but the endpoint is v6; another process holds the bound port (address in use); sandbox/container without the network capability to bind; restrictive SELinux/AppArmor policy.

Related errors


AI-assisted analysis of stamparm/maltrail@77cfb06d76 (2026-09-13). Data as JSON: /api/errors/8d1cc0c2f8c2dd77. Report an issue: GitHub.

Appendix: source

Thrown at sensor/src/output.rs:498

            }
        }
    }

    fn send_datagram(&mut self, endpoint: &str, data: &[u8]) {
        let Some(addr) = self.endpoint_addr(endpoint) else {
            self.remote_log_errors += 1;
            return;
        };

        let is_v6 = addr.is_ipv6();
        let bind: &str = if is_v6 { "[::]:0" } else { "0.0.0.0:0" };
        let sock = if is_v6 { &mut self.sock6 } else { &mut self.sock4 };
        if sock.is_none() {
            *sock = UdpSocket::bind(bind).ok();
        }
        let Some(s) = sock.as_ref() else {
            // A socket that will not bind used to be swallowed by `.ok()` and an early return.
            log_error(&format!("unable to open a remote logging socket for '{endpoint}'"), true);
            self.remote_log_errors += 1;
            return;
        };
        if s.send_to(data, addr).is_err() {
            // Drop and recreate the socket once, exactly like `_send_datagram`.
            let fresh = UdpSocket::bind(bind).ok();
            let retried = match &fresh {
                Some(f) => f.send_to(data, addr).is_ok(),
                None => false,
            };
            if is_v6 {
                self.sock6 = fresh;
            } else {
                self.sock4 = fresh;
            }
            if !retried {
                // The second failure used to be discarded outright, so a remote-only deployment
                // could lose every event while `events_written` kept climbing.

View on GitHub (pinned to 77cfb06d76)