948 lines
21 KiB
Go
948 lines
21 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/xml"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"lux/crypto"
|
|
"lux/host"
|
|
"lux/node"
|
|
"lux/proto"
|
|
"lux/rpc"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/op/go-logging"
|
|
)
|
|
|
|
var isNode bool
|
|
var isHost bool
|
|
var isRpc bool
|
|
var configPath string
|
|
var bootstrap bool
|
|
var justNodeId bool
|
|
|
|
type LogConfig struct {
|
|
XMLName xml.Name `xml:"log"`
|
|
Level string `xml:"level,attr"`
|
|
LogPath string `xml:",innerxml"`
|
|
}
|
|
|
|
type UpdateHookConfig struct {
|
|
XMLName xml.Name `xml:"hook"`
|
|
HostID string `xml:"id"`
|
|
Script string `xml:"script"`
|
|
}
|
|
|
|
type NodeConfig struct {
|
|
XMLName xml.Name `xml:"node"`
|
|
KeyStore string `xml:"keystore"`
|
|
ID string `xml:"id"`
|
|
Interior []string `xml:"interior"`
|
|
Exterior []string `xml:"exterior"`
|
|
Neighbors []struct {
|
|
XMLName xml.Name `xml:"neighbor"`
|
|
ID string `xml:"id"`
|
|
Address string `xml:"address"`
|
|
} `xml:"neighbor"`
|
|
|
|
Sync int `xml:"sync"`
|
|
|
|
RPCEndpoints []string `xml:"rpc"`
|
|
|
|
DNS []string `xml:"dns"`
|
|
|
|
Log LogConfig `xml:"log"`
|
|
|
|
Hooks []UpdateHookConfig `xml:"hook"`
|
|
}
|
|
|
|
func setupLogging(log LogConfig) {
|
|
var level logging.Level
|
|
switch log.Level {
|
|
case "critical":
|
|
level = logging.CRITICAL
|
|
case "error":
|
|
level = logging.ERROR
|
|
case "warning":
|
|
level = logging.WARNING
|
|
case "notice":
|
|
level = logging.NOTICE
|
|
case "info":
|
|
level = logging.INFO
|
|
case "debug":
|
|
level = logging.DEBUG
|
|
default:
|
|
level = logging.INFO
|
|
}
|
|
|
|
// if log tag has file path, then it open file in append mode,
|
|
// otherwise it will use default stdout logger
|
|
if log.LogPath != "" {
|
|
logFile, err := os.OpenFile(log.LogPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.FileMode(0600))
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to open log file: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
logging.SetBackend(logging.NewLogBackend(logFile, "", 0))
|
|
}
|
|
|
|
logging.SetLevel(level, "")
|
|
}
|
|
|
|
func bootstrapNode() {
|
|
xmlBytes, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
var config NodeConfig
|
|
if err := xml.Unmarshal(xmlBytes, &config); err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to parse xml: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// create keystore, generate node key
|
|
ks := crypto.NewLuxKeyStore(config.KeyStore)
|
|
nodeKey, err := crypto.NewLuxKey(proto.LuxTypeNode)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
if err := ks.Put(nodeKey); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if justNodeId {
|
|
fmt.Println(nodeKey.Id.String())
|
|
} else {
|
|
fmt.Printf("Your node key ID is: %s\nAdd <id>%s</id> to your node config!\n",
|
|
nodeKey.Id.String(), nodeKey.Id.String())
|
|
}
|
|
}
|
|
|
|
var log = logging.MustGetLogger("main")
|
|
|
|
type hookUpdateSubscriber struct {
|
|
Hooks map[proto.LuxID]UpdateHookConfig
|
|
}
|
|
|
|
func (subscriber *hookUpdateSubscriber) HandleStateUpdate(state node.LuxHostState) {
|
|
hook, ok := subscriber.Hooks[state.HostId]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// spawn executable and pipe xml host state into it's stdin
|
|
xmlBytes, err := xml.Marshal(&rpc.LuxRpcHost{
|
|
HostID: state.HostId.String(),
|
|
Hostname: state.State.Hostname,
|
|
State: state.State.IntoRpc(),
|
|
})
|
|
if err != nil {
|
|
log.Errorf("failed to marshal host update for hook: %v\n", err)
|
|
return
|
|
}
|
|
|
|
buffer := bytes.Buffer{}
|
|
buffer.Write(xmlBytes)
|
|
|
|
cmd := exec.Command(hook.Script)
|
|
cmd.Stdin = &buffer
|
|
cmd.Stdout = io.Discard
|
|
cmd.Stderr = io.Discard
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
log.Warningf("failed to execute script %s: %v\n", hook.Script, err)
|
|
}
|
|
}
|
|
|
|
func nodeMain() {
|
|
xmlBytes, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
var config NodeConfig
|
|
if err := xml.Unmarshal(xmlBytes, &config); err != nil {
|
|
log.Criticalf("failed to parse xml: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// setup logging
|
|
setupLogging(config.Log)
|
|
|
|
// check presense of keystore and id in config
|
|
if config.KeyStore == "" {
|
|
log.Critical("no keystore path specified!")
|
|
os.Exit(1)
|
|
}
|
|
if config.ID == "" {
|
|
log.Critical("no ID in config! You need to --bootstrap")
|
|
os.Exit(1)
|
|
}
|
|
|
|
nodeId, err := proto.ParseLuxID(config.ID)
|
|
if err != nil {
|
|
log.Criticalf("failed to parse node id: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// load keystore
|
|
ks := crypto.NewLuxKeyStore(config.KeyStore)
|
|
if err := ks.Load(); err != nil {
|
|
log.Criticalf("failed to laod keystore: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
nodeKey, ok := ks.Get(nodeId)
|
|
if !ok {
|
|
log.Critical("node key is not present in key store!")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// create node
|
|
node := node.NewLuxNode(nodeKey, ks)
|
|
|
|
// add interior exterior channels
|
|
for _, interior := range config.Interior {
|
|
if err := node.AddInterior(interior); err != nil {
|
|
log.Criticalf("failed to add interior %s: %v", interior, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
for _, exterior := range config.Exterior {
|
|
if err := node.AddExterior(exterior); err != nil {
|
|
log.Criticalf("failed to add exterior %s: %v", exterior, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// add neighbors
|
|
for _, neighbor := range config.Neighbors {
|
|
neighId, err := proto.ParseLuxID(neighbor.ID)
|
|
if err != nil {
|
|
log.Criticalf("failed to parse neigh id %s: %v", neighbor.ID, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err := node.AddNeighbor(neighId, neighbor.Address); err != nil {
|
|
log.Criticalf("failed to add neighbor %s: %v", neighbor.ID, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// create rpc server
|
|
sv := rpc.NewLuxRpcServer()
|
|
sv.RegisterController(&node)
|
|
|
|
// parse and and spawn rpc endpoints
|
|
for _, rpcPath := range config.RPCEndpoints {
|
|
if strings.HasPrefix(rpcPath, "unix://") {
|
|
path := rpcPath[7:]
|
|
|
|
if err := sv.AddEndpoint("unix", path, rpc.LuxRpcTypeRoot); err != nil {
|
|
log.Criticalf("failed to add root rpc %s: %v", path, err)
|
|
os.Exit(1)
|
|
}
|
|
} else if strings.HasPrefix(rpcPath, "tcp://") {
|
|
path := rpcPath[6:]
|
|
|
|
if err := sv.AddEndpoint("tcp", path, rpc.LuxRpcTypeQuery); err != nil {
|
|
log.Criticalf("failed to add query rpc %s: %v", path, err)
|
|
os.Exit(1)
|
|
}
|
|
} else {
|
|
log.Criticalf("unknown rpc type %s. It must be either unix:// or tcp://\n", rpcPath)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// add dns server frontends
|
|
for _, dnsListen := range config.DNS {
|
|
if err := node.AddDnsFrontend(dnsListen); err != nil {
|
|
log.Criticalf("failed to spawn dns %s: %v\n", dnsListen, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// add update hooks
|
|
hook := hookUpdateSubscriber{
|
|
Hooks: make(map[proto.LuxID]UpdateHookConfig),
|
|
}
|
|
for _, item := range config.Hooks {
|
|
id, err := proto.ParseLuxID(item.HostID)
|
|
if err != nil {
|
|
log.Criticalf("failed to parse hook host id %s: %v\n", item.HostID, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
hook.Hooks[id] = item
|
|
}
|
|
node.AddSubscriber(&hook)
|
|
|
|
// start node
|
|
node.Start()
|
|
|
|
// node state sync scheduled task
|
|
stopChan := make(chan struct{})
|
|
|
|
if config.Sync != 0 {
|
|
ticker := time.NewTicker(time.Duration(config.Sync) * time.Minute)
|
|
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
err := node.MulticastSync()
|
|
if err != nil {
|
|
log.Errorf("MulticastSync err: %v", err)
|
|
}
|
|
case <-stopChan:
|
|
ticker.Stop()
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
} else {
|
|
log.Info("sync interval is not set. Node will not sync with neighbors")
|
|
}
|
|
|
|
// register go channel to receive unix signals,
|
|
// while hogging main thread to read them and take action.
|
|
// its important to keep main thread alive for process to run
|
|
sigs := make(chan os.Signal, 1)
|
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
signaling:
|
|
for {
|
|
sig := <-sigs
|
|
log.Debug(sig)
|
|
switch sig {
|
|
case syscall.SIGINT, syscall.SIGTERM:
|
|
break signaling
|
|
}
|
|
}
|
|
|
|
// stop daemon
|
|
close(stopChan)
|
|
node.Stop()
|
|
}
|
|
|
|
type HostCommand struct {
|
|
XMLName xml.Name `xml:"command"`
|
|
Executable string `xml:",innerxml"`
|
|
}
|
|
|
|
type HostConfig struct {
|
|
XMLName xml.Name `xml:"host"`
|
|
KeyStore string `xml:"keystore"`
|
|
ID string `xml:"id"`
|
|
Hostname string `xml:"hostname"`
|
|
|
|
Log LogConfig `xml:"log"`
|
|
|
|
Heartbeat int `xml:"heartbeat"`
|
|
|
|
Options []struct {
|
|
XMLName xml.Name `xml:"option"`
|
|
Type string `xml:"type,attr"`
|
|
|
|
WAN struct {
|
|
XMLName xml.Name `xml:"wan"`
|
|
|
|
Method string `xml:"method,attr"`
|
|
|
|
Addr4 string `xml:"addr4"`
|
|
Addr6 string `xml:"addr6"`
|
|
|
|
Command HostCommand `xml:"command"`
|
|
} `xml:"wan"`
|
|
} `xml:"option"`
|
|
|
|
Nodes []struct {
|
|
XMLName xml.Name `xml:"node"`
|
|
ID string `xml:"id"`
|
|
Exterior string `xml:"exterior"`
|
|
} `xml:"node"`
|
|
}
|
|
|
|
type HostStaticWAN struct {
|
|
addr4 netip.Addr
|
|
addr6 netip.Addr
|
|
}
|
|
|
|
func NewHostStaticWAN(ip4 string, ip6 string) (HostStaticWAN, error) {
|
|
addr4, err := netip.ParseAddr(ip4)
|
|
if err != nil {
|
|
return HostStaticWAN{}, err
|
|
}
|
|
|
|
addr6, err := netip.ParseAddr(ip6)
|
|
if err != nil {
|
|
return HostStaticWAN{}, err
|
|
}
|
|
|
|
return HostStaticWAN{addr4, addr6}, nil
|
|
}
|
|
|
|
func (wan *HostStaticWAN) Provide() (host.LuxOption, error) {
|
|
return &host.LuxOptionWAN{Addr4: wan.addr4, Addr6: wan.addr6}, nil
|
|
}
|
|
|
|
type HostIdentWAN struct{}
|
|
|
|
func dialIdentMe(identIp netip.Addr) (netip.Addr, error) {
|
|
var network string
|
|
if identIp.Is6() {
|
|
network = "tcp6"
|
|
} else {
|
|
network = "tcp4"
|
|
}
|
|
|
|
cl, err := net.DialTCP(network, nil,
|
|
net.TCPAddrFromAddrPort(netip.AddrPortFrom(identIp, 80)))
|
|
if err != nil {
|
|
return netip.Addr{}, err
|
|
}
|
|
defer cl.Close()
|
|
|
|
// I dont want to bundle http package for embedded reasons,
|
|
// so I will just bang http headers directly
|
|
const req = "GET / HTTP/1.1\r\nHost: ident.me\r\nUser-Agent: LUX-v1\r\nAccept: */*\r\n\r\n"
|
|
_, err = cl.Write([]byte(req))
|
|
if err != nil {
|
|
return netip.Addr{}, err
|
|
}
|
|
|
|
res := make([]byte, 1024)
|
|
n, err := cl.Read(res)
|
|
if err != nil {
|
|
return netip.Addr{}, err
|
|
}
|
|
|
|
// parse http headers to get response
|
|
sep := []byte{0x0D, 0x0A, 0x0D, 0x0A}
|
|
|
|
ipStr := ""
|
|
for i := 0; i < n-4; i++ {
|
|
if bytes.Equal(res[i:i+4], sep) {
|
|
ipStr = string(res[i+4 : n])
|
|
}
|
|
}
|
|
|
|
// parse ip
|
|
if ipStr == "" {
|
|
return netip.Addr{}, fmt.Errorf("IP not found in ident.me response: %s", string(res))
|
|
}
|
|
|
|
// to prevent panics
|
|
parsedIp := net.ParseIP(ipStr)
|
|
if parsedIp == nil {
|
|
return netip.Addr{}, fmt.Errorf("failed to parse ident.me IP")
|
|
}
|
|
|
|
return proto.LuxProtoIPToAddr(parsedIp), nil
|
|
}
|
|
|
|
func (*HostIdentWAN) Provide() (host.LuxOption, error) {
|
|
wan := host.NewLuxOptionWAN()
|
|
// so first we gonna resolve ident.me and see if there is IPv6
|
|
addrs, err := net.LookupHost("ident.me")
|
|
if err != nil {
|
|
return &wan, err
|
|
}
|
|
|
|
for _, addr := range addrs {
|
|
ip := net.ParseIP(addr)
|
|
if ip.To4() == nil && wan.Addr6.IsUnspecified() {
|
|
// we gonna resolve IPv6
|
|
addr, err := dialIdentMe(proto.LuxProtoIPToAddr(ip))
|
|
if err == nil {
|
|
// we got IPv6
|
|
wan.Addr6 = addr
|
|
}
|
|
} else if wan.Addr4.IsUnspecified() {
|
|
addr, err := dialIdentMe(proto.LuxProtoIPToAddr(ip))
|
|
if err != nil {
|
|
// if no IPv4 its considered catastrophic error
|
|
return &wan, err
|
|
} else {
|
|
wan.Addr4 = addr
|
|
}
|
|
}
|
|
}
|
|
|
|
return &wan, nil
|
|
}
|
|
|
|
type HostNetif struct{}
|
|
|
|
func (*HostNetif) Provide() (host.LuxOption, error) {
|
|
netif := host.NewLuxOptionNetIf()
|
|
if err := netif.EnumerateNetlink(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &netif, nil
|
|
}
|
|
|
|
func hostMain() {
|
|
xmlBytes, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to open config: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
var config HostConfig
|
|
if err := xml.Unmarshal(xmlBytes, &config); err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to parse host config: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
ks := crypto.NewLuxKeyStore(config.KeyStore)
|
|
if err := ks.Load(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to load keystore: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
hostId, err := proto.ParseLuxID(config.ID)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to parse host ID: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
hostKey, ok := ks.Get(hostId)
|
|
if !ok {
|
|
fmt.Fprintln(os.Stderr, "host key is not present in keystore!")
|
|
os.Exit(1)
|
|
}
|
|
|
|
if config.Hostname == "" {
|
|
fmt.Fprintln(os.Stderr, "no hostname specified!")
|
|
os.Exit(1)
|
|
}
|
|
|
|
if config.Heartbeat == 0 {
|
|
fmt.Fprintln(os.Stderr, "no minute interval provided in <heartbeat>!")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// setup logging
|
|
setupLogging(config.Log)
|
|
|
|
// create host
|
|
host := host.NewLuxHost(config.Hostname, hostKey, ks)
|
|
|
|
// populate option providers
|
|
for _, option := range config.Options {
|
|
if option.Type == "wan" {
|
|
wan := &option.WAN
|
|
|
|
if wan.Method == "static" {
|
|
provider, err := NewHostStaticWAN(wan.Addr4, wan.Addr6)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to create static wan provider: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
host.AddOptionProvider(&provider)
|
|
} else if wan.Method == "identme" {
|
|
host.AddOptionProvider(&HostIdentWAN{})
|
|
}
|
|
} else if option.Type == "netif" {
|
|
host.AddOptionProvider(&HostNetif{})
|
|
}
|
|
}
|
|
|
|
// add nodes
|
|
for _, node := range config.Nodes {
|
|
nodeId, err := proto.ParseLuxID(node.ID)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to parse node id: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err := host.AddNode(nodeId, node.Exterior); err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to add node: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// start host
|
|
host.Start()
|
|
|
|
// start heartbeat timer
|
|
stopChan := make(chan struct{})
|
|
ticker := time.NewTicker(time.Duration(config.Heartbeat) * time.Minute)
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
if err := host.Heartbeat(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to heartbeat: %v\n", err)
|
|
}
|
|
case <-stopChan:
|
|
ticker.Stop()
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
// handle signals in main thread
|
|
sigs := make(chan os.Signal, 1)
|
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
signaling:
|
|
for {
|
|
sig := <-sigs
|
|
switch sig {
|
|
case syscall.SIGINT:
|
|
break signaling
|
|
case syscall.SIGTERM:
|
|
break signaling
|
|
}
|
|
}
|
|
|
|
// stop host
|
|
close(stopChan)
|
|
host.Stop()
|
|
}
|
|
|
|
func printRpcHost(host rpc.LuxRpcHost) {
|
|
fmt.Printf("%s\n", host.HostID)
|
|
fmt.Printf("|hostname: %s\n", host.Hostname)
|
|
fmt.Printf("|wan:\n|\taddr4\t%s\n|\taddr6\t%s\n", host.State.WAN.Addr4, host.State.WAN.Addr6)
|
|
for _, netif := range host.State.NetIf.Interfaces {
|
|
fmt.Printf("|netif %d: %s\n", netif.Index, netif.Name)
|
|
for _, addr := range netif.Addrs {
|
|
fmt.Printf("|\t%s\t%s\t\n", addr.Type, addr.Addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
var rpcPath string
|
|
var rpcNewHost string
|
|
var rpcNewNode string
|
|
var rpcQueryHost string
|
|
var rpcQueryHostname string
|
|
var rpcGetRoutes bool
|
|
var rpcGetKeys bool
|
|
var rpcGetHosts bool
|
|
var rpcXml bool
|
|
|
|
func rpcMain() {
|
|
var cl rpc.LuxRpcClient
|
|
var err error
|
|
|
|
if strings.HasPrefix(rpcPath, "unix://") {
|
|
cl, err = rpc.LuxDialRpc("unix", rpcPath[7:])
|
|
} else if strings.HasPrefix(rpcPath, "tcp://") {
|
|
cl, err = rpc.LuxDialRpc("tcp", rpcPath[6:])
|
|
} else {
|
|
fmt.Fprintln(os.Stderr, "unknown RPC network (must be unix:// or tcp://)")
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to dial RPC: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
defer cl.Close()
|
|
|
|
// now we send requests
|
|
counter := 0
|
|
|
|
if rpcGetRoutes {
|
|
rpcRes, rpcErr, err := cl.Execute(rpc.LuxRpcRequest{
|
|
RequestID: counter,
|
|
Controller: "router",
|
|
Command: "get",
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to send request: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
counter++
|
|
|
|
if rpcErr.ErrorCode != 0 {
|
|
// we got error
|
|
fmt.Fprintf(os.Stderr, "RPC error %d: %s\n", rpcErr.ErrorCode, rpcErr.Message)
|
|
os.Exit(1)
|
|
}
|
|
if rpcXml {
|
|
xmlBytes, err := xml.Marshal(&rpcRes)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to marshal rpc output: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println(string(xmlBytes))
|
|
return
|
|
}
|
|
|
|
// pretty print routes
|
|
for _, route := range rpcRes.Routes {
|
|
fmt.Println(route.String())
|
|
}
|
|
}
|
|
|
|
if rpcGetKeys {
|
|
rpcRes, rpcErr, err := cl.Execute(rpc.LuxRpcRequest{
|
|
RequestID: counter,
|
|
Controller: "ks",
|
|
Command: "get",
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to send request: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
counter++
|
|
|
|
if rpcErr.ErrorCode != 0 {
|
|
// we got error
|
|
fmt.Fprintf(os.Stderr, "RPC error %d: %s\n", rpcErr.ErrorCode, rpcErr.Message)
|
|
os.Exit(1)
|
|
}
|
|
if rpcXml {
|
|
xmlBytes, err := xml.Marshal(&rpcRes)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to marshal rpc output: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println(string(xmlBytes))
|
|
return
|
|
}
|
|
|
|
// pretty print keys
|
|
for _, node := range rpcRes.Keystore.Nodes {
|
|
fmt.Printf("node %s\n", node.ID)
|
|
}
|
|
for _, host := range rpcRes.Keystore.Hosts {
|
|
fmt.Printf("host %s\n", host.ID)
|
|
}
|
|
}
|
|
|
|
if rpcNewHost != "" {
|
|
rpcRes, rpcErr, err := cl.Execute(rpc.LuxRpcRequest{
|
|
RequestID: counter,
|
|
Controller: "node",
|
|
Command: "new-host",
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to send request: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
counter++
|
|
|
|
if rpcErr.ErrorCode != 0 {
|
|
// we got error
|
|
fmt.Fprintf(os.Stderr, "RPC error %d: %s\n", rpcErr.ErrorCode, rpcErr.Message)
|
|
os.Exit(1)
|
|
}
|
|
if rpcXml {
|
|
xmlBytes, err := xml.Marshal(&rpcRes)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to marshal rpc output: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println(string(xmlBytes))
|
|
return
|
|
}
|
|
|
|
// deserialize keystore
|
|
_, err = crypto.LuxKeyStoreFromRpc(rpcRes.Keystore, rpcNewHost)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to save host keystore: %v\n", err)
|
|
}
|
|
|
|
fmt.Printf("New host ID: %s\n", rpcRes.NewHostID)
|
|
}
|
|
|
|
if rpcNewNode != "" {
|
|
rpcRes, rpcErr, err := cl.Execute(rpc.LuxRpcRequest{
|
|
RequestID: counter,
|
|
Controller: "node",
|
|
Command: "new-node",
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to send request: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
counter++
|
|
|
|
if rpcErr.ErrorCode != 0 {
|
|
// we got error
|
|
fmt.Fprintf(os.Stderr, "RPC error %d: %s\n", rpcErr.ErrorCode, rpcErr.Message)
|
|
os.Exit(1)
|
|
}
|
|
if rpcXml {
|
|
xmlBytes, err := xml.Marshal(&rpcRes)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to marshal rpc output: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println(string(xmlBytes))
|
|
return
|
|
}
|
|
|
|
// deserialize keystore
|
|
_, err = crypto.LuxKeyStoreFromRpc(rpcRes.Keystore, rpcNewNode)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to save node keystore: %v\n", err)
|
|
}
|
|
|
|
fmt.Printf("New neighbor node ID: %s\n", rpcRes.NewNodeID)
|
|
}
|
|
|
|
if rpcQueryHost != "" || rpcQueryHostname != "" {
|
|
var rpcReq rpc.LuxRpcRequest
|
|
if rpcQueryHost != "" {
|
|
rpcReq = rpc.LuxRpcRequest{
|
|
RequestID: counter,
|
|
Controller: "node",
|
|
Command: "query",
|
|
Hosts: []rpc.LuxRpcHost{
|
|
{HostID: rpcQueryHost},
|
|
},
|
|
}
|
|
} else {
|
|
rpcReq = rpc.LuxRpcRequest{
|
|
RequestID: counter,
|
|
Controller: "node",
|
|
Command: "query",
|
|
Hosts: []rpc.LuxRpcHost{
|
|
{Hostname: rpcQueryHostname},
|
|
},
|
|
}
|
|
}
|
|
|
|
rpcRes, rpcErr, err := cl.Execute(rpcReq)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to send request: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
counter++
|
|
|
|
if rpcErr.ErrorCode != 0 {
|
|
// we got error
|
|
fmt.Fprintf(os.Stderr, "RPC error %d: %s\n", rpcErr.ErrorCode, rpcErr.Message)
|
|
os.Exit(1)
|
|
}
|
|
if rpcXml {
|
|
xmlBytes, err := xml.Marshal(&rpcRes)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to marshal rpc output: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println(string(xmlBytes))
|
|
return
|
|
}
|
|
|
|
// print state
|
|
printRpcHost(rpcRes.Hosts[0])
|
|
}
|
|
|
|
if rpcGetHosts {
|
|
rpcRes, rpcErr, err := cl.Execute(rpc.LuxRpcRequest{
|
|
RequestID: counter,
|
|
Controller: "node",
|
|
Command: "get-hosts",
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to send request: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
counter++
|
|
|
|
if rpcErr.ErrorCode != 0 {
|
|
// we got error
|
|
fmt.Fprintf(os.Stderr, "RPC error %d: %s\n", rpcErr.ErrorCode, rpcErr.Message)
|
|
os.Exit(1)
|
|
}
|
|
if rpcXml {
|
|
xmlBytes, err := xml.Marshal(&rpcRes)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to marshal rpc output: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println(string(xmlBytes))
|
|
return
|
|
}
|
|
|
|
for _, host := range rpcRes.Hosts {
|
|
printRpcHost(host)
|
|
}
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
// first, we need to determine who we are: node, host or rpc.
|
|
// determine by executable name (lux binary will be symlinked to lux-node, lux-host, luc-rpc),
|
|
// or by explicit cli flag (--node, --host, --rpc)
|
|
flag.BoolVar(&isNode, "node", false, "LUX node")
|
|
flag.BoolVar(&isHost, "host", false, "LUX host")
|
|
flag.StringVar(&configPath, "config", "", "node or host config")
|
|
flag.BoolVar(&bootstrap, "bootstrap", false, "bootstrap node keystore. config must be specified")
|
|
flag.BoolVar(&justNodeId, "just-node-id", false, "when bootstrapping only output node id to stdout")
|
|
|
|
flag.StringVar(&rpcPath, "rpc", "", "Run as RPC client, specify path to RPC UNIX socket or TCP socket, must be in unix:// or tcp:// form")
|
|
flag.StringVar(&rpcNewHost, "rpc-new-host", "", "RPC node create new host, specifies path for new keystore")
|
|
flag.StringVar(&rpcNewNode, "rpc-new-node", "", "RPC node create new node, specifies path for new keystore")
|
|
flag.StringVar(&rpcQueryHost, "rpc-query-host", "", "RPC node query host state by ID")
|
|
flag.StringVar(&rpcQueryHostname, "rpc-query-hostname", "", "RPC node querty host state by hostname")
|
|
flag.BoolVar(&rpcGetRoutes, "rpc-get-routes", false, "RPC node list established routes")
|
|
flag.BoolVar(&rpcGetKeys, "rpc-get-keys", false, "RPC node list keys")
|
|
flag.BoolVar(&rpcGetHosts, "rpc-get-hosts", false, "RPC node list hosts")
|
|
flag.BoolVar(&rpcXml, "rpc-xml", false, "output RPC results in XML")
|
|
flag.Parse()
|
|
|
|
if rpcPath != "" {
|
|
isRpc = true
|
|
} else if !isNode && !isHost {
|
|
// determine by argv[0]
|
|
if strings.Contains(os.Args[0], "node") {
|
|
isNode = true
|
|
} else if strings.Contains(os.Args[0], "host") {
|
|
isHost = true
|
|
}
|
|
}
|
|
|
|
if (isNode || isHost) && configPath == "" {
|
|
fmt.Fprintln(os.Stderr, "must provide config path")
|
|
os.Exit(1)
|
|
} else if isRpc && rpcPath == "" {
|
|
fmt.Fprintln(os.Stderr, "must provide RPC socket path")
|
|
os.Exit(1)
|
|
}
|
|
|
|
if isNode && bootstrap {
|
|
bootstrapNode()
|
|
return
|
|
}
|
|
|
|
if isNode {
|
|
nodeMain()
|
|
} else if isHost {
|
|
hostMain()
|
|
} else if isRpc {
|
|
rpcMain()
|
|
}
|
|
}
|