bridge.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. // Copyright 2014 CNI authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package bridge
  15. import (
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. "net"
  20. "runtime"
  21. "syscall"
  22. "github.com/containernetworking/cni/pkg/ip"
  23. "github.com/containernetworking/cni/pkg/ipam"
  24. "github.com/containernetworking/cni/pkg/ns"
  25. "github.com/containernetworking/cni/pkg/skel"
  26. "github.com/containernetworking/cni/pkg/types"
  27. "github.com/containernetworking/cni/pkg/utils"
  28. "github.com/vishvananda/netlink"
  29. )
  30. const defaultBrName = "cni0"
  31. type NetConf struct {
  32. types.NetConf
  33. BrName string `json:"bridge"`
  34. IsGW bool `json:"isGateway"`
  35. IsDefaultGW bool `json:"isDefaultGateway"`
  36. IPMasq bool `json:"ipMasq"`
  37. MTU int `json:"mtu"`
  38. HairpinMode bool `json:"hairpinMode"`
  39. }
  40. func init() {
  41. // this ensures that main runs only on main thread (thread group leader).
  42. // since namespace ops (unshare, setns) are done for a single thread, we
  43. // must ensure that the goroutine does not jump from OS thread to thread
  44. runtime.LockOSThread()
  45. }
  46. func loadNetConf(bytes []byte) (*NetConf, error) {
  47. n := &NetConf{
  48. BrName: defaultBrName,
  49. }
  50. if err := json.Unmarshal(bytes, n); err != nil {
  51. return nil, fmt.Errorf("failed to load netconf: %v", err)
  52. }
  53. return n, nil
  54. }
  55. func ensureBridgeAddr(br *netlink.Bridge, ipn *net.IPNet) error {
  56. addrs, err := netlink.AddrList(br, syscall.AF_INET)
  57. if err != nil && err != syscall.ENOENT {
  58. return fmt.Errorf("could not get list of IP addresses: %v", err)
  59. }
  60. // if there're no addresses on the bridge, it's ok -- we'll add one
  61. if len(addrs) > 0 {
  62. ipnStr := ipn.String()
  63. for _, a := range addrs {
  64. // string comp is actually easiest for doing IPNet comps
  65. if a.IPNet.String() == ipnStr {
  66. return nil
  67. }
  68. }
  69. return fmt.Errorf("%q already has an IP address different from %v", br.Name, ipn.String())
  70. }
  71. addr := &netlink.Addr{IPNet: ipn, Label: ""}
  72. if err := netlink.AddrAdd(br, addr); err != nil {
  73. return fmt.Errorf("could not add IP address to %q: %v", br.Name, err)
  74. }
  75. return nil
  76. }
  77. func bridgeByName(name string) (*netlink.Bridge, error) {
  78. l, err := netlink.LinkByName(name)
  79. if err != nil {
  80. return nil, fmt.Errorf("could not lookup %q: %v", name, err)
  81. }
  82. br, ok := l.(*netlink.Bridge)
  83. if !ok {
  84. return nil, fmt.Errorf("%q already exists but is not a bridge", name)
  85. }
  86. return br, nil
  87. }
  88. func ensureBridge(brName string, mtu int) (*netlink.Bridge, error) {
  89. br := &netlink.Bridge{
  90. LinkAttrs: netlink.LinkAttrs{
  91. Name: brName,
  92. MTU: mtu,
  93. // Let kernel use default txqueuelen; leaving it unset
  94. // means 0, and a zero-length TX queue messes up FIFO
  95. // traffic shapers which use TX queue length as the
  96. // default packet limit
  97. TxQLen: -1,
  98. },
  99. }
  100. if err := netlink.LinkAdd(br); err != nil {
  101. if err != syscall.EEXIST {
  102. return nil, fmt.Errorf("could not add %q: %v", brName, err)
  103. }
  104. // it's ok if the device already exists as long as config is similar
  105. br, err = bridgeByName(brName)
  106. if err != nil {
  107. return nil, err
  108. }
  109. }
  110. if err := netlink.LinkSetUp(br); err != nil {
  111. return nil, err
  112. }
  113. return br, nil
  114. }
  115. func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool) error {
  116. var hostVethName string
  117. err := netns.Do(func(hostNS ns.NetNS) error {
  118. // create the veth pair in the container and move host end into host netns
  119. hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS)
  120. if err != nil {
  121. return err
  122. }
  123. hostVethName = hostVeth.Attrs().Name
  124. return nil
  125. })
  126. if err != nil {
  127. return err
  128. }
  129. // need to lookup hostVeth again as its index has changed during ns move
  130. hostVeth, err := netlink.LinkByName(hostVethName)
  131. if err != nil {
  132. return fmt.Errorf("failed to lookup %q: %v", hostVethName, err)
  133. }
  134. // connect host veth end to the bridge
  135. if err = netlink.LinkSetMaster(hostVeth, br); err != nil {
  136. return fmt.Errorf("failed to connect %q to bridge %v: %v", hostVethName, br.Attrs().Name, err)
  137. }
  138. // set hairpin mode
  139. if err = netlink.LinkSetHairpin(hostVeth, hairpinMode); err != nil {
  140. return fmt.Errorf("failed to setup hairpin mode for %v: %v", hostVethName, err)
  141. }
  142. return nil
  143. }
  144. func calcGatewayIP(ipn *net.IPNet) net.IP {
  145. nid := ipn.IP.Mask(ipn.Mask)
  146. return ip.NextIP(nid)
  147. }
  148. func setupBridge(n *NetConf) (*netlink.Bridge, error) {
  149. // create bridge if necessary
  150. br, err := ensureBridge(n.BrName, n.MTU)
  151. if err != nil {
  152. return nil, fmt.Errorf("failed to create bridge %q: %v", n.BrName, err)
  153. }
  154. return br, nil
  155. }
  156. func cmdAdd(args *skel.CmdArgs) error {
  157. n, err := loadNetConf(args.StdinData)
  158. if err != nil {
  159. return err
  160. }
  161. if n.IsDefaultGW {
  162. n.IsGW = true
  163. }
  164. br, err := setupBridge(n)
  165. if err != nil {
  166. return err
  167. }
  168. netns, err := ns.GetNS(args.Netns)
  169. if err != nil {
  170. return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
  171. }
  172. defer netns.Close()
  173. if err = setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode); err != nil {
  174. return err
  175. }
  176. // run the IPAM plugin and get back the config to apply
  177. result, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
  178. if err != nil {
  179. return err
  180. }
  181. // TODO: make this optional when IPv6 is supported
  182. if result.IP4 == nil {
  183. return errors.New("IPAM plugin returned missing IPv4 config")
  184. }
  185. if result.IP4.Gateway == nil && n.IsGW {
  186. result.IP4.Gateway = calcGatewayIP(&result.IP4.IP)
  187. }
  188. if err := netns.Do(func(_ ns.NetNS) error {
  189. // set the default gateway if requested
  190. if n.IsDefaultGW {
  191. _, defaultNet, err := net.ParseCIDR("0.0.0.0/0")
  192. if err != nil {
  193. return err
  194. }
  195. for _, route := range result.IP4.Routes {
  196. if defaultNet.String() == route.Dst.String() {
  197. if route.GW != nil && !route.GW.Equal(result.IP4.Gateway) {
  198. return fmt.Errorf(
  199. "isDefaultGateway ineffective because IPAM sets default route via %q",
  200. route.GW,
  201. )
  202. }
  203. }
  204. }
  205. result.IP4.Routes = append(
  206. result.IP4.Routes,
  207. types.Route{Dst: *defaultNet, GW: result.IP4.Gateway},
  208. )
  209. // TODO: IPV6
  210. }
  211. return ipam.ConfigureIface(args.IfName, result)
  212. }); err != nil {
  213. return err
  214. }
  215. if n.IsGW {
  216. gwn := &net.IPNet{
  217. IP: result.IP4.Gateway,
  218. Mask: result.IP4.IP.Mask,
  219. }
  220. if err = ensureBridgeAddr(br, gwn); err != nil {
  221. return err
  222. }
  223. if err := ip.EnableIP4Forward(); err != nil {
  224. return fmt.Errorf("failed to enable forwarding: %v", err)
  225. }
  226. }
  227. if n.IPMasq {
  228. chain := utils.FormatChainName(n.Name, args.ContainerID)
  229. comment := utils.FormatComment(n.Name, args.ContainerID)
  230. if err = ip.SetupIPMasq(ip.Network(&result.IP4.IP), chain, comment); err != nil {
  231. return err
  232. }
  233. }
  234. result.DNS = n.DNS
  235. return result.Print()
  236. }
  237. func cmdDel(args *skel.CmdArgs) error {
  238. n, err := loadNetConf(args.StdinData)
  239. if err != nil {
  240. return err
  241. }
  242. if err := ipam.ExecDel(n.IPAM.Type, args.StdinData); err != nil {
  243. return err
  244. }
  245. if args.Netns == "" {
  246. return nil
  247. }
  248. var ipn *net.IPNet
  249. err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
  250. var err error
  251. ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4)
  252. return err
  253. })
  254. if err != nil {
  255. return err
  256. }
  257. if n.IPMasq {
  258. chain := utils.FormatChainName(n.Name, args.ContainerID)
  259. comment := utils.FormatComment(n.Name, args.ContainerID)
  260. if err = ip.TeardownIPMasq(ipn, chain, comment); err != nil {
  261. return err
  262. }
  263. }
  264. return nil
  265. }
  266. func Main() {
  267. skel.PluginMain(cmdAdd, cmdDel)
  268. }