lux/main.go
2025-01-24 20:14:49 +02:00

357 lines
8.8 KiB
Go

package main
import (
"encoding/xml"
"flag"
"fmt"
"lux/crypto"
"lux/node"
"lux/proto"
"lux/rpc"
"os"
"os/signal"
"strings"
"syscall"
)
var isNode bool
var isHost bool
var isRpc bool
var configPath string
var bootstrap bool
var justNodeId bool
var daemonize bool
var pidPath string
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"`
RPCEndpoints []string `xml:"rpc"`
}
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())
}
}
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 {
fmt.Fprintf(os.Stderr, "failed to parse xml: %v\n", err)
os.Exit(1)
}
// check presense of keystore and id in config
if config.KeyStore == "" {
fmt.Fprintln(os.Stderr, "no keystore path specified!")
os.Exit(1)
}
if config.ID == "" {
fmt.Fprintln(os.Stderr, "no ID in config! You need to --bootstrap")
os.Exit(1)
}
nodeId, err := proto.ParseLuxID(config.ID)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to parse node id: %v\n", err)
os.Exit(1)
}
// load keystore
ks := crypto.NewLuxKeyStore(config.KeyStore)
if err := ks.Load(); err != nil {
fmt.Fprintf(os.Stderr, "failed to laod keystore: %v\n", err)
os.Exit(1)
}
nodeKey, ok := ks.Get(nodeId)
if !ok {
fmt.Fprintln(os.Stderr, "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 {
fmt.Fprintf(os.Stderr, "failed to add interior %s: %v\n", interior, err)
os.Exit(1)
}
}
for _, exterior := range config.Exterior {
if err := node.AddExterior(exterior); err != nil {
fmt.Fprintf(os.Stderr, "failed to add exterior %s: %v\n", exterior, err)
os.Exit(1)
}
}
// add neighbors
for _, neighbor := range config.Neighbors {
neighId, err := proto.ParseLuxID(neighbor.ID)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to parse neigh id %s: %v\n", neighbor.ID, err)
os.Exit(1)
}
if err := node.AddNeighbor(neighId, neighbor.Address); err != nil {
fmt.Fprintf(os.Stderr, "failed to add neighbor %s: %v\n", 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 {
fmt.Fprintf(os.Stderr, "failed to add root rpc %s: %v\n", path, err)
os.Exit(1)
}
} else if strings.HasPrefix(rpcPath, "tcp://") {
path := rpcPath[6:]
if err := sv.AddEndpoint("tcp", path, rpc.LuxRpcTypeQuery); err != nil {
fmt.Fprintf(os.Stderr, "failed to add query rpc %s: %v\n", path, err)
os.Exit(1)
}
} else {
fmt.Fprintf(os.Stderr, "unknown rpc type %s. It must be either unix:// or tcp://\n", rpcPath)
os.Exit(1)
}
}
// TODO: daemonize
// 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
fmt.Println(sig)
switch sig {
case syscall.SIGINT:
break signaling
case syscall.SIGTERM:
break signaling
}
}
// stop daemon
node.Stop()
}
func hostMain() {
}
var rpcPath string
var rpcNewHost string
var rpcNewNode string
var rpcQueryHost string
var rpcQueryHostname string
var rpcGetRoutes 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)
}
// pretty print routes
for _, route := range rpcRes.Routes {
fmt.Println(route)
}
}
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)
}
// deserialize keystore
_, err = crypto.LuxKeyStoreFromRpc(rpcRes.Keystore, rpcNewHost)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to save host keystore: %v\n", err)
}
}
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)
}
// deserialize keystore
_, err = crypto.LuxKeyStoreFromRpc(rpcRes.Keystore, rpcNewHost)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to save node keystore: %v\n", err)
}
}
}
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.BoolVar(&daemonize, "daemonize", false, "run LUX as daemon in background")
flag.StringVar(&pidPath, "pid", "", "after daemonization LUX will write its PID here")
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.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()
}
}