//go:build !noyggdrasil // +build !noyggdrasil /* NNCP -- Node to Node copy, utilities for store-and-forward data exchange Copyright (C) 2016-2022 Sergey Matveev 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 . */ 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 }