Commit c494b7d5 authored by ale's avatar ale

autodetect public IP address and network interface

This provides some discovery capabilities to radiod and redirectord, so that in most cases fewer command-line options are required for operation.
parent ed350cdb
......@@ -16,9 +16,9 @@ import (
var (
name = flag.String("name", shortHostname(), "Name for this node")
publicIPs = util.IPList("ip", "Public IP for this machine (may be specified more than once)")
internalIPs = util.IPList("internal-ip", "Internal IP for this machine (within the cluster), if different than --ip")
netDev = flag.String("interface", "eth0", "Network interface to monitor for utilization")
publicIPs = util.IPList("ip", "Public IP for this machine (may be specified more than once). If unset, the program will try to resolve the local hostname, or it will fall back to inspecting network devices.")
internalIPs = util.IPList("internal-ip", "Internal IP for this machine (within the cluster), if different from --ip")
netDev = flag.String("interface", "", "Network interface to monitor for utilization. If unset, default to the interface associated with --ip.")
bwLimit = flag.Int("bwlimit", 100, "Bandwidth usage limit (Mbps)")
maxClients = flag.Int("max-clients", 1000, "Maximum number of connected clients")
)
......@@ -35,11 +35,15 @@ func main() {
log.SetFlags(0)
flag.Parse()
if err := util.DetectPublicNetworkParams(publicIPs, *internalIPs, netDev); err != nil {
log.Fatal(err)
}
instrumentation.NewCounter("radiod.restarts").Incr()
client := autoradio.NewEtcdClient(true)
bwLimitBytes := float64(*bwLimit * 1000000 / 8)
n := node.NewRadioNode(*name, util.IPListWithDefault(*publicIPs, "127.0.0.1"), *internalIPs, *netDev, bwLimitBytes, *maxClients, client)
n := node.NewRadioNode(*name, *publicIPs, *internalIPs, *netDev, bwLimitBytes, *maxClients, client)
// Set up a clean shutdown function on SIGTERM.
stopch := make(chan os.Signal)
......
......@@ -15,7 +15,7 @@ import (
var (
domain = flag.String("domain", "", "DNS domain to serve")
publicIps = util.IPList("ip", "Public IP for this machine (may be specified more than once)")
publicIPs = util.IPList("ip", "Public IP for this machine (may be specified more than once). If unset, the program will try to resolve the local hostname, or it will fall back to inspecting network devices.")
dnsPort = flag.Int("dns-port", 53, "DNS port")
httpPort = flag.Int("http-port", 80, "HTTP port")
staticDir = flag.String("static-dir", "/usr/share/autoradio/htdocs/static", "Static content directory")
......@@ -34,11 +34,15 @@ func main() {
log.Fatal("Must specify --domain")
}
if err := util.DetectPublicNetworkParams(publicIPs, nil, nil); err != nil {
log.Fatal(err)
}
instrumentation.NewCounter("redirectord.restarts").Incr()
client := autoradio.NewClient(autoradio.NewEtcdClient(false))
dnsRed := fe.NewDNSRedirector(client, *domain, util.IPListWithDefault(*publicIps, "127.0.0.1"), dnsTtl)
dnsRed := fe.NewDNSRedirector(client, *domain, *publicIPs, dnsTtl)
dnsRed.Start(fmt.Sprintf(":%d", *dnsPort))
red, err := fe.NewHTTPRedirector(client, *domain, *lbPolicy, *staticDir, *templateDir)
......
......@@ -31,10 +31,3 @@ func IPList(name, help string) *[]net.IP {
flag.Var(&l, name, help)
return (*[]net.IP)(&l)
}
func IPListWithDefault(l []net.IP, deflt string) []net.IP {
if len(l) == 0 {
return []net.IP{net.ParseIP(deflt)}
}
return l
}
package util
import (
"errors"
"fmt"
"log"
"net"
"os"
)
// DetectPublicNetworkParams tries to be smart and find a public IP
// address and network interface for the local host.
//
// If *publicIPs is nil, attempts to resolve the local hostname and
// look for non-local addresses. If none are found, figures out the IP
// from the network interface, if given, or finds the first
// non-loopback interface.
//
// If *netDev is an empty string, tries to figure out the network
// device to use from either internalIPs or *publicIPs, whichever is
// not nil. The netDev pointer itself can be nil, in which case no
// network device detection is attempted.
//
func DetectPublicNetworkParams(publicIPs *[]net.IP, internalIPs []net.IP, netDev *string) error {
if len(*publicIPs) == 0 && netDev != nil && *netDev != "" {
ips, err := findIPsForInterface(*netDev)
if err != nil {
log.Printf("Warning: %v", err)
}
if len(ips) > 0 {
log.Printf("autodetected IP addresses %v on %s", ips, *netDev)
*publicIPs = ips
}
}
if len(*publicIPs) == 0 {
// Try to guess the public IP from the hostname.
ips, err := guessPublicIPs()
if err != nil {
log.Printf("Warning: %v", err)
dev, ips, err := findFirstNonLoopbackInterface()
if err != nil {
log.Printf("Warning: no non-loopback interfaces found")
} else {
if netDev != nil {
*netDev = dev
}
*publicIPs = ips
log.Printf("autodetected network device %s and IP addresses %v", dev, ips)
}
} else {
log.Printf("autodetected IP addresses %v", ips)
*publicIPs = ips
}
}
if len(*publicIPs) == 0 {
return errors.New("public IP address is not set and autodetection has failed, can't start")
}
if netDev != nil && *netDev == "" {
ips := internalIPs
if len(ips) == 0 {
ips = *publicIPs
}
dev, err := findInterfaceForIPs(ips)
if err != nil {
log.Printf("Warning: %v", err)
log.Printf("will assume interface = eth0")
dev = "eth0"
} else {
log.Printf("autodetected network interface %s", dev)
}
*netDev = dev
}
return nil
}
func filterUnicast(ips []net.IP) []net.IP {
var out []net.IP
for _, ip := range ips {
if ip.IsGlobalUnicast() {
out = append(out, ip)
}
}
return out
}
func guessPublicIPs() ([]net.IP, error) {
// Resolve our hostname and try to find non-local addresses.
hostname, _ := os.Hostname()
addrs, err := net.LookupIP(hostname)
if err != nil || len(addrs) == 0 {
return nil, fmt.Errorf("could not resolve own hostname: %v", err)
}
addrs = filterUnicast(addrs)
if len(addrs) == 0 {
return nil, fmt.Errorf("no non-loopback addresses for %v", hostname)
}
return addrs, nil
}
func getActiveInterfaces() []net.Interface {
ifs, err := net.Interfaces()
if err != nil {
log.Printf("Warning: %v", err)
return nil
}
var out []net.Interface
for _, i := range ifs {
if (i.Flags&net.FlagLoopback) == net.FlagLoopback || (i.Flags&net.FlagUp) == 0 {
continue
}
out = append(out, i)
}
return out
}
func interfaceIPs(i net.Interface) []net.IP {
var out []net.IP
addrs, err := i.Addrs()
if err != nil {
return nil
}
for _, addr := range addrs {
ip, _, err := net.ParseCIDR(addr.String())
if err == nil {
out = append(out, ip)
}
}
return out
}
func findInterfaceForIP(ip net.IP) (string, error) {
for _, i := range getActiveInterfaces() {
for _, ifIP := range filterUnicast(interfaceIPs(i)) {
log.Printf("found interface address %s %s", i.Name, ifIP)
if ifIP.Equal(ip) {
return i.Name, nil
}
}
}
return "", errors.New("not found")
}
func findInterfaceForIPs(ips []net.IP) (string, error) {
for _, ip := range ips {
if result, err := findInterfaceForIP(ip); err == nil {
return result, nil
}
}
return "", errors.New("no interfaces found")
}
func findIPsForInterface(dev string) ([]net.IP, error) {
i, err := net.InterfaceByName(dev)
if err != nil {
return nil, err
}
return filterUnicast(interfaceIPs(*i)), nil
}
func findFirstNonLoopbackInterface() (string, []net.IP, error) {
for _, i := range getActiveInterfaces() {
addrs := interfaceIPs(i)
if len(addrs) > 0 {
return i.Name, addrs, nil
}
}
return "", nil, errors.New("no non-loopback interfaces found")
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment