The change makes "weave expose" to call the "/expose" HTTP endpoint implemented in router which exposes the bridge. Such architectural change required as we need to know whether NPC is enabled when exposing the bridge and weaver knows this fact the best. Unfortunately, this change makes impossible to "weave expose" when weaver is not running.
107 lines
3.3 KiB
Go
107 lines
3.3 KiB
Go
package net
|
|
|
|
import (
|
|
"net"
|
|
"syscall"
|
|
|
|
"github.com/coreos/go-iptables/iptables"
|
|
"github.com/j-keck/arping"
|
|
"github.com/pkg/errors"
|
|
"github.com/vishvananda/netlink"
|
|
)
|
|
|
|
// Expose makes the network accessible from a host by assigning a given IP address
|
|
// to the weave bridge.
|
|
//
|
|
// List of params:
|
|
// * "bridgeName" - a name of the weave bridge.
|
|
// * "ipAddr" - IP addr to be assigned to the bridge.
|
|
// * "removeDefaultRoute" - whether to remove a default route installed by the kernel (used only in the AWSVPC mode).
|
|
// * "npc" - whether is Weave NPC running.
|
|
func Expose(bridgeName string, ipAddr *net.IPNet, removeDefaultRoute, npc bool) error {
|
|
if err := addBridgeIPAddr(bridgeName, ipAddr, removeDefaultRoute); err != nil {
|
|
return errors.Wrap(err, "addBridgeIPAddr")
|
|
}
|
|
|
|
if err := exposeNAT(ipAddr); err != nil {
|
|
return errors.Wrap(err, "exposeNAT")
|
|
}
|
|
|
|
if !npc {
|
|
// TODO comment why not in npc mode && add filter rules and docs
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func addBridgeIPAddr(bridgeName string, addr *net.IPNet, removeDefaultRoute bool) error {
|
|
link, err := netlink.LinkByName(bridgeName)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "addBridgeIPAddr finding bridge %q", bridgeName)
|
|
}
|
|
err = netlink.AddrAdd(link, &netlink.Addr{IPNet: addr})
|
|
// The IP addr might have been already set by a concurrent request, just ignore then
|
|
if err != nil && err != syscall.Errno(syscall.EEXIST) {
|
|
return errors.Wrapf(err, "adding address %v to %q", addr, bridgeName)
|
|
}
|
|
|
|
// Sending multiple ARP REQUESTs in the case of EEXIST above does not hurt
|
|
arping.GratuitousArpOverIfaceByName(addr.IP, bridgeName)
|
|
|
|
// Remove a default route installed by the kernel. Required by the AWSVPC mode.
|
|
if removeDefaultRoute {
|
|
routeFilter := &netlink.Route{
|
|
LinkIndex: link.Attrs().Index,
|
|
Dst: &net.IPNet{IP: addr.IP.Mask(addr.Mask), Mask: addr.Mask},
|
|
Protocol: 2, // RTPROT_KERNEL
|
|
}
|
|
filterMask := netlink.RT_FILTER_OIF | netlink.RT_FILTER_DST | netlink.RT_FILTER_PROTOCOL
|
|
routes, err := netlink.RouteListFiltered(netlink.FAMILY_V4, routeFilter, filterMask)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get route list for bridge %q", bridgeName)
|
|
}
|
|
for _, r := range routes {
|
|
err = netlink.RouteDel(&r)
|
|
// Again, there might be a concurrent request for removing routes
|
|
if err != nil && err != syscall.Errno(syscall.ESRCH) {
|
|
return errors.Wrapf(err, "failed to delete default route %+v", r)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func exposeNAT(ipnet *net.IPNet) error {
|
|
ipt, err := iptables.New()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cidr := ipnet.String()
|
|
|
|
if err := addNatRule(ipt, "-s", cidr, "-d", "224.0.0.0/4", "-j", "RETURN"); err != nil {
|
|
return err
|
|
}
|
|
if err := addNatRule(ipt, "-d", cidr, "!", "-s", cidr, "-j", "MASQUERADE"); err != nil {
|
|
return err
|
|
}
|
|
return addNatRule(ipt, "-s", cidr, "!", "-d", cidr, "-j", "MASQUERADE")
|
|
}
|
|
|
|
func addNatRule(ipt *iptables.IPTables, rulespec ...string) error {
|
|
// Loop until we get an exit code other than "temporarily unavailable"
|
|
for {
|
|
if err := ipt.AppendUnique("nat", "WEAVE", rulespec...); err != nil {
|
|
if ierr, ok := err.(*iptables.Error); ok {
|
|
if status, ok := ierr.ExitError.Sys().(syscall.WaitStatus); ok {
|
|
// (magic exit code 4 found in iptables source code; undocumented)
|
|
if status.ExitStatus() == 4 {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
}
|