From 49bade567852d01003fa48c777259ec36dd7d2b6 Mon Sep 17 00:00:00 2001 From: Felix Ehlers Date: Thu, 1 Jan 2026 16:06:44 +0100 Subject: [PATCH] net: add raw sockets support (implement feature request from #19565) (#26237) --- vlib/net/aasocket.c.v | 1 + vlib/net/raw.c.v | 261 ++++++++++++++++++++++++++++++++++++ vlib/net/raw_test.v | 52 +++++++ vlib/net/socket_options.c.v | 11 ++ 4 files changed, 325 insertions(+) create mode 100644 vlib/net/raw.c.v create mode 100644 vlib/net/raw_test.v diff --git a/vlib/net/aasocket.c.v b/vlib/net/aasocket.c.v index d5c5c7b99..ee5ab42eb 100644 --- a/vlib/net/aasocket.c.v +++ b/vlib/net/aasocket.c.v @@ -31,6 +31,7 @@ pub enum SocketType { udp = C.SOCK_DGRAM tcp = C.SOCK_STREAM seqpacket = C.SOCK_SEQPACKET + raw = C.SOCK_RAW } // AddrFamily are the available address families diff --git a/vlib/net/raw.c.v b/vlib/net/raw.c.v new file mode 100644 index 000000000..be79b8554 --- /dev/null +++ b/vlib/net/raw.c.v @@ -0,0 +1,261 @@ +module net + +import time + +const raw_default_read_timeout = time.second / 10 +const raw_default_write_timeout = time.second / 10 + +struct RawSocket { + Socket + l Addr +mut: + has_r bool + r Addr + protocol Protocol +} + +// RawConn represents a raw socket connection for low-level network access. +// Raw sockets allow sending and receiving packets at the IP layer, +// bypassing the TCP/UDP transport layer. +pub struct RawConn { +pub mut: + sock RawSocket +mut: + write_deadline time.Time + read_deadline time.Time + read_timeout time.Duration + write_timeout time.Duration +} + +// RawSocketConfig configures the creation of a raw socket. +@[params] +pub struct RawSocketConfig { +pub: + family AddrFamily = .ip + protocol Protocol = .icmp +} + +// new_raw_socket creates a new raw socket with the given configuration. +// Raw sockets typically require elevated privileges (root/administrator). +pub fn new_raw_socket(config RawSocketConfig) !&RawConn { + sockfd := socket_error(C.socket(config.family, SocketType.raw, int(config.protocol)))! + mut s := &RawSocket{ + handle: sockfd + protocol: config.protocol + l: Addr{ + addr: AddrData{ + Ip6: Ip6{} + } + } + r: Addr{ + addr: AddrData{ + Ip6: Ip6{} + } + } + } + + $if net_nonblocking_sockets ? { + set_blocking(sockfd, false)! + } + + return &RawConn{ + sock: s + read_timeout: raw_default_read_timeout + write_timeout: raw_default_write_timeout + } +} + +// write_ptr writes data from `b` of length `len` to the connected remote address. +pub fn (mut c RawConn) write_ptr(b &u8, len int) !int { + remote := c.sock.remote() or { return error('no remote address set for raw socket') } + return c.write_to_ptr(remote, b, len) +} + +// write writes `buf` to the connected remote address. +pub fn (mut c RawConn) write(buf []u8) !int { + return c.write_ptr(buf.data, buf.len) +} + +// write_string writes string `s` to the connected remote address. +pub fn (mut c RawConn) write_string(s string) !int { + return c.write_ptr(s.str, s.len) +} + +// write_to_ptr writes data from `b` of length `len` to the specified `addr`. +pub fn (mut c RawConn) write_to_ptr(addr Addr, b &u8, len int) !int { + res := C.sendto(c.sock.handle, b, len, 0, voidptr(&addr), addr.len()) + if res >= 0 { + return res + } + code := error_code() + if code == int(error_ewouldblock) { + c.wait_for_write()! + socket_error(C.sendto(c.sock.handle, b, len, 0, voidptr(&addr), addr.len()))! + } else { + wrap_error(code)! + } + return error('write failed') +} + +// write_to writes `buf` to the specified `addr`. +pub fn (mut c RawConn) write_to(addr Addr, buf []u8) !int { + return c.write_to_ptr(addr, buf.data, buf.len) +} + +// write_to_string writes string `s` to the specified `addr`. +pub fn (mut c RawConn) write_to_string(addr Addr, s string) !int { + return c.write_to_ptr(addr, s.str, s.len) +} + +// read_ptr reads from the socket into `buf_ptr` up to `len` bytes, +// returning the number of bytes read and the source `Addr`. +pub fn (c &RawConn) read_ptr(buf_ptr &u8, len int) !(int, Addr) { + mut addr := Addr{ + addr: AddrData{ + Ip6: Ip6{} + } + } + addr_len := sizeof(Addr) + mut res := wrap_read_result(C.recvfrom(c.sock.handle, voidptr(buf_ptr), len, 0, voidptr(&addr), + &addr_len))! + if res > 0 { + return res, addr + } + code := error_code() + if code == int(error_ewouldblock) { + c.wait_for_read()! + res = wrap_read_result(C.recvfrom(c.sock.handle, voidptr(buf_ptr), len, 0, voidptr(&addr), + &addr_len))! + res2 := socket_error(res)! + return res2, addr + } else { + wrap_error(code)! + } + return error('read failed') +} + +// read reads from the socket into `buf`, returning the number of bytes read and the source `Addr`. +pub fn (mut c RawConn) read(mut buf []u8) !(int, Addr) { + return c.read_ptr(buf.data, buf.len)! +} + +// read_deadline returns the current read deadline. +pub fn (c &RawConn) read_deadline() !time.Time { + if c.read_deadline.unix() == 0 { + return c.read_deadline + } + return error('none') +} + +// set_read_deadline sets the read deadline. +pub fn (mut c RawConn) set_read_deadline(deadline time.Time) { + c.read_deadline = deadline +} + +// write_deadline returns the current write deadline. +pub fn (c &RawConn) write_deadline() !time.Time { + if c.write_deadline.unix() == 0 { + return c.write_deadline + } + return error('none') +} + +// set_write_deadline sets the write deadline. +pub fn (mut c RawConn) set_write_deadline(deadline time.Time) { + c.write_deadline = deadline +} + +// read_timeout returns the current read timeout duration. +pub fn (c &RawConn) read_timeout() time.Duration { + return c.read_timeout +} + +// set_read_timeout sets the read timeout duration. +pub fn (mut c RawConn) set_read_timeout(t time.Duration) { + c.read_timeout = t +} + +// write_timeout returns the current write timeout duration. +pub fn (c &RawConn) write_timeout() time.Duration { + return c.write_timeout +} + +// set_write_timeout sets the write timeout duration. +pub fn (mut c RawConn) set_write_timeout(t time.Duration) { + c.write_timeout = t +} + +// wait_for_read waits for a read operation to be available. +@[inline] +pub fn (c &RawConn) wait_for_read() ! { + return wait_for_read(c.sock.handle, c.read_deadline, c.read_timeout) +} + +// wait_for_write waits for a write operation to be available. +@[inline] +pub fn (mut c RawConn) wait_for_write() ! { + return wait_for_write(c.sock.handle, c.write_deadline, c.write_timeout) +} + +// str returns a string representation of the RawConn. +pub fn (c &RawConn) str() string { + return 'RawConn' +} + +// close closes the raw socket connection. +pub fn (mut c RawConn) close() ! { + return c.sock.close() +} + +// set_remote sets the remote address for write operations. +pub fn (mut c RawConn) set_remote(addr Addr) { + c.sock.has_r = true + c.sock.r = addr +} + +// protocol returns the protocol used by this socket. +pub fn (c &RawConn) protocol() Protocol { + return c.sock.protocol +} + +// set_option_bool sets a boolean socket option. +pub fn (mut s RawSocket) set_option_bool(opt SocketOption, value bool) ! { + x := int(value) + socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &x, sizeof(int)))! +} + +// set_option_int sets an integer socket option. +pub fn (mut s RawSocket) set_option_int(opt SocketOption, value int) ! { + socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &value, sizeof(int)))! +} + +// set_ip_header_included enables or disables the IP_HDRINCL option. +// When enabled, the user must provide the complete IP header. +pub fn (mut s RawSocket) set_ip_header_included(on bool) ! { + x := int(on) + socket_error(C.setsockopt(s.handle, C.IPPROTO_IP, C.IP_HDRINCL, &x, sizeof(int)))! +} + +// close shuts down and closes the socket. +pub fn (mut s RawSocket) close() ! { + shutdown(s.handle) + return close(s.handle) +} + +// select waits for no more than `timeout` for the IO operation, defined by `test`, to be available. +pub fn (mut s RawSocket) select(test Select, timeout time.Duration) !bool { + return select(s.handle, test, timeout) +} + +// remote returns the remote `Addr` of the socket or `none` if not set. +pub fn (s &RawSocket) remote() ?Addr { + if s.has_r { + return s.r + } + return none +} + +// addr returns the local address of the socket. +pub fn (c &RawConn) addr() !Addr { + return c.sock.address() +} diff --git a/vlib/net/raw_test.v b/vlib/net/raw_test.v new file mode 100644 index 000000000..d41791c41 --- /dev/null +++ b/vlib/net/raw_test.v @@ -0,0 +1,52 @@ +import net + +fn test_protocol_enum() { + assert int(net.Protocol.icmp) == 1 + assert int(net.Protocol.tcp) == 6 + assert int(net.Protocol.udp) == 17 + assert int(net.Protocol.icmpv6) == 58 + assert int(net.Protocol.raw) == 255 +} + +fn test_socket_type_raw() { + assert int(net.SocketType.raw) > 0 +} + +fn test_raw_socket_config_defaults() { + config := net.RawSocketConfig{} + assert config.family == .ip + assert config.protocol == .icmp +} + +fn test_raw_socket_config_custom() { + config := net.RawSocketConfig{ + family: .ip6 + protocol: .icmpv6 + } + assert config.family == .ip6 + assert config.protocol == .icmpv6 +} + +fn test_raw_socket_creation_requires_privileges() { + mut sock := net.new_raw_socket(family: .ip, protocol: .icmp) or { + assert err.msg().len > 0 + return + } + sock.close() or {} +} + +fn test_raw_socket_protocols() { + protocols := [ + net.Protocol.icmp, + net.Protocol.icmpv6, + net.Protocol.raw, + ] + + for proto in protocols { + config := net.RawSocketConfig{ + family: if proto == .icmpv6 { net.AddrFamily.ip6 } else { net.AddrFamily.ip } + protocol: proto + } + assert config.protocol == proto + } +} diff --git a/vlib/net/socket_options.c.v b/vlib/net/socket_options.c.v index 1e8320d63..01019056d 100644 --- a/vlib/net/socket_options.c.v +++ b/vlib/net/socket_options.c.v @@ -1,5 +1,16 @@ module net +// Protocol specifies the IP protocol to use with raw sockets. +pub enum Protocol { + not_set = 0 + icmp = C.IPPROTO_ICMP + tcp = C.IPPROTO_TCP + udp = C.IPPROTO_UDP + ipv6 = C.IPPROTO_IPV6 + raw = C.IPPROTO_RAW + icmpv6 = C.IPPROTO_ICMPV6 +} + pub enum SocketOption { // TODO: SO_ACCEPT_CONN is not here because windows doesn't support it // and there is no easy way to define it -- 2.39.5