Subject: hurd: pfinet: Raw sockets reject MSG_NOSIGNAL flag with EINVAL
Package: hurd
X-Debbugs-Cc: [email protected]
Version: 1:0.9.git20260919-1+b1
Severity: normal

Hi,

While adding Debian GNU/Hurd support to HiPerConTracer (https://www.nntb.no/~dreibh/hipercontracer/), I encountered an issue when sending packets over raw sockets using Boost.Asio.

Boost.Asio automatically sets the MSG_NOSIGNAL flag on send operations (send() / send_to()). On Linux, POSIX, and standard Hurd UDP sockets, passing MSG_NOSIGNAL to non-stream sockets is accepted/ignored. However, on GNU/Hurd raw sockets, the call fails with "Invalid argument":

Invalid argument [system:1073741846 at /usr/include/boost/asio/detail/reactive_socket_service.hpp:261:5 in function 'size_t boost::asio::detail::reactive_socket_service<Protocol>::send_to(...)']

--- REPRODUCTION ---

Attached are two minimal C++ test cases:
  - s1.cc: Raw socket (SOCK_RAW / IPPROTO_UDP) with IP_HDRINCL
  - s2.cc: Standard UDP socket (SOCK_DGRAM)

Compilation:
  g++ s1.cc -o s1 -lboost_system
  g++ s2.cc -o s2 -lboost_system

Run:
  sudo ./s1 127.0.0.1 12345 127.0.0.1 7777
  ./s2 127.0.0.1 12345 127.0.0.1 7777

Result:
  - s2 (UDP socket) succeeds on GNU/Hurd.
  - s1 (Raw socket) fails on GNU/Hurd with "Invalid argument".

Workaround in application code:
Uncommenting the following block in s1.cc before Boost header inclusion allows s1 to work on Hurd:

#if defined(__GNU__)
#include <sys/socket.h>
#undef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif

--- ROOT CAUSE IN PFINET ---

Comparing pfinet's protocol handlers reveals an inconsistency in flag bitmask checking between UDP and raw sockets.

In pfinet/linux-src/net/ipv4/udp.c:
    if (msg->msg_flags & ~(MSG_DONTROUTE|MSG_DONTWAIT|MSG_NOSIGNAL))
            return -EINVAL;

In pfinet/linux-src/net/ipv4/raw.c (raw_sendmsg):
    if (msg->msg_flags & ~(MSG_DONTROUTE|MSG_DONTWAIT))
            return(-EINVAL);

Because MSG_NOSIGNAL is omitted from the allowed flag mask in pfinet/linux-src/net/ipv4/raw.c, raw_sendmsg() returns -EINVAL whenever MSG_NOSIGNAL is set.

Adding MSG_NOSIGNAL to the permitted flag mask in pfinet/linux-src/net/ipv4/raw.c (and pfinet/linux-src/net/ipv6/raw.c if applicable) would probably align raw socket behavior with standard UDP sockets and resolve Boost.Asio compatibility on Hurd.

-- System Information:
Debian Release: forky/sid
  APT prefers unreleased
  APT policy: (500, 'unreleased'), (500, 'unstable')
Architecture: hurd-amd64 (x86_64)

Kernel: GNU-Mach 1.8+git20260805-smp-amd64/Hurd-0.9
Locale: LANG=nb_NO.UTF-8, LC_CTYPE=nb_NO.UTF-8 (charmap=UTF-8), LANGUAGE not set
Shell: /bin/sh linked to /usr/bin/dash
Init: sysvinit (via /sbin/init)

Versions of packages hurd depends on:
ii  hurd-libs0.3      1:0.9.git20260919-1+b1
ii  less              668-1
ii  libacpica0        0~20220331-7
ii  libblkid1         2.42.4-1
ii  libc0.3           2.43-7~hurd.1
ii  libdaemon0        0.14-7.1+b4
ii  libgcrypt20       1.12.4-2
ii  liblwip0t64       2.2.1+dfsg1-5
ii  libncursesw6      6.6+20260608-2
ii  libparted2t64     3.8-2
ii  libpciaccess0     0.19-2
ii  librump0          0~20250111-9
ii  librumpdev-disk0  0~20250111-9
ii  librumpdev-if0    0~20250111-9
ii  librumpdev-pci0   0~20250111-9
ii  librumpdev-usb0   0~20250111-9
ii  librumpnet0       0~20250111-9
ii  libtinfo6         6.6+20260608-2
ii  libxkbcommon0     1.7.0-2.1
ii  passwd            1:4.20.2-2
ii  sysv-rc           3.18-1
ii  xkb-data          2.48-1

Versions of packages hurd recommends:
ii  bf-utf-source  0.08+nmu2
ii  netdde         0.0.20200330-13

Versions of packages hurd suggests:
pn  hurd-doc  <none>

-- no debconf information


#include <iostream>
#include <vector>
#include <cstring>
#include <string>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/udp.h>

/*
#if defined(__GNU__)
#include <sys/socket.h>
#undef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
*/

#include <boost/asio.hpp>


#define USE_BOOST_SENDTO


// IPv4 Pseudo Header required for UDP Checksum
struct __attribute__((packed)) pseudo_header {
    uint32_t src_ip;
    uint32_t dest_ip;
    uint8_t zero;
    uint8_t protocol;
    uint16_t udp_len;
};

// Safe 16-bit word summation using memcpy (avoids unaligned pointer warnings)
uint32_t accumulate_sum(const void* buf, size_t len, uint32_t initial_sum = 0) {
    const auto* ptr = static_cast<const uint8_t*>(buf);
    uint32_t sum = initial_sum;

    while (len > 1) {
        uint16_t word;
        std::memcpy(&word, ptr, sizeof(word));
        sum += word;
        ptr += 2;
        len -= 2;
    }

    if (len == 1) {
        uint16_t word = 0;
        std::memcpy(&word, ptr, 1);
        sum += word;
    }

    return sum;
}

// Standard Internet Checksum calculation (RFC 1071)
uint16_t in_cksum(const void* buf, size_t len, uint32_t initial_sum = 0) {
    uint32_t sum = accumulate_sum(buf, len, initial_sum);

    while (sum >> 16) {
        sum = (sum & 0xFFFF) + (sum >> 16);
    }

    return static_cast<uint16_t>(~sum);
}

// Calculate UDP Checksum over pseudo-header, UDP header, and payload
uint16_t calculate_udp_checksum(const struct iphdr* ip, const struct udphdr* udp) {
    struct pseudo_header psh{};
    psh.src_ip = ip->saddr;
    psh.dest_ip = ip->daddr;
    psh.zero = 0;
    psh.protocol = IPPROTO_UDP;
    psh.udp_len = udp->len;

    uint32_t psh_sum = accumulate_sum(&psh, sizeof(psh));
    uint16_t check = in_cksum(udp, ntohs(udp->len), psh_sum);
    return (check == 0) ? 0xFFFF : check;
}

int main(int argc, char* argv[]) {
    if (argc < 5) {
        std::cerr << "Usage: " << argv[0] << " <Source IP> <Source Port> <Destination IP> <Destination Port>\n";
        return 1;
    }

    std::string src_ip = argv[1];
    uint16_t src_port = static_cast<uint16_t>(std::stoi(argv[2]));
    std::string dest_ip = argv[3];
    uint16_t dest_port = static_cast<uint16_t>(std::stoi(argv[4]));
    std::string message = "Hello from Boost raw UDP socket!";

    try {
        boost::asio::io_context io_context;

        // 1. Construct Boost Endpoint directly (validates dest_ip string)
        boost::asio::ip::udp::endpoint target_endpoint(
            boost::asio::ip::make_address(dest_ip), dest_port
        );

        // 2. Create Boost raw socket (SOCK_RAW / IPPROTO_UDP)
        boost::asio::generic::raw_protocol raw_udp(AF_INET, IPPROTO_UDP);
        boost::asio::generic::raw_protocol::socket raw_socket(io_context, raw_udp);
        int fd = raw_socket.native_handle();

        // 3. Enable IP_HDRINCL
        int on = 1;
        if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) {
            perror("setsockopt IP_HDRINCL failed");
            return 1;
        }

        // 4. Allocate buffer for IP Header + UDP Header + Payload
        size_t packet_size = sizeof(struct iphdr) + sizeof(struct udphdr) + message.size();
        std::vector<char> packet(packet_size, 0);

        // 5. Populate IPv4 Header
        auto* ip_header = reinterpret_cast<struct iphdr*>(packet.data());
        ip_header->ihl = 5;
        ip_header->version = 4;
        ip_header->tos = 0;
        ip_header->tot_len = htons(packet_size);
        ip_header->id = htons(54321);
        ip_header->frag_off = 0;
        ip_header->ttl = 64;
        ip_header->protocol = IPPROTO_UDP;
        ip_header->check = 0;

        if (inet_pton(AF_INET, src_ip.c_str(), &ip_header->saddr) <= 0 ||
            inet_pton(AF_INET, dest_ip.c_str(), &ip_header->daddr) <= 0) {
            std::cerr << "Invalid IP address format provided.\n";
            return 1;
        }

        // 6. Populate UDP Header
        auto* udp_header = reinterpret_cast<struct udphdr*>(packet.data() + sizeof(struct iphdr));
        udp_header->source = htons(src_port);
        udp_header->dest = htons(dest_port);
        udp_header->len = htons(sizeof(struct udphdr) + message.size());
        udp_header->check = 0;

        // 7. Copy Payload
        std::memcpy(packet.data() + sizeof(struct iphdr) + sizeof(struct udphdr), message.data(), message.size());

        // 8. Calculate Checksums
        ip_header->check = in_cksum(ip_header, sizeof(struct iphdr));
        udp_header->check = calculate_udp_checksum(ip_header, udp_header);

#ifdef USE_BOOST_SENDTO
#warning Using BOOST send_to() call!
        // 9. Send Raw Packet
        size_t bytes_sent = raw_socket.send_to(boost::asio::buffer(packet), target_endpoint);
#else
#warning Using POSIX sendto() call!
        // 9. Send via POSIX sendto()
        ssize_t bytes_sent = ::sendto(fd, packet.data(), packet.size(), MSG_NOSIGNAL, target_endpoint.data(), target_endpoint.size());
        if (bytes_sent < 0) {
            perror("sendto() failed");
            return 1;
        }
#endif

        std::cout << "Successfully sent " << bytes_sent << " bytes from "
                  << src_ip << ":" << src_port << " to "
                  << dest_ip << ":" << dest_port << "!\n";

    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << "\n";
        return 1;
    }

    return 0;
}
#include <iostream>
#include <string>
#include <unistd.h>

