Add support for NAT and LocalService 33/4533/3 21-12-01
authorHuifeng Le <huifeng.le@intel.com>
Mon, 29 Nov 2021 14:31:30 +0000 (22:31 +0800)
committerHuifeng Le <huifeng.le@intel.com>
Wed, 1 Dec 2021 12:30:14 +0000 (20:30 +0800)
Update implementation for route and rule

Signed-off-by: Huifeng Le <huifeng.le@intel.com>
Change-Id: I314ab5fbdfec3c1b7bda5e61d373ddfd1ea57bad
Signed-off-by: Huifeng Le <huifeng.le@intel.com>
21 files changed:
platform/cnf-openwrt/src/rest_v1/ifutil.lua [new file with mode: 0644]
platform/cnf-openwrt/src/rest_v1/index.lua
platform/cnf-openwrt/src/rest_v1/ipsec_rest.lua
platform/cnf-openwrt/src/rest_v1/nat_rest.lua [new file with mode: 0644]
platform/cnf-openwrt/src/rest_v1/route_rest.lua
platform/cnf-openwrt/src/rest_v1/rule_rest.lua
platform/cnf-openwrt/src/rest_v1/utils.lua
platform/crd-ctrlr/src/api/v1alpha1/bucket_permission_webhook.go
platform/crd-ctrlr/src/api/v1alpha1/cnflocalservice_types.go [new file with mode: 0644]
platform/crd-ctrlr/src/api/v1alpha1/cnfnat_types.go [new file with mode: 0644]
platform/crd-ctrlr/src/api/v1alpha1/label_validate_webhook.go
platform/crd-ctrlr/src/config/crd/kustomization.yaml
platform/crd-ctrlr/src/config/crd/patches/cainjection_in_cnfnats.yaml [new file with mode: 0644]
platform/crd-ctrlr/src/config/crd/patches/webhook_in_cnfnats.yaml [new file with mode: 0644]
platform/crd-ctrlr/src/config/manager/manager.yaml
platform/crd-ctrlr/src/config/samples/batch_v1alpha1_cnfnat.yaml [new file with mode: 0644]
platform/crd-ctrlr/src/controllers/base_controller.go
platform/crd-ctrlr/src/controllers/cnflocalservice_controller.go [new file with mode: 0644]
platform/crd-ctrlr/src/controllers/cnfnat_controller.go [new file with mode: 0644]
platform/crd-ctrlr/src/main.go
platform/crd-ctrlr/src/openwrt/nat.go [new file with mode: 0644]

diff --git a/platform/cnf-openwrt/src/rest_v1/ifutil.lua b/platform/cnf-openwrt/src/rest_v1/ifutil.lua
new file mode 100644 (file)
index 0000000..24a376b
--- /dev/null
@@ -0,0 +1,150 @@
+--- SPDX-License-Identifier: Apache-2.0
+--- Copyright (c) 2021 Intel Corporation
+
+module("luci.controller.rest_v1.ifutil", package.seeall)
+
+NX = require("nixio")
+io = require "io"
+json = require "luci.jsonc"
+sys = require "luci.sys"
+util = require "luci.util"
+utils = require "luci.controller.rest_v1.utils"
+
+fields_table = {
+    {field="ip_address", key="inet", type="array", format=function(data) return format_ip(data) end},
+    {field="mac_address", key="link/ether"},
+    {field="ip6_address", key="inet6", format=function(data) return format_ip(data) end},
+}
+
+function index()
+end
+
+function is_interface_available(interface)
+    local f = io.open("/sys/class/net/" .. interface .. "/operstate", "r")
+    if f == nil then
+        return false
+    end
+    f:close()
+    return true
+end
+
+function format_ip(data)
+    local i, j = string.find(data, "/")
+    if i ~= nil then
+        return string.sub(data, 1, i-1)
+    end
+    return data
+end
+
+function get_field(data, key, field_type, format)
+    if type(key) == "function" then
+        return key(data)
+    end
+
+    local reg = {
+        key .. " [^%s]+[%s]",
+    }
+
+    local ret = nil
+    for index=1, #reg do
+        for item in string.gmatch(data, reg[index]) do
+            local value = nil
+            local i,j = string.find(item, key .. ": ")
+            if i ~= nil then
+                value = string.sub(item, j+1, string.len(item)-1)
+            else
+                i,j = string.find(item, key .. ":")
+                if i ~= nil then
+                    value = string.sub(item, j+1, string.len(item)-1)
+                else
+                    i,j = string.find(item, key .. " ")
+                    if i ~= nil then
+                        value = string.sub(item, j+1, string.len(item)-1)
+                    end
+                end
+            end
+            if value ~= nil then
+                if format ~= nil and type(format) == "function" then
+                    value = format(value)
+                end
+
+                if field_type == "array" then
+                    if ret == nil then
+                        ret = {value}
+                    else
+                        ret[#ret+1] = value
+                    end
+                else
+                    ret = value
+                    break
+                end
+            end
+        end
+    end
+    return ret
+end
+
+function get_interface(interface)
+    local ret = {}
+    local data = util.exec("ip a show dev " .. interface)
+    if data == nil then
+        for j=1, 3 do
+            utils.log("ip command failed, retrying ... ")
+            NX.nanosleep(1)
+            data = util.exec("ip a show dev " .. interface)
+            if data ~= nil then
+                break
+            end
+        end
+    end
+    ret["name"] = interface
+    for i,v in pairs(fields_table) do
+        local value = get_field(data, v["key"], v["type"], v["format"])
+        if value ~= nil then
+            ret[v["field"]] = value
+        end
+    end
+    return ret
+end
+
+function get_interface_info()
+    local ret = {}
+    local index = 1
+    for interface in util.execi("ifconfig | awk '/^[^ \t]+/{print $1}'") do
+        if interface ~= "lo" then
+           ret[index] = get_interface(interface)
+           index = index + 1
+        end
+    end
+    return ret
+end
+
+function get_name_by_ip(ip_addr)
+    local ifs = get_interface_info()
+    for i, interface in pairs(ifs) do
+        if interface["ip_address"] ~= nil then
+            for j, ipa in pairs(interface["ip_address"]) do
+                if ipa == ip_addr then
+                    return interface["name"]
+                end
+            end
+        end
+    end
+    return nil
+end
+
+function get_default_ifname()
+    local data = util.exec("ip route | grep '^default' 2>/dev/null")
+    if data ~= nil then
+        reg = "dev [^%s]+[%s]"
+        for item in string.gmatch(data, reg) do
+            local value = nil
+            local i,j = string.find(item, "dev ")
+            if i ~= nil then
+                value = string.sub(item, j+1, string.len(item)-1)
+                return value
+            end
+        end
+    end
+    return nil
+end
index 4d700d3..6c1bbf3 100644 (file)
@@ -13,6 +13,7 @@ function index()
     entry({"sdewan", "application", ver}, call("help")).dependent = false
     entry({"sdewan", "route", ver}, call("help")).dependent = false
     entry({"sdewan", "rule", ver}, call("help")).dependent = false
+    entry({"sdewan", "nat", ver}, call("help")).dependent = false
 
 end
 
index 75ce2cc..6e21d10 100644 (file)
@@ -40,7 +40,7 @@ config_type=function(value) return value["conn_type"] end,
     {name="remote_sourceip"},
     {name="remote_updown"},
     {name="remote_firewall", validator=function(value) return utils.in_array(value, {"yes", "no"}) end},
-    {name="crypto_proposal", is_list=true, item_validator=function(value) return is_proposal_available(value) end, message="invalid crypto_proposal"},
+    {name="crypto_proposal", is_list=true, item_validator=function(value) return is_proposal_available(value) end, message="invalid crypto_proposal", code="428"},
     {name="mark"},
 
 }
