Skip to content

Commit

Permalink
sockets: prototype for ipv6
Browse files Browse the repository at this point in the history
With the current prototype, I'm opting for keeping the transport protocol
address family agnostic. This means that variants have to be used to distinguish
between IPv4 and IPv6 addresses, but simplifies the typical usage of data transport
drastically by not requiring strict type disambiguation.

I might revisit making TCP4/TCP6 sockets a thing when a form of polymorphism exists
for the language.

Breaking changes:

- resolveIP4 -> resolveIP
- addressing APIs now return IPEndpoint, which could be either IP4 or IP6 endpoint
  • Loading branch information
alaviss committed Jul 18, 2023
1 parent 333bb3c commit 9c4dcec
Show file tree
Hide file tree
Showing 8 changed files with 519 additions and 59 deletions.
28 changes: 28 additions & 0 deletions src/sys/private/addresses_posix.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import syscall/posix
type
IP4Impl {.borrow: `.`.} = distinct InAddr
IP6Impl {.borrow: `.`.} = distinct In6Addr
IP6Octet = char

template ip4Word() {.dirty.} =
result = ip.s_addr
Expand All @@ -33,3 +34,30 @@ template ip4EndpointAddr() {.dirty.} =

template ip4EndpointPort() {.dirty.} =
result = Port fromBE(e.sin_port)

template octets(ip: IP6Impl): untyped =
ip.s6_addr

type IP6EndpointImpl {.requiresInit, borrow: `.`.} = distinct Sockaddr_in6

template ip6InitEndpoint() {.dirty.} =
result = IP6EndpointImpl:
Sockaddr_in6(
sin6_family: AF_INET6.TSa_Family,
sin6_addr: cast[In6AddrOrig](ip),
sin6_port: toBE(port.uint16),
sin6_flowinfo: uint32(flowId),

Check failure on line 49 in src/sys/private/addresses_posix.nim

View workflow job for this annotation

GitHub Actions / macos on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 49 in src/sys/private/addresses_posix.nim

View workflow job for this annotation

GitHub Actions / macos on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 49 in src/sys/private/addresses_posix.nim

View workflow job for this annotation

GitHub Actions / macos on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 49 in src/sys/private/addresses_posix.nim

View workflow job for this annotation

GitHub Actions / macos on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 49 in src/sys/private/addresses_posix.nim

View workflow job for this annotation

GitHub Actions / macos on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 49 in src/sys/private/addresses_posix.nim

View workflow job for this annotation

GitHub Actions / macos on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 49 in src/sys/private/addresses_posix.nim

View workflow job for this annotation

GitHub Actions / linux on i386 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 49 in src/sys/private/addresses_posix.nim

View workflow job for this annotation

GitHub Actions / linux on i386 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 49 in src/sys/private/addresses_posix.nim

View workflow job for this annotation

GitHub Actions / linux on i386 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 49 in src/sys/private/addresses_posix.nim

View workflow job for this annotation

GitHub Actions / linux on i386 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 49 in src/sys/private/addresses_posix.nim

View workflow job for this annotation

GitHub Actions / macos on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 49 in src/sys/private/addresses_posix.nim

View workflow job for this annotation

GitHub Actions / macos on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 49 in src/sys/private/addresses_posix.nim

View workflow job for this annotation

GitHub Actions / macos on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 49 in src/sys/private/addresses_posix.nim

View workflow job for this annotation

GitHub Actions / macos on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 49 in src/sys/private/addresses_posix.nim

View workflow job for this annotation

GitHub Actions / macos on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 49 in src/sys/private/addresses_posix.nim

View workflow job for this annotation

GitHub Actions / linux on i386 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'
sin6_scope_id: uint32(scopeId)
)

template ip6EndpointAddr() {.dirty.} =
result = IP6 cast[IP6Impl](e.sin6_addr)

template ip6EndpointPort() {.dirty.} =
result = Port fromBE(e.sin6_port)

template ip6EndpointFlowId() {.dirty.} =
result = FlowId e.sin6_flowinfo

template ip6EndpointScopeId() {.dirty.} =
result = ScopeId e.sin6_scope_id
25 changes: 25 additions & 0 deletions src/sys/private/addresses_windows.nim
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,29 @@ template ip4EndpointAddr() {.dirty.} =
template ip4EndpointPort() {.dirty.} =
result = Port fromBE(e.sin_port)

template octets(ip: IP6Impl): untyped =
ip.Byte

type IP6EndpointImpl {.requiresInit, borrow: `.`.} = distinct sockaddr_in6

