123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- // Copyright 2014 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 bridge
- import (
- "encoding/json"
- "errors"
- "fmt"
- "net"
- "runtime"
- "syscall"
- "github.com/containernetworking/cni/pkg/ip"
- "github.com/containernetworking/cni/pkg/ipam"
- "github.com/containernetworking/cni/pkg/ns"
- "github.com/containernetworking/cni/pkg/skel"
- "github.com/containernetworking/cni/pkg/types"
- "github.com/containernetworking/cni/pkg/utils"
- "github.com/vishvananda/netlink"
- )
- const defaultBrName = "cni0"
- type NetConf struct {
- types.NetConf
- BrName string `json:"bridge"`
- IsGW bool `json:"isGateway"`
- IsDefaultGW bool `json:"isDefaultGateway"`
- IPMasq bool `json:"ipMasq"`
- MTU int `json:"mtu"`
- HairpinMode bool `json:"hairpinMode"`
- }
- func init() {
- // this ensures that main runs only on main thread (thread group leader).
- // since namespace ops (unshare, setns) are done for a single thread, we
- // must ensure that the goroutine does not jump from OS thread to thread
- runtime.LockOSThread()
- }
- func loadNetConf(bytes []byte) (*NetConf, error) {
- n := &NetConf{
- BrName: defaultBrName,
- }
- if err := json.Unmarshal(bytes, n); err != nil {
- return nil, fmt.Errorf("failed to load netconf: %v", err)
- }
- return n, nil
- }
- func ensureBridgeAddr(br *netlink.Bridge, ipn *net.IPNet) error {
- addrs, err := netlink.AddrList(br, syscall.AF_INET)
- if err != nil && err != syscall.ENOENT {
- return fmt.Errorf("could not get list of IP addresses: %v", err)
- }
- // if there're no addresses on the bridge, it's ok -- we'll add one
- if len(addrs) > 0 {
- ipnStr := ipn.String()
- for _, a := range addrs {
- // string comp is actually easiest for doing IPNet comps
- if a.IPNet.String() == ipnStr {
- return nil
- }
- }
- return fmt.Errorf("%q already has an IP address different from %v", br.Name, ipn.String())
- }
- addr := &netlink.Addr{IPNet: ipn, Label: ""}
- if err := netlink.AddrAdd(br, addr); err != nil {
- return fmt.Errorf("could not add IP address to %q: %v", br.Name, err)
- }
- return nil
- }
- func bridgeByName(name string) (*netlink.Bridge, error) {
- l, err := netlink.LinkByName(name)
- if err != nil {
- return nil, fmt.Errorf("could not lookup %q: %v", name, err)
- }
- br, ok := l.(*netlink.Bridge)
- if !ok {
- return nil, fmt.Errorf("%q already exists but is not a bridge", name)
- }
- return br, nil
- }
- func ensureBridge(brName string, mtu int) (*netlink.Bridge, error) {
- br := &netlink.Bridge{
- LinkAttrs: netlink.LinkAttrs{
- Name: brName,
- MTU: mtu,
- // Let kernel use default txqueuelen; leaving it unset
- // means 0, and a zero-length TX queue messes up FIFO
- // traffic shapers which use TX queue length as the
- // default packet limit
- TxQLen: -1,
- },
- }
- if err := netlink.LinkAdd(br); err != nil {
- if err != syscall.EEXIST {
- return nil, fmt.Errorf("could not add %q: %v", brName, err)
- }
- // it's ok if the device already exists as long as config is similar
- br, err = bridgeByName(brName)
- if err != nil {
- return nil, err
- }
- }
- if err := netlink.LinkSetUp(br); err != nil {
- return nil, err
- }
- return br, nil
- }
- func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool) error {
- var hostVethName string
- err := netns.Do(func(hostNS ns.NetNS) error {
- // create the veth pair in the container and move host end into host netns
- hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS)
- if err != nil {
- return err
- }
- hostVethName = hostVeth.Attrs().Name
- return nil
- })
- if err != nil {
- return err
- }
- // need to lookup hostVeth again as its index has changed during ns move
- hostVeth, err := netlink.LinkByName(hostVethName)
- if err != nil {
- return fmt.Errorf("failed to lookup %q: %v", hostVethName, err)
- }
- // connect host veth end to the bridge
- if err = netlink.LinkSetMaster(hostVeth, br); err != nil {
- return fmt.Errorf("failed to connect %q to bridge %v: %v", hostVethName, br.Attrs().Name, err)
- }
- // set hairpin mode
- if err = netlink.LinkSetHairpin(hostVeth, hairpinMode); err != nil {
- return fmt.Errorf("failed to setup hairpin mode for %v: %v", hostVethName, err)
- }
- return nil
- }
- func calcGatewayIP(ipn *net.IPNet) net.IP {
- nid := ipn.IP.Mask(ipn.Mask)
- return ip.NextIP(nid)
- }
- func setupBridge(n *NetConf) (*netlink.Bridge, error) {
- // create bridge if necessary
- br, err := ensureBridge(n.BrName, n.MTU)
- if err != nil {
- return nil, fmt.Errorf("failed to create bridge %q: %v", n.BrName, err)
- }
- return br, nil
- }
- func cmdAdd(args *skel.CmdArgs) error {
- n, err := loadNetConf(args.StdinData)
- if err != nil {
- return err
- }
- if n.IsDefaultGW {
- n.IsGW = true
- }
- br, err := setupBridge(n)
- if err != nil {
- return err
- }
- netns, err := ns.GetNS(args.Netns)
- if err != nil {
- return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
- }
- defer netns.Close()
- if err = setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode); err != nil {
- return err
- }
- // run the IPAM plugin and get back the config to apply
- result, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
- if err != nil {
- return err
- }
- // TODO: make this optional when IPv6 is supported
- if result.IP4 == nil {
- return errors.New("IPAM plugin returned missing IPv4 config")
- }
- if result.IP4.Gateway == nil && n.IsGW {
- result.IP4.Gateway = calcGatewayIP(&result.IP4.IP)
- }
- if err := netns.Do(func(_ ns.NetNS) error {
- // set the default gateway if requested
- if n.IsDefaultGW {
- _, defaultNet, err := net.ParseCIDR("0.0.0.0/0")
- if err != nil {
- return err
- }
- for _, route := range result.IP4.Routes {
- if defaultNet.String() == route.Dst.String() {
- if route.GW != nil && !route.GW.Equal(result.IP4.Gateway) {
- return fmt.Errorf(
- "isDefaultGateway ineffective because IPAM sets default route via %q",
- route.GW,
- )
- }
- }
- }
- result.IP4.Routes = append(
- result.IP4.Routes,
- types.Route{Dst: *defaultNet, GW: result.IP4.Gateway},
- )
- // TODO: IPV6
- }
- return ipam.ConfigureIface(args.IfName, result)
- }); err != nil {
- return err
- }
- if n.IsGW {
- gwn := &net.IPNet{
- IP: result.IP4.Gateway,
- Mask: result.IP4.IP.Mask,
- }
- if err = ensureBridgeAddr(br, gwn); err != nil {
- return err
- }
- if err := ip.EnableIP4Forward(); err != nil {
- return fmt.Errorf("failed to enable forwarding: %v", err)
- }
- }
- if n.IPMasq {
- chain := utils.FormatChainName(n.Name, args.ContainerID)
- comment := utils.FormatComment(n.Name, args.ContainerID)
- if err = ip.SetupIPMasq(ip.Network(&result.IP4.IP), chain, comment); err != nil {
- return err
- }
- }
- result.DNS = n.DNS
- return result.Print()
- }
- func cmdDel(args *skel.CmdArgs) error {
- n, err := loadNetConf(args.StdinData)
- if err != nil {
- return err
- }
- if err := ipam.ExecDel(n.IPAM.Type, args.StdinData); err != nil {
- return err
- }
- if args.Netns == "" {
- return nil
- }
- var ipn *net.IPNet
- err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
- var err error
- ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4)
- return err
- })
- if err != nil {
- return err
- }
- if n.IPMasq {
- chain := utils.FormatChainName(n.Name, args.ContainerID)
- comment := utils.FormatComment(n.Name, args.ContainerID)
- if err = ip.TeardownIPMasq(ipn, chain, comment); err != nil {
- return err
- }
- }
- return nil
- }
- func Main() {
- skel.PluginMain(cmdAdd, cmdDel)
- }
|