/// <summary> /// Exchanges "version" and "verack" messages with the peer. /// <para>Both parties have to send their "version" messages to the other party /// as well as to acknowledge that they are happy with the other party's "version" information.</para> /// </summary> /// <param name="requirements">Protocol requirement for network peers the node wants to be connected to.</param> /// <param name="cancellationToken">Cancellation that allows aborting the operation at any stage.</param> public async Task VersionHandshakeAsync(NetworkPeerRequirement requirements, CancellationToken cancellationToken = default(CancellationToken)) { this.logger.LogTrace("({0}.{1}:{2})", nameof(requirements), nameof(requirements.RequiredServices), requirements?.RequiredServices); requirements = requirements ?? new NetworkPeerRequirement(); using (var listener = new NetworkPeerListener(this).Where(p => (p.Message.Payload is VersionPayload) || (p.Message.Payload is RejectPayload) || (p.Message.Payload is VerAckPayload))) { await this.SendMessageAsync(this.MyVersion).ConfigureAwait(false); Payload payload = listener.ReceivePayload <Payload>(cancellationToken); if (payload is RejectPayload) { this.logger.LogTrace("(-)[HANDSHAKE_REJECTED]"); throw new ProtocolException("Handshake rejected: " + ((RejectPayload)payload).Reason); } var version = (VersionPayload)payload; this.PeerVersion = version; if (!version.AddressReceiver.Address.Equals(this.MyVersion.AddressFrom.Address)) { this.logger.LogDebug("Different external address detected by the node '{0}' instead of '{1}'.", version.AddressReceiver.Address, this.MyVersion.AddressFrom.Address); } if (version.Version < ProtocolVersion.MIN_PEER_PROTO_VERSION) { this.logger.LogDebug("Outdated version {0} received, disconnecting peer.", version.Version); this.Disconnect("Outdated version"); this.logger.LogTrace("(-)[OUTDATED]"); return; } if (!requirements.Check(version)) { this.logger.LogTrace("(-)[UNSUPPORTED_REQUIREMENTS]"); this.Disconnect("The peer does not support the required services requirement"); return; } await this.SendMessageAsync(new VerAckPayload()).ConfigureAwait(false); listener.ReceivePayload <VerAckPayload>(cancellationToken); this.State = NetworkPeerState.HandShaked; if (this.Advertize && this.MyVersion.AddressFrom.Address.IsRoutable(true)) { this.SendMessageVoidAsync(new AddrPayload(new NetworkAddress(this.MyVersion.AddressFrom) { Time = this.dateTimeProvider.GetTimeOffset() })); } } this.logger.LogTrace("(-)"); }
/// <inheritdoc/> public async Task VersionHandshakeAsync(NetworkPeerRequirement requirements, CancellationToken cancellationToken) { this.logger.LogTrace("({0}.{1}:{2})", nameof(requirements), nameof(requirements.RequiredServices), requirements?.RequiredServices); requirements = requirements ?? new NetworkPeerRequirement(); using (var listener = new NetworkPeerListener(this)) { this.logger.LogTrace("Sending my version."); await this.SendMessageAsync(this.MyVersion, cancellationToken).ConfigureAwait(false); this.logger.LogTrace("Waiting for version or rejection message."); bool versionReceived = false; bool verAckReceived = false; while (!versionReceived || !verAckReceived) { Payload payload = await listener.ReceivePayloadAsync <Payload>(cancellationToken).ConfigureAwait(false); switch (payload) { case RejectPayload rejectPayload: this.logger.LogTrace("(-)[HANDSHAKE_REJECTED]"); throw new ProtocolException("Handshake rejected: " + rejectPayload.Reason); case VersionPayload versionPayload: versionReceived = true; this.PeerVersion = versionPayload; if (!versionPayload.AddressReceiver.Address.Equals(this.MyVersion.AddressFrom.Address)) { this.logger.LogDebug("Different external address detected by the node '{0}' instead of '{1}'.", versionPayload.AddressReceiver.Address, this.MyVersion.AddressFrom.Address); } if (versionPayload.Version < ProtocolVersion.MIN_PEER_PROTO_VERSION) { this.logger.LogDebug("Outdated version {0} received, disconnecting peer.", versionPayload.Version); this.Disconnect("Outdated version"); this.logger.LogTrace("(-)[OUTDATED]"); return; } if (!requirements.Check(versionPayload)) { this.logger.LogTrace("(-)[UNSUPPORTED_REQUIREMENTS]"); this.Disconnect("The peer does not support the required services requirement"); return; } this.logger.LogTrace("Sending version acknowledgement."); await this.SendMessageAsync(new VerAckPayload(), cancellationToken).ConfigureAwait(false); break; case VerAckPayload verAckPayload: verAckReceived = true; break; } } await this.SetStateAsync(NetworkPeerState.HandShaked).ConfigureAwait(false); if (this.advertize && this.MyVersion.AddressFrom.Address.IsRoutable(true)) { var addrPayload = new AddrPayload ( new NetworkAddress(this.MyVersion.AddressFrom) { Time = this.dateTimeProvider.GetTimeOffset() } ); await this.SendMessageAsync(addrPayload, cancellationToken).ConfigureAwait(false); } } this.logger.LogTrace("(-)"); }
/// <inheritdoc/> public async Task VersionHandshakeAsync(NetworkPeerRequirement requirements, CancellationToken cancellationToken) { // In stratisX, the equivalent functionality is contained in main.cpp, method ProcessMessage() requirements = requirements ?? new NetworkPeerRequirement(); using (var listener = new NetworkPeerListener(this, this.asyncProvider)) { this.logger.LogTrace("Sending my version."); await this.SendMessageAsync(this.MyVersion, cancellationToken).ConfigureAwait(false); this.logger.LogTrace("Waiting for version or rejection message."); bool versionReceived = false; bool verAckReceived = false; while (!versionReceived || !verAckReceived) { Payload payload = await listener.ReceivePayloadAsync <Payload>(cancellationToken).ConfigureAwait(false); switch (payload) { case RejectPayload rejectPayload: this.logger.LogTrace("(-)[HANDSHAKE_REJECTED]"); throw new ProtocolException("Handshake rejected: " + rejectPayload.Reason); case VersionPayload versionPayload: versionReceived = true; this.PeerVersion = versionPayload; if (!versionPayload.AddressReceiver.Address.Equals(this.MyVersion.AddressFrom.Address)) { this.logger.LogDebug("Different external address detected by the node '{0}' instead of '{1}'.", versionPayload.AddressReceiver.Address, this.MyVersion.AddressFrom.Address); } if (versionPayload.Version < ProtocolVersion.MIN_PEER_PROTO_VERSION) { this.logger.LogDebug("Outdated version {0} received, disconnecting peer.", versionPayload.Version); this.Disconnect("Outdated version"); this.logger.LogTrace("(-)[OUTDATED]"); return; } if (!requirements.Check(versionPayload, this.Inbound, out string reason)) { this.logger.LogTrace("(-)[UNSUPPORTED_REQUIREMENTS]"); this.Disconnect("The peer does not support the required services requirement, reason: " + reason); return; } this.logger.LogTrace("Sending version acknowledgement."); await this.SendMessageAsync(new VerAckPayload(), cancellationToken).ConfigureAwait(false); this.selfEndpointTracker.UpdateAndAssignMyExternalAddress(versionPayload.AddressFrom, false); break; case VerAckPayload verAckPayload: verAckReceived = true; break; } } await this.SetStateAsync(NetworkPeerState.HandShaked).ConfigureAwait(false); if (this.advertize && this.MyVersion.AddressFrom.Address.IsRoutable(true)) { var addrPayload = new AddrPayload ( new NetworkAddress(this.MyVersion.AddressFrom) { Time = this.dateTimeProvider.GetTimeOffset() } ); await this.SendMessageAsync(addrPayload, cancellationToken).ConfigureAwait(false); } // Ask the just-handshaked peer for the peers they know about to aid in our own peer discovery. await this.SendMessageAsync(new GetAddrPayload(), cancellationToken).ConfigureAwait(false); } }
/// <inheritdoc/> public async Task VersionHandshakeAsync(NetworkPeerRequirement requirements, CancellationToken cancellationToken) { // Note that this method gets called for outbound peers. When our peer is inbound we receive the initial version handshake from the initiating peer, and it is handled via this.ProcessMessageAsync() only. // In stratisX, the equivalent functionality is contained in main.cpp, method ProcessMessage() requirements = requirements ?? new NetworkPeerRequirement(); NetworkPeerListener listener = null; try { listener = new NetworkPeerListener(this, this.asyncProvider); this.logger.LogDebug("Sending my version."); await this.SendMessageAsync(this.MyVersion, cancellationToken).ConfigureAwait(false); this.logger.LogDebug("Waiting for version or rejection message."); bool versionReceived = false; bool verAckReceived = false; while (!versionReceived || !verAckReceived) { Payload payload = await listener.ReceivePayloadAsync <Payload>(cancellationToken).ConfigureAwait(false); switch (payload) { case RejectPayload rejectPayload: this.logger.LogTrace("(-)[HANDSHAKE_REJECTED]"); throw new ProtocolException("Handshake rejected: " + rejectPayload.Reason); case VersionPayload versionPayload: versionReceived = true; this.PeerVersion = versionPayload; if (!versionPayload.AddressReceiver.Address.MapToIPv6().Equals(this.MyVersion.AddressFrom.Address.MapToIPv6())) { this.logger.LogDebug("Different external address detected by the node '{0}' instead of '{1}'.", versionPayload.AddressReceiver.Address, this.MyVersion.AddressFrom.Address); } if (versionPayload.Version < ProtocolVersion.MIN_PEER_PROTO_VERSION) { this.logger.LogDebug("Outdated version {0} received, disconnecting peer.", versionPayload.Version); this.Disconnect("Outdated version"); this.logger.LogTrace("(-)[OUTDATED]"); return; } if (!requirements.Check(versionPayload, this.Inbound, out string reason)) { this.logger.LogTrace("(-)[UNSUPPORTED_REQUIREMENTS]"); this.Disconnect("The peer does not support the required services requirement, reason: " + reason); return; } this.logger.LogDebug("Sending version acknowledgement."); await this.SendMessageAsync(new VerAckPayload(), cancellationToken).ConfigureAwait(false); // Note that we only update our external address data from information returned by outbound peers. // TODO: Is this due to a security assumption or is it an oversight? There is a higher risk the inbounds could be spoofing what they claim our external IP is. We would then use it in future version payloads, so that could be considered an attack. // For outbounds: AddressFrom is our current external endpoint from our perspective, and could easily be incorrect if it has been automatically detected from local NICs. // Whereas AddressReceiver is the endpoint from the peer's perspective, so we update our view using that. this.selfEndpointTracker.UpdateAndAssignMyExternalAddress(versionPayload.AddressReceiver, false); break; case VerAckPayload verAckPayload: verAckReceived = true; break; } } await this.SetStateAsync(NetworkPeerState.HandShaked).ConfigureAwait(false); if (this.advertize && this.MyVersion.AddressFrom.Address.IsRoutable(true)) { var addrPayload = new AddrPayload ( new NetworkAddress(this.MyVersion.AddressFrom) { Time = this.dateTimeProvider.GetTimeOffset() } ); await this.SendMessageAsync(addrPayload, cancellationToken).ConfigureAwait(false); } // Ask the just-handshaked peer for the peers they know about to aid in our own peer discovery. await this.SendMessageAsync(new GetAddrPayload(), cancellationToken).ConfigureAwait(false); } catch { throw; } finally { listener?.Dispose(); } }