From 4b5cecd902cc4126ff9d6cda9edb78a13a421239 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Sun, 12 Dec 2021 13:39:56 -0800 Subject: [PATCH] Various std net improvements (#19132) * Variant of that works with raw IpAddresses. - Add doc tests for new net proc's. - Aadd recvFrom impl - Add recvFrom impl -- tweak handling data var - Update lib/pure/net.nim Co-authored-by: Dominik Picheta - cleaning up sendTo args - remove extra connect test - cleaning up sendTo args - fix inet_ntop test - fix test failing - byte len * fix test failing - byte len * debugging odd windows build failure * debugging odd windows build failure * more experiments to figure out the windows failure * try manual assigment on InAddr Co-authored-by: Jaremy Creechley --- lib/posix/posix.nim | 4 +- lib/pure/net.nim | 61 +++++++++++++++++++++++----- tests/stdlib/tnet_ll.nim | 88 ++++++++++++++++++++-------------------- 3 files changed, 97 insertions(+), 56 deletions(-) diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index c2504f994824..57dd6e2c7e17 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -155,9 +155,9 @@ when not defined(zephyr): proc inet_addr*(a1: cstring): InAddrT {.importc, header: "".} proc inet_ntoa*(a1: InAddr): cstring {.importc, header: "".} -proc inet_ntop*(a1: cint, a2: pointer, a3: cstring, a4: int32): cstring {. +proc inet_ntop*(a1: cint, a2: pointer | ptr InAddr | ptr In6Addr, a3: cstring, a4: int32): cstring {. importc:"(char *)$1", header: "".} -proc inet_pton*(a1: cint, a2: cstring, a3: pointer): cint {. +proc inet_pton*(a1: cint, a2: cstring, a3: pointer | ptr InAddr | ptr In6Addr): cint {. importc, header: "".} var diff --git a/lib/pure/net.nim b/lib/pure/net.nim index 7b0ff78e767d..2d1bb0b3347e 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -65,6 +65,11 @@ runnableExamples("-r:off"): let socket = newSocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) socket.sendTo("192.168.0.1", Port(27960), "status\n") +runnableExamples("-r:off"): + let socket = newSocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) + let ip = parseIpAddress("192.168.0.1") + doAssert socket.sendTo(ip, Port(27960), "status\c\l") == 8 + ## Creating a server ## ----------------- ## @@ -1607,11 +1612,12 @@ proc recvLine*(socket: Socket, timeout = -1, result = "" readLine(socket, result, timeout, flags, maxLength) -proc recvFrom*(socket: Socket, data: var string, length: int, - address: var string, port: var Port, flags = 0'i32): int {. +proc recvFrom*[T: string | IpAddress](socket: Socket, data: var string, length: int, + address: var T, port: var Port, flags = 0'i32): int {. tags: [ReadIOEffect].} = ## Receives data from `socket`. This function should normally be used with - ## connection-less sockets (UDP sockets). + ## connection-less sockets (UDP sockets). The source address of the data + ## packet is stored in the `address` argument as either a string or an IpAddress. ## ## If an error occurs an OSError exception will be raised. Otherwise the return ## value will be the length of data received. @@ -1620,31 +1626,37 @@ proc recvFrom*(socket: Socket, data: var string, length: int, ## so when `socket` is buffered the non-buffered implementation will be ## used. Therefore if `socket` contains something in its buffer this ## function will make no effort to return it. - template adaptRecvFromToDomain(domain: Domain) = + template adaptRecvFromToDomain(sockAddress: untyped, domain: Domain) = var addrLen = sizeof(sockAddress).SockLen result = recvfrom(socket.fd, cstring(data), length.cint, flags.cint, cast[ptr SockAddr](addr(sockAddress)), addr(addrLen)) if result != -1: data.setLen(result) - address = getAddrString(cast[ptr SockAddr](addr(sockAddress))) - when domain == AF_INET6: - port = ntohs(sockAddress.sin6_port).Port + + when typeof(address) is string: + address = getAddrString(cast[ptr SockAddr](addr(sockAddress))) + when domain == AF_INET6: + port = ntohs(sockAddress.sin6_port).Port + else: + port = ntohs(sockAddress.sin_port).Port else: - port = ntohs(sockAddress.sin_port).Port + data.setLen(result) + sockAddress.fromSockAddr(addrLen, address, port) else: raiseOSError(osLastError()) assert(socket.protocol != IPPROTO_TCP, "Cannot `recvFrom` on a TCP socket") # TODO: Buffered sockets data.setLen(length) + case socket.domain of AF_INET6: var sockAddress: Sockaddr_in6 - adaptRecvFromToDomain(AF_INET6) + adaptRecvFromToDomain(sockAddress, AF_INET6) of AF_INET: var sockAddress: Sockaddr_in - adaptRecvFromToDomain(AF_INET) + adaptRecvFromToDomain(sockAddress, AF_INET) else: raise newException(ValueError, "Unknown socket address family") @@ -1707,7 +1719,8 @@ proc sendTo*(socket: Socket, address: string, port: Port, data: pointer, tags: [WriteIOEffect].} = ## This proc sends `data` to the specified `address`, ## which may be an IP address or a hostname, if a hostname is specified - ## this function will try each IP of that hostname. + ## this function will try each IP of that hostname. This function + ## should normally be used with connection-less sockets (UDP sockets). ## ## If an error occurs an OSError exception will be raised. ## @@ -1741,12 +1754,38 @@ proc sendTo*(socket: Socket, address: string, port: Port, ## This proc sends `data` to the specified `address`, ## which may be an IP address or a hostname, if a hostname is specified ## this function will try each IP of that hostname. + ## + ## Generally for use with connection-less (UDP) sockets. ## ## If an error occurs an OSError exception will be raised. ## ## This is the high-level version of the above `sendTo` function. socket.sendTo(address, port, cstring(data), data.len, socket.domain) +proc sendTo*(socket: Socket, address: IpAddress, port: Port, + data: string, flags = 0'i32): int {. + discardable, tags: [WriteIOEffect].} = + ## This proc sends `data` to the specified `IpAddress` and returns + ## the number of bytes written. + ## + ## Generally for use with connection-less (UDP) sockets. + ## + ## If an error occurs an OSError exception will be raised. + ## + ## This is the high-level version of the above `sendTo` function. + assert(socket.protocol != IPPROTO_TCP, "Cannot `sendTo` on a TCP socket") + assert(not socket.isClosed, "Cannot `sendTo` on a closed socket") + + var sa: Sockaddr_storage + var sl: Socklen + toSockAddr(address, port, sa, sl) + result = sendto(socket.fd, cstring(data), data.len().cint, flags.cint, + cast[ptr SockAddr](addr sa), sl) + + if result == -1'i32: + let osError = osLastError() + raiseOSError(osError) + proc isSsl*(socket: Socket): bool = ## Determines whether `socket` is a SSL socket. diff --git a/tests/stdlib/tnet_ll.nim b/tests/stdlib/tnet_ll.nim index affa21947fc1..248dbb79b68f 100644 --- a/tests/stdlib/tnet_ll.nim +++ b/tests/stdlib/tnet_ll.nim @@ -1,43 +1,45 @@ -discard """ - action: run - output: ''' - -[Suite] inet_ntop tests -''' -""" - -when defined(windows): - import winlean -elif defined(posix): - import posix -else: - {.error: "Unsupported OS".} - -import unittest, strutils - -suite "inet_ntop tests": - - setup: - when defined(windows): - var wsa: WSAData - discard wsaStartup(0x101'i16, wsa.addr) - - test "IP V4": - var ip4 = 0x10111213 - var buff: array[0..255, char] - let r = inet_ntop(AF_INET, ip4.addr, buff[0].addr, buff.sizeof.int32) - let res = if r == nil: "" else: $r - check: res == "19.18.17.16" - - - test "IP V6": - when defined(windows): - let ipv6Support = (getVersion() and 0xff) > 0x5 - else: - let ipv6Support = true - - var ip6 = [0x1000'u16, 0x1001, 0x2000, 0x2001, 0x3000, 0x3001, 0x4000, 0x4001] - var buff: array[0..255, char] - let r = inet_ntop(AF_INET6, ip6[0].addr, buff[0].addr, buff.sizeof.int32) - let res = if r == nil: "" else: $r - check: not ipv6Support or res == "10:110:20:120:30:130:40:140" +discard """ + action: run + output: ''' + +[Suite] inet_ntop tests +''' +""" + +when defined(windows): + import winlean +elif defined(posix): + import posix +else: + {.error: "Unsupported OS".} + +import unittest, strutils + +suite "inet_ntop tests": + + setup: + when defined(windows): + var wsa: WSAData + discard wsaStartup(0x101'i16, wsa.addr) + + test "IP V4": + # regular + var ip4 = InAddr() + ip4.s_addr = 0x10111213'u32 + + var buff: array[0..255, char] + let r = inet_ntop(AF_INET, cast[pointer](ip4.s_addr.addr), buff[0].addr, buff.len.int32) + let res = if r == nil: "" else: $r + check: res == "19.18.17.16" + + test "IP V6": + when defined(windows): + let ipv6Support = (getVersion() and 0xff) > 0x5 + else: + let ipv6Support = true + + var ip6 = [0x1000'u16, 0x1001, 0x2000, 0x2001, 0x3000, 0x3001, 0x4000, 0x4001] + var buff: array[0..255, char] + let r = inet_ntop(AF_INET6, cast[pointer](ip6[0].addr), buff[0].addr, buff.len.int32) + let res = if r == nil: "" else: $r + check: not ipv6Support or res == "10:110:20:120:30:130:40:140"