]> Cypherpunks.ru repositories - nncp.git/blobdiff - src/yggdrasil/yggdrasil.go
Various Yggdrasil related refactoring and tcpip network stack
[nncp.git] / src / yggdrasil / yggdrasil.go
diff --git a/src/yggdrasil/yggdrasil.go b/src/yggdrasil/yggdrasil.go
new file mode 100644 (file)
index 0000000..4901361
--- /dev/null
@@ -0,0 +1,323 @@
+//go:build !noyggdrasil
+// +build !noyggdrasil
+
+/*
+NNCP -- Node to Node copy, utilities for store-and-forward data exchange
+Copyright (C) 2016-2022 Sergey Matveev <stargrave@stargrave.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 3 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package yggdrasil
+
+import (
+       "encoding/hex"
+       "errors"
+       "log"
+       "net"
+       "net/url"
+       "strconv"
+       "strings"
+       "sync"
+
+       iwt "github.com/Arceliar/ironwood/types"
+       gologme "github.com/gologme/log"
+       yaddr "github.com/yggdrasil-network/yggdrasil-go/src/address"
+       ycfg "github.com/yggdrasil-network/yggdrasil-go/src/config"
+       ycore "github.com/yggdrasil-network/yggdrasil-go/src/core"
+       ymcast "github.com/yggdrasil-network/yggdrasil-go/src/multicast"
+       "golang.org/x/crypto/ed25519"
+)
+
+const DefaultPort = 5400
+
+// Copy-pasted from yggdrasil-go/src/ipv6rwc/ipv6rwc.go,
+// because they are non-exportable.
+const (
+       typeKeyDummy = iota
+       typeKeyLookup
+       typeKeyResponse
+)
+
+var (
+       glog *gologme.Logger
+
+       stacks  map[string]*TCPIPEndpoint
+       stacksM sync.Mutex
+)
+
+func init() {
+       glog = gologme.New(log.Writer(), "yggdrasil: ", gologme.Lmsgprefix)
+       glog.EnableLevel("warn")
+       glog.EnableLevel("error")
+       glog.EnableLevel("info")
+       stacks = make(map[string]*TCPIPEndpoint)
+}
+
+func ycoreStart(cfg *ycfg.NodeConfig, port int, mcasts []string) (*ycore.Core, error) {
+       var err error
+       for _, mcast := range mcasts {
+               cols := strings.SplitN(mcast, ":", 2)
+               mport := 0
+               if len(cols) == 2 {
+                       mcast = cols[0]
+                       mport, err = strconv.Atoi(cols[1])
+                       if err != nil {
+                               return nil, err
+                       }
+               }
+               cfg.MulticastInterfaces = append(
+                       cfg.MulticastInterfaces, ycfg.MulticastInterfaceConfig{
+                               Regex:  mcast,
+                               Beacon: true,
+                               Listen: true,
+                               Port:   uint16(mport),
+                       },
+               )
+       }
+       core := &ycore.Core{}
+       if err := core.Start(cfg, glog); err != nil {
+               return nil, err
+       }
+       if len(mcasts) > 0 {
+               mc := &ymcast.Multicast{}
+               if err := mc.Init(core, cfg, glog, nil); err != nil {
+                       core.Stop()
+                       return nil, err
+               }
+               if err := mc.Start(); err != nil {
+                       core.Stop()
+                       return nil, err
+               }
+       }
+       glog.Infoln("Public key:", hex.EncodeToString(core.GetSelf().Key))
+       glog.Infof("NNCP TCP: [%s]:%d", core.Address().String(), port)
+       glog.Infoln("MTU:", core.MTU())
+       return core, nil
+}
+
+func NewConn(aliases map[string]string, in string) (net.Conn, error) {
+       // yggdrasilc://PUB[:PORT]?prv=PRV[&peer=PEER][&mcast=REGEX[:PORT]]
+       u, err := url.Parse(in)
+       if err != nil {
+               return nil, err
+       }
+       if u.Scheme != "yggdrasilc" {
+               return nil, errors.New("expected yggdrasilc:// scheme")
+       }
+
+       pubHex := u.Hostname()
+       if v, ok := aliases[pubHex]; ok {
+               pubHex = v
+       }
+       pubRaw, err := hex.DecodeString(pubHex)
+       if err != nil {
+               return nil, err
+       }
+
+       port := DefaultPort
+       if p := u.Port(); p != "" {
+               port, err = strconv.Atoi(p)
+               if err != nil {
+                       return nil, err
+               }
+       }
+
+       values := u.Query()
+       if len(values["prv"]) == 0 {
+               return nil, errors.New("yggdrasilc:// misses prv field")
+       }
+       prvHex := values["prv"][0]
+       if v, ok := aliases[prvHex]; ok {
+               prvHex = v
+       }
+       prvRaw, err := hex.DecodeString(prvHex)
+       if err != nil {
+               return nil, err
+       }
+       if len(prvRaw) != ed25519.PrivateKeySize {
+               return nil, errors.New("invalid private key size")
+       }
+       var peers []string
+       for _, peer := range values["peer"] {
+               if v, ok := aliases[peer]; ok {
+                       peer = v
+               }
+               peers = append(peers, peer)
+       }
+       var mcasts []string
+       for _, mcast := range values["mcast"] {
+               if v, ok := aliases[mcast]; ok {
+                       mcast = v
+               }
+               mcasts = append(mcasts, mcast)
+       }
+
+       addrOur := yaddr.AddrForKey(ed25519.PublicKey(
+               prvRaw[len(prvRaw)-ed25519.PublicKeySize:],
+       ))
+       ipOur := net.IP(addrOur[:])
+       addrTheir := yaddr.AddrForKey(ed25519.PublicKey(pubRaw))
+       ipTheir := net.IP(addrTheir[:])
+       var ip yaddr.Address
+       copy(ip[:], addrTheir[:])
+
+       stacksM.Lock()
+       defer stacksM.Unlock()
+       e, ok := stacks[prvHex]
+       if ok {
+               e.ipToAddr[ip] = iwt.Addr(pubRaw)
+               return e.DialTCP(&net.TCPAddr{IP: ipTheir, Port: port})
+       }
+       cfg := ycfg.NodeConfig{
+               PrivateKey:      prvHex,
+               Peers:           peers,
+               NodeInfo:        map[string]interface{}{"name": "NNCP"},
+               NodeInfoPrivacy: true,
+       }
+       core, err := ycoreStart(&cfg, port, mcasts)
+       if err != nil {
+               return nil, err
+       }
+       e, err = NewTCPIPEndpoint(core, ipOur, uint32(core.MTU()))
+       if err != nil {
+               return nil, err
+       }
+       e.ipToAddr[ip] = iwt.Addr(pubRaw)
+       stacks[prvHex] = e
+       return e.DialTCP(&net.TCPAddr{IP: ipTheir, Port: port})
+}
+
+type OOBState struct {
+       c      *ycore.Core
+       subnet yaddr.Subnet
+}
+
+func (state *OOBState) Handler(fromKey, toKey ed25519.PublicKey, data []byte) {
+       if len(data) != 1+ed25519.SignatureSize {
+               return
+       }
+       if data[0] == typeKeyLookup {
+               snet := *yaddr.SubnetForKey(toKey)
+               sig := data[1:]
+               if snet == state.subnet && ed25519.Verify(fromKey, toKey[:], sig) {
+                       state.c.SendOutOfBand(fromKey, append(
+                               []byte{typeKeyResponse},
+                               ed25519.Sign(state.c.PrivateKey(), fromKey[:])...,
+                       ))
+               }
+       }
+}
+
+func NewListener(aliases map[string]string, in string) (net.Listener, error) {
+       // yggdrasils://PRV[:PORT]?[bind=BIND][&pub=PUB][&peer=PEER][&mcast=REGEX[:PORT]]
+       u, err := url.Parse(in)
+       if err != nil {
+               return nil, err
+       }
+       if u.Scheme != "yggdrasils" {
+               return nil, errors.New("expected yggdrasils:// scheme")
+       }
+
+       prvHex := u.Hostname()
+       if v, ok := aliases[prvHex]; ok {
+               prvHex = v
+       }
+       prvRaw, err := hex.DecodeString(prvHex)
+       if err != nil {
+               return nil, err
+       }
+       if len(prvRaw) != ed25519.PrivateKeySize {
+               return nil, errors.New("invalid private key size")
+       }
+
+       port := DefaultPort
+       if p := u.Port(); p != "" {
+               port, err = strconv.Atoi(p)
+               if err != nil {
+                       return nil, err
+               }
+       }
+
+       values := u.Query()
+       var binds []string
+       for _, bind := range values["bind"] {
+               if v, ok := aliases[bind]; ok {
+                       bind = v
+               }
+               binds = append(binds, bind)
+       }
+       var pubs []string
+       for _, pub := range values["pub"] {
+               if v, ok := aliases[pub]; ok {
+                       pub = v
+               }
+               pubs = append(pubs, pub)
+       }
+       var peers []string
+       for _, peer := range values["peer"] {
+               if v, ok := aliases[peer]; ok {
+                       peer = v
+               }
+               peers = append(peers, peer)
+       }
+       var mcasts []string
+       for _, mcast := range values["mcast"] {
+               if v, ok := aliases[mcast]; ok {
+                       mcast = v
+               }
+               mcasts = append(mcasts, mcast)
+       }
+
+       addrOur := yaddr.AddrForKey(ed25519.PublicKey(
+               prvRaw[len(prvRaw)-ed25519.PublicKeySize:],
+       ))
+       ipOur := net.IP(addrOur[:])
+
+       stacksM.Lock()
+       defer stacksM.Unlock()
+       e, ok := stacks[prvHex]
+       if ok {
+               return e.ListenTCP(&net.TCPAddr{IP: ipOur, Port: port})
+       }
+       cfg := ycfg.NodeConfig{
+               PrivateKey:        prvHex,
+               Listen:            binds,
+               AllowedPublicKeys: pubs,
+               Peers:             peers,
+               NodeInfo:          map[string]interface{}{"name": "NNCP"},
+               NodeInfoPrivacy:   true,
+       }
+       core, err := ycoreStart(&cfg, port, mcasts)
+       if err != nil {
+               return nil, err
+       }
+       oobState := OOBState{core, *yaddr.SubnetForKey(core.PublicKey())}
+       if err := core.SetOutOfBandHandler(oobState.Handler); err != nil {
+               core.Stop()
+               return nil, err
+       }
+       e, err = NewTCPIPEndpoint(core, ipOur, uint32(core.MTU()))
+       if err != nil {
+               core.Stop()
+               return nil, err
+       }
+       ln, err := e.ListenTCP(&net.TCPAddr{IP: ipOur, Port: port})
+       if err != nil {
+               e.Close()
+               core.Stop()
+               return nil, err
+       }
+       stacks[prvHex] = e
+       return ln, nil
+}