From 843abd7c1927cf82784fc025cadb4ca6eeb700f4 Mon Sep 17 00:00:00 2001 From: mengqi <5b5f7426@gmail.com> Date: Wed, 24 Jun 2020 08:32:14 +0800 Subject: [PATCH] Add command line option: -b, -bind-ip. With this option, you can bind one or more IP addresses to the ran web server. --- global/config.go | 15 +++++- global/ip.go | 124 +++++++++++++++++++++++++++++++++++++++++++++ ran.go | 128 +++++++++++++++++++---------------------------- 3 files changed, 190 insertions(+), 77 deletions(-) create mode 100644 global/ip.go diff --git a/global/config.go b/global/config.go index c8fa66a..0dfaad9 100755 --- a/global/config.go +++ b/global/config.go @@ -31,6 +31,7 @@ const DefaultTLSPolicy = TLSOnly // Setting about ran server type Setting struct { + IP []string // IP addresses binded to ran server. Port uint // HTTP port. Default is 8080. ShowConf bool // If show config info in the log. Debug bool // If turns on debug mode. Default is false. @@ -264,6 +265,9 @@ Usage: ran [Options...] Options: -r, -root= Root path of the site. Default is current working directory. + -b, -bind-ip= Bind one or more IP addresses to the ran web server. + Multiple IP addresses should be separated by comma. + If not provide this Option, ran will use 0.0.0.0. -p, -port= HTTP port. Default is 8080. -404= Path of a custom 404 file, relative to Root. Example: /404.html. -i, -index= File name of index, priority depends on the order of values. @@ -338,7 +342,7 @@ func LoadConfig(versionInfo string) { os.Exit(1) } - var configPath, root, path404, authMethod, auth, path401, certPath, keyPath, tlsPolicy string + var configPath, bindip, root, path404, authMethod, auth, path401, certPath, keyPath, tlsPolicy string var port, tlsPort uint var indexName server.Index var version, help, makeCert bool @@ -350,6 +354,8 @@ func LoadConfig(versionInfo string) { // TODO: load config file } + flag.StringVar(&bindip, "b", "", "IP addresses binded to ran server") + flag.StringVar(&bindip, "bind-ip", "", "IP addresses binded to ran server") flag.UintVar( &port, "p", 0, "HTTP port") flag.UintVar( &port, "port", 0, "HTTP port") flag.StringVar(&root, "r", "", "Root path of the website") @@ -428,6 +434,12 @@ func LoadConfig(versionInfo string) { } } + Config.IP, err = getIPs(bindip) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %s\n", err) + os.Exit(1) + } + if port > 0 { Config.Port = port } @@ -480,3 +492,4 @@ func LoadConfig(versionInfo string) { return } + diff --git a/global/ip.go b/global/ip.go new file mode 100644 index 0000000..71869bd --- /dev/null +++ b/global/ip.go @@ -0,0 +1,124 @@ +package global + +import ( + "net" + "strings" + "fmt" +) + + +// Get network interface index by it's IP. +// Usage example: getInterfaceIndexByIP(net.ParseIP("fe80::aaaa:bbbb:cccc:dddd")) +func getInterfaceIndexByIP(addr net.IP) (index int, e error) { + if addr == nil { + e = fmt.Errorf("IP address is not correct") + return + } + + iface, err := net.Interfaces() + if e != nil { + e = err + return + } + + for _, i := range iface { + addrs, err := i.Addrs() + if err != nil { + e = err + return + } + + for _, a := range addrs { + address := net.ParseIP(strings.SplitN(a.String(), "/", 2)[0]) + if addr.Equal(address) { + index = i.Index + return + } + } + } + + e = fmt.Errorf("Could not found interface by this IP address: '%s'", addr.String()) + + return +} + + +// check if an address is IPv6 link local address (fe80::/10) +func isIPv6LinkLocalAddress(addr net.IP) bool { + if addr.To4() != nil { + return false + } + return addr.IsLinkLocalUnicast() +} + + +// get all IPs from command-line option -bind-ip +func getIPs(bindIP string) (ips []string, err error) { + anyIP := []string{"0.0.0.0"} + + if bindIP == "" { + ips = anyIP + return + } + + var invalidIPs []string + allIPs := make(map[string]interface{}) + + for _, item := range strings.Split(bindIP, ",") { + item = strings.TrimSpace(item) + if item == "" { + continue + } + add := net.ParseIP(item) + if add != nil { + if add.To4() != nil { + // IPv4 address + allIPs[add.String()] = nil + } else { + // IPv6 address + + if isIPv6LinkLocalAddress(add) { + // if the address is an IPv6 link local address (fe80::/10), + // add a scope (interface index) to the end of the address. + index, e := getInterfaceIndexByIP(add) + if e != nil { + err = e + return + } + allIPs[fmt.Sprintf("[%s%%%d]", add.String(), index)] = nil + } else { + allIPs[fmt.Sprintf("[%s]", add.String())] = nil + } + } + } else { + invalidIPs = append(invalidIPs, item) + } + } + + if len(invalidIPs) > 0 { + err = fmt.Errorf("Invalid IP: %s", strings.Join(invalidIPs, ", ")) + return + } + + + if _, ok := allIPs["0.0.0.0"]; ok { + ips = anyIP + return + } + if _, ok := allIPs["[::]"]; ok { + ips = anyIP + return + } + + for key, _ := range allIPs { + ips = append(ips, key) + } + + if len(ips) == 0 { + err = fmt.Errorf("No IP address provided") + return + } + + return +} + diff --git a/ran.go b/ran.go index a64fcdb..c5f9781 100755 --- a/ran.go +++ b/ran.go @@ -2,7 +2,6 @@ package main import "syscall" import "os/signal" -import "net" import "net/http" import "os" import "fmt" @@ -35,62 +34,26 @@ func catchSignal() { } - -// Get all available IPv4 addresses in system's network interface. -func getIPAutomaticly() (ip []string, e error) { - iface, err := net.Interfaces() - if err != nil { - e = err - return - } - - for _, i := range iface { - addrs, err := i.Addrs() - if e != nil { - e = err - return - } - for _, a := range addrs { - add := net.ParseIP(strings.SplitN(a.String(), "/", 2)[0]) - if add.To4() != nil { - ip = append(ip, add.String()) - } - } - } - - for _, i := range ip { - if i == "127.0.0.1" { - return - } - } - - // add loopback - ip = append(ip, "127.0.0.1") - - return -} - - -// Get all Listening address, like: http://127.0.0.1:8080 +// Get all Listening address, like: http://127.0.0.1:8080. The return value is used for recording logs. func getListeningAddr() (addr []string, err error) { - ips, err := getIPAutomaticly() - if err != nil { - return - } - - for _, i := range ips { + for _, ip := range global.Config.IP { if global.Config.TLS != nil { if global.Config.TLS.Policy == global.TLSOnly { - addr = append(addr, fmt.Sprintf("https://%s:%d", i, global.Config.TLS.Port)) + addr = append(addr, fmt.Sprintf("https://%s:%d", ip, global.Config.TLS.Port)) } else { - addr = append(addr, fmt.Sprintf("http://%s:%d", i, global.Config.Port)) - addr = append(addr, fmt.Sprintf("https://%s:%d", i, global.Config.TLS.Port)) + addr = append(addr, fmt.Sprintf("http://%s:%d", ip, global.Config.Port)) + addr = append(addr, fmt.Sprintf("https://%s:%d", ip, global.Config.TLS.Port)) } } else { - addr = append(addr, fmt.Sprintf("http://%s:%d", i, global.Config.Port)) + addr = append(addr, fmt.Sprintf("http://%s:%d", ip, global.Config.Port)) } } + if len(addr) == 0 { + err = fmt.Errorf("No IP address provided") + return + } + return } @@ -132,7 +95,6 @@ func startLog() { func main() { - global.LoadConfig(versionInfo) defer func() { @@ -158,39 +120,53 @@ func main() { ran := server.NewRanServer(global.Config.Config, global.Logger) startHTTPServer := func() { - wg.Add(1) - go func() { - err := http.ListenAndServe(fmt.Sprintf(":%d", global.Config.Port), ran.Serve()) - if err != nil { - global.Logger.Fatal(err) - } - wg.Done() - }() + for _, ip := range global.Config.IP { + wg.Add(1) + go func(ip string) { + err := http.ListenAndServe( + fmt.Sprintf("%s:%d", ip, global.Config.Port), + ran.Serve(), + ) + if err != nil { + global.Logger.Fatal(err) + } + wg.Done() + }(ip) + } } startTLSServer := func() { - wg.Add(1) - go func() { - err := http.ListenAndServeTLS(fmt.Sprintf(":%d", global.Config.TLS.Port), - global.Config.TLS.PublicKey, - global.Config.TLS.PrivateKey, - ran.Serve()) - if err != nil { - global.Logger.Fatal(err) - } - wg.Done() - }() + for _, ip := range global.Config.IP { + wg.Add(1) + go func(ip string) { + err := http.ListenAndServeTLS( + fmt.Sprintf("%s:%d", ip, global.Config.TLS.Port), + global.Config.TLS.PublicKey, + global.Config.TLS.PrivateKey, + ran.Serve(), + ) + if err != nil { + global.Logger.Fatal(err) + } + wg.Done() + }(ip) + } } redirectToHTTPS := func() { - wg.Add(1) - go func() { - err := http.ListenAndServe(fmt.Sprintf(":%d", global.Config.Port), ran.RedirectToHTTPS(global.Config.TLS.Port)) - if err != nil { - global.Logger.Fatal(err) - } - wg.Done() - }() + for _, ip := range global.Config.IP { + wg.Add(1) + go func(ip string) { + err := http.ListenAndServe( + fmt.Sprintf("%s:%d", ip, global.Config.Port), + ran.RedirectToHTTPS(global.Config.TLS.Port), + ) + if err != nil { + global.Logger.Fatal(err) + } + wg.Done() + }(ip) + } } if global.Config.TLS != nil {