template ip6InitEndpoint() {.dirty.} =
result = IP6EndpointImpl:
Sockaddr_in6(
sin6_family: AF_INET6,
sin6_addr: In6Addr(ip),
sin6_port: toBE(port.uint16),
sin6_flowinfo: uint32(flowId)

Check failure on line 48 in src/sys/private/addresses_windows.nim

View workflow job for this annotation

GitHub Actions / windows on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 48 in src/sys/private/addresses_windows.nim

View workflow job for this annotation

GitHub Actions / windows on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 48 in src/sys/private/addresses_windows.nim

View workflow job for this annotation

GitHub Actions / windows on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 48 in src/sys/private/addresses_windows.nim

View workflow job for this annotation

GitHub Actions / windows on i386 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 48 in src/sys/private/addresses_windows.nim

View workflow job for this annotation

GitHub Actions / windows on i386 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 48 in src/sys/private/addresses_windows.nim

View workflow job for this annotation

GitHub Actions / windows on i386 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 48 in src/sys/private/addresses_windows.nim

View workflow job for this annotation

GitHub Actions / windows on i386 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 48 in src/sys/private/addresses_windows.nim

View workflow job for this annotation

GitHub Actions / windows on i386 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 48 in src/sys/private/addresses_windows.nim

View workflow job for this annotation

GitHub Actions / windows on i386 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 48 in src/sys/private/addresses_windows.nim

View workflow job for this annotation

GitHub Actions / windows on i386 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 48 in src/sys/private/addresses_windows.nim

View workflow job for this annotation

GitHub Actions / windows on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 48 in src/sys/private/addresses_windows.nim

View workflow job for this annotation

GitHub Actions / windows on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'

Check failure on line 48 in src/sys/private/addresses_windows.nim

View workflow job for this annotation

GitHub Actions / windows on amd64 (Nim devel)

type mismatch: got 'uint32' for 'uint32(flowId)' but expected 'int32'
)
result.union1.sin6_scope_id = uint32(scopeId)

template ip6EndpointAddr() {.dirty.} =
result = IP6 e.sin6_addr

template ip6EndpointPort() {.dirty.} =
result = Port fromBE(e.sin6_port)

template ip6EndpointFlowId() {.dirty.} =
result = e.sin6_flowinfo

template ip6EndpointScopeId() {.dirty.} =
result = e.sin6_scope_id
98 changes: 75 additions & 23 deletions src/sys/private/sockets_posix.nim
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,17 @@ proc `=destroy`(r: var ResolverResultImpl) =
freeaddrinfo(r.info)
r.info = nil

template ip4Resolve() {.dirty.} =
template ipResolve() {.dirty.} =
result = new ResolverResultImpl

let hints = AddrInfo(
ai_family: AF_INET,
ai_family:
if isNone(kind):
AF_UNSPEC
else:
case kind.get
of V4: AF_INET
of V6: AF_INET6,
ai_flags: AI_NUMERICSERV or AI_ADDRCONFIG
)

Expand All @@ -129,8 +135,13 @@ template resolvedItems() {.dirty.} =
var info = r.info
while info != nil:
if info.ai_addr != nil:
if info.ai_addr.sa_family == AF_INET.TSa_Family:
yield cast[ptr IP4Endpoint](info.ai_addr)[]
case info.ai_addr.sa_family
of AF_INET.TSa_Family:
yield IPEndpoint(kind: V4, v4: cast[ptr IP4Endpoint](info.ai_addr)[])
of AF_INET6.TSa_Family:
yield IPEndpoint(kind: V6, v6: cast[ptr IP6Endpoint](info.ai_addr)[])
else:
discard "Should not be possible, but harmless even if it is"

info = info.ai_next

Expand All @@ -153,7 +164,12 @@ proc handleAsyncConnectResult(fd: SocketFD) {.raises: [OSError].} =
raise newOSError(error, $Error.Connect)

template tcpConnect() {.dirty.} =
let sock = makeSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
const addressFamily =
when endpoint is IP4Endpoint:
AF_INET
elif endpoint is IP6Endpoint:
AF_INET6
let sock = makeSocket(addressFamily, SOCK_STREAM, IPPROTO_TCP)

if connect(
SocketHandle(sock.fd),
Expand All @@ -176,7 +192,12 @@ template tcpConnect() {.dirty.} =
result = Conn[TCP] newSocket(sock)

template tcpAsyncConnect() {.dirty.} =
var sock = makeSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, {sfNonBlock})
const addressFamily =
when endpoint is IP4Endpoint:
AF_INET
elif endpoint is IP6Endpoint:
AF_INET6
var sock = makeSocket(addressFamily, SOCK_STREAM, IPPROTO_TCP, {sfNonBlock})

