From e54f013ef7d8091ef46e17561f530200a1cb41ff Mon Sep 17 00:00:00 2001 From: ge Date: Tue, 28 Apr 2026 23:18:57 +0300 Subject: [PATCH] upd5 --- README.md | 4 +- translate.c.v => addr_and_name_info.c.v | 20 ++++---- examples/host_fqdn.v | 2 +- examples/tcp_echo_client.v | 8 +++- examples/tcp_echo_server.v | 15 +++--- if.c.v | 64 +++++++++++++++++++++++++ if_test.v | 15 ++++++ protocol/README.md | 4 +- socket.c.v | 4 +- socket_addr.c.v | 48 +++++++++++-------- socket_addr_test.v | 8 ++-- 11 files changed, 142 insertions(+), 50 deletions(-) rename translate.c.v => addr_and_name_info.c.v (83%) create mode 100644 if.c.v create mode 100644 if_test.v diff --git a/README.md b/README.md index 82dbbcc..11f9a9a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # 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: * Provides the low-level wrappers around C API. * Supports any kind of sockets, socket options, address families and protocols. + +The `netio.protocol` module provides access to the operatins system protocols database. diff --git a/translate.c.v b/addr_and_name_info.c.v similarity index 83% rename from translate.c.v rename to addr_and_name_info.c.v index ae636f1..94a74b2 100644 --- a/translate.c.v +++ b/addr_and_name_info.c.v @@ -26,30 +26,30 @@ pub struct AddrInfo { pub: flags AddrInfoFlag family AddrFamily - sock_type SocketType + socktype SocketType protocol Protocol addr SocketAddr canonical string } @[params] -pub struct TranslateAddrParams { +pub struct AddrInfoParams { pub: node ?string service ?string family AddrFamily = af_unspec - sock_type SocketType + socktype SocketType protocol Protocol 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. -pub fn translate_addr(hints TranslateAddrParams) ![]AddrInfo { +pub fn addr_info(hints AddrInfoParams) ![]AddrInfo { mut hints_ := C.addrinfo{} unsafe { vmemset(&hints_, 0, int(sizeof(hints_))) } 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_flags = i32(hints.flags) mut node := unsafe { nil } @@ -77,7 +77,7 @@ pub fn translate_addr(hints TranslateAddrParams) ![]AddrInfo { addrs << AddrInfo{ flags: int(result.ai_flags) family: int(result.ai_family) - sock_type: int(result.ai_socktype) + socktype: int(result.ai_socktype) protocol: int(result.ai_protocol) addr: unsafe { SocketAddr.from_ptr(result.ai_addr, result.ai_addrlen)! } canonical: if isnil(result.ai_canonname) { @@ -93,14 +93,14 @@ pub fn translate_addr(hints TranslateAddrParams) ![]AddrInfo { } @[params] -pub struct TranslateNameParams { +pub struct NameInfoParams { pub: 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. -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 serv := []u8{len: C.NI_MAXSERV} code := C.getnameinfo(sa.ptr(), sa.size(), addr.data, addr.len, serv.data, serv.len, diff --git a/examples/host_fqdn.v b/examples/host_fqdn.v index 25d9345..50d47fe 100644 --- a/examples/host_fqdn.v +++ b/examples/host_fqdn.v @@ -5,7 +5,7 @@ fn main() { // Resolve the fully qualified domain name for host. // This programm is analog for `hostname -f` command. 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 := '' for a in ai { // Not needed to iterate over all entries, return the first one per getaddrinfo(3). diff --git a/examples/tcp_echo_client.v b/examples/tcp_echo_client.v index b8a7200..5bd5fc1 100644 --- a/examples/tcp_echo_client.v +++ b/examples/tcp_echo_client.v @@ -4,12 +4,13 @@ fn main() { // Create new TCP socket. mut socket := netio.Socket.new(netio.af_inet, netio.sock_stream, 0)! + // Close socket on exit. defer { socket.close() or { panic(err) } } // 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. socket.connect(server_addr) or { @@ -22,7 +23,10 @@ fn main() { // Send message to the server. 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}') diff --git a/examples/tcp_echo_server.v b/examples/tcp_echo_server.v index c3533e4..94be062 100644 --- a/examples/tcp_echo_server.v +++ b/examples/tcp_echo_server.v @@ -3,11 +3,11 @@ import netio fn main() { // 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. - ai := netio.translate_addr( - service: '1081' // The port number for listen. - sock_type: netio.sock_stream // Address must support TCP transport. - family: netio.af_inet6 // IPv6 support. - flags: netio.ai_passive // Passive mode for binding to any address (0.0.0.0, ::). + ai := netio.addr_info( + service: '1081' // The port number to listen. + socktype: netio.sock_stream // Address must support TCP transport. + family: netio.af_inet6 // IPv6 support. + flags: netio.ai_passive // Passive mode for binding to any address (0.0.0.0, ::). )! // Just initialize variables. @@ -17,7 +17,7 @@ fn main() { // Create socket and bind to the first available address. for a in ai { // 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 // is still in a `TIME-WAIT` state from a previous connection. @@ -49,6 +49,7 @@ fn main() { defer { socket.close() or { panic(err) } } + // Start listening for incoming connections on socket. socket.listen(10) or { eprintln('LISTEN: ${err}') @@ -70,7 +71,7 @@ fn main() { } // 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 )! diff --git a/if.c.v b/if.c.v new file mode 100644 index 0000000..ea91b99 --- /dev/null +++ b/if.c.v @@ -0,0 +1,64 @@ +module netio + +import os + +#include + +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 +} diff --git a/if_test.v b/if_test.v new file mode 100644 index 0000000..596625d --- /dev/null +++ b/if_test.v @@ -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' +} diff --git a/protocol/README.md b/protocol/README.md index f0de0f1..3bf42a1 100644 --- a/protocol/README.md +++ b/protocol/README.md @@ -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) and [getprotoent(3)](https://man7.org/linux/man-pages/man3/getprotoent.3.html) diff --git a/socket.c.v b/socket.c.v index 82b6d66..82f5a7a 100644 --- a/socket.c.v +++ b/socket.c.v @@ -37,8 +37,8 @@ pub fn Socket.new(domain AddrFamily, st SocketType, proto Protocol) !Socket { } } -// type_of reports the actual socket type. -pub fn (s Socket) type_of() !SocketType { +// type reports the actual socket type. +pub fn (s Socket) type() !SocketType { return s.get_option_int(C.SOL_SOCKET, C.SO_TYPE)! } diff --git a/socket_addr.c.v b/socket_addr.c.v index e98dc46..395e271 100644 --- a/socket_addr.c.v +++ b/socket_addr.c.v @@ -4,8 +4,7 @@ import encoding.binary struct C.sockaddr_storage {} -// max_unix_path_size value is used to pad the sockaddr_un struct. -const max_unix_path_size = $if linux { +pub const max_unix_path_len = $if linux { 108 } $else $if windows { 108 @@ -20,9 +19,9 @@ mut: 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. -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) } unsafe { 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 } -// 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. // 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) } unsafe { 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 } -// SocketAddr.unix creates new AF_UNIX socket address. -pub fn SocketAddr.unix(path string) !SocketAddr { - if path.len > max_unix_path_size { +// SocketAddr.new_unix creates new AF_UNIX socket address with given path. The path must +// fit in platform dependent const `max_unix_path_len` value. +pub fn SocketAddr.new_unix(path string) !SocketAddr { + if path.len > max_unix_path_len { 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 { sock_addr.push(path.bytes()) or {} } @@ -124,9 +124,9 @@ pub fn SocketAddr.new(af AddrFamily, size isize) SocketAddr { 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] -pub fn SocketAddr.from_ptr(ptr voidptr, size isize) !SocketAddr { +pub fn SocketAddr.from_ptr_copy(ptr voidptr, size isize) !SocketAddr { if isnil(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. // Note: It returns 0 if socket address is nil, see also `is_empty()`. pub fn (a SocketAddr) family() AddrFamily { @@ -195,13 +208,6 @@ pub fn (a SocketAddr) size() u32 { 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. // 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', @@ -242,10 +248,10 @@ pub fn (a SocketAddr) str() string { return '[' + res + ']:' + port_int.str() } af_unix { - mut path := [max_unix_path_size]u8{} + mut path := [max_unix_path_len]u8{} mut res := '' 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)) } return res diff --git a/socket_addr_test.v b/socket_addr_test.v index 2df6ae0..ce73097 100644 --- a/socket_addr_test.v +++ b/socket_addr_test.v @@ -1,17 +1,17 @@ import netio 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' } fn test_socket_addr_ipv6() { - addr := netio.SocketAddr.ipv6([u8(0xfd), 0xf1, 0x72, 0xd1, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x47]!, 25535, 0, 0) + addr := netio.SocketAddr.new_ipv6([u8(0xfd), 0xf1, 0x72, 0xd1, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x47]!, 25535, 0, 0) assert addr.str() == '[fdf1:72d1:0033:0000:0000:0000:0000:0247]:25535' } 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' }