Some attempt at exchanging session metadata over the wire (broken)

This commit is contained in:
Neil Alexander 2018-10-21 22:58:27 +01:00
parent 4f435705e3
commit a1b72c16d8
No known key found for this signature in database
GPG Key ID: A02A2019A2BB0944
9 changed files with 228 additions and 83 deletions

View File

@ -470,7 +470,9 @@ func (a *admin) getData_getSelf() *admin_nodeInfo {
{"ip", a.core.GetAddress().String()},
{"subnet", a.core.GetSubnet().String()},
{"coords", fmt.Sprint(coords)},
{"friendly_name", a.core.friendlyName},
{"name", a.core.metadata.name},
{"location", a.core.metadata.location},
{"contact", a.core.metadata.contact},
}
return &self
}
@ -494,7 +496,6 @@ func (a *admin) getData_getPeers() []admin_nodeInfo {
{"bytes_sent", atomic.LoadUint64(&p.bytesSent)},
{"bytes_recvd", atomic.LoadUint64(&p.bytesRecvd)},
{"endpoint", p.endpoint},
{"friendly_name", p.friendlyName},
}
peerInfos = append(peerInfos, info)
}
@ -520,7 +521,6 @@ func (a *admin) getData_getSwitchPeers() []admin_nodeInfo {
{"bytes_sent", atomic.LoadUint64(&peer.bytesSent)},
{"bytes_recvd", atomic.LoadUint64(&peer.bytesRecvd)},
{"endpoint", peer.endpoint},
{"friendly_name", peer.friendlyName},
}
peerInfos = append(peerInfos, info)
}

View File

