summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorbaude <bbaude@redhat.com>2019-09-17 12:58:08 -0500
committerbaude <bbaude@redhat.com>2019-09-17 13:02:05 -0500
commit71399a972c12fb3adeac8990ba2ba0afbd5ae3bc (patch)
treed2d1249d4f98611022141b335e988acd794aa2c9 /plugins
parent0fae0bb06923ce3b2b6b7900dc00cfaaad3838fd (diff)
origin dnsname commit
Signed-off-by: baude <bbaude@redhat.com>
Diffstat (limited to 'plugins')
-rw-r--r--plugins/meta/dnsname/conf.go55
-rw-r--r--plugins/meta/dnsname/dnsname_suite_test.go27
-rw-r--r--plugins/meta/dnsname/dnsname_test.go203
-rw-r--r--plugins/meta/dnsname/files.go194
-rw-r--r--plugins/meta/dnsname/files_test.go55
-rw-r--r--plugins/meta/dnsname/main.go250
-rw-r--r--plugins/meta/dnsname/result.go68
-rw-r--r--plugins/meta/dnsname/service.go98
8 files changed, 950 insertions, 0 deletions
diff --git a/plugins/meta/dnsname/conf.go b/plugins/meta/dnsname/conf.go
new file mode 100644
index 0000000..9c1039b
--- /dev/null
+++ b/plugins/meta/dnsname/conf.go
@@ -0,0 +1,55 @@
+package main
+
+import (
+ "errors"
+
+ "github.com/containernetworking/cni/pkg/types"
+)
+
+const (
+ // dnsNameConfPath is where we store the conf, pid, and hosts files
+ dnsNameConfPath = "/run/containers/cni/dnsname"
+ // confFileName is the name of the dns masq conf file
+ confFileName = "dnsmasq.conf"
+ // hostsFileName is the name of the addnhosts file
+ hostsFileName = "addnhosts"
+ // pidFileName is the file where the dnsmasq file is stored
+ pidFileName = "pidfile"
+)
+
+const dnsMasqTemplate = `## WARNING: THIS IS AN AUTOGENERATED FILE
+## AND SHOULD NOT BE EDITED MANUALLY AS IT
+## LIKELY TO AUTOMATICALLY BE REPLACED.
+strict-order
+local=/{{.Domain}}/
+domain={{.Domain}}
+expand-hosts
+pid-file={{.PidFile}}
+except-interface=lo
+bind-dynamic
+no-hosts
+interface={{.NetworkInterface}}
+addn-hosts={{.AddOnHostsFile}}`
+
+var (
+ // ErrBinaryNotFound means that the dnsmasq binary was not found
+ ErrBinaryNotFound = errors.New("unable to locate dnsmasq in path")
+ // ErrNoIPAddressFound means that CNI was unable to resolve an IP address in the CNI configuration
+ ErrNoIPAddressFound = errors.New("no ip address was found in the network")
+)
+
+// DNSNameConf represents the cni config with the domain name attribute
+type DNSNameConf struct {
+ types.NetConf
+ DomainName string `json:"domainName"`
+}
+
+// dnsNameFile describes the plugin's attributes
+type dnsNameFile struct {
+ AddOnHostsFile string
+ Binary string
+ ConfigFile string
+ Domain string
+ NetworkInterface string
+ PidFile string
+}
diff --git a/plugins/meta/dnsname/dnsname_suite_test.go b/plugins/meta/dnsname/dnsname_suite_test.go
new file mode 100644
index 0000000..d2022b9
--- /dev/null
+++ b/plugins/meta/dnsname/dnsname_suite_test.go
@@ -0,0 +1,27 @@
+// Copyright 2017 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 (
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+
+ "testing"
+)
+
+func TestTuning(t *testing.T) {
+ RegisterFailHandler(Fail)
+ RunSpecs(t, "plugins/meta/dnsname")
+}
diff --git a/plugins/meta/dnsname/dnsname_test.go b/plugins/meta/dnsname/dnsname_test.go
new file mode 100644
index 0000000..9646d8b
--- /dev/null
+++ b/plugins/meta/dnsname/dnsname_test.go
@@ -0,0 +1,203 @@
+package main
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "reflect"
+ "syscall"
+ "time"
+
+ "github.com/containernetworking/cni/pkg/skel"
+ "github.com/containernetworking/cni/pkg/types/current"
+ "github.com/containernetworking/plugins/pkg/ns"
+ "github.com/containernetworking/plugins/pkg/testutils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/vishvananda/netlink"
+)
+
+func cleanup(d dnsNameFile) error {
+ _ = d.stop()
+ return os.RemoveAll(filepath.Dir(d.PidFile))
+}
+
+var _ = Describe("dnsname tests", func() {
+ var originalNS, targetNS ns.NetNS
+ const IFNAME string = "dummy0"
+
+ fullConf := []byte(`{
+ "cniVersion": "0.4.0",
+ "name": "test",
+ "type": "dnsname",
+ "domainName": "foobar.io",
+ "prevResult": {
+ "cniVersion": "0.4.0",
+ "interfaces": [
+ {
+ "name": "dummy0",
+ "mac": "a6:a7:ca:6b:34:2e"
+ },
+ {
+ "name": "vetha0a83b38",
+ "mac": "9a:45:bd:b0:2d:dd"
+ },
+ {
+ "name": "eth0",
+ "mac": "ea:63:0e:63:3e:86",
+ "sandbox": "/var/run/netns/baude"
+ }
+ ],
+ "ips": [
+ {
+ "version": "4",
+ "interface": 2,
+ "address": "10.88.8.5/24",
+ "gateway": "10.88.8.1"
+ }
+ ],
+ "routes": [
+ {
+ "dst": "0.0.0.0/0"
+ }
+ ]
+}
+ }`)
+
+ BeforeEach(func() {
+ // Create a new NetNS so we don't modify the host
+ var err error
+ originalNS, err = testutils.NewNS()
+ Expect(err).NotTo(HaveOccurred())
+
+ err = originalNS.Do(func(ns.NetNS) error {
+ defer GinkgoRecover()
+
+ err = netlink.LinkAdd(&netlink.Dummy{
+ LinkAttrs: netlink.LinkAttrs{
+ Name: IFNAME,
+ },
+ })
+ Expect(err).NotTo(HaveOccurred())
+ _, err = netlink.LinkByName(IFNAME)
+ Expect(err).NotTo(HaveOccurred())
+ return nil
+ })
+ Expect(err).NotTo(HaveOccurred())
+
+ targetNS, err = testutils.NewNS()
+ Expect(err).NotTo(HaveOccurred())
+ })
+
+ AfterEach(func() {
+ Expect(originalNS.Close()).To(Succeed())
+ Expect(targetNS.Close()).To(Succeed())
+ })
+
+ It("dnsname add", func() {
+ args := &skel.CmdArgs{
+ ContainerID: "dummy",
+ Netns: targetNS.Path(),
+ IfName: IFNAME,
+ StdinData: fullConf,
+ }
+
+ err := originalNS.Do(func(ns.NetNS) error {
+ defer GinkgoRecover()
+
+ r, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
+ return cmdAdd(args)
+ })
+ Expect(err).NotTo(HaveOccurred())
+
+ _, err = current.GetResult(r)
+ Expect(err).NotTo(HaveOccurred())
+
+ // Check that all configuration files are created
+ files, err := ioutil.ReadDir("/run/containers/cni/dnsname/test")
+ Expect(err).To(BeNil())
+ expectedFileNames := []string{"addnhosts", "dnsmasq.conf", "lock", "pidfile"}
+ var resultingFileNames []string
+ for _, f := range files {
+ resultingFileNames = append(resultingFileNames, f.Name())
+ }
+ Expect(reflect.DeepEqual(expectedFileNames, resultingFileNames)).To(BeTrue())
+
+ d, err := newDNSMasqFile("foobar.io", "dummy0", "test")
+ Expect(err).To(BeNil())
+
+ // Check that the dns masq instance is running
+ pid, err := d.getPidProcess()
+ Expect(err).To(BeNil())
+ // Send it a signal 0; if alive, error will be nil
+ err = pid.Signal(syscall.Signal(0))
+ Expect(err).To(BeNil())
+
+ // Stop the dnsmasq instance and clean up files in the filesystem
+ Expect(cleanup(d)).To(BeNil())
+ return nil
+ })
+ Expect(err).NotTo(HaveOccurred())
+ })
+
+ It("dnsname del", func() {
+ var (
+ dnsDead bool
+ counter int
+ )
+ args := &skel.CmdArgs{
+ ContainerID: "dummy",
+ Netns: targetNS.Path(),
+ IfName: IFNAME,
+ StdinData: fullConf,
+ }
+
+ err := originalNS.Do(func(ns.NetNS) error {
+ defer GinkgoRecover()
+
+ r, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
+ return cmdAdd(args)
+ })
+ Expect(err).NotTo(HaveOccurred())
+
+ _, err = current.GetResult(r)
+ Expect(err).NotTo(HaveOccurred())
+
+ d, err := newDNSMasqFile("foobar.io", "dummy0", "test")
+ Expect(err).To(BeNil())
+
+ pid, err := d.getPidProcess()
+ Expect(err).To(BeNil())
+ err = pid.Signal(syscall.Signal(0))
+ Expect(err).To(BeNil())
+
+ err = testutils.CmdDel(targetNS.Path(), args.ContainerID, IFNAME, func() error {
+ return cmdDel(args)
+ })
+ Expect(err).To(BeNil())
+
+ // Ensure the dnsmasq instance has been stopped on del
+ // It sometimes takes time for the dnsmasq pid to be killed
+ // check every .5 second for maximum of 10 tries
+ for {
+ err = pid.Signal(syscall.Signal(0))
+ if err != nil {
+ dnsDead = true
+ break
+ }
+ if counter == 10 {
+ break
+ }
+ counter++
+ time.Sleep(500 * time.Millisecond)
+ }
+
+ Expect(dnsDead).To(BeTrue())
+
+ // Cleanup behind ourselves
+ Expect(cleanup(d)).To(BeNil())
+ return nil
+ })
+ Expect(err).NotTo(HaveOccurred())
+ })
+})
diff --git a/plugins/meta/dnsname/files.go b/plugins/meta/dnsname/files.go
new file mode 100644
index 0000000..70fdfe5
--- /dev/null
+++ b/plugins/meta/dnsname/files.go
@@ -0,0 +1,194 @@
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "github.com/coreos/go-iptables/iptables"
+ "io/ioutil"
+ "net"
+ "os"
+ "strings"
+ "text/template"
+
+ "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk"
+ "github.com/sirupsen/logrus"
+)
+
+// dnsNameLock embeds the CNI disk lock so we can hang methods from it
+type dnsNameLock struct {
+ lock *disk.FileLock
+}
+
+// release unlocks and closes the disk lock
+func (m *dnsNameLock) release() error {
+ if err := m.lock.Unlock(); err != nil {
+ return err
+ }
+ return m.lock.Close()
+}
+
+// getLock returns a dnsNameLock. the lock should be that of the configuration
+// directory for the domain.
+func getLock(path string) (*dnsNameLock, error) {
+ l, err := disk.NewFileLock(path)
+ if err != nil {
+ return nil, err
+ }
+ if err := l.Lock(); err != nil {
+ return nil, err
+ }
+ return &dnsNameLock{l}, nil
+}
+
+// checkFromDNSMasqConfFile ensures that the dnsmasq conf file for
+// the network interface exists or it creates it
+func checkForDNSMasqConfFile(conf dnsNameFile) error {
+ if _, err := os.Stat(conf.ConfigFile); err == nil {
+ // the file already exists, we can proceed
+ return err
+ }
+ newConfig, err := generateDNSMasqConfig(conf)
+ if err != nil {
+ return err
+ }
+ ip, err := iptables.New()
+ if err != nil {
+ return err
+ }
+ args := []string{"-i", conf.NetworkInterface, "-p", "udp", "-m", "udp", "--dport", "53", "-j", "ACCEPT"}
+ exists, err := ip.Exists("filter", "INPUT", args...)
+ if err != nil {
+ return err
+ }
+ if !exists {
+ if err := ip.Insert("filter", "INPUT", 1, args...); err != nil {
+ return err
+ }
+ }
+ // Generate the template and compile it.
+ return ioutil.WriteFile(conf.ConfigFile, newConfig, 0700)
+}
+
+// generateDNSMasqConfig fills out the configuration file template for the dnsmasq service
+func generateDNSMasqConfig(config dnsNameFile) ([]byte, error) {
+ var buf bytes.Buffer
+ templ, err := template.New("dnsmasq-conf-file").Parse(dnsMasqTemplate)
+ if err != nil {
+ return nil, err
+ }
+ if err := templ.Execute(&buf, config); err != nil {
+ return nil, err
+ }
+ buf.WriteByte('\n')
+ return buf.Bytes(), nil
+}
+
+// appendToFile appends a new entry to the dnsmasqs hosts file
+func appendToFile(path, podname string, ips []*net.IPNet) error {
+ f, err := openFile(path)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if err := f.Close(); err != nil {
+ logrus.Errorf("failed to close file '%s': %q", path, err)
+ }
+ }()
+ for _, ip := range ips {
+ entry := fmt.Sprintf("%s\t%s\n", ip.IP.String(), podname)
+ if _, err = f.WriteString(entry); err != nil {
+ return err
+ }
+ logrus.Debugf("appended %s: %s", path, entry)
+ }
+ return nil
+}
+
+// removeLineFromFile removes a given entry from the dnsmasq host file
+func removeFromFile(path, podname string) (bool, error) {
+ var (
+ keepers []string
+ found bool
+ )
+ shouldHUP := false
+ backup := fmt.Sprintf("%s.old", path)
+ if err := os.Rename(path, backup); err != nil {
+ return shouldHUP, err
+ }
+ f, err := os.Open(backup)
+ if err != nil {
+ // if the open fails here, we need to revert things
+ renameFile(backup, path)
+ return shouldHUP, err
+ }
+ defer func() {
+ if err := f.Close(); err != nil {
+ logrus.Errorf("unable to close '%s': %q", backup, err)
+ }
+ }()
+
+ oldFile := bufio.NewScanner(f)
+ // Iterate the old file
+ for oldFile.Scan() {
+ fields := strings.Fields(oldFile.Text())
+ // if the IP of the entry and the given IP dont match, it should
+ // go into the new file
+ if len(fields) > 1 && fields[1] != podname {
+ keepers = append(keepers, fmt.Sprintf("%s\n", oldFile.Text()))
+ continue
+ }
+ found = true
+ }
+ if !found {
+ // We never found a matching record; non-fatal
+ logrus.Debugf("a record for %s was never found in %s", podname, path)
+ }
+ fileLength, err := writeFile(path, keepers)
+ if err != nil {
+ renameFile(backup, path)
+ return shouldHUP, err
+ }
+ if fileLength > 0 {
+ shouldHUP = true
+ }
+ if err := os.Remove(backup); err != nil {
+ logrus.Errorf("unable to delete '%s': %q", backup, err)
+ }
+ return shouldHUP, nil
+}
+
+// renameFile renames a file to backup
+func renameFile(oldpath, newpath string) {
+ if renameError := os.Rename(oldpath, newpath); renameError != nil {
+ logrus.Errorf("unable to restore '%s' to '%s': %q", oldpath, newpath, renameError)
+ }
+}
+
+// writeFile writes a []string to the given path and returns the number
+// of lines in the file
+func writeFile(path string, content []string) (int, error) {
+ var counter int
+ f, err := openFile(path)
+ if err != nil {
+ return 0, err
+ }
+ defer func() {
+ if err := f.Close(); err != nil {
+ logrus.Errorf("unable to close '%s': %q", path, err)
+ }
+ }()
+
+ for _, line := range content {
+ if _, err := f.WriteString(line); err != nil {
+ return 0, err
+ }
+ counter++
+ }
+ return counter, nil
+}
+
+// openFile opens a file for reading
+func openFile(path string) (*os.File, error) {
+ return os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+}
diff --git a/plugins/meta/dnsname/files_test.go b/plugins/meta/dnsname/files_test.go
new file mode 100644
index 0000000..e86dcc4
--- /dev/null
+++ b/plugins/meta/dnsname/files_test.go
@@ -0,0 +1,55 @@
+package main
+
+import (
+ "reflect"
+ "testing"
+)
+
+func Test_generateDNSMasqConfig(t *testing.T) {
+ testResult := `## WARNING: THIS IS AN AUTOGENERATED FILE
+## AND SHOULD NOT BE EDITED MANUALLY AS IT
+## LIKELY TO AUTOMATICALLY BE REPLACED.
+strict-order
+local=/foobar.org/
+domain=foobar.org
+expand-hosts
+pid-file=/run/containers/cni/dnsname/cni0/pidfile
+except-interface=lo
+bind-dynamic
+no-hosts
+interface=cni0
+addn-hosts=/run/containers/cni/dnsname/cni0/addnhosts
+`
+
+ testConfig := dnsNameFile{
+ AddOnHostsFile: makePath("cni0", hostsFileName),
+ Binary: "/usr/bin/foo",
+ ConfigFile: makePath("cni0", confFileName),
+ Domain: "foobar.org",
+ NetworkInterface: "cni0",
+ PidFile: makePath("cni0", pidFileName),
+ }
+ type args struct {
+ config dnsNameFile
+ }
+ tests := []struct {
+ name string
+ args args
+ want []byte
+ wantErr bool
+ }{
+ {"pass", args{testConfig}, []byte(testResult), false},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := generateDNSMasqConfig(tt.args.config)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("generateDNSMasqConfig() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("generateDNSMasqConfig() got = '%v', want '%v'", string(got), string(tt.want))
+ }
+ })
+ }
+}
diff --git a/plugins/meta/dnsname/main.go b/plugins/meta/dnsname/main.go
new file mode 100644
index 0000000..79fde02
--- /dev/null
+++ b/plugins/meta/dnsname/main.go
@@ -0,0 +1,250 @@
+// Copyright 2017 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.
+
+// This is a post-setup plugin that establishes port forwarding - using iptables,
+// from the host's network interface(s) to a pod's network interface.
+//
+// It is intended to be used as a chained CNI plugin, and determines the container
+// IP from the previous result. If the result includes an IPv6 address, it will
+// also be configured. (IPTables will not forward cross-family).
+//
+// This has one notable limitation: it does not perform any kind of reservation
+// of the actual host port. If there is a service on the host, it will have all
+// its traffic captured by the container. If another container also claims a given
+// port, it will caputure the traffic - it is last-write-wins.
+package main
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+
+ "github.com/containernetworking/cni/pkg/skel"
+ "github.com/containernetworking/cni/pkg/types"
+ "github.com/containernetworking/cni/pkg/types/current"
+ "github.com/containernetworking/cni/pkg/version"
+ bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
+ "github.com/sirupsen/logrus"
+)
+
+func cmdAdd(args *skel.CmdArgs) error {
+ if err := findDNSMasq(); err != nil {
+ return ErrBinaryNotFound
+ }
+ netConf, result, podname, err := parseConfig(args.StdinData, args.Args)
+ if err != nil {
+ return fmt.Errorf("failed to parse config: %v", err)
+ }
+ if netConf.PrevResult == nil {
+ return fmt.Errorf("must be called as chained plugin")
+ }
+ ips, err := getIPs(result)
+ if err != nil {
+ return err
+ }
+
+ dnsNameConf, err := newDNSMasqFile(netConf.DomainName, result.Interfaces[0].Name, netConf.Name)
+ if err != nil {
+ return err
+ }
+ domainBaseDir := filepath.Dir(dnsNameConf.PidFile)
+ // Check if the configuration file directory exists, else make it
+ if _, err := os.Stat(domainBaseDir); os.IsNotExist(err) {
+ if makeDirErr := os.MkdirAll(domainBaseDir, 0700); makeDirErr != nil {
+ return makeDirErr
+ }
+ }
+ // we use the configuration directory for our locking mechanism but read/write and hup
+ lock, err := getLock(domainBaseDir)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if err := lock.release(); err != nil {
+ logrus.Errorf("unable to release lock for '%s': %q", dnsNameConf.AddOnHostsFile, err)
+ }
+ }()
+ if err := checkForDNSMasqConfFile(dnsNameConf); err != nil {
+ return err
+ }
+ if err := appendToFile(dnsNameConf.AddOnHostsFile, podname, ips); err != nil {
+ return err
+ }
+ // Now we need to HUP
+ if err := dnsNameConf.hup(); err != nil {
+ return err
+ }
+ nameservers, err := getInterfaceAddresses(dnsNameConf)
+ if err != nil {
+ return err
+ }
+ // keep anything that was passed in already
+ nameservers = append(nameservers, result.DNS.Nameservers...)
+ result.DNS.Nameservers = nameservers
+ // Pass through the previous result
+ return types.PrintResult(result, netConf.CNIVersion)
+}
+
+func cmdDel(args *skel.CmdArgs) error {
+ if err := findDNSMasq(); err != nil {
+ return ErrBinaryNotFound
+ }
+ netConf, result, podname, err := parseConfig(args.StdinData, args.Args)
+ if err != nil {
+ return fmt.Errorf("failed to parse config: %v", err)
+ }
+ if result == nil {
+ return nil
+ }
+ dnsNameConf, err := newDNSMasqFile(netConf.DomainName, result.Interfaces[0].Name, netConf.Name)
+ if err != nil {
+ return err
+ }
+ domainBaseDir := filepath.Dir(dnsNameConf.PidFile)
+ lock, err := getLock(domainBaseDir)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ // if the lock isn't given up by another process
+ if err := lock.release(); err != nil {
+ logrus.Errorf("unable to release lock for '%s': %q", domainBaseDir, err)
+ }
+ }()
+ shouldHUP, err := removeFromFile(filepath.Join(domainBaseDir, hostsFileName), podname)
+ if err != nil {
+ return err
+ }
+ if !shouldHUP {
+ // if there are no hosts, we should just stop the dnsmasq instance to not take
+ // system resources
+ return dnsNameConf.stop()
+ }
+ // Now we need to HUP
+ return dnsNameConf.hup()
+}
+
+func main() {
+ skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("dnsname"))
+}
+
+func cmdCheck(args *skel.CmdArgs) error {
+ var (
+ conffiles []string
+ )
+ if err := findDNSMasq(); err != nil {
+ return ErrBinaryNotFound
+ }
+ netConf, result, podname, err := parseConfig(args.StdinData, args.Args)
+ if err != nil {
+ return fmt.Errorf("failed to parse config: %v", err)
+ }
+
+ _ = podname
+
+ // Ensure we have previous result.
+ if result == nil {
+ return fmt.Errorf("Required prevResult missing")
+ }
+ dnsNameConf, err := newDNSMasqFile(netConf.DomainName, result.Interfaces[0].Name, netConf.Name)
+ if err != nil {
+ return err
+ }
+ domainBaseDir := filepath.Dir(dnsNameConf.PidFile)
+ lock, err := getLock(domainBaseDir)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ // if the lock isn't given up by another process
+ if err := lock.release(); err != nil {
+ logrus.Errorf("unable to release lock for '%s': %q", domainBaseDir, err)
+ }
+ }()
+
+ pid, err := dnsNameConf.getPidProcess()
+ if err != nil {
+ return err
+ }
+
+ // Ensure the dnsmasq instance is running
+ if !isRunning(pid) {
+ return errors.New("dnsmasq instance not running")
+ }
+ // Above will make sure the pidfile exists
+ files, err := ioutil.ReadDir(dnsNameConfPath)
+ if err != nil {
+ return err
+ }
+ for _, f := range files {
+ conffiles = append(conffiles, f.Name())
+ }
+ if !stringInSlice("addnhosts", conffiles) {
+ return errors.New("addnhost file missing from configuration")
+ }
+ if !stringInSlice("dnsmasq.conf", conffiles) {
+ return errors.New("dnsmasq.conf file missing from configuration")
+ }
+ return nil
+}
+
+// stringInSlice is simple util to check for the presence of a string
+// in a string slice
+func stringInSlice(s string, slice []string) bool {
+ for _, sl := range slice {
+ if s == sl {
+ return true
+ }
+ }
+ return false
+}
+
+type podname struct {
+ types.CommonArgs
+ K8S_POD_NAME types.UnmarshallableString `json:"podname,omitempty"`
+}
+
+// parseConfig parses the supplied configuration (and prevResult) from stdin.
+func parseConfig(stdin []byte, args string) (*DNSNameConf, *current.Result, string, error) {
+ conf := DNSNameConf{}
+ if err := json.Unmarshal(stdin, &conf); err != nil {
+ return nil, nil, "", fmt.Errorf("failed to parse network configuration: %v", err)
+ }
+ // Parse previous result.
+ var result *current.Result
+ if conf.RawPrevResult != nil {
+ var err error
+ if err = version.ParsePrevResult(&conf.NetConf); err != nil {
+ return nil, nil, "", fmt.Errorf("could not parse prevResult: %v", err)
+ }
+ result, err = current.NewResultFromResult(conf.PrevResult)
+ if err != nil {
+ return nil, nil, "", fmt.Errorf("could not convert result to current version: %v", err)
+ }
+ }
+ e := podname{}
+ if err := types.LoadArgs(args, &e); err != nil {
+ return nil, nil, "", err
+ }
+ return &conf, result, string(e.K8S_POD_NAME), nil
+}
+
+func findDNSMasq() error {
+ _, err := exec.LookPath("dnsmasq")
+ return err
+}
diff --git a/plugins/meta/dnsname/result.go b/plugins/meta/dnsname/result.go
new file mode 100644
index 0000000..115f69a
--- /dev/null
+++ b/plugins/meta/dnsname/result.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+ "errors"
+ "net"
+
+ "github.com/containernetworking/cni/pkg/types/current"
+)
+
+// getIPs iterates a result and returns all the IP addresses
+// associated with it
+func getIPs(r *current.Result) ([]*net.IPNet, error) {
+ var (
+ ips []*net.IPNet
+ )
+ if len(r.IPs) < 1 {
+ return nil, ErrNoIPAddressFound
+ }
+ if len(r.IPs) == 1 {
+ return append(ips, &r.IPs[0].Address), nil
+ }
+ for _, ip := range r.IPs {
+ if ip.Address.IP != nil && ip.Interface != nil {
+ if isInterfaceIndexSandox(*ip.Interface, r) {
+ ips = append(ips, &ip.Address)
+ } else {
+ return nil, errors.New("unable to check if interface has a sandbox due to index being out of range")
+ }
+ }
+ }
+ if len(ips) < 1 {
+ return nil, ErrNoIPAddressFound
+ }
+ return ips, nil
+}
+
+// isInterfaceIndexSandox determines if the given interface index has the sandbox
+// attribute and the value is greater than 0
+func isInterfaceIndexSandox(idx int, r *current.Result) bool {
+ if idx >= 0 && idx < len(r.Interfaces) {
+ return len(r.Interfaces[idx].Sandbox) > 0
+ }
+ return false
+}
+
+// getInterfaceAddresses gets all globalunicast IP addresses for a given
+// interface
+func getInterfaceAddresses(nameConf dnsNameFile) ([]string, error) {
+ var nameservers []string
+ nic, err := net.InterfaceByName(nameConf.NetworkInterface)
+ if err != nil {
+ return nil, err
+ }
+ addrs, err := nic.Addrs()
+ if err != nil {
+ return nil, err
+ }
+ for _, addr := range addrs {
+ ip, _, err := net.ParseCIDR(addr.String())
+ if err != nil {
+ return nil, err
+ }
+ if ip.IsGlobalUnicast() {
+ nameservers = append(nameservers, ip.String())
+ }
+ }
+ return nameservers, nil
+}
diff --git a/plugins/meta/dnsname/service.go b/plugins/meta/dnsname/service.go
new file mode 100644
index 0000000..f569f73
--- /dev/null
+++ b/plugins/meta/dnsname/service.go
@@ -0,0 +1,98 @@
+package main
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "syscall"
+
+ "golang.org/x/sys/unix"
+)
+
+// newDNSMasqFile creates a new instance of a dnsNameFile
+func newDNSMasqFile(domainName, networkInterface, networkName string) (dnsNameFile, error) {
+ dnsMasqBinary, err := exec.LookPath("dnsmasq")
+ if err != nil {
+ return dnsNameFile{}, errors.New("the dnsmasq cni plugin requires the dnsmasq binary be in PATH")
+ }
+ masqConf := dnsNameFile{
+ ConfigFile: makePath(networkName, confFileName),
+ Domain: domainName,
+ PidFile: makePath(networkName, pidFileName),
+ NetworkInterface: networkInterface,
+ AddOnHostsFile: makePath(networkName, hostsFileName),
+ Binary: dnsMasqBinary,
+ }
+ return masqConf, nil
+}
+
+// hup sends a sighup to a running dnsmasq to reload its hosts file. if
+// there is no instance of the dnsmasq, then it simply starts it.
+func (d dnsNameFile) hup() error {
+ // First check for pidfile; if it does not exist, we just
+ // start the service
+ if _, err := os.Stat(d.PidFile); os.IsNotExist(err) {
+ return d.start()
+ }
+ pid, err := d.getPidProcess()
+ if err != nil {
+ return err
+ }
+ if !isRunning(pid) {
+ return d.start()
+ }
+ return pid.Signal(unix.SIGHUP)
+}
+
+// isRunning sends a signal 0 to the pid to determine if it
+// responds or not
+func isRunning(pid *os.Process) bool {
+ if err := pid.Signal(syscall.Signal(0)); err != nil {
+ return false
+ }
+ return true
+}
+
+func (d dnsNameFile) start() error {
+ args := []string{
+ "-u",
+ "root",
+ fmt.Sprintf("--conf-file=%s", d.ConfigFile),
+ }
+ cmd := exec.Command(d.Binary, args...)
+ return cmd.Run()
+}
+
+func (d dnsNameFile) stop() error {
+ pid, err := d.getPidProcess()
+ if err != nil {
+ return err
+ }
+ return pid.Kill()
+}
+
+// getPidProcess reads the PID for the dnsmasq instance and returns it in the
+// form of an int
+func (d dnsNameFile) getPidProcess() (*os.Process, error) {
+ pidFileContents, err := ioutil.ReadFile(d.PidFile)
+ if err != nil {
+ return nil, err
+ }
+ pid, err := strconv.Atoi(strings.TrimSpace(string(pidFileContents)))
+ if err != nil {
+ return nil, err
+ }
+ return os.FindProcess(pid)
+}
+
+// makePath formats a path name given a domain and suffix
+func makePath(networkName, fileName string) string {
+ // the generic path for where conf, host, pid files are kept is:
+ // /run/containers/cni/dnsmasq/<network-name>/
+ return filepath.Join(dnsNameConfPath, networkName, fileName)
+}