Removed dead code from docker after libnetwork integration
As part of this some generic packages like iptables, etchosts and resolvconf have also been moved to libnetwork. Even though they can still be consumed in a generic fashion they will reside and be maintained from within the libnetwork project. Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
This commit is contained in:
parent
0b092ef5cf
commit
24fd826fc0
10 changed files with 0 additions and 1439 deletions
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue