diff options
author | baude <bbaude@redhat.com> | 2019-09-17 12:58:08 -0500 |
---|---|---|
committer | baude <bbaude@redhat.com> | 2019-09-17 13:02:05 -0500 |
commit | 71399a972c12fb3adeac8990ba2ba0afbd5ae3bc (patch) | |
tree | d2d1249d4f98611022141b335e988acd794aa2c9 /plugins | |
parent | 0fae0bb06923ce3b2b6b7900dc00cfaaad3838fd (diff) |
origin dnsname commit
Signed-off-by: baude <bbaude@redhat.com>
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/meta/dnsname/conf.go | 55 | ||||
-rw-r--r-- | plugins/meta/dnsname/dnsname_suite_test.go | 27 | ||||
-rw-r--r-- | plugins/meta/dnsname/dnsname_test.go | 203 | ||||
-rw-r--r-- | plugins/meta/dnsname/files.go | 194 | ||||
-rw-r--r-- | plugins/meta/dnsname/files_test.go | 55 | ||||
-rw-r--r-- | plugins/meta/dnsname/main.go | 250 | ||||
-rw-r--r-- | plugins/meta/dnsname/result.go | 68 | ||||
-rw-r--r-- | plugins/meta/dnsname/service.go | 98 |
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) +} |