diff --git a/etchosts/etchosts.go b/etchosts/etchosts.go deleted file mode 100644 index bef4a48..0000000 --- a/etchosts/etchosts.go +++ /dev/null @@ -1,79 +0,0 @@ -package etchosts - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "regexp" -) - -// Structure for a single host record -type Record struct { - Hosts string - IP string -} - -// Writes record to file and returns bytes written or error -func (r Record) WriteTo(w io.Writer) (int64, error) { - n, err := fmt.Fprintf(w, "%s\t%s\n", r.IP, r.Hosts) - return int64(n), err -} - -// Default hosts config records slice -var defaultContent = []Record{ - {Hosts: "localhost", IP: "127.0.0.1"}, - {Hosts: "localhost ip6-localhost ip6-loopback", IP: "::1"}, - {Hosts: "ip6-localnet", IP: "fe00::0"}, - {Hosts: "ip6-mcastprefix", IP: "ff00::0"}, - {Hosts: "ip6-allnodes", IP: "ff02::1"}, - {Hosts: "ip6-allrouters", IP: "ff02::2"}, -} - -// Build function -// path is path to host file string required -// IP, hostname, and domainname set main record leave empty for no master record -// extraContent is an array of extra host records. -func Build(path, IP, hostname, domainname string, extraContent []Record) error { - content := bytes.NewBuffer(nil) - if IP != "" { - //set main record - var mainRec Record - mainRec.IP = IP - if domainname != "" { - mainRec.Hosts = fmt.Sprintf("%s.%s %s", hostname, domainname, hostname) - } else { - mainRec.Hosts = hostname - } - if _, err := mainRec.WriteTo(content); err != nil { - return err - } - } - // Write defaultContent slice to buffer - for _, r := range defaultContent { - if _, err := r.WriteTo(content); err != nil { - return err - } - } - // Write extra content from function arguments - for _, r := range extraContent { - if _, err := r.WriteTo(content); err != nil { - return err - } - } - - return ioutil.WriteFile(path, content.Bytes(), 0644) -} - -// Update all IP addresses where hostname matches. -// path is path to host file -// IP is new IP address -// hostname is hostname to search for to replace IP -func Update(path, IP, hostname string) error { - old, err := ioutil.ReadFile(path) - if err != nil { - return err - } - var re = regexp.MustCompile(fmt.Sprintf("(\\S*)(\\t%s)", regexp.QuoteMeta(hostname))) - return ioutil.WriteFile(path, re.ReplaceAll(old, []byte(IP+"$2")), 0644) -} diff --git a/etchosts/etchosts_test.go b/etchosts/etchosts_test.go deleted file mode 100644 index c033904..0000000 --- a/etchosts/etchosts_test.go +++ /dev/null @@ -1,134 +0,0 @@ -package etchosts - -import ( - "bytes" - "io/ioutil" - "os" - "testing" -) - -func TestBuildDefault(t *testing.T) { - file, err := ioutil.TempFile("", "") - if err != nil { - t.Fatal(err) - } - defer os.Remove(file.Name()) - - // check that /etc/hosts has consistent ordering - for i := 0; i <= 5; i++ { - err = Build(file.Name(), "", "", "", nil) - if err != nil { - t.Fatal(err) - } - - content, err := ioutil.ReadFile(file.Name()) - if err != nil { - t.Fatal(err) - } - expected := "127.0.0.1\tlocalhost\n::1\tlocalhost ip6-localhost ip6-loopback\nfe00::0\tip6-localnet\nff00::0\tip6-mcastprefix\nff02::1\tip6-allnodes\nff02::2\tip6-allrouters\n" - - if expected != string(content) { - t.Fatalf("Expected to find '%s' got '%s'", expected, content) - } - } -} - -func TestBuildHostnameDomainname(t *testing.T) { - file, err := ioutil.TempFile("", "") - if err != nil { - t.Fatal(err) - } - defer os.Remove(file.Name()) - - err = Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil) - if err != nil { - t.Fatal(err) - } - - content, err := ioutil.ReadFile(file.Name()) - if err != nil { - t.Fatal(err) - } - - if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) { - t.Fatalf("Expected to find '%s' got '%s'", expected, content) - } -} - -func TestBuildHostname(t *testing.T) { - file, err := ioutil.TempFile("", "") - if err != nil { - t.Fatal(err) - } - defer os.Remove(file.Name()) - - err = Build(file.Name(), "10.11.12.13", "testhostname", "", nil) - if err != nil { - t.Fatal(err) - } - - content, err := ioutil.ReadFile(file.Name()) - if err != nil { - t.Fatal(err) - } - - if expected := "10.11.12.13\ttesthostname\n"; !bytes.Contains(content, []byte(expected)) { - t.Fatalf("Expected to find '%s' got '%s'", expected, content) - } -} - -func TestBuildNoIP(t *testing.T) { - file, err := ioutil.TempFile("", "") - if err != nil { - t.Fatal(err) - } - defer os.Remove(file.Name()) - - err = Build(file.Name(), "", "testhostname", "", nil) - if err != nil { - t.Fatal(err) - } - - content, err := ioutil.ReadFile(file.Name()) - if err != nil { - t.Fatal(err) - } - - if expected := ""; !bytes.Contains(content, []byte(expected)) { - t.Fatalf("Expected to find '%s' got '%s'", expected, content) - } -} - -func TestUpdate(t *testing.T) { - file, err := ioutil.TempFile("", "") - if err != nil { - t.Fatal(err) - } - defer os.Remove(file.Name()) - - if err := Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil); err != nil { - t.Fatal(err) - } - - content, err := ioutil.ReadFile(file.Name()) - if err != nil { - t.Fatal(err) - } - - if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) { - t.Fatalf("Expected to find '%s' got '%s'", expected, content) - } - - if err := Update(file.Name(), "1.1.1.1", "testhostname"); err != nil { - t.Fatal(err) - } - - content, err = ioutil.ReadFile(file.Name()) - if err != nil { - t.Fatal(err) - } - - if expected := "1.1.1.1\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) { - t.Fatalf("Expected to find '%s' got '%s'", expected, content) - } -} diff --git a/iptables/firewalld.go b/iptables/firewalld.go deleted file mode 100644 index 89f123a..0000000 --- a/iptables/firewalld.go +++ /dev/null @@ -1,159 +0,0 @@ -package iptables - -import ( - "fmt" - "strings" - - "github.com/Sirupsen/logrus" - "github.com/godbus/dbus" -) - -type IPV string - -const ( - Iptables IPV = "ipv4" - Ip6tables IPV = "ipv6" - Ebtables IPV = "eb" -) -const ( - dbusInterface = "org.fedoraproject.FirewallD1" - dbusPath = "/org/fedoraproject/FirewallD1" -) - -// Conn is a connection to firewalld dbus endpoint. -type Conn struct { - sysconn *dbus.Conn - sysobj *dbus.Object - signal chan *dbus.Signal -} - -var ( - connection *Conn - firewalldRunning bool // is Firewalld service running - onReloaded []*func() // callbacks when Firewalld has been reloaded -) - -func FirewalldInit() error { - var err error - - if connection, err = newConnection(); err != nil { - return fmt.Errorf("Failed to connect to D-Bus system bus: %v", err) - } - if connection != nil { - go signalHandler() - } - - firewalldRunning = checkRunning() - return nil -} - -// New() establishes a connection to the system bus. -func newConnection() (*Conn, error) { - c := new(Conn) - if err := c.initConnection(); err != nil { - return nil, err - } - - return c, nil -} - -// Innitialize D-Bus connection. -func (c *Conn) initConnection() error { - var err error - - c.sysconn, err = dbus.SystemBus() - if err != nil { - return err - } - - // This never fails, even if the service is not running atm. - c.sysobj = c.sysconn.Object(dbusInterface, dbus.ObjectPath(dbusPath)) - - rule := fmt.Sprintf("type='signal',path='%s',interface='%s',sender='%s',member='Reloaded'", - dbusPath, dbusInterface, dbusInterface) - c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule) - - rule = fmt.Sprintf("type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',path='/org/freedesktop/DBus',sender='org.freedesktop.DBus',arg0='%s'", - dbusInterface) - c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule) - - c.signal = make(chan *dbus.Signal, 10) - c.sysconn.Signal(c.signal) - - return nil -} - -func signalHandler() { - for signal := range connection.signal { - if strings.Contains(signal.Name, "NameOwnerChanged") { - firewalldRunning = checkRunning() - dbusConnectionChanged(signal.Body) - } else if strings.Contains(signal.Name, "Reloaded") { - reloaded() - } - } -} - -func dbusConnectionChanged(args []interface{}) { - name := args[0].(string) - old_owner := args[1].(string) - new_owner := args[2].(string) - - if name != dbusInterface { - return - } - - if len(new_owner) > 0 { - connectionEstablished() - } else if len(old_owner) > 0 { - connectionLost() - } -} - -func connectionEstablished() { - reloaded() -} - -func connectionLost() { - // Doesn't do anything for now. Libvirt also doesn't react to this. -} - -// call all callbacks -func reloaded() { - for _, pf := range onReloaded { - (*pf)() - } -} - -// add callback -func OnReloaded(callback func()) { - for _, pf := range onReloaded { - if pf == &callback { - return - } - } - onReloaded = append(onReloaded, &callback) -} - -// Call some remote method to see whether the service is actually running. -func checkRunning() bool { - var zone string - var err error - - if connection != nil { - err = connection.sysobj.Call(dbusInterface+".getDefaultZone", 0).Store(&zone) - logrus.Infof("Firewalld running: %t", err == nil) - return err == nil - } - return false -} - -// Firewalld's passthrough method simply passes args through to iptables/ip6tables -func Passthrough(ipv IPV, args ...string) ([]byte, error) { - var output string - logrus.Debugf("Firewalld passthrough: %s, %s", ipv, args) - if err := connection.sysobj.Call(dbusInterface+".direct.passthrough", 0, ipv, args).Store(&output); err != nil { - return nil, err - } - return []byte(output), nil -} diff --git a/iptables/firewalld_test.go b/iptables/firewalld_test.go deleted file mode 100644 index 547ba7e..0000000 --- a/iptables/firewalld_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package iptables - -import ( - "net" - "strconv" - "testing" -) - -func TestFirewalldInit(t *testing.T) { - if !checkRunning() { - t.Skip("firewalld is not running") - } - if err := FirewalldInit(); err != nil { - t.Fatal(err) - } -} - -func TestReloaded(t *testing.T) { - var err error - var fwdChain *Chain - - fwdChain, err = NewChain("FWD", "lo", Filter, false) - if err != nil { - t.Fatal(err) - } - defer fwdChain.Remove() - - // copy-pasted from iptables_test:TestLink - ip1 := net.ParseIP("192.168.1.1") - ip2 := net.ParseIP("192.168.1.2") - port := 1234 - proto := "tcp" - - err = fwdChain.Link(Append, ip1, ip2, port, proto) - if err != nil { - t.Fatal(err) - } else { - // to be re-called again later - OnReloaded(func() { fwdChain.Link(Append, ip1, ip2, port, proto) }) - } - - rule1 := []string{ - "-i", fwdChain.Bridge, - "-o", fwdChain.Bridge, - "-p", proto, - "-s", ip1.String(), - "-d", ip2.String(), - "--dport", strconv.Itoa(port), - "-j", "ACCEPT"} - - if !Exists(fwdChain.Table, fwdChain.Name, rule1...) { - t.Fatalf("rule1 does not exist") - } - - // flush all rules - fwdChain.Remove() - - reloaded() - - // make sure the rules have been recreated - if !Exists(fwdChain.Table, fwdChain.Name, rule1...) { - t.Fatalf("rule1 hasn't been recreated") - } -} - -func TestPassthrough(t *testing.T) { - rule1 := []string{ - "-i", "lo", - "-p", "udp", - "--dport", "123", - "-j", "ACCEPT"} - - if firewalldRunning { - _, err := Passthrough(Iptables, append([]string{"-A"}, rule1...)...) - if err != nil { - t.Fatal(err) - } - if !Exists(Filter, "INPUT", rule1...) { - t.Fatalf("rule1 does not exist") - } - } - -} diff --git a/iptables/iptables.go b/iptables/iptables.go deleted file mode 100644 index 9cf1bbf..0000000 --- a/iptables/iptables.go +++ /dev/null @@ -1,305 +0,0 @@ -package iptables - -import ( - "errors" - "fmt" - "net" - "os/exec" - "strconv" - "strings" - "sync" - - "github.com/Sirupsen/logrus" -) - -type Action string -type Table string - -const ( - Append Action = "-A" - Delete Action = "-D" - Insert Action = "-I" - Nat Table = "nat" - Filter Table = "filter" - Mangle Table = "mangle" -) - -var ( - iptablesPath string - supportsXlock = false - // used to lock iptables commands if xtables lock is not supported - bestEffortLock sync.Mutex - ErrIptablesNotFound = errors.New("Iptables not found") -) - -type Chain struct { - Name string - Bridge string - Table Table -} - -type ChainError struct { - Chain string - Output []byte -} - -func (e ChainError) Error() string { - return fmt.Sprintf("Error iptables %s: %s", e.Chain, string(e.Output)) -} - -func initCheck() error { - - if iptablesPath == "" { - path, err := exec.LookPath("iptables") - if err != nil { - return ErrIptablesNotFound - } - iptablesPath = path - supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil - } - return nil -} - -func NewChain(name, bridge string, table Table, hairpinMode bool) (*Chain, error) { - c := &Chain{ - Name: name, - Bridge: bridge, - Table: table, - } - - if string(c.Table) == "" { - c.Table = Filter - } - - // Add chain if it doesn't exist - if _, err := Raw("-t", string(c.Table), "-n", "-L", c.Name); err != nil { - if output, err := Raw("-t", string(c.Table), "-N", c.Name); err != nil { - return nil, err - } else if len(output) != 0 { - return nil, fmt.Errorf("Could not create %s/%s chain: %s", c.Table, c.Name, output) - } - } - - switch table { - case Nat: - preroute := []string{ - "-m", "addrtype", - "--dst-type", "LOCAL"} - if !Exists(Nat, "PREROUTING", preroute...) { - if err := c.Prerouting(Append, preroute...); err != nil { - return nil, fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err) - } - } - output := []string{ - "-m", "addrtype", - "--dst-type", "LOCAL"} - if !hairpinMode { - output = append(output, "!", "--dst", "127.0.0.0/8") - } - if !Exists(Nat, "OUTPUT", output...) { - if err := c.Output(Append, output...); err != nil { - return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err) - } - } - case Filter: - link := []string{ - "-o", c.Bridge, - "-j", c.Name} - if !Exists(Filter, "FORWARD", link...) { - insert := append([]string{string(Insert), "FORWARD"}, link...) - if output, err := Raw(insert...); err != nil { - return nil, err - } else if len(output) != 0 { - return nil, fmt.Errorf("Could not create linking rule to %s/%s: %s", c.Table, c.Name, output) - } - } - } - return c, nil -} - -func RemoveExistingChain(name string, table Table) error { - c := &Chain{ - Name: name, - Table: table, - } - if string(c.Table) == "" { - c.Table = Filter - } - return c.Remove() -} - -// Add forwarding rule to 'filter' table and corresponding nat rule to 'nat' table -func (c *Chain) Forward(action Action, ip net.IP, port int, proto, destAddr string, destPort int) error { - daddr := ip.String() - if ip.IsUnspecified() { - // iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we - // want "0.0.0.0/0". "0/0" is correctly interpreted as "any - // value" by both iptables and ip6tables. - daddr = "0/0" - } - if output, err := Raw("-t", string(Nat), string(action), c.Name, - "-p", proto, - "-d", daddr, - "--dport", strconv.Itoa(port), - "-j", "DNAT", - "--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))); err != nil { - return err - } else if len(output) != 0 { - return ChainError{Chain: "FORWARD", Output: output} - } - - if output, err := Raw("-t", string(Filter), string(action), c.Name, - "!", "-i", c.Bridge, - "-o", c.Bridge, - "-p", proto, - "-d", destAddr, - "--dport", strconv.Itoa(destPort), - "-j", "ACCEPT"); err != nil { - return err - } else if len(output) != 0 { - return ChainError{Chain: "FORWARD", Output: output} - } - - if output, err := Raw("-t", string(Nat), string(action), "POSTROUTING", - "-p", proto, - "-s", destAddr, - "-d", destAddr, - "--dport", strconv.Itoa(destPort), - "-j", "MASQUERADE"); err != nil { - return err - } else if len(output) != 0 { - return ChainError{Chain: "FORWARD", Output: output} - } - - return nil -} - -// Add reciprocal ACCEPT rule for two supplied IP addresses. -// Traffic is allowed from ip1 to ip2 and vice-versa -func (c *Chain) Link(action Action, ip1, ip2 net.IP, port int, proto string) error { - if output, err := Raw("-t", string(Filter), string(action), c.Name, - "-i", c.Bridge, "-o", c.Bridge, - "-p", proto, - "-s", ip1.String(), - "-d", ip2.String(), - "--dport", strconv.Itoa(port), - "-j", "ACCEPT"); err != nil { - return err - } else if len(output) != 0 { - return fmt.Errorf("Error iptables forward: %s", output) - } - if output, err := Raw("-t", string(Filter), string(action), c.Name, - "-i", c.Bridge, "-o", c.Bridge, - "-p", proto, - "-s", ip2.String(), - "-d", ip1.String(), - "--sport", strconv.Itoa(port), - "-j", "ACCEPT"); err != nil { - return err - } else if len(output) != 0 { - return fmt.Errorf("Error iptables forward: %s", output) - } - return nil -} - -// Add linking rule to nat/PREROUTING chain. -func (c *Chain) Prerouting(action Action, args ...string) error { - a := []string{"-t", string(Nat), string(action), "PREROUTING"} - if len(args) > 0 { - a = append(a, args...) - } - if output, err := Raw(append(a, "-j", c.Name)...); err != nil { - return err - } else if len(output) != 0 { - return ChainError{Chain: "PREROUTING", Output: output} - } - return nil -} - -// Add linking rule to an OUTPUT chain -func (c *Chain) Output(action Action, args ...string) error { - a := []string{"-t", string(c.Table), string(action), "OUTPUT"} - if len(args) > 0 { - a = append(a, args...) - } - if output, err := Raw(append(a, "-j", c.Name)...); err != nil { - return err - } else if len(output) != 0 { - return ChainError{Chain: "OUTPUT", Output: output} - } - return nil -} - -func (c *Chain) Remove() error { - // Ignore errors - This could mean the chains were never set up - if c.Table == Nat { - c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL") - c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8") - c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL") // Created in versions <= 0.1.6 - - c.Prerouting(Delete) - c.Output(Delete) - } - Raw("-t", string(c.Table), "-F", c.Name) - Raw("-t", string(c.Table), "-X", c.Name) - return nil -} - -// Check if a rule exists -func Exists(table Table, chain string, rule ...string) bool { - if string(table) == "" { - table = Filter - } - - // iptables -C, --check option was added in v.1.4.11 - // http://ftp.netfilter.org/pub/iptables/changes-iptables-1.4.11.txt - - // try -C - // if exit status is 0 then return true, the rule exists - if _, err := Raw(append([]string{ - "-t", string(table), "-C", chain}, rule...)...); err == nil { - return true - } - - // parse "iptables -S" for the rule (this checks rules in a specific chain - // in a specific table) - ruleString := strings.Join(rule, " ") - existingRules, _ := exec.Command(iptablesPath, "-t", string(table), "-S", chain).Output() - - return strings.Contains(string(existingRules), ruleString) -} - -// Call 'iptables' system command, passing supplied arguments -func Raw(args ...string) ([]byte, error) { - if firewalldRunning { - output, err := Passthrough(Iptables, args...) - if err == nil || !strings.Contains(err.Error(), "was not provided by any .service files") { - return output, err - } - - } - - if err := initCheck(); err != nil { - return nil, err - } - if supportsXlock { - args = append([]string{"--wait"}, args...) - } else { - bestEffortLock.Lock() - defer bestEffortLock.Unlock() - } - - logrus.Debugf("%s, %v", iptablesPath, args) - - output, err := exec.Command(iptablesPath, args...).CombinedOutput() - if err != nil { - return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err) - } - - // ignore iptables' message about xtables lock - if strings.Contains(string(output), "waiting for it to exit") { - output = []byte("") - } - - return output, err -} diff --git a/iptables/iptables_test.go b/iptables/iptables_test.go deleted file mode 100644 index cd61739..0000000 --- a/iptables/iptables_test.go +++ /dev/null @@ -1,237 +0,0 @@ -package iptables - -import ( - "net" - "os/exec" - "strconv" - "strings" - "sync" - "testing" -) - -const chainName = "DOCKERTEST" - -var natChain *Chain -var filterChain *Chain - -func TestNewChain(t *testing.T) { - var err error - - natChain, err = NewChain(chainName, "lo", Nat, false) - if err != nil { - t.Fatal(err) - } - - filterChain, err = NewChain(chainName, "lo", Filter, false) - if err != nil { - t.Fatal(err) - } -} - -func TestForward(t *testing.T) { - ip := net.ParseIP("192.168.1.1") - port := 1234 - dstAddr := "172.17.0.1" - dstPort := 4321 - proto := "tcp" - - err := natChain.Forward(Insert, ip, port, proto, dstAddr, dstPort) - if err != nil { - t.Fatal(err) - } - - dnatRule := []string{ - "-d", ip.String(), - "-p", proto, - "--dport", strconv.Itoa(port), - "-j", "DNAT", - "--to-destination", dstAddr + ":" + strconv.Itoa(dstPort), - } - - if !Exists(natChain.Table, natChain.Name, dnatRule...) { - t.Fatalf("DNAT rule does not exist") - } - - filterRule := []string{ - "!", "-i", filterChain.Bridge, - "-o", filterChain.Bridge, - "-d", dstAddr, - "-p", proto, - "--dport", strconv.Itoa(dstPort), - "-j", "ACCEPT", - } - - if !Exists(filterChain.Table, filterChain.Name, filterRule...) { - t.Fatalf("filter rule does not exist") - } - - masqRule := []string{ - "-d", dstAddr, - "-s", dstAddr, - "-p", proto, - "--dport", strconv.Itoa(dstPort), - "-j", "MASQUERADE", - } - - if !Exists(natChain.Table, "POSTROUTING", masqRule...) { - t.Fatalf("MASQUERADE rule does not exist") - } -} - -func TestLink(t *testing.T) { - var err error - - ip1 := net.ParseIP("192.168.1.1") - ip2 := net.ParseIP("192.168.1.2") - port := 1234 - proto := "tcp" - - err = filterChain.Link(Append, ip1, ip2, port, proto) - if err != nil { - t.Fatal(err) - } - - rule1 := []string{ - "-i", filterChain.Bridge, - "-o", filterChain.Bridge, - "-p", proto, - "-s", ip1.String(), - "-d", ip2.String(), - "--dport", strconv.Itoa(port), - "-j", "ACCEPT"} - - if !Exists(filterChain.Table, filterChain.Name, rule1...) { - t.Fatalf("rule1 does not exist") - } - - rule2 := []string{ - "-i", filterChain.Bridge, - "-o", filterChain.Bridge, - "-p", proto, - "-s", ip2.String(), - "-d", ip1.String(), - "--sport", strconv.Itoa(port), - "-j", "ACCEPT"} - - if !Exists(filterChain.Table, filterChain.Name, rule2...) { - t.Fatalf("rule2 does not exist") - } -} - -func TestPrerouting(t *testing.T) { - args := []string{ - "-i", "lo", - "-d", "192.168.1.1"} - - err := natChain.Prerouting(Insert, args...) - if err != nil { - t.Fatal(err) - } - - rule := []string{ - "-j", natChain.Name} - - rule = append(rule, args...) - - if !Exists(natChain.Table, "PREROUTING", rule...) { - t.Fatalf("rule does not exist") - } - - delRule := append([]string{"-D", "PREROUTING", "-t", string(Nat)}, rule...) - if _, err = Raw(delRule...); err != nil { - t.Fatal(err) - } -} - -func TestOutput(t *testing.T) { - args := []string{ - "-o", "lo", - "-d", "192.168.1.1"} - - err := natChain.Output(Insert, args...) - if err != nil { - t.Fatal(err) - } - - rule := []string{ - "-j", natChain.Name} - - rule = append(rule, args...) - - if !Exists(natChain.Table, "OUTPUT", rule...) { - t.Fatalf("rule does not exist") - } - - delRule := append([]string{"-D", "OUTPUT", "-t", - string(natChain.Table)}, rule...) - if _, err = Raw(delRule...); err != nil { - t.Fatal(err) - } -} - -func TestConcurrencyWithWait(t *testing.T) { - RunConcurrencyTest(t, true) -} - -func TestConcurrencyNoWait(t *testing.T) { - RunConcurrencyTest(t, false) -} - -// Runs 10 concurrent rule additions. This will fail if iptables -// is actually invoked simultaneously without --wait. -// Note that if iptables does not support the xtable lock on this -// system, then allowXlock has no effect -- it will always be off. -func RunConcurrencyTest(t *testing.T, allowXlock bool) { - var wg sync.WaitGroup - - if !allowXlock && supportsXlock { - supportsXlock = false - defer func() { supportsXlock = true }() - } - - ip := net.ParseIP("192.168.1.1") - port := 1234 - dstAddr := "172.17.0.1" - dstPort := 4321 - proto := "tcp" - - for i := 0; i < 10; i++ { - wg.Add(1) - go func() { - defer wg.Done() - err := natChain.Forward(Append, ip, port, proto, dstAddr, dstPort) - if err != nil { - t.Fatal(err) - } - }() - } - wg.Wait() -} - -func TestCleanup(t *testing.T) { - var err error - var rules []byte - - // Cleanup filter/FORWARD first otherwise output of iptables-save is dirty - link := []string{"-t", string(filterChain.Table), - string(Delete), "FORWARD", - "-o", filterChain.Bridge, - "-j", filterChain.Name} - if _, err = Raw(link...); err != nil { - t.Fatal(err) - } - filterChain.Remove() - - err = RemoveExistingChain(chainName, Nat) - if err != nil { - t.Fatal(err) - } - - rules, err = exec.Command("iptables-save").Output() - if err != nil { - t.Fatal(err) - } - if strings.Contains(string(rules), chainName) { - t.Fatalf("Removing chain failed. %s found in iptables-save", chainName) - } -} diff --git a/resolvconf/README.md b/resolvconf/README.md deleted file mode 100644 index cdda554..0000000 --- a/resolvconf/README.md +++ /dev/null @@ -1 +0,0 @@ -Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf diff --git a/resolvconf/dns/resolvconf.go b/resolvconf/dns/resolvconf.go deleted file mode 100644 index c2f23ef..0000000 --- a/resolvconf/dns/resolvconf.go +++ /dev/null @@ -1,16 +0,0 @@ -package dns - -import ( - "regexp" -) - -const IpLocalhost = `((127\.([0-9]{1,3}.){2}[0-9]{1,3})|(::1))` - -var localhostIPRegexp = regexp.MustCompile(IpLocalhost) - -// IsLocalhost returns true if ip matches the localhost IP regular expression. -// Used for determining if nameserver settings are being passed which are -// localhost addresses -func IsLocalhost(ip string) bool { - return localhostIPRegexp.MatchString(ip) -} diff --git a/resolvconf/resolvconf.go b/resolvconf/resolvconf.go deleted file mode 100644 index 907934c..0000000 --- a/resolvconf/resolvconf.go +++ /dev/null @@ -1,187 +0,0 @@ -// Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf -package resolvconf - -import ( - "bytes" - "io/ioutil" - "regexp" - "strings" - "sync" - - "github.com/Sirupsen/logrus" - "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/resolvconf/dns" -) - -var ( - // Note: the default IPv4 & IPv6 resolvers are set to Google's Public DNS - defaultIPv4Dns = []string{"nameserver 8.8.8.8", "nameserver 8.8.4.4"} - defaultIPv6Dns = []string{"nameserver 2001:4860:4860::8888", "nameserver 2001:4860:4860::8844"} - ipv4NumBlock = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)` - ipv4Address = `(` + ipv4NumBlock + `\.){3}` + ipv4NumBlock - // This is not an IPv6 address verifier as it will accept a super-set of IPv6, and also - // will *not match* IPv4-Embedded IPv6 Addresses (RFC6052), but that and other variants - // -- e.g. other link-local types -- either won't work in containers or are unnecessary. - // For readability and sufficiency for Docker purposes this seemed more reasonable than a - // 1000+ character regexp with exact and complete IPv6 validation - ipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})` - - localhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\s+` + dns.IpLocalhost + `\s*\n*`) - nsIPv6Regexp = regexp.MustCompile(`(?m)^nameserver\s+` + ipv6Address + `\s*\n*`) - nsRegexp = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`) - searchRegexp = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`) -) - -var lastModified struct { - sync.Mutex - sha256 string - contents []byte -} - -// Get returns the contents of /etc/resolv.conf -func Get() ([]byte, error) { - resolv, err := ioutil.ReadFile("/etc/resolv.conf") - if err != nil { - return nil, err - } - return resolv, nil -} - -// GetIfChanged retrieves the host /etc/resolv.conf file, checks against the last hash -// and, if modified since last check, returns the bytes and new hash. -// This feature is used by the resolv.conf updater for containers -func GetIfChanged() ([]byte, string, error) { - lastModified.Lock() - defer lastModified.Unlock() - - resolv, err := ioutil.ReadFile("/etc/resolv.conf") - if err != nil { - return nil, "", err - } - newHash, err := ioutils.HashData(bytes.NewReader(resolv)) - if err != nil { - return nil, "", err - } - if lastModified.sha256 != newHash { - lastModified.sha256 = newHash - lastModified.contents = resolv - return resolv, newHash, nil - } - // nothing changed, so return no data - return nil, "", nil -} - -// GetLastModified retrieves the last used contents and hash of the host resolv.conf. -// Used by containers updating on restart -func GetLastModified() ([]byte, string) { - lastModified.Lock() - defer lastModified.Unlock() - - return lastModified.contents, lastModified.sha256 -} - -// FilterResolvDns cleans up the config in resolvConf. It has two main jobs: -// 1. It looks for localhost (127.*|::1) entries in the provided -// resolv.conf, removing local nameserver entries, and, if the resulting -// cleaned config has no defined nameservers left, adds default DNS entries -// 2. Given the caller provides the enable/disable state of IPv6, the filter -// code will remove all IPv6 nameservers if it is not enabled for containers -// -// It returns a boolean to notify the caller if changes were made at all -func FilterResolvDns(resolvConf []byte, ipv6Enabled bool) ([]byte, bool) { - changed := false - cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{}) - // if IPv6 is not enabled, also clean out any IPv6 address nameserver - if !ipv6Enabled { - cleanedResolvConf = nsIPv6Regexp.ReplaceAll(cleanedResolvConf, []byte{}) - } - // if the resulting resolvConf has no more nameservers defined, add appropriate - // default DNS servers for IPv4 and (optionally) IPv6 - if len(GetNameservers(cleanedResolvConf)) == 0 { - logrus.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers : %v", defaultIPv4Dns) - dns := defaultIPv4Dns - if ipv6Enabled { - logrus.Infof("IPv6 enabled; Adding default IPv6 external servers : %v", defaultIPv6Dns) - dns = append(dns, defaultIPv6Dns...) - } - cleanedResolvConf = append(cleanedResolvConf, []byte("\n"+strings.Join(dns, "\n"))...) - } - if !bytes.Equal(resolvConf, cleanedResolvConf) { - changed = true - } - return cleanedResolvConf, changed -} - -// getLines parses input into lines and strips away comments. -func getLines(input []byte, commentMarker []byte) [][]byte { - lines := bytes.Split(input, []byte("\n")) - var output [][]byte - for _, currentLine := range lines { - var commentIndex = bytes.Index(currentLine, commentMarker) - if commentIndex == -1 { - output = append(output, currentLine) - } else { - output = append(output, currentLine[:commentIndex]) - } - } - return output -} - -// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf -func GetNameservers(resolvConf []byte) []string { - nameservers := []string{} - for _, line := range getLines(resolvConf, []byte("#")) { - var ns = nsRegexp.FindSubmatch(line) - if len(ns) > 0 { - nameservers = append(nameservers, string(ns[1])) - } - } - return nameservers -} - -// GetNameserversAsCIDR returns nameservers (if any) listed in -// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32") -// This function's output is intended for net.ParseCIDR -func GetNameserversAsCIDR(resolvConf []byte) []string { - nameservers := []string{} - for _, nameserver := range GetNameservers(resolvConf) { - nameservers = append(nameservers, nameserver+"/32") - } - return nameservers -} - -// GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf -// If more than one search line is encountered, only the contents of the last -// one is returned. -func GetSearchDomains(resolvConf []byte) []string { - domains := []string{} - for _, line := range getLines(resolvConf, []byte("#")) { - match := searchRegexp.FindSubmatch(line) - if match == nil { - continue - } - domains = strings.Fields(string(match[1])) - } - return domains -} - -// Build writes a configuration file to path containing a "nameserver" entry -// for every element in dns, and a "search" entry for every element in -// dnsSearch. -func Build(path string, dns, dnsSearch []string) error { - content := bytes.NewBuffer(nil) - for _, dns := range dns { - if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil { - return err - } - } - if len(dnsSearch) > 0 { - if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." { - if _, err := content.WriteString("search " + searchString + "\n"); err != nil { - return err - } - } - } - - return ioutil.WriteFile(path, content.Bytes(), 0644) -} diff --git a/resolvconf/resolvconf_test.go b/resolvconf/resolvconf_test.go deleted file mode 100644 index b0647e7..0000000 --- a/resolvconf/resolvconf_test.go +++ /dev/null @@ -1,238 +0,0 @@ -package resolvconf - -import ( - "bytes" - "io/ioutil" - "os" - "testing" -) - -func TestGet(t *testing.T) { - resolvConfUtils, err := Get() - if err != nil { - t.Fatal(err) - } - resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf") - if err != nil { - t.Fatal(err) - } - if string(resolvConfUtils) != string(resolvConfSystem) { - t.Fatalf("/etc/resolv.conf and GetResolvConf have different content.") - } -} - -func TestGetNameservers(t *testing.T) { - for resolv, result := range map[string][]string{` -nameserver 1.2.3.4 -nameserver 40.3.200.10 -search example.com`: {"1.2.3.4", "40.3.200.10"}, - `search example.com`: {}, - `nameserver 1.2.3.4 -search example.com -nameserver 4.30.20.100`: {"1.2.3.4", "4.30.20.100"}, - ``: {}, - ` nameserver 1.2.3.4 `: {"1.2.3.4"}, - `search example.com -nameserver 1.2.3.4 -#nameserver 4.3.2.1`: {"1.2.3.4"}, - `search example.com -nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4"}, - } { - test := GetNameservers([]byte(resolv)) - if !strSlicesEqual(test, result) { - t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv) - } - } -} - -func TestGetNameserversAsCIDR(t *testing.T) { - for resolv, result := range map[string][]string{` -nameserver 1.2.3.4 -nameserver 40.3.200.10 -search example.com`: {"1.2.3.4/32", "40.3.200.10/32"}, - `search example.com`: {}, - `nameserver 1.2.3.4 -search example.com -nameserver 4.30.20.100`: {"1.2.3.4/32", "4.30.20.100/32"}, - ``: {}, - ` nameserver 1.2.3.4 `: {"1.2.3.4/32"}, - `search example.com -nameserver 1.2.3.4 -#nameserver 4.3.2.1`: {"1.2.3.4/32"}, - `search example.com -nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4/32"}, - } { - test := GetNameserversAsCIDR([]byte(resolv)) - if !strSlicesEqual(test, result) { - t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv) - } - } -} - -func TestGetSearchDomains(t *testing.T) { - for resolv, result := range map[string][]string{ - `search example.com`: {"example.com"}, - `search example.com # ignored`: {"example.com"}, - ` search example.com `: {"example.com"}, - ` search example.com # ignored`: {"example.com"}, - `search foo.example.com example.com`: {"foo.example.com", "example.com"}, - ` search foo.example.com example.com `: {"foo.example.com", "example.com"}, - ` search foo.example.com example.com # ignored`: {"foo.example.com", "example.com"}, - ``: {}, - `# ignored`: {}, - `nameserver 1.2.3.4 -search foo.example.com example.com`: {"foo.example.com", "example.com"}, - `nameserver 1.2.3.4 -search dup1.example.com dup2.example.com -search foo.example.com example.com`: {"foo.example.com", "example.com"}, - `nameserver 1.2.3.4 -search foo.example.com example.com -nameserver 4.30.20.100`: {"foo.example.com", "example.com"}, - } { - test := GetSearchDomains([]byte(resolv)) - if !strSlicesEqual(test, result) { - t.Fatalf("Wrong search domain string {%s} should be %v. Input: %s", test, result, resolv) - } - } -} - -func strSlicesEqual(a, b []string) bool { - if len(a) != len(b) { - return false - } - - for i, v := range a { - if v != b[i] { - return false - } - } - - return true -} - -func TestBuild(t *testing.T) { - file, err := ioutil.TempFile("", "") - if err != nil { - t.Fatal(err) - } - defer os.Remove(file.Name()) - - err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"search1"}) - if err != nil { - t.Fatal(err) - } - - content, err := ioutil.ReadFile(file.Name()) - if err != nil { - t.Fatal(err) - } - - if expected := "nameserver ns1\nnameserver ns2\nnameserver ns3\nsearch search1\n"; !bytes.Contains(content, []byte(expected)) { - t.Fatalf("Expected to find '%s' got '%s'", expected, content) - } -} - -func TestBuildWithZeroLengthDomainSearch(t *testing.T) { - file, err := ioutil.TempFile("", "") - if err != nil { - t.Fatal(err) - } - defer os.Remove(file.Name()) - - err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"."}) - if err != nil { - t.Fatal(err) - } - - content, err := ioutil.ReadFile(file.Name()) - if err != nil { - t.Fatal(err) - } - - if expected := "nameserver ns1\nnameserver ns2\nnameserver ns3\n"; !bytes.Contains(content, []byte(expected)) { - t.Fatalf("Expected to find '%s' got '%s'", expected, content) - } - if notExpected := "search ."; bytes.Contains(content, []byte(notExpected)) { - t.Fatalf("Expected to not find '%s' got '%s'", notExpected, content) - } -} - -func TestFilterResolvDns(t *testing.T) { - ns0 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\n" - - if result, _ := FilterResolvDns([]byte(ns0), false); result != nil { - if ns0 != string(result) { - t.Fatalf("Failed No Localhost: expected \n<%s> got \n<%s>", ns0, string(result)) - } - } - - ns1 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\nnameserver 127.0.0.1\n" - if result, _ := FilterResolvDns([]byte(ns1), false); result != nil { - if ns0 != string(result) { - t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result)) - } - } - - ns1 = "nameserver 10.16.60.14\nnameserver 127.0.0.1\nnameserver 10.16.60.21\n" - if result, _ := FilterResolvDns([]byte(ns1), false); result != nil { - if ns0 != string(result) { - t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result)) - } - } - - ns1 = "nameserver 127.0.1.1\nnameserver 10.16.60.14\nnameserver 10.16.60.21\n" - if result, _ := FilterResolvDns([]byte(ns1), false); result != nil { - if ns0 != string(result) { - t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result)) - } - } - - ns1 = "nameserver ::1\nnameserver 10.16.60.14\nnameserver 127.0.2.1\nnameserver 10.16.60.21\n" - if result, _ := FilterResolvDns([]byte(ns1), false); result != nil { - if ns0 != string(result) { - t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result)) - } - } - - ns1 = "nameserver 10.16.60.14\nnameserver ::1\nnameserver 10.16.60.21\nnameserver ::1" - if result, _ := FilterResolvDns([]byte(ns1), false); result != nil { - if ns0 != string(result) { - t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result)) - } - } - - // with IPv6 disabled (false param), the IPv6 nameserver should be removed - ns1 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\nnameserver ::1" - if result, _ := FilterResolvDns([]byte(ns1), false); result != nil { - if ns0 != string(result) { - t.Fatalf("Failed Localhost+IPv6 off: expected \n<%s> got \n<%s>", ns0, string(result)) - } - } - - // with IPv6 enabled, the IPv6 nameserver should be preserved - ns0 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\n" - ns1 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\nnameserver ::1" - if result, _ := FilterResolvDns([]byte(ns1), true); result != nil { - if ns0 != string(result) { - t.Fatalf("Failed Localhost+IPv6 on: expected \n<%s> got \n<%s>", ns0, string(result)) - } - } - - // with IPv6 enabled, and no non-localhost servers, Google defaults (both IPv4+IPv6) should be added - ns0 = "\nnameserver 8.8.8.8\nnameserver 8.8.4.4\nnameserver 2001:4860:4860::8888\nnameserver 2001:4860:4860::8844" - ns1 = "nameserver 127.0.0.1\nnameserver ::1\nnameserver 127.0.2.1" - if result, _ := FilterResolvDns([]byte(ns1), true); result != nil { - if ns0 != string(result) { - t.Fatalf("Failed no Localhost+IPv6 enabled: expected \n<%s> got \n<%s>", ns0, string(result)) - } - } - - // with IPv6 disabled, and no non-localhost servers, Google defaults (only IPv4) should be added - ns0 = "\nnameserver 8.8.8.8\nnameserver 8.8.4.4" - ns1 = "nameserver 127.0.0.1\nnameserver ::1\nnameserver 127.0.2.1" - if result, _ := FilterResolvDns([]byte(ns1), false); result != nil { - if ns0 != string(result) { - t.Fatalf("Failed no Localhost+IPv6 enabled: expected \n<%s> got \n<%s>", ns0, string(result)) - } - } -}