Skip to content

Commit 49bade5

Browse files
authored
net: add raw sockets support (implement feature request from #19565) (#26237)
1 parent 6c46fdd commit 49bade5

4 files changed

Lines changed: 325 additions & 0 deletions

File tree

‎vlib/net/aasocket.c.v‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub enum SocketType {
3131
udp = C.SOCK_DGRAM
3232
tcp = C.SOCK_STREAM
3333
seqpacket = C.SOCK_SEQPACKET
34+
raw = C.SOCK_RAW
3435
}
3536

3637
// AddrFamily are the available address families

‎vlib/net/raw.c.v‎

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
module net
2+
3+
import time
4+
5+
const raw_default_read_timeout = time.second / 10
6+
const raw_default_write_timeout = time.second / 10
7+
8+
struct RawSocket {
9+
Socket
10+
l Addr
11+
mut:
12+
has_r bool
13+
r Addr
14+
protocol Protocol
15+
}
16+
17+
// RawConn represents a raw socket connection for low-level network access.
18+
// Raw sockets allow sending and receiving packets at the IP layer,
19+
// bypassing the TCP/UDP transport layer.
20+
pub struct RawConn {
21+
pub mut:
22+
sock RawSocket
23+
mut:
24+
write_deadline time.Time
25+
read_deadline time.Time
26+
read_timeout time.Duration
27+
write_timeout time.Duration
28+
}
29+
30+
// RawSocketConfig configures the creation of a raw socket.
31+
@[params]
32+
pub struct RawSocketConfig {
33+
pub:
34+
family AddrFamily = .ip
35+
protocol Protocol = .icmp
36+
}
37+
38+
// new_raw_socket creates a new raw socket with the given configuration.
39+
// Raw sockets typically require elevated privileges (root/administrator).
40+
pub fn new_raw_socket(config RawSocketConfig) !&RawConn {
41+
sockfd := socket_error(C.socket(config.family, SocketType.raw, int(config.protocol)))!
42+
mut s := &RawSocket{
43+
handle: sockfd
44+
protocol: config.protocol
45+
l: Addr{
46+
addr: AddrData{
47+
Ip6: Ip6{}
48+
}
49+
}
50+
r: Addr{
51+
addr: AddrData{
52+
Ip6: Ip6{}
53+
}
54+
}
55+
}
56+
57+
$if net_nonblocking_sockets ? {
58+
set_blocking(sockfd, false)!
59+
}
60+
61+
return &RawConn{
62+
sock: s
63+
read_timeout: raw_default_read_timeout
64+
write_timeout: raw_default_write_timeout
65+
}
66+
}
67+
68+
// write_ptr writes data from `b` of length `len` to the connected remote address.
69+
pub fn (mut c RawConn) write_ptr(b &u8, len int) !int {
70+
remote := c.sock.remote() or { return error('no remote address set for raw socket') }
71+
return c.write_to_ptr(remote, b, len)
72+
}
73+
74+
// write writes `buf` to the connected remote address.
75+
pub fn (mut c RawConn) write(buf []u8) !int {
76+
return c.write_ptr(buf.data, buf.len)
77+
}
78+
79+
// write_string writes string `s` to the connected remote address.
80+
pub fn (mut c RawConn) write_string(s string) !int {
81+
return c.write_ptr(s.str, s.len)
82+
}
83+
84+
// write_to_ptr writes data from `b` of length `len` to the specified `addr`.
85+
pub fn (mut c RawConn) write_to_ptr(addr Addr, b &u8, len int) !int {
86+
res := C.sendto(c.sock.handle, b, len, 0, voidptr(&addr), addr.len())
87+
if res >= 0 {
88+
return res
89+
}
90+
code := error_code()
91+
if code == int(error_ewouldblock) {
92+
c.wait_for_write()!
93+
socket_error(C.sendto(c.sock.handle, b, len, 0, voidptr(&addr), addr.len()))!
94+
} else {
95+
wrap_error(code)!
96+
}
97+
return error('write failed')
98+
}
99+
100+
// write_to writes `buf` to the specified `addr`.
101+
pub fn (mut c RawConn) write_to(addr Addr, buf []u8) !int {
102+
return c.write_to_ptr(addr, buf.data, buf.len)
103+
}
104+
105+
// write_to_string writes string `s` to the specified `addr`.
106+
pub fn (mut c RawConn) write_to_string(addr Addr, s string) !int {
107+
return c.write_to_ptr(addr, s.str, s.len)
108+
}
109+
110+
// read_ptr reads from the socket into `buf_ptr` up to `len` bytes,
111+
// returning the number of bytes read and the source `Addr`.
112+
pub fn (c &RawConn) read_ptr(buf_ptr &u8, len int) !(int, Addr) {
113+
mut addr := Addr{
114+
addr: AddrData{
115+
Ip6: Ip6{}
116+
}
117+
}
118+
addr_len := sizeof(Addr)
119+
mut res := wrap_read_result(C.recvfrom(c.sock.handle, voidptr(buf_ptr), len, 0, voidptr(&addr),
120+
&addr_len))!
121+
if res > 0 {
122+
return res, addr
123+
}
124+
code := error_code()
125+
if code == int(error_ewouldblock) {
126+
c.wait_for_read()!
127+
res = wrap_read_result(C.recvfrom(c.sock.handle, voidptr(buf_ptr), len, 0, voidptr(&addr),
128+
&addr_len))!
129+
res2 := socket_error(res)!
130+
return res2, addr
131+
} else {
132+
wrap_error(code)!
133+
}
134+
return error('read failed')
135+
}
136+
137+
// read reads from the socket into `buf`, returning the number of bytes read and the source `Addr`.
138+
pub fn (mut c RawConn) read(mut buf []u8) !(int, Addr) {
139+
return c.read_ptr(buf.data, buf.len)!
140+
}
141+
142+
// read_deadline returns the current read deadline.
143+
pub fn (c &RawConn) read_deadline() !time.Time {
144+
if c.read_deadline.unix() == 0 {
145+
return c.read_deadline
146+
}
147+
return error('none')
148+
}
149+
150+
// set_read_deadline sets the read deadline.
151+
pub fn (mut c RawConn) set_read_deadline(deadline time.Time) {
152+
c.read_deadline = deadline
153+
}
154+
155+
// write_deadline returns the current write deadline.
156+
pub fn (c &RawConn) write_deadline() !time.Time {
157+
if c.write_deadline.unix() == 0 {
158+
return c.write_deadline
159+
}
160+
return error('none')
161+
}
162+
163+
// set_write_deadline sets the write deadline.
164+
pub fn (mut c RawConn) set_write_deadline(deadline time.Time) {
165+
c.write_deadline = deadline
166+
}
167+
168+
// read_timeout returns the current read timeout duration.
169+
pub fn (c &RawConn) read_timeout() time.Duration {
170+
return c.read_timeout
171+
}
172+
173+
// set_read_timeout sets the read timeout duration.
174+
pub fn (mut c RawConn) set_read_timeout(t time.Duration) {
175+
c.read_timeout = t
176+
}
177+
178+
// write_timeout returns the current write timeout duration.
179+
pub fn (c &RawConn) write_timeout() time.Duration {
180+
return c.write_timeout
181+
}
182+
183+
// set_write_timeout sets the write timeout duration.
184+
pub fn (mut c RawConn) set_write_timeout(t time.Duration) {
185+
c.write_timeout = t
186+
}
187+
188+
// wait_for_read waits for a read operation to be available.
189+
@[inline]
190+
pub fn (c &RawConn) wait_for_read() ! {
191+
return wait_for_read(c.sock.handle, c.read_deadline, c.read_timeout)
192+
}
193+
194+
// wait_for_write waits for a write operation to be available.
195+
@[inline]
196+
pub fn (mut c RawConn) wait_for_write() ! {
197+
return wait_for_write(c.sock.handle, c.write_deadline, c.write_timeout)
198+
}
199+
200+
// str returns a string representation of the RawConn.
201+
pub fn (c &RawConn) str() string {
202+
return 'RawConn'
203+
}
204+
205+
// close closes the raw socket connection.
206+
pub fn (mut c RawConn) close() ! {
207+
return c.sock.close()
208+
}
209+
210+
// set_remote sets the remote address for write operations.
211+
pub fn (mut c RawConn) set_remote(addr Addr) {
212+
c.sock.has_r = true
213+
c.sock.r = addr
214+
}
215+
216+
// protocol returns the protocol used by this socket.
217+
pub fn (c &RawConn) protocol() Protocol {
218+
return c.sock.protocol
219+
}
220+
221+
// set_option_bool sets a boolean socket option.
222+
pub fn (mut s RawSocket) set_option_bool(opt SocketOption, value bool) ! {
223+
x := int(value)
224+
socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &x, sizeof(int)))!
225+
}
226+
227+
// set_option_int sets an integer socket option.
228+
pub fn (mut s RawSocket) set_option_int(opt SocketOption, value int) ! {
229+
socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &value, sizeof(int)))!
230+
}
231+
232+
// set_ip_header_included enables or disables the IP_HDRINCL option.
233+
// When enabled, the user must provide the complete IP header.
234+
pub fn (mut s RawSocket) set_ip_header_included(on bool) ! {
235+
x := int(on)
236+
socket_error(C.setsockopt(s.handle, C.IPPROTO_IP, C.IP_HDRINCL, &x, sizeof(int)))!
237+
}
238+
239+
// close shuts down and closes the socket.
240+
pub fn (mut s RawSocket) close() ! {
241+
shutdown(s.handle)
242+
return close(s.handle)
243+
}
244+
245+
// select waits for no more than `timeout` for the IO operation, defined by `test`, to be available.
246+
pub fn (mut s RawSocket) select(test Select, timeout time.Duration) !bool {
247+
return select(s.handle, test, timeout)
248+
}
249+
250+
// remote returns the remote `Addr` of the socket or `none` if not set.
251+
pub fn (s &RawSocket) remote() ?Addr {
252+
if s.has_r {
253+
return s.r
254+
}
255+
return none
256+
}
257+
258+
// addr returns the local address of the socket.
259+
pub fn (c &RawConn) addr() !Addr {
260+
return c.sock.address()
261+
}