@@ -56,7 +56,7 @@ remote_validator = {
     {name="pre_shared_key"},
     {name="local_identifier"},
     {name="remote_identifier"},
-    {name="crypto_proposal", is_list=true, item_validator=function(value) return is_proposal_available(value) end, message="invalid crypto_proposal"},
+    {name="crypto_proposal", is_list=true, item_validator=function(value) return is_proposal_available(value) end, message="invalid crypto_proposal", code="428"},
     {name="force_crypto_proposal", validator=function(value) return utils.in_array(value, {"0", "1"}) end, message="invalid input for ForceCryptoProposal"},
     {name="local_public_cert",
         load_func=function(value) return load_cert(value["local_public_cert"]) end,
diff --git a/platform/cnf-openwrt/src/rest_v1/nat_rest.lua b/platform/cnf-openwrt/src/rest_v1/nat_rest.lua
new file mode 100644 (file)
index 0000000..adde1c2
--- /dev/null
@@ -0,0 +1,237 @@
+--- SPDX-License-Identifier: Apache-2.0
+--- Copyright (c) 2021 Intel Corporation
+
+module("luci.controller.rest_v1.nat_rest", package.seeall)
+
+local uci = require "luci.model.uci"
+
+json = require "luci.jsonc"
+io = require "io"
+sys = require "luci.sys"
+utils = require "luci.controller.rest_v1.utils"
+ifutil = require "luci.controller.rest_v1.ifutil"
+
+uci_conf = "firewall-nat"
+
+nat_validator = {
+    create_section_name=false,
+    object_validator=function(value) return check_nat(value) end,
+    {name="name"},
+    {name="src", validator=function(value) return utils.in_array(value, {"#default", "#source"}) or ifutil.is_interface_available(value) end, message="invalid src", code="428"},
+    {name="src_ip", validator=function(value) return utils.is_valid_ip(value) end, message="invalid src_ip"},
+    {name="src_dip", validator=function(value) return utils.is_valid_ip(value) end, message="invalid src_dip"},
+    {name="src_port", validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="invalid src_port"},
+    {name="src_dport", validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="invalid src_port"},
+    {name="proto", validator=function(value) return utils.in_array(value, {"tcp", "udp", "tcpudp", "udplite", "icmp", "esp", "ah", "sctp", "all"}) end, message="invalid proto"},
+    {name="dest", validator=function(value) return utils.in_array(value, {"#default", "#source"}) or ifutil.is_interface_available(value) end, message="invalid dest", code="428"},
+    {name="dest_ip", validator=function(value) return utils.is_valid_ip(value) end, message="invalid dest_ip"},
+    {name="dest_port", validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="invalid dest_port"},
+    {name="target", validator=function(value) return utils.in_array(value, {"DNAT", "SNAT", "MASQUERADE"}) end, message="invalid target"},
+    {name="index", validator=function(value) return utils.is_integer_and_in_range(value, -1) end, message="invalid index"},
+}
+
+nat_processor = {
+    nat={create="create_nat", delete="delete_nat", validator=nat_validator},
+    configuration=uci_conf
+}
+
+function index()
+    ver = "v1"
+    configuration = "nat"
+    entry({"sdewan", configuration, ver, "nats"}, call("handle_request")).leaf = true
+end
+
+function check_nat(value)
+    local target = value["target"]
+    if target == "SNAT" then
+        if value["src_dip"] == nil then
+            return false, "src_dip is required for SNAT"
+        end
+        if value["dest"] == nil then
+            return false, "dest is required for SNAT"
+        end
+    end
+
+    if target == "MASQUERADE" then
+        if value["dest"] == nil then
+            return false, "dest is required for SNAT MASQUERADE"
+        end
+    end
+
+    if target == "DNAT" then
+--      if value["src"] == nil then
+--          return false, "src is required for DNAT"
+--      end
+        if value["dest_ip"] == nil and value["dest_port"] == nil then
+            return false, "dest_ip or dest_port are required for DNAT"
+        end
+    end
+
+    return true, value
+end
+
+-- Request Handler
+function handle_request()
+    local conf = io.open("/etc/config/" .. uci_conf, "r")
+    if conf == nil then
+        conf = io.open("/etc/config/" .. uci_conf, "w")
+    end
+    conf:close()
+
+    local handler = utils.handles_table[utils.get_req_method()]
+    if handler == nil then
+        utils.response_error(405, "Method Not Allowed")
+    else
+        return utils[handler](_M, nat_processor)
+    end
+end
+
+-- generate iptables command for nat
+function nat_command(nat, op)
+    local target = nat["target"]
+    local proto = nat["proto"]
+    local src = nat["src"]
+    local src_ip = nat["src_ip"]
+    if src == "#default" then
+        src = ifutil.get_default_ifname()
+    end
+    local src_dip = nat["src_dip"]
+    local src_port = nat["src_port"]
+    local src_dport = nat["src_dport"]
+    local dest = nat["dest"]
+    local dest_ip = nat["dest_ip"]
+    if dest == "#default" then
+        dest = ifutil.get_default_ifname()
+    end
+    local dest_port = nat["dest_port"]
+    local index = nat["index"]
+    if index == nil or index == "" then
+        index = "0"
+    end
+
+    local comm = "iptables -t nat"
+    if op == "create" then
+        if index == "0" then
+            comm = comm .. " -A"
+        else
+            comm = comm .. " -I"
+        end
+    else
+        comm = comm .. " -D"
+    end
+    if target == "SNAT" or target == "MASQUERADE" then
+        comm = comm .. " POSTROUTING"
+        if index ~= "0" and op == "create" then
+            comm = comm .. " " .. index
+        end
+        if dest == "#source" then
+            dest = ifutil.get_name_by_ip(src_dip)
+        end
+        if dest ~= nil and dest ~= "" then
+            comm = comm .. " -o " .. dest
+        end
+    else
+        comm = comm .. " PREROUTING"
+        if index ~= "0" and op == "create" then
+            comm = comm .. " " .. index
+        end
+        if src ~= nil and src ~= "" then
+            comm = comm .. " -i " .. src
+        end
+    end
+
+    if proto ~= nil and proto ~= "" then
+        comm = comm .. " -p " .. proto
+    end
+    if src_ip ~= nil and src_ip ~= "" then
+        comm = comm .. " -s " .. src_ip
+    end
+    if src_port ~= nil and src_port ~= "" then
+        comm = comm .. " --sport " .. src_port
+    end
+
+    if target == "SNAT" then
+        if dest_ip ~= nil and dest_ip ~= "" then
+            comm = comm .. " -d " .. dest_ip
+        end
+        if dest_port ~= nil and dest_port ~= "" then
+            comm = comm .. " --dport " .. dest_port
+        end
+        local new_src = src_dip
+        if src_dport ~= nil and src_dport ~= "" then
+            new_src = new_src .. ":" .. src_dport
+        end
+        comm = comm .. " -j SNAT --to-source " .. new_src
+    elseif target == "DNAT" then
+        if src_dip ~= nil and src_dip ~= "" then
+            comm = comm .. " -d " .. src_dip
+        end
+        if src_dport ~= nil and src_dport ~= "" then
+            comm = comm .. " --dport " .. src_dport
+        end
+        local new_des = dest_ip
+        if new_des ~= nil and new_des ~= "" then
+            if dest_port ~= nil and dest_port ~= "" then
+                new_des = new_des .. ":" .. dest_port
+            end
+            comm = comm .. " -j DNAT --to-destination " .. new_des
+        else
+            if dest_port ~= nil and dest_port ~= "" then
+                new_des = dest_port
+                comm = comm .. " -j REDIRECT --to-port " .. new_des
+            end
+        end
+    else
+        if dest_ip ~= nil and dest_ip ~= "" then
+            comm = comm .. " -d " .. dest_ip
+        end
+        if dest_port ~= nil and dest_port ~= "" then
+            comm = comm .. " --dport " .. dest_port
+        end
+        comm = comm .. " -j MASQUERADE"
+    end
+    utils.log(comm)
+    return comm
+end
+
+-- create a nat
+function create_nat(nat)
+    local name = nat.name
+    local res, code, msg = utils.create_uci_section(uci_conf, nat_validator, "nat", nat)
+
+    if res == false then
+        uci:revert(uci_conf)
+        return res, code, msg
+    end
+
+    -- create nat rule
+    local comm = nat_command(nat, "create")
+    os.execute(comm)
+
+    -- commit change
+    uci:save(uci_conf)
+    uci:commit(uci_conf)
+
+    return true
+end
+
+-- delete a nat
+function delete_nat(name)
+    -- check whether nat is defined
+    local nat = utils.get_object(_M, nat_processor, "nat", name)
+    if nat == nil then
+        return false, 404, "nat " .. name .. " is not defined"
+    end
+
+    -- delete nat rule in iptable
+    local comm = nat_command(nat, "delete")
+    os.execute(comm)
+
+    utils.delete_uci_section(uci_conf, nat_validator, nat, "nat")
+
+    -- commit change
+    uci:save(uci_conf)
+    uci:commit(uci_conf)
+
+    return true
+end
index df79eae..5983a03 100644 (file)
@@ -1,4 +1,4 @@
---- SPDX-License-Identifier: Apache-2.0 
+--- SPDX-License-Identifier: Apache-2.0
 --- Copyright (c) 2021 Intel Corporation
 
 module("luci.controller.rest_v1.route_rest", package.seeall)
@@ -9,6 +9,24 @@ json = require "luci.jsonc"
 io = require "io"
 sys = require "luci.sys"
 utils = require "luci.controller.rest_v1.utils"
+ifutil = require "luci.controller.rest_v1.ifutil"
+
+uci_conf = "route-cnf"
+
+route_validator = {
+    create_section_name=false,
+    {name="name"},
+    {name="dst", required=true, validator=function(value) return (value == "default") or utils.is_valid_ip(value) end, message="Invalid Destination IP Address"},
+    {name="src", validator=function(value) return utils.is_valid_ip_address(value) end, message="Invalid Source IP Address"},
+    {name="gw", validator=function(value) return utils.is_valid_ip_address(value) end, message="Invalid Gateway IP Address"},
+    {name="dev", required=true, validator=function(value) return (value == "#default") or ifutil.is_interface_available(value) end, message="Invalid interface", code="428"},
+    {name="table", validator=function(value) return utils.in_array(value, {"default", "cnf"}) end, message="Bad route table"},
+}
+
+route_processor = {
+    route={create="create_route", delete="delete_route", validator=route_validator},
+    configuration=uci_conf
+}
 
 function index()
     ver = "v1"
@@ -18,230 +36,92 @@ end
 
 -- Request Handler
 function handle_request()
-    local method = utils.get_req_method()
-    if method == "PUT" then
-        return update_route()
-    elseif method == "POST" then
-        return create_route()
-    elseif method == "DELETE" then
-        return delete_route()
-    elseif method == "GET" then
-        return get_route()
-    else
+    local conf = io.open("/etc/config/" .. uci_conf, "r")
+    if conf == nil then
+        conf = io.open("/etc/config/" .. uci_conf, "w")
+    end
+    conf:close()
+
+    local handler = utils.handles_table[utils.get_req_method()]
+    if handler == nil then
         utils.response_error(405, "Method Not Allowed")
+    else
+        return utils[handler](_M, route_processor)
     end
 end
 
--- Post
-function create_route()
-    local obj = utils.get_request_body_object()
-    if obj == nil then
-        utils.response_error(400, "No Route Data")
-        return
-    end
-    if is_duplicated(obj.name, obj.dst) then
-        utils.response_error(409, "Duplicated Route Configuration")
-        return
-    end
-    if not utils.is_valid_ip(obj.dst) then
-        utils.response_error(400, "Invalid Destination IP Address")
-        return
-    end
-    if not utils.is_valid_ip_address(obj.gw) then
-        utils.response_error(400, "Invalid gateway IP Address")
-        return
+-- generate command for route
+function route_command(route, op)
+    local dst = route["dst"]
+    local src = route["src"]
+    local gw = route["gw"]
+    local dev = route["dev"]
+    local t = route["table"]
+    if dev == "#default" then
+        dev = ifutil.get_default_ifname()
     end
 
-    local iface = get_dev_name(obj.dev)
-    if obj.table == "default" then
-        local comm = "ip route add "..obj.dst.." via "..obj.gw.." dev "..iface
-        os.execute(comm)
-    elseif obj.table == "cnf" then
-        local comm = "ip route add table 40 "..obj.dst.." via "..obj.gw.." dev "..iface
-        os.execute(comm)
+    local comm = "ip route"
+    if op == "create" then
+        comm = comm .. " add"
     else
-        utils.response_error(400, "Bad route table")
-        return
+        comm = comm .. " del"
     end
-    local file = io.open("/etc/route_cr.info", "a+")
-    file:write(obj.name, " ", obj.dst, " ", obj.gw, " ", obj.dev, " ", obj.table, "\n")
-    file:close()
-    luci.http.prepare_content("application/json")
-    luci.http.write_json(obj)
-end
 
--- Delete
-function delete_route()
-    local uri_list = utils.get_URI_list(7)
-    if uri_list == nil then
-        return
+    if t == "cnf" then
+        comm = comm .. " table 40"
     end
-    local name = uri_list[#uri_list]
-    local file = io.open("/etc/route_cr.info", "r")
-    content = {}
-    for line in file:lines() do
-        local message = split(line, ' ')
-        if name ~= message[1] then
-            content[#content+1] = line
-        else
-           local iface = get_dev_name(message[4])
-            if message[5] == "cnf" then
-                local comm = "ip route del table 40 "..message[2].." via "..message[3].." dev "..iface
-                os.execute(comm)
-            else
-                local comm = "ip route del "..message[2].." via "..message[3].." dev "..iface
-                os.execute(comm)
-            end
-        end
+    comm = comm .. " " .. dst
+    if gw ~= nil and gw ~= "" then
+        comm = comm .. " via " .. gw
     end
-    file:close()
-    local file = io.open("/etc/route_cr.info", "w+")
-    for i = 1, #content do
-        file:write(content[i])
+    comm = comm .. " dev " .. dev
+    if src ~= nil and src ~= "" then
+        comm = comm .. " src " .. src
     end
-    file:close()
+
+    utils.log(comm)
+    return comm
 end
 
--- Update
-function update_route()
-    local uri_list = utils.get_URI_list(7)
-    if uri_list == nil then
-        return
-    end
-    local name = uri_list[#uri_list]
-    local obj = utils.get_request_body_object()
-    if obj == nil then
-        utils.response_error(400, "Route CR not found")
-        return
-    end
-    if obj.name ~= name then
-        utils.response_error(400, "Route CR name mismatch")
-        return
-    end
-    if not utils.is_valid_ip(obj.dst) then
-        utils.response_error(400, "Invalid Destination IP Address")
-        return
-    end
-    if not utils.is_valid_ip_address(obj.gw) then
-        utils.response_error(400, "Invalid gateway IP Address")
-        return
-    end
+-- create a route
+function create_route(route)
+    local name = route.name
+    local res, code, msg = utils.create_uci_section(uci_conf, route_validator, "route", route)
 
-    local file = io.open("/etc/route_cr.info", "r")
-    content = {}
-    for line in file:lines() do
-        local message = split(line, ' ')
-        if name ~= message[1] then
-            content[#content+1] = line
-        else
-            if obj.dst ~= message[2] or obj.table ~= message[5] then
-                utils.response_error(400, "Route CR mismatch")
-               file:close()
-                return
-            end
-           local iface = get_dev_name(obj.dev)
-            if obj.table == "default" then
-                local comm = "ip route replace "..obj.dst.." via "..obj.gw.." dev "..iface
-                os.execute(comm)
-            elseif obj.table == "cnf" then
-                local comm = "ip route replace table 40 "..obj.dst.." via "..obj.gw.." dev "..iface
-                os.execute(comm)
-            else
-                utils.response_error(400, "Bad route table")
-                return
-            end
-            content[#content+1] = obj.name.." "..obj.dst.." "..obj.gw.." "..obj.dev.." "..obj.table.."\n"
-        end
-    end
-    file:close()
-    local file = io.open("/etc/route_cr.info", "w+")
-    for i = 1, #content do
-        file:write(content[i])
+    if res == false then
+        uci:revert(uci_conf)
+        return res, code, msg
     end
-    file:close()
-    luci.http.prepare_content("application/json")
-    luci.http.write_json(obj)
-end
 
--- Get
-function get_route()
-    local uri_list = utils.get_URI_list()
-    local file = io.open("/etc/route_cr.info", "r")
-    if #uri_list == 6 then
-        local objs = {}
-        objs["routes"] = {}
-        for line in file:lines() do
-            local message = split(line, ' ')
-            local obj = {}
-            obj["name"] = message[1]
-            obj["dst"] = message[2]
-            obj["gw"] = message[3]
-            obj["dev"] = message[4]
-            obj["table"] = message[5]
-            table.insert(objs["routes"], obj)
-        end
-        luci.http.prepare_content("application/json")
-        luci.http.write_json(objs)
-    elseif #uri_list == 7 then
-        local name = uri_list[#uri_list]
-        local no = true
-        for line in file:lines() do
-            local message = split(line, ' ')
-            if name == message[1] then
-                no = false
-                local obj = {}
-                obj["name"] = message[1]
-                obj["dst"] = message[2]
-                obj["gw"] = message[3]
-                obj["dev"] = message[4]
-                obj["table"] = message[5]
-                luci.http.prepare_content("application/json")
-                luci.http.write_json(obj)
-                break
-            end
-        end
-        if no then
-            utils.response_error(404, "Cannot find ".."Route CR ".."[".. name.."]" )
-        end
-    else
-        utils.response_error(400, "Bad request URI")
-    end
-    file:close()
-end
+    -- create route rule
+    local comm = route_command(route, "create")
+    os.execute(comm)
+
+    -- commit change
+    uci:save(uci_conf)
+    uci:commit(uci_conf)
 
--- Sync and validate
-function split(str,reps)
-    local arr = {}
-    string.gsub(str,'[^'..reps..']+',function(w)
-        table.insert(arr, w)
-    end)
-    return arr
+    return true
 end
 
-function is_duplicated(name, dst)
-    local file = io.open("/etc/route_cr.info", "r")
-    local judge = false
-    for line in file:lines() do
-        local message = split(line, ' ')
-        if name == message[1] then
-            judge = true
-            break
-        end
-        if dst == message[2] then
-            judge = true
-            break
-        end
+-- delete a route
+function delete_route(name)
+    -- check whether route is defined
+    local route = utils.get_object(_M, route_processor, "route", name)
+    if route == nil then
+        return false, 404, "route " .. name .. " is not defined"
     end
-    file:close()
-    return judge
-end
 
-function get_dev_name(name)
-    --TODO
-    return name
-end
+    -- delete route rule
+    local comm = route_command(route, "delete")
+    os.execute(comm)
+
+    utils.delete_uci_section(uci_conf, route_validator, route, "route")
+
+    -- commit change
+    uci:save(uci_conf)
+    uci:commit(uci_conf)
 
-function strict_subnet(ip)
-    --TODO
     return true
 end
index 7458935..1c1dcaa 100644 (file)
@@ -1,4 +1,4 @@
---- SPDX-License-Identifier: Apache-2.0 
+--- SPDX-License-Identifier: Apache-2.0
 --- Copyright (c) 2021 Intel Corporation
 
 module("luci.controller.rest_v1.rule_rest", package.seeall)
@@ -9,6 +9,28 @@ json = require "luci.jsonc"
 io = require "io"
 sys = require "luci.sys"
 utils = require "luci.controller.rest_v1.utils"
+ifutil = require "luci.controller.rest_v1.ifutil"
+
+uci_conf = "rule-cnf"
+
+rule_validator = {
+    create_section_name=false,
+    object_validator=function(value) return check_rule(value) end,
+    {name="name"},
+    {name="src", validator=function(value) return utils.is_valid_ip(value) end, message="Invalid Source IP Address"},
+    {name="dst", validator=function(value) return utils.is_valid_ip(value) end, message="Invalid Destination IP Address"},
+    {name="prio", validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="Invalid Prioroty"},
+    {name="table", required=true, validator=function(value) return utils.in_array(value, {"main", "local", "default"}) or utils.is_integer_and_in_range(value, 0) end, message="Invalid Table"},
+    {name="fwmark", validator=function(value) return check_fwmark(value) end, message="Invalid fwmark"},
+    {name="flag",
+        load_func=function(value) if value["flag"] == "true" then return true else return false end end,
+        save_func=function(value) if value["flag"] == true then return true, "true" else return true, "false" end end},
+}
+
+rule_processor = {
+    rule={create="create_rule", delete="delete_rule", validator=rule_validator},
+    configuration=uci_conf
+}
 
 function index()
     ver = "v1"
@@ -16,311 +38,123 @@ function index()
     entry({"sdewan", configuration, ver, "rules"}, call("handle_request")).leaf = true
 end
 
+function check_rule(value)
+    local src = value["src"]
+    local dst = value["dst"]
+    if src == "" and dst == "" then
+        return false, "src or dst are required for rule"
+    end
+
+    return true, value
+end
+
 -- Request Handler
 function handle_request()
-    local method = utils.get_req_method()
-    if method == "PUT" then
-        return update_rule()
-    elseif method == "POST" then
-        return create_rule()
-    elseif method == "DELETE" then
-        return delete_rule()
-    elseif method == "GET" then
-        return get_rule()
-    else
+    local conf = io.open("/etc/config/" .. uci_conf, "r")
+    if conf == nil then
+        conf = io.open("/etc/config/" .. uci_conf, "w")
+    end
+    conf:close()
+
+    local handler = utils.handles_table[utils.get_req_method()]
+    if handler == nil then
         utils.response_error(405, "Method Not Allowed")
+    else
+        return utils[handler](_M, rule_processor)
     end
 end
 
--- Post
-function create_rule()
-    local obj = utils.get_request_body_object()
-    if obj == nil then
-        utils.response_error(400, "No Rule Data")
-        return
-    end
-    if is_duplicated(obj.name, obj.src, obj.dst) then
-        utils.response_error(409, "Duplicated Rule Configuration")
-        return
-    end
-    if not is_valid_format(obj.src, obj.dst, obj.prio, obj.table, obj.fwmark) then
-        utils.response_error(400, "Invalid rule format")
-        return
+function check_fwmark(value)
+    local num = tonumber(value, 16)
+    if not num then
+        return false, "not a number"
+    elseif string.len(value) > 10 then
+        return false, "too large"
     end
 
-    local comm = "ip rule add "
-    comm = rule_gen(comm, obj.src, obj.dst, obj.prio, obj.table, obj.fwmark, obj.flag)
-    os.execute(comm)
-
-    local file = io.open("/etc/rule_cr.info", "a+")
-    local rule_str = input_format(obj.name, obj.src, obj.dst, obj.prio, obj.table, obj.fwmark, obj.flag)
-    file:write(rule_str, "\n")
-    file:close()
-    luci.http.prepare_content("application/json")
-    luci.http.write_json(obj)
+    return true, value
 end
 
--- Delete
-function delete_rule()
-    local uri_list = utils.get_URI_list(7)
-    if uri_list == nil then
-        return
-    end
-    local name = uri_list[#uri_list]
-    local file = io.open("/etc/rule_cr.info", "r")
-    local content = {}
-    for line in file:lines() do
-        local message = split(line, ',')
-        if name ~= message[1] then
-            content[#content+1] = line
-        else
-            local comm = "ip rule del "
-            comm = rule_gen(comm, message[2], message[3], message[4], message[5], message[6], message[7])
-            os.execute(comm)
-        end
-    end
-    file:close()
-    local file = io.open("/etc/rule_cr.info", "w+")
-    for i = 1, #content do
-        file:write(content[i], "\n")
+-- generate command for rule
+function rule_command(rule, op)
+    local src = rule["src"]
+    local dst = rule["dst"]
+    local prio = rule["prio"]
+    local t = rule["table"]
+    local fwmark = rule["fwmark"]
+    local flag = rule["flag"]
+
+    local comm = "ip rule"
+    if op == "create" then
+        comm = comm .. " add"
+    else
+        comm = comm .. " del"
     end
-    file:close()
-end
 
--- Update
-function update_rule()
-    local uri_list = utils.get_URI_list(7)
-    if uri_list == nil then
-        return
-    end
-    local name = uri_list[#uri_list]
-    local obj = utils.get_request_body_object()
-    if obj == nil then
-        utils.response_error(400, "Rule CR not found")
-        return
+    if tostring(flag) == "true" then
+        comm = comm .. " not"
     end
-    if obj.name ~= name then
-        utils.response_error(400, "Rule CR name mismatch")
-        return
+    if prio ~= nil and prio ~= "" then
+        comm = comm .." prio " .. prio
     end
-    if not is_valid_format(obj.src, obj.dst, obj.prio, obj.table, obj.fwmark) then
-        utils.response_error(400, "Invalid rule format")
-        return
+    if src ~= nil and src ~= "" then
+        comm = comm .." from " .. src
     end
-
-    local file = io.open("/etc/rule_cr.info", "r")
-    local content = {}
-    local is_found = false
-    for line in file:lines() do
-        local message = split(line, ',')
-        if name ~= message[1] then
-            content[#content+1] = line
-        else
-            is_found = true
-            local pre_comm = "ip rule del "
-            pre_comm = rule_gen(pre_comm, message[2], message[3], message[4], message[5], message[6], message[7])
-            os.execute(pre_comm)
-            local post_comm = "ip rule add "
-            post_comm = rule_gen(post_comm, obj.src, obj.dst, obj.prio, obj.table, obj.fwmark, obj.flag)
-            os.execute(post_comm)
-            content[#content+1] = input_format(obj.name, obj.src, obj.dst, obj.prio, obj.table, obj.fwmark, obj.flag)
-        end
+    if dst ~= nil and dst ~= "" then
+        comm = comm .. " to " .. dst
     end
-    file:close()
 
-    if not is_found then
-        utils.response_error(404, "Cannot find ".."Rule ".."[".. name.."]".." to update." )
-        return
+    if t == nil or t == "" then
+        t = "main"
     end
+    comm = comm .. " lookup " .. t
 
-    local file = io.open("/etc/rule_cr.info", "w+")
-    for i = 1, #content do
-        file:write(content[i], "\n")
+    if fwmark ~= nil and fwmark ~= "" then
+        comm = comm .. " fwmark " .. fwmark
     end
-    file:close()
-    luci.http.prepare_content("application/json")
-    luci.http.write_json(obj)
-end
 
--- Get
-function get_rule()
-    local uri_list = utils.get_URI_list()
-    local file = io.open("/etc/rule_cr.info", "r")
-    if #uri_list == 6 then
-        local objs = {}
-        objs["rules"] = {}
-        for line in file:lines() do
-            local message = split(line, ',')
-            local obj = {}
-            obj["name"] = message[1]
-            obj["src"] = message[2]
-            obj["dst"] = message[3]
-            obj["prio"] = message[4]
-            obj["table"] = message[5]
-            obj["fwmark"] = message[6]
-            if message[7] == "false" then
-                obj["flag"] = false
-            else
-                obj["flag"] = true
-            end
-            table.insert(objs["rules"], obj)
-        end
-        luci.http.prepare_content("application/json")
-        luci.http.write_json(objs)
-    elseif #uri_list == 7 then
-        local name = uri_list[#uri_list]
-        local no = true
-        for line in file:lines() do
-            local message = split(line, ',')
-            if name == message[1] then
-                no = false
-                local obj = {}
-                obj["name"] = message[1]
-                obj["src"] = message[2]
-                obj["dst"] = message[3]
-                obj["prio"] = message[4]
-                obj["table"] = message[5]
-                obj["fwmark"] = message[6]
-                if message[7] == "false" then
-                    obj["flag"] = false
-                else
-                    obj["flag"] = true
-                end
-                luci.http.prepare_content("application/json")
-                luci.http.write_json(obj)
-                break
-            end
-        end
-        if no then
-            utils.response_error(404, "Cannot find ".."Rule CR ".."[".. name.."]" )
-        end
-    else
-        utils.response_error(400, "Bad request URI")
-    end
-    file:close()
+    utils.log(comm)
+    return comm
 end
 
--- Sync and validate
-function split(str,reps)
-    local arr = {}
-    string.gsub(str,'[^'..reps..']+',function(w)
-        table.insert(arr, w)
-    end)
-    return arr
-end
+-- create a rule
+function create_rule(rule)
+    local name = rule.name
+    local res, code, msg = utils.create_uci_section(uci_conf, rule_validator, "rule", rule)
 
-function is_duplicated(name, src, dst)
-    local file = io.open("/etc/rule_cr.info", "r")
-    local judge = false
-    for line in file:lines() do
-        local message = split(line, ',')
-        if name == message[1] then
-            judge = true
-            break
-        end
-        if src == "" then
-            src = "NULL"
-        end
-        if dst == "" then
-            dst = "NULL"
-        end
-        if src == message[2] and dst == message[3] then
-            judge = true
-            break
-        end
+    if res == false then
+        uci:revert(uci_conf)
+        return res, code, msg
     end
-    file:close()
-    return judge
-end
 
-function is_valid_format(src, dst, prio, table, fwmark)
-    local judge = true
-    if src == "" and dst == "" then
-        judge = false
-    elseif src == "" then
-        judge = utils.is_valid_ip(dst)
-    elseif dst == "" then
-        judge = utils.is_valid_ip(src)
-    else
-        judge = utils.is_valid_ip(dst) and utils.is_valid_ip(src)
-    end
+    -- create rule
+    local comm = rule_command(rule, "create")
+    os.execute(comm)
 
-    if prio ~= "" then
-        judge = judge and utils.is_integer_and_in_range(prio, 0)
-    end
+    -- commit change
+    uci:save(uci_conf)
+    uci:commit(uci_conf)
 
-    if fwmark ~= "" then
-        local num = tonumber(fwmark, 16)
-        if not num then
-            judge = false
-        elseif string.len(fwmark) > 10 then
-            judge = false
-        end
-    end
-
-    if table == "main" or table == "local" or table == "default" or table == "" then
-        return judge
-    else
-        table_id = get_table_id(table)
-        judge = judge and utils.is_integer_and_in_range(table_id, 0)
-        return judge
-    end
+    return true
 end
 
-function rule_gen(comm, src, dst, prio, table, fwmark, flag)
-    if tostring(flag) == "true" then
-        comm = comm.."not "
-    end
-    if prio ~= "" and prio ~= "NULL" then
-        comm = comm.."prio "..prio.." "
-    end
-    if src == "" or src == "NULL" then
-        comm = comm.."to "..dst.." "
-    elseif dst == "" or dst == "NULL" then
-        comm = comm.."from "..src.." "
-    else
-        comm = comm.."from "..src.." to "..dst.." "
+-- delete a rule
+function delete_rule(name)
+    -- check whether rule is defined
+    local rule = utils.get_object(_M, rule_processor, "rule", name)
+    if rule == nil then
+        return false, 404, "rule " .. name .. " is not defined"
     end
-    local table_id = get_table_id(table)
-    comm = comm.."lookup "..table_id
-    if fwmark ~= "" and fwmark ~= "NULL" then
-        comm = comm.." fwmark "..fwmark
-    end
-    return comm
-end
 
-function get_table_id(table)
-    --TODO
-    local table_id = table
-    if table == "" then
-        table_id = "main"
-    end
-    return table_id
-end
+    -- delete rule
+    local comm = rule_command(rule, "delete")
+    os.execute(comm)
 
-function input_format(name, src, dst, prio, table, fwmark, flag)
-    local str = name
-    if src == "" then
-        str = str..",".."NULL"
-    else
-        str = str..","..src
-    end
-    if dst == "" then
-        str = str..",".."NULL"
-    else
-        str = str..","..dst
-    end
-    if prio == "" then
-        str = str..",".."NULL"
-    else
-        str = str..","..prio
-    end
-    str = str..","..get_table_id(table)
-    if fwmark  == "" then
-        str = str..",".."NULL"
-    else
-        str = str..","..fwmark
-    end
-    str = str..","..tostring(flag)
-    return str
+    utils.delete_uci_section(uci_conf, rule_validator, rule, "rule")
+
+    -- commit change
+    uci:save(uci_conf)
+    uci:commit(uci_conf)
+
+    return true
 end
index 99cb307..4376296 100644 (file)
@@ -446,9 +446,12 @@ function create_uci_section(configuration, validator, object_type, obj)
                     end
                     local obj_value = obj[name]
                     if v["save_func"] ~= nil and type(v["save_func"]) == "function" then
-                        res, obj_value = v["save_func"](obj)
+                        res, obj_value, code = v["save_func"](obj)
+                        if code == nil then
+                            code = 400
+                        end
                         if res == false then
-                            return res, obj_value
+                            return res, code, obj_value
                         end
                     end
                     if type(obj_value) == "table" then
@@ -464,9 +467,9 @@ function create_uci_section(configuration, validator, object_type, obj)
                                 local sub_obj_names = {}
                                 for k=1, #section_obj do
                                     sub_obj_names[k] = section_obj[k].name
-                                    local res, msg = create_uci_section(configuration, v["item_validator"], option_name, section_obj[k])
+                                    local res, code, msg = create_uci_section(configuration, v["item_validator"], option_name, section_obj[k])
                                     if res == false then
-                                        return res, msg
+                                        return res, code, msg
                                     end
                                 end
                                 uci:set_list(configuration, obj_section, option_name, sub_obj_names)
@@ -476,9 +479,9 @@ function create_uci_section(configuration, validator, object_type, obj)
                         end
                     else
                         if v["validator"] ~= nil and type(v["validator"]) == "table" then
-                            local res, msg = create_uci_section(configuration, v["validator"], target_name, obj_value)
+                            local res, code, msg = create_uci_section(configuration, v["validator"], target_name, obj_value)
                             if res == false then
-                                return res, msg
+                                return res, code, msg
                             end
                             uci:set(configuration, obj_section, target_name, obj_value.name)
                         else
@@ -501,11 +504,11 @@ function create_object(module_table, processors, object_type, obj)
        and module_table[processors[object_type]["create"]] ~= nil then
             return module_table[processors[object_type]["create"]](obj)
     else
-        local res, msg = create_uci_section(processors["configuration"], processors[object_type].validator, object_type, obj)
+        local res, code, msg = create_uci_section(processors["configuration"], processors[object_type].validator, object_type, obj)
 
         if res == false then
             uci:revert(processors["configuration"])
-            return res, msg
+            return res, code, msg
         end
 
         -- commit change
@@ -680,6 +683,7 @@ function validate_and_set_data(validator, src)
             local val_func = v["validator"]
             local item_val_func = v["item_validator"]
             local error_message = v["message"]
+            local error_code = v["code"]
             local required = v["required"]
             local default = v["default"]
             local target_name = name
@@ -692,6 +696,12 @@ function validate_and_set_data(validator, src)
                 error_message = ""
             end
 
+            if error_code == nil then
+                error_code = ""
+            else
+                error_code = error_code .. ":"
+            end
+
             local value = src[name]
             if value ~= nil and type(value) == "string" then
                 value = trim(value)
@@ -714,9 +724,9 @@ function validate_and_set_data(validator, src)
 
                 if res == false then
                     if ret_obj ~= nil and ret_obj ~= "" then
-                        return false, "Field[" .. name .. "] checked failed: " .. error_message .. " [" .. ret_obj .. "]"
+                        return false, error_code .. "Field[" .. name .. "] checked failed: " .. error_message .. " [" .. ret_obj .. "]"
                     else
-                        return false, "Field[" .. name .. "] checked failed: " .. error_message
+                        return false, error_code .. "Field[" .. name .. "] checked failed: " .. error_message
                     end
                 else
                     if ret_obj ~= nil then
@@ -779,7 +789,17 @@ function get_and_validate_body_object(validator)
 
     local res, res_obj = validate_and_set_data(validator, body_obj)
     if not res then
-        response_error(400, res_obj)
+        local code = 400
+        local message = res_obj
+        local i,j = string.find(res_obj, ":")
+        if i ~= nil then
+            local co = string.sub(res_obj, 1, i-1)
+            if tonumber(co) ~= nil then
+                code = co
+                message = string.sub(res_obj, j+1, string.len(res_obj))
+            end
+        end
+        response_error(code, message)
         return nil
     end
 
index 1325b2e..87c9d3b 100644 (file)
@@ -66,7 +66,7 @@ func wildMatchArray(p []rune, pindex int, v []rune, vindex int) bool {
        return true
 }
 
-// +kubebuilder:webhook:path=/validate-sdewan-bucket-permission,mutating=false,failurePolicy=fail,groups="batch.sdewan.akraino.org",resources=mwan3policies;mwan3rules;firewallzones;firewallforwardings;firewallrules;firewallsnats;firewalldnats;cnfservice;cnfstatuses;sdewanapplication;ipsecproposals;ipsechosts;ipsecsites,verbs=create;update;delete,versions=v1alpha1,name=validate-sdewan-bucket.akraino.org
+// +kubebuilder:webhook:path=/validate-sdewan-bucket-permission,mutating=false,failurePolicy=fail,groups="batch.sdewan.akraino.org",resources=mwan3policies;mwan3rules;firewallzones;firewallforwardings;firewallrules;firewallsnats;firewalldnats;cnfnats;cnfroutes;cnfrouterules;cnfservices;cnflocalservices;cnfstatuses;sdewanapplication;ipsecproposals;ipsechosts;ipsecsites,verbs=create;update;delete,versions=v1alpha1,name=validate-sdewan-bucket.akraino.org
 
 // bucketPermissionValidator validates Pods
 type bucketPermissionValidator struct {
@@ -114,6 +114,12 @@ func (v *bucketPermissionValidator) Handle(ctx context.Context, req admission.Re
                obj = &FirewallZone{}
        case "FirewallRule":
                obj = &FirewallRule{}
+       case "CNFNAT":
+               obj = &CNFNAT{}
+       case "CNFRoute":
+               obj = &CNFRoute{}
+       case "CNFRouteRule":
+               obj = &CNFRouteRule{}
        case "FirewallDNAT":
                obj = &FirewallDNAT{}
        case "FirewallSNAT":
@@ -128,6 +134,8 @@ func (v *bucketPermissionValidator) Handle(ctx context.Context, req admission.Re
                obj = &CNFService{}
        case "CNFStatus":
                obj = &CNFStatus{}
+       case "CNFLocalService":
+               obj = &CNFLocalService{}
        case "SdewanApplication":
                obj = &SdewanApplication{}
        default:
diff --git a/platform/crd-ctrlr/src/api/v1alpha1/cnflocalservice_types.go b/platform/crd-ctrlr/src/api/v1alpha1/cnflocalservice_types.go
new file mode 100644 (file)
index 0000000..23a2545
--- /dev/null
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: Apache-2.0\r
+// Copyright (c) 2021 Intel Corporation\r
+package v1alpha1\r
+\r
+import (\r
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"\r
+)\r
+\r
+// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\r
+// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\r
+\r
+// CNFLocalServiceStatus defines the observed state of CNFLocalServiceStatus\r
+type CNFLocalServiceStatus struct {\r
+       // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\r
+       // Important: Run "make" to regenerate code after modifying this file\r
+       // +optional\r
+       LocalIP      string `json:"localip,omitempty"`\r
+       // +optional\r
+       LocalPort     string `json:"localport,omitempty"`\r
+       // +optional\r
+       RemoteIPs    []string `json:"remoteips,omitempty"`\r
+       // +optional\r
+       RemotePort    string `json:"remoteport,omitempty"`\r
+       // +optional\r
+       Message      string `json:"message,omitempty"`\r
+}\r
+\r
+func (c *CNFLocalServiceStatus) IsEqual(s *CNFLocalServiceStatus) bool {\r
+       if c.LocalIP != s.LocalIP ||\r
+          c.LocalPort != s.LocalPort ||\r
+          c.RemotePort != s.RemotePort {\r
+               return false\r
+          }\r
+       if len(c.RemoteIPs) != len(s.RemoteIPs) {\r
+               return false\r
+       }\r
+\r
+       for i:=0; i<len(c.RemoteIPs); i++ {\r
+               if c.RemoteIPs[i] != s.RemoteIPs[i] {\r
+                       return false\r
+               }\r
+       }\r
+\r
+       return true\r
+}\r
+\r
+// CNFLocalServiceSpec defines the desired state of CNFService\r
+type CNFLocalServiceSpec struct {\r
+       LocalService string `json:"localservice,omitempty"`\r
+       LocalPort     string `json:"localport,omitempty"`\r
+       RemoteService string `json:"remoteservice,omitempty"`\r
+       RemotePort    string `json:"remoteport,omitempty"`\r
+}\r
+\r
+// +kubebuilder:object:root=true\r
+// +kubebuilder:subresource:status\r
+\r
+// CNFLocalService is the Schema for the cnflocalservices API\r
+type CNFLocalService struct {\r
+       metav1.TypeMeta   `json:",inline"`\r
+       metav1.ObjectMeta `json:"metadata,omitempty"`\r
+\r
+       Spec   CNFLocalServiceSpec `json:"spec,omitempty"`\r
+       Status CNFLocalServiceStatus   `json:"status,omitempty"`\r
+}\r
+\r
+// +kubebuilder:object:root=true\r
+\r
+// CNFLocalServiceList contains a list of CNFLocalServiceList\r
+type CNFLocalServiceList struct {\r
+       metav1.TypeMeta `json:",inline"`\r
+       metav1.ListMeta `json:"metadata,omitempty"`\r
+       Items           []CNFLocalService `json:"items"`\r
+}\r
+\r
+func init() {\r
+       SchemeBuilder.Register(&CNFLocalService{}, &CNFLocalServiceList{})\r
+}\r
diff --git a/platform/crd-ctrlr/src/api/v1alpha1/cnfnat_types.go b/platform/crd-ctrlr/src/api/v1alpha1/cnfnat_types.go
new file mode 100644 (file)
index 0000000..3d5c2c6
--- /dev/null
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: Apache-2.0\r
+// Copyright (c) 2021 Intel Corporation\r
+package v1alpha1\r
+\r
+import (\r
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"\r
+)\r
+\r
+// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\r
+// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\r
+\r
+// CNFNATSpec defines the desired state of CNFNAT\r
+type CNFNATSpec struct {\r
+       // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\r
+       // Important: Run "make" to regenerate code after modifying this file\r
+       Name     string `json:"name,omitempty"`\r
+       Src      string `json:"src,omitempty"`\r
+       SrcIp    string `json:"src_ip,omitempty"`\r
+       SrcDIp   string `json:"src_dip,omitempty"`\r
+       SrcPort  string `json:"src_port,omitempty"`\r
+       SrcDPort string `json:"src_dport,omitempty"`\r
+       Proto    string `json:"proto,omitempty"`\r
+       Dest     string `json:"dest,omitempty"`\r
+       DestIp   string `json:"dest_ip,omitempty"`\r
+       DestPort string `json:"dest_port,omitempty"`\r
+       Target   string `json:"target,omitempty"`\r
+       Index   string `json:"index,omitempty"`\r
+}\r
+\r
+// +kubebuilder:object:root=true\r
+// +kubebuilder:subresource:status\r
+\r
+// CNFNAT is the Schema for the cnfnats API\r
+type CNFNAT struct {\r
+       metav1.TypeMeta   `json:",inline"`\r
+       metav1.ObjectMeta `json:"metadata,omitempty"`\r
+\r
+       Spec   CNFNATSpec `json:"spec,omitempty"`\r
+       Status SdewanStatus     `json:"status,omitempty"`\r
+}\r
+\r
+// +kubebuilder:object:root=true\r
+\r
+// CNFNATList contains a list of CNFNAT\r
+type CNFNATList struct {\r
+       metav1.TypeMeta `json:",inline"`\r
+       metav1.ListMeta `json:"metadata,omitempty"`\r
+       Items           []CNFNAT `json:"items"`\r
+}\r
+\r
+func init() {\r
+       SchemeBuilder.Register(&CNFNAT{}, &CNFNATList{})\r
+}\r
index ac97f94..d962ee1 100644 (file)
@@ -29,7 +29,7 @@ func SetupLabelValidateWebhookWithManager(mgr ctrl.Manager) error {
        return nil
 }
 
-// +kubebuilder:webhook:path=/validate-label,mutating=false,failurePolicy=fail,groups=apps;batch.sdewan.akraino.org,resources=deployments;mwan3policies;mwan3rules;firewallzones;firewallforwardings;firewallrules;firewallsnats;firewalldnats;cnfservice;cnfstatuses;sdewanapplication;ipsecproposals;ipsechosts;ipsecsites,verbs=update,versions=v1;v1alpha1,name=validate-label.akraino.org
+// +kubebuilder:webhook:path=/validate-label,mutating=false,failurePolicy=fail,groups=apps;batch.sdewan.akraino.org,resources=deployments;mwan3policies;mwan3rules;firewallzones;firewallforwardings;firewallrules;firewallsnats;firewalldnats;cnfnats;cnfservices;cnfroutes;cnfrouterules;cnflocalservices;cnfstatuses;sdewanapplication;ipsecproposals;ipsechosts;ipsecsites,verbs=update,versions=v1;v1alpha1,name=validate-label.akraino.org
 
 type labelValidator struct {
        Client  client.Client
@@ -51,6 +51,12 @@ func (v *labelValidator) Handle(ctx context.Context, req admission.Request) admi
                obj = &FirewallZone{}
        case "FirewallRule":
                obj = &FirewallRule{}
+       case "CNFNAT":
+               obj = &CNFNAT{}
+       case "CNFRoute":
+               obj = &CNFRoute{}
+       case "CNFRouteRule":
+               obj = &CNFRouteRule{}
        case "FirewallDNAT":
                obj = &FirewallDNAT{}
        case "FirewallSNAT":
@@ -63,6 +69,8 @@ func (v *labelValidator) Handle(ctx context.Context, req admission.Request) admi
                obj = &IpsecSite{}
        case "CNFService":
                obj = &CNFService{}
+       case "CNFLocalService":
+               obj = &CNFLocalService{}
        case "CNFStatus":
                obj = &CNFStatus{}
        case "SdewanApplication":
index 2d220f4..daeb9c2 100644 (file)
@@ -15,10 +15,12 @@ resources:
 - bases/batch.sdewan.akraino.org_ipsechosts.yaml
 - bases/batch.sdewan.akraino.org_ipsecsites.yaml
 - bases/batch.sdewan.akraino.org_cnfservices.yaml
+- bases/batch.sdewan.akraino.org_cnflocalservices.yaml
 - bases/batch.sdewan.akraino.org_sdewanapplications.yaml
 - bases/batch.sdewan.akraino.org_cnfstatuses.yaml
 - bases/batch.sdewan.akraino.org_cnfroutes.yaml
 - bases/batch.sdewan.akraino.org_cnfrouterules.yaml
+- bases/batch.sdewan.akraino.org_cnfnats.yaml
 # +kubebuilder:scaffold:crdkustomizeresource
 
 patchesStrategicMerge:
@@ -35,10 +37,12 @@ patchesStrategicMerge:
 #- patches/webhook_in_ipsechosts.yaml
 #- patches/webhook_in_ipsecsites.yaml
 #- patches/webhook_in_cnfservices.yaml
+#- patches/webhook_in_cnflocalservices.yaml
 #- patches/webhook_in_sdewanapplications.yaml
 #- patches/webhook_in_cnfstatuses.yaml
 #- patches/webhook_in_cnfroutes.yaml
 #- patches/webhook_in_cnfrouterules.yaml
+#- patches/webhook_in_cnfnats.yaml
 # +kubebuilder:scaffold:crdkustomizewebhookpatch
 
 # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.
@@ -54,10 +58,12 @@ patchesStrategicMerge:
 #- patches/cainjection_in_ipsechosts.yaml
 #- patches/cainjection_in_ipsecsites.yaml
 #- patches/cainjection_in_cnfservices.yaml
+#- patches/cainjection_in_cnflocalservices.yaml
 #- patches/cainjection_in_sdewanapplications.yaml
 #- patches/cainjection_in_cnfstatuses.yaml
 #- patches/cainjection_in_cnfroutes.yaml
 #- patches/cainjection_in_cnfrouterules.yaml
+#- patches/cainjection_in_cnfnats.yaml
 # +kubebuilder:scaffold:crdkustomizecainjectionpatch
 
 # the following config is for teaching kustomize how to do kustomization for CRDs.
diff --git a/platform/crd-ctrlr/src/config/crd/patches/cainjection_in_cnfnats.yaml b/platform/crd-ctrlr/src/config/crd/patches/cainjection_in_cnfnats.yaml
new file mode 100644 (file)
index 0000000..add52c9
--- /dev/null
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: Apache-2.0\r
+# Copyright (c) 2021 Intel Corporation\r
+# The following patch adds a directive for certmanager to inject CA into the CRD\r
+# CRD conversion requires k8s 1.13 or later.\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+kind: CustomResourceDefinition\r
+metadata:\r
+  annotations:\r
+    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)\r
+  name: cnfnats.batch.sdewan.akraino.org\r
diff --git a/platform/crd-ctrlr/src/config/crd/patches/webhook_in_cnfnats.yaml b/platform/crd-ctrlr/src/config/crd/patches/webhook_in_cnfnats.yaml
new file mode 100644 (file)
index 0000000..3652d6a
--- /dev/null
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: Apache-2.0\r
+# Copyright (c) 2021 Intel Corporation\r
+# The following patch enables conversion webhook for CRD\r
+# CRD conversion requires k8s 1.13 or later.\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+kind: CustomResourceDefinition\r
+metadata:\r
+  name: cnfnats.batch.sdewan.akraino.org\r
+spec:\r
+  conversion:\r
+    strategy: Webhook\r
+    webhookClientConfig:\r
+      # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,\r
+      # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)\r
+      caBundle: Cg==\r
+      service:\r
+        namespace: system\r
+        name: webhook-service\r
+        path: /convert\r
index 8a536b0..b38366c 100644 (file)
@@ -34,8 +34,8 @@ spec:
         resources:
           limits:
             cpu: 100m
-            memory: 30Mi
+            memory: 100Mi
           requests:
             cpu: 100m
-            memory: 20Mi
+            memory: 60Mi
       terminationGracePeriodSeconds: 10
diff --git a/platform/crd-ctrlr/src/config/samples/batch_v1alpha1_cnfnat.yaml b/platform/crd-ctrlr/src/config/samples/batch_v1alpha1_cnfnat.yaml
new file mode 100644 (file)
index 0000000..08e1244
--- /dev/null
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: Apache-2.0\r
+# Copyright (c) 2021 Intel Corporation\r
+---\r
+apiVersion: batch.sdewan.akraino.org/v1alpha1\r
+kind: CNFNAT\r
+metadata:\r
+  name: nat-sample\r
+  namespace: default\r
+  labels:\r
+    sdewanPurpose: cnf1\r
+spec:\r
+  src: iface1\r
+  src_ip: 192.168.1.2\r
+  src_dip: 1.2.3.4\r
+  dest: iface2\r
+  proto: tcp\r
+  target: DNAT\r
+...
index 2d878da..f196c7e 100644 (file)
@@ -279,15 +279,16 @@ func ProcessReconcile(r client.Client, logger logr.Logger, req ctrl.Request, han
                if err != nil {
                        log.Error(err, "Failed to add/update "+handler.GetType())
                        setStatus(instance, batchv1alpha1.SdewanStatus{State: batchv1alpha1.Applying, Message: err.Error()})
-                       _, ok := err.(*openwrt.OpenwrtError)
+                       err2, ok := err.(*openwrt.OpenwrtError)
                        err = r.Status().Update(ctx, instance)
                        if err != nil {
                                log.Error(err, "Failed to update status for "+handler.GetType())
                                return ctrl.Result{}, err
                        }
-                       if ok {
+                       if ok && err2.Code != 428 {
                                return ctrl.Result{}, err
                        } else {
+                               // retry if network error or operation failed due to pre-condition is not satisfied (e.g. Code == 428)
                                return ctrl.Result{RequeueAfter: during}, nil
                        }
                }
@@ -347,4 +348,4 @@ func ProcessReconcile(r client.Client, logger logr.Logger, req ctrl.Request, han
        }
 
        return ctrl.Result{}, nil
-}
+}
\ No newline at end of file
diff --git a/platform/crd-ctrlr/src/controllers/cnflocalservice_controller.go b/platform/crd-ctrlr/src/controllers/cnflocalservice_controller.go
new file mode 100644 (file)
index 0000000..0b5dcdf
--- /dev/null
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: Apache-2.0\r
+// Copyright (c) 2021 Intel Corporation\r
+package controllers\r
+\r
+import (\r
+       "context"\r
+       "errors"\r
+       "net"\r
+       "strconv"\r
+       "strings"\r
+       "sync"\r
+       "time"\r
+\r
+       "github.com/go-logr/logr"\r
+       "k8s.io/apimachinery/pkg/runtime"\r
+       errs "k8s.io/apimachinery/pkg/api/errors"\r
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"\r
+       "k8s.io/apimachinery/pkg/util/wait"\r
+       ctrl "sigs.k8s.io/controller-runtime"\r
+       "sigs.k8s.io/controller-runtime/pkg/builder"\r
+       "sigs.k8s.io/controller-runtime/pkg/client"\r
+       "sigs.k8s.io/controller-runtime/pkg/predicate"\r
+\r
+       batchv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"\r
+)\r
+\r
+var inLSQueryStatus = false\r
+\r
+// CNFLocalServiceReconciler reconciles a CNFLocalService object\r
+type CNFLocalServiceReconciler struct {\r
+       client.Client\r
+       Log    logr.Logger\r
+       CheckInterval time.Duration\r
+       Scheme *runtime.Scheme\r
+       mux    sync.Mutex\r
+}\r
+\r
+// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=cnflocalservices,verbs=get;list;watch;create;update;patch;delete\r
+// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=cnflocalservices/status,verbs=get;update;patch\r
+\r
+func (r *CNFLocalServiceReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {\r
+       ctx := context.Background()\r
+       log := r.Log.WithValues("CNFLocalService", req.NamespacedName)\r
+       during, _ := time.ParseDuration("5s")\r
+\r
+       instance, err := r.getInstance(req)\r
+       if err != nil {\r
+               if errs.IsNotFound(err) {\r
+                       // No instance\r
+                       return ctrl.Result{}, nil\r
+               }\r
+               // Error reading the object - requeue the request.\r
+               return ctrl.Result{RequeueAfter: during}, nil\r
+       }\r
+\r
+       finalizerName := "cnflocalservice.finalizers.sdewan.akraino.org"\r
+       delete_timestamp := getDeletionTempstamp(instance)\r
+\r
+       if delete_timestamp.IsZero() {\r
+               // Creating or updating CR\r
+               // Process instance\r
+               err = r.processInstance(instance)\r
+               if err != nil {\r
+                       log.Error(err, "Adding/Updating CR")\r
+                       instance.Status.Message = err.Error()\r
+                       r.Status().Update(ctx, instance)\r
+\r
+                       return ctrl.Result{}, err\r
+               }\r
+\r
+               finalizers := getFinalizers(instance)\r
+               if !containsString(finalizers, finalizerName) {\r
+                       appendFinalizer(instance, finalizerName)\r
+                       if err := r.Update(ctx, instance); err != nil {\r
+                               return ctrl.Result{}, err\r
+                       }\r
+                       log.Info("Added finalizer for CNFLocalService")\r
+               }\r
+       } else {\r
+               // Deleting CR\r
+               // Remove instance\r
+               err = r.removeInstance(instance)\r
+               if err != nil {\r
+                       log.Error(err, "Deleting CR")\r
+                       return ctrl.Result{RequeueAfter: during}, nil\r
+               }\r
+\r
+               finalizers := getFinalizers(instance)\r
+               if containsString(finalizers, finalizerName) {\r
+                       removeFinalizer(instance, finalizerName)\r
+                       if err := r.Update(ctx, instance); err != nil {\r
+                               return ctrl.Result{}, err\r
+                       }\r
+               }\r
+       }\r
+\r
+       return ctrl.Result{}, nil\r
+}\r
+\r
+func (r *CNFLocalServiceReconciler) getInstance(req ctrl.Request) (*batchv1alpha1.CNFLocalService, error) {\r
+       instance := &batchv1alpha1.CNFLocalService{}\r
+       err := r.Get(context.Background(), req.NamespacedName, instance)\r
+       return instance, err\r
+}\r
+\r
+func (r *CNFLocalServiceReconciler) getIP4s(dns string) ([]string, error) {\r
+       ips, err := net.LookupIP(dns)\r
+       var ip4s []string\r
+\r
+       if err == nil {\r
+               for _, ip := range ips {\r
+                       if strings.Contains(ip.String(), ".") {\r
+                               ip4s = append(ip4s, ip.String())\r
+                       }\r
+               }\r
+       }\r
+\r
+       return ip4s, err\r
+}\r
+\r
+func (r *CNFLocalServiceReconciler) processInstance(instance *batchv1alpha1.CNFLocalService) error {\r
+       r.mux.Lock()\r
+       defer r.mux.Unlock()\r
+\r
+       // check local service\r
+       ls := instance.Spec.LocalService\r
+       lips, err := r.getIP4s(ls)\r
+       if err != nil || len(lips) == 0 {\r
+               if err != nil {\r
+                       r.Log.Error(err, "Local Service")\r
+               }\r
+               return errors.New("Cannot reterive LocalService ip")\r
+       }\r
+\r
+       // check remote service\r
+       rs := instance.Spec.RemoteService\r
+       rips, err := r.getIP4s(rs)\r
+       if err != nil || len(rips) == 0 {\r
+               if err != nil {\r
+                       r.Log.Error(err, "Remote Service")\r
+               }\r
+               return errors.New("Cannot reterive RemoteService ip")\r
+       }\r
+\r
+       // check local port\r
+       lp := instance.Spec.LocalPort\r
+       if lp != "" {\r
+               _, err = strconv.Atoi(lp)\r
+               if err != nil {\r
+                       return errors.New("LocalPort: " + err.Error())\r
+               }\r
+       }\r
+\r
+       // check remote port\r
+       rp := instance.Spec.RemotePort\r
+       if rp != "" {\r
+               _, err = strconv.Atoi(rp)\r
+               if err != nil {\r
+                       return errors.New("RemotePort: " + err.Error())\r
+               }\r
+       }\r
+\r
+       var curStatus = batchv1alpha1.CNFLocalServiceStatus {\r
+               LocalIP: lips[0],\r
+               LocalPort: lp,\r
+               RemoteIPs: rips,\r
+               RemotePort: rp,\r
+               Message: "",\r
+       }\r
+\r
+       if !curStatus.IsEqual(&instance.Status) {\r
+               r.removeNats(instance)\r
+               r.addNats(instance, &curStatus)\r
+               instance.Status = curStatus\r
+               r.Status().Update(context.Background(), instance)\r
+       }\r
+\r
+       return nil\r
+}\r
+\r
+func (r *CNFLocalServiceReconciler) addNats(instance *batchv1alpha1.CNFLocalService, status *batchv1alpha1.CNFLocalServiceStatus) error {\r
+       r.Log.Info("Creating New CNFNAT CR for Local Service : " + instance.Name)\r
+       nat_base_name := instance.Name + "nat"\r
+       for i, rip := range status.RemoteIPs {\r
+               nat_name := nat_base_name + strconv.Itoa(i)\r
+               nat_instance := &batchv1alpha1.CNFNAT{\r
+                       ObjectMeta: metav1.ObjectMeta{\r
+                               Name:      nat_name,\r
+                               Namespace: instance.Namespace,\r
+                               Labels: instance.Labels,\r
+                       },\r
+                       Spec: batchv1alpha1.CNFNATSpec{\r
+                               SrcDIp: rip,\r
+                               SrcDPort: status.RemotePort,\r
+                               DestIp: status.LocalIP,\r
+                               DestPort: status.LocalPort,\r
+                               Proto: "tcp",\r
+                               Target: "DNAT",\r
+                       },\r
+               }\r
+\r
+               err := r.Create(context.Background(), nat_instance)\r
+               if err != nil {\r
+                       r.Log.Error(err, "Creating NAT CR : " + nat_name)\r
+               }\r
+       }\r
+       return nil\r
+}\r
+\r
+func (r *CNFLocalServiceReconciler) removeInstance(instance *batchv1alpha1.CNFLocalService) error {\r
+       r.mux.Lock()\r
+       defer r.mux.Unlock()\r
+       return r.removeNats(instance)\r
+}\r
+\r
+func (r *CNFLocalServiceReconciler) removeNats(instance *batchv1alpha1.CNFLocalService) error {\r
+       r.Log.Info("Deleting CNFNAT CR for Local Service : " + instance.Name)\r
+       nat_base_name := instance.Name + "nat"\r
+       for i, _ := range instance.Status.RemoteIPs {\r
+               nat_name := nat_base_name + strconv.Itoa(i)\r
+               nat_instance := &batchv1alpha1.CNFNAT{\r
+                       ObjectMeta: metav1.ObjectMeta{\r
+                               Name:      nat_name,\r
+                               Namespace: instance.Namespace,\r
+                               Labels: instance.Labels,\r
+                       },\r
+                       Spec: batchv1alpha1.CNFNATSpec{},\r
+               }\r
+\r
+               err := r.Delete(context.Background(), nat_instance)\r
+               if err != nil {\r
+                       r.Log.Error(err, "Deleting NAT CR : " + nat_name)\r
+               }\r
+\r
+               // check resource\r
+               err = wait.PollImmediate(time.Second, time.Second*10,\r
+                       func() (bool, error) {\r
+                               nat_instance_temp := &batchv1alpha1.CNFNAT{}\r
+                               err_get := r.Get(context.Background(), client.ObjectKey{\r
+                                       Namespace: instance.Namespace,\r
+                                       Name:      nat_name,\r
+                               }, nat_instance_temp)\r
+\r
+                               if errs.IsNotFound(err_get) {\r
+                                       return true, nil\r
+                               }\r
+                               r.Log.Info("Waiting for Deleting CR : " + nat_name)\r
+                               return false, nil\r
+                       },\r
+               )\r
+\r
+               if err != nil {\r
+                       r.Log.Error(err, "Failed to delete CR : " + nat_name)\r
+               }\r
+       }\r
+\r
+       return nil\r
+}\r
+\r
+// Query CNFStatus information\r
+func (r *CNFLocalServiceReconciler) check() {\r
+       ls_list := &batchv1alpha1.CNFLocalServiceList{}\r
+       err := r.List(context.Background(), ls_list)\r
+       if err != nil {\r
+               r.Log.Error(err, "Failed to list CNFLocalService CRs")\r
+       } else {\r
+               if len(ls_list.Items) > 0 {\r
+                       for _, inst := range ls_list.Items {\r
+                               r.Log.Info("Checking CNFLocalService: " + inst.Name)\r
+                               r.processInstance(&inst)\r
+                       }\r
+               }\r
+       }\r
+}\r
+\r
+// Query CNFStatus information\r
+func (r *CNFLocalServiceReconciler) SafeCheck() {\r
+       doCheck := true\r
+       r.mux.Lock()\r
+       if !inLSQueryStatus {\r
+               inLSQueryStatus = true\r
+       } else {\r
+               doCheck = false\r
+       }\r
+       r.mux.Unlock()\r
+\r
+       if doCheck {\r
+               r.check()\r
+\r
+               r.mux.Lock()\r
+               inLSQueryStatus = false\r
+               r.mux.Unlock()\r
+       }\r
+}\r
+\r
+func (r *CNFLocalServiceReconciler) SetupWithManager(mgr ctrl.Manager) error {\r
+       // Start the loop to check ip address change of local/remote services\r
+       go func() {\r
+                       interval := time.After(r.CheckInterval)\r
+                       for {\r
+                               select {\r
+                               case <-interval:\r
+                                       r.SafeCheck()\r
+                                       interval = time.After(r.CheckInterval)\r
+                               case <-context.Background().Done():\r
+                                       return\r
+                               }\r
+                       }\r
+               }()\r
+\r
+       ps := builder.WithPredicates(predicate.GenerationChangedPredicate{})\r
+       return ctrl.NewControllerManagedBy(mgr).\r
+               For(&batchv1alpha1.CNFLocalService{}, ps).\r
+               Complete(r)\r
+}\r
diff --git a/platform/crd-ctrlr/src/controllers/cnfnat_controller.go b/platform/crd-ctrlr/src/controllers/cnfnat_controller.go
new file mode 100644 (file)
index 0000000..bbb7cea
--- /dev/null
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: Apache-2.0\r
+// Copyright (c) 2021 Intel Corporation\r
+package controllers\r
+\r
+import (\r
+       "context"\r
+       "reflect"\r
+\r
+       "github.com/go-logr/logr"\r
+       appsv1 "k8s.io/api/apps/v1"\r
+       "k8s.io/apimachinery/pkg/runtime"\r
+       ctrl "sigs.k8s.io/controller-runtime"\r
+       "sigs.k8s.io/controller-runtime/pkg/builder"\r
+       "sigs.k8s.io/controller-runtime/pkg/client"\r
+       "sigs.k8s.io/controller-runtime/pkg/handler"\r
+       "sigs.k8s.io/controller-runtime/pkg/predicate"\r
+       "sigs.k8s.io/controller-runtime/pkg/source"\r
+\r
+       batchv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"\r
+       "sdewan.akraino.org/sdewan/openwrt"\r
+)\r
+\r
+var cnfnatHandler = new(CNFNatHandler)\r
+\r
+type CNFNatHandler struct {\r
+}\r
+\r
+func (m *CNFNatHandler) GetType() string {\r
+       return "CNFNAT"\r
+}\r
+\r
+func (m *CNFNatHandler) GetName(instance runtime.Object) string {\r
+       nat := instance.(*batchv1alpha1.CNFNAT)\r
+       return nat.Name\r
+}\r
+\r
+func (m *CNFNatHandler) GetFinalizer() string {\r
+       return "cnfnat.finalizers.sdewan.akraino.org"\r
+}\r
+\r
+func (m *CNFNatHandler) GetInstance(r client.Client, ctx context.Context, req ctrl.Request) (runtime.Object, error) {\r
+       instance := &batchv1alpha1.CNFNAT{}\r
+       err := r.Get(ctx, req.NamespacedName, instance)\r
+       return instance, err\r
+}\r
+\r
+//pupulate "nat" to target field as default value\r
+func (m *CNFNatHandler) Convert(instance runtime.Object, deployment appsv1.Deployment) (openwrt.IOpenWrtObject, error) {\r
+       cnfnat := instance.(*batchv1alpha1.CNFNAT)\r
+       cnfnat.Spec.Name = cnfnat.ObjectMeta.Name\r
+       cnfnatObject := openwrt.SdewanNat(cnfnat.Spec)\r
+       return &cnfnatObject, nil\r
+}\r
+\r
+func (m *CNFNatHandler) IsEqual(instance1 openwrt.IOpenWrtObject, instance2 openwrt.IOpenWrtObject) bool {\r
+       nat1 := instance1.(*openwrt.SdewanNat)\r
+       nat2 := instance2.(*openwrt.SdewanNat)\r
+       return reflect.DeepEqual(*nat1, *nat2)\r
+}\r
+\r
+func (m *CNFNatHandler) GetObject(clientInfo *openwrt.OpenwrtClientInfo, name string) (openwrt.IOpenWrtObject, error) {\r
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)\r
+       natClient := openwrt.NatClient{OpenwrtClient: openwrtClient}\r
+       ret, err := natClient.GetNat(name)\r
+       return ret, err\r
+}\r
+\r
+func (m *CNFNatHandler) CreateObject(clientInfo *openwrt.OpenwrtClientInfo, instance openwrt.IOpenWrtObject) (openwrt.IOpenWrtObject, error) {\r
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)\r
+       natClient := openwrt.NatClient{OpenwrtClient: openwrtClient}\r
+       nat := instance.(*openwrt.SdewanNat)\r
+       return natClient.CreateNat(*nat)\r
+}\r
+\r
+func (m *CNFNatHandler) UpdateObject(clientInfo *openwrt.OpenwrtClientInfo, instance openwrt.IOpenWrtObject) (openwrt.IOpenWrtObject, error) {\r
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)\r
+       natClient := openwrt.NatClient{OpenwrtClient: openwrtClient}\r
+       nat := instance.(*openwrt.SdewanNat)\r
+       return natClient.UpdateNat(*nat)\r
+}\r
+\r
+func (m *CNFNatHandler) DeleteObject(clientInfo *openwrt.OpenwrtClientInfo, name string) error {\r
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)\r
+       natClient := openwrt.NatClient{OpenwrtClient: openwrtClient}\r
+       return natClient.DeleteNat(name)\r
+}\r
+\r
+func (m *CNFNatHandler) Restart(clientInfo *openwrt.OpenwrtClientInfo) (bool, error) {\r
+       return true, nil\r
+}\r
+\r
+// CNFNATReconciler reconciles a CNFNAT object\r
+type CNFNATReconciler struct {\r
+       client.Client\r
+       Log    logr.Logger\r
+       Scheme *runtime.Scheme\r
+}\r
+\r
+// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=cnfnats,verbs=get;list;watch;create;update;patch;delete\r
+// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=cnfnats/status,verbs=get;update;patch\r
+\r
+func (r *CNFNATReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {\r
+       return ProcessReconcile(r, r.Log, req, cnfnatHandler)\r
+}\r
+\r
+func (r *CNFNATReconciler) SetupWithManager(mgr ctrl.Manager) error {\r
+       ps := builder.WithPredicates(predicate.GenerationChangedPredicate{})\r
+       return ctrl.NewControllerManagedBy(mgr).\r
+               For(&batchv1alpha1.CNFNAT{}, ps).\r
+               Watches(\r
+                       &source.Kind{Type: &appsv1.Deployment{}},\r
+                       &handler.EnqueueRequestsFromMapFunc{\r
+                               ToRequests: handler.ToRequestsFunc(GetToRequestsFunc(r, &batchv1alpha1.CNFNATList{})),\r
+                       },\r
+                       Filter).\r
+               Complete(r)\r
+}\r
index 40b3d79..c10e933 100644 (file)
@@ -43,7 +43,7 @@ func main() {
        flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
                "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
        flag.IntVar(&checkInterval, "check-interval", 30,
-               "The check interval for query of CNF Status (seconds)")
+               "The check interval of CRD Controller (seconds)")
        flag.Parse()
 
        ctrl.SetLogger(zap.New(func(o *zap.Options) {
@@ -126,6 +126,14 @@ func main() {
                setupLog.Error(err, "unable to create controller", "controller", "Mwan3Rule")
                os.Exit(1)
        }
+       if err = (&controllers.CNFNATReconciler{
+               Client: mgr.GetClient(),
+               Log:    ctrl.Log.WithName("controllers").WithName("CNFNAT"),
+               Scheme: mgr.GetScheme(),
+       }).SetupWithManager(mgr); err != nil {
+               setupLog.Error(err, "unable to create controller", "controller", "CNFNAT")
+               os.Exit(1)
+       }
        if err = (&controllers.FirewallZoneReconciler{
                Client: mgr.GetClient(),
                Log:    ctrl.Log.WithName("controllers").WithName("FirewallZone"),
@@ -232,6 +240,15 @@ func main() {
                setupLog.Error(err, "unable to create controller", "controller", "CNFRouteRule")
                os.Exit(1)
        }
+       if err = (&controllers.CNFLocalServiceReconciler{
+               Client: mgr.GetClient(),
+               Log:    ctrl.Log.WithName("controllers").WithName("CNFLocalService"),
+               CheckInterval: time.Duration(checkInterval) * time.Second,
+               Scheme: mgr.GetScheme(),
+       }).SetupWithManager(mgr); err != nil {
+               setupLog.Error(err, "unable to create controller", "controller", "CNFLocalService")
+               os.Exit(1)
+       }
        // +kubebuilder:scaffold:builder
 
        setupLog.Info("start CNFStatusController to query CNF status periodicly")
diff --git a/platform/crd-ctrlr/src/openwrt/nat.go b/platform/crd-ctrlr/src/openwrt/nat.go
new file mode 100644 (file)
index 0000000..7ca7db6
--- /dev/null
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: Apache-2.0\r
+// Copyright (c) 2021 Intel Corporation\r
+\r
+package openwrt\r
+\r
+import (\r
+       "encoding/json"\r
+)\r
+\r
+const (\r
+       natBaseURL = "sdewan/nat/v1/"\r
+)\r
+\r
+type NatClient struct {\r
+       OpenwrtClient *openwrtClient\r
+}\r
+\r
+// Nat\r
+type SdewanNat struct {\r
+       Name     string `json:"name"`\r
+       Src      string `json:"src"`\r
+       SrcIp    string `json:"src_ip"`\r
+       SrcDIp   string `json:"src_dip"`\r
+       SrcPort  string `json:"src_port"`\r
+       SrcDPort string `json:"src_dport"`\r
+       Proto    string `json:"proto"`\r
+       Dest     string `json:"dest"`\r
+       DestIp   string `json:"dest_ip"`\r
+       DestPort string `json:"dest_port"`\r
+       Target   string `json:"target"`\r
+       Index    string `json:"index"`\r
+}\r
+\r
+func (o *SdewanNat) GetName() string {\r
+       return o.Name\r
+}\r
+\r
+type SdewanNats struct {\r
+       Nats []SdewanNat `json:"nats"`\r
+}\r
+\r
+// Nat APIs\r
+// get nats\r
+func (f *NatClient) GetNats() (*SdewanNats, error) {\r
+       var response string\r
+       var err error\r
+       response, err = f.OpenwrtClient.Get(natBaseURL + "nats")\r
+       if err != nil {\r
+               return nil, err\r
+       }\r
+\r
+       var sdewanNats SdewanNats\r
+       err = json.Unmarshal([]byte(response), &sdewanNats)\r
+       if err != nil {\r
+               return nil, err\r
+       }\r
+\r
+       return &sdewanNats, nil\r
+}\r
+\r
+// get nat\r
+func (m *NatClient) GetNat(nat string) (*SdewanNat, error) {\r
+       var response string\r
+       var err error\r
+       response, err = m.OpenwrtClient.Get(natBaseURL + "nats/" + nat)\r
+       if err != nil {\r
+               return nil, err\r
+       }\r
+\r
+       var sdewanNat SdewanNat\r
+       err = json.Unmarshal([]byte(response), &sdewanNat)\r
+       if err != nil {\r
+               return nil, err\r
+       }\r
+\r
+       return &sdewanNat, nil\r
+}\r
+\r
+// create nat\r
+func (m *NatClient) CreateNat(nat SdewanNat) (*SdewanNat, error) {\r
+       var response string\r
+       var err error\r
+       nat_obj, _ := json.Marshal(nat)\r
+       response, err = m.OpenwrtClient.Post(natBaseURL+"nats", string(nat_obj))\r
+       if err != nil {\r
+               return nil, err\r
+       }\r
+\r
+       var sdewanNat SdewanNat\r
+       err = json.Unmarshal([]byte(response), &sdewanNat)\r
+       if err != nil {\r
+               return nil, err\r
+       }\r
+\r
+       return &sdewanNat, nil\r
+}\r
+\r
+// delete nat\r
+func (m *NatClient) DeleteNat(nat_name string) error {\r
+       _, err := m.OpenwrtClient.Delete(natBaseURL + "nats/" + nat_name)\r
+       if err != nil {\r
+               return err\r
+       }\r
+\r
+       return nil\r
+}\r
+\r
+// update nat\r
+func (m *NatClient) UpdateNat(nat SdewanNat) (*SdewanNat, error) {\r
+       var response string\r
+       var err error\r
+       nat_obj, _ := json.Marshal(nat)\r
+       nat_name := nat.Name\r
+       response, err = m.OpenwrtClient.Put(natBaseURL+"nats/"+nat_name, string(nat_obj))\r
+       if err != nil {\r
+               return nil, err\r
+       }\r
+\r
+       var sdewanNat SdewanNat\r
+       err = json.Unmarshal([]byte(response), &sdewanNat)\r
+       if err != nil {\r
+               return nil, err\r
+       }\r
+\r
+       return &sdewanNat, nil\r
+}\r