2022-07-01 18:19:57 +00:00
|
|
|
# Nim-Libp2p
|
2023-01-20 14:47:40 +00:00
|
|
|
# Copyright (c) 2023 Status Research & Development GmbH
|
2022-07-01 18:19:57 +00:00
|
|
|
# Licensed under either of
|
|
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
|
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
|
|
|
# at your option.
|
|
|
|
# This file may not be copied, modified, or distributed except according to
|
|
|
|
# those terms.
|
2019-01-09 17:12:15 +00:00
|
|
|
|
2023-06-07 11:12:49 +00:00
|
|
|
{.push raises: [].}
|
2021-05-21 16:27:01 +00:00
|
|
|
|
2019-01-09 17:12:15 +00:00
|
|
|
## This module implements wire network connection procedures.
|
2020-11-19 02:06:42 +00:00
|
|
|
import chronos, stew/endians2
|
2023-02-14 09:35:44 +00:00
|
|
|
import multiaddress, multicodec, errors, utility
|
2019-01-09 17:12:15 +00:00
|
|
|
|
2019-08-01 05:56:59 +00:00
|
|
|
when defined(windows):
|
|
|
|
import winlean
|
|
|
|
else:
|
|
|
|
import posix
|
|
|
|
|
2020-11-19 02:06:42 +00:00
|
|
|
const
|
2021-08-03 13:48:03 +00:00
|
|
|
RTRANSPMA* = mapOr(
|
2023-06-13 15:58:41 +00:00
|
|
|
TCP,
|
|
|
|
WebSockets,
|
2021-08-03 13:48:03 +00:00
|
|
|
UNIX
|
2020-11-19 02:06:42 +00:00
|
|
|
)
|
|
|
|
|
2021-08-03 13:48:03 +00:00
|
|
|
TRANSPMA* = mapOr(
|
|
|
|
RTRANSPMA,
|
2023-06-13 15:58:41 +00:00
|
|
|
UDP,
|
2020-11-19 02:06:42 +00:00
|
|
|
)
|
|
|
|
|
2021-08-03 13:48:03 +00:00
|
|
|
|
2021-06-02 13:39:10 +00:00
|
|
|
proc initTAddress*(ma: MultiAddress): MaResult[TransportAddress] =
|
2019-01-09 17:12:15 +00:00
|
|
|
## Initialize ``TransportAddress`` with MultiAddress ``ma``.
|
|
|
|
##
|
|
|
|
## MultiAddress must be wire address, e.g. ``{IP4, IP6, UNIX}/{TCP, UDP}``.
|
2021-05-21 16:27:01 +00:00
|
|
|
##
|
|
|
|
|
2023-06-13 15:58:41 +00:00
|
|
|
if mapOr(TCP_IP, WebSockets_IP, UNIX, UDP_IP).match(ma):
|
2020-11-19 02:06:42 +00:00
|
|
|
var pbuf: array[2, byte]
|
2021-06-02 13:39:10 +00:00
|
|
|
let code = (?(?ma[0]).protoCode())
|
2020-11-19 02:06:42 +00:00
|
|
|
if code == multiCodec("unix"):
|
|
|
|
var res = TransportAddress(family: AddressFamily.Unix)
|
2021-06-02 13:39:10 +00:00
|
|
|
if (?(?ma[0]).protoArgument(res.address_un)) == 0:
|
2020-11-19 02:06:42 +00:00
|
|
|
err("Incorrect Unix domain address")
|
2019-01-09 17:12:15 +00:00
|
|
|
else:
|
2020-11-19 02:06:42 +00:00
|
|
|
res.port = Port(1)
|
|
|
|
ok(res)
|
|
|
|
elif code == multiCodec("ip4"):
|
|
|
|
var res = TransportAddress(family: AddressFamily.IPv4)
|
2021-06-02 13:39:10 +00:00
|
|
|
if (?(?ma[0]).protoArgument(res.address_v4)) == 0:
|
2020-11-19 02:06:42 +00:00
|
|
|
err("Incorrect IPv4 address")
|
2019-01-09 17:12:15 +00:00
|
|
|
else:
|
2021-06-02 13:39:10 +00:00
|
|
|
if (?(?ma[1]).protoArgument(pbuf)) == 0:
|
2020-11-19 02:06:42 +00:00
|
|
|
err("Incorrect port number")
|
|
|
|
else:
|
|
|
|
res.port = Port(fromBytesBE(uint16, pbuf))
|
|
|
|
ok(res)
|
|
|
|
else:
|
|
|
|
var res = TransportAddress(family: AddressFamily.IPv6)
|
2021-06-02 13:39:10 +00:00
|
|
|
if (?(?ma[0]).protoArgument(res.address_v6)) == 0:
|
2020-11-19 02:06:42 +00:00
|
|
|
err("Incorrect IPv6 address")
|
|
|
|
else:
|
2021-06-02 13:39:10 +00:00
|
|
|
if (?(?ma[1]).protoArgument(pbuf)) == 0:
|
2020-11-19 02:06:42 +00:00
|
|
|
err("Incorrect port number")
|
|
|
|
else:
|
|
|
|
res.port = Port(fromBytesBE(uint16, pbuf))
|
|
|
|
ok(res)
|
|
|
|
else:
|
2023-04-18 10:50:21 +00:00
|
|
|
err("MultiAddress must be wire address (tcp, udp or unix): " & $ma)
|
2019-01-09 17:12:15 +00:00
|
|
|
|
2021-06-02 13:39:10 +00:00
|
|
|
proc connect*(
|
2024-03-06 12:12:05 +00:00
|
|
|
ma: MultiAddress,
|
|
|
|
bufferSize = DefaultStreamBufferSize,
|
|
|
|
child: StreamTransport = nil,
|
|
|
|
flags = default(set[SocketFlags]),
|
|
|
|
localAddress: Opt[MultiAddress] = Opt.none(MultiAddress)
|
|
|
|
): Future[StreamTransport] {.async: (raises: [CancelledError, LPError]).} =
|
2019-01-09 17:12:15 +00:00
|
|
|
## Open new connection to remote peer with address ``ma`` and create
|
|
|
|
## new transport object ``StreamTransport`` for established connection.
|
|
|
|
## ``bufferSize`` is size of internal buffer for transport.
|
2021-06-02 13:39:10 +00:00
|
|
|
##
|
2020-11-19 02:06:42 +00:00
|
|
|
if not(RTRANSPMA.match(ma)):
|
|
|
|
raise newException(MaInvalidAddress, "Incorrect or unsupported address!")
|
2019-08-01 05:56:59 +00:00
|
|
|
|
2023-02-14 09:35:44 +00:00
|
|
|
let transportAddress = initTAddress(ma).tryGet()
|
|
|
|
|
2024-03-06 12:12:05 +00:00
|
|
|
try:
|
|
|
|
compilesOr:
|
|
|
|
return await connect(transportAddress, bufferSize, child,
|
|
|
|
if localAddress.isSome():
|
|
|
|
initTAddress(localAddress.expect("just checked"))
|
|
|
|
.tryGet()
|
|
|
|
else:
|
|
|
|
TransportAddress(),
|
|
|
|
flags)
|
|
|
|
do:
|
|
|
|
# support for older chronos versions
|
|
|
|
return await connect(transportAddress, bufferSize, child)
|
|
|
|
except TransportError as exc:
|
|
|
|
raise newException(LPError, "Connect failed", exc)
|
|
|
|
|
|
|
|
proc createStreamServer*[T](
|
|
|
|
ma: MultiAddress,
|
|
|
|
cbproc: StreamCallback2,
|
|
|
|
flags: set[ServerFlags] = {},
|
|
|
|
udata: ref T,
|
|
|
|
sock: AsyncFD = asyncInvalidSocket,
|
|
|
|
backlog: int = 100,
|
|
|
|
bufferSize: int = DefaultStreamBufferSize,
|
|
|
|
child: StreamServer = nil,
|
|
|
|
init: TransportInitCallback = nil): StreamServer {.raises: [LPError].} =
|
2019-01-09 17:12:15 +00:00
|
|
|
## Create new TCP stream server which bounds to ``ma`` address.
|
2020-11-19 02:06:42 +00:00
|
|
|
if not(RTRANSPMA.match(ma)):
|
|
|
|
raise newException(MaInvalidAddress, "Incorrect or unsupported address!")
|
|
|
|
|
2021-05-21 16:27:01 +00:00
|
|
|
try:
|
2021-06-02 13:39:10 +00:00
|
|
|
return createStreamServer(
|
|
|
|
initTAddress(ma).tryGet(),
|
|
|
|
cbproc,
|
|
|
|
flags,
|
|
|
|
udata,
|
|
|
|
sock,
|
|
|
|
backlog,
|
|
|
|
bufferSize,
|
|
|
|
child,
|
|
|
|
init)
|
2024-03-06 12:12:05 +00:00
|
|
|
except TransportError as exc:
|
2021-06-02 13:39:10 +00:00
|
|
|
raise newException(LPError, exc.msg)
|
2020-11-19 02:06:42 +00:00
|
|
|
|
2024-03-06 12:12:05 +00:00
|
|
|
proc createStreamServer*[T](
|
|
|
|
ma: MultiAddress,
|
|
|
|
flags: set[ServerFlags] = {},
|
|
|
|
udata: ref T,
|
|
|
|
sock: AsyncFD = asyncInvalidSocket,
|
|
|
|
backlog: int = 100,
|
|
|
|
bufferSize: int = DefaultStreamBufferSize,
|
|
|
|
child: StreamServer = nil,
|
|
|
|
init: TransportInitCallback = nil): StreamServer {.raises: [LPError].} =
|
2020-11-19 02:06:42 +00:00
|
|
|
## Create new TCP stream server which bounds to ``ma`` address.
|
2021-05-21 16:27:01 +00:00
|
|
|
##
|
2020-11-19 02:06:42 +00:00
|
|
|
if not(RTRANSPMA.match(ma)):
|
|
|
|
raise newException(MaInvalidAddress, "Incorrect or unsupported address!")
|
|
|
|
|
2021-05-21 16:27:01 +00:00
|
|
|
try:
|
2021-06-02 13:39:10 +00:00
|
|
|
return createStreamServer(
|
|
|
|
initTAddress(ma).tryGet(),
|
|
|
|
flags,
|
|
|
|
udata,
|
|
|
|
sock,
|
|
|
|
backlog,
|
|
|
|
bufferSize,
|
|
|
|
child,
|
|
|
|
init)
|
2024-03-06 12:12:05 +00:00
|
|
|
except TransportError as exc:
|
2021-06-02 13:39:10 +00:00
|
|
|
raise newException(LPError, exc.msg)
|
2019-08-01 05:56:59 +00:00
|
|
|
|
2024-03-06 12:12:05 +00:00
|
|
|
proc createAsyncSocket*(ma: MultiAddress): AsyncFD {.raises: [LPError].} =
|
2019-08-01 05:56:59 +00:00
|
|
|
## Create new asynchronous socket using MultiAddress' ``ma`` socket type and
|
|
|
|
## protocol information.
|
|
|
|
##
|
|
|
|
## Returns ``asyncInvalidSocket`` on error.
|
2020-11-19 02:06:42 +00:00
|
|
|
##
|
|
|
|
## Note: This procedure only used in `go-libp2p-daemon` wrapper.
|
2021-06-02 13:39:10 +00:00
|
|
|
##
|
|
|
|
|
2019-08-01 05:56:59 +00:00
|
|
|
var
|
|
|
|
socktype: SockType = SockType.SOCK_STREAM
|
|
|
|
protocol: Protocol = Protocol.IPPROTO_TCP
|
|
|
|
|
2021-06-02 13:39:10 +00:00
|
|
|
let address = initTAddress(ma).tryGet()
|
2019-08-01 05:56:59 +00:00
|
|
|
if address.family in {AddressFamily.IPv4, AddressFamily.IPv6}:
|
2020-05-31 14:22:49 +00:00
|
|
|
if ma[1].tryGet().protoCode().tryGet() == multiCodec("udp"):
|
2019-08-01 05:56:59 +00:00
|
|
|
socktype = SockType.SOCK_DGRAM
|
|
|
|
protocol = Protocol.IPPROTO_UDP
|
2020-05-31 14:22:49 +00:00
|
|
|
elif ma[1].tryGet().protoCode().tryGet() == multiCodec("tcp"):
|
2019-08-01 05:56:59 +00:00
|
|
|
socktype = SockType.SOCK_STREAM
|
|
|
|
protocol = Protocol.IPPROTO_TCP
|
|
|
|
elif address.family in {AddressFamily.Unix}:
|
|
|
|
socktype = SockType.SOCK_STREAM
|
|
|
|
protocol = cast[Protocol](0)
|
|
|
|
else:
|
|
|
|
return asyncInvalidSocket
|
|
|
|
|
2024-03-06 12:12:05 +00:00
|
|
|
createAsyncSocket(address.getDomain(), socktype, protocol)
|
2021-05-21 16:27:01 +00:00
|
|
|
|
|
|
|
proc bindAsyncSocket*(sock: AsyncFD, ma: MultiAddress): bool
|
2023-06-07 11:12:49 +00:00
|
|
|
{.raises: [LPError].} =
|
2019-08-01 05:56:59 +00:00
|
|
|
## Bind socket ``sock`` to MultiAddress ``ma``.
|
2020-11-19 02:06:42 +00:00
|
|
|
##
|
|
|
|
## Note: This procedure only used in `go-libp2p-daemon` wrapper.
|
2021-06-02 13:39:10 +00:00
|
|
|
##
|
|
|
|
|
2019-08-01 05:56:59 +00:00
|
|
|
var
|
|
|
|
saddr: Sockaddr_storage
|
|
|
|
slen: SockLen
|
2020-11-19 02:06:42 +00:00
|
|
|
|
2021-06-02 13:39:10 +00:00
|
|
|
let address = initTAddress(ma).tryGet()
|
2019-08-01 05:56:59 +00:00
|
|
|
toSAddr(address, saddr, slen)
|
2020-11-19 02:06:42 +00:00
|
|
|
if bindSocket(SocketHandle(sock), cast[ptr SockAddr](addr saddr),
|
|
|
|
slen) == 0:
|
2019-08-01 05:56:59 +00:00
|
|
|
result = true
|
|
|
|
else:
|
|
|
|
result = false
|
|
|
|
|
|
|
|
proc getLocalAddress*(sock: AsyncFD): TransportAddress =
|
|
|
|
## Retrieve local socket ``sock`` address.
|
2020-11-19 02:06:42 +00:00
|
|
|
##
|
|
|
|
## Note: This procedure only used in `go-libp2p-daemon` wrapper.
|
2019-08-01 05:56:59 +00:00
|
|
|
var saddr: Sockaddr_storage
|
|
|
|
var slen = SockLen(sizeof(Sockaddr_storage))
|
|
|
|
|
|
|
|
if getsockname(SocketHandle(sock), cast[ptr SockAddr](addr saddr),
|
|
|
|
addr slen) == 0:
|
|
|
|
fromSAddr(addr saddr, slen, result)
|
2023-06-13 15:58:41 +00:00
|
|
|
|
|
|
|
proc isPublicMA*(ma: MultiAddress): bool =
|
|
|
|
if DNS.matchPartial(ma):
|
|
|
|
return true
|
|
|
|
|
|
|
|
let hostIP = initTAddress(ma).valueOr: return false
|
|
|
|
return hostIP.isGlobal()
|