Files
weave/prog/weaver/main.go
T
Martynas Pumputis baa8f229f7 Detect enabling of hairpin mode on vethwe-bridge
Enabled hairpin mode on vethwe-bridge was one of the root causes
for installing looping flows in the fastdp mode.

Also, it confused the weave custom L2 bridge, as the bridge treats
a bounced back packet, e.g. ARP brodcast request, as a local one
(the case for "Captured a packet associated with remote host").
2016-12-08 11:37:52 +00:00

598 lines
19 KiB
Go

package main
import (
"fmt"
"net"
"net/http"
_ "net/http/pprof"
"os"
"runtime"
"strconv"
"strings"
"time"
"github.com/gorilla/mux"
"github.com/pkg/profile"
"github.com/weaveworks/docker/pkg/mflag"
"github.com/weaveworks/mesh"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/common/docker"
"github.com/weaveworks/weave/db"
"github.com/weaveworks/weave/ipam"
"github.com/weaveworks/weave/ipam/tracker"
"github.com/weaveworks/weave/nameserver"
weavenet "github.com/weaveworks/weave/net"
"github.com/weaveworks/weave/net/address"
weave "github.com/weaveworks/weave/router"
)
var version = "unreleased"
var Log = common.Log
type ipamConfig struct {
IPRangeCIDR string
IPSubnetCIDR string
PeerCount int
Mode string
Observer bool
SeedPeerNames []mesh.PeerName
}
type dnsConfig struct {
Domain string
ListenAddress string
TTL int
ClientTimeout time.Duration
EffectiveListenAddress string
ResolvConf string
}
func (c *ipamConfig) Enabled() bool {
var (
hasPeerCount = c.PeerCount > 0
hasMode = len(c.Mode) > 0
hasRange = c.IPRangeCIDR != ""
hasSubnet = c.IPSubnetCIDR != ""
)
switch {
case !(hasPeerCount || hasMode || hasRange || hasSubnet):
return false
case !hasRange && hasSubnet:
Log.Fatal("--ipalloc-default-subnet specified without --ipalloc-range.")
case !hasRange:
Log.Fatal("--ipalloc-init or --init-peer-count specified without --ipalloc-range.")
case hasMode && hasPeerCount:
Log.Fatal("At most one of --ipalloc-init or --init-peer-count may be specified.")
}
if hasMode {
if err := c.parseMode(); err != nil {
Log.Fatalf("Unable to parse --ipalloc-init: %s", err)
}
}
return true
}
func (c *ipamConfig) parseMode() error {
modeAndParam := strings.SplitN(c.Mode, "=", 2)
switch modeAndParam[0] {
case "consensus":
if len(modeAndParam) == 2 {
peerCount, err := strconv.Atoi(modeAndParam[1])
if err != nil {
return fmt.Errorf("bad consensus parameter: %s", err)
}
c.PeerCount = peerCount
}
case "seed":
if len(modeAndParam) != 2 {
return fmt.Errorf("seed mode requires peer name list")
}
seedPeerNames, err := parsePeerNames(modeAndParam[1])
if err != nil {
return fmt.Errorf("bad seed parameter: %s", err)
}
c.SeedPeerNames = seedPeerNames
case "observer":
if len(modeAndParam) != 1 {
return fmt.Errorf("observer mode takes no parameter")
}
c.Observer = true
default:
return fmt.Errorf("unknown mode: %s", modeAndParam[0])
}
return nil
}
func main() {
procs := runtime.NumCPU()
// packet sniffing can block an OS thread, so we need one thread
// for that plus at least one more.
if procs < 2 {
procs = 2
}
runtime.GOMAXPROCS(procs)
var (
justVersion bool
config mesh.Config
networkConfig weave.NetworkConfig
protocolMinVersion int
resume bool
ifaceName string
routerName string
nickName string
password string
pktdebug bool
logLevel string
prof string
bufSzMB int
noDiscovery bool
httpAddr string
statusAddr string
ipamConfig ipamConfig
dockerAPI string
peers []string
noDNS bool
dnsConfig dnsConfig
datapathName string
trustedSubnetStr string
dbPrefix string
isAWSVPC bool
defaultDockerHost = "unix:///var/run/docker.sock"
)
if val := os.Getenv("DOCKER_HOST"); val != "" {
defaultDockerHost = val
}
mflag.BoolVar(&justVersion, []string{"#version", "-version"}, false, "print version and exit")
mflag.StringVar(&config.Host, []string{"-host"}, "", "router host")
mflag.IntVar(&config.Port, []string{"#port", "-port"}, mesh.Port, "router port")
mflag.IntVar(&protocolMinVersion, []string{"-min-protocol-version"}, mesh.ProtocolMinVersion, "minimum weave protocol version")
mflag.BoolVar(&resume, []string{"-resume"}, false, "resume connections to previous peers")
mflag.StringVar(&ifaceName, []string{"#iface", "-iface"}, "", "name of interface to capture/inject from (disabled if blank)")
mflag.StringVar(&routerName, []string{"#name", "-name"}, "", "name of router (defaults to MAC of interface)")
mflag.StringVar(&nickName, []string{"#nickname", "-nickname"}, "", "nickname of peer (defaults to hostname)")
mflag.StringVar(&password, []string{"#password", "-password"}, "", "network password")
mflag.StringVar(&logLevel, []string{"-log-level"}, "info", "logging level (debug, info, warning, error)")
mflag.BoolVar(&pktdebug, []string{"#pktdebug", "#-pktdebug", "-pkt-debug"}, false, "enable per-packet debug logging")
mflag.StringVar(&prof, []string{"#profile", "-profile"}, "", "enable profiling and write profiles to given path")
mflag.IntVar(&config.ConnLimit, []string{"#connlimit", "#-connlimit", "-conn-limit"}, 30, "connection limit (0 for unlimited)")
mflag.BoolVar(&noDiscovery, []string{"#nodiscovery", "#-nodiscovery", "-no-discovery"}, false, "disable peer discovery")
mflag.IntVar(&bufSzMB, []string{"#bufsz", "-bufsz"}, 8, "capture buffer size in MB")
mflag.StringVar(&httpAddr, []string{"#httpaddr", "#-httpaddr", "-http-addr"}, "", "address to bind HTTP interface to (disabled if blank, absolute path indicates unix domain socket)")
mflag.StringVar(&statusAddr, []string{"-status-addr"}, "", "address to bind status+metrics interface to (disabled if blank, absolute path indicates unix domain socket)")
mflag.StringVar(&ipamConfig.Mode, []string{"-ipalloc-init"}, "", "allocator initialisation strategy (consensus, seed or observer)")
mflag.StringVar(&ipamConfig.IPRangeCIDR, []string{"#iprange", "#-iprange", "-ipalloc-range"}, "", "IP address range reserved for automatic allocation, in CIDR notation")
mflag.StringVar(&ipamConfig.IPSubnetCIDR, []string{"#ipsubnet", "#-ipsubnet", "-ipalloc-default-subnet"}, "", "subnet to allocate within by default, in CIDR notation")
mflag.IntVar(&ipamConfig.PeerCount, []string{"#initpeercount", "#-initpeercount", "-init-peer-count"}, 0, "number of peers in network (for IP address allocation)")
mflag.StringVar(&dockerAPI, []string{"#api", "#-api", "-docker-api"}, defaultDockerHost, "Docker API endpoint")
mflag.BoolVar(&noDNS, []string{"-no-dns"}, false, "disable DNS server")
mflag.StringVar(&dnsConfig.Domain, []string{"-dns-domain"}, nameserver.DefaultDomain, "local domain to server requests for")
mflag.StringVar(&dnsConfig.ListenAddress, []string{"-dns-listen-address"}, nameserver.DefaultListenAddress, "address to listen on for DNS requests")
mflag.IntVar(&dnsConfig.TTL, []string{"-dns-ttl"}, nameserver.DefaultTTL, "TTL for DNS request from our domain")
mflag.DurationVar(&dnsConfig.ClientTimeout, []string{"-dns-fallback-timeout"}, nameserver.DefaultClientTimeout, "timeout for fallback DNS requests")
mflag.StringVar(&dnsConfig.EffectiveListenAddress, []string{"-dns-effective-listen-address"}, "", "address DNS will actually be listening, after Docker port mapping")
mflag.StringVar(&dnsConfig.ResolvConf, []string{"-resolv-conf"}, "", "path to resolver configuration for fallback DNS lookups")
mflag.StringVar(&datapathName, []string{"-datapath"}, "", "ODP datapath name")
mflag.StringVar(&trustedSubnetStr, []string{"-trusted-subnets"}, "", "comma-separated list of trusted subnets in CIDR notation")
mflag.StringVar(&dbPrefix, []string{"-db-prefix"}, "/weavedb/weave", "pathname/prefix of filename to store data")
mflag.BoolVar(&isAWSVPC, []string{"-awsvpc"}, false, "use AWS VPC for routing")
// crude way of detecting that we probably have been started in a
// container, with `weave launch` --> suppress misleading paths in
// mflags error messages.
if os.Args[0] == "/home/weave/weaver" { // matches the Dockerfile ENTRYPOINT
os.Args[0] = "weave"
mflag.CommandLine.Init("weave", mflag.ExitOnError)
}
mflag.Parse()
peers = mflag.Args()
if resume && len(peers) > 0 {
Log.Fatalf("You must not specify an initial peer list in conjunction with --resume")
}
common.SetLogLevel(logLevel)
if justVersion {
fmt.Printf("weave router %s\n", version)
os.Exit(0)
}
Log.Println("Command line options:", options())
if prof != "" {
defer profile.Start(profile.CPUProfile, profile.ProfilePath(prof), profile.NoShutdownHook).Stop()
}
if protocolMinVersion < mesh.ProtocolMinVersion || protocolMinVersion > mesh.ProtocolMaxVersion {
Log.Fatalf("--min-protocol-version must be in range [%d,%d]", mesh.ProtocolMinVersion, mesh.ProtocolMaxVersion)
}
config.ProtocolMinVersion = byte(protocolMinVersion)
if pktdebug {
networkConfig.PacketLogging = packetLogging{}
} else {
networkConfig.PacketLogging = nopPacketLogging{}
}
overlay, bridge := createOverlay(datapathName, ifaceName, isAWSVPC, config.Host, config.Port, bufSzMB)
networkConfig.Bridge = bridge
if bridge != nil {
if err := weavenet.DetectHairpin("vethwe-bridge", Log); err != nil {
Log.Errorf("DetectHairpin failed: %s", err)
}
}
name := peerName(routerName, bridge.Interface())
if nickName == "" {
var err error
nickName, err = os.Hostname()
checkFatal(err)
}
config.Password = determinePassword(password)
config.TrustedSubnets = parseTrustedSubnets(trustedSubnetStr)
config.PeerDiscovery = !noDiscovery
if isAWSVPC && len(config.Password) > 0 {
Log.Fatalf("--awsvpc mode is not compatible with the --password option")
}
db, err := db.NewBoltDB(dbPrefix + "data.db")
checkFatal(err)
defer db.Close()
router := weave.NewNetworkRouter(config, networkConfig, name, nickName, overlay, db)
Log.Println("Our name is", router.Ourself)
if peers, err = router.InitialPeers(resume, peers); err != nil {
Log.Fatal("Unable to get initial peer set: ", err)
}
var dockerCli *docker.Client
dockerVersion := "none"
if dockerAPI != "" {
dc, err := docker.NewClient(dockerAPI)
if err != nil {
Log.Fatal("Unable to start docker client: ", err)
} else {
Log.Info(dc.Info())
}
dockerCli = dc
dockerVersion = dockerCli.DockerVersion()
}
network := ""
if isAWSVPC {
network = "awsvpc"
}
checkForUpdates(dockerVersion, network)
observeContainers := func(o docker.ContainerObserver) {
if dockerCli != nil {
if err := dockerCli.AddObserver(o); err != nil {
Log.Fatal("Unable to start watcher", err)
}
}
}
isKnownPeer := func(name mesh.PeerName) bool {
return router.Peers.Fetch(name) != nil
}
var (
allocator *ipam.Allocator
defaultSubnet address.CIDR
trackerName string
)
if ipamConfig.Enabled() {
var t tracker.LocalRangeTracker
if isAWSVPC {
Log.Infoln("Creating AWSVPC LocalRangeTracker")
t, err = tracker.NewAWSVPCTracker()
if err != nil {
Log.Fatalf("Cannot create AWSVPC LocalRangeTracker: %s", err)
}
trackerName = "awsvpc"
}
allocator, defaultSubnet = createAllocator(router, ipamConfig, db, t, isKnownPeer)
observeContainers(allocator)
if dockerCli != nil {
ids, err := dockerCli.AllContainerIDs()
checkFatal(err)
allocator.PruneOwned(ids)
}
}
var (
ns *nameserver.Nameserver
dnsserver *nameserver.DNSServer
)
if !noDNS {
ns, dnsserver = createDNSServer(dnsConfig, router.Router, isKnownPeer)
observeContainers(ns)
ns.Start()
defer ns.Stop()
dnsserver.ActivateAndServe()
defer dnsserver.Stop()
}
router.Start()
if errors := router.InitiateConnections(peers, false); len(errors) > 0 {
Log.Fatal(common.ErrorMessages(errors))
}
checkFatal(router.CreateRestartSentinel())
// The weave script always waits for a status call to succeed,
// so there is no point in doing "weave launch --http-addr ''".
// This is here to support stand-alone use of weaver.
if httpAddr != "" {
muxRouter := mux.NewRouter()
if allocator != nil {
allocator.HandleHTTP(muxRouter, defaultSubnet, trackerName, dockerCli)
}
if ns != nil {
ns.HandleHTTP(muxRouter, dockerCli)
}
router.HandleHTTP(muxRouter)
HandleHTTP(muxRouter, version, router, allocator, defaultSubnet, ns, dnsserver)
muxRouter.Methods("GET").Path("/metrics").Handler(metricsHandler(router, allocator, ns, dnsserver))
http.Handle("/", common.LoggingHTTPHandler(muxRouter))
Log.Println("Listening for HTTP control messages on", httpAddr)
go listenAndServeHTTP(httpAddr, nil)
}
if statusAddr != "" {
muxRouter := mux.NewRouter()
HandleHTTP(muxRouter, version, router, allocator, defaultSubnet, ns, dnsserver)
muxRouter.Methods("GET").Path("/metrics").Handler(metricsHandler(router, allocator, ns, dnsserver))
statusMux := http.NewServeMux()
statusMux.Handle("/", muxRouter)
Log.Println("Listening for metrics requests on", statusAddr)
go listenAndServeHTTP(statusAddr, statusMux)
}
common.SignalHandlerLoop(router)
}
func options() map[string]string {
options := make(map[string]string)
mflag.Visit(func(f *mflag.Flag) {
value := f.Value.String()
name := canonicalName(f)
if name == "password" {
value = "<elided>"
}
options[name] = value
})
return options
}
func canonicalName(f *mflag.Flag) string {
for _, n := range f.Names {
if n[0] != '#' {
return strings.TrimLeft(n, "#-")
}
}
return ""
}
type packetLogging struct{}
func (packetLogging) LogPacket(msg string, key weave.PacketKey) {
Log.Println(msg, key.SrcMAC, "->", key.DstMAC)
}
func (packetLogging) LogForwardPacket(msg string, key weave.ForwardPacketKey) {
Log.Println(msg, key.SrcPeer, key.SrcMAC, "->", key.DstPeer, key.DstMAC)
}
type nopPacketLogging struct{}
func (nopPacketLogging) LogPacket(string, weave.PacketKey) {
}
func (nopPacketLogging) LogForwardPacket(string, weave.ForwardPacketKey) {
}
func createOverlay(datapathName string, ifaceName string, isAWSVPC bool, host string, port int, bufSzMB int) (weave.NetworkOverlay, weave.Bridge) {
overlay := weave.NewOverlaySwitch()
var bridge weave.Bridge
var ignoreSleeve bool
switch {
case isAWSVPC:
vpc := weave.NewAWSVPC()
overlay.Add("awsvpc", vpc)
bridge = weave.NullBridge{}
// Currently, we do not support any overlay with AWSVPC
ignoreSleeve = true
case datapathName != "" && ifaceName != "":
Log.Fatal("At most one of --datapath and --iface must be specified.")
case datapathName != "":
iface, err := weavenet.EnsureInterface(datapathName)
checkFatal(err)
fastdp, err := weave.NewFastDatapath(iface, port)
checkFatal(err)
bridge = fastdp.Bridge()
overlay.Add("fastdp", fastdp.Overlay())
case ifaceName != "":
iface, err := weavenet.EnsureInterface(ifaceName)
checkFatal(err)
bridge, err = weave.NewPcap(iface, bufSzMB*1024*1024) // bufsz flag is in MB
checkFatal(err)
default:
bridge = weave.NullBridge{}
}
if !ignoreSleeve {
sleeve := weave.NewSleeveOverlay(host, port)
overlay.Add("sleeve", sleeve)
overlay.SetCompatOverlay(sleeve)
}
return overlay, bridge
}
func createAllocator(router *weave.NetworkRouter, config ipamConfig, db db.DB, track tracker.LocalRangeTracker, isKnownPeer func(mesh.PeerName) bool) (*ipam.Allocator, address.CIDR) {
ipRange, err := ipam.ParseCIDRSubnet(config.IPRangeCIDR)
checkFatal(err)
defaultSubnet := ipRange
if config.IPSubnetCIDR != "" {
defaultSubnet, err = ipam.ParseCIDRSubnet(config.IPSubnetCIDR)
checkFatal(err)
if !ipRange.Range().Overlaps(defaultSubnet.Range()) {
Log.Fatalf("IP address allocation default subnet %s does not overlap with allocation range %s", defaultSubnet, ipRange)
}
}
c := ipam.Config{
OurName: router.Ourself.Peer.Name,
OurUID: router.Ourself.Peer.UID,
OurNickname: router.Ourself.Peer.NickName,
Seed: config.SeedPeerNames,
Universe: ipRange,
IsObserver: config.Observer,
Quorum: func() uint { return determineQuorum(config.PeerCount, router) },
Db: db,
IsKnownPeer: isKnownPeer,
Tracker: track,
}
allocator := ipam.NewAllocator(c)
allocator.SetInterfaces(router.NewGossip("IPallocation", allocator))
allocator.Start()
router.Peers.OnGC(func(peer *mesh.Peer) { allocator.PeerGone(peer.Name) })
return allocator, defaultSubnet
}
func createDNSServer(config dnsConfig, router *mesh.Router, isKnownPeer func(mesh.PeerName) bool) (*nameserver.Nameserver, *nameserver.DNSServer) {
ns := nameserver.New(router.Ourself.Peer.Name, config.Domain, isKnownPeer)
router.Peers.OnGC(func(peer *mesh.Peer) { ns.PeerGone(peer.Name) })
ns.SetGossip(router.NewGossip("nameserver", ns))
upstream := nameserver.NewUpstream(config.ResolvConf, config.EffectiveListenAddress)
dnsserver, err := nameserver.NewDNSServer(ns, config.Domain, config.ListenAddress,
upstream, uint32(config.TTL), config.ClientTimeout)
if err != nil {
Log.Fatal("Unable to start dns server: ", err)
}
listenAddr := config.ListenAddress
if config.EffectiveListenAddress != "" {
listenAddr = config.EffectiveListenAddress
}
Log.Println("Listening for DNS queries on", listenAddr)
return ns, dnsserver
}
// Pick a quorum size based on the number of peer addresses.
func determineQuorum(initPeerCountFlag int, router *weave.NetworkRouter) uint {
if initPeerCountFlag > 0 {
return uint(initPeerCountFlag/2 + 1)
}
peers := router.ConnectionMaker.Targets(true)
// Guess a suitable quorum size based on the list of peer
// addresses. The peer list might or might not contain an
// address for this peer, so the conservative assumption is
// that it doesn't. The list might contain multiple addresses
// that resolve to the same peer, in which case the quorum
// might be larger than it needs to be. But the user can
// specify it explicitly if that becomes a problem.
clusterSize := uint(len(peers) + 1)
quorum := clusterSize/2 + 1
Log.Println("Assuming quorum size of", quorum)
return quorum
}
func determinePassword(password string) []byte {
if password == "" {
password = os.Getenv("WEAVE_PASSWORD")
}
if password == "" {
Log.Println("Communication between peers is unencrypted.")
return nil
}
Log.Println("Communication between peers via untrusted networks is encrypted.")
return []byte(password)
}
func peerName(routerName string, iface *net.Interface) mesh.PeerName {
if routerName == "" {
if iface == nil {
Log.Fatal("Either an interface must be specified with --datapath or --iface, or a name with --name")
}
routerName = iface.HardwareAddr.String()
}
name, err := mesh.PeerNameFromUserInput(routerName)
checkFatal(err)
return name
}
func parseTrustedSubnets(trustedSubnetStr string) []*net.IPNet {
trustedSubnets := []*net.IPNet{}
if trustedSubnetStr == "" {
return trustedSubnets
}
for _, subnetStr := range strings.Split(trustedSubnetStr, ",") {
_, subnet, err := net.ParseCIDR(subnetStr)
if err != nil {
Log.Fatal("Unable to parse trusted subnets: ", err)
}
trustedSubnets = append(trustedSubnets, subnet)
}
return trustedSubnets
}
func parsePeerNames(s string) ([]mesh.PeerName, error) {
peerNames := []mesh.PeerName{}
if s == "" {
return peerNames, nil
}
for _, peerNameStr := range strings.Split(s, ",") {
peerName, err := mesh.PeerNameFromUserInput(peerNameStr)
if err != nil {
return nil, fmt.Errorf("error parsing peer names: %s", err)
}
peerNames = append(peerNames, peerName)
}
return peerNames, nil
}
func listenAndServeHTTP(httpAddr string, handler http.Handler) {
protocol := "tcp"
if strings.HasPrefix(httpAddr, "/") {
os.Remove(httpAddr) // in case it's there from last time
protocol = "unix"
}
l, err := net.Listen(protocol, httpAddr)
if err != nil {
Log.Fatal("Unable to create http listener socket: ", err)
}
err = http.Serve(l, handler)
if err != nil {
Log.Fatal("Unable to create http server", err)
}
}
func checkFatal(e error) {
if e != nil {
Log.Fatal(e)
}
}