‎vlib/net/raw_test.v‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import net
2+
3+
fn test_protocol_enum() {
4+
assert int(net.Protocol.icmp) == 1
5+
assert int(net.Protocol.tcp) == 6
6+
assert int(net.Protocol.udp) == 17
7+
assert int(net.Protocol.icmpv6) == 58
8+
assert int(net.Protocol.raw) == 255
9+
}
10+
11+
fn test_socket_type_raw() {
12+
assert int(net.SocketType.raw) > 0
13+
}
14+
15+
fn test_raw_socket_config_defaults() {
16+
config := net.RawSocketConfig{}
17+
assert config.family == .ip
18+
assert config.protocol == .icmp
19+
}
20+
21+
fn test_raw_socket_config_custom() {
22+
config := net.RawSocketConfig{
23+
family: .ip6
24+
protocol: .icmpv6
25+
}
26+
assert config.family == .ip6
27+
assert config.protocol == .icmpv6
28+
}
29+
30+
fn test_raw_socket_creation_requires_privileges() {
31+
mut sock := net.new_raw_socket(family: .ip, protocol: .icmp) or {
32+
assert err.msg().len > 0
33+
return
34+
}
35+
sock.close() or {}
36+
}
37+
38+
fn test_raw_socket_protocols() {
39+
protocols := [
40+
net.Protocol.icmp,
41+
net.Protocol.icmpv6,
42+
net.Protocol.raw,
43+
]
44+
45+
for proto in protocols {
46+
config := net.RawSocketConfig{
47+
family: if proto == .icmpv6 { net.AddrFamily.ip6 } else { net.AddrFamily.ip }
48+
protocol: proto
49+
}
50+
assert config.protocol == proto
51+
}
52+
}

‎vlib/net/socket_options.c.v‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
module net
22

3+
// Protocol specifies the IP protocol to use with raw sockets.
4+
pub enum Protocol {
5+
not_set = 0
6+
icmp = C.IPPROTO_ICMP
7+
tcp = C.IPPROTO_TCP
8+
udp = C.IPPROTO_UDP
9+
ipv6 = C.IPPROTO_IPV6
10+
raw = C.IPPROTO_RAW
11+
icmpv6 = C.IPPROTO_ICMPV6
12+
}
13+
314
pub enum SocketOption {
415
// TODO: SO_ACCEPT_CONN is not here because windows doesn't support it
516
// and there is no easy way to define it

0 commit comments

Comments
 (0)