nim-libp2p/libp2p/protocols/connectivity/autonat/server.nim

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

176 lines
5.7 KiB
Nim
Raw Permalink Normal View History

2023-01-06 10:14:38 +00:00
# Nim-LibP2P
2023-01-20 14:47:40 +00:00
# Copyright (c) 2023 Status Research & Development GmbH
2023-01-06 10:14:38 +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.
2023-06-07 11:12:49 +00:00
{.push raises: [].}
2023-01-06 10:14:38 +00:00
import std/[sets, sequtils]
2023-01-06 10:14:38 +00:00
import stew/results
import chronos, chronicles
2023-01-06 10:14:38 +00:00
import
../../protocol,
../../../switch,
../../../multiaddress,
../../../multicodec,
../../../peerid,
../../../utils/[semaphore, future],
2023-01-06 10:14:38 +00:00
../../../errors
import core
export core
logScope:
topics = "libp2p autonat"
type Autonat* = ref object of LPProtocol
sem: AsyncSemaphore
switch*: Switch
dialTimeout: Duration
proc sendDial(conn: Connection, pid: PeerId, addrs: seq[MultiAddress]) {.async.} =
let pb = AutonatDial(
peerInfo: Opt.some(AutonatPeerInfo(id: Opt.some(pid), addrs: addrs))
2023-01-06 10:14:38 +00:00
).encode()
await conn.writeLp(pb.buffer)
proc sendResponseError(
conn: Connection, status: ResponseStatus, text: string = ""
) {.async.} =
let pb = AutonatDialResponse(
status: status,
text:
if text == "":
Opt.none(string)
else:
Opt.some(text)
,
ma: Opt.none(MultiAddress),
2023-01-06 10:14:38 +00:00
).encode()
await conn.writeLp(pb.buffer)
proc sendResponseOk(conn: Connection, ma: MultiAddress) {.async.} =
let pb = AutonatDialResponse(
status: ResponseStatus.Ok, text: Opt.some("Ok"), ma: Opt.some(ma)
).encode()
await conn.writeLp(pb.buffer)
proc tryDial(autonat: Autonat, conn: Connection, addrs: seq[MultiAddress]) {.async.} =
await autonat.sem.acquire()
var futs: seq[Future[Opt[MultiAddress]]]
2023-01-06 10:14:38 +00:00
try:
# This is to bypass the per peer max connections limit
let outgoingConnection =
autonat.switch.connManager.expectConnection(conn.peerId, Out)
if outgoingConnection.failed() and
outgoingConnection.error of AlreadyExpectingConnectionError:
await conn.sendResponseError(DialRefused, outgoingConnection.error.msg)
return
# Safer to always try to cancel cause we aren't sure if the connection was established
defer:
outgoingConnection.cancel()
# tryDial is to bypass the global max connections limit
futs = addrs.mapIt(autonat.switch.dialer.tryDial(conn.peerId, @[it]))
let fut = await anyCompleted(futs).wait(autonat.dialTimeout)
let ma = await fut
ma.withValue(maddr):
await conn.sendResponseOk(maddr)
2023-01-06 10:14:38 +00:00
else:
await conn.sendResponseError(DialError, "Missing observed address")
except CancelledError as exc:
raise exc
except AllFuturesFailedError as exc:
debug "All dial attempts failed", addrs, description = exc.msg
await conn.sendResponseError(DialError, "All dial attempts failed")
except AsyncTimeoutError as exc:
debug "Dial timeout", addrs, description = exc.msg
await conn.sendResponseError(DialError, "Dial timeout")
2023-01-06 10:14:38 +00:00
except CatchableError as exc:
debug "Unexpected error", addrs, description = exc.msg
await conn.sendResponseError(DialError, "Unexpected error")
2023-01-06 10:14:38 +00:00
finally:
autonat.sem.release()
for f in futs:
if not f.finished():
f.cancel()
2023-01-06 10:14:38 +00:00
proc handleDial(autonat: Autonat, conn: Connection, msg: AutonatMsg): Future[void] =
let dial = msg.dial.valueOr:
return conn.sendResponseError(BadRequest, "Missing Dial")
let peerInfo = dial.peerInfo.valueOr:
2023-01-06 10:14:38 +00:00
return conn.sendResponseError(BadRequest, "Missing Peer Info")
peerInfo.id.withValue(id):
if id != conn.peerId:
return conn.sendResponseError(BadRequest, "PeerId mismatch")
2023-01-06 10:14:38 +00:00
let observedAddr = conn.observedAddr.valueOr:
2023-01-06 10:14:38 +00:00
return conn.sendResponseError(BadRequest, "Missing observed address")
var isRelayed = observedAddr.contains(multiCodec("p2p-circuit")).valueOr:
return conn.sendResponseError(DialRefused, "Invalid observed address")
if isRelayed:
2023-01-06 10:14:38 +00:00
return
conn.sendResponseError(DialRefused, "Refused to dial a relayed observed address")
let hostIp = observedAddr[0].valueOr:
return conn.sendResponseError(InternalError, "Wrong observed address")
if not IP.match(hostIp):
2023-01-06 10:14:38 +00:00
return conn.sendResponseError(InternalError, "Expected an IP address")
var addrs = initHashSet[MultiAddress]()
addrs.incl(observedAddr)
trace "addrs received", addrs = peerInfo.addrs
2023-01-06 10:14:38 +00:00
for ma in peerInfo.addrs:
isRelayed = ma.contains(multiCodec("p2p-circuit")).valueOr:
continue
let maFirst = ma[0].valueOr:
continue
if not DNS_OR_IP.match(maFirst):
continue
2023-01-06 10:14:38 +00:00
try:
addrs.incl(
if maFirst == hostIp:
2023-01-06 10:14:38 +00:00
ma
else:
let maEnd = ma[1 ..^ 1].valueOr:
continue
hostIp & maEnd
2023-01-06 10:14:38 +00:00
)
except LPError as exc:
continue
if len(addrs) >= AddressLimit:
break
if len(addrs) == 0:
return conn.sendResponseError(DialRefused, "No dialable address")
let addrsSeq = toSeq(addrs)
trace "trying to dial", addrs = addrsSeq
return autonat.tryDial(conn, addrsSeq)
2023-01-06 10:14:38 +00:00
proc new*(
T: typedesc[Autonat], switch: Switch, semSize: int = 1, dialTimeout = 15.seconds
): T =
let autonat =
T(switch: switch, sem: newAsyncSemaphore(semSize), dialTimeout: dialTimeout)
proc handleStream(conn: Connection, proto: string) {.async.} =
2023-01-06 10:14:38 +00:00
try:
let msg = AutonatMsg.decode(await conn.readLp(1024)).valueOr:
2023-01-06 10:14:38 +00:00
raise newException(AutonatError, "Received malformed message")
if msg.msgType != MsgType.Dial:
raise newException(AutonatError, "Message type should be dial")
2023-01-06 10:14:38 +00:00
await autonat.handleDial(conn, msg)
except CancelledError as exc:
raise exc
except CatchableError as exc:
debug "exception in autonat handler", description = exc.msg, conn
2023-01-06 10:14:38 +00:00
finally:
trace "exiting autonat handler", conn
await conn.close()
autonat.handler = handleStream
autonat.codec = AutonatCodec
autonat