From 9eb1127b409a01e2da54546c6b9ba13fcdf7641a Mon Sep 17 00:00:00 2001 From: Kleissner Date: Tue, 11 May 2021 10:33:06 +0200 Subject: [PATCH] Alpha 2 release. Includes Traverse message according to latest Whitepaper 0.8. Send and traverse code needs some cleanup. --- Bootstrap.go | 15 ++-- Command Traverse.go | 148 ++++++++++++++++++++++++++++++++++++++++ Config.go | 2 +- Connection.go | 15 ++-- Message Encoding.go | 162 ++++++++++++++++++++++++++++++++++++++++++++ Network.go | 11 ++- README.md | 2 +- 7 files changed, 339 insertions(+), 16 deletions(-) create mode 100644 Command Traverse.go diff --git a/Bootstrap.go b/Bootstrap.go index 907b69b..d341bcd 100644 --- a/Bootstrap.go +++ b/Bootstrap.go @@ -240,15 +240,16 @@ func (peer *PeerInfo) cmdResponseBootstrapFindSelf(msg *MessageResponse, closest if contactArbitraryPeer(closePeer.PublicKey, []*net.UDPAddr{{IP: closePeer.IP, Port: int(port)}}) { // Blacklist the target Peer ID, IP:Port for contact in the next 10 minutes. // TODO - } - // If NAT is detected and the port is not forwarded, send a Traverse message. - // NAT detection is the same algorithm as connection.IsBehindNAT. - if closePeer.PortReportedExternal == 0 && closePeer.Port != closePeer.PortReportedInternal { - // TODO send traverse message - //fmt.Printf("FIND_SELF Traverse message needed for target %s target port %d internal %d\n", closePeer.IP.String(), closePeer.Port, closePeer.PortReportedInternal) + // If NAT is detected and the port is not forwarded, send a Traverse message. + // NAT detection is the same algorithm as connection.IsBehindNAT. + if closePeer.PortReportedExternal == 0 && closePeer.Port != closePeer.PortReportedInternal { + // TODO: PortExternal needs to be guaranteed. send() needs to be broken up. + if raw, err := createVirtualAnnouncement(msg.MessageRaw.connection.Network, closePeer.PublicKey, &bootstrapFindSelf{}); err == nil { + peer.sendTraverse(raw, closePeer.PublicKey) + } + } } - } } diff --git a/Command Traverse.go b/Command Traverse.go new file mode 100644 index 0000000..a081d9a --- /dev/null +++ b/Command Traverse.go @@ -0,0 +1,148 @@ +/* +File Name: Command Traverse.go +Copyright: 2021 Peernet s.r.o. +Author: Peter Kleissner +*/ + +package core + +import ( + "errors" + "net" + "time" + + "github.com/btcsuite/btcd/btcec" +) + +// cmdTraverseForward handles an incoming traverse message that should be forwarded to another peer +func (peer *PeerInfo) cmdTraverseForward(msg *MessageTraverse) { + // Verify the signature. This makes sure that a fowarded message cannot be replayed by others. + if !msg.SignerPublicKey.IsEqual(peer.PublicKey) || !msg.SignerPublicKey.IsEqual(msg.SenderPublicKey) { + return + } + + // Check expiration + if msg.Expires.Before(time.Now()) { + return + } + + // Check if the target peer is known in the peer list. If not, nothing will be done. + // The original sender should only send the Traverse message as answer to a Response that contains a reported peer that is behind a NAT. + // In that case the target peer should be still in this peers' peer list. + peerTarget := PeerlistLookup(msg.TargetPeer) + if peerTarget == nil { + return + } + + // Get the right IP:Port of the original sender to share to the target peer. + allowIPv4 := peerTarget.Features&(1< 0 + allowIPv6 := peerTarget.Features&(1< 0 + connectionIPv4 := peer.GetConnection2Share(false, allowIPv4, false) + connectionIPv6 := peer.GetConnection2Share(false, false, allowIPv6) + + if connectionIPv4 == nil && connectionIPv6 == nil { + return + } + + if err := msgEncodeTraverseSetAddress(msg.Payload, connectionIPv4, connectionIPv6); err != nil { + return + } + + peerTarget.send(&PacketRaw{Command: CommandTraverse, Payload: msg.Payload}) +} + +func (peer *PeerInfo) cmdTraverseReceive(msg *MessageTraverse) { + if msg.Expires.Before(time.Now()) { + return + } + + // already an active connection established? nothing todo. + peerOriginalSender := PeerlistLookup(msg.SignerPublicKey) + if peerOriginalSender != nil { + // could process the packet? + //if connections := peerOriginalSender.GetConnections(true); len(connections) > 0 { + // rawPacketsIncoming <- networkWire{network: connections[0].Network, sender: addressOriginalSender, raw: msg.EmbeddedPacketRaw, receiverPublicKey: peerPublicKey, unicast: true} + //} + return + } + + // ---- fork packetWorker to decode and validate embedded packet --- + decoded, senderPublicKey, err := PacketDecrypt(msg.EmbeddedPacketRaw, peerPublicKey) + if err != nil { + return + } + if !senderPublicKey.IsEqual(msg.SignerPublicKey) { + return + } else if senderPublicKey.IsEqual(peerPublicKey) { + return + } else if decoded.Protocol != 0 { + return + } else if decoded.Command != CommandAnnouncement { + return + } + + // -------- + virtualMessage := &MessageRaw{PacketRaw: *decoded} + announce, err := msgDecodeAnnouncement(virtualMessage) + if err != nil { + return + } + + // Proper handling of announcement todo, virtual announcement function + var hashesNotFound [][]byte + if announce.Actions&(1< 0 { + hashesNotFound = append(hashesNotFound, peer.NodeID) + } + if announce.Actions&(1< 0 && len(announce.FindPeerKeys) > 0 { + for _, findPeer := range announce.FindPeerKeys { + hashesNotFound = append(hashesNotFound, findPeer.Hash) + } + } + if announce.Actions&(1< 0 { + for _, findHash := range announce.FindDataKeys { + hashesNotFound = append(hashesNotFound, findHash.Hash) + } + } + + // TODO + //peer.sendResponse(announce.Sequence, true, nil, nil, hashesNotFound) + //packets, err := msgEncodeResponse(true, nil, nil, hashesNotFound) + //sendAllNetworks() + var addresses []*net.UDPAddr + + if !msg.IPv4.IsUnspecified() { + addressOriginalSenderIPv4 := &net.UDPAddr{IP: msg.IPv4, Port: int(msg.PortIPv4)} + if msg.PortIPv4ReportedExternal > 0 { + addressOriginalSenderIPv4.Port = int(msg.PortIPv4ReportedExternal) + } + addresses = append(addresses, addressOriginalSenderIPv4) + } + + if !msg.IPv6.IsUnspecified() { + addressOriginalSenderIPv6 := &net.UDPAddr{IP: msg.IPv6, Port: int(msg.PortIPv6)} + if msg.PortIPv4ReportedExternal > 0 { + addressOriginalSenderIPv6.Port = int(msg.PortIPv6ReportedExternal) + } + + addresses = append(addresses, addressOriginalSenderIPv6) + } + + // for now send a packet which should open up the NAT and establish a connection + contactArbitraryPeer(msg.SignerPublicKey, addresses) +} + +// createVirtualAnnouncement is temporary code and will be improved. +func createVirtualAnnouncement(network *Network, receiverPublicKey *btcec.PublicKey, sequenceData interface{}) (raw []byte, err error) { + packets := msgEncodeAnnouncement(true, ShouldSendFindSelf(), nil, nil, nil) + if len(packets) == 0 || packets[0].err != nil { + return nil, errors.New("error creating virtual packet") + } + + packet := &PacketRaw{Command: CommandAnnouncement, Payload: packets[0].raw} + setAnnouncementPorts(packet, network) + + packet.Sequence = msgArbitrarySequence(receiverPublicKey, sequenceData).sequence + packet.Protocol = ProtocolVersion + + return PacketEncrypt(peerPrivateKey, receiverPublicKey, packet) +} diff --git a/Config.go b/Config.go index 164c3f3..11ae87d 100644 --- a/Config.go +++ b/Config.go @@ -16,7 +16,7 @@ import ( ) // Version is the current core library version -const Version = "0.2" +const Version = "Alpha 2" var config struct { LogFile string `yaml:"LogFile"` // Log file diff --git a/Connection.go b/Connection.go index 8cb6f2d..13b4d48 100644 --- a/Connection.go +++ b/Connection.go @@ -102,7 +102,7 @@ func (peer *PeerInfo) IsConnectable(allowLocal, allowIPv4, allowIPv6 bool) bool // GetConnection2Share returns a connection to share. Nil if none. // allowLocal specifies whether it is OK to return local IPs. -func (peer *PeerInfo) GetConnection2Share(allowLocal, allowIPv4, allowIPv6 bool) (connections *Connection) { +func (peer *PeerInfo) GetConnection2Share(allowLocal, allowIPv4, allowIPv6 bool) (connection *Connection) { peer.RLock() defer peer.RUnlock() @@ -340,8 +340,10 @@ func (peer *PeerInfo) send(packet *PacketRaw) (err error) { if err = c.Network.send(c.Address.IP, c.Address.Port, raw); err == nil { // Send Traverse message if the peer is behind NAT and this is the first message. - if isFirstPacketOut && c.IsBehindNAT() { - //fmt.Printf("Traverse message needed for target %s\n", c.Address.String()) + if isFirstPacketOut && c.IsBehindNAT() && c.traversePeer != nil { + if raw, err := createVirtualAnnouncement(c.Network, peer.PublicKey, &bootstrapFindSelf{}); err == nil { + c.traversePeer.sendTraverse(raw, peer.PublicKey) + } } return nil @@ -370,9 +372,10 @@ func (peer *PeerInfo) send(packet *PacketRaw) (err error) { if err = c.Network.send(c.Address.IP, c.Address.Port, raw); err == nil { // Send Traverse message if the peer is behind NAT and this is the first message. - if isFirstPacketOut && c.IsBehindNAT() { - //fmt.Printf("Traverse message needed for target %s\n", c.Address.String()) - + if isFirstPacketOut && c.IsBehindNAT() && c.traversePeer != nil { + if raw, err := createVirtualAnnouncement(c.Network, peer.PublicKey, &bootstrapFindSelf{}); err == nil { + c.traversePeer.sendTraverse(raw, peer.PublicKey) + } } } } diff --git a/Message Encoding.go b/Message Encoding.go index 6f2620d..452faf7 100644 --- a/Message Encoding.go +++ b/Message Encoding.go @@ -144,6 +144,23 @@ type MessageResponse struct { HashesNotFound [][]byte // Hashes that were reported back as not found } +// MessageTraverse is the decoded traverse message. +// It is sent by an original sender to a relay, to a final receiver (targert peer). +type MessageTraverse struct { + *MessageRaw // Underlying raw message. + TargetPeer *btcec.PublicKey // End receiver peer ID. + AuthorizedRelayPeer *btcec.PublicKey // Peer ID that is authorized to relay this message to the end receiver. + Expires time.Time // Expiration time when this forwarded message becomes invalid. + EmbeddedPacketRaw []byte // Embedded packet. + SignerPublicKey *btcec.PublicKey // Public key that signed this message, ECDSA (secp256k1) 257-bit + IPv4 net.IP // IPv4 address of the original sender. Set by authorized relay. 0 if not set. + PortIPv4 uint16 // Port (actual one used for connection) of the original sender. Set by authorized relay. + PortIPv4ReportedExternal uint16 // External port as reported by the original sender. This is used in case of port forwarding (manual or automated). + IPv6 net.IP // IPv6 address of the original sender. Set by authorized relay. 0 if not set. + PortIPv6 uint16 // Port (actual one used for connection) of the original sender. Set by authorized relay. + PortIPv6ReportedExternal uint16 // External port as reported by the original sender. This is used in case of port forwarding (manual or automated). +} + // ---- message decoding ---- // Minimum length of Announcement payload header without User Agent @@ -783,6 +800,141 @@ createPacketLoop: } } +// ---- Traverse ---- + +const traversePayloadHeaderSize = 76 + 65 + 28 + +// msgDecodeTraverse decodes a traverse message. +// It does not verify if the receiver is authorized to read or forward this message. +// It validates the signature, but does not validate the signer. +func msgDecodeTraverse(msg *MessageRaw) (result *MessageTraverse, err error) { + result = &MessageTraverse{ + MessageRaw: msg, + } + + if len(msg.Payload) < traversePayloadHeaderSize { + return nil, errors.New("traverse: invalid minimum length") + } + + targetPeerIDcompressed := msg.Payload[0:33] + authorizedRelayPeerIDcompressed := msg.Payload[33:66] + + if result.TargetPeer, err = btcec.ParsePubKey(targetPeerIDcompressed, btcec.S256()); err != nil { + return nil, err + } + if result.AuthorizedRelayPeer, err = btcec.ParsePubKey(authorizedRelayPeerIDcompressed, btcec.S256()); err != nil { + return nil, err + } + + // receiver and target must not be the same + if result.TargetPeer.IsEqual(result.AuthorizedRelayPeer) { + return nil, errors.New("traverse: target and relay invalid") + } + + expires64 := binary.LittleEndian.Uint64(msg.Payload[66 : 66+8]) + result.Expires = time.Unix(int64(expires64), 0) + + sizePacketEmbed := binary.LittleEndian.Uint16(msg.Payload[74 : 74+2]) + if int(sizePacketEmbed) != len(msg.Payload)-traversePayloadHeaderSize { + return nil, errors.New("traverse: size embedded packet mismatch") + } + + result.EmbeddedPacketRaw = msg.Payload[76 : 76+sizePacketEmbed] + + signature := msg.Payload[76+sizePacketEmbed : 76+sizePacketEmbed+65] + + result.SignerPublicKey, _, err = btcec.RecoverCompact(btcec.S256(), signature, hashData(msg.Payload[:76+sizePacketEmbed])) + if err != nil { + return nil, err + } + + // IPv4 + ipv4B := make([]byte, 4) + copy(ipv4B[:], msg.Payload[76+sizePacketEmbed+65:76+sizePacketEmbed+65+4]) + + result.IPv4 = ipv4B + result.PortIPv4 = binary.LittleEndian.Uint16(msg.Payload[76+sizePacketEmbed+65+4 : 76+sizePacketEmbed+65+4+2]) + result.PortIPv4ReportedExternal = binary.LittleEndian.Uint16(msg.Payload[76+sizePacketEmbed+65+6 : 76+sizePacketEmbed+65+6+2]) + + // IPv6 + ipv6B := make([]byte, 16) + copy(ipv6B[:], msg.Payload[76+sizePacketEmbed+65+8:76+sizePacketEmbed+65+8+16]) + + result.IPv6 = ipv6B + result.PortIPv6 = binary.LittleEndian.Uint16(msg.Payload[76+sizePacketEmbed+65+24 : 76+sizePacketEmbed+65+24+2]) + result.PortIPv6ReportedExternal = binary.LittleEndian.Uint16(msg.Payload[76+sizePacketEmbed+65+26 : 76+sizePacketEmbed+65+26+2]) + + // TODO: Validate IPv4 and IPv6. Only external ones allowed. + if result.IPv6.To4() != nil { + return nil, errors.New("traverse: ipv6 address mismatch") + } + + return result, nil +} + +// msgEncodeTraverse encodes a traverse message +func msgEncodeTraverse(senderPrivateKey *btcec.PrivateKey, embeddedPacketRaw []byte, receiverEnd *btcec.PublicKey, relayPeer *btcec.PublicKey) (packetRaw []byte, err error) { + sizePacketEmbed := len(embeddedPacketRaw) + if isPacketSizeExceed(traversePayloadHeaderSize, sizePacketEmbed) { + return nil, errors.New("traverse encode: embedded packet too big") + } + + raw := make([]byte, traversePayloadHeaderSize+sizePacketEmbed) + + targetPeerID := receiverEnd.SerializeCompressed() + copy(raw[0:33], targetPeerID) + authorizedRelayPeerID := relayPeer.SerializeCompressed() + copy(raw[33:66], authorizedRelayPeerID) + + expires64 := time.Now().Add(time.Hour).UTC().Unix() + binary.LittleEndian.PutUint64(raw[66:66+8], uint64(expires64)) + + binary.LittleEndian.PutUint16(raw[74:74+2], uint16(sizePacketEmbed)) + copy(raw[76:76+sizePacketEmbed], embeddedPacketRaw) + + // add signature + signature, err := btcec.SignCompact(btcec.S256(), senderPrivateKey, hashData(raw[:76+sizePacketEmbed]), true) + if err != nil { + return nil, err + } + copy(raw[76+sizePacketEmbed:76+sizePacketEmbed+65], signature) + + // IP and ports are to be filled by authorized relay peer + + return raw, nil +} + +// msgEncodeTraverseSetAddress sets the IP and Port +//func msgEncodeTraverseSetAddress(raw []byte, IPv4 net.IP, PortIPv4, PortIPv4ReportedExternal uint16, IPv6 net.IP, PortIPv6, PortIPv6ReportedExternal uint16) (err error) { +func msgEncodeTraverseSetAddress(raw []byte, connectionIPv4, connectionIPv6 *Connection) (err error) { + if isPacketSizeExceed(len(raw), 0) { + return errors.New("traverse encode 2: embedded packet too big") + } else if len(raw) < traversePayloadHeaderSize { + return errors.New("traverse encode 2: invalid packet") + } + + sizePacketEmbed := binary.LittleEndian.Uint16(raw[74 : 74+2]) + if int(sizePacketEmbed) != len(raw)-traversePayloadHeaderSize { + return errors.New("traverse encode 2: size embedded packet mismatch") + } + + // IPv4 + if connectionIPv4 != nil && connectionIPv4.IsIPv4() { + copy(raw[76+sizePacketEmbed+65:76+sizePacketEmbed+65+4], connectionIPv4.Address.IP.To4()) + binary.LittleEndian.PutUint16(raw[76+sizePacketEmbed+65+4:76+sizePacketEmbed+65+4+2], uint16(connectionIPv4.Address.Port)) + binary.LittleEndian.PutUint16(raw[76+sizePacketEmbed+65+6:76+sizePacketEmbed+65+6+2], connectionIPv4.PortExternal) + } + + // IPv6 + if connectionIPv6 != nil && connectionIPv6.IsIPv6() { + copy(raw[76+sizePacketEmbed+65+8:76+sizePacketEmbed+65+8+16], connectionIPv6.Address.IP.To16()) + binary.LittleEndian.PutUint16(raw[76+sizePacketEmbed+65+24:76+sizePacketEmbed+65+24+2], uint16(connectionIPv6.Address.Port)) + binary.LittleEndian.PutUint16(raw[76+sizePacketEmbed+65+26:76+sizePacketEmbed+65+26+2], connectionIPv6.PortExternal) + } + + return nil +} + // ---- messages sending ---- // pingConnection sends a ping to the target peer via the specified connection @@ -822,3 +974,13 @@ func (peer *PeerInfo) sendResponse(sequence uint32, sendUA bool, hash2Peers []Ha return err } + +// sendTraverse sends a traverse message +func (peer *PeerInfo) sendTraverse(embeddedPacketRaw []byte, receiverEnd *btcec.PublicKey) (err error) { + packetRaw, err := msgEncodeTraverse(peerPrivateKey, embeddedPacketRaw, receiverEnd, peer.PublicKey) + if err != nil { + return err + } + + return peer.send(&PacketRaw{Command: CommandTraverse, Payload: packetRaw}) +} diff --git a/Network.go b/Network.go index 07c3475..8e9892e 100644 --- a/Network.go +++ b/Network.go @@ -203,7 +203,7 @@ func packetWorker(packets <-chan networkWire) { if len(announce.UserAgent) > 0 { peer.UserAgent = announce.UserAgent } - peer.Features = response.Features + peer.Features = announce.Features peer.cmdLocalDiscovery(announce) } @@ -225,6 +225,15 @@ func packetWorker(packets <-chan networkWire) { case CommandChat: // Chat [debug] peer.cmdChat(raw) + case CommandTraverse: + if traverse, _ := msgDecodeTraverse(raw); traverse != nil { + if traverse.TargetPeer.IsEqual(peerPublicKey) && traverse.AuthorizedRelayPeer.IsEqual(peer.PublicKey) { + peer.cmdTraverseReceive(traverse) + } else if traverse.AuthorizedRelayPeer.IsEqual(peerPublicKey) { + peer.cmdTraverseForward(traverse) + } + } + default: // Unknown command } diff --git a/README.md b/README.md index a7dac0f..0b61f89 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ The core library which is needed for any Peernet application. It provides connectivity to the network and all basic functions. For details about Peernet see https://peernet.org/. For the current technical roadmap and upcoming releases see the [Talk forum](https://talk.peernet.org/discussion/10/technical-roadmap). -Current version: 0.2 (pre alpha 2) +Current version: Alpha 2 Current development status: Initial connectivity works. DHT functionality is in development.