From 5cde14cd7bdb2bd930c480a6a101d10a5b453adf Mon Sep 17 00:00:00 2001 From: Casey Callendrello Date: Mon, 21 Nov 2016 19:05:41 +0100 Subject: [PATCH] ipam/host-local: add ResolvConf argument for DNS configuration This adds the option `resolvConf` to the host-local IPAM configuration. If specified, the plugin will try to parse the file as a resolv.conf(5) type file and return it in the DNS response. --- Documentation/host-local.md | 1 + plugins/ipam/host-local/README.md | 6 +- .../host-local/backend/allocator/config.go | 1 + plugins/ipam/host-local/dns.go | 64 +++++++++++++++ plugins/ipam/host-local/dns_test.go | 80 +++++++++++++++++++ plugins/ipam/host-local/host_local_test.go | 10 ++- plugins/ipam/host-local/main.go | 14 +++- 7 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 plugins/ipam/host-local/dns.go create mode 100644 plugins/ipam/host-local/dns_test.go diff --git a/Documentation/host-local.md b/Documentation/host-local.md index 37446157..00c1e84c 100644 --- a/Documentation/host-local.md +++ b/Documentation/host-local.md @@ -30,6 +30,7 @@ It stores the state locally on the host filesystem, therefore ensuring uniquenes * `rangeEnd` (string, optional): IP inside of "subnet" with which to end allocating addresses. Defaults to ".254" IP inside of the "subnet" block. * `gateway` (string, optional): IP inside of "subnet" to designate as the gateway. Defaults to ".1" IP inside of the "subnet" block. * `routes` (string, optional): list of routes to add to the container namespace. Each route is a dictionary with "dst" and optional "gw" fields. If "gw" is omitted, value of "gateway" will be used. +* `resolvConf` (string, optional): Path to a `resolv.conf` on the host to parse and return as the DNS configuration ## Supported arguments The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported: diff --git a/plugins/ipam/host-local/README.md b/plugins/ipam/host-local/README.md index 2efd2103..128bc6d8 100644 --- a/plugins/ipam/host-local/README.md +++ b/plugins/ipam/host-local/README.md @@ -1,6 +1,7 @@ # host-local IP address manager -host-local IPAM allocates IPv4 and IPv6 addresses out of a specified address range. +host-local IPAM allocates IPv4 and IPv6 addresses out of a specified address range. Optionally, +it can include a DNS configuration from a `resolv.conf` file on the host. ## Usage @@ -65,7 +66,8 @@ f81d4fae-7dec-11d0-a765-00a0c91e6bf6 "rangeEnd": "3ffe:ffff:0:01ff::0020", "routes": [ { "dst": "3ffe:ffff:0:01ff::1/64" } - ] + ], + "resolvConf": "/etc/resolv.conf" } } ``` diff --git a/plugins/ipam/host-local/backend/allocator/config.go b/plugins/ipam/host-local/backend/allocator/config.go index 6e5e4ab6..cf80ac25 100644 --- a/plugins/ipam/host-local/backend/allocator/config.go +++ b/plugins/ipam/host-local/backend/allocator/config.go @@ -32,6 +32,7 @@ type IPAMConfig struct { Gateway net.IP `json:"gateway"` Routes []types.Route `json:"routes"` DataDir string `json:"dataDir"` + ResolvConf string `json:"resolvConf"` Args *IPAMArgs `json:"-"` } diff --git a/plugins/ipam/host-local/dns.go b/plugins/ipam/host-local/dns.go new file mode 100644 index 00000000..1b3975a5 --- /dev/null +++ b/plugins/ipam/host-local/dns.go @@ -0,0 +1,64 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bufio" + "os" + "strings" + + "github.com/containernetworking/cni/pkg/types" +) + +// parseResolvConf parses an existing resolv.conf in to a DNS struct +func parseResolvConf(filename string) (*types.DNS, error) { + fp, err := os.Open(filename) + if err != nil { + return nil, err + } + + dns := types.DNS{} + scanner := bufio.NewScanner(fp) + for scanner.Scan() { + line := scanner.Text() + line = strings.TrimSpace(line) + + // Skip comments, empty lines + if len(line) == 0 || line[0] == '#' || line[0] == ';' { + continue + } + + fields := strings.Fields(line) + if len(fields) < 2 { + continue + } + switch fields[0] { + case "nameserver": + dns.Nameservers = append(dns.Nameservers, fields[1]) + case "domain": + dns.Domain = fields[1] + case "search": + dns.Search = append(dns.Search, fields[1:]...) + case "options": + dns.Options = append(dns.Options, fields[1:]...) + } + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return &dns, nil +} diff --git a/plugins/ipam/host-local/dns_test.go b/plugins/ipam/host-local/dns_test.go new file mode 100644 index 00000000..4f3a05fa --- /dev/null +++ b/plugins/ipam/host-local/dns_test.go @@ -0,0 +1,80 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "io/ioutil" + "os" + + "github.com/containernetworking/cni/pkg/types" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("parsing resolv.conf", func() { + It("parses a simple resolv.conf file", func() { + contents := ` + nameserver 192.0.2.0 + nameserver 192.0.2.1 + ` + dns, err := parse(contents) + Expect(err).NotTo(HaveOccurred()) + Expect(*dns).Should(Equal(types.DNS{Nameservers: []string{"192.0.2.0", "192.0.2.1"}})) + }) + It("ignores comments", func() { + dns, err := parse(` +nameserver 192.0.2.0 +;nameserver 192.0.2.1 +`) + Expect(err).NotTo(HaveOccurred()) + Expect(*dns).Should(Equal(types.DNS{Nameservers: []string{"192.0.2.0"}})) + }) + It("parses all fields", func() { + dns, err := parse(` +nameserver 192.0.2.0 +nameserver 192.0.2.2 +domain example.com +;nameserver comment +#nameserver comment +search example.net example.org +search example.gov +options one two three +options four +`) + Expect(err).NotTo(HaveOccurred()) + Expect(*dns).Should(Equal(types.DNS{ + Nameservers: []string{"192.0.2.0", "192.0.2.2"}, + Domain: "example.com", + Search: []string{"example.net", "example.org", "example.gov"}, + Options: []string{"one", "two", "three", "four"}, + })) + }) +}) + +func parse(contents string) (*types.DNS, error) { + f, err := ioutil.TempFile("", "host_local_resolv") + defer f.Close() + defer os.Remove(f.Name()) + + if err != nil { + return nil, err + } + + if _, err := f.WriteString(contents); err != nil { + return nil, err + } + + return parseResolvConf(f.Name()) +} diff --git a/plugins/ipam/host-local/host_local_test.go b/plugins/ipam/host-local/host_local_test.go index 97b3b6ff..01906bb9 100644 --- a/plugins/ipam/host-local/host_local_test.go +++ b/plugins/ipam/host-local/host_local_test.go @@ -38,6 +38,9 @@ var _ = Describe("host-local Operations", func() { Expect(err).NotTo(HaveOccurred()) defer os.RemoveAll(tmpDir) + err = ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644) + Expect(err).NotTo(HaveOccurred()) + conf := fmt.Sprintf(`{ "cniVersion": "0.2.0", "name": "mynet", @@ -46,9 +49,10 @@ var _ = Describe("host-local Operations", func() { "ipam": { "type": "host-local", "subnet": "10.1.2.0/24", - "dataDir": "%s" + "dataDir": "%s", + "resolvConf": "%s/resolv.conf" } -}`, tmpDir) +}`, tmpDir, tmpDir) args := &skel.CmdArgs{ ContainerID: "dummy", @@ -80,6 +84,8 @@ var _ = Describe("host-local Operations", func() { Expect(err).NotTo(HaveOccurred()) Expect(string(contents)).To(Equal("10.1.2.2")) + Expect(result.DNS).To(Equal(types.DNS{Nameservers: []string{"192.0.2.3"}})) + // Release the IP err = testutils.CmdDelWithResult(nspath, ifname, func() error { return cmdDel(args) diff --git a/plugins/ipam/host-local/main.go b/plugins/ipam/host-local/main.go index 330d9ac2..287c0a09 100644 --- a/plugins/ipam/host-local/main.go +++ b/plugins/ipam/host-local/main.go @@ -33,6 +33,16 @@ func cmdAdd(args *skel.CmdArgs) error { return err } + r := types.Result{} + + if ipamConf.ResolvConf != "" { + dns, err := parseResolvConf(ipamConf.ResolvConf) + if err != nil { + return err + } + r.DNS = *dns + } + store, err := disk.New(ipamConf.Name, ipamConf.DataDir) if err != nil { return err @@ -48,10 +58,8 @@ func cmdAdd(args *skel.CmdArgs) error { if err != nil { return err } + r.IP4 = ipConf - r := &types.Result{ - IP4: ipConf, - } return r.Print() }