浏览代码

Merge branch 'master' into discordianfish-https_client

Docker-DCO-1.1-Signed-off-by: Victor Vieux <victor.vieux@docker.com> (github: vieux)
Victor Vieux 11 年之前
父节点
当前提交
aa63ece2ec

+ 4 - 0
daemonconfig/config.go

@@ -18,6 +18,7 @@ type Config struct {
 	Root                        string
 	AutoRestart                 bool
 	Dns                         []string
+	DnsSearch                   []string
 	EnableIptables              bool
 	EnableIpForward             bool
 	DefaultIp                   net.IP
@@ -49,6 +50,9 @@ func ConfigFromJob(job *engine.Job) *Config {
 	if dns := job.GetenvList("Dns"); dns != nil {
 		config.Dns = dns
 	}
+	if dnsSearch := job.GetenvList("DnsSearch"); dnsSearch != nil {
+		config.DnsSearch = dnsSearch
+	}
 	if mtu := job.GetenvInt("Mtu"); mtu != 0 {
 		config.Mtu = mtu
 	} else {

+ 3 - 0
docker/docker.go

@@ -48,6 +48,7 @@ func main() {
 		flSocketGroup        = flag.String([]string{"G", "-group"}, "docker", "Group to assign the unix socket specified by -H when running in daemon mode; use '' (the empty string) to disable setting of a group")
 		flEnableCors         = flag.Bool([]string{"#api-enable-cors", "-api-enable-cors"}, false, "Enable CORS headers in the remote API")
 		flDns                = opts.NewListOpts(opts.ValidateIp4Address)
+		flDnsSearch          = opts.NewListOpts(opts.ValidateDomain)
 		flEnableIptables     = flag.Bool([]string{"#iptables", "-iptables"}, true, "Enable Docker's addition of iptables rules")
 		flEnableIpForward    = flag.Bool([]string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward")
 		flDefaultIp          = flag.String([]string{"#ip", "-ip"}, "0.0.0.0", "Default IP address to use when binding container ports")
@@ -63,6 +64,7 @@ func main() {
 		flKey                = flag.String([]string{"-tlskey"}, dockerConfDir+defaultKeyFile, "Path to TLS key file")
 	)
 	flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers")
+	flag.Var(&flDnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains")
 	flag.Var(&flHosts, []string{"H", "-host"}, "tcp://host:port, unix://path/to/socket, fd://* or fd://socketfd to use in daemon mode. Multiple sockets can be specified")
 
 	flag.Parse()
@@ -134,6 +136,7 @@ func main() {
 			job.Setenv("Root", realRoot)
 			job.SetenvBool("AutoRestart", *flAutoRestart)
 			job.SetenvList("Dns", flDns.GetAll())
+			job.SetenvList("DnsSearch", flDnsSearch.GetAll())
 			job.SetenvBool("EnableIptables", *flEnableIptables)
 			job.SetenvBool("EnableIpForward", *flEnableIpForward)
 			job.Setenv("BridgeIface", *bridgeName)

+ 1 - 0
docs/sources/reference/api/docker_remote_api_v1.10.rst

@@ -136,6 +136,7 @@ Create a container
                 },
                 "VolumesFrom":"",
                 "WorkingDir":"",
+                "DisableNetwork": false,
                 "ExposedPorts":{
                         "22/tcp": {}
                 }

+ 7 - 2
docs/sources/reference/commandline/cli.rst

@@ -77,6 +77,7 @@ Commands
       --bip="": Use this CIDR notation address for the network bridge's IP, not compatible with -b
       -d, --daemon=false: Enable daemon mode
       --dns=[]: Force docker to use specific DNS servers
+      --dns-search=[]: Force Docker to use specific DNS search domains
       -g, --graph="/var/lib/docker": Path to use as the root of the docker runtime
       --icc=true: Enable inter-container communication
       --ip="0.0.0.0": Default IP address to use when binding container ports
@@ -101,6 +102,8 @@ To force Docker to use devicemapper as the storage driver, use ``docker -d -s de
 
 To set the DNS server for all Docker containers, use ``docker -d --dns 8.8.8.8``.
 
+To set the DNS search domain for all Docker containers, use ``docker -d --dns-search example.com``.
+
 To run the daemon with debug output, use ``docker -d -D``.
 
 To use lxc as the execution driver, use ``docker -d -e lxc``.
@@ -401,6 +404,7 @@ not overridden in the JSON hash will be merged in.
       "VolumesFrom" : "",
       "Cmd" : ["cat", "-e", "/etc/resolv.conf"],
       "Dns" : ["8.8.8.8", "8.8.4.4"],
+      "DnsSearch" : ["example.com"],
       "MemorySwap" : 0,
       "AttachStdin" : false,
       "AttachStderr" : false,
@@ -1136,6 +1140,7 @@ image is removed.
       -t, --tty=false: Allocate a pseudo-tty
       -u, --user="": Username or UID
       --dns=[]: Set custom dns servers for the container
+      --dns-search=[]: Set custom DNS search domains for the container
       -v, --volume=[]: Create a bind mount to a directory or file with: [host-path]:[container-path]:[rw|ro]. If a directory "container-path" is missing, then docker creates a new volume.
       --volumes-from="": Mount all volumes from the given container(s)
       --entrypoint="": Overwrite the default entrypoint set by the image
@@ -1293,7 +1298,7 @@ A complete example
    $ sudo docker run -d --name static static-web-files sh
    $ sudo docker run -d --expose=8098 --name riak riakserver
    $ sudo docker run -d -m 100m -e DEVELOPMENT=1 -e BRANCH=example-code -v $(pwd):/app/bin:ro --name app appserver
-   $ sudo docker run -d -p 1443:443 --dns=dns.dev.org -v /var/log/httpd --volumes-from static --link riak --link app -h www.sven.dev.org --name web webserver
+   $ sudo docker run -d -p 1443:443 --dns=dns.dev.org --dns-search=dev.org -v /var/log/httpd --volumes-from static --link riak --link app -h www.sven.dev.org --name web webserver
    $ sudo docker run -t -i --rm --volumes-from web -w /var/log/httpd busybox tail -f access.log
 
 This example shows 5 containers that might be set up to test a web application change:
@@ -1301,7 +1306,7 @@ This example shows 5 containers that might be set up to test a web application c
 1. Start a pre-prepared volume image ``static-web-files`` (in the background) that has CSS, image and static HTML in it, (with a ``VOLUME`` instruction in the ``Dockerfile`` to allow the web server to use those files);
 2. Start a pre-prepared ``riakserver`` image, give the container name ``riak`` and expose port ``8098`` to any containers that link to it;
 3. Start the ``appserver`` image, restricting its memory usage to 100MB, setting two environment variables ``DEVELOPMENT`` and ``BRANCH`` and bind-mounting the current directory (``$(pwd)``) in the container in read-only mode as ``/app/bin``;
-4. Start the ``webserver``, mapping port ``443`` in the container to port ``1443`` on the Docker server, setting the DNS server to ``dns.dev.org``, creating a volume to put the log files into (so we can access it from another container), then importing the files from the volume exposed by the ``static`` container, and linking to all exposed ports from ``riak`` and ``app``. Lastly, we set the hostname to ``web.sven.dev.org`` so its consistent with the pre-generated SSL certificate;
+4. Start the ``webserver``, mapping port ``443`` in the container to port ``1443`` on the Docker server, setting the DNS server to ``dns.dev.org`` and DNS search domain to ``dev.org``, creating a volume to put the log files into (so we can access it from another container), then importing the files from the volume exposed by the ``static`` container, and linking to all exposed ports from ``riak`` and ``app``. Lastly, we set the hostname to ``web.sven.dev.org`` so its consistent with the pre-generated SSL certificate;
 5. Finally, we create a container that runs ``tail -f access.log`` using the logs volume from the ``web`` container, setting the workdir to ``/var/log/httpd``. The ``--rm`` option means that when the container exits, the container's layer is removed.
 
 

+ 13 - 0
opts/opts.go

@@ -136,3 +136,16 @@ func ValidateIp4Address(val string) (string, error) {
 	}
 	return "", fmt.Errorf("%s is not an ip4 address", val)
 }
+
+func ValidateDomain(val string) (string, error) {
+	alpha := regexp.MustCompile(`[a-zA-Z]`)
+	if alpha.FindString(val) == "" {
+		return "", fmt.Errorf("%s is not a valid domain", val)
+	}
+	re := regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`)
+	ns := re.FindSubmatch([]byte(val))
+	if len(ns) > 0 {
+		return string(ns[1]), nil
+	}
+	return "", fmt.Errorf("%s is not a valid domain", val)
+}

+ 54 - 0
opts/opts_test.go

@@ -22,3 +22,57 @@ func TestValidateIP4(t *testing.T) {
 	}
 
 }
+
+func TestValidateDomain(t *testing.T) {
+	valid := []string{
+		`a`,
+		`a.`,
+		`1.foo`,
+		`17.foo`,
+		`foo.bar`,
+		`foo.bar.baz`,
+		`foo.bar.`,
+		`foo.bar.baz`,
+		`foo1.bar2`,
+		`foo1.bar2.baz`,
+		`1foo.2bar.`,
+		`1foo.2bar.baz`,
+		`foo-1.bar-2`,
+		`foo-1.bar-2.baz`,
+		`foo-1.bar-2.`,
+		`foo-1.bar-2.baz`,
+		`1-foo.2-bar`,
+		`1-foo.2-bar.baz`,
+		`1-foo.2-bar.`,
+		`1-foo.2-bar.baz`,
+	}
+
+	invalid := []string{
+		``,
+		`.`,
+		`17`,
+		`17.`,
+		`.17`,
+		`17-.`,
+		`17-.foo`,
+		`.foo`,
+		`foo-.bar`,
+		`-foo.bar`,
+		`foo.bar-`,
+		`foo.bar-.baz`,
+		`foo.-bar`,
+		`foo.-bar.baz`,
+	}
+
+	for _, domain := range valid {
+		if ret, err := ValidateDomain(domain); err != nil || ret == "" {
+			t.Fatalf("ValidateDomain(`"+domain+"`) got %s %s", ret, err)
+		}
+	}
+
+	for _, domain := range invalid {
+		if ret, err := ValidateDomain(domain); err == nil || ret != "" {
+			t.Fatalf("ValidateDomain(`"+domain+"`) got %s %s", ret, err)
+		}
+	}
+}

+ 6 - 0
runconfig/compare.go

@@ -20,6 +20,7 @@ func Compare(a, b *Config) bool {
 	}
 	if len(a.Cmd) != len(b.Cmd) ||
 		len(a.Dns) != len(b.Dns) ||
+		len(a.DnsSearch) != len(b.DnsSearch) ||
 		len(a.Env) != len(b.Env) ||
 		len(a.PortSpecs) != len(b.PortSpecs) ||
 		len(a.ExposedPorts) != len(b.ExposedPorts) ||
@@ -38,6 +39,11 @@ func Compare(a, b *Config) bool {
 			return false
 		}
 	}
+	for i := 0; i < len(a.DnsSearch); i++ {
+		if a.DnsSearch[i] != b.DnsSearch[i] {
+			return false
+		}
+	}
 	for i := 0; i < len(a.Env); i++ {
 		if a.Env[i] != b.Env[i] {
 			return false

+ 4 - 0
runconfig/config.go

@@ -26,6 +26,7 @@ type Config struct {
 	Env             []string
 	Cmd             []string
 	Dns             []string
+	DnsSearch       []string
 	Image           string // Name of the image as it was passed by the operator (eg. could be symbolic)
 	Volumes         map[string]struct{}
 	VolumesFrom     string
@@ -68,6 +69,9 @@ func ContainerConfigFromJob(job *engine.Job) *Config {
 	if Dns := job.GetenvList("Dns"); Dns != nil {
 		config.Dns = Dns
 	}
+	if DnsSearch := job.GetenvList("DnsSearch"); DnsSearch != nil {
+		config.DnsSearch = DnsSearch
+	}
 	if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil {
 		config.Entrypoint = Entrypoint
 	}

+ 16 - 0
runconfig/config_test.go

@@ -164,6 +164,7 @@ func TestCompare(t *testing.T) {
 	volumes1["/test1"] = struct{}{}
 	config1 := Config{
 		Dns:         []string{"1.1.1.1", "2.2.2.2"},
+		DnsSearch:   []string{"foo", "bar"},
 		PortSpecs:   []string{"1111:1111", "2222:2222"},
 		Env:         []string{"VAR1=1", "VAR2=2"},
 		VolumesFrom: "11111111",
@@ -171,6 +172,7 @@ func TestCompare(t *testing.T) {
 	}
 	config2 := Config{
 		Dns:         []string{"0.0.0.0", "2.2.2.2"},
+		DnsSearch:   []string{"foo", "bar"},
 		PortSpecs:   []string{"1111:1111", "2222:2222"},
 		Env:         []string{"VAR1=1", "VAR2=2"},
 		VolumesFrom: "11111111",
@@ -178,6 +180,7 @@ func TestCompare(t *testing.T) {
 	}
 	config3 := Config{
 		Dns:         []string{"1.1.1.1", "2.2.2.2"},
+		DnsSearch:   []string{"foo", "bar"},
 		PortSpecs:   []string{"0000:0000", "2222:2222"},
 		Env:         []string{"VAR1=1", "VAR2=2"},
 		VolumesFrom: "11111111",
@@ -185,6 +188,7 @@ func TestCompare(t *testing.T) {
 	}
 	config4 := Config{
 		Dns:         []string{"1.1.1.1", "2.2.2.2"},
+		DnsSearch:   []string{"foo", "bar"},
 		PortSpecs:   []string{"0000:0000", "2222:2222"},
 		Env:         []string{"VAR1=1", "VAR2=2"},
 		VolumesFrom: "22222222",
@@ -194,11 +198,20 @@ func TestCompare(t *testing.T) {
 	volumes2["/test2"] = struct{}{}
 	config5 := Config{
 		Dns:         []string{"1.1.1.1", "2.2.2.2"},
+		DnsSearch:   []string{"foo", "bar"},
 		PortSpecs:   []string{"0000:0000", "2222:2222"},
 		Env:         []string{"VAR1=1", "VAR2=2"},
 		VolumesFrom: "11111111",
 		Volumes:     volumes2,
 	}
+	config6 := Config{
+		Dns:         []string{"1.1.1.1", "2.2.2.2"},
+		DnsSearch:   []string{"foos", "bars"},
+		PortSpecs:   []string{"1111:1111", "2222:2222"},
+		Env:         []string{"VAR1=1", "VAR2=2"},
+		VolumesFrom: "11111111",
+		Volumes:     volumes1,
+	}
 	if Compare(&config1, &config2) {
 		t.Fatalf("Compare should return false, Dns are different")
 	}
@@ -211,6 +224,9 @@ func TestCompare(t *testing.T) {
 	if Compare(&config1, &config5) {
 		t.Fatalf("Compare should return false, Volumes are different")
 	}
+	if Compare(&config1, &config6) {
+		t.Fatalf("Compare should return false, DnsSearch are different")
+	}
 	if !Compare(&config1, &config1) {
 		t.Fatalf("Compare should return true")
 	}

+ 6 - 0
runconfig/merge.go

@@ -100,6 +100,12 @@ func Merge(userConf, imageConf *Config) error {
 		//duplicates aren't an issue here
 		userConf.Dns = append(userConf.Dns, imageConf.Dns...)
 	}
+	if userConf.DnsSearch == nil || len(userConf.DnsSearch) == 0 {
+		userConf.DnsSearch = imageConf.DnsSearch
+	} else {
+		//duplicates aren't an issue here
+		userConf.DnsSearch = append(userConf.DnsSearch, imageConf.DnsSearch...)
+	}
 	if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 {
 		userConf.Entrypoint = imageConf.Entrypoint
 	}

+ 3 - 0
runconfig/parse.go

@@ -42,6 +42,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 		flPublish     opts.ListOpts
 		flExpose      opts.ListOpts
 		flDns         opts.ListOpts
+		flDnsSearch   = opts.NewListOpts(opts.ValidateDomain)
 		flVolumesFrom opts.ListOpts
 		flLxcOpts     opts.ListOpts
 
@@ -73,6 +74,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 	cmd.Var(&flPublish, []string{"p", "-publish"}, fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", nat.PortSpecTemplateFormat))
 	cmd.Var(&flExpose, []string{"#expose", "-expose"}, "Expose a port from the container without publishing it to your host")
 	cmd.Var(&flDns, []string{"#dns", "-dns"}, "Set custom dns servers")
+	cmd.Var(&flDnsSearch, []string{"-dns-search"}, "Set custom dns search domains")
 	cmd.Var(&flVolumesFrom, []string{"#volumes-from", "-volumes-from"}, "Mount volumes from the specified container(s)")
 	cmd.Var(&flLxcOpts, []string{"#lxc-conf", "-lxc-conf"}, "Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
 
@@ -196,6 +198,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 		Env:             flEnv.GetAll(),
 		Cmd:             runCmd,
 		Dns:             flDns.GetAll(),
+		DnsSearch:       flDnsSearch.GetAll(),
 		Image:           image,
 		Volumes:         flVolumes.GetMap(),
 		VolumesFrom:     strings.Join(flVolumesFrom.GetAll(), ","),

+ 14 - 3
runtime/runtime.go

@@ -493,13 +493,19 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe
 	}
 
 	// If custom dns exists, then create a resolv.conf for the container
-	if len(config.Dns) > 0 || len(runtime.config.Dns) > 0 {
-		var dns []string
+	if len(config.Dns) > 0 || len(runtime.config.Dns) > 0 || len(config.DnsSearch) > 0 || len(runtime.config.DnsSearch) > 0 {
+		dns := utils.GetNameservers(resolvConf)
+		dnsSearch := utils.GetSearchDomains(resolvConf)
 		if len(config.Dns) > 0 {
 			dns = config.Dns
-		} else {
+		} else if len(runtime.config.Dns) > 0 {
 			dns = runtime.config.Dns
 		}
+		if len(config.DnsSearch) > 0 {
+			dnsSearch = config.DnsSearch
+		} else if len(runtime.config.DnsSearch) > 0 {
+			dnsSearch = runtime.config.DnsSearch
+		}
 		container.ResolvConfPath = path.Join(container.root, "resolv.conf")
 		f, err := os.Create(container.ResolvConfPath)
 		if err != nil {
@@ -511,6 +517,11 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe
 				return nil, nil, err
 			}
 		}
+		if len(dnsSearch) > 0 {
+			if _, err := f.Write([]byte("search " + strings.Join(dnsSearch, " ") + "\n")); err != nil {
+				return nil, nil, err
+			}
+		}
 	} else {
 		container.ResolvConfPath = "/etc/resolv.conf"
 	}

+ 48 - 24
utils/utils.go

@@ -731,54 +731,78 @@ func GetResolvConf() ([]byte, error) {
 // CheckLocalDns looks into the /etc/resolv.conf,
 // it returns true if there is a local nameserver or if there is no nameserver.
 func CheckLocalDns(resolvConf []byte) bool {
-	var parsedResolvConf = StripComments(resolvConf, []byte("#"))
-	if !bytes.Contains(parsedResolvConf, []byte("nameserver")) {
-		return true
-	}
-	for _, ip := range [][]byte{
-		[]byte("127.0.0.1"),
-		[]byte("127.0.1.1"),
-	} {
-		if bytes.Contains(parsedResolvConf, ip) {
-			return true
+	for _, line := range GetLines(resolvConf, []byte("#")) {
+		if !bytes.Contains(line, []byte("nameserver")) {
+			continue
+		}
+		for _, ip := range [][]byte{
+			[]byte("127.0.0.1"),
+			[]byte("127.0.1.1"),
+		} {
+			if bytes.Contains(line, ip) {
+				return true
+			}
 		}
+		return false
 	}
-	return false
+	return true
 }
 
-// StripComments parses input into lines and strips away comments.
-func StripComments(input []byte, commentMarker []byte) []byte {
+// 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
+	var output [][]byte
 	for _, currentLine := range lines {
 		var commentIndex = bytes.Index(currentLine, commentMarker)
 		if commentIndex == -1 {
-			output = append(output, currentLine...)
+			output = append(output, currentLine)
 		} else {
-			output = append(output, currentLine[:commentIndex]...)
+			output = append(output, currentLine[:commentIndex])
 		}
-		output = append(output, []byte("\n")...)
 	}
 	return output
 }
 
-// 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 {
-	var parsedResolvConf = StripComments(resolvConf, []byte("#"))
+// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf
+func GetNameservers(resolvConf []byte) []string {
 	nameservers := []string{}
 	re := regexp.MustCompile(`^\s*nameserver\s*(([0-9]+\.){3}([0-9]+))\s*$`)
-	for _, line := range bytes.Split(parsedResolvConf, []byte("\n")) {
+	for _, line := range GetLines(resolvConf, []byte("#")) {
 		var ns = re.FindSubmatch(line)
 		if len(ns) > 0 {
-			nameservers = append(nameservers, string(ns[1])+"/32")
+			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 {
+	re := regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`)
+	domains := []string{}
+	for _, line := range GetLines(resolvConf, []byte("#")) {
+		match := re.FindSubmatch(line)
+		if match == nil {
+			continue
+		}
+		domains = strings.Fields(string(match[1]))
+	}
+	return domains
+}
+
 // FIXME: Change this not to receive default value as parameter
 func ParseHost(defaultHost string, defaultUnix, addr string) (string, error) {
 	var (

+ 51 - 0
utils/utils_test.go

@@ -444,6 +444,30 @@ func TestParsePortMapping(t *testing.T) {
 	}
 }
 
+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
@@ -468,6 +492,33 @@ nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4/32"},
 	}
 }
 
+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