diff --git a/Dockerfile b/Dockerfile index e4e75a7..0ca8f1a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,20 +1,24 @@ -FROM golang:alpine as builder +FROM golang:1.12-alpine as builder + +ENV GO111MODULE=on + RUN apk update && \ apk upgrade && \ apk add --no-cache git && \ apk add make -RUN mkdir -p /opt/gocast -RUN mkdir -p /go/src/github.com/mayuresh82 -RUN cd /go/src/github.com/mayuresh82 && \ - git clone https://github.com/mayuresh82/gocast +RUN mkdir -p /go/src/github.com/mayuresh82/gocast + +COPY . /go/src/github.com/mayuresh82/gocast + WORKDIR /go/src/github.com/mayuresh82/gocast + +RUN go mod download RUN make -RUN cp gocast /opt/gocast/ FROM alpine:latest RUN apk --no-cache add ca-certificates bash iptables netcat-openbsd sudo WORKDIR /root/ -COPY --from=builder /opt/gocast/gocast . +COPY --from=builder /go/src/github.com/mayuresh82/gocast . EXPOSE 8080/tcp diff --git a/Makefile b/Makefile index 50d742e..3b74e3d 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,23 @@ .PHONY: all gocast test +DOCKER_IMAGE := mayuresh82/gocast +VERSION := $(shell git describe --exact-match --tags 2>/dev/null) +BRANCH := $(shell git rev-parse --abbrev-ref HEAD) +COMMIT := $(shell git rev-parse --short HEAD) +DOCKER_TAG := $(COMMIT) +LDFLAGS := $(LDFLAGS) -X main.commit=$(COMMIT) -X main.branch=$(BRANCH) +ifdef VERSION + LDFLAGS += -X main.version=$(VERSION) + DOCKER_TAG = $(VERSION) +endif + all: - $(MAKE) deps $(MAKE) gocast -deps: - go get -u golang.org/x/lint/golint - go get -u github.com/golang/dep/cmd/dep - dep ensure - gocast: - go build . + go build -ldflags "$(LDFLAGS)" . debug: - dep ensure go build -race . test: diff --git a/config.yaml b/config.yaml index f9ccf23..d6f4090 100644 --- a/config.yaml +++ b/config.yaml @@ -11,12 +11,12 @@ agent: consul_query_interval: 5m bgp: - local_as: 12345 - remote_as: 6789 - # override the peer IP to use instead of auto discovering - peer_ip: 10.10.10.1 - communities: - - asn:nnnn - - asn:nnnn - origin: igp - + - local_as: 12345 + remote_as: 6789 + # override the peer IP to use instead of auto discovering + peer_ip: 10.10.10.1 + communities: + - asn:nnnn + - asn:nnnn + origin: igp + addr_family: "4" \ No newline at end of file diff --git a/config/config.go b/config/config.go index aea31a5..26a645b 100644 --- a/config/config.go +++ b/config/config.go @@ -1,11 +1,12 @@ package config import ( - "github.com/golang/glog" - "gopkg.in/yaml.v2" "io/ioutil" "path/filepath" "time" + + "github.com/golang/glog" + "gopkg.in/yaml.v2" ) type Config struct { @@ -16,12 +17,13 @@ type Config struct { ConsulAddr string `yaml:"consul_addr"` ConsulQueryInterval time.Duration `yaml:"consul_query_interval"` } - Bgp struct { + Bgp []struct { LocalAS int `yaml:"local_as"` PeerAS int `yaml:"peer_as"` PeerIP string `yaml:"peer_ip"` Communities []string Origin string + AddrFamily string `yaml:"addr_family"` } Apps []struct { Name string diff --git a/controller/app.go b/controller/app.go index 5c0dce7..0544757 100644 --- a/controller/app.go +++ b/controller/app.go @@ -2,17 +2,19 @@ package controller import ( "fmt" - "github.com/golang/glog" "net" "strings" + + "github.com/golang/glog" ) type MonitorType int const ( - Monitor_PORT MonitorType = 1 - Monitor_EXEC MonitorType = 2 - Monitor_CONSUL MonitorType = 3 + Monitor_PORT MonitorType = 1 + Monitor_EXEC MonitorType = 2 + Monitor_CONSUL MonitorType = 3 + defaultFailThreshold = 3 ) var MonitorMap = map[string]MonitorType{"port": Monitor_PORT, "exec": Monitor_EXEC, "consul": Monitor_CONSUL} @@ -27,10 +29,11 @@ func (m MonitorType) String() string { } type Monitor struct { - Type MonitorType - Port string - Protocol string - Cmd string + Type MonitorType + Port string + Protocol string + Cmd string + FailCount int } func (m *Monitor) Equal(other *Monitor) bool { @@ -48,9 +51,19 @@ func (m Monitors) Contains(elem *Monitor) bool { return false } +type Vip struct { + IP net.IP + Net *net.IPNet + Family string +} + +func (v *Vip) Equal(other *Vip) bool { + return v.IP.Equal(other.IP) +} + type App struct { Name string - Vip *net.IPNet + Vip *Vip Monitors Monitors Nats []string } @@ -64,7 +77,7 @@ func (a *App) Equal(other *App) bool { return false } } - return a.Name == other.Name && a.Vip.String() == other.Vip.String() + return a.Name == other.Name && a.Vip.Equal(other.Vip) } func NewApp(appName, vip string, monitors []string, nats []string) (*App, error) { @@ -72,11 +85,16 @@ func NewApp(appName, vip string, monitors []string, nats []string) (*App, error) return nil, fmt.Errorf("Invalid app name") } app := &App{Name: appName, Nats: nats} - _, ipnet, err := net.ParseCIDR(vip) + ip, ipnet, err := net.ParseCIDR(vip) if err != nil { return nil, fmt.Errorf("Invalid VIP specified, need ip/mask") } - app.Vip = ipnet + app.Vip = &Vip{IP: ip, Net: ipnet} + if ip.To4() != nil { + app.Vip.Family = "4" + } else { + app.Vip.Family = "6" + } for _, m := range monitors { // valid monitor formats: // "port:tcp:123" , "exec:/local/check.sh", "consul" diff --git a/controller/bgp.go b/controller/bgp.go index ba44fd1..3537b16 100644 --- a/controller/bgp.go +++ b/controller/bgp.go @@ -3,160 +3,214 @@ package controller import ( "context" "fmt" + "net" + "strconv" + "strings" + "github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes/any" c "github.com/mayuresh82/gocast/config" api "github.com/osrg/gobgp/api" gobgp "github.com/osrg/gobgp/pkg/server" - "net" - "strconv" - "strings" ) -type Controller struct { +type Peer struct { peerAS int localIP, peerIP net.IP communities []string origin uint32 multiHop bool - s *gobgp.BgpServer + family string + announced []string +} + +type Controller struct { + peers []*Peer + s *gobgp.BgpServer } func NewController(config *c.Config) (*Controller, error) { - c := &Controller{} - var gw net.IP - var err error - if config.Bgp.PeerIP == "" { - gw, err = gateway() - c.peerIP = gw - } else { - c.peerIP = net.ParseIP(config.Bgp.PeerIP) - gw, err = via(c.peerIP) - } - if err != nil || c.peerIP == nil { - return nil, fmt.Errorf("Unable to get peer IP : %v", err) - } - c.communities = config.Bgp.Communities - switch config.Bgp.Origin { - case "igp": - c.origin = 0 - case "egp": - c.origin = 1 - case "unknown": - c.origin = 2 - } - s := gobgp.NewBgpServer() - go s.Serve() - localAddr, err := localAddress(gw) - if err != nil { - return nil, err - } - c.localIP = localAddr - if err := s.StartBgp(context.Background(), &api.StartBgpRequest{ - Global: &api.Global{ - As: uint32(config.Bgp.LocalAS), - RouterId: localAddr.String(), - ListenPort: -1, // gobgp won't listen on tcp:179 - }, - }); err != nil { - return nil, fmt.Errorf("Unable to start bgp: %v", err) - } - c.s = s - c.peerAS = config.Bgp.PeerAS - // set mh by default for all ebgp peers - if c.peerAS != config.Bgp.LocalAS { - c.multiHop = true + c := &Controller{s: gobgp.NewBgpServer()} + go c.s.Serve() + for _, bgpConf := range config.Bgp { + p := &Peer{} + var err error + if bgpConf.PeerIP == "" { + p.peerIP, err = gateway(bgpConf.AddrFamily) + } else { + p.peerIP = net.ParseIP(bgpConf.PeerIP) + } + if err != nil || p.peerIP == nil { + return nil, fmt.Errorf("Unable to get peer IP : %v", err) + } + p.communities = bgpConf.Communities + switch bgpConf.Origin { + case "igp": + p.origin = 0 + case "egp": + p.origin = 1 + case "unknown": + p.origin = 2 + } + dev, err := via(p.peerIP) + if err != nil { + return nil, err + } + localAddr, err := localAddress(dev, bgpConf.AddrFamily) + if err != nil { + return nil, err + } + p.localIP = localAddr + p.peerAS = bgpConf.PeerAS + // set mh by default for all ebgp peers + if p.peerAS != bgpConf.LocalAS { + p.multiHop = true + } + localAddr4, _ := localAddress(dev, "4") // for router-id + if err := c.s.StartBgp(context.Background(), &api.StartBgpRequest{ + Global: &api.Global{ + As: uint32(bgpConf.LocalAS), + RouterId: localAddr4.String(), + ListenPort: -1, // gobgp won't listen on tcp:179 + }, + }); err != nil { + return nil, fmt.Errorf("Unable to start bgp: %v", err) + } + p.family = "6" + if p.peerIP.To4() != nil { + p.family = "4" + } + c.peers = append(c.peers, p) } return c, nil } -func (c *Controller) AddPeer(peer string) error { +func (c *Controller) localIP(family string) net.IP { + for _, peer := range c.peers { + if peer.family == family { + return peer.localIP + } + } + return nil +} + +func (c *Controller) AddPeer(p *Peer) error { n := &api.Peer{ Conf: &api.PeerConf{ - NeighborAddress: peer, - PeerAs: uint32(c.peerAS), + NeighborAddress: p.peerIP.String(), + PeerAs: uint32(p.peerAS), }, } - if c.multiHop { + if p.multiHop { n.EbgpMultihop = &api.EbgpMultihop{Enabled: true, MultihopTtl: uint32(255)} } return c.s.AddPeer(context.Background(), &api.AddPeerRequest{Peer: n}) } -func (c *Controller) getApiPath(route *net.IPNet) *api.Path { +func (c *Controller) getApiPath(p *Peer, route *net.IPNet, withdraw bool) *api.Path { afi := api.Family_AFI_IP if route.IP.To4() == nil { afi = api.Family_AFI_IP6 } + family := &api.Family{Afi: afi, Safi: api.Family_SAFI_UNICAST} prefixlen, _ := route.Mask.Size() nlri, _ := ptypes.MarshalAny(&api.IPAddressPrefix{ Prefix: route.IP.String(), PrefixLen: uint32(prefixlen), }) a1, _ := ptypes.MarshalAny(&api.OriginAttribute{ - Origin: c.origin, - }) - a2, _ := ptypes.MarshalAny(&api.NextHopAttribute{ - NextHop: c.localIP.String(), + Origin: p.origin, }) var communities []uint32 - for _, comm := range c.communities { + for _, comm := range p.communities { communities = append(communities, convertCommunity(comm)) } - a3, _ := ptypes.MarshalAny(&api.CommunitiesAttribute{ + a2, _ := ptypes.MarshalAny(&api.CommunitiesAttribute{ Communities: communities, }) - attrs := []*any.Any{a1, a2, a3} - return &api.Path{ - Family: &api.Family{Afi: afi, Safi: api.Family_SAFI_UNICAST}, - AnyNlri: nlri, - AnyPattrs: attrs, + attrs := []*any.Any{a1, a2} + path := &api.Path{AnyNlri: nlri, Family: family, AnyPattrs: attrs, IsWithdraw: withdraw} + switch afi { + case api.Family_AFI_IP: + nh, _ := ptypes.MarshalAny(&api.NextHopAttribute{ + NextHop: p.localIP.String(), + }) + path.AnyPattrs = append(path.AnyPattrs, nh) + case api.Family_AFI_IP6: + mpReachAttr, _ := ptypes.MarshalAny(&api.MpReachNLRIAttribute{ + Family: family, + NextHops: []string{p.localIP.String()}, + Nlris: []*any.Any{nlri}, + }) + mpUnreachAttr, _ := ptypes.MarshalAny(&api.MpUnreachNLRIAttribute{ + Family: family, + Nlris: []*any.Any{nlri}, + }) + if withdraw { + path.AnyPattrs = append(path.AnyPattrs, mpUnreachAttr) + } else { + path.AnyPattrs = append(path.AnyPattrs, mpReachAttr) + } } + return path } func (c *Controller) Announce(route *net.IPNet) error { - peers, err := c.s.ListPeer(context.Background(), &api.ListPeerRequest{}) - if err != nil { - return err + family := "6" + if route.IP.To4() != nil { + family = "4" } - var found bool - for _, p := range peers { - if p.Conf.NeighborAddress == c.peerIP.String() { - found = true - break + for _, peer := range c.peers { + if peer.family != family { + continue } - } - if !found { - if err := c.AddPeer(c.peerIP.String()); err != nil { + peers, err := c.s.ListPeer(context.Background(), &api.ListPeerRequest{}) + if err != nil { return err } + var found bool + for _, p := range peers { + if p.Conf.NeighborAddress == peer.peerIP.String() { + found = true + break + } + } + if !found { + if err := c.AddPeer(peer); err != nil { + return err + } + } + if _, err := c.s.AddPath(context.Background(), &api.AddPathRequest{Path: c.getApiPath(peer, route, false)}); err != nil { + return err + } + peer.announced = append(peer.announced, route.String()) } - _, err = c.s.AddPath(context.Background(), &api.AddPathRequest{Path: c.getApiPath(route)}) - return err + return nil } func (c *Controller) Withdraw(route *net.IPNet) error { - return c.s.DeletePath(context.Background(), &api.DeletePathRequest{Path: c.getApiPath(route)}) -} - -func (c *Controller) PeerInfo() (*api.Peer, error) { - peers, err := c.s.ListPeer(context.Background(), &api.ListPeerRequest{}) - if err != nil { - return nil, err - } - for _, p := range peers { - if p.Conf.NeighborAddress == c.peerIP.String() { - return p, nil + for _, peer := range c.peers { + if !contains(peer.announced, route.String()) { + continue + } + if err := c.s.DeletePath(context.Background(), &api.DeletePathRequest{Path: c.getApiPath(peer, route, true)}); err != nil { + return err } } - return nil, nil + return nil +} + +func (c *Controller) PeerInfo() ([]*api.Peer, error) { + return c.s.ListPeer(context.Background(), &api.ListPeerRequest{}) } func (c *Controller) Shutdown() error { - if err := c.s.ShutdownPeer(context.Background(), &api.ShutdownPeerRequest{ - Address: c.peerIP.String(), - }); err != nil { - return err + for _, peer := range c.peers { + if err := c.s.ShutdownPeer(context.Background(), &api.ShutdownPeerRequest{ + Address: peer.peerIP.String(), + }); err != nil { + return err + } } if err := c.s.StopBgp(context.Background(), &api.StopBgpRequest{}); err != nil { return err diff --git a/controller/monitor.go b/controller/monitor.go index 3969a46..ae8265e 100644 --- a/controller/monitor.go +++ b/controller/monitor.go @@ -2,18 +2,19 @@ package controller import ( "fmt" - "github.com/golang/glog" - c "github.com/mayuresh82/gocast/config" - api "github.com/osrg/gobgp/api" "net" "os/exec" "strings" "sync" "time" + + "github.com/golang/glog" + c "github.com/mayuresh82/gocast/config" + api "github.com/osrg/gobgp/api" ) const ( - defaultMonitorInterval = 10 * time.Second + defaultMonitorInterval = 20 * time.Second defaultCleanupTimer = 15 * time.Minute ) @@ -53,12 +54,14 @@ func execMonitor(cmd string) bool { } type appMon struct { - app *App - done chan bool - announced bool - checkOn bool + app *App + done chan bool + announced bool + vipCreated bool + checkOn bool } +// MonitorMgr manages all registered apps and their healthcheck monitors type MonitorMgr struct { monitors map[string]*appMon cleanups map[string]chan bool @@ -69,6 +72,7 @@ type MonitorMgr struct { sync.Mutex } +// NewMonitor returns a new instance of MonitorMgr func NewMonitor(config *c.Config) *MonitorMgr { ctrl, err := NewController(config) if err != nil { @@ -141,6 +145,7 @@ func (m *MonitorMgr) consulMon() { } } +// Add adds a new app to be monitored func (m *MonitorMgr) Add(app *App) { // check if already running m.Lock() @@ -150,8 +155,8 @@ func (m *MonitorMgr) Add(app *App) { glog.V(2).Infof("App %s already exists", app.Name) return } - if appMon.app.Vip.String() == app.Vip.String() && appMon.app.Name != app.Name { - glog.Errorf("Error: Vip %s is already being announced by app: %s", app.Vip.String(), appMon.app.Name) + if appMon.app.Vip.Equal(app.Vip) && appMon.app.Name != app.Name { + glog.Errorf("Error: Vip %s is already being announced by app: %s", app.Vip.IP.String(), appMon.app.Name) return } } @@ -162,16 +167,20 @@ func (m *MonitorMgr) Add(app *App) { glog.Infof("Registered a new app: %v", app) } +// Remove removes an existing app and withdraws the bgp vip func (m *MonitorMgr) Remove(appName string) { if a, ok := m.monitors[appName]; ok { if a.checkOn { a.done <- true } if a.announced { - if err := m.ctrl.Withdraw(a.app.Vip); err != nil { + if err := m.ctrl.Withdraw(a.app.Vip.Net); err != nil { glog.Errorf("Failed to withdraw route: %v", err) } } + if !a.vipCreated { + return + } if err := deleteLoopback(a.app.Vip); err != nil { glog.Errorf("Failed to remove app: %s: %v", a.app.Name, err) } @@ -180,7 +189,12 @@ func (m *MonitorMgr) Remove(appName string) { if len(parts) != 2 { continue } - if err := natRule("D", a.app.Vip.IP, m.ctrl.localIP, parts[0], parts[1]); err != nil { + localIP := m.ctrl.localIP(a.app.Vip.Family) + if localIP == nil { + glog.Errorf("Failed to get local IP for family %s", a.app.Vip.Family) + continue + } + if err := natRule("D", a.app.Vip.IP, localIP, parts[0], parts[1]); err != nil { glog.Errorf("Failed to remove app: %s: %v", a.app.Name, err) } } @@ -204,9 +218,16 @@ func (m *MonitorMgr) runMonitors(app *App) bool { } if !check { glog.V(2).Infof("%s Monitor for app: %s Failed", mon.Type.String(), app.Name) - return false + if mon.FailCount >= defaultFailThreshold { + return false + } + mon.FailCount++ + } + if check { + mon.FailCount = 0 } } + glog.V(2).Infof("All Monitors for app: %s succeeded", app.Name) return true } @@ -215,8 +236,7 @@ func (m *MonitorMgr) checkCond(am *appMon) error { m.Lock() defer m.Unlock() if m.runMonitors(app) { - glog.V(2).Infof("All Monitors for app: %s succeeded", app.Name) - if !am.announced { + if !am.vipCreated { if err := addLoopback(app.Name, app.Vip); err != nil { return err } @@ -225,11 +245,19 @@ func (m *MonitorMgr) checkCond(am *appMon) error { if len(parts) != 2 { continue } - if err := natRule("A", app.Vip.IP, m.ctrl.localIP, parts[0], parts[1]); err != nil { + localIP := m.ctrl.localIP(app.Vip.Family) + if localIP == nil { + glog.Errorf("Failed to get local IP for family %s", app.Vip.Family) + continue + } + if err := natRule("A", app.Vip.IP, localIP, parts[0], parts[1]); err != nil { return err } } - if err := m.ctrl.Announce(app.Vip); err != nil { + am.vipCreated = true + } + if !am.announced { + if err := m.ctrl.Announce(app.Vip.Net); err != nil { return fmt.Errorf("Failed to announce route: %v", err) } am.announced = true @@ -239,7 +267,7 @@ func (m *MonitorMgr) checkCond(am *appMon) error { } } else { if am.announced { - if err := m.ctrl.Withdraw(app.Vip); err != nil { + if err := m.ctrl.Withdraw(app.Vip.Net); err != nil { return fmt.Errorf("Failed to withdraw route: %v", err) } am.announced = false @@ -271,6 +299,7 @@ func (m *MonitorMgr) runLoop(am *appMon) { } } +// CloseAll closes all open bgp sessions and cleans up all apps and their VIPs func (m *MonitorMgr) CloseAll() { glog.Infof("Shutting down all open bgp sessions") if err := m.ctrl.Shutdown(); err != nil { @@ -286,11 +315,17 @@ func (m *MonitorMgr) CloseAll() { if len(parts) != 2 { continue } - natRule("D", am.app.Vip.IP, m.ctrl.localIP, parts[0], parts[1]) + localIP := m.ctrl.localIP(am.app.Vip.Family) + if localIP == nil { + glog.Errorf("Failed to get local IP for family %s", am.app.Vip.Family) + continue + } + natRule("D", am.app.Vip.IP, localIP, parts[0], parts[1]) } } } +// Cleanup waits for cleanuptimer to expire and then removes the app func (m *MonitorMgr) Cleanup(app string, exit chan bool) { t := time.NewTimer(m.config.Agent.CleanupTimer) defer t.Stop() @@ -307,6 +342,7 @@ func (m *MonitorMgr) Cleanup(app string, exit chan bool) { } } -func (m *MonitorMgr) GetInfo() (*api.Peer, error) { +// GetInfo returns BGP peer info for a specific peer +func (m *MonitorMgr) GetInfo() ([]*api.Peer, error) { return m.ctrl.PeerInfo() } diff --git a/controller/system.go b/controller/system.go index 471d8c0..e55ebba 100644 --- a/controller/system.go +++ b/controller/system.go @@ -7,8 +7,12 @@ import ( "strings" ) -func gateway() (net.IP, error) { - cmd := `ip route | grep "^default" | cut -d" " -f3` +func gateway(family string) (net.IP, error) { + ipCmd := "ip" + if family == "6" { + ipCmd = "ip -6" + } + cmd := fmt.Sprintf(`%s route | grep "^default" | cut -d" " -f3`, ipCmd) out, err := exec.Command("bash", "-c", cmd).Output() if err != nil { return nil, fmt.Errorf("Failed to execute command: %s", cmd) @@ -16,40 +20,59 @@ func gateway() (net.IP, error) { return net.ParseIP(strings.TrimSpace(string(out))), nil } -func via(dest net.IP) (net.IP, error) { - cmd := fmt.Sprintf(`ip route get %s | grep via | cut -d" " -f3`, dest.String()) +func via(dest net.IP) (string, error) { + ipCmd := "ip" + if dest.To4() == nil { + ipCmd = "ip -6" + } + cmd := fmt.Sprintf(`%s route get %s | grep src | cut -d" " -f3`, ipCmd, dest.String()) out, err := exec.Command("bash", "-c", cmd).Output() if err != nil { - return nil, fmt.Errorf("Failed to execute command: %s", cmd) + return "", fmt.Errorf("Failed to execute command: %s", cmd) } - return net.ParseIP(strings.TrimSpace(string(out))), nil + return strings.TrimSpace(string(out)), nil } -func localAddress(gw net.IP) (net.IP, error) { - addrs, err := net.InterfaceAddrs() +func localAddress(dev string, family string) (net.IP, error) { + iface, err := net.InterfaceByName(dev) + if err != nil { + return nil, err + } + addrs, err := iface.Addrs() if err != nil { return nil, err } for _, addr := range addrs { + var ip net.IP switch v := addr.(type) { case *net.IPNet: - if v.Contains(gw) { - return v.IP, nil - } + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + if family == "4" && ip.To4() != nil { + return ip, nil + } + if family == "6" && ip.To4() == nil && !ip.IsLinkLocalUnicast() { + return ip, nil } } return nil, fmt.Errorf("Unable to find local address") } -func addLoopback(name string, addr *net.IPNet) error { - deleteLoopback(addr) - prefixLen, _ := addr.Mask.Size() +func addLoopback(name string, vip *Vip) error { + deleteLoopback(vip) + prefixLen, _ := vip.Net.Mask.Size() label := fmt.Sprintf("lo:%s", name) // linux kernel limits labels to 15 chars if len(label) > 15 { label = label[:15] } - cmd := fmt.Sprintf("ip address add %s/%d dev lo label %s", addr.IP.String(), prefixLen, label) + ipCmd := "ip" + if vip.Family == "6" { + ipCmd = "ip -6" + } + cmd := fmt.Sprintf("%s address add %s/%d dev lo label %s", ipCmd, vip.IP.String(), prefixLen, label) _, err := exec.Command("bash", "-c", cmd).Output() if err != nil { return fmt.Errorf("Failed to Add loopback command: %s: %v", cmd, err) @@ -57,9 +80,13 @@ func addLoopback(name string, addr *net.IPNet) error { return nil } -func deleteLoopback(addr *net.IPNet) error { - prefixLen, _ := addr.Mask.Size() - cmd := fmt.Sprintf("ip address delete %s/%d dev lo", addr.IP.String(), prefixLen) +func deleteLoopback(vip *Vip) error { + prefixLen, _ := vip.Net.Mask.Size() + ipCmd := "ip" + if vip.Family == "6" { + ipCmd = "ip -6" + } + cmd := fmt.Sprintf("%s address delete %s/%d dev lo", ipCmd, vip.IP.String(), prefixLen) _, err := exec.Command("bash", "-c", cmd).Output() if err != nil { return fmt.Errorf("Failed to delete loopback command: %s: %v", cmd, err) @@ -68,9 +95,15 @@ func deleteLoopback(addr *net.IPNet) error { } func natRule(op string, vip, localAddr net.IP, protocol, port string) error { + iptCmd := "iptables" + toDest := fmt.Sprintf("%s:%s", localAddr.String(), port) + if vip.To4() == nil { + iptCmd = "ip6tables" + toDest = fmt.Sprintf("[%s]:%s", localAddr.String(), port) + } cmd := fmt.Sprintf( - "iptables -t nat -%s PREROUTING -p %s -d %s --dport %s -j DNAT --to-destination %s:%s", - op, protocol, vip.String(), port, localAddr.String(), port, + "%s -t nat -%s PREROUTING -p %s -d %s --dport %s -j DNAT --to-destination %s", + iptCmd, op, protocol, vip.String(), port, toDest, ) _, err := exec.Command("bash", "-c", cmd).Output() if err != nil { diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..31186af --- /dev/null +++ b/go.mod @@ -0,0 +1,29 @@ +module github.com/mayuresh82/gocast + +go 1.12 + +require ( + github.com/armon/go-radix v1.0.0 // indirect + github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 // indirect + github.com/eapache/channels v1.1.0 // indirect + github.com/eapache/queue v1.1.0 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b + github.com/golang/protobuf v1.2.0 + github.com/influxdata/influxdb v1.6.4 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.1.2 // indirect + github.com/osrg/gobgp v0.0.0-20180929145215-329c2d316efe + github.com/satori/go.uuid v0.0.0-20181016184021-8ccf5352a842 // indirect + github.com/sirupsen/logrus v1.1.1 + github.com/spf13/pflag v1.0.3 // indirect + github.com/spf13/viper v0.0.0-20180930044127-62edee319679 // indirect + github.com/vishvananda/netlink v0.0.0-20181018205019-d3a23fd178f1 // indirect + github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc // indirect + golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e // indirect + golang.org/x/net v0.0.0-20181017193950-04a2e542c03f // indirect + golang.org/x/sys v0.0.0-20181021155630-eda9bb28ed51 // indirect + google.golang.org/genproto v0.0.0-20181016170114-94acd270e44e // indirect + google.golang.org/grpc v1.15.0 // indirect + gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect + gopkg.in/yaml.v2 v2.2.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4fed646 --- /dev/null +++ b/go.sum @@ -0,0 +1,93 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 h1:afESQBXJEnj3fu+34X//E8Wg3nEbMJxJkwSc0tPePK0= +github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k= +github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v0.0.0-20181022004443-7be363195599/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/influxdata/influxdb v1.6.4 h1:K8wPlkrP02HzHTJbbUQQ1CZ2Hw6LtpG4xbNEgnlhMZU= +github.com/influxdata/influxdb v1.6.4/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/osrg/gobgp v0.0.0-20180929145215-329c2d316efe h1:KuFjGka+ETeoDzymQaoF3leDNaIs7RZVwOLD8UR9OUM= +github.com/osrg/gobgp v0.0.0-20180929145215-329c2d316efe/go.mod h1:vGVJPLW6JFDD7WA1vJsjB8OKmbbC2TKwHtr90CZS/u4= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/satori/go.uuid v0.0.0-20181016184021-8ccf5352a842 h1:FnHGUoRWCQGG7mgyYKfpi6DM0hamU/OhJ3KQwE9V4JY= +github.com/satori/go.uuid v0.0.0-20181016184021-8ccf5352a842/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sirupsen/logrus v1.1.1 h1:VzGj7lhU7KEB9e9gMpAV/v5XT2NVSvLJhJLCWbnkgXg= +github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg= +github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v0.0.0-20180930044127-62edee319679 h1:77hFT1rWlkgnjOELbbHqfcJlUj4dsFj1+Y/Dw778Tuc= +github.com/spf13/viper v0.0.0-20180930044127-62edee319679/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/vishvananda/netlink v0.0.0-20181018205019-d3a23fd178f1 h1:JpigzxrBek5A4uli75H58EakZya3hdzLssY4gTUMplw= +github.com/vishvananda/netlink v0.0.0-20181018205019-d3a23fd178f1/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e h1:IzypfodbhbnViNUO/MEh0FzCUooG97cIGfdggUrUSyU= +golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181017193950-04a2e542c03f h1:4pRM7zYwpBjCnfA1jRmhItLxYJkaEnsmuAcRtA347DA= +golang.org/x/net v0.0.0-20181017193950-04a2e542c03f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181021155630-eda9bb28ed51 h1:GNXpDwiINQORfoRpKYZBUNeIGY4giY2DonS5etRdlnE= +golang.org/x/sys v0.0.0-20181021155630-eda9bb28ed51/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181016170114-94acd270e44e h1:I5s8aUkxqPjgAssfOv+dVr+4/7BC40WV6JhcVoORltI= +google.golang.org/genproto v0.0.0-20181016170114-94acd270e44e/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.15.0 h1:Az/KuahOM4NAidTEuJCv/RonAA7rYsTPkqXVjr+8OOw= +google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs= +gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index 125a644..ce2eee4 100644 --- a/main.go +++ b/main.go @@ -3,20 +3,33 @@ package main import ( "context" "flag" + "fmt" + "os" + "os/signal" + "syscall" + "github.com/golang/glog" c "github.com/mayuresh82/gocast/config" "github.com/mayuresh82/gocast/controller" "github.com/mayuresh82/gocast/server" log "github.com/sirupsen/logrus" - "os" - "os/signal" - "syscall" ) var ( - config = flag.String("config", "", "Path to config file") + config = flag.String("config", "", "Path to config file") + nextVersion = "0.0.1" + version string + commit string + branch string ) +func getVersion() string { + if version == "" { + return fmt.Sprintf("v%s~%s", nextVersion, commit) + } + return fmt.Sprintf("%s~%s", version, commit) +} + func main() { flag.Parse() if glog.V(4) { @@ -26,8 +39,11 @@ func main() { mon := controller.NewMonitor(conf) srv := server.NewServer(conf.Agent.ListenAddr, mon) + glog.Infof("Starting GoCast %s", getVersion()) ctx, cancel := context.WithCancel(context.Background()) + go srv.Serve(ctx) // catch interrupt + shutdown := make(chan struct{}) signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, os.Interrupt, syscall.SIGHUP, syscall.SIGTERM) go func() { @@ -36,9 +52,10 @@ func main() { if sig == os.Interrupt || sig == syscall.SIGTERM { mon.CloseAll() cancel() + shutdown <- struct{}{} return } } }() - srv.Serve(ctx) + <-shutdown }