@ -2,7 +2,7 @@ package config
// NodeConfig defines all configuration values needed to run a signle yggdrasil node
type NodeConfig struct {
FriendlyName string `comment:"Friendly name for this node. It is visible to direct peers."`
Metadata Metadata `comment:"Optional node metadata. Entirely optional but visible to all\npeers and nodes with open sessions."`
Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."`
AdminListen string `comment:"Listen address for admin connections Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X."`
Peers []string `comment:"List of connection strings for static peers in URI format, i.e.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."`
@ -35,3 +35,9 @@ type SessionFirewall struct {
WhitelistEncryptionPublicKeys []string `comment:"List of public keys from which network traffic is always accepted,\nregardless of AllowFromDirect or AllowFromRemote."`
BlacklistEncryptionPublicKeys []string `comment:"List of public keys from which network traffic is always rejected,\nregardless of the whitelist, AllowFromDirect or AllowFromRemote."`
}
type Metadata struct {
Name string
Location string
Contact string
}

View File

@ -16,31 +16,31 @@ import (
// object for each Yggdrasil node you plan to run.
type Core struct {
// This is the main data structure that holds everything else for a node
boxPub boxPubKey
boxPriv boxPrivKey
sigPub sigPubKey
sigPriv sigPrivKey
friendlyName string
switchTable switchTable
peers peers
sigs sigManager
sessions sessions
router router
dht dht
tun tunDevice
admin admin
searches searches
multicast multicast
tcp tcpInterface
log *log.Logger
ifceExpr []*regexp.Regexp // the zone of link-local IPv6 peers must match this
boxPub boxPubKey
boxPriv boxPrivKey
sigPub sigPubKey
sigPriv sigPrivKey
metadata metadata
switchTable switchTable
peers peers
sigs sigManager
sessions sessions
router router
dht dht
tun tunDevice
admin admin
searches searches
multicast multicast
tcp tcpInterface
log *log.Logger
ifceExpr []*regexp.Regexp // the zone of link-local IPv6 peers must match this
}
func (c *Core) init(bpub *boxPubKey,
bpriv *boxPrivKey,
spub *sigPubKey,
spriv *sigPrivKey,
friendlyname string) {
metadata metadata) {
// TODO separate init and start functions
// Init sets up structs
// Start launches goroutines that depend on structs being set up
@ -51,7 +51,7 @@ func (c *Core) init(bpub *boxPubKey,
}
c.boxPub, c.boxPriv = *bpub, *bpriv
c.sigPub, c.sigPriv = *spub, *spriv
c.friendlyName = friendlyname
c.metadata = metadata
c.admin.core = c
c.sigs.init()
c.searches.init(c)
@ -65,11 +65,8 @@ func (c *Core) init(bpub *boxPubKey,
}
// Gets the friendly name of this node, as specified in the NodeConfig.
func (c *Core) GetFriendlyName() string {
if c.friendlyName == "" {
return "(none)"
}
return c.friendlyName
func (c *Core) GetMeta() metadata {
return c.metadata
}
// Starts up Yggdrasil using the provided NodeConfig, and outputs debug logging
@ -105,7 +102,13 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error {
copy(sigPub[:], sigPubHex)
copy(sigPriv[:], sigPrivHex)
c.init(&boxPub, &boxPriv, &sigPub, &sigPriv, nc.FriendlyName)
meta := metadata{
name: nc.Metadata.Name,
location: nc.Metadata.Location,
contact: nc.Metadata.Contact,
}
c.init(&boxPub, &boxPriv, &sigPub, &sigPriv, meta)
c.admin.init(c, nc.AdminListen)
if err := c.tcp.init(c, nc.Listen, nc.ReadTimeout); err != nil {

View File

@ -0,0 +1,7 @@
package yggdrasil
type metadata struct {
name string
location string
contact string
}

View File

@ -79,34 +79,34 @@ type peer struct {
bytesSent uint64 // To track bandwidth usage for getPeers
bytesRecvd uint64 // To track bandwidth usage for getPeers
// BUG: sync/atomic, 32 bit platforms need the above to be the first element
core *Core
port switchPort
box boxPubKey
sig sigPubKey
shared boxSharedKey
linkShared boxSharedKey
endpoint string
friendlyName string
firstSeen time.Time // To track uptime for getPeers
linkOut (chan []byte) // used for protocol traffic (to bypass queues)
doSend (chan struct{}) // tell the linkLoop to send a switchMsg
dinfo *dhtInfo // used to keep the DHT working
out func([]byte) // Set up by whatever created the peers struct, used to send packets to other nodes
close func() // Called when a peer is removed, to close the underlying connection, or via admin api
core *Core
port switchPort
box boxPubKey
sig sigPubKey
shared boxSharedKey
linkShared boxSharedKey
endpoint string
metadata metadata
firstSeen time.Time // To track uptime for getPeers
linkOut (chan []byte) // used for protocol traffic (to bypass queues)
doSend (chan struct{}) // tell the linkLoop to send a switchMsg
dinfo *dhtInfo // used to keep the DHT working
out func([]byte) // Set up by whatever created the peers struct, used to send packets to other nodes
close func() // Called when a peer is removed, to close the underlying connection, or via admin api
}
// Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unocupied port number.
func (ps *peers) newPeer(box *boxPubKey, sig *sigPubKey, linkShared *boxSharedKey, endpoint string, friendlyname string) *peer {
func (ps *peers) newPeer(box *boxPubKey, sig *sigPubKey, linkShared *boxSharedKey, endpoint string, metadata metadata) *peer {
now := time.Now()
p := peer{box: *box,
sig: *sig,
shared: *getSharedKey(&ps.core.boxPriv, box),
linkShared: *linkShared,
endpoint: endpoint,
friendlyName: friendlyname,
firstSeen: now,
doSend: make(chan struct{}, 1),
core: ps.core}
sig: *sig,
shared: *getSharedKey(&ps.core.boxPriv, box),
linkShared: *linkShared,
endpoint: endpoint,
metadata: metadata,
firstSeen: now,
doSend: make(chan struct{}, 1),
core: ps.core}
ps.mutex.Lock()
defer ps.mutex.Unlock()
oldPorts := ps.getPorts()

View File

@ -47,7 +47,7 @@ func (r *router) init(core *Core) {
r.core = core
r.addr = *address_addrForNodeID(&r.core.dht.nodeID)
in := make(chan []byte, 32) // TODO something better than this...
p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)", r.core.GetFriendlyName())
p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)", r.core.metadata)
p.out = func(packet []byte) {
// This is to make very sure it never blocks
select {
@ -324,6 +324,10 @@ func (r *router) handleProto(packet []byte) {
r.handlePing(bs, &p.FromKey)
case wire_SessionPong:
r.handlePong(bs, &p.FromKey)
case wire_SessionMetaRequest:
fallthrough
case wire_SessionMetaResponse:
r.handleMeta(bs, &p.FromKey)
case wire_DHTLookupRequest:
r.handleDHTReq(bs, &p.FromKey)
case wire_DHTLookupResponse:
@ -368,6 +372,17 @@ func (r *router) handleDHTRes(bs []byte, fromKey *boxPubKey) {
r.core.dht.handleRes(&res)
}
// Decodes meta request
func (r *router) handleMeta(bs []byte, fromKey *boxPubKey) {
req := sessionMeta{}
if !req.decode(bs) {
return
}
req.SendPermPub = *fromKey
r.core.log.Printf("handleMeta: %+v\n", req)
r.core.sessions.handleMeta(&req)
}
// Passed a function to call.
// This will send the function to r.admin and block until it finishes.
// It's used by the admin socket to ask the router mainLoop goroutine about information in the session or dht structs, which cannot be read safely from outside that goroutine.

View File

@ -13,34 +13,37 @@ import (
// All the information we know about an active session.
// This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API.
type sessionInfo struct {
core *Core
theirAddr address
theirSubnet subnet
theirPermPub boxPubKey
theirSesPub boxPubKey
mySesPub boxPubKey
mySesPriv boxPrivKey
sharedSesKey boxSharedKey // derived from session keys
theirHandle handle
myHandle handle
theirNonce boxNonce
myNonce boxNonce
theirMTU uint16
myMTU uint16
wasMTUFixed bool // Was the MTU fixed by a receive error?
time time.Time // Time we last received a packet
coords []byte // coords of destination
packet []byte // a buffered packet, sent immediately on ping/pong
init bool // Reset if coords change
send chan []byte
recv chan *wire_trafficPacket
nonceMask uint64
tstamp int64 // tstamp from their last session ping, replay attack mitigation
mtuTime time.Time // time myMTU was last changed
pingTime time.Time // time the first ping was sent since the last received packet
pingSend time.Time // time the last ping was sent
bytesSent uint64 // Bytes of real traffic sent in this session
bytesRecvd uint64 // Bytes of real traffic received in this session
core *Core
theirAddr address
theirSubnet subnet
theirPermPub boxPubKey
theirSesPub boxPubKey
mySesPub boxPubKey
mySesPriv boxPrivKey
sharedSesKey boxSharedKey // derived from session keys
theirHandle handle
myHandle handle
theirNonce boxNonce
myNonce boxNonce
metaReqTime time.Time
metaResTime time.Time
theirMetadata metadata
theirMTU uint16
myMTU uint16
wasMTUFixed bool // Was the MTU fixed by a receive error?
time time.Time // Time we last received a packet
coords []byte // coords of destination
packet []byte // a buffered packet, sent immediately on ping/pong
init bool // Reset if coords change
send chan []byte
recv chan *wire_trafficPacket
nonceMask uint64
tstamp int64 // tstamp from their last session ping, replay attack mitigation
mtuTime time.Time // time myMTU was last changed
pingTime time.Time // time the first ping was sent since the last received packet
pingSend time.Time // time the last ping was sent
bytesSent uint64 // Bytes of real traffic sent in this session
bytesRecvd uint64 // Bytes of real traffic received in this session
}
// Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU.
@ -54,6 +57,13 @@ type sessionPing struct {
MTU uint16
}
// Represents a session metadata packet.
type sessionMeta struct {
SendPermPub boxPubKey // Sender's permanent key
IsResponse bool
Metadata metadata
}
// Updates session info in response to a ping, after checking that the ping is OK.
// Returns true if the session was updated, or false otherwise.
func (s *sessionInfo) update(p *sessionPing) bool {
@ -431,6 +441,66 @@ func (ss *sessions) handlePing(ping *sessionPing) {
bs, sinfo.packet = sinfo.packet, nil
ss.core.router.sendPacket(bs)
}
if time.Since(sinfo.metaResTime).Minutes() > 15 {
if time.Since(sinfo.metaReqTime).Minutes() > 1 {
ss.sendMeta(sinfo, false)
}
}
}
func (ss *sessions) sendMeta(sinfo *sessionInfo, isResponse bool) {
meta := sessionMeta{
IsResponse: isResponse,
Metadata: metadata{
name: "some.name.com", //[]byte(ss.core.friendlyName)[0:len(ss.core.friendlyName):32],
location: "Some Place",
contact: "someone@somewhere.com",
},
}
bs := meta.encode()
shared := ss.getSharedKey(&ss.core.boxPriv, &sinfo.theirPermPub)
payload, nonce := boxSeal(shared, bs, nil)
p := wire_protoTrafficPacket{
Coords: sinfo.coords,
ToKey: sinfo.theirPermPub,
FromKey: ss.core.boxPub,
Nonce: *nonce,
Payload: payload,
}
packet := p.encode()
ss.core.router.out(packet)
if isResponse {
ss.core.log.Println("Sent meta response to", sinfo.theirAddr)
} else {
ss.core.log.Println("Sent meta request to", sinfo.theirAddr)
sinfo.metaReqTime = time.Now()
}
}
// Handles a meta request/response.
func (ss *sessions) handleMeta(meta *sessionMeta) {
// Get the corresponding session (or create a new session)
sinfo, isIn := ss.getByTheirPerm(&meta.SendPermPub)
// Check the session firewall
if !isIn && ss.sessionFirewallEnabled {
if !ss.isSessionAllowed(&meta.SendPermPub, false) {
return
}
}
if !isIn || sinfo.timedout() {
return
}
if meta.IsResponse {
ss.core.log.Println("Received meta response", string(meta.Metadata.name), "from", sinfo.theirAddr)
sinfo.theirMetadata = meta.Metadata
sinfo.metaResTime = time.Now()
ss.core.log.Println("- name:", meta.Metadata.name)
ss.core.log.Println("- contact:", meta.Metadata.contact)
ss.core.log.Println("- location:", meta.Metadata.location)
} else {
ss.core.log.Println("Received meta request", string(meta.Metadata.name), "from", sinfo.theirAddr)
ss.sendMeta(sinfo, true)
}
}
// Used to subtract one nonce from another, staying in the range +- 64.

View File

@ -287,7 +287,7 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) {
}()
// Note that multiple connections to the same node are allowed
// E.g. over different interfaces
p := iface.core.peers.newPeer(&info.box, &info.sig, getSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String(), "(none)")
p := iface.core.peers.newPeer(&info.box, &info.sig, getSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String(), metadata{})
p.linkOut = make(chan []byte, 1)
in := func(bs []byte) {
p.handlePacket(bs)

View File

@ -16,6 +16,8 @@ const (
wire_SessionPong // inside protocol traffic header
wire_DHTLookupRequest // inside protocol traffic header
wire_DHTLookupResponse // inside protocol traffic header
wire_SessionMetaRequest // inside protocol traffic header
wire_SessionMetaResponse // inside protocol traffic header
)
// Calls wire_put_uint64 on a nil slice.
@ -353,6 +355,48 @@ func (p *sessionPing) decode(bs []byte) bool {
////////////////////////////////////////////////////////////////////////////////
// Encodes a sessionPing into its wire format.
func (p *sessionMeta) encode() []byte {
var pTypeVal uint64
if p.IsResponse {
pTypeVal = wire_SessionMetaResponse
} else {
pTypeVal = wire_SessionMetaRequest
}
bs := wire_encode_uint64(pTypeVal)
if p.IsResponse {
bs = append(bs, p.Metadata.name...)
bs = append(bs, p.Metadata.location...)
bs = append(bs, p.Metadata.contact...)
}
return bs
}
// Decodes an encoded sessionPing into the struct, returning true if successful.
func (p *sessionMeta) decode(bs []byte) bool {
var pType uint64
switch {
case !wire_chop_uint64(&pType, &bs):
return false
case pType != wire_SessionMetaRequest && pType != wire_SessionMetaResponse:
return false
}
p.IsResponse = pType == wire_SessionMetaResponse
if p.IsResponse {
switch {
case !wire_chop_slice([]byte(p.Metadata.name), &bs):
return false
case !wire_chop_slice([]byte(p.Metadata.location), &bs):
return false
case !wire_chop_slice([]byte(p.Metadata.contact), &bs):
return false
}
}
return true
}
////////////////////////////////////////////////////////////////////////////////
// Encodes a dhtReq into its wire format.
func (r *dhtReq) encode() []byte {
coords := wire_encode_coords(r.Coords)