Files
netio1/socket_addr.c.v
T
2026-04-04 00:47:35 +03:00

327 lines
8.7 KiB
V

module netio
import encoding.binary
import os
#include <netdb.h>
struct C.addrinfo {
mut:
ai_flags i32
ai_family i32
ai_socktype i32
ai_protocol i32
ai_addrlen i32
ai_addr voidptr
ai_canonname voidptr
ai_next voidptr
}
fn C.getaddrinfo(&char, &char, &C.addrinfo, &&C.addrinfo) i32
fn C.freeaddrinfo(&C.addrinfo)
// max_unix_path_size value is used to pad the sockaddr_un struct.
const max_unix_path_size = $if linux {
108
} $else $if windows {
108
} $else {
104
}
// AddrInfo represents the [addrinfo](https://man7.org/linux/man-pages/man3/getaddrinfo.3.html).
pub struct AddrInfo {
pub:
flags int
family AddrFamily
sock_type SocketType
protocol Protocol
addr SocketAddr
canonical string
}
@[params]
pub struct TranslateAddrHints {
pub:
node ?string
service ?string
family AddrFamily = af_unspec
sock_type SocketType
protocol Protocol
flags int
}
// translate_addr translates the network address. This is a low-level wrapper around
// the [getaddrinfo(3)](https://man7.org/linux/man-pages/man3/getaddrinfo.3.html) C API.
// Example:
// ```v
// import os
// import netio
//
// // Resolve the host FQND
// addr_info := netio.translate_addr(node: os.hostname()!, flags: ai_canonname)!
// for addr in addr_info {
// println(addr.canonical)
// }
// ```
pub fn translate_addr(hints TranslateAddrHints) ![]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_protocol = i32(hints.protocol)
hints_.ai_flags = i32(hints.flags)
mut node := unsafe { nil }
if hints.node != none {
node = &char(hints.node.str)
}
mut service := unsafe { nil }
if hints.service != none {
service = &char(hints.service.str)
}
mut results := &C.addrinfo(unsafe { nil })
if C.getaddrinfo(node, service, &hints_, &results) == -1 {
return os.last_error()
}
defer {
C.freeaddrinfo(results)
}
mut addrs := []AddrInfo{}
for result := unsafe { results }; !isnil(result); result = result.ai_next {
addrs << AddrInfo{
flags: int(result.ai_flags)
family: int(result.ai_family)
sock_type: 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) {
''
} else {
unsafe {
cstring_to_vstring(result.ai_canonname)
}
}
}
}
return addrs
}
// SocketAddr.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 {
mut sock_addr := unsafe { SocketAddr.new(af_inet, 16) }
unsafe {
sock_addr.push(binary.big_endian_get_u16(port)) or {}
sock_addr.push(addr[..]) or {}
}
return sock_addr
}
// SocketAddr.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 {
mut sock_addr := unsafe { SocketAddr.new(af_inet6, 28) }
unsafe {
sock_addr.push(binary.big_endian_get_u16(port)) or {}
sock_addr.push(binary.big_endian_get_u32(flow_info)) or {}
sock_addr.push(addr[..]) or {}
sock_addr.push(binary.big_endian_get_u32(scope_id)) or {}
}
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 {
return error('Too long path to socket')
}
mut sock_addr := unsafe { SocketAddr.new(af_unix, usize(max_unix_path_size) + 2) }
unsafe {
sock_addr.push(path.bytes()) or {}
}
return sock_addr
}
// SocketAddr.new creates new instance of SocketAddr with specified address family and size.
//
// This function allocates memory (zero filled) for the address, but does not initialize
// the address itself, you need to do that manually. The benefit is that you can create the
// any kind of socket address.
//
// Note: This function sets the address family struct field for you, it is always first
// and 2-byte size (`u16` type).
//
// SocketAddr is a "constructor" for
// [sockaddr(3type)](https://www.man7.org/linux/man-pages/man3/sockaddr.3type.html) objects.
// Use this function only if you understand what you do. You must manually write the data
// for the desired socket address, ensuring the correct sizes of all types, the order of
// the fields in the struct, the byte order, and the total size of the struct. The sizes
// and byte order may vary by platform, so you'll need to keep an eye on that as well.
// A mistake while creating an address will crash your application. So this function is
// marked as `unsafe`.
//
// The example below creates a sockaddr_in struct describing the loopback IPv4-address
// 127.0.0.1 with port number 1080. Note the comment in the example. This is a fragment
// of [sockaddr_in(3type)](https://www.man7.org/linux/man-pages/man3/sockaddr.3type.html)
// manual page, which shows the target C struct. Summing the field sizes yields 8
// bytes, but we need to allocate 16 bytes according to the <netinet/in.h>.
// Data must be padded to sockaddr struct size which is 16 bytes. Each field is then
// written in turn, from top to bottom. Keep in mind that two-byte address family field
// (sin_family in this case) is already written. According to the manual page, the
// address and port are written using the network (big endian) byte order.
//
// Example:
// ```v
// import encoding.binary
// import netio
//
// // struct sockaddr_in {
// // sa_family_t sin_family; /* AF_INET */
// // in_port_t sin_port; /* Port number */
// // struct in_addr sin_addr; /* IPv4 address */
// // };
// //
// // struct in_addr {
// // in_addr_t s_addr;
// // };
// //
// // typedef uint32_t in_addr_t;
// // typedef uint16_t in_port_t;
//
// mut sa := unsafe { netio.SocketAddr.new(netio.af_inet, 16) }
// unsafe {
// sa.push(binary.big_endian_get_u16(u16(1080)))!
// sa.push([u8(127), 0, 0, 1])!
// }
// ```
@[unsafe]
pub fn SocketAddr.new(af AddrFamily, size isize) SocketAddr {
ptr := unsafe { vcalloc(usize(size)) }
mut sock_addr := SocketAddr{
data: ptr
len: int(size)
}
unsafe {
$if little_endian {
sock_addr.push(binary.little_endian_get_u16(u16(af))) or {}
} $else {
sock_addr.push(binary.big_endian_get_u16(u16(af))) or {}
}
}
return sock_addr
}
// SocketAddr.from_ptr creates new socket address by copying data from specified pointer.
@[unsafe]
pub fn SocketAddr.from_ptr(ptr voidptr, size isize) !SocketAddr {
data := unsafe { vcalloc(usize(size)) }
unsafe {
vmemcpy(data, ptr, size)
}
return SocketAddr{
data: data
len: int(size)
}
}
pub struct SocketAddr {
mut:
data &u8 = unsafe { nil }
len int
pos int
}
// family returns the socket address family.
pub fn (a SocketAddr) family() AddrFamily {
mut f := AddrFamily(0)
unsafe { vmemcpy(&f, a.data, isize(2)) }
return f
}
// push appends the `inp` bytes into internal data buffer.
@[unsafe]
pub fn (mut a SocketAddr) push(inp []u8) ! {
if a.pos + inp.len > a.len {
return error('push: data overflow')
}
mut i := 0
for a.pos + 1 < a.len {
unsafe {
a.data[a.pos + i] = inp[i]
}
i++
if i >= inp.len {
break
}
}
a.pos += inp.len
}
// ptr returns the pointer to sockaddr data.
pub fn (a SocketAddr) ptr() &u8 {
return a.data
}
// size reports the size of sockaddr data.
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.
pub fn (a SocketAddr) str() string {
match a.family() {
af_inet {
mut addr := [4]u8{}
mut port := [2]u8{}
unsafe {
vmemcpy(port, a.ptr() + 2, 2)
vmemcpy(addr, a.ptr() + 4, 4)
}
port_int := binary.big_endian_u16_fixed(port)
// vfmt off
return addr[0].str() + '.'
+ addr[1].str() + '.'
+ addr[2].str() + '.'
+ addr[3].str() + ':' + port_int.str()
// vfmt on
}
af_inet6 {
mut addr := [16]u8{}
mut port := [2]u8{}
mut res := ''
unsafe {
vmemcpy(port, a.ptr() + 2, 2)
vmemcpy(addr, a.ptr() + 8, 16)
}
for i := 0; i < 16; i += 2 {
res += addr[i..i + 2].hex()
if i < 14 {
res += ':'
}
}
port_int := binary.big_endian_u16_fixed(port)
return '[' + res + ']:' + port_int.str()
}
af_unix {
mut path := [max_unix_path_size]u8{}
mut res := ''
unsafe {
vmemcpy(path, a.ptr() + 2, max_unix_path_size)
res = tos_clone(&u8(path[..].data))
}
return res
}
else {
return 'SocketAddr(${a.data.str()})'
}
}
}