#include <boost/asio.hpp>


#define USE_BOOST_SENDTO


int main(int argc, char* argv[]) {
    if (argc < 5) {
        std::cerr << "Usage: " << argv[0] << " <Source IP> <Source Port> <Destination IP> <Destination Port>\n";
        return 1;
    }

    std::string src_ip = argv[1];
    uint16_t src_port = static_cast<uint16_t>(std::stoi(argv[2]));
    std::string dest_ip = argv[3];
    uint16_t dest_port = static_cast<uint16_t>(std::stoi(argv[4]));
    std::string message = "Hello from Boost standard UDP socket!";

    try {
        boost::asio::io_context io_context;

        // 1. Construct Source and Destination Endpoints
        boost::asio::ip::udp::endpoint source_endpoint(
            boost::asio::ip::make_address(src_ip), src_port
        );
        boost::asio::ip::udp::endpoint target_endpoint(
            boost::asio::ip::make_address(dest_ip), dest_port
        );

        // 2. Create and open a standard UDP socket
        boost::asio::ip::udp::socket udp_socket(io_context);
        udp_socket.open(boost::asio::ip::udp::v4());

        // 3. Allow address reuse and bind to the specified Source IP & Port
        udp_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
        udp_socket.bind(source_endpoint);

#ifdef USE_BOOST_SENDTO
#warning Using BOOST send_to() call!
        // 4. Send payload using Boost socket send_to
        size_t bytes_sent = udp_socket.send_to(boost::asio::buffer(message), target_endpoint);
#else
#warning Using POSIX sendto() call!
        // 4. Send payload via POSIX sendto()
        int fd = udp_socket.native_handle();
        ssize_t bytes_sent = ::sendto(fd, message.data(), message.size(), MSG_NOSIGNAL, target_endpoint.data(), target_endpoint.size());
        if (bytes_sent < 0) {
            perror("sendto() failed");
            return 1;
        }
#endif

        std::cout << "Successfully sent " << bytes_sent << " bytes from "
                  << src_ip << ":" << src_port << " to "
                  << dest_ip << ":" << dest_port << "!\n";

    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << "\n";
        return 1;
    }

    return 0;
}

Attachment: OpenPGP_signature.asc
Description: OpenPGP digital signature

Reply via email to