+//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
+}