diff --git a/src/admin/getpeers.go b/src/admin/getpeers.go index 90736ad6..a013d12a 100644 --- a/src/admin/getpeers.go +++ b/src/admin/getpeers.go @@ -33,6 +33,7 @@ type PeerEntry struct { Latency time.Duration `json:"latency,omitempty"` LastErrorTime time.Duration `json:"last_error_time,omitempty"` LastError string `json:"last_error,omitempty"` + NodeInfo string `json:"nodeinfo,omitempty"` // NodeInfo from peer handshake } func (a *AdminSocket) getPeersHandler(_ *GetPeersRequest, res *GetPeersResponse) error { @@ -63,6 +64,11 @@ func (a *AdminSocket) getPeersHandler(_ *GetPeersRequest, res *GetPeersResponse) peer.LastError = p.LastError.Error() peer.LastErrorTime = time.Since(p.LastErrorTime) } + + // Add NodeInfo if available + if len(p.NodeInfo) > 0 { + peer.NodeInfo = string(p.NodeInfo) + } res.Peers = append(res.Peers, peer) } diff --git a/src/core/api.go b/src/core/api.go index cc1bde32..d4b96391 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -37,6 +37,7 @@ type PeerInfo struct { TXRate uint64 Uptime time.Duration Latency time.Duration + NodeInfo []byte // NodeInfo received during handshake } type TreeEntryInfo struct { @@ -92,6 +93,11 @@ func (c *Core) GetPeers() []PeerInfo { peerinfo.RXRate = atomic.LoadUint64(&c.rxrate) peerinfo.TXRate = atomic.LoadUint64(&c.txrate) peerinfo.Uptime = time.Since(c.up) + // Add NodeInfo from handshake + if len(state._nodeInfo) > 0 { + peerinfo.NodeInfo = make([]byte, len(state._nodeInfo)) + copy(peerinfo.NodeInfo, state._nodeInfo) + } } if p, ok := conns[conn]; ok { peerinfo.Key = p.Key diff --git a/src/core/link.go b/src/core/link.go index dce19278..7645aa3d 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -64,9 +64,10 @@ type link struct { linkType linkType // Type of link, i.e. outbound/inbound, persistent/ephemeral linkProto string // Protocol carrier of link, e.g. TCP, AWDL // The remaining fields can only be modified safely from within the links actor - _conn *linkConn // Connected link, if any, nil if not connected - _err error // Last error on the connection, if any - _errtime time.Time // Last time an error occurred + _conn *linkConn // Connected link, if any, nil if not connected + _err error // Last error on the connection, if any + _errtime time.Time // Last time an error occurred + _nodeInfo []byte // NodeInfo received from peer during handshake } type linkOptions struct { @@ -246,6 +247,7 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error { linkType: linkType, linkProto: strings.ToUpper(u.Scheme), kick: make(chan struct{}), + _nodeInfo: nil, // Initialize NodeInfo field } state.ctx, state.cancel = context.WithCancel(l.core.ctx) @@ -524,6 +526,7 @@ func (l *links) listen(u *url.URL, sintf string, local bool) (*Listener, error) linkType: linkTypeIncoming, linkProto: strings.ToUpper(u.Scheme), kick: make(chan struct{}), + _nodeInfo: nil, // Initialize NodeInfo field } } if state._conn != nil { @@ -605,6 +608,16 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn, s meta := version_getBaseMetadata() meta.publicKey = l.core.public meta.priority = options.priority + + // Add our NodeInfo to handshake if available + phony.Block(&l.core.proto.nodeinfo, func() { + nodeInfo := l.core.proto.nodeinfo._getNodeInfo() + if len(nodeInfo) > 0 { + meta.nodeInfo = make([]byte, len(nodeInfo)) + copy(meta.nodeInfo, nodeInfo) + } + }) + metaBytes, err := meta.encode(l.core.secret, options.password) if err != nil { return fmt.Errorf("failed to generate handshake: %w", err) @@ -661,6 +674,20 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn, s } } + // Store the received NodeInfo in the link state + if len(meta.nodeInfo) > 0 { + phony.Block(l, func() { + // Find the link state for this connection + for _, state := range l._links { + if state._conn != nil && state._conn.Conn == conn { + state._nodeInfo = make([]byte, len(meta.nodeInfo)) + copy(state._nodeInfo, meta.nodeInfo) + break + } + } + }) + } + dir := "outbound" if linkType == linkTypeIncoming { dir = "inbound" diff --git a/src/core/version.go b/src/core/version.go index bb3b9538..05b5c810 100644 --- a/src/core/version.go +++ b/src/core/version.go @@ -8,6 +8,7 @@ import ( "bytes" "crypto/ed25519" "encoding/binary" + "fmt" "io" "golang.org/x/crypto/blake2b" @@ -21,6 +22,7 @@ type version_metadata struct { minorVer uint16 publicKey ed25519.PublicKey priority uint8 + nodeInfo []byte // NodeInfo data from configuration } const ( @@ -35,6 +37,7 @@ const ( metaVersionMinor // uint16 metaPublicKey // [32]byte metaPriority // uint8 + metaNodeInfo // []byte ) type handshakeError string @@ -52,6 +55,7 @@ func version_getBaseMetadata() version_metadata { return version_metadata{ majorVer: ProtocolVersionMajor, minorVer: ProtocolVersionMinor, + nodeInfo: nil, // Will be set during handshake } } @@ -77,6 +81,16 @@ func (m *version_metadata) encode(privateKey ed25519.PrivateKey, password []byte bs = binary.BigEndian.AppendUint16(bs, 1) bs = append(bs, m.priority) + // Add NodeInfo if available (with size validation) + if len(m.nodeInfo) > 0 { + if len(m.nodeInfo) > 16384 { + return nil, fmt.Errorf("NodeInfo exceeds max length of 16384 bytes") + } + bs = binary.BigEndian.AppendUint16(bs, metaNodeInfo) + bs = binary.BigEndian.AppendUint16(bs, uint16(len(m.nodeInfo))) + bs = append(bs, m.nodeInfo...) + } + hasher, err := blake2b.New512(password) if err != nil { return nil, err @@ -135,6 +149,13 @@ func (m *version_metadata) decode(r io.Reader, password []byte) error { case metaPriority: m.priority = bs[0] + + case metaNodeInfo: + if oplen > 16384 { + return fmt.Errorf("received NodeInfo exceeds max length of 16384 bytes") + } + m.nodeInfo = make([]byte, oplen) + copy(m.nodeInfo, bs[:oplen]) } bs = bs[oplen:] }