if connect(
SocketHandle(sock.fd),
Expand Down Expand Up @@ -210,7 +231,13 @@ func maxBacklog(): Natural =
SOMAXCONN

template tcpListen() {.dirty.} =
let sock = makeSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
const addressFamily =
when endpoint is IP4Endpoint:
AF_INET
elif endpoint is IP6Endpoint:
AF_INET6

let sock = makeSocket(addressFamily, SOCK_STREAM, IPPROTO_TCP)

# Bind the address to the socket
posixChk bindSocket(
Expand All @@ -227,7 +254,13 @@ template tcpListen() {.dirty.} =
result = Listener[TCP] newSocket(sock)

template tcpAsyncListen() {.dirty.} =
var sock = makeSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, {sfNonBlock})
const addressFamily =
when endpoint is IP4Endpoint:
AF_INET
elif endpoint is IP6Endpoint:
AF_INET6

var sock = makeSocket(addressFamily, SOCK_STREAM, IPPROTO_TCP, {sfNonBlock})

if bindSocket(
SocketHandle(sock.fd),
Expand Down Expand Up @@ -303,13 +336,8 @@ proc commonAccept[T](fd: SocketFD, remoteAddr: var T,
if conn.fd == InvalidFD:
return

# TODO: Remove this once IPv6 support lands
#
# This is used to verify that we are getting IPv4 address.
assert remoteLen == SockLen(sizeof remoteAddr):
"The length of the endpoint structure does not match assumption. This is a nim-sys bug."
assert remoteAddr.sin_family == AF_INET.TSa_Family:
"The address is not IPv4. This is a nim-sys bug."
assert remoteLen <= SockLen(sizeof remoteAddr):
"The length of the endpoint structure is bigger than expected size. This is a nim-sys bug."

when not declared(accept4):
# On systems without accept4, flags have to be set manually.
Expand All @@ -322,16 +350,25 @@ proc commonAccept[T](fd: SocketFD, remoteAddr: var T,
result = conn

template tcpAccept() {.dirty.} =
let conn = commonAccept[IP4Endpoint](l.fd, result.remote)
var saddr: SockaddrStorage
let conn = commonAccept(l.fd, saddr)
if conn.fd == InvalidFD:
raise newOSError(errno, $Error.Accept)

result.conn = Conn[TCP] newSocket(conn)
case saddr.ss_family
of AF_INET.TSa_Family:
result.remote = IPEndpoint(kind: V4, v4: cast[IP4Endpoint](saddr))
of AF_INET6.TSa_Family:
result.remote = IPEndpoint(kind: V6, v6: cast[IP6Endpoint](saddr))
else:
doAssert false, "Unexpected remote address family: " & $saddr.ss_family

template tcpAsyncAccept() {.dirty.} =
# Loop until we get a connection
while true:
var conn = commonAccept[IP4Endpoint](l.fd, result.remote, {sfNonBlock})
var saddr: SockaddrStorage
var conn = commonAccept(l.fd, saddr, {sfNonBlock})

if conn.fd == InvalidFD:
# If the socket signals that no connections are pending
Expand All @@ -343,19 +380,34 @@ template tcpAsyncAccept() {.dirty.} =
else:
# We got a connection
result.conn = AsyncConn[TCP] newAsyncSocket(move conn)
case saddr.ss_family
of AF_INET.TSa_Family:
result.remote = IPEndpoint(kind: V4, v4: cast[IP4Endpoint](saddr))
of AF_INET6.TSa_Family:
result.remote = IPEndpoint(kind: V6, v6: cast[IP6Endpoint](saddr))
else:
doAssert false, "Unexpected remote address family: " & $saddr.ss_family
return

template tcpLocalEndpoint() {.dirty.} =
var endpointLen = SockLen sizeof(result)
var
saddr: SockaddrStorage
endpointLen = SockLen sizeof(saddr)

posixChk getsockname(
SocketHandle l.fd,
cast[ptr SockAddr](addr result),
cast[ptr SockAddr](addr saddr),
addr endpointLen
):
$Error.LocalEndpoint

assert endpointLen == SockLen sizeof(result):
"The length of the endpoint structure does not match assumption. This is a nim-sys bug."
assert result.sin_family == TSa_Family(AF_INET):
"The address is not IPv4. This is a nim-sys bug."
assert endpointLen <= SockLen(sizeof saddr):
"The length of the endpoint structure is bigger than expected size. This is a nim-sys bug."

case saddr.ss_family
of AF_INET.TSa_Family:
result = IPEndpoint(kind: V4, v4: cast[IP4Endpoint](saddr))
of AF_INET6.TSa_Family:
result = IPEndpoint(kind: V6, v6: cast[IP6Endpoint](saddr))
else:
doAssert false, "Unexpected remote address family: " & $saddr.ss_family
10 changes: 9 additions & 1 deletion src/sys/private/syscall/posix.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

# Seems to be a compiler bug, it shouldn't trigger unused imports for this line.
import std/posix as std_posix
export std_posix
export std_posix except In6Addr

# XXX: Remove when we fully replace std/posix
{.used.}
Expand All @@ -30,6 +30,14 @@ when defined(bsd) or defined(linux):
proc pipe2*(pipefd: var array[2, cint],
flags: cint): cint {.importc, header: "<unistd.h>".}

type
# Overrides the std/posix version to fix the type.
In6Addr* {.importc: "struct in6_addr", pure, final,
header: "<netinet/in.h>".} = object
s6_addr*: array[16, byte]

In6AddrOrig* = std_posix.In6Addr

template retryOnEIntr*(op: untyped): untyped =
## Given a POSIX operation that returns `-1` on error, automatically retry it
## if the error was `EINTR`.
Expand Down
Loading

0 comments on commit 9c4dcec

Please sign in to comment.