module netio import encoding.binary import os #include 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 . // 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()})' } } }