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 %s 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) } } } 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() } }