--- /dev/null
+# flannel plugin
+
+## Overview
+This plugin is designed to work in conjunction with [flannel](https://github.com/coreos/flannel), a network fabric for containers.
+When flannel daemon is started, it outputs a `/run/flannel/subnet.env` file that looks like this:
+```
+FLANNEL_SUBNET=10.1.17.0/24
+FLANNEL_MTU=1472
+FLANNEL_IPMASQ=true
+```
+
+This information reflects the attributes of flannel network on the host.
+The flannel CNI plugin uses this information to configure another CNI plugin, such as bridge plugin.
+
+## Operation
+Given the following network configuration file and the contents of `/run/flannel/subnet.env` above,
+```
+{
+ "name": "mynet",
+ "type": "flannel"
+}
+```
+the flannel plugin will generate another network configuration file:
+```
+{
+ "name": "mynet",
+ "type": "bridge",
+ "mtu": 1472,
+ "ipMasq": false,
+ "isGateway": true,
+ "ipam": {
+ "type": "host-local",
+ "subnet": "10.1.17.0/24"
+ }
+}
+```
+
+It will then invoke the bridge plugin, passing it the generated configuration.
+
+As can be seen from above, the flannel plugin, by default, will delegate to the bridge plugin.
+If additional configuration values need to be passed to the bridge plugin, it can be done so via the `delegate` field:
+```
+{
+ "name": "mynet",
+ "type": "flannel",
+ "delegate": {
+ "bridge": "mynet0",
+ "mtu": 1400
+ }
+}
+```
+
+This supplies a configuration parameter to the bridge plugin -- the created bridge will now be named `mynet0`.
+Notice that `mtu` has also been specified and this value will not be overwritten by flannel plugin.
+
+Additionally, the `delegate` field can be used to select a different kind of plugin altogether.
+To use `ipvlan` instead of `bridge`, the following configuratoin can be specified:
+
+```
+{
+ "name": "mynet",
+ "type": "flannel",
+ "delegate": {
+ "type": "ipvlan",
+ "master": "eth0"
+ }
+}
+```
+
+## Network configuration reference
+
+* `name` (string, required): the name of the network
+* `type` (string, required): "flannel"
+* `subnetFile` (string, optional): full path to the subnet file written out by flanneld. Defaults to /run/flannel/subnet.env
+* `delegate` (dictionary, optional): specifies configuration options for the delegated plugin.
+
+flannel plugin will always set the following fields in the delegated plugin configuration:
+
+* `name`: value of its "name" field.
+* `ipam`: "host-local" type will be used with "subnet" set to `$FLANNEL_SUBNET`.
+
+flannel plugin will set the following fields in the delegated plugin configuration if they are not present:
+* `ipMasq`: the inverse of `$FLANNEL_IPMASQ`
+* `mtu`: `$FLANNEL_MTU`
+
+Additionally, for the bridge plugin, `isGateway` will be set to `true`, if not present.
--- /dev/null
+// Copyright 2015 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 "meta-plugin". It reads in its own netconf, combines it with
+// the data from flannel generated subnet file and then invokes a plugin
+// like bridge or ipvlan to do the real work.
+
+package main
+
+import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/appc/cni/pkg/plugin"
+ "github.com/appc/cni/pkg/skel"
+)
+
+const (
+ defaultSubnetFile = "/run/flannel/subnet.env"
+ stateDir = "/var/lib/cni/flannel"
+)
+
+type NetConf struct {
+ plugin.NetConf
+ SubnetFile string `json:"subnetFile"`
+ Delegate map[string]interface{} `json:"delegate"`
+}
+
+type subnetEnv struct {
+ sn *net.IPNet
+ mtu uint
+ ipmasq bool
+}
+
+func loadFlannelNetConf(bytes []byte) (*NetConf, error) {
+ n := &NetConf{
+ SubnetFile: defaultSubnetFile,
+ }
+ if err := json.Unmarshal(bytes, n); err != nil {
+ return nil, fmt.Errorf("failed to load netconf: %v", err)
+ }
+ return n, nil
+}
+
+func loadFlannelSubnetEnv(fn string) (*subnetEnv, error) {
+ f, err := os.Open(fn)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ se := &subnetEnv{}
+
+ s := bufio.NewScanner(f)
+ for s.Scan() {
+ parts := strings.SplitN(s.Text(), "=", 2)
+ switch parts[0] {
+ case "FLANNEL_SUBNET":
+ _, se.sn, err = net.ParseCIDR(parts[1])
+ if err != nil {
+ return nil, err
+ }
+
+ case "FLANNEL_MTU":
+ mtu, err := strconv.ParseUint(parts[1], 10, 32)
+ if err != nil {
+ return nil, err
+ }
+ se.mtu = uint(mtu)
+
+ case "FLANNEL_IPMASQ":
+ se.ipmasq = parts[1] == "true"
+ }
+ }
+ if err := s.Err(); err != nil {
+ return nil, err
+ }
+
+ return se, nil
+}
+
+func saveScratchNetConf(containerID string, netconf []byte) error {
+ if err := os.MkdirAll(stateDir, 0700); err != nil {
+ return err
+ }
+ path := filepath.Join(stateDir, containerID)
+ return ioutil.WriteFile(path, netconf, 0600)
+}
+
+func consumeScratchNetConf(containerID string) ([]byte, error) {
+ path := filepath.Join(stateDir, containerID)
+ defer os.Remove(path)
+
+ return ioutil.ReadFile(path)
+}
+
+func delegateAdd(cid string, netconf map[string]interface{}) error {
+ netconfBytes, err := json.Marshal(netconf)
+ if err != nil {
+ return fmt.Errorf("error serializing delegate netconf: %v", err)
+ }
+
+ // save the rendered netconf for cmdDel
+ if err = saveScratchNetConf(cid, netconfBytes); err != nil {
+ return err
+ }
+
+ result, err := plugin.ExecAdd(netconf["type"].(string), netconfBytes)
+ if err != nil {
+ return err
+ }
+
+ return result.Print()
+}
+
+func hasKey(m map[string]interface{}, k string) bool {
+ _, ok := m[k]
+ return ok
+}
+
+func isString(i interface{}) bool {
+ _, ok := i.(string)
+ return ok
+}
+
+func cmdAdd(args *skel.CmdArgs) error {
+ n, err := loadFlannelNetConf(args.StdinData)
+ if err != nil {
+ return err
+ }
+
+ fenv, err := loadFlannelSubnetEnv(n.SubnetFile)
+ if err != nil {
+ return err
+ }
+
+ if n.Delegate == nil {
+ n.Delegate = make(map[string]interface{})
+ } else {
+ if hasKey(n.Delegate, "type") && !isString(n.Delegate["type"]) {
+ return fmt.Errorf("'delegate' dictionary, if present, must have (string) 'type' field")
+ }
+ if hasKey(n.Delegate, "name") {
+ return fmt.Errorf("'delegate' dictionary must not have 'name' field, it'll be set by flannel")
+ }
+ if hasKey(n.Delegate, "ipam") {
+ return fmt.Errorf("'delegate' dictionary must not have 'ipam' field, it'll be set by flannel")
+ }
+ }
+
+ n.Delegate["name"] = n.Name
+
+ if !hasKey(n.Delegate, "type") {
+ n.Delegate["type"] = "bridge"
+ }
+
+ if !hasKey(n.Delegate, "ipMasq") {
+ // if flannel is not doing ipmasq, we should
+ ipmasq := !fenv.ipmasq
+ n.Delegate["ipMasq"] = ipmasq
+ }
+
+ if !hasKey(n.Delegate, "mtu") {
+ mtu := fenv.mtu
+ n.Delegate["mtu"] = mtu
+ }
+
+ if n.Delegate["type"].(string) == "bridge" {
+ if !hasKey(n.Delegate, "isGateway") {
+ n.Delegate["isGateway"] = true
+ }
+ }
+
+ n.Delegate["ipam"] = map[string]string{
+ "type": "host-local",
+ "subnet": fenv.sn.String(),
+ }
+
+ return delegateAdd(args.ContainerID, n.Delegate)
+}
+
+func cmdDel(args *skel.CmdArgs) error {
+ netconfBytes, err := consumeScratchNetConf(args.ContainerID)
+ if err != nil {
+ return err
+ }
+
+ n := &plugin.NetConf{}
+ if err = json.Unmarshal(netconfBytes, n); err != nil {
+ return fmt.Errorf("failed to parse netconf: %v", err)
+ }
+
+ return plugin.ExecDel(n.Type, netconfBytes)
+}
+
+func main() {
+ skel.PluginMain(cmdAdd, cmdDel)
+}