shikigrid/mesh/peer.go

387 lines
9.9 KiB
Go

package mesh
import (
"bytes"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"net"
"regexp"
"strings"
"sync"
"time"
"github.com/evilsocket/islazy/log"
"github.com/gopacket/gopacket/layers"
"github.com/andatoshiki/shikigrid/crypto"
"github.com/andatoshiki/shikigrid/version"
"github.com/andatoshiki/shikigrid/wifi"
)
var (
SignalingPeriod = 300
fingValidator = regexp.MustCompile("^[a-fA-F0-9]{64}$")
)
type SessionID []byte
type Peer struct {
sync.Mutex
MetAt time.Time // first time met
DetectedAt time.Time // first time detected on this session
SeenAt time.Time // last time detected on this session
PrevSeenAt time.Time // if we met this unit before, this is the last time it's been seen
Encounters uint64
Channel int
RSSI int
SessionID SessionID
SessionIDStr string
Keys *crypto.KeyPair
AdvData sync.Map
AdvPeriod int
advEnabled bool
ForceDisabled bool
mux *PacketMuxer
stop chan struct{}
}
func MakeLocalPeer(name string, keys *crypto.KeyPair, advertise bool) *Peer {
now := time.Now()
peer := &Peer{
DetectedAt: now,
SeenAt: now,
PrevSeenAt: now,
SessionID: make([]byte, 6),
Keys: keys,
AdvData: sync.Map{},
AdvPeriod: SignalingPeriod,
stop: make(chan struct{}),
advEnabled: false,
ForceDisabled: false,
}
if !advertise {
peer.ForceDisabled = true
}
if _, err := rand.Read(peer.SessionID); err != nil {
panic(err)
}
parts := make([]string, 6)
for idx, byte := range peer.SessionID {
parts[idx] = fmt.Sprintf("%02x", byte)
}
peer.SessionIDStr = strings.Join(parts, ":")
peer.AdvData.Store("name", name)
peer.AdvData.Store("identity", keys.FingerprintHex)
peer.AdvData.Store("session_id", peer.SessionIDStr)
peer.AdvData.Store("grid_version", version.Version)
peer.AdvData.Range(func(key, value interface{}) bool {
log.Debug("local.adv.%s = %s", key, value)
return true
})
return peer
}
func (peer *Peer) Advertise(enabled bool) {
peer.Lock()
defer peer.Unlock()
diff := peer.advEnabled != enabled
peer.advEnabled = enabled
if diff {
if enabled {
log.Info("peer advertisement enabled")
} else {
log.Info("peer advertisement disabled")
}
}
}
func NewPeer(radiotap *layers.RadioTap, dot11 *layers.Dot11, adv map[string]interface{}) (peer *Peer, err error) {
now := time.Now()
peer = &Peer{
DetectedAt: now,
SeenAt: now,
PrevSeenAt: now,
Channel: wifi.Freq2Chan(int(radiotap.ChannelFrequency)),
RSSI: int(radiotap.DBMAntennaSignal),
SessionID: SessionID(dot11.Address3),
AdvData: sync.Map{},
}
parts := make([]string, 6)
for idx, byte := range peer.SessionID {
parts[idx] = fmt.Sprintf("%02x", byte)
}
peer.SessionIDStr = strings.Join(parts, ":")
// parse the fingerprint, the signature and the public key
fingerprint, found := adv["identity"].(string)
if !found {
return nil, fmt.Errorf("peer %x is not advertising any identity", peer.SessionID)
} else if !fingValidator.MatchString(fingerprint) {
return nil, fmt.Errorf("peer %x is advertising an invalid fingerprint: %s", peer.SessionID, fingerprint)
}
if pubKey64, found := adv["public_key"]; found {
pubKey, err := base64.StdEncoding.DecodeString(pubKey64.(string))
if err != nil {
return nil, fmt.Errorf("error decoding peer %s public key: %s", fingerprint, err)
}
peer.Keys, err = crypto.FromPublicPEM(string(pubKey))
if err != nil {
return nil, fmt.Errorf("error parsing peer %s public key: %s", fingerprint, err)
}
// basic consistency check
if peer.Keys.FingerprintHex != fingerprint {
return nil, fmt.Errorf("peer %x is advertising fingerprint %s, but it should be %s", peer.SessionID, fingerprint, peer.Keys.FingerprintHex)
}
} else if !found {
log.Debug("peer %s is not advertising any public key", fingerprint)
}
/*
No need for signature in the advertisement protocol, however:
signature64, found := adv["signature"].(string)
if !found {
return nil, fmt.Errorf("peer %s is advertising unsigned data", fingerprint)
}
signature, err := base64.StdEncoding.DecodeString(signature64)
if err != nil {
return nil, fmt.Errorf("error decoding peer %s signature: %s", fingerprint, err)
}
// the signature is SIGN(advertisement), so we need to remove the signature field and convert back to json.
// NOTE: fortunately, keys will be always sorted, so we don't have to do anything in order to guarantee signature
// consistency (https://stackoverflow.com/questions/18668652/how-to-produce-json-with-sorted-keys-in-go)
signedMap := adv
delete(signedMap, "signature")
signedData, err := json.Marshal(signedMap)
if err != nil {
return nil, fmt.Errorf("error packing data for signature verification: %v", err)
}
// verify the signature
if err = peer.Keys.VerifyMessage(signedData, signature); err != nil {
return nil, fmt.Errorf("peer %x signature is invalid", peer.SessionID)
}
*/
for key, value := range adv {
peer.AdvData.Store(key, value)
}
return peer, nil
}
func (peer *Peer) Update(radio *layers.RadioTap, dot11 *layers.Dot11, adv map[string]interface{}) (err error) {
peer.Lock()
defer peer.Unlock()
// parse the fingerprint, the signature and the public key
fingerprint, found := adv["identity"].(string)
if !found {
return fmt.Errorf("peer %x is not advertising any identity", peer.SessionID)
}
// basic consistency check
if peer.Keys != nil && peer.Keys.FingerprintHex != fingerprint {
return fmt.Errorf("peer %x is advertising fingerprint %s, but it should be %s", peer.SessionID, fingerprint, peer.Keys.FingerprintHex)
}
/*
No need for signature in the advertisement protocol, however:
signature64, found := adv["signature"].(string)
if !found {
return fmt.Errorf("peer %x is not advertising any signature", peer.SessionID)
}
signature, err := base64.StdEncoding.DecodeString(signature64)
if err != nil {
return fmt.Errorf("error decoding peer %d signature: %s", peer.SessionID, err)
}
// the signature is SIGN(advertisement), so we need to remove the signature field and convert back to json.
// NOTE: fortunately, keys will always be sorted, so we don't have to do anything in order to guarantee signature
// consistency (https://stackoverflow.com/questions/18668652/how-to-produce-json-with-sorted-keys-in-go)
signedMap := adv
delete(signedMap, "signature")
signedData, err := json.Marshal(signedMap)
if err != nil {
return fmt.Errorf("error packing data for signature verification: %v", err)
}
// verify the signature
if err = peer.Keys.VerifyMessage(signedData, signature); err != nil {
return fmt.Errorf("peer %x signature is invalid", peer.SessionID)
}
*/
peer.Channel = wifi.Freq2Chan(int(radio.ChannelFrequency))
peer.RSSI = int(radio.DBMAntennaSignal)
if !bytes.Equal(peer.SessionID, dot11.Address3) {
log.Info("peer %s changed session id: %x -> %x", peer.ID(), peer.SessionIDStr, dot11.Address3)
copy(peer.SessionID, dot11.Address3)
parts := make([]string, 6)
for idx, byte := range peer.SessionID {
parts[idx] = fmt.Sprintf("%02x", byte)
}
peer.SessionIDStr = strings.Join(parts, ":")
}
for key, value := range adv {
peer.AdvData.Store(key, value)
}
return nil
}
func (peer *Peer) ID() string {
name, _ := peer.AdvData.Load("name")
ident := "???"
if peer.Keys != nil {
ident = peer.Keys.FingerprintHex
} else if _ident, found := peer.AdvData.Load("identity"); found {
ident = _ident.(string)
}
return fmt.Sprintf("%s@%s", name, ident)
}
func (peer *Peer) InactiveFor() float64 {
peer.Lock()
defer peer.Unlock()
return time.Since(peer.DetectedAt).Seconds()
}
func (peer *Peer) SetData(adv map[string]interface{}) {
if peer == nil {
return
}
peer.Lock()
defer peer.Unlock()
for key, val := range adv {
if val == nil {
peer.AdvData.Delete(key)
} else {
peer.AdvData.Store(key, val)
}
}
}
func (peer *Peer) Data() map[string]interface{} {
peer.Lock()
defer peer.Unlock()
return peer.dataFrame()
}
func (peer *Peer) dataFrame() map[string]interface{} {
data := map[string]interface{}{}
peer.AdvData.Range(func(key, value interface{}) bool {
data[key.(string)] = value
return true
})
return data
}
func (peer *Peer) advertise() {
peer.Lock()
defer peer.Unlock()
if peer.advEnabled {
data := peer.dataFrame()
data["timestamp"] = time.Now().Unix()
adv, err := json.Marshal(data)
if err != nil {
log.Error("could not serialize advertisement data: %v", err)
return
}
/*
No need for signature in the advertisement protocol, however:
// sign the advertisement
signature, err := peer.Keys.SignMessage(adv)
if err != nil {
log.Error("error signing advertisement: %v", err)
return
}
// add the signature to the advertisement itself and encode again
data["signature"] = base64.StdEncoding.EncodeToString(signature)
adv, err = json.Marshal(data)
if err != nil {
log.Error("could not serialize signed advertisement data: %v", err)
return
}
log.Debug("advertising:\n%+v", data)
*/
err, raw := wifi.Pack(
net.HardwareAddr(peer.SessionID),
wifi.BroadcastAddr,
adv,
false) // set compression to true if using signature
if err != nil {
log.Error("could not encapsulate %d bytes of advertisement data: %v", len(adv), err)
return
}
if err = peer.mux.Write(raw); err != nil {
log.Error("error sending %d bytes of advertisement frame: %v", len(raw), err)
}
}
}
func (peer *Peer) StartAdvertising(iface string) (err error) {
if peer.mux == nil {
if peer.mux, err = NewPacketMuxer(iface, "", Workers); err != nil {
return
}
}
go func() {
period := time.Duration(peer.AdvPeriod) * time.Millisecond
ticker := time.NewTicker(period)
log.Debug("advertiser started with a %s period", period)
for {
select {
case _ = <-ticker.C:
peer.advertise()
case <-peer.stop:
log.Info("advertiser stopped")
return
}
}
}()
return nil
}
func (peer *Peer) StopAdvertising() {
log.Debug("stopping advertiser ...")
peer.stop <- struct{}{}
}