From 7b993d7b7c466348df42a6dd92a5c3fd5b57766e Mon Sep 17 00:00:00 2001 From: Kioubit Date: Mon, 24 Apr 2023 17:34:37 +0300 Subject: [PATCH] Improve configuration --- README.md | 2 +- config.go | 90 +++++++++++++++++--------- modules/modules.go | 1 + modules/userInterface/userInterface.go | 83 +++++++++++------------- pndpd.conf | 8 +-- 5 files changed, 102 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 2a22195..eadf336 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PNDPD - NDP Responder / Proxy (IPv6) +# PNDPD - NDP Proxy / Responder (IPv6) ## Features - **Efficiently** process incoming packets using bpf (which runs in the kernel) - **Proxy** NDP between interfaces with an optional whitelist diff --git a/config.go b/config.go index a28b693..2fb39ff 100644 --- a/config.go +++ b/config.go @@ -12,54 +12,75 @@ import ( func readConfig(dest string) { file, err := os.Open(dest) if err != nil { - fmt.Println("Error:", err.Error()) - os.Exit(1) + configFatalError(err, "") } - defer func(file *os.File) { - _ = file.Close() - }(file) + var ( + currentOption string + blockMap map[string][]string + ) scanner := bufio.NewScanner(file) - for scanner.Scan() { line := scanner.Text() - if strings.HasPrefix(line, "//") || strings.TrimSpace(line) == "" { + line, _, _ = strings.Cut(line, "//") + line = strings.TrimSpace(line) + if line == "" { continue } - if strings.HasPrefix(line, "debug") { - if strings.Contains(line, "on") { + + if after, found := strings.CutPrefix(line, "debug"); found { + if strings.TrimSpace(after) == "on" { pndp.GlobalDebug = true fmt.Println("DEBUG ON") } continue } - if strings.HasSuffix(line, "{") { - option := strings.TrimSuffix(strings.TrimSpace(line), "{") - option = strings.TrimSpace(option) - module, command := modules.GetCommand(option, modules.Config) - var lines = make([]string, 0) - if module != nil { - for { - if !scanner.Scan() { - break - } - line := strings.TrimSpace(scanner.Text()) - if strings.Contains(line, "}") { - break - } + if option, after, found := strings.Cut(line, "{"); found { + if after != "" { + configFatalError(nil, "Nothing may follow after '{'. A new line must be used") + } + if blockMap != nil { + configFatalError(nil, "A new '{' block was started before the previous one was closed") + } + currentOption = strings.TrimSpace(option) + blockMap = make(map[string][]string) + continue + } - lines = append(lines, line) - } - modules.ExecuteInit(module, modules.CallbackInfo{ - CallbackType: modules.Config, - Command: command, - Arguments: lines, - }) + if before, after, found := strings.Cut(line, "}"); found { + if after != "" || before != "" { + configFatalError(nil, "Nothing may precede or follow '}'. A new line must be used") + } + if blockMap == nil { + configFatalError(nil, "Found a '}' tag without a matching '{' tag.") + } + module, command := modules.GetCommand(currentOption, modules.Config) + if module == nil { + configFatalError(nil, "Unknown configuration block: "+currentOption) } + modules.ExecuteInit(module, modules.CallbackInfo{ + CallbackType: modules.Config, + Command: command, + Config: blockMap, + }) + blockMap = nil + continue } + if blockMap != nil { + kv := strings.SplitN(line, " ", 2) + if len(kv) != 2 { + configFatalError(nil, "Key without value") + } + if blockMap[kv[0]] == nil { + blockMap[kv[0]] = make([]string, 0) + } + blockMap[kv[0]] = append(blockMap[kv[0]], kv[1]) + } } + _ = file.Close() + if modules.ExistsBlockingModule() { modules.ExecuteComplete() waitForSignal() @@ -67,7 +88,14 @@ func readConfig(dest string) { } if err := scanner.Err(); err != nil { - panic(err) + configFatalError(err, "") } +} +func configFatalError(err error, explanation string) { + fmt.Println("Error reading config file:", explanation) + if err != nil { + fmt.Println(err) + } + os.Exit(1) } diff --git a/modules/modules.go b/modules/modules.go index c1c50ba..1115d9f 100644 --- a/modules/modules.go +++ b/modules/modules.go @@ -29,6 +29,7 @@ type CallbackInfo struct { CallbackType CallbackType Command Command Arguments []string + Config map[string][]string } func RegisterModule(name string, commands []Command, initCallback func(CallbackInfo), CompleteCallback func(), shutdownCallback func()) { diff --git a/modules/userInterface/userInterface.go b/modules/userInterface/userInterface.go index dbf60f2..af2f86d 100644 --- a/modules/userInterface/userInterface.go +++ b/modules/userInterface/userInterface.go @@ -121,31 +121,21 @@ func initCallback(callback modules.CallbackInfo) { switch callback.Command.CommandText { case "proxy": obj := configProxy{} + obj.Iface1 = getDefaultConfValue(callback.Config["ext-iface"]) + obj.Iface2 = getDefaultConfValue(callback.Config["int-iface"]) + obj.autosense = getDefaultConfValue(callback.Config["autosense"]) + obj.DontMonitorInterfaces = getDefaultConfValue(callback.Config["monitor-changes"]) == "off" + filter := "" - for _, n := range callback.Arguments { - if strings.HasPrefix(n, "ext-iface") { - obj.Iface1 = strings.TrimSpace(strings.TrimPrefix(n, "ext-iface")) - } - if strings.HasPrefix(n, "int-iface") { - obj.Iface2 = strings.TrimSpace(strings.TrimPrefix(n, "int-iface")) - } - if strings.HasPrefix(n, "filter") { - filter += strings.TrimSpace(strings.TrimPrefix(n, "filter")) + ";" - if strings.Contains(n, ";") { - showError("config: the use of semicolons is not allowed in the filter arguments") - } - } - if strings.HasPrefix(n, "autosense") { - obj.autosense = strings.TrimSpace(strings.TrimPrefix(n, "autosense")) - } - if strings.HasPrefix(n, "monitor-changes") { - obj.DontMonitorInterfaces = strings.TrimSpace(strings.TrimPrefix(n, "monitor-changes")) == "off" - } - if strings.Contains(n, "//") { - showError("config: comments are not allowed after arguments") + for i := range callback.Config["filter"] { + value := callback.Config["filter"][i] + if strings.Contains(value, ";") { + showError("config: the use of semicolons is not allowed in the filter arguments") } + filter += value + ";" } obj.Filter = strings.TrimSuffix(filter, ";") + if obj.autosense != "" && obj.Filter != "" { showError("config: cannot have both a filter and autosense enabled on a proxy object") } @@ -155,40 +145,40 @@ func initCallback(callback modules.CallbackInfo) { allProxies = append(allProxies, &obj) case "responder": obj := configResponder{} + obj.Iface = getDefaultConfValue(callback.Config["iface"]) + obj.autosense = getDefaultConfValue(callback.Config["autosense"]) + obj.DontMonitorInterfaces = getDefaultConfValue(callback.Config["monitor-changes"]) == "off" filter := "" - for _, n := range callback.Arguments { - if strings.HasPrefix(n, "iface") { - obj.Iface = strings.TrimSpace(strings.TrimPrefix(n, "iface")) - } - if strings.HasPrefix(n, "filter") { - filter += strings.TrimSpace(strings.TrimPrefix(n, "filter")) + ";" - if strings.Contains(n, ";") { - showError("config: the use of semicolons is not allowed in the filter arguments") - } - } - if strings.HasPrefix(n, "autosense") { - obj.autosense = strings.TrimSpace(strings.TrimPrefix(n, "autosense")) - } - if obj.autosense != "" && obj.Filter != "" { - showError("config: cannot have both a filter and autosense enabled on a responder object") - } - if obj.Iface == "" { - showError("config: interface not specified in the responder object. (iface parameter)") - } - if strings.HasPrefix(n, "monitor-changes") { - obj.DontMonitorInterfaces = strings.TrimSpace(strings.TrimPrefix(n, "monitor-changes")) == "off" - } - if strings.Contains(n, "//") { - showError("config: comments are not allowed after arguments") + for i := range callback.Config["filter"] { + value := callback.Config["filter"][i] + if strings.Contains(value, ";") { + showError("config: the use of semicolons is not allowed in the filter arguments") } + filter += value + ";" } obj.Filter = strings.TrimSuffix(filter, ";") - allResponders = append(allResponders, &obj) + if obj.autosense != "" && obj.Filter != "" { + showError("config: cannot have both a filter and autosense enabled on a responder object") + } + if obj.Iface == "" { + showError("config: interface not specified in the responder object. (iface parameter)") + } + allResponders = append(allResponders, &obj) } } } +func getDefaultConfValue(in []string) string { + if in == nil { + return "" + } + if len(in) == 0 { + return "" + } + return in[0] +} + func completeCallback() { for _, n := range allProxies { o := pndp.NewProxy(n.Iface1, n.Iface2, pndp.ParseFilter(n.Filter), n.autosense, !n.DontMonitorInterfaces) @@ -201,6 +191,7 @@ func completeCallback() { o.Start() } } + func shutdownCallback() { for _, n := range allProxies { n.instance.Stop() diff --git a/pndpd.conf b/pndpd.conf index c8813f9..2806669 100644 --- a/pndpd.conf +++ b/pndpd.conf @@ -2,12 +2,12 @@ // Proxy example with autoconfigured allow-list // The allow-list of IP addresses to proxy is configured based -// on the addresses assigned to the interface specified via the autosense parameter. +// on the networks assigned to the interface specified via the autosense parameter. // This works even if the IP addresses change frequently. proxy { ext-iface eth0 int-iface eth1 - autosense eth1 + autosense eth1 // If eth1 has fd01::1/64 assigned to it, then fd01::/64 will be configured as an allow-list // Disable monitor-changes only if the IP addresses assigned to the specified interfaces never change (with the exception of the autosense interface) // monitor-changes on } @@ -28,11 +28,11 @@ proxy { // Responder example with autoconfigured allow-list (Not recommended - prefer using proxy mode) // Create an NDP responder that listens and responds on interface "eth0" // The allow-list of IP addresses to proxy is configured based -// on the addresses assigned to the interface specified via the autosense parameter. +// on the networks assigned to the interface specified via the autosense parameter. // This works even if the IP addresses change frequently. responder { iface eth0 - autosense eth0 + autosense eth0 // If eth0 has fd01::1/64 assigned to it, then fd01::/64 will be configured as an allow-list // Disable monitor-changes only if the IP addresses assigned to the specified interfaces never change (with the exception of the autosense interface) // monitor-changes on }