mirror of
https://github.com/luscis/openlan.git
synced 2025-09-27 12:52:16 +08:00
fea:support nexthop group for routing ha (#57)
This commit is contained in:
23
dist/rootfs/etc/openlan/switch/network/nextgroup.json.example
vendored
Normal file
23
dist/rootfs/etc/openlan/switch/network/nextgroup.json.example
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "default",
|
||||
"provider": "openlan",
|
||||
"bridge": {
|
||||
"address": "172.32.99.40/24"
|
||||
},
|
||||
"subnet": {
|
||||
"startAt": "172.32.99.250",
|
||||
"endAt": "172.32.99.254",
|
||||
"netmask": "255.255.255.0"
|
||||
},
|
||||
"routes": [
|
||||
{
|
||||
"prefix": "172.32.10.0/24"
|
||||
"nextgroup": "ng1"
|
||||
}
|
||||
],
|
||||
"openvpn": {
|
||||
"listen": "0.0.0.0:3294",
|
||||
"subnet": "172.32.194.0/24"
|
||||
},
|
||||
"acl": "acl-1"
|
||||
}
|
14
dist/rootfs/etc/openlan/switch/nextgroup/nextgroup.json.example
vendored
Normal file
14
dist/rootfs/etc/openlan/switch/nextgroup/nextgroup.json.example
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"ng1": {
|
||||
"check": "ping",
|
||||
"ping": {
|
||||
"count": 5,
|
||||
"loss": 2
|
||||
},
|
||||
"mode": "active-backup",
|
||||
"nexthop": [
|
||||
"192.168.33.11",
|
||||
"192.168.33.10"
|
||||
]
|
||||
}
|
||||
}
|
4
dist/rootfs/etc/openlan/switch/route/example.nextgroup.json.example
vendored
Normal file
4
dist/rootfs/etc/openlan/switch/route/example.nextgroup.json.example
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
[
|
||||
{"prefix": "172.18.22.0/24", "nextgroup": "ng1"},
|
||||
{"prefix": "172.18.44.0/24", "nextgroup": "ng1"}
|
||||
]
|
13
pkg/cache/network.go
vendored
13
pkg/cache/network.go
vendored
@@ -54,6 +54,19 @@ func (w *network) ListRoute(name string) <-chan *models.Route {
|
||||
return c
|
||||
}
|
||||
|
||||
func (w *network) UpdateRoute(name string, rt co.PrefixRoute, call func(obj *models.Route)) {
|
||||
n := w.Get(name)
|
||||
if n != nil {
|
||||
for _, route := range n.Routes {
|
||||
if route.Prefix == rt.Prefix {
|
||||
call(route)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w *network) DelRoute(name string, rt co.PrefixRoute) {
|
||||
n := w.Get(name)
|
||||
if n != nil {
|
||||
|
@@ -26,6 +26,7 @@ type Network struct {
|
||||
ZTrust string `json:"ztrust,omitempty"`
|
||||
Qos string `json:"qos,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
NextGroup map[string]NextGroup `json:"nextgroup,omitempty"`
|
||||
}
|
||||
|
||||
func (n *Network) NewSpecifies() interface{} {
|
||||
@@ -97,6 +98,11 @@ func (n *Network) Correct(sw *Switch) {
|
||||
n.OpenVPN.Merge(obj)
|
||||
n.OpenVPN.Correct(sw)
|
||||
}
|
||||
|
||||
for key, value := range n.NextGroup {
|
||||
value.Correct()
|
||||
n.NextGroup[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Network) Dir(elem ...string) string {
|
||||
@@ -131,6 +137,15 @@ func (n *Network) LoadOutput() {
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Network) LoadNextGroup() {
|
||||
file := n.Dir("nextgroup", n.Name+".json")
|
||||
if err := libol.FileExist(file); err == nil {
|
||||
if err := libol.UnmarshalLoad(&n.NextGroup, file); err != nil {
|
||||
libol.Error("Network.LoadNextGroup... %n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Network) Save() {
|
||||
obj := *n
|
||||
obj.Routes = nil
|
||||
@@ -142,6 +157,7 @@ func (n *Network) Save() {
|
||||
n.SaveRoute()
|
||||
n.SaveLink()
|
||||
n.SaveOutput()
|
||||
n.SaveNextGroup()
|
||||
}
|
||||
|
||||
func (n *Network) SaveRoute() {
|
||||
@@ -174,6 +190,16 @@ func (n *Network) SaveOutput() {
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Network) SaveNextGroup() {
|
||||
file := n.Dir("nextgroup", n.Name+".json")
|
||||
if n.NextGroup == nil {
|
||||
return
|
||||
}
|
||||
if err := libol.MarshalSave(n.NextGroup, file, true); err != nil {
|
||||
libol.Error("Network.SaveNextGroup %s %s", n.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Network) Reload() {
|
||||
switch n.Provider {
|
||||
case "esp":
|
||||
|
23
pkg/config/nextgroup.go
Normal file
23
pkg/config/nextgroup.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package config
|
||||
|
||||
type NextGroup struct {
|
||||
Check string `json:"check"`
|
||||
Ping PingParams `json:"ping"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
NextHop []string `json:"nexthop"`
|
||||
AvailableNextHop []MultiPath `json:"availableNexthop,omitempty"`
|
||||
}
|
||||
|
||||
func (ng *NextGroup) Correct() {
|
||||
|
||||
if ng.AvailableNextHop == nil {
|
||||
ng.AvailableNextHop = []MultiPath{}
|
||||
}
|
||||
}
|
||||
|
||||
type PingParams struct {
|
||||
Count int `json:"count"`
|
||||
Loss int `json:"loss,omitempty"`
|
||||
Rtt int `json:"rtt,omitempty"`
|
||||
CheckFrequency int `json:"checkFrequency,omitempty"`
|
||||
}
|
@@ -18,6 +18,10 @@ type MultiPath struct {
|
||||
Weight int `json:"weight"`
|
||||
}
|
||||
|
||||
func (mp *MultiPath) CompareEqual(b MultiPath) bool {
|
||||
return mp.NextHop == b.NextHop
|
||||
}
|
||||
|
||||
type PrefixRoute struct {
|
||||
File string `json:"-"`
|
||||
Network string `json:"network,omitempty"`
|
||||
@@ -26,6 +30,7 @@ type PrefixRoute struct {
|
||||
MultiPath []MultiPath `json:"multipath,omitempty"`
|
||||
Metric int `json:"metric"`
|
||||
Mode string `json:"forward,omitempty"` // route or snat
|
||||
NextGroup string `json:"nextgroup,omitempty"`
|
||||
}
|
||||
|
||||
func (r *PrefixRoute) String() string {
|
||||
@@ -55,6 +60,7 @@ func (r *PrefixRoute) CorrectRoute(nexthop string) {
|
||||
if r.Mode == "" {
|
||||
r.Mode = "snat"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func CorrectRoutes(routes []PrefixRoute, nexthop string) {
|
||||
|
@@ -190,6 +190,7 @@ func (s *Switch) LoadNetworkWithData(data []byte) (*Network, error) {
|
||||
obj.LoadLink()
|
||||
obj.LoadRoute()
|
||||
obj.LoadOutput()
|
||||
obj.LoadNextGroup()
|
||||
s.Network[obj.Name] = obj
|
||||
return obj, nil
|
||||
}
|
||||
|
@@ -13,6 +13,12 @@ type Route struct {
|
||||
Metric int `json:"metric"`
|
||||
Mode string `json:"mode"`
|
||||
Origin string `json:"origin"`
|
||||
MultiPath []MultiPath `json:"multipath,omitempty"`
|
||||
}
|
||||
|
||||
type MultiPath struct {
|
||||
NextHop string `json:"nexthop"`
|
||||
Weight int `json:"weight"`
|
||||
}
|
||||
|
||||
func NewRoute(prefix string, nexthop, mode string) (this *Route) {
|
||||
|
@@ -102,12 +102,21 @@ func NewNetworkSchema(n *Network) schema.Network {
|
||||
}
|
||||
|
||||
func NewRouteSchema(route *Route) schema.PrefixRoute {
|
||||
multiPath := make([]schema.MultiPath, 0, len(route.MultiPath))
|
||||
|
||||
for _, mp := range route.MultiPath {
|
||||
multiPath = append(multiPath, schema.MultiPath{
|
||||
NextHop: mp.NextHop,
|
||||
Weight: mp.Weight,
|
||||
})
|
||||
}
|
||||
pr := schema.PrefixRoute{
|
||||
Prefix: route.Prefix,
|
||||
NextHop: route.NextHop,
|
||||
Metric: route.Metric,
|
||||
Mode: route.Mode,
|
||||
Origin: route.Origin,
|
||||
MultiPath: multiPath,
|
||||
}
|
||||
return pr
|
||||
}
|
||||
|
@@ -14,6 +14,12 @@ type PrefixRoute struct {
|
||||
Metric int `json:"metric"`
|
||||
Mode string `json:"mode"`
|
||||
Origin string `json:"origin"`
|
||||
MultiPath []MultiPath `json:"multipath,omitempty"`
|
||||
}
|
||||
|
||||
type MultiPath struct {
|
||||
NextHop string `json:"nexthop"`
|
||||
Weight int `json:"weight"`
|
||||
}
|
||||
|
||||
type Subnet struct {
|
||||
|
@@ -76,6 +76,7 @@ type WorkerImpl struct {
|
||||
table int
|
||||
br cn.Bridger
|
||||
acl *ACL
|
||||
nextgroup *NextGroup
|
||||
}
|
||||
|
||||
func NewWorkerApi(c *co.Network) *WorkerImpl {
|
||||
@@ -118,6 +119,8 @@ func (w *WorkerImpl) Initialize() {
|
||||
w.acl = NewACL(cfg.Name)
|
||||
w.acl.Initialize()
|
||||
|
||||
w.nextgroup = NewNextGroup(cfg.Name, cfg.NextGroup)
|
||||
|
||||
n := models.Network{
|
||||
Name: cfg.Name,
|
||||
IpStart: cfg.Subnet.Start,
|
||||
@@ -142,10 +145,10 @@ func (w *WorkerImpl) Initialize() {
|
||||
w.fire = cn.NewFireWallTable(cfg.Name)
|
||||
|
||||
if out, err := w.setV.Clear(); err != nil {
|
||||
w.out.Error("WorkImpl.Initialize: create ipset: %s %s", out, err)
|
||||
w.out.Error("WorkerImpl.Initialize: create ipset: %s %s", out, err)
|
||||
}
|
||||
if out, err := w.setR.Clear(); err != nil {
|
||||
w.out.Error("WorkImpl.Initialize: create ipset: %s %s", out, err)
|
||||
w.out.Error("WorkerImpl.Initialize: create ipset: %s %s", out, err)
|
||||
}
|
||||
|
||||
if cfg.ZTrust == "enable" {
|
||||
@@ -276,7 +279,7 @@ func (w *WorkerImpl) loadRoute(rt co.PrefixRoute) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if ifAddr == rt.NextHop && rt.MultiPath == nil {
|
||||
if ifAddr == rt.NextHop && rt.MultiPath == nil && rt.NextGroup == "" {
|
||||
// route's next-hop is local not install again.
|
||||
return
|
||||
}
|
||||
@@ -295,7 +298,13 @@ func (w *WorkerImpl) loadRoute(rt co.PrefixRoute) {
|
||||
nlr.Gw = net.ParseIP(rt.NextHop)
|
||||
nlr.Priority = rt.Metric
|
||||
}
|
||||
w.out.Debug("WorkerImpl.loadRoute: %s", nlr.String())
|
||||
if rt.NextGroup != "" {
|
||||
w.out.Info("WorkerImpl.loadRoute: %s , ng: %s", nlr.String(), rt.NextGroup)
|
||||
w.nextgroup.LoadRoute(rt.NextGroup, &nlr)
|
||||
return
|
||||
}
|
||||
w.out.Info("WorkerImpl.loadRoute: %s", nlr.String())
|
||||
|
||||
rt_c := rt
|
||||
promise := libol.NewPromise()
|
||||
promise.Go(func() error {
|
||||
@@ -312,7 +321,7 @@ func (w *WorkerImpl) loadRoute(rt co.PrefixRoute) {
|
||||
func (w *WorkerImpl) loadRoutes() {
|
||||
// install routes
|
||||
cfg := w.cfg
|
||||
w.out.Debug("WorkerImpl.LoadRoute: %v", cfg.Routes)
|
||||
w.out.Info("WorkerImpl.LoadRoute: %v", cfg.Routes)
|
||||
|
||||
for _, rt := range cfg.Routes {
|
||||
w.loadRoute(rt)
|
||||
@@ -343,6 +352,8 @@ func (w *WorkerImpl) Start(v api.Switcher) {
|
||||
|
||||
w.out.Info("WorkerImpl.Start")
|
||||
|
||||
w.nextgroup.Start()
|
||||
|
||||
w.loadVRF()
|
||||
w.loadRoutes()
|
||||
|
||||
@@ -504,6 +515,11 @@ func (w *WorkerImpl) unloadRoute(rt co.PrefixRoute) {
|
||||
nlr.Gw = net.ParseIP(rt.NextHop)
|
||||
nlr.Priority = rt.Metric
|
||||
}
|
||||
|
||||
if rt.NextGroup != "" {
|
||||
w.nextgroup.UnloadRoute(rt.NextGroup, &nlr)
|
||||
return
|
||||
}
|
||||
w.out.Debug("WorkerImpl.UnLoadRoute: %s", nlr.String())
|
||||
if err := nl.RouteDel(&nlr); err != nil {
|
||||
w.out.Warn("WorkerImpl.UnLoadRoute: %s", err)
|
||||
@@ -529,6 +545,9 @@ func (w *WorkerImpl) Stop() {
|
||||
w.out.Info("WorkerImpl.Stop")
|
||||
|
||||
w.fire.Stop()
|
||||
|
||||
w.nextgroup.Stop()
|
||||
|
||||
w.unloadRoutes()
|
||||
|
||||
if !(w.vpn == nil) {
|
||||
@@ -886,7 +905,7 @@ func (w *WorkerImpl) delCacheRoute(rt co.PrefixRoute) {
|
||||
if rt.Metric > 0 {
|
||||
rte.Metric = rt.Metric
|
||||
}
|
||||
if rt.NextHop != "" {
|
||||
if rt.NextGroup == "" && rt.NextHop != "" {
|
||||
rte.Origin = rt.NextHop
|
||||
}
|
||||
|
||||
@@ -894,17 +913,21 @@ func (w *WorkerImpl) delCacheRoute(rt co.PrefixRoute) {
|
||||
}
|
||||
|
||||
func (w *WorkerImpl) addCacheRoute(rt co.PrefixRoute) {
|
||||
w.out.Info("WorkerImpl.addCacheRoute: %v ", rt)
|
||||
if rt.NextHop == "" {
|
||||
w.out.Warn("WorkerImpl.AddCacheRoute: %s ", rt.Prefix)
|
||||
return
|
||||
}
|
||||
|
||||
rte := models.NewRoute(rt.Prefix, w.IfAddr(), rt.Mode)
|
||||
if rt.Metric > 0 {
|
||||
rte.Metric = rt.Metric
|
||||
}
|
||||
if rt.NextHop != "" {
|
||||
|
||||
if rt.NextGroup == "" && rt.NextHop != "" {
|
||||
rte.Origin = rt.NextHop
|
||||
}
|
||||
|
||||
cache.Network.AddRoute(w.cfg.Name, rte)
|
||||
}
|
||||
|
||||
@@ -987,7 +1010,7 @@ func (w *WorkerImpl) AddRoute(route *schema.PrefixRoute, switcher api.Switcher)
|
||||
|
||||
w.cfg.Routes = append(w.cfg.Routes, rt)
|
||||
|
||||
libol.Info("WorkerImpl.AddRoute: %v", rt)
|
||||
w.out.Info("WorkerImpl.AddRoute: %v", rt)
|
||||
|
||||
w.addIpSet(rt)
|
||||
if inet, err := libol.ParseNet(rt.Prefix); err == nil {
|
||||
|
456
pkg/switch/nextgroup.go
Normal file
456
pkg/switch/nextgroup.go
Normal file
@@ -0,0 +1,456 @@
|
||||
package cswitch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/luscis/openlan/pkg/cache"
|
||||
co "github.com/luscis/openlan/pkg/config"
|
||||
"github.com/luscis/openlan/pkg/libol"
|
||||
"github.com/luscis/openlan/pkg/models"
|
||||
nl "github.com/vishvananda/netlink"
|
||||
"net"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type NextGroup struct {
|
||||
Network string
|
||||
cfg map[string]co.NextGroup
|
||||
strategies map[string]NextGroupStrategy
|
||||
out *libol.SubLogger
|
||||
}
|
||||
|
||||
func NewNextGroup(network string, cfg map[string]co.NextGroup) *NextGroup {
|
||||
strategies := make(map[string]NextGroupStrategy, 32)
|
||||
for key, ng := range cfg {
|
||||
strategy := newCheckStrategy(key, network, ng)
|
||||
|
||||
if strategy != nil {
|
||||
strategies[key] = strategy
|
||||
}
|
||||
}
|
||||
|
||||
return &NextGroup{
|
||||
Network: network,
|
||||
cfg: cfg,
|
||||
strategies: strategies,
|
||||
out: libol.NewSubLogger("nextgroup"),
|
||||
}
|
||||
}
|
||||
|
||||
func newCheckStrategy(name string, network string, cfg co.NextGroup) NextGroupStrategy {
|
||||
switch cfg.Check {
|
||||
case "ping":
|
||||
return NewPingStrategy(name, network, &cfg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ng *NextGroup) Start() {
|
||||
ng.out.Info("NextGroup.Start: nextgroup, ng strategies size: %d", len(ng.strategies))
|
||||
if len(ng.strategies) > 0 {
|
||||
for _, checker := range ng.strategies {
|
||||
checker.Start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ng *NextGroup) Stop() {
|
||||
if len(ng.strategies) > 0 {
|
||||
for _, strategy := range ng.strategies {
|
||||
strategy.Stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for add nextgrou dynamicly
|
||||
func (ng *NextGroup) AddNextGroup(name string, cfg co.NextGroup) {
|
||||
if _, ok := ng.strategies[name]; ok {
|
||||
ng.out.Error("NextGroup.addNextGroup: checker already exists %s", name)
|
||||
return
|
||||
}
|
||||
|
||||
strategy := newCheckStrategy(name, ng.Network, cfg)
|
||||
if strategy != nil {
|
||||
ng.strategies[name] = strategy
|
||||
} else {
|
||||
ng.out.Error("NextGroup.AddNextGroup: don't support this strategy %s", name)
|
||||
}
|
||||
strategy.Start()
|
||||
}
|
||||
|
||||
// for del nextgrou dynamicly
|
||||
func (ng *NextGroup) DelNextGroup(name string, cfg co.NextGroup) {
|
||||
if strategy, ok := ng.strategies[name]; !ok {
|
||||
ng.out.Error("NextGroup.addNextGroup: checker not exists %s", name)
|
||||
return
|
||||
} else {
|
||||
if strategy.HasRoute() {
|
||||
ng.out.Error("NextGroup.delNextGroup: checker has route %s", name)
|
||||
return
|
||||
}
|
||||
strategy.Stop()
|
||||
delete(ng.strategies, name)
|
||||
}
|
||||
}
|
||||
|
||||
func (ng *NextGroup) LoadRoute(nextgroup string, nlr *nl.Route) {
|
||||
if strategy, ok := ng.strategies[nextgroup]; ok {
|
||||
ng.out.Debug("NextGroup.loadRoute: %v", nlr)
|
||||
strategy.LoadRoute(nlr)
|
||||
} else {
|
||||
ng.out.Error("NextGroup.loadRoute: checker not found %s", nextgroup)
|
||||
}
|
||||
}
|
||||
|
||||
func (ng *NextGroup) UnloadRoute(nextgroup string, nlr *nl.Route) {
|
||||
if strategy, ok := ng.strategies[nextgroup]; ok {
|
||||
ng.out.Debug("NextGroup.unloadRoute: %v", nlr)
|
||||
strategy.UnloadRoute(nlr)
|
||||
} else {
|
||||
ng.out.Error("NextGroup.unloadRoute: checker not found %s", nextgroup)
|
||||
}
|
||||
}
|
||||
|
||||
type NextGroupStrategy interface {
|
||||
Name() string
|
||||
Check([]string) []co.MultiPath
|
||||
Start()
|
||||
Stop()
|
||||
ReloadRoute()
|
||||
LoadRoute(route *nl.Route)
|
||||
UnloadRoute(route *nl.Route)
|
||||
HasRoute() bool
|
||||
}
|
||||
|
||||
type NextGroupStrategyImpl struct {
|
||||
Network string
|
||||
routes []*nl.Route
|
||||
cfg *co.NextGroup
|
||||
out *libol.SubLogger
|
||||
}
|
||||
|
||||
func (c *NextGroupStrategyImpl) Name() string {
|
||||
return "common"
|
||||
}
|
||||
|
||||
func (c *NextGroupStrategyImpl) Start() {
|
||||
|
||||
}
|
||||
|
||||
func (c *NextGroupStrategyImpl) Stop() {
|
||||
|
||||
}
|
||||
|
||||
func (c *NextGroupStrategyImpl) HasRoute() bool {
|
||||
return len(c.routes) > 0
|
||||
}
|
||||
|
||||
func (c *NextGroupStrategyImpl) Check(ipList []string) []co.MultiPath {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NextGroupStrategyImpl) UpdateAvailableNexthops(mp []co.MultiPath) bool {
|
||||
if c.cfg.Mode == "active-backup" {
|
||||
var newPath co.MultiPath
|
||||
if len(mp) > 0 {
|
||||
newPath = mp[0]
|
||||
}
|
||||
var oldPath co.MultiPath
|
||||
if len(c.cfg.AvailableNextHop) > 0 {
|
||||
oldPath = c.cfg.AvailableNextHop[0]
|
||||
}
|
||||
if !newPath.CompareEqual(oldPath) {
|
||||
c.cfg.AvailableNextHop = []co.MultiPath{newPath}
|
||||
c.out.Debug("NextGroupStrategyImpl.UpdateAvailableNexthops: final available nexthops %v", c.cfg.AvailableNextHop)
|
||||
return true
|
||||
}
|
||||
|
||||
} else if c.cfg.Mode == "load-balance" {
|
||||
if !compareMultiPaths(mp, c.cfg.AvailableNextHop) {
|
||||
c.cfg.AvailableNextHop = mp
|
||||
c.out.Debug("NextGroupStrategyImpl.UpdateAvailableNexthops: final available nexthops %v", c.cfg.AvailableNextHop)
|
||||
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
c.cfg.AvailableNextHop = []co.MultiPath{}
|
||||
c.out.Debug("NextGroupStrategyImpl.UpdateAvailableNexthops: final available nexthops %v", c.cfg.AvailableNextHop)
|
||||
//ignore
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *NextGroupStrategyImpl) ReloadRoute() {
|
||||
c.out.Debug("NextGroupStrategyImpl.ReloadRoute: route reload %d", len(c.routes))
|
||||
for _, rt := range c.routes {
|
||||
c.updateRoute(rt)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *NextGroupStrategyImpl) modelMultiPath() []models.MultiPath {
|
||||
var modelMultiPath []models.MultiPath
|
||||
for _, mp := range c.cfg.AvailableNextHop {
|
||||
modelMultiPath = append(modelMultiPath, models.MultiPath{
|
||||
NextHop: mp.NextHop,
|
||||
Weight: mp.Weight,
|
||||
})
|
||||
}
|
||||
|
||||
return modelMultiPath
|
||||
}
|
||||
|
||||
func (c *NextGroupStrategyImpl) buildNexthopInfos() []*nl.NexthopInfo {
|
||||
multiPath := make([]*nl.NexthopInfo, 0, len(c.cfg.AvailableNextHop))
|
||||
|
||||
if len(c.cfg.AvailableNextHop) > 0 {
|
||||
for _, mr := range c.cfg.AvailableNextHop {
|
||||
nxhe := &nl.NexthopInfo{
|
||||
Hops: mr.Weight,
|
||||
Gw: net.ParseIP(mr.NextHop),
|
||||
}
|
||||
multiPath = append(multiPath, nxhe)
|
||||
}
|
||||
}
|
||||
|
||||
return multiPath
|
||||
}
|
||||
|
||||
func (c *NextGroupStrategyImpl) updateRoute(nlr *nl.Route) {
|
||||
c.out.Debug("NextGroupStrategyImpl.updateRoute: %v ", nlr)
|
||||
multiPath := c.buildNexthopInfos()
|
||||
modelMultiPath := c.modelMultiPath()
|
||||
|
||||
nlr.MultiPath = multiPath
|
||||
|
||||
cache.Network.UpdateRoute(c.Network, co.PrefixRoute{
|
||||
Prefix: nlr.Dst.String(),
|
||||
}, func(obj *models.Route) {
|
||||
obj.MultiPath = modelMultiPath
|
||||
})
|
||||
|
||||
promise := libol.NewPromise()
|
||||
promise.Go(func() error {
|
||||
if err := nl.RouteReplace(nlr); err != nil {
|
||||
c.out.Warn("NextGroupStrategyImpl.updateRoute: %v %s", nlr, err)
|
||||
return err
|
||||
}
|
||||
c.out.Info("NextGroupStrategyImpl.updateRoute: %v success", nlr.String())
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (c *NextGroupStrategyImpl) LoadRoute(nlr *nl.Route) {
|
||||
c.out.Debug("NextGroupStrategyImpl.LoadRoute: %v", nlr)
|
||||
c.routes = append(c.routes, nlr)
|
||||
nlr.MultiPath = c.buildNexthopInfos()
|
||||
nlr.Gw = nil
|
||||
c.updateRoute(nlr)
|
||||
}
|
||||
|
||||
func (c *NextGroupStrategyImpl) UnloadRoute(rt *nl.Route) {
|
||||
c.out.Debug("NextGroupStrategyImpl.UnLoadRoute: %v", rt)
|
||||
//find route in routes
|
||||
var nlr *nl.Route
|
||||
for i, r := range c.routes {
|
||||
if r.Dst == rt.Dst && r.Table == rt.Table {
|
||||
nlr = r
|
||||
c.routes = append(c.routes[:i], c.routes[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if nlr != nil {
|
||||
if err := nl.RouteDel(nlr); err != nil {
|
||||
c.out.Warn("NextGroupStrategyImpl.UnLoadRoute: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type PingCheckResult struct {
|
||||
Ip string
|
||||
AvgLatency float64
|
||||
PacketLoss int
|
||||
}
|
||||
|
||||
type PingCheckStrategy struct {
|
||||
*NextGroupStrategyImpl
|
||||
CfgName string
|
||||
Running bool
|
||||
PingParams *co.PingParams
|
||||
out *libol.SubLogger
|
||||
}
|
||||
|
||||
func NewPingStrategy(name string, network string, cfg *co.NextGroup) *PingCheckStrategy {
|
||||
return &PingCheckStrategy{
|
||||
CfgName: name,
|
||||
NextGroupStrategyImpl: &NextGroupStrategyImpl{
|
||||
Network: network,
|
||||
cfg: cfg,
|
||||
out: libol.NewSubLogger(cfg.Check + "_common_" + name),
|
||||
},
|
||||
PingParams: &cfg.Ping,
|
||||
out: libol.NewSubLogger(cfg.Check + "_" + name),
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PingCheckStrategy) Name() string {
|
||||
return "ping"
|
||||
}
|
||||
|
||||
func filter(results []PingCheckResult, condition func(PingCheckResult) bool) []PingCheckResult {
|
||||
var filteredResults []PingCheckResult
|
||||
for _, result := range results {
|
||||
if condition(result) {
|
||||
filteredResults = append(filteredResults, result)
|
||||
}
|
||||
}
|
||||
return filteredResults
|
||||
}
|
||||
|
||||
func compareMultiPaths(a []co.MultiPath, b []co.MultiPath) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range a {
|
||||
if !a[i].CompareEqual(b[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// check the ipList and return the available NextHops
|
||||
func (pc *PingCheckStrategy) Check(ipList []string) []co.MultiPath {
|
||||
count := pc.PingParams.Count
|
||||
loss := pc.PingParams.Loss
|
||||
|
||||
pc.out.Debug("PingCheckStrategy.Check: start check ips")
|
||||
var resultIps []PingCheckResult
|
||||
for _, ip := range ipList {
|
||||
avgLatency, packetLoss, err := pc.ping(ip, count)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
resultIps = append(resultIps, PingCheckResult{
|
||||
Ip: ip,
|
||||
AvgLatency: avgLatency,
|
||||
PacketLoss: packetLoss,
|
||||
})
|
||||
}
|
||||
// filter loss
|
||||
filterResultIps := filter(resultIps, func(result PingCheckResult) bool {
|
||||
return result.PacketLoss <= loss
|
||||
})
|
||||
|
||||
sort.Slice(filterResultIps, func(i, j int) bool {
|
||||
if filterResultIps[i].PacketLoss != filterResultIps[j].PacketLoss {
|
||||
return filterResultIps[i].PacketLoss < filterResultIps[j].PacketLoss
|
||||
}
|
||||
return filterResultIps[i].AvgLatency < filterResultIps[j].AvgLatency
|
||||
})
|
||||
|
||||
var sortedIPs []co.MultiPath
|
||||
for _, result := range filterResultIps {
|
||||
sortedIPs = append(sortedIPs, co.MultiPath{
|
||||
NextHop: result.Ip,
|
||||
Weight: 1,
|
||||
})
|
||||
pc.out.Debug("PingCheckStrategy.Check: available ip : %s , rtt:%.4f, loss: %d ", result.Ip, result.AvgLatency, result.PacketLoss)
|
||||
}
|
||||
|
||||
return sortedIPs
|
||||
}
|
||||
|
||||
func (pc *PingCheckStrategy) updateNextHop() {
|
||||
ipList := pc.Check(pc.cfg.NextHop)
|
||||
if pc.UpdateAvailableNexthops(ipList) {
|
||||
pc.ReloadRoute()
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PingCheckStrategy) update() {
|
||||
frequency := pc.cfg.Ping.CheckFrequency
|
||||
|
||||
if frequency <= 0 {
|
||||
frequency = 10
|
||||
}
|
||||
//wait tun device start
|
||||
time.Sleep(time.Second * time.Duration(2))
|
||||
for pc.Running = true; pc.Running; {
|
||||
pc.updateNextHop()
|
||||
time.Sleep(time.Second * time.Duration(frequency))
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PingCheckStrategy) Start() {
|
||||
libol.Go(pc.update)
|
||||
}
|
||||
|
||||
func (pc *PingCheckStrategy) Stop() {
|
||||
pc.Running = false
|
||||
}
|
||||
|
||||
func (pc *PingCheckStrategy) ping(ip string, count int) (float64, int, error) {
|
||||
pingPath, err := exec.LookPath("ping")
|
||||
if err != nil {
|
||||
pc.out.Warn("PingCheckStrategy.Ping: cmd not found :", err)
|
||||
}
|
||||
|
||||
output, err := libol.Exec(pingPath, ip, "-c", strconv.Itoa(count))
|
||||
if err != nil {
|
||||
pc.out.Error("PingCheckStrategy.Ping: exec ping ip: %s, error: %s", ip, err)
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
avgLatency := pc.extractAvgLatency(output)
|
||||
|
||||
packetLossRate, err := pc.extractPacketLoss(output)
|
||||
if err != nil {
|
||||
pc.out.Error("PingCheckStrategy.Ping:parse loss error : %s", err)
|
||||
return 0, 0, err
|
||||
}
|
||||
packetLoss := int(packetLossRate * float64(count) / 100)
|
||||
|
||||
pc.out.Debug("PingCheckStrategy.Ping: ping ip[%s], rtt:%.4f, loss: %.f%%", ip, avgLatency, packetLossRate)
|
||||
return avgLatency, packetLoss, nil
|
||||
}
|
||||
|
||||
func (pc *PingCheckStrategy) extractAvgLatency(outputStr string) float64 {
|
||||
pattern := `rtt min/avg/max/mdev = (\d+\.*\d*)/(\d+\.*\d*)/(\d+\.*\d*)/(\d+\.*\d*) ms`
|
||||
|
||||
re := regexp.MustCompile(pattern)
|
||||
subMatches := re.FindStringSubmatch(outputStr)
|
||||
if len(subMatches) != 5 {
|
||||
pc.out.Error("PingCheckStrategy.Ping: Cannot extract average delay.")
|
||||
return 0
|
||||
}
|
||||
|
||||
avgLatencyStr := subMatches[2]
|
||||
|
||||
avgLatency, err := strconv.ParseFloat(avgLatencyStr, 64)
|
||||
if err != nil {
|
||||
pc.out.Error("PingCheckStrategy.Ping: parse float error : %s", err)
|
||||
return 0
|
||||
}
|
||||
return avgLatency
|
||||
}
|
||||
|
||||
func (pc *PingCheckStrategy) extractPacketLoss(outputStr string) (float64, error) {
|
||||
re := regexp.MustCompile(`(\d+)% packet loss`)
|
||||
match := re.FindStringSubmatch(outputStr)
|
||||
if len(match) < 2 {
|
||||
return 0, fmt.Errorf("PingCheckStrategy.Ping: packet loss parse error")
|
||||
}
|
||||
lossRate, err := strconv.ParseFloat(match[1], 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return lossRate, nil
|
||||
}
|
Reference in New Issue
Block a user