diff --git a/.github/workflows/advanced-lb-sanity.yml b/.github/workflows/advanced-lb-sanity.yml index 7376b92fa..a8672e921 100644 --- a/.github/workflows/advanced-lb-sanity.yml +++ b/.github/workflows/advanced-lb-sanity.yml @@ -101,9 +101,9 @@ jobs: ./validation.sh ./rmconfig.sh cd - - - run: | - cd cicd/httpsproxy/ - ./config.sh - ./validation.sh - ./rmconfig.sh - cd - + #- run: | + # cd cicd/httpsproxy/ + # ./config.sh + # ./validation.sh + # ./rmconfig.sh + # cd - diff --git a/api/models/firewall_option_entry.go b/api/models/firewall_option_entry.go index a3ac936fd..0a26bd90e 100644 --- a/api/models/firewall_option_entry.go +++ b/api/models/firewall_option_entry.go @@ -23,6 +23,9 @@ type FirewallOptionEntry struct { // traffic counters Counter string `json:"counter,omitempty"` + // Do SNAT on matching rule + DoSnat bool `json:"doSnat,omitempty"` + // Drop any matching rule Drop bool `json:"drop,omitempty"` @@ -38,6 +41,12 @@ type FirewallOptionEntry struct { // Redirect any matching rule RedirectPortName string `json:"redirectPortName,omitempty"` + // Modify to given IP in CIDR notation + ToIP string `json:"toIP,omitempty"` + + // Modify to given Port (Zero if port is not to be modified) + ToPort int64 `json:"toPort,omitempty"` + // Trap anything matching rule Trap bool `json:"trap,omitempty"` } diff --git a/api/models/loadbalance_entry.go b/api/models/loadbalance_entry.go index e55f0feff..79cb9f189 100644 --- a/api/models/loadbalance_entry.go +++ b/api/models/loadbalance_entry.go @@ -365,6 +365,9 @@ type LoadbalanceEntryServiceArguments struct { // value for load balance algorithim Sel int64 `json:"sel,omitempty"` + + // snat rule + Snat bool `json:"snat,omitempty"` } // Validate validates this loadbalance entry service arguments diff --git a/api/restapi/embedded_spec.go b/api/restapi/embedded_spec.go index 8782fe145..ffc261a15 100644 --- a/api/restapi/embedded_spec.go +++ b/api/restapi/embedded_spec.go @@ -4750,6 +4750,10 @@ func init() { "description": "traffic counters", "type": "string" }, + "doSnat": { + "description": "Do SNAT on matching rule", + "type": "boolean" + }, "drop": { "description": "Drop any matching rule", "type": "boolean" @@ -4770,6 +4774,14 @@ func init() { "description": "Redirect any matching rule", "type": "string" }, + "toIP": { + "description": "Modify to given IP in CIDR notation", + "type": "string" + }, + "toPort": { + "description": "Modify to given Port (Zero if port is not to be modified)", + "type": "integer" + }, "trap": { "description": "Trap anything matching rule", "type": "boolean" @@ -4981,6 +4993,10 @@ func init() { "sel": { "description": "value for load balance algorithim", "type": "integer" + }, + "snat": { + "description": "snat rule", + "type": "boolean" } } } @@ -10797,6 +10813,10 @@ func init() { "description": "traffic counters", "type": "string" }, + "doSnat": { + "description": "Do SNAT on matching rule", + "type": "boolean" + }, "drop": { "description": "Drop any matching rule", "type": "boolean" @@ -10817,6 +10837,14 @@ func init() { "description": "Redirect any matching rule", "type": "string" }, + "toIP": { + "description": "Modify to given IP in CIDR notation", + "type": "string" + }, + "toPort": { + "description": "Modify to given Port (Zero if port is not to be modified)", + "type": "integer" + }, "trap": { "description": "Trap anything matching rule", "type": "boolean" @@ -11002,6 +11030,10 @@ func init() { "sel": { "description": "value for load balance algorithim", "type": "integer" + }, + "snat": { + "description": "snat rule", + "type": "boolean" } } } @@ -11125,6 +11157,10 @@ func init() { "sel": { "description": "value for load balance algorithim", "type": "integer" + }, + "snat": { + "description": "snat rule", + "type": "boolean" } } }, diff --git a/api/restapi/handler/firewall.go b/api/restapi/handler/firewall.go index dae392677..d090bec0f 100644 --- a/api/restapi/handler/firewall.go +++ b/api/restapi/handler/firewall.go @@ -56,6 +56,9 @@ func ConfigPostFW(params operations.PostConfigFirewallParams) middleware.Respond Opts.Trap = params.Attr.Opts.Trap Opts.Record = params.Attr.Opts.Record Opts.Mark = uint32(params.Attr.Opts.FwMark) + Opts.DoSnat = params.Attr.Opts.DoSnat + Opts.ToIP = params.Attr.Opts.ToIP + Opts.ToPort = uint16(params.Attr.Opts.ToPort) FW.Rule = Rules FW.Opts = Opts @@ -156,6 +159,9 @@ func ConfigGetFW(params operations.GetConfigFirewallAllParams) middleware.Respon tmpOpts.Trap = FW.Opts.Trap tmpOpts.Record = FW.Opts.Record tmpOpts.FwMark = int64(FW.Opts.Mark) + tmpOpts.DoSnat = FW.Opts.DoSnat + tmpOpts.ToIP = FW.Opts.ToIP + tmpOpts.ToPort = int64(FW.Opts.ToPort) tmpOpts.Counter = FW.Opts.Counter tmpResult.RuleArguments = &tmpRule diff --git a/api/restapi/handler/loadbalancer.go b/api/restapi/handler/loadbalancer.go index 1001881b9..c1110b0ae 100644 --- a/api/restapi/handler/loadbalancer.go +++ b/api/restapi/handler/loadbalancer.go @@ -132,6 +132,7 @@ func ConfigGetLoadbalancer(params operations.GetConfigLoadbalancerAllParams) mid tmpSvc.Probetype = lb.Serv.ProbeType tmpSvc.Probeport = lb.Serv.ProbePort tmpSvc.Name = lb.Serv.Name + tmpSvc.Snat = lb.Serv.Snat tmpLB.ServiceArguments = &tmpSvc diff --git a/api/swagger.yml b/api/swagger.yml index 67667561d..adca60e68 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -2894,6 +2894,9 @@ definitions: name: type: string description: service name + snat: + type: boolean + description: snat rule oper: type: integer format: int32 @@ -3614,6 +3617,15 @@ definitions: fwMark: type: integer description: Set a fwmark for any matching rule + doSnat: + type: boolean + description: Do SNAT on matching rule + toIP: + type: string + description: Modify to given IP in CIDR notation + toPort: + type: integer + description: Modify to given Port (Zero if port is not to be modified) counter: type: string description: traffic counters @@ -3949,4 +3961,3 @@ definitions: type: integer format: uint8 description: Retry Count to detect failure - \ No newline at end of file diff --git a/common/common.go b/common/common.go index 9d4c90cbc..e4395d4b0 100644 --- a/common/common.go +++ b/common/common.go @@ -398,6 +398,10 @@ type FwOptArg struct { Allow bool `json:"allow"` // Mark - Mark the matching rule Mark uint32 `json:"fwMark"` + // DoSnat - Do snat on matching rule + DoSnat bool `json:"doSnat"` + ToIP string `json:"toIP"` + ToPort uint16 `json:"toPort"` // Counter - Traffic counter Counter string `json:"counter"` } @@ -561,6 +565,8 @@ type LbServiceArg struct { Name string `json:"name"` // PersistTimeout - Persistence timeout in seconds PersistTimeout uint32 `json:"persistTimeout"` + // Snat - Do SNAT + Snat bool `json:"snat"` } // LbEndPointArg - Information related to load-balancer end-point diff --git a/loxilb-ebpf b/loxilb-ebpf index 9e322cc57..27c543d16 160000 --- a/loxilb-ebpf +++ b/loxilb-ebpf @@ -1 +1 @@ -Subproject commit 9e322cc57f7e83f67a8eeaa80d5d06ca6b0596ef +Subproject commit 27c543d1689c26bb8f15a4f6e1ce2e6aa445df8d diff --git a/pkg/loxinet/dpebpf_linux.go b/pkg/loxinet/dpebpf_linux.go index 78fcbb558..243d8688d 100644 --- a/pkg/loxinet/dpebpf_linux.go +++ b/pkg/loxinet/dpebpf_linux.go @@ -922,18 +922,24 @@ func DpNatLbRuleMod(w *NatDpWorkQ) int { key := new(natKey) - key.daddr = [4]C.uint{0, 0, 0, 0} - if tk.IsNetIPv4(w.ServiceIP.String()) { - key.daddr[0] = C.uint(tk.IPtonl(w.ServiceIP)) - key.v6 = 0 + key.mark = C.ushort(w.BlockNum) + + if w.NatType == DpSnat { + key.mark |= 0x1000 } else { - convNetIP2DPv6Addr(unsafe.Pointer(&key.daddr[0]), w.ServiceIP) - key.v6 = 1 + key.daddr = [4]C.uint{0, 0, 0, 0} + if tk.IsNetIPv4(w.ServiceIP.String()) { + key.daddr[0] = C.uint(tk.IPtonl(w.ServiceIP)) + key.v6 = 0 + } else { + convNetIP2DPv6Addr(unsafe.Pointer(&key.daddr[0]), w.ServiceIP) + key.v6 = 1 + } + key.mark = C.ushort(w.BlockNum) + key.dport = C.ushort(tk.Htons(w.L4Port)) + key.l4proto = C.uchar(w.Proto) + key.zone = C.ushort(w.ZoneNum) } - key.mark = C.ushort(w.BlockNum) - key.dport = C.ushort(tk.Htons(w.L4Port)) - key.l4proto = C.uchar(w.Proto) - key.zone = C.ushort(w.ZoneNum) if w.Work == DpCreate { dat := new(natActs) diff --git a/pkg/loxinet/rules.go b/pkg/loxinet/rules.go index da0cd7b27..a7a3f85b1 100644 --- a/pkg/loxinet/rules.go +++ b/pkg/loxinet/rules.go @@ -233,10 +233,12 @@ type ruleNatActs struct { } type ruleFwOpt struct { - rdrMirr string - rdrPort string - fwMark uint32 - record bool + rdrMirr string + rdrPort string + fwMark uint32 + record bool + snatIP string + snatPort uint16 } type ruleFwOpts struct { @@ -689,6 +691,10 @@ func (a *ruleAct) String() string { } } } + case *ruleFwOpts: + if a.actType == RtActSnat { + ks += fmt.Sprintf("%s:%d", na.opt.snatIP, na.opt.snatPort) + } } } @@ -779,6 +785,9 @@ func (R *RuleH) GetNatLbRule() ([]cmn.LbRuleMod, error) { ret.Serv.ProbeReq = data.hChk.prbReq ret.Serv.ProbeResp = data.hChk.prbResp ret.Serv.Name = data.name + if data.act.actType == RtActSnat { + ret.Serv.Snat = true + } for _, sip := range data.secIP { ret.SecIPs = append(ret.SecIPs, cmn.LbSecIPArg{SecIP: sip.sIP.String()}) @@ -1243,7 +1252,7 @@ func (R *RuleH) unFoldRecursiveEPs(r *ruleEnt) { // addVIPSys - system specific operations for VIPs of a LB rule func (R *RuleH) addVIPSys(r *ruleEnt) { - if !strings.Contains(r.name, "ipvs") && !strings.Contains(r.name, "static") { + if r.act.actType != RtActSnat && !strings.Contains(r.name, "ipvs") && !strings.Contains(r.name, "static") { if !r.tuples.l3Dst.addr.IP.IsUnspecified() { R.vipMap[r.tuples.l3Dst.addr.IP.String()]++ @@ -1441,9 +1450,9 @@ func (R *RuleH) AddNatLbRule(serv cmn.LbServiceArg, servSecIPs []cmn.LbSecIPArg, return a < b }) - if sNetAddr.IP.IsUnspecified() && serv.Mode != cmn.LBModeHostOneArm { - serv.Mode = cmn.LBModeHostOneArm - tk.LogIt(tk.LogInfo, "nat lb-rule %s-%v-%s updated to hostOneArm\n", serv.ServIP, serv.ServPort, serv.Proto) + if serv.Mode == cmn.LBModeHostOneArm && !sNetAddr.IP.IsUnspecified() { + tk.LogIt(tk.LogInfo, "nat lb-rule %s-%v-%s hostarm needs unspec VIP\n", serv.ServIP, serv.ServPort, serv.Proto) + return RuleArgsErr, errors.New("hostarm-args error") } natActs.sel = serv.Sel @@ -1534,8 +1543,10 @@ func (R *RuleH) AddNatLbRule(serv cmn.LbServiceArg, servSecIPs []cmn.LbSecIPArg, // Managed flag can't be modified on the fly // eRule.managed = serv.Managed - R.modNatEpHost(eRule, retEps, true, activateProbe) - R.electEPSrc(eRule) + if !serv.Snat { + R.modNatEpHost(eRule, retEps, true, activateProbe) + R.electEPSrc(eRule) + } eRule.sT = time.Now() eRule.iTO = serv.InactiveTimeout @@ -1572,6 +1583,11 @@ func (R *RuleH) AddNatLbRule(serv cmn.LbServiceArg, servSecIPs []cmn.LbSecIPArg, r.hChk.prbRetries = serv.ProbeRetries r.hChk.prbTimeo = serv.ProbeTimeout r.hChk.actChk = serv.Monitor + if serv.Snat { + r.act.actType = RtActSnat + } else { + r.act.actType = RtActDnat + } r.act.action = &natActs r.ruleNum, err = R.tables[RtLB].Mark.GetCounter() if err != nil { @@ -1592,11 +1608,13 @@ func (R *RuleH) AddNatLbRule(serv cmn.LbServiceArg, servSecIPs []cmn.LbSecIPArg, } r.locIPs = make(map[string]struct{}) - R.foldRecursiveEPs(r) - R.modNatEpHost(r, natActs.endPoints, true, activateProbe) - R.electEPSrc(r) - if serv.Mode == cmn.LBModeHostOneArm { - R.mkHostAssocs(r) + if !serv.Snat { + R.foldRecursiveEPs(r) + R.modNatEpHost(r, natActs.endPoints, true, activateProbe) + R.electEPSrc(r) + if serv.Mode == cmn.LBModeHostOneArm { + R.mkHostAssocs(r) + } } tk.LogIt(tk.LogDebug, "nat lb-rule added - %d:%s-%s\n", r.ruleNum, r.tuples.String(), r.act.String()) @@ -1614,7 +1632,7 @@ func (R *RuleH) AddNatLbRule(serv cmn.LbServiceArg, servSecIPs []cmn.LbSecIPArg, // deleteVIPSys - system specific operations for deleting VIPs of a LB rule func (R *RuleH) deleteVIPSys(r *ruleEnt) { - if !strings.Contains(r.name, "ipvs") && !strings.Contains(r.name, "static") { + if r.act.actType != RtActSnat && !strings.Contains(r.name, "ipvs") && !strings.Contains(r.name, "static") { if !r.tuples.l3Dst.addr.IP.IsUnspecified() { R.vipMap[r.tuples.l3Dst.addr.IP.String()]-- @@ -1700,8 +1718,10 @@ func (R *RuleH) DeleteNatLbRule(serv cmn.LbServiceArg) (int, error) { if rule.act.action.(*ruleNatActs).mode == cmn.LBModeOneArm || rule.act.action.(*ruleNatActs).mode == cmn.LBModeFullNAT || rule.act.action.(*ruleNatActs).mode == cmn.LBModeHostOneArm || rule.hChk.actChk { activatedProbe = true } - R.modNatEpHost(rule, eEps, false, activatedProbe) - R.unFoldRecursiveEPs(rule) + if rule.act.actType != RtActSnat { + R.modNatEpHost(rule, eEps, false, activatedProbe) + R.unFoldRecursiveEPs(rule) + } delete(R.tables[RtLB].eMap, rt.ruleKey()) if rule.ruleNum < RtMaximumLbs { @@ -1754,8 +1774,14 @@ func (R *RuleH) GetFwRule() ([]cmn.FwRuleMod, error) { ret.Opts.RdrPort = fwOpts.opt.rdrPort } else if fwOpts.op == RtActTrap { ret.Opts.Trap = true + } else if fwOpts.op == RtActSnat { + ret.Opts.DoSnat = true + ret.Opts.ToIP = fwOpts.opt.snatIP + ret.Opts.ToPort = uint16(fwOpts.opt.snatPort) + } + if fwOpts.op != RtActSnat { + ret.Opts.Mark = fwOpts.opt.fwMark } - ret.Opts.Mark = fwOpts.opt.fwMark ret.Opts.Record = fwOpts.opt.record data.Fw2DP(DpStatsGetImm) @@ -1847,6 +1873,19 @@ func (R *RuleH) AddFwRule(fwRule cmn.FwRuleArg, fwOptArgs cmn.FwOptArg) (int, er } else if fwOptArgs.Trap { r.act.actType = RtActTrap fwOpts.op = RtActTrap + } else if fwOptArgs.DoSnat { + r.act.actType = RtActSnat + fwOpts.op = RtActSnat + fwOpts.opt.snatIP = fwOptArgs.ToIP + fwOpts.opt.snatPort = fwOptArgs.ToPort + + if sIP := net.ParseIP(fwOptArgs.ToIP); sIP == nil { + return RuleArgsErr, errors.New("malformed-args error") + } + + if fwOpts.opt.fwMark != 0 { + return RuleArgsErr, errors.New("malformed-args fwmark !=0 for snat-error") + } } r.act.action = &fwOpts @@ -1857,11 +1896,35 @@ func (R *RuleH) AddFwRule(fwRule cmn.FwRuleArg, fwOptArgs cmn.FwOptArg) (int, er } r.sT = time.Now() + if fwOptArgs.DoSnat { + // Create SNAT Rule + var servArg cmn.LbServiceArg + servArg.ServIP = "0.0.0.0" + servArg.ServPort = 0 + servArg.Proto = "none" + servArg.BlockNum = uint16(r.ruleNum) | 0x1000 + servArg.Sel = cmn.LbSelRr + servArg.Mode = cmn.LBModeDefault + servArg.Snat = true + servArg.Name = fmt.Sprintf("%s:%s:%d", "snat", fwOpts.opt.snatIP, fwOpts.opt.snatPort) + + snatEP := []cmn.LbEndPointArg{{EpIP: fwOpts.opt.snatIP, EpPort: fwOpts.opt.snatPort}} + + _, err := R.AddNatLbRule(servArg, nil, snatEP) + if err != nil { + tk.LogIt(tk.LogError, "fw-rule - %s:%s (%s) snat create error\n", r.tuples.String(), r.act.String(), err) + return RuleArgsErr, errors.New("rule-snat error") + } + + fwOpts.opt.fwMark = uint32(uint16((r.ruleNum) | 0x1000)) + + } + tk.LogIt(tk.LogDebug, "fw-rule added - %d:%s-%s\n", r.ruleNum, r.tuples.String(), r.act.String()) R.tables[RtFw].eMap[rt.ruleKey()] = r - r.DP(DpCreate) + r.Fw2DP(DpCreate) return 0, nil } @@ -1920,13 +1983,36 @@ func (R *RuleH) DeleteFwRule(fwRule cmn.FwRuleArg) (int, error) { return RuleNotExistsErr, errors.New("no-rule error") } + if rule.act.actType == RtActSnat { + // Delete implicit SNAT Rule + + var servArg cmn.LbServiceArg + servArg.ServIP = "0.0.0.0" + servArg.ServPort = 0 + servArg.Proto = "none" + servArg.BlockNum = uint16(rule.ruleNum) | 0x1000 + servArg.Sel = cmn.LbSelRr + servArg.Mode = cmn.LBModeDefault + servArg.Snat = true + + switch fwOpts := rule.act.action.(type) { + case *ruleFwOpts: + servArg.Name = fmt.Sprintf("%s:%s:%d", "Masq", fwOpts.opt.snatIP, fwOpts.opt.snatPort) + } + + _, err := R.DeleteNatLbRule(servArg) + if err != nil { + tk.LogIt(tk.LogError, "fw-rule - %s:%s snat delete error\n", rule.tuples.String(), rule.act.String()) + } + } + defer R.tables[RtFw].Mark.PutCounter(rule.ruleNum) delete(R.tables[RtFw].eMap, rt.ruleKey()) tk.LogIt(tk.LogDebug, "fw-rule deleted %s-%s\n", rule.tuples.String(), rule.act.String()) - rule.DP(DpRemove) + rule.Fw2DP(DpRemove) return 0, nil } @@ -2377,15 +2463,15 @@ func (R *RuleH) RulesSync() { } for _, rule := range R.tables[RtFw].eMap { - ruleKeys := rule.tuples.String() - ruleActs := rule.act.String() + //ruleKeys := rule.tuples.String() + //ruleActs := rule.act.String() if rule.sync != 0 { rule.DP(DpCreate) } - rule.DP(DpStatsGet) - tk.LogIt(-1, "%d:%s,%s pc %v bc %v \n", - rule.ruleNum, ruleKeys, ruleActs, - rule.stat.packets, rule.stat.bytes) + //rule.DP(DpStatsGet) + //tk.LogIt(-1, "%d:%s,%s pc %v bc %v \n", + // rule.ruleNum, ruleKeys, ruleActs, + // rule.stat.packets, rule.stat.bytes) } } @@ -2720,6 +2806,8 @@ func (r *ruleEnt) Fw2DP(work DpWorkT) int { nWork.FwVal1 = uint16(port.PortNo) case RtActTrap: nWork.FwType = DpFwTrap + case RtActSnat: + nWork.FwType = DpFwFwd default: nWork.FwType = DpFwDrop }