diff --git a/Connection.go b/Connection.go index f3bc3a4..f1b8c69 100644 --- a/Connection.go +++ b/Connection.go @@ -426,7 +426,7 @@ func sendAllNetworks(receiverPublicKey *btcec.PublicKey, packet *protocol.Packet } if sequenceData != nil { - packet.Sequence = msgArbitrarySequence(receiverPublicKey, sequenceData).sequence + packet.Sequence = ArbitrarySequence(receiverPublicKey, sequenceData).SequenceNumber } err = (&Connection{Network: network, Address: remote, PortInternal: receiverPortInternal, traversePeer: traversePeer}).send(packet, receiverPublicKey, isFirstPacket) isFirstPacket = false diff --git a/Message Send.go b/Message Send.go index 2d5a3eb..386db6b 100644 --- a/Message Send.go +++ b/Message Send.go @@ -15,7 +15,7 @@ import ( // pingConnection sends a ping to the target peer via the specified connection func (peer *PeerInfo) pingConnection(connection *Connection) { - raw := &protocol.PacketRaw{Command: protocol.CommandPing, Sequence: msgNewSequence(peer.PublicKey, &peer.messageSequence, nil).sequence} + raw := &protocol.PacketRaw{Command: protocol.CommandPing, Sequence: NewSequence(peer.PublicKey, &peer.messageSequence, nil).SequenceNumber} Filters.MessageOutPing(peer, raw, connection) err := peer.sendConnection(raw, connection) @@ -37,8 +37,7 @@ func (peer *PeerInfo) sendAnnouncement(sendUA, findSelf bool, findPeer []KeyHash packets := EncodeAnnouncement(sendUA, findSelf, findPeer, findValue, files, FeatureSupport(), blockchainHeight, blockchainVersion) for _, packet := range packets { - sequence := msgNewSequence(peer.PublicKey, &peer.messageSequence, sequenceData) - raw := &protocol.PacketRaw{Command: protocol.CommandAnnouncement, Payload: packet, Sequence: sequence.sequence} + raw := &protocol.PacketRaw{Command: protocol.CommandAnnouncement, Payload: packet, Sequence: NewSequence(peer.PublicKey, &peer.messageSequence, sequenceData).SequenceNumber} Filters.MessageOutAnnouncement(peer.PublicKey, peer, raw, findSelf, findPeer, findValue, files) peer.send(raw) } diff --git a/Message Sequence.go b/Message Sequence.go index 97a2822..8464eb4 100644 --- a/Message Sequence.go +++ b/Message Sequence.go @@ -3,7 +3,7 @@ File Name: Message Sequence.go Copyright: 2021 Peernet s.r.o. Author: Peter Kleissner -Records and verifies message sequences. +Records and verifies message sequences. Sequence numbers are valid on a peer level, independent of which network connection was used. Advantages: * This secures against replay and poisoning attacks. @@ -25,6 +25,9 @@ import ( "github.com/btcsuite/btcd/btcec" ) +// SequenceReplyTimeout is the round-trip timeout for message sequences. +var SequenceReplyTimeout = 20 + // sequences stores all sequence numbers that are valid at the moment. The value represents the time the sequence number was used. // Key = Peer ID + Sequence Number var sequences map[string]*SequenceExpiry @@ -32,11 +35,11 @@ var sequencesMutex sync.Mutex // SequenceExpiry contains the decoded sequence information of a message. type SequenceExpiry struct { - sequence uint32 // Sequence number - created time.Time // When the sequence was created. - expires time.Time // When the sequence expires. This can be extended on the fly! - counter int // How many replies used the sequence. Multiple Response messages may be returned for a single Announcement one. - Data interface{} // Optional high-level data associated with the sequence + SequenceNumber uint32 // Sequence number + created time.Time // When the sequence was created. + expires time.Time // When the sequence expires. This can be extended on the fly! + counter int // How many replies used the sequence. Multiple Response messages may be returned for a single Announcement one. + Data interface{} // Optional high-level data associated with the sequence } func initMessageSequence() { @@ -45,7 +48,7 @@ func initMessageSequence() { // auto-delete worker to remove expired sequences go func() { for { - time.Sleep(time.Duration(ReplyTimeout) * time.Second) + time.Sleep(time.Duration(SequenceReplyTimeout) * time.Second) now := time.Now() sequencesMutex.Lock() @@ -59,18 +62,18 @@ func initMessageSequence() { }() } -// msgNewSequence returns a new sequence and registers it. messageSequence must point to the variable holding the continuous next sequence number. +// NewSequence returns a new sequence and registers it. messageSequence must point to the variable holding the continuous next sequence number. // Use only for Announcement and Ping messages. -func msgNewSequence(publicKey *btcec.PublicKey, messageSequence *uint32, data interface{}) (info *SequenceExpiry) { +func NewSequence(publicKey *btcec.PublicKey, messageSequence *uint32, data interface{}) (info *SequenceExpiry) { info = &SequenceExpiry{ - sequence: atomic.AddUint32(messageSequence, 1), - created: time.Now(), - expires: time.Now().Add(time.Duration(ReplyTimeout) * time.Second), - Data: data, + SequenceNumber: atomic.AddUint32(messageSequence, 1), + created: time.Now(), + expires: time.Now().Add(time.Duration(SequenceReplyTimeout) * time.Second), + Data: data, } // Add the sequence to the list. Sequences are unique enough that collisions are unlikely and negligible. - key := string(publicKey.SerializeCompressed()) + strconv.FormatUint(uint64(info.sequence), 10) + key := string(publicKey.SerializeCompressed()) + strconv.FormatUint(uint64(info.SequenceNumber), 10) sequencesMutex.Lock() sequences[key] = info sequencesMutex.Unlock() @@ -78,17 +81,17 @@ func msgNewSequence(publicKey *btcec.PublicKey, messageSequence *uint32, data in return } -// msgArbitrarySequence returns an arbitrary sequence to be used for uncontacted peers -func msgArbitrarySequence(publicKey *btcec.PublicKey, data interface{}) (info *SequenceExpiry) { +// ArbitrarySequence returns an arbitrary sequence to be used for uncontacted peers +func ArbitrarySequence(publicKey *btcec.PublicKey, data interface{}) (info *SequenceExpiry) { info = &SequenceExpiry{ - sequence: rand.Uint32(), - created: time.Now(), - expires: time.Now().Add(time.Duration(ReplyTimeout) * time.Second), - Data: data, + SequenceNumber: rand.Uint32(), + created: time.Now(), + expires: time.Now().Add(time.Duration(SequenceReplyTimeout) * time.Second), + Data: data, } // Add the sequence to the list. Sequences are unique enough that collisions are unlikely and negligible. - key := string(publicKey.SerializeCompressed()) + strconv.FormatUint(uint64(info.sequence), 10) + key := string(publicKey.SerializeCompressed()) + strconv.FormatUint(uint64(info.SequenceNumber), 10) sequencesMutex.Lock() sequences[key] = info sequencesMutex.Unlock() @@ -96,8 +99,8 @@ func msgArbitrarySequence(publicKey *btcec.PublicKey, data interface{}) (info *S return } -// msgValidateSequence validates the sequence number of an incoming message. It will set raw.sequence if valid. -func msgValidateSequence(raw *MessageRaw, invalidate bool) (valid bool, rtt time.Duration) { +// ValidateSequence validates the sequence number of an incoming message. It will set raw.sequence if valid. +func ValidateSequence(raw *MessageRaw, invalidate bool) (valid bool, rtt time.Duration) { // Only Response and Pong if raw.Command != protocol.CommandResponse && raw.Command != protocol.CommandPong { return true, rtt @@ -127,7 +130,7 @@ func msgValidateSequence(raw *MessageRaw, invalidate bool) (valid bool, rtt time delete(sequences, key) } else if raw.Command == protocol.CommandResponse { // Special case CommandResponse: Extend validity in case there are follow-up responses by half of the round-trip time since they will be sent one-way. - sequence.expires = time.Now().Add(time.Duration(ReplyTimeout) * time.Second / 2) + sequence.expires = time.Now().Add(time.Duration(SequenceReplyTimeout) * time.Second / 2) } raw.SequenceInfo = sequence @@ -135,8 +138,8 @@ func msgValidateSequence(raw *MessageRaw, invalidate bool) (valid bool, rtt time return sequence.expires.After(time.Now()), rtt } -// msgInvalidateSequence invalidates the sequence number. -func msgInvalidateSequence(raw *MessageRaw) { +// InvalidateSequence invalidates the sequence number. +func InvalidateSequence(raw *MessageRaw) { // Only Response if raw.Command != protocol.CommandResponse { return diff --git a/Network.go b/Network.go index d58a6aa..d6135ce 100644 --- a/Network.go +++ b/Network.go @@ -49,9 +49,6 @@ var countListen4, countListen6 int64 // Default ports to use. This may be randomized in the future to prevent fingerprinting (and subsequent blocking) by corporate and ISP firewalls. const defaultPort = 'p' // 112 -// ReplyTimeout is the round-trip timeout for message sequences. -var ReplyTimeout = 20 - // AutoAssignPort assigns a port for the given IP. Use port 0 for zero configuration. func (network *Network) AutoAssignPort(ip net.IP, port int) (err error) { networkA := "udp6" @@ -198,7 +195,7 @@ func packetWorker(packets <-chan networkWire) { case protocol.CommandResponse: // Response if response, _ := msgDecodeResponse(raw); response != nil { // Validate sequence number which prevents unsolicited responses. - if valid, rtt := msgValidateSequence(raw, response.Actions&(1< 0); !valid { + if valid, rtt := ValidateSequence(raw, response.Actions&(1< 0); !valid { //Filters.LogError("packetWorker", "message with invalid sequence %d command %d from %s\n", raw.Sequence, raw.Command, raw.connection.Address.String()) // Only log for debug purposes. continue } else if rtt > 0 { @@ -240,7 +237,7 @@ func packetWorker(packets <-chan networkWire) { case protocol.CommandPong: // Ping // Validate sequence number which prevents unsolicited responses. - if valid, rtt := msgValidateSequence(raw, true); !valid { + if valid, rtt := ValidateSequence(raw, true); !valid { //Filters.LogError("packetWorker", "message with invalid sequence %d command %d from %s\n", raw.Sequence, raw.Command, raw.connection.Address.String()) // Only log for debug purposes. continue } else if rtt > 0 {