From 22e38f28431b038db74145e2cb437fb44d25a187 Mon Sep 17 00:00:00 2001 From: Huifeng Le Date: Mon, 29 Nov 2021 22:31:30 +0800 Subject: [PATCH] Add support for NAT and LocalService Update implementation for route and rule Signed-off-by: Huifeng Le Change-Id: I314ab5fbdfec3c1b7bda5e61d373ddfd1ea57bad Signed-off-by: Huifeng Le --- platform/cnf-openwrt/src/rest_v1/ifutil.lua | 150 ++++++++ platform/cnf-openwrt/src/rest_v1/index.lua | 1 + platform/cnf-openwrt/src/rest_v1/ipsec_rest.lua | 4 +- platform/cnf-openwrt/src/rest_v1/nat_rest.lua | 237 +++++++++++++ platform/cnf-openwrt/src/rest_v1/route_rest.lua | 286 +++++---------- platform/cnf-openwrt/src/rest_v1/rule_rest.lua | 384 ++++++--------------- platform/cnf-openwrt/src/rest_v1/utils.lua | 42 ++- .../src/api/v1alpha1/bucket_permission_webhook.go | 10 +- .../src/api/v1alpha1/cnflocalservice_types.go | 78 +++++ .../crd-ctrlr/src/api/v1alpha1/cnfnat_types.go | 53 +++ .../src/api/v1alpha1/label_validate_webhook.go | 10 +- .../crd-ctrlr/src/config/crd/kustomization.yaml | 6 + .../config/crd/patches/cainjection_in_cnfnats.yaml | 10 + .../src/config/crd/patches/webhook_in_cnfnats.yaml | 19 + platform/crd-ctrlr/src/config/manager/manager.yaml | 4 +- .../src/config/samples/batch_v1alpha1_cnfnat.yaml | 18 + .../crd-ctrlr/src/controllers/base_controller.go | 7 +- .../src/controllers/cnflocalservice_controller.go | 315 +++++++++++++++++ .../crd-ctrlr/src/controllers/cnfnat_controller.go | 117 +++++++ platform/crd-ctrlr/src/main.go | 19 +- platform/crd-ctrlr/src/openwrt/nat.go | 126 +++++++ 21 files changed, 1397 insertions(+), 499 deletions(-) create mode 100644 platform/cnf-openwrt/src/rest_v1/ifutil.lua create mode 100644 platform/cnf-openwrt/src/rest_v1/nat_rest.lua create mode 100644 platform/crd-ctrlr/src/api/v1alpha1/cnflocalservice_types.go create mode 100644 platform/crd-ctrlr/src/api/v1alpha1/cnfnat_types.go create mode 100644 platform/crd-ctrlr/src/config/crd/patches/cainjection_in_cnfnats.yaml create mode 100644 platform/crd-ctrlr/src/config/crd/patches/webhook_in_cnfnats.yaml create mode 100644 platform/crd-ctrlr/src/config/samples/batch_v1alpha1_cnfnat.yaml create mode 100644 platform/crd-ctrlr/src/controllers/cnflocalservice_controller.go create mode 100644 platform/crd-ctrlr/src/controllers/cnfnat_controller.go create mode 100644 platform/crd-ctrlr/src/openwrt/nat.go diff --git a/platform/cnf-openwrt/src/rest_v1/ifutil.lua b/platform/cnf-openwrt/src/rest_v1/ifutil.lua new file mode 100644 index 0000000..24a376b --- /dev/null +++ b/platform/cnf-openwrt/src/rest_v1/ifutil.lua @@ -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 diff --git a/platform/cnf-openwrt/src/rest_v1/index.lua b/platform/cnf-openwrt/src/rest_v1/index.lua index 4d700d3..6c1bbf3 100644 --- a/platform/cnf-openwrt/src/rest_v1/index.lua +++ b/platform/cnf-openwrt/src/rest_v1/index.lua @@ -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 diff --git a/platform/cnf-openwrt/src/rest_v1/ipsec_rest.lua b/platform/cnf-openwrt/src/rest_v1/ipsec_rest.lua index 75ce2cc..6e21d10 100644 --- a/platform/cnf-openwrt/src/rest_v1/ipsec_rest.lua +++ b/platform/cnf-openwrt/src/rest_v1/ipsec_rest.lua @@ -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 index 0000000..adde1c2 --- /dev/null +++ b/platform/cnf-openwrt/src/rest_v1/nat_rest.lua @@ -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 diff --git a/platform/cnf-openwrt/src/rest_v1/route_rest.lua b/platform/cnf-openwrt/src/rest_v1/route_rest.lua index df79eae..5983a03 100644 --- a/platform/cnf-openwrt/src/rest_v1/route_rest.lua +++ b/platform/cnf-openwrt/src/rest_v1/route_rest.lua @@ -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 diff --git a/platform/cnf-openwrt/src/rest_v1/rule_rest.lua b/platform/cnf-openwrt/src/rest_v1/rule_rest.lua index 7458935..1c1dcaa 100644 --- a/platform/cnf-openwrt/src/rest_v1/rule_rest.lua +++ b/platform/cnf-openwrt/src/rest_v1/rule_rest.lua @@ -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 diff --git a/platform/cnf-openwrt/src/rest_v1/utils.lua b/platform/cnf-openwrt/src/rest_v1/utils.lua index 99cb307..4376296 100644 --- a/platform/cnf-openwrt/src/rest_v1/utils.lua +++ b/platform/cnf-openwrt/src/rest_v1/utils.lua @@ -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 diff --git a/platform/crd-ctrlr/src/api/v1alpha1/bucket_permission_webhook.go b/platform/crd-ctrlr/src/api/v1alpha1/bucket_permission_webhook.go index 1325b2e..87c9d3b 100644 --- a/platform/crd-ctrlr/src/api/v1alpha1/bucket_permission_webhook.go +++ b/platform/crd-ctrlr/src/api/v1alpha1/bucket_permission_webhook.go @@ -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 index 0000000..23a2545 --- /dev/null +++ b/platform/crd-ctrlr/src/api/v1alpha1/cnflocalservice_types.go @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2021 Intel Corporation +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// CNFLocalServiceStatus defines the observed state of CNFLocalServiceStatus +type CNFLocalServiceStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + // +optional + LocalIP string `json:"localip,omitempty"` + // +optional + LocalPort string `json:"localport,omitempty"` + // +optional + RemoteIPs []string `json:"remoteips,omitempty"` + // +optional + RemotePort string `json:"remoteport,omitempty"` + // +optional + Message string `json:"message,omitempty"` +} + +func (c *CNFLocalServiceStatus) IsEqual(s *CNFLocalServiceStatus) bool { + if c.LocalIP != s.LocalIP || + c.LocalPort != s.LocalPort || + c.RemotePort != s.RemotePort { + return false + } + if len(c.RemoteIPs) != len(s.RemoteIPs) { + return false + } + + for i:=0; i 0 { + for _, inst := range ls_list.Items { + r.Log.Info("Checking CNFLocalService: " + inst.Name) + r.processInstance(&inst) + } + } + } +} + +// Query CNFStatus information +func (r *CNFLocalServiceReconciler) SafeCheck() { + doCheck := true + r.mux.Lock() + if !inLSQueryStatus { + inLSQueryStatus = true + } else { + doCheck = false + } + r.mux.Unlock() + + if doCheck { + r.check() + + r.mux.Lock() + inLSQueryStatus = false + r.mux.Unlock() + } +} + +func (r *CNFLocalServiceReconciler) SetupWithManager(mgr ctrl.Manager) error { + // Start the loop to check ip address change of local/remote services + go func() { + interval := time.After(r.CheckInterval) + for { + select { + case <-interval: + r.SafeCheck() + interval = time.After(r.CheckInterval) + case <-context.Background().Done(): + return + } + } + }() + + ps := builder.WithPredicates(predicate.GenerationChangedPredicate{}) + return ctrl.NewControllerManagedBy(mgr). + For(&batchv1alpha1.CNFLocalService{}, ps). + Complete(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 index 0000000..bbb7cea --- /dev/null +++ b/platform/crd-ctrlr/src/controllers/cnfnat_controller.go @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2021 Intel Corporation +package controllers + +import ( + "context" + "reflect" + + "github.com/go-logr/logr" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" + + batchv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1" + "sdewan.akraino.org/sdewan/openwrt" +) + +var cnfnatHandler = new(CNFNatHandler) + +type CNFNatHandler struct { +} + +func (m *CNFNatHandler) GetType() string { + return "CNFNAT" +} + +func (m *CNFNatHandler) GetName(instance runtime.Object) string { + nat := instance.(*batchv1alpha1.CNFNAT) + return nat.Name +} + +func (m *CNFNatHandler) GetFinalizer() string { + return "cnfnat.finalizers.sdewan.akraino.org" +} + +func (m *CNFNatHandler) GetInstance(r client.Client, ctx context.Context, req ctrl.Request) (runtime.Object, error) { + instance := &batchv1alpha1.CNFNAT{} + err := r.Get(ctx, req.NamespacedName, instance) + return instance, err +} + +//pupulate "nat" to target field as default value +func (m *CNFNatHandler) Convert(instance runtime.Object, deployment appsv1.Deployment) (openwrt.IOpenWrtObject, error) { + cnfnat := instance.(*batchv1alpha1.CNFNAT) + cnfnat.Spec.Name = cnfnat.ObjectMeta.Name + cnfnatObject := openwrt.SdewanNat(cnfnat.Spec) + return &cnfnatObject, nil +} + +func (m *CNFNatHandler) IsEqual(instance1 openwrt.IOpenWrtObject, instance2 openwrt.IOpenWrtObject) bool { + nat1 := instance1.(*openwrt.SdewanNat) + nat2 := instance2.(*openwrt.SdewanNat) + return reflect.DeepEqual(*nat1, *nat2) +} + +func (m *CNFNatHandler) GetObject(clientInfo *openwrt.OpenwrtClientInfo, name string) (openwrt.IOpenWrtObject, error) { + openwrtClient := openwrt.GetOpenwrtClient(*clientInfo) + natClient := openwrt.NatClient{OpenwrtClient: openwrtClient} + ret, err := natClient.GetNat(name) + return ret, err +} + +func (m *CNFNatHandler) CreateObject(clientInfo *openwrt.OpenwrtClientInfo, instance openwrt.IOpenWrtObject) (openwrt.IOpenWrtObject, error) { + openwrtClient := openwrt.GetOpenwrtClient(*clientInfo) + natClient := openwrt.NatClient{OpenwrtClient: openwrtClient} + nat := instance.(*openwrt.SdewanNat) + return natClient.CreateNat(*nat) +} + +func (m *CNFNatHandler) UpdateObject(clientInfo *openwrt.OpenwrtClientInfo, instance openwrt.IOpenWrtObject) (openwrt.IOpenWrtObject, error) { + openwrtClient := openwrt.GetOpenwrtClient(*clientInfo) + natClient := openwrt.NatClient{OpenwrtClient: openwrtClient} + nat := instance.(*openwrt.SdewanNat) + return natClient.UpdateNat(*nat) +} + +func (m *CNFNatHandler) DeleteObject(clientInfo *openwrt.OpenwrtClientInfo, name string) error { + openwrtClient := openwrt.GetOpenwrtClient(*clientInfo) + natClient := openwrt.NatClient{OpenwrtClient: openwrtClient} + return natClient.DeleteNat(name) +} + +func (m *CNFNatHandler) Restart(clientInfo *openwrt.OpenwrtClientInfo) (bool, error) { + return true, nil +} + +// CNFNATReconciler reconciles a CNFNAT object +type CNFNATReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=cnfnats,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=cnfnats/status,verbs=get;update;patch + +func (r *CNFNATReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + return ProcessReconcile(r, r.Log, req, cnfnatHandler) +} + +func (r *CNFNATReconciler) SetupWithManager(mgr ctrl.Manager) error { + ps := builder.WithPredicates(predicate.GenerationChangedPredicate{}) + return ctrl.NewControllerManagedBy(mgr). + For(&batchv1alpha1.CNFNAT{}, ps). + Watches( + &source.Kind{Type: &appsv1.Deployment{}}, + &handler.EnqueueRequestsFromMapFunc{ + ToRequests: handler.ToRequestsFunc(GetToRequestsFunc(r, &batchv1alpha1.CNFNATList{})), + }, + Filter). + Complete(r) +} diff --git a/platform/crd-ctrlr/src/main.go b/platform/crd-ctrlr/src/main.go index 40b3d79..c10e933 100644 --- a/platform/crd-ctrlr/src/main.go +++ b/platform/crd-ctrlr/src/main.go @@ -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 index 0000000..7ca7db6 --- /dev/null +++ b/platform/crd-ctrlr/src/openwrt/nat.go @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2021 Intel Corporation + +package openwrt + +import ( + "encoding/json" +) + +const ( + natBaseURL = "sdewan/nat/v1/" +) + +type NatClient struct { + OpenwrtClient *openwrtClient +} + +// Nat +type SdewanNat struct { + Name string `json:"name"` + Src string `json:"src"` + SrcIp string `json:"src_ip"` + SrcDIp string `json:"src_dip"` + SrcPort string `json:"src_port"` + SrcDPort string `json:"src_dport"` + Proto string `json:"proto"` + Dest string `json:"dest"` + DestIp string `json:"dest_ip"` + DestPort string `json:"dest_port"` + Target string `json:"target"` + Index string `json:"index"` +} + +func (o *SdewanNat) GetName() string { + return o.Name +} + +type SdewanNats struct { + Nats []SdewanNat `json:"nats"` +} + +// Nat APIs +// get nats +func (f *NatClient) GetNats() (*SdewanNats, error) { + var response string + var err error + response, err = f.OpenwrtClient.Get(natBaseURL + "nats") + if err != nil { + return nil, err + } + + var sdewanNats SdewanNats + err = json.Unmarshal([]byte(response), &sdewanNats) + if err != nil { + return nil, err + } + + return &sdewanNats, nil +} + +// get nat +func (m *NatClient) GetNat(nat string) (*SdewanNat, error) { + var response string + var err error + response, err = m.OpenwrtClient.Get(natBaseURL + "nats/" + nat) + if err != nil { + return nil, err + } + + var sdewanNat SdewanNat + err = json.Unmarshal([]byte(response), &sdewanNat) + if err != nil { + return nil, err + } + + return &sdewanNat, nil +} + +// create nat +func (m *NatClient) CreateNat(nat SdewanNat) (*SdewanNat, error) { + var response string + var err error + nat_obj, _ := json.Marshal(nat) + response, err = m.OpenwrtClient.Post(natBaseURL+"nats", string(nat_obj)) + if err != nil { + return nil, err + } + + var sdewanNat SdewanNat + err = json.Unmarshal([]byte(response), &sdewanNat) + if err != nil { + return nil, err + } + + return &sdewanNat, nil +} + +// delete nat +func (m *NatClient) DeleteNat(nat_name string) error { + _, err := m.OpenwrtClient.Delete(natBaseURL + "nats/" + nat_name) + if err != nil { + return err + } + + return nil +} + +// update nat +func (m *NatClient) UpdateNat(nat SdewanNat) (*SdewanNat, error) { + var response string + var err error + nat_obj, _ := json.Marshal(nat) + nat_name := nat.Name + response, err = m.OpenwrtClient.Put(natBaseURL+"nats/"+nat_name, string(nat_obj)) + if err != nil { + return nil, err + } + + var sdewanNat SdewanNat + err = json.Unmarshal([]byte(response), &sdewanNat) + if err != nil { + return nil, err + } + + return &sdewanNat, nil +} -- 2.16.6