-
Notifications
You must be signed in to change notification settings - Fork 66
Autonat protocol #739
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Autonat protocol #739
Changes from 7 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
f3efcba
Autonat decode / encode message
lchenut 0e2f3a7
Begin autonat protocol
lchenut 796c2ad
Autonat protocol done
lchenut 083425f
Autonat client side
lchenut ae280c4
Merge remote-tracking branch 'origin/unstable' into autonat
lchenut 70e8dc3
Add autonat testing
lchenut e948f49
Fix some bugs
lchenut 812772a
Replace IP addresses with the IP of observedAddr
lchenut 7685ad4
extract connManager from dialAndUpgrade
Menduist 314e3e5
change canDial to use dialAndUpgrade
lchenut b989a34
add await
lchenut 868ebf4
make tryDial a global Dial method
lchenut 0289a5d
syntax
lchenut 2f87d6e
Add a semaphore to dialBack
lchenut c070356
renaming
lchenut 419e4f8
rewrite getParts & len MultiAddress functions
lchenut 09c1f3a
Update libp2p/protocols/autonat.nim
Menduist c469cfe
Refused dial request via relayed connection
lchenut d6c30a9
Add a safety check
lchenut 8f1ed87
Merge remote-tracking branch 'origin/unstable' into autonat
lchenut 5c1f4c0
Merge branch 'unstable' into autonat
Menduist a3c09ed
make it nim1.4+ compatible
lchenut File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,276 @@ | ||
| # Nim-LibP2P | ||
| # Copyright (c) 2022 Status Research & Development GmbH | ||
| # 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. | ||
|
|
||
| {.push raises: [Defect].} | ||
|
|
||
| import std/[options, sets, sequtils] | ||
| import chronos, chronicles, stew/objects | ||
| import ./protocol, | ||
| ../switch, | ||
| ../multiaddress, | ||
| ../peerid, | ||
| ../errors | ||
|
|
||
| logScope: | ||
| topics = "libp2p identify" | ||
Menduist marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| const | ||
| AutonatCodec* = "/libp2p/autonat/1.0.0" | ||
| ArbitraryLimit = 128 # TODO: make it a configuration variable or smthg | ||
lchenut marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| type | ||
| AutonatError* = object of LPError | ||
|
|
||
| MsgType* = enum | ||
| Dial = 0 | ||
| DialResponse = 1 | ||
|
|
||
| ResponseStatus* = enum | ||
| Ok = 0 | ||
| DialError = 100 | ||
| DialRefused = 101 | ||
| BadRequest = 200 | ||
| InternalError = 300 | ||
|
|
||
| AutonatPeerInfo* = object | ||
| id: Option[PeerID] | ||
| addrs: seq[MultiAddress] | ||
|
|
||
| AutonatDial* = object | ||
| peerInfo: Option[AutonatPeerInfo] | ||
|
|
||
| AutonatDialResponse* = object | ||
| status*: ResponseStatus | ||
| text*: Option[string] | ||
| ma*: Option[MultiAddress] | ||
|
|
||
| AutonatMsg = object | ||
| msgType: MsgType | ||
| dial: Option[AutonatDial] | ||
| response: Option[AutonatDialResponse] | ||
|
|
||
| proc encode*(msg: AutonatMsg): ProtoBuffer = | ||
| result = initProtoBuffer() | ||
| result.write(1, msg.msgType.uint) | ||
| if msg.dial.isSome(): | ||
| var dial = initProtoBuffer() | ||
| if msg.dial.get().peerInfo.isSome(): | ||
| var bufferPeerInfo = initProtoBuffer() | ||
| let peerInfo = msg.dial.get().peerInfo.get() | ||
| if peerInfo.id.isSome(): | ||
| bufferPeerInfo.write(1, peerInfo.id.get()) | ||
| for ma in peerInfo.addrs: | ||
| bufferPeerInfo.write(2, ma.data.buffer) | ||
| bufferPeerInfo.finish() | ||
| dial.write(1, bufferPeerInfo.buffer) | ||
| dial.finish() | ||
| result.write(2, dial.buffer) | ||
| if msg.response.isSome(): | ||
| var bufferResponse = initProtoBuffer() | ||
| let response = msg.response.get() | ||
| bufferResponse.write(1, response.status.uint) | ||
| if response.text.isSome(): | ||
| bufferResponse.write(2, response.text.get()) | ||
| if response.ma.isSome(): | ||
| bufferResponse.write(3, response.ma.get()) | ||
| bufferResponse.finish() | ||
| result.write(3, bufferResponse.buffer) | ||
| result.finish() | ||
|
|
||
| proc encode*(d: AutonatDial): ProtoBuffer = | ||
| result = initProtoBuffer() | ||
| result.write(1, MsgType.Dial.uint) | ||
| var dial = initProtoBuffer() | ||
| if d.peerInfo.isSome(): | ||
| var bufferPeerInfo = initProtoBuffer() | ||
| let peerInfo = d.peerInfo.get() | ||
| if peerInfo.id.isSome(): | ||
| bufferPeerInfo.write(1, peerInfo.id.get()) | ||
| for ma in peerInfo.addrs: | ||
| bufferPeerInfo.write(2, ma.data.buffer) | ||
| bufferPeerInfo.finish() | ||
| dial.write(1, bufferPeerInfo.buffer) | ||
| dial.finish() | ||
| result.write(2, dial.buffer) | ||
| result.finish() | ||
|
|
||
| proc encode*(r: AutonatDialResponse): ProtoBuffer = | ||
| result = initProtoBuffer() | ||
| result.write(1, MsgType.DialResponse.uint) | ||
| var bufferResponse = initProtoBuffer() | ||
| bufferResponse.write(1, r.status.uint) | ||
| if r.text.isSome(): | ||
| bufferResponse.write(2, r.text.get()) | ||
| if r.ma.isSome(): | ||
| bufferResponse.write(3, r.ma.get()) | ||
| bufferResponse.finish() | ||
| result.write(3, bufferResponse.buffer) | ||
| result.finish() | ||
|
|
||
| proc decode(_: typedesc[AutonatMsg], buf: seq[byte]): Option[AutonatMsg] = | ||
| var | ||
| msgTypeOrd: uint32 | ||
| pbDial: ProtoBuffer | ||
| pbResponse: ProtoBuffer | ||
| msg: AutonatMsg | ||
|
|
||
| let | ||
| pb = initProtoBuffer(buf) | ||
| r1 = pb.getField(1, msgTypeOrd) | ||
| r2 = pb.getField(2, pbDial) | ||
| r3 = pb.getField(3, pbResponse) | ||
| if r1.isErr() or r2.isErr() or r3.isErr(): return none(AutonatMsg) | ||
|
|
||
| if r1.get() and not checkedEnumAssign(msg.msgType, msgTypeOrd): | ||
| return none(AutonatMsg) | ||
| if r2.get(): | ||
| var | ||
| pbPeerInfo: ProtoBuffer | ||
| dial: AutonatDial | ||
| let | ||
| r4 = pbDial.getField(1, pbPeerInfo) | ||
| if r4.isErr(): return none(AutonatMsg) | ||
|
|
||
| var peerInfo: AutonatPeerInfo | ||
| if r4.get(): | ||
| var pid: PeerId | ||
| let | ||
| r5 = pbPeerInfo.getField(1, pid) | ||
| r6 = pbPeerInfo.getRepeatedField(2, peerInfo.addrs) | ||
| if r5.isErr() or r6.isErr(): return none(AutonatMsg) | ||
| if r5.get(): peerInfo.id = some(pid) | ||
| dial.peerInfo = some(peerInfo) | ||
| msg.dial = some(dial) | ||
|
|
||
| if r3.get(): | ||
| var | ||
| statusOrd: uint | ||
| text: string | ||
| ma: MultiAddress | ||
| response: AutonatDialResponse | ||
|
|
||
| let | ||
| r4 = pbResponse.getField(1, statusOrd) | ||
| r5 = pbResponse.getField(2, text) | ||
| r6 = pbResponse.getField(3, ma) | ||
|
|
||
| if r4.isErr() or r5.isErr() or r6.isErr() or | ||
| (r4.get() and not checkedEnumAssign(response.status, statusOrd)): | ||
| return none(AutonatMsg) | ||
| if r5.get(): response.text = some(text) | ||
| if r6.get(): response.ma = some(ma) | ||
| msg.response = some(response) | ||
|
|
||
| return some(msg) | ||
|
|
||
| proc sendDial(conn: Connection, pid: PeerId, addrs: seq[MultiAddress]) {.async.} = | ||
| let pb = AutonatDial(peerInfo: some(AutonatPeerInfo( | ||
| id: some(pid), | ||
| addrs: addrs | ||
| ))).encode() | ||
| await conn.writeLp(pb.buffer) | ||
|
|
||
| proc sendResponseError(conn: Connection, status: ResponseStatus, text: string = "") {.async.} = | ||
| let pb = AutonatDialResponse( | ||
| status: status, | ||
| text: if text == "": none(string) else: some(text), | ||
| ma: none(MultiAddress) | ||
| ).encode() | ||
| await conn.writeLp(pb.buffer) | ||
|
|
||
| proc sendResponseOk(conn: Connection, ma: MultiAddress) {.async.} = | ||
| let pb = AutonatDialResponse( | ||
| status: ResponseStatus.Ok, | ||
| text: some("Ok"), | ||
| ma: some(ma) | ||
| ).encode() | ||
| await conn.writeLp(pb.buffer) | ||
|
|
||
| type | ||
| Autonat* = ref object of LPProtocol | ||
| switch*: Switch | ||
|
|
||
| proc dialBack*(a: Autonat, pid: PeerId, ma: MultiAddress|seq[MultiAddress]): | ||
| Future[MultiAddress] {.async.} = | ||
| let addrs = when ma is MultiAddress: @[ma] else: ma | ||
| let conn = await a.switch.dial(pid, addrs, AutonatCodec) | ||
| defer: await conn.close() | ||
| await conn.sendDial(a.switch.peerInfo.peerId, a.switch.peerInfo.addrs) | ||
| let msgOpt = AutonatMsg.decode(await conn.readLp(1024)) | ||
| if msgOpt.isNone() or | ||
| msgOpt.get().msgType != DialResponse or | ||
| msgOpt.get().response.isNone(): | ||
| raise newException(AutonatError, "Unexpected response") | ||
| let response = msgOpt.get().response.get() | ||
| if response.status != ResponseStatus.Ok: | ||
| raise newException(AutonatError, "Bad status " & | ||
| $response.status & " " & | ||
| response.text.get("")) | ||
| if response.ma.isNone(): | ||
| raise newException(AutonatError, "Missing address") | ||
| return response.ma.get() | ||
|
|
||
| proc doDial(a: Autonat, conn: Connection, addrs: seq[MultiAddress]) {.async.} = | ||
| try: | ||
| let ma = await Dialer(a.switch.dialer).canDial(conn.peerId, addrs, @[AutonatCodec]) | ||
lchenut marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| await conn.sendResponseOk(ma) | ||
| except CancelledError as exc: | ||
| raise exc | ||
| except CatchableError as exc: | ||
| await conn.sendResponseError(DialError, exc.msg) | ||
|
|
||
| proc handleDial(a: Autonat, conn: Connection, msg: AutonatMsg): Future[void] = | ||
| if msg.dial.isNone() or msg.dial.get().peerInfo.isNone(): | ||
| return conn.sendResponseError(BadRequest, "Missing Peer Info") | ||
| let peerInfo = msg.dial.get().peerInfo.get() | ||
| if peerInfo.id.isSome() and peerInfo.id.get() != conn.peerId: | ||
| return conn.sendResponseError(BadRequest, "PeerId mismatch") | ||
|
|
||
| if conn.observedAddr[0].isErr() or (not IP4.match(conn.observedAddr[0].get()) and | ||
| not IP6.match(conn.observedAddr[0].get())): | ||
| return conn.sendResponseError(InternalError, "Expected an IP address") | ||
lchenut marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| var addrs = initHashSet[MultiAddress]() | ||
| addrs.incl(conn.observedAddr) | ||
| for ma in peerInfo.addrs: | ||
| let maFirst = ma[0] | ||
| if maFirst.isErr() or (not IP4.match(maFirst.get()) and | ||
| not IP6.match(maFirst.get())): | ||
lchenut marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| continue | ||
| addrs.incl(ma) | ||
lchenut marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if len(addrs) >= ArbitraryLimit: | ||
| break | ||
|
|
||
| if len(addrs) == 0: | ||
| return conn.sendResponseError(DialRefused, "No dialable address") | ||
| return a.doDial(conn, toSeq(addrs)) | ||
|
|
||
| proc new*(T: typedesc[Autonat], switch: Switch): T = | ||
| let autonat = T(switch: switch) | ||
| autonat.init() | ||
| autonat | ||
|
|
||
| method init*(a: Autonat) = | ||
| proc handleStream(conn: Connection, proto: string) {.async, gcsafe.} = | ||
| try: | ||
| let msgOpt = AutonatMsg.decode(await conn.readLp(1024)) | ||
| if msgOpt.isNone() or msgOpt.get().msgType != MsgType.Dial: | ||
| raise newException(AutonatError, "Received malformed message") | ||
| let msg = msgOpt.get() | ||
| await a.handleDial(conn, msg) | ||
| except CancelledError as exc: | ||
| raise exc | ||
| except CatchableError as exc: | ||
| trace "exception in autonat handler", exc = exc.msg, conn | ||
| finally: | ||
| trace "exiting autonat handler", conn | ||
| await conn.close() | ||
|
|
||
| a.handler = handleStream | ||
| a.codecs = @[AutonatCodec] | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.