This commit is contained in:
ge
2026-04-28 23:18:57 +03:00
parent 7b17a4a33b
commit e54f013ef7
11 changed files with 142 additions and 50 deletions
+3 -1
View File
@@ -1,8 +1,10 @@
# Networking Library for V # Networking Library for V
**netio** is a flexible networking library for V programming language. `netio` is a flexible networking library for V programming language.
Differences with the V standard library `net` module: Differences with the V standard library `net` module:
* Provides the low-level wrappers around C API. * Provides the low-level wrappers around C API.
* Supports any kind of sockets, socket options, address families and protocols. * Supports any kind of sockets, socket options, address families and protocols.
The `netio.protocol` module provides access to the operatins system protocols database.
+10 -10
View File
@@ -26,30 +26,30 @@ pub struct AddrInfo {
pub: pub:
flags AddrInfoFlag flags AddrInfoFlag
family AddrFamily family AddrFamily
sock_type SocketType socktype SocketType
protocol Protocol protocol Protocol
addr SocketAddr addr SocketAddr
canonical string canonical string
} }
@[params] @[params]
pub struct TranslateAddrParams { pub struct AddrInfoParams {
pub: pub:
node ?string node ?string
service ?string service ?string
family AddrFamily = af_unspec family AddrFamily = af_unspec
sock_type SocketType socktype SocketType
protocol Protocol protocol Protocol
flags AddrInfoFlag flags AddrInfoFlag
} }
// translate_addr translates the network address. This is a low-level wrapper around // addr_info translates the network addresses and services. This is a low-level wrapper around
// the [getaddrinfo(3)](https://man7.org/linux/man-pages/man3/getaddrinfo.3.html) C API. // the [getaddrinfo(3)](https://man7.org/linux/man-pages/man3/getaddrinfo.3.html) C API.
pub fn translate_addr(hints TranslateAddrParams) ![]AddrInfo { pub fn addr_info(hints AddrInfoParams) ![]AddrInfo {
mut hints_ := C.addrinfo{} mut hints_ := C.addrinfo{}
unsafe { vmemset(&hints_, 0, int(sizeof(hints_))) } unsafe { vmemset(&hints_, 0, int(sizeof(hints_))) }
hints_.ai_family = i32(hints.family) hints_.ai_family = i32(hints.family)
hints_.ai_socktype = i32(hints.sock_type) hints_.ai_socktype = i32(hints.socktype)
hints_.ai_protocol = i32(hints.protocol) hints_.ai_protocol = i32(hints.protocol)
hints_.ai_flags = i32(hints.flags) hints_.ai_flags = i32(hints.flags)
mut node := unsafe { nil } mut node := unsafe { nil }
@@ -77,7 +77,7 @@ pub fn translate_addr(hints TranslateAddrParams) ![]AddrInfo {
addrs << AddrInfo{ addrs << AddrInfo{
flags: int(result.ai_flags) flags: int(result.ai_flags)
family: int(result.ai_family) family: int(result.ai_family)
sock_type: int(result.ai_socktype) socktype: int(result.ai_socktype)
protocol: int(result.ai_protocol) protocol: int(result.ai_protocol)
addr: unsafe { SocketAddr.from_ptr(result.ai_addr, result.ai_addrlen)! } addr: unsafe { SocketAddr.from_ptr(result.ai_addr, result.ai_addrlen)! }
canonical: if isnil(result.ai_canonname) { canonical: if isnil(result.ai_canonname) {
@@ -93,14 +93,14 @@ pub fn translate_addr(hints TranslateAddrParams) ![]AddrInfo {
} }
@[params] @[params]
pub struct TranslateNameParams { pub struct NameInfoParams {
pub: pub:
flags NameInfoFlag flags NameInfoFlag
} }
// translate_name does address-to-name translation and returns the host and service names. // name_info does address-to-name translation and returns the host and service names.
// See [getnameinfo(3)](https://man7.org/linux/man-pages/man3/getnameinfo.3.html) for details. // See [getnameinfo(3)](https://man7.org/linux/man-pages/man3/getnameinfo.3.html) for details.
pub fn translate_name(sa SocketAddr, params TranslateNameParams) !(string, string) { pub fn name_info(sa SocketAddr, params NameInfoParams) !(string, string) {
mut addr := []u8{len: C.NI_MAXHOST} mut addr := []u8{len: C.NI_MAXHOST}
mut serv := []u8{len: C.NI_MAXSERV} mut serv := []u8{len: C.NI_MAXSERV}
code := C.getnameinfo(sa.ptr(), sa.size(), addr.data, addr.len, serv.data, serv.len, code := C.getnameinfo(sa.ptr(), sa.size(), addr.data, addr.len, serv.data, serv.len,
+1 -1
View File
@@ -5,7 +5,7 @@ fn main() {
// Resolve the fully qualified domain name for host. // Resolve the fully qualified domain name for host.
// This programm is analog for `hostname -f` command. // This programm is analog for `hostname -f` command.
hostname := os.hostname()! hostname := os.hostname()!
ai := netio.translate_addr(node: hostname, flags: netio.ai_canonname)! ai := netio.addr_info(node: hostname, flags: netio.ai_canonname)!
mut fqdn := '' mut fqdn := ''
for a in ai { for a in ai {
// Not needed to iterate over all entries, return the first one per getaddrinfo(3). // Not needed to iterate over all entries, return the first one per getaddrinfo(3).
+6 -2
View File
@@ -4,12 +4,13 @@ fn main() {
// Create new TCP socket. // Create new TCP socket.
mut socket := netio.Socket.new(netio.af_inet, netio.sock_stream, 0)! mut socket := netio.Socket.new(netio.af_inet, netio.sock_stream, 0)!
// Close socket on exit.
defer { defer {
socket.close() or { panic(err) } socket.close() or { panic(err) }
} }
// Create the server socket address. // Create the server socket address.
server_addr := netio.SocketAddr.ipv4([u8(127), 0, 0, 1]!, 1081) server_addr := netio.SocketAddr.new_ipv4([u8(127), 0, 0, 1]!, 1081)
// Connect socket to the server address. // Connect socket to the server address.
socket.connect(server_addr) or { socket.connect(server_addr) or {
@@ -22,7 +23,10 @@ fn main() {
// Send message to the server. // Send message to the server.
msg := 'Hello from client!' msg := 'Hello from client!'
sent := socket.send(msg.bytes(), 0)! sent := socket.send(msg.bytes(), 0) or {
eprintln('SEND: ${err}')
exit(1)
}
eprintln('Sent to the server: ${sent} bytes, data: ${msg}') eprintln('Sent to the server: ${sent} bytes, data: ${msg}')
+6 -5
View File
@@ -3,9 +3,9 @@ import netio
fn main() { fn main() {
// We want to bind a server socket to the all available local addresses, // We want to bind a server socket to the all available local addresses,
// (both IPv4 and IPv6) so collect the address info entries for it. // (both IPv4 and IPv6) so collect the address info entries for it.
ai := netio.translate_addr( ai := netio.addr_info(
service: '1081' // The port number for listen. service: '1081' // The port number to listen.
sock_type: netio.sock_stream // Address must support TCP transport. socktype: netio.sock_stream // Address must support TCP transport.
family: netio.af_inet6 // IPv6 support. family: netio.af_inet6 // IPv6 support.
flags: netio.ai_passive // Passive mode for binding to any address (0.0.0.0, ::). flags: netio.ai_passive // Passive mode for binding to any address (0.0.0.0, ::).
)! )!
@@ -17,7 +17,7 @@ fn main() {
// Create socket and bind to the first available address. // Create socket and bind to the first available address.
for a in ai { for a in ai {
// Create a socket with advertised parameters. // Create a socket with advertised parameters.
socket = netio.Socket.new(a.family, a.sock_type, a.protocol)! socket = netio.Socket.new(a.family, a.socktype, a.protocol)!
// Set SO_REUSEADDR enabled. It allows a server to bind to a port that // Set SO_REUSEADDR enabled. It allows a server to bind to a port that
// is still in a `TIME-WAIT` state from a previous connection. // is still in a `TIME-WAIT` state from a previous connection.
@@ -49,6 +49,7 @@ fn main() {
defer { defer {
socket.close() or { panic(err) } socket.close() or { panic(err) }
} }
// Start listening for incoming connections on socket. // Start listening for incoming connections on socket.
socket.listen(10) or { socket.listen(10) or {
eprintln('LISTEN: ${err}') eprintln('LISTEN: ${err}')
@@ -70,7 +71,7 @@ fn main() {
} }
// Get remote host and port in numeric format. // Get remote host and port in numeric format.
remote_host, remote_port := netio.translate_name(remote_addr, remote_host, remote_port := netio.name_info(remote_addr,
flags: netio.ni_numerichost | netio.ni_numericserv flags: netio.ni_numerichost | netio.ni_numericserv
)! )!
+64
View File
@@ -0,0 +1,64 @@
module netio
import os
#include <net/if.h>
struct C.if_nameindex {
if_index u32
if_name &char
}
fn C.if_nametoindex(&char) u32
fn C.if_indextoname(u32, &char) &char
fn C.if_nameindex() &C.if_nameindex
fn C.if_freenameindex(voidptr)
// name_to_index translates the network interface name to index.
pub fn name_to_index(name string) !u32 {
index := C.if_nametoindex(&char(name.str))
if index == 0 {
return error('${@FN}: no index for `${name}`')
}
return index
}
// index_to_name translates the network interface index to name e.g. 'eth0'.
pub fn index_to_name(index u32) !string {
name := []u8{len: C.IF_NAMESIZE}
ifname := C.if_indextoname(index, name.data)
if isnil(ifname) {
return os.last_error()
}
return unsafe { cstring_to_vstring(ifname) }
}
pub struct NetworkInterface {
index u32
name string
}
// interfaces returns an array with names and indexes of all network interfaces in system.
pub fn interfaces() ![]NetworkInterface {
ifaces := C.if_nameindex()
if isnil(ifaces) {
return os.last_error()
}
defer {
C.if_freenameindex(ifaces)
}
mut result := []NetworkInterface{}
mut i := 0
for {
iface := unsafe { ifaces[i] }
i++
if iface.if_index == 0 && isnil(iface.if_name) {
break
}
result << NetworkInterface{
index: iface.if_index
name: unsafe { cstring_to_vstring(iface.if_name) }
}
}
return result
}
+15
View File
@@ -0,0 +1,15 @@
import netio
fn test_interfaces() {
ifs := netio.interfaces()!
dump(ifs)
assert ifs.len > 0
}
fn test_name_to_index() {
assert netio.name_to_index('lo')! == 1
}
fn test_index_to_name() {
assert netio.index_to_name(1)! == 'lo'
}
+2 -2
View File
@@ -1,6 +1,6 @@
# Protocols Database # Protocols Database Access
The **protocol** module provides thread-safe access to the operatins system protocols database. The `protocol` module provides thread-safe access to the operatins system protocols database.
See [protocols(5)](https://www.man7.org/linux/man-pages/man5/protocols.5.html) See [protocols(5)](https://www.man7.org/linux/man-pages/man5/protocols.5.html)
and [getprotoent(3)](https://man7.org/linux/man-pages/man3/getprotoent.3.html) and [getprotoent(3)](https://man7.org/linux/man-pages/man3/getprotoent.3.html)
+2 -2
View File
@@ -37,8 +37,8 @@ pub fn Socket.new(domain AddrFamily, st SocketType, proto Protocol) !Socket {
} }
} }
// type_of reports the actual socket type. // type reports the actual socket type.
pub fn (s Socket) type_of() !SocketType { pub fn (s Socket) type() !SocketType {
return s.get_option_int(C.SOL_SOCKET, C.SO_TYPE)! return s.get_option_int(C.SOL_SOCKET, C.SO_TYPE)!
} }
+27 -21
View File
@@ -4,8 +4,7 @@ import encoding.binary
struct C.sockaddr_storage {} struct C.sockaddr_storage {}
// max_unix_path_size value is used to pad the sockaddr_un struct. pub const max_unix_path_len = $if linux {
const max_unix_path_size = $if linux {
108 108
} $else $if windows { } $else $if windows {
108 108
@@ -20,9 +19,9 @@ mut:
pos int pos int
} }
// SocketAddr.ipv4 creates new AF_INET socket address. // SocketAddr.new_ipv4 creates new AF_INET socket address.
// addr must be set in network (big-endian) byte order. // addr must be set in network (big-endian) byte order.
pub fn SocketAddr.ipv4(addr [4]u8, port u16) SocketAddr { pub fn SocketAddr.new_ipv4(addr [4]u8, port u16) SocketAddr {
mut sock_addr := unsafe { SocketAddr.new(af_inet, 16) } mut sock_addr := unsafe { SocketAddr.new(af_inet, 16) }
unsafe { unsafe {
sock_addr.push(binary.big_endian_get_u16(port)) or {} sock_addr.push(binary.big_endian_get_u16(port)) or {}
@@ -31,10 +30,10 @@ pub fn SocketAddr.ipv4(addr [4]u8, port u16) SocketAddr {
return sock_addr return sock_addr
} }
// SocketAddr.ipv6 creates new AF_INET6 socket address. // SocketAddr.new_ipv6 creates new AF_INET6 socket address.
// addr must be set in network (big-endian) byte order. // addr must be set in network (big-endian) byte order.
// Use if_nametoindex(3) to get an integer scope_id from its string representation. // Use if_nametoindex(3) to get an integer scope_id from its string representation.
pub fn SocketAddr.ipv6(addr [16]u8, port u16, flow_info u32, scope_id u32) SocketAddr { pub fn SocketAddr.new_ipv6(addr [16]u8, port u16, flow_info u32, scope_id u32) SocketAddr {
mut sock_addr := unsafe { SocketAddr.new(af_inet6, 28) } mut sock_addr := unsafe { SocketAddr.new(af_inet6, 28) }
unsafe { unsafe {
sock_addr.push(binary.big_endian_get_u16(port)) or {} sock_addr.push(binary.big_endian_get_u16(port)) or {}
@@ -45,12 +44,13 @@ pub fn SocketAddr.ipv6(addr [16]u8, port u16, flow_info u32, scope_id u32) Socke
return sock_addr return sock_addr
} }
// SocketAddr.unix creates new AF_UNIX socket address. // SocketAddr.new_unix creates new AF_UNIX socket address with given path. The path must
pub fn SocketAddr.unix(path string) !SocketAddr { // fit in platform dependent const `max_unix_path_len` value.
if path.len > max_unix_path_size { pub fn SocketAddr.new_unix(path string) !SocketAddr {
if path.len > max_unix_path_len {
return error('Too long path to socket') return error('Too long path to socket')
} }
mut sock_addr := unsafe { SocketAddr.new(af_unix, usize(max_unix_path_size) + 2) } mut sock_addr := unsafe { SocketAddr.new(af_unix, usize(max_unix_path_len) + 2) }
unsafe { unsafe {
sock_addr.push(path.bytes()) or {} sock_addr.push(path.bytes()) or {}
} }
@@ -124,9 +124,9 @@ pub fn SocketAddr.new(af AddrFamily, size isize) SocketAddr {
return sock_addr return sock_addr
} }
// SocketAddr.from_ptr creates new socket address by copying data from specified pointer. // SocketAddr.from_ptr_copy creates new socket address by copying data from specified pointer.
@[unsafe] @[unsafe]
pub fn SocketAddr.from_ptr(ptr voidptr, size isize) !SocketAddr { pub fn SocketAddr.from_ptr_copy(ptr voidptr, size isize) !SocketAddr {
if isnil(ptr) { if isnil(ptr) {
return error('${@METHOD}: cannot accept nil ptr') return error('${@METHOD}: cannot accept nil ptr')
} }
@@ -140,6 +140,19 @@ pub fn SocketAddr.from_ptr(ptr voidptr, size isize) !SocketAddr {
} }
} }
// SocketAddr.from_ptr creates new socket address from specified pointer.
// Note: The data is reused, not copied. See also SocketAddr.from_ptr_copy().
@[unsafe]
pub fn SocketAddr.from_ptr(ptr voidptr, size isize) !SocketAddr {
if isnil(ptr) {
return error('${@METHOD}: cannot accept nil ptr')
}
return SocketAddr{
data: ptr
len: int(size)
}
}
// family returns the socket address family. // family returns the socket address family.
// Note: It returns 0 if socket address is nil, see also `is_empty()`. // Note: It returns 0 if socket address is nil, see also `is_empty()`.
pub fn (a SocketAddr) family() AddrFamily { pub fn (a SocketAddr) family() AddrFamily {
@@ -195,13 +208,6 @@ pub fn (a SocketAddr) size() u32 {
return u32(a.len) return u32(a.len)
} }
// u8_array returns the socket address data as is as bytes array.
pub fn (a SocketAddr) u8_array() []u8 {
mut addr := []u8{len: int(a.size()), init: 0}
unsafe { vmemcpy(addr.data, a.ptr(), a.size()) }
return addr
}
// str returns the string representation of socket address. // str returns the string representation of socket address.
// Supported address families are AF_INET, AF_INET6, and AF_UNIX. // Supported address families are AF_INET, AF_INET6, and AF_UNIX.
// Examples: '172.16.16.132:1080', '[fdf1:72d1:0033:0000:0000:0000:0000:0247]:25535', // Examples: '172.16.16.132:1080', '[fdf1:72d1:0033:0000:0000:0000:0000:0247]:25535',
@@ -242,10 +248,10 @@ pub fn (a SocketAddr) str() string {
return '[' + res + ']:' + port_int.str() return '[' + res + ']:' + port_int.str()
} }
af_unix { af_unix {
mut path := [max_unix_path_size]u8{} mut path := [max_unix_path_len]u8{}
mut res := '' mut res := ''
unsafe { unsafe {
vmemcpy(path, a.ptr() + 2, max_unix_path_size) vmemcpy(path, a.ptr() + 2, max_unix_path_len)
res = tos_clone(&u8(path[..].data)) res = tos_clone(&u8(path[..].data))
} }
return res return res
+4 -4
View File
@@ -1,17 +1,17 @@
import netio import netio
fn test_socket_addr_ipv4() { fn test_socket_addr_ipv4() {
addr := netio.SocketAddr.ipv4([u8(127), 0, 0, 1]!, 1080) addr := netio.SocketAddr.new_ipv4([u8(127), 0, 0, 1]!, 1080)
assert addr.str() == '127.0.0.1:1080' assert addr.str() == '127.0.0.1:1080'
} }
fn test_socket_addr_ipv6() { fn test_socket_addr_ipv6() {
addr := netio.SocketAddr.ipv6([u8(0xfd), 0xf1, 0x72, 0xd1, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, addr := netio.SocketAddr.new_ipv6([u8(0xfd), 0xf1, 0x72, 0xd1, 0x00, 0x33, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x47]!, 25535, 0, 0) 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x47]!, 25535, 0, 0)
assert addr.str() == '[fdf1:72d1:0033:0000:0000:0000:0000:0247]:25535' assert addr.str() == '[fdf1:72d1:0033:0000:0000:0000:0000:0247]:25535'
} }
fn test_socket_addr_unix() { fn test_socket_addr_unix() {
addr := netio.SocketAddr.unix('/run/app.sock')! addr := netio.SocketAddr.new_unix('/run/app.sock')!
assert addr.str() == '/run/app.sock' assert addr.str() == '/run/app.sock'
} }