SDEWAN CNF Rest API support 62/2262/3
authorHuifeng Le <huifeng.le@intel.com>
Thu, 27 Feb 2020 13:44:44 +0000 (21:44 +0800)
committerHuifeng Le <huifeng.le@intel.com>
Mon, 2 Mar 2020 08:36:18 +0000 (16:36 +0800)
Add SDEWAN Rest API implementation for Service, Mwan3, Firewall and IPSec,
API design can be found at:
IPSec: https://wiki.akraino.org/display/AK/IPSec+Design#IPSecDesign
Service/Mwan3/Firewall: https://wiki.akraino.org/display/AK/SDEWAN+CNF

Signed-off-by: Huifeng Le <huifeng.le@intel.com>
Change-Id: I649305d27ab6f0de9f57ff4411a9f4b1267cf504

18 files changed:
cnf/build/Dockerfile_1806_mwan3.tpl [new file with mode: 0644]
cnf/build/Dockerfile_1806_mwan3_noproxy.tpl [new file with mode: 0644]
cnf/build/build_image.sh [new file with mode: 0644]
cnf/build/rest_v1/firewall_rest.lua [new file with mode: 0644]
cnf/build/rest_v1/index.lua [new file with mode: 0644]
cnf/build/rest_v1/ipsec_rest.lua [new file with mode: 0644]
cnf/build/rest_v1/mwan3_rest.lua [new file with mode: 0644]
cnf/build/rest_v1/service.lua [new file with mode: 0644]
cnf/build/rest_v1/utils.lua [new file with mode: 0644]
cnf/build/set_proxy [new file with mode: 0644]
cnf/build/system [new file with mode: 0644]
cnf/test/openwrtclient_fw_test.go [new file with mode: 0644]
cnf/test/openwrtclient_ipsec_test.go [new file with mode: 0644]
cnf/test/openwrtclient_test.go [new file with mode: 0644]
cnf/test/test.go [new file with mode: 0644]
openwrt/firewall.go [new file with mode: 0644]
openwrt/ipsec.go [new file with mode: 0644]
readme.md

diff --git a/cnf/build/Dockerfile_1806_mwan3.tpl b/cnf/build/Dockerfile_1806_mwan3.tpl
new file mode 100644 (file)
index 0000000..52bf6ac
--- /dev/null
@@ -0,0 +1,26 @@
+FROM openwrt-1806-4-base
+
+#EXPOSE 80
+ENV http_proxy={docker_proxy}
+ENV https_proxy={docker_proxy}
+ENV no_proxy=localhost,120.0.0.1,192.168.*
+
+RUN mkdir /var/lock && \
+    opkg update && \
+    opkg install uhttpd-mod-lua && \
+    uci set uhttpd.main.interpreter='.lua=/usr/bin/lua' && \
+    uci commit uhttpd && \
+    opkg install mwan3 && \
+    opkg install luci-app-mwan3; exit 0
+
+COPY system /etc/config/system
+COPY rest_v1 /usr/lib/lua/luci/controller/rest_v1
+
+ENV http_proxy=
+ENV https_proxy=
+ENV no_proxy=
+
+USER root
+
+# using exec format so that /sbin/init is proc 1 (see procd docs)
+CMD ["/sbin/init"]
diff --git a/cnf/build/Dockerfile_1806_mwan3_noproxy.tpl b/cnf/build/Dockerfile_1806_mwan3_noproxy.tpl
new file mode 100644 (file)
index 0000000..0b0590e
--- /dev/null
@@ -0,0 +1,19 @@
+FROM openwrt-1806-4-base
+
+#EXPOSE 80
+
+RUN mkdir /var/lock && \
+    opkg update && \
+    opkg install uhttpd-mod-lua && \
+    uci set uhttpd.main.interpreter='.lua=/usr/bin/lua' && \
+    uci commit uhttpd && \
+    opkg install mwan3 && \
+    opkg install luci-app-mwan3; exit 0
+
+COPY system /etc/config/system
+COPY rest_v1 /usr/lib/lua/luci/controller/rest_v1
+
+USER root
+
+# using exec format so that /sbin/init is proc 1 (see procd docs)
+CMD ["/sbin/init"]
diff --git a/cnf/build/build_image.sh b/cnf/build/build_image.sh
new file mode 100644 (file)
index 0000000..7ff6e20
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+# usage: build_images.sh
+
+set -ex
+base_image_tag=openwrt-1806-4-base
+docker_file=Dockerfile_1806_mwan3
+image_tag=openwrt-1806-mwan3
+package=openwrt-18.06.4-x86-64-generic-rootfs
+
+# build openwrt base docker images
+base_image=`docker images | grep $base_image_tag | awk '{print $1}'`
+if [ -z "$base_image" ]; then
+    # download driver source package
+    if [ ! -e /tmp/$package.tar.gz ]; then
+        wget -P /tmp https://downloads.openwrt.org/releases/18.06.4/targets/x86/64/$package.tar.gz
+    fi
+    cp /tmp/$package.tar.gz .
+
+    docker import $package.tar.gz $base_image_tag
+fi
+
+# generate Dockerfile
+test -f ./set_proxy && . set_proxy
+docker_proxy=${docker_proxy-""}
+if [ -z "$docker_proxy" ]; then
+    cp ${docker_file}_noproxy.tpl $docker_file
+else
+    cp $docker_file.tpl $docker_file
+    sed -i "s,{docker_proxy},$docker_proxy,g" $docker_file
+fi
+
+# build docker images for openwrt with wman3
+docker build --network=host -f $docker_file -t $image_tag .
+
+# clear
+docker image rm $base_image_tag
+rm -rf $docker_file
+rm -rf $package.tar.gz
diff --git a/cnf/build/rest_v1/firewall_rest.lua b/cnf/build/rest_v1/firewall_rest.lua
new file mode 100644 (file)
index 0000000..d1531c8
--- /dev/null
@@ -0,0 +1,285 @@
+-- Licensed to the public under the GNU General Public License v2.
+
+module("luci.controller.rest_v1.firewall_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"
+
+uci_conf = "firewall"
+
+zone_validator = {
+    create_section_name=false,
+    {name="name"},
+    {name="network", item_validator=function(value) return is_network_interface_available(value) end, message="invalid network"},
+    {name="masq", validator=function(value) return utils.in_array(value, {"0", "1"}) end, message="invalid masq"},
+    {name="masq_src", item_validator=function(value) return is_valid_masq_subset(value) end, message="invalid masq_src"},
+    {name="masq_dest", item_validator=function(value) return is_valid_masq_subset(value) end, message="invalid masq_dest"},
+    {name="masq_allow_invalid", validator=function(value) return utils.in_array(value, {"0", "1"}) end, message="invalid masq_allow_invalid"},
+    {name="mtu_fix", validator=function(value) return utils.in_array(value, {"0", "1"}) end, message="invalid mtu_fix"},
+    {name="input", validator=function(value) return utils.in_array(value, {"ACCEPT", "REJECT", "DROP"}) end, message="invalid input"},
+    {name="forward", validator=function(value) return utils.in_array(value, {"ACCEPT", "REJECT", "DROP"}) end, message="invalid forward"},
+    {name="output", validator=function(value) return utils.in_array(value, {"ACCEPT", "REJECT", "DROP"}) end, message="invalid output"},
+    {name="family", validator=function(value) return utils.in_array(value, {"ipv4", "ipv6", "any"}) end, message="invalid family"},
+    {name="subnet", item_validator=function(value) return utils.is_valid_ip(value) end, message="invalid subnet"},
+    {name="extra_src"},
+    {name="etra_dest"},
+}
+
+redirect_validator = {
+    create_section_name=false,
+    object_validator=function(value) return check_redirect(value) end,
+    {name="name"},
+    {name="src", validator=function(value) return is_zone_available(value) end, message="invalid src"},
+    {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_mac", validator=function(value) return utils.is_valid_mac(value) end, message="invalid src_mac"},
+    {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 is_zone_available(value) end, message="invalid dest"},
+    {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="mark"},
+    {name="target", validator=function(value) return utils.in_array(value, {"DNAT", "SNAT"}) end, message="invalid target"},
+    {name="family", validator=function(value) return utils.in_array(value, {"ipv4", "ipv6", "any"}) end, message="invalid family"},
+}
+
+rule_validator = {
+    create_section_name=false,
+    object_validator=function(value) return check_rule(value) end,
+    {name="name"},
+    {name="src", validator=function(value) return is_zone_available(value) end, message="invalid src"},
+    {name="src_ip", validator=function(value) return utils.is_valid_ip(value) end, message="invalid src_ip"},
+    {name="src_mac", validator=function(value) return utils.is_valid_mac(value) end, message="invalid src_mac"},
+    {name="src_port", 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="icmp_type", is_list=true, item_validator=function(value) return check_icmp_type(value) end, message="invalid icmp_type"},
+    {name="dest", validator=function(value) return is_zone_available(value) end, message="invalid dest"},
+    {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="mark"},
+    {name="target", validator=function(value) return utils.in_array(value, {"ACCEPT", "REJECT", "DROP", "MARK", "NOTRACK"}) end, message="invalid target"},
+    {name="set_mark"},
+    {name="set_xmark"},
+    {name="family", validator=function(value) return utils.in_array(value, {"ipv4", "ipv6", "any"}) end, message="invalid family"},
+    {name="extra"},
+}
+
+forwarding_validator = {
+    create_section_name=false,
+    {name="name"},
+    {name="src", required=true, validator=function(value) return is_zone_available(value) end, message="invalid src"},
+    {name="dest", required=true, validator=function(value) return is_zone_available(value) end, message="invalid dest"},
+    {name="family", validator=function(value) return utils.in_array(value, {"ipv4", "ipv6", "any"}) end, message="invalid family"},
+}
+
+firewall_processor = {
+    zone={update="update_zone", delete="delete_zone", validator=zone_validator},
+    redirect={validator=redirect_validator},
+    rule={validator=rule_validator},
+    forwarding={validator=forwarding_validator},
+    configuration=uci_conf
+}
+
+zone_checker = {
+    {name="rule", checker={"src", "dest"}},
+    {name="forwarding", checker={"src", "dest"}},
+    {name="redirect", checker={"src", "dest"}},
+}
+
+function index()
+    ver = "v1"
+    configuration = "firewall"
+    entry({"sdewan", configuration, ver, "zones"}, call("get_zones"))
+    entry({"sdewan", configuration, ver, "redirects"}, call("get_redirects"))
+    entry({"sdewan", configuration, ver, "rules"}, call("get_rules"))
+    entry({"sdewan", configuration, ver, "forwardings"}, call("get_forwardings"))
+    entry({"sdewan", configuration, ver, "zone"}, call("handle_request")).leaf = true
+    entry({"sdewan", configuration, ver, "redirect"}, call("handle_request")).leaf = true
+    entry({"sdewan", configuration, ver, "rule"}, call("handle_request")).leaf = true
+    entry({"sdewan", configuration, ver, "forwarding"}, call("handle_request")).leaf = true
+end
+
+function is_network_interface_available(interface)
+    local interfaces = uci:get_all("network", interface)
+    if interfaces == nil then
+        return false, "Interface[" .. interface .. "] is not definied"
+    end
+
+    return true, interface
+end
+
+function is_valid_masq_subset(s)
+    local ip = s
+    if utils.start_with(ip, "!") then
+        ip = string.sub(ip, 2, string.len(ip))
+    end
+
+    local res, _ = utils.is_valid_ip(ip)
+    if res then
+        return true, s
+    else
+        return false
+    end
+end
+
+function check_redirect(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 == "DNAT" then
+        if value["src"] == nil then
+            return false, "src is required for DNAT"
+        end
+    end
+
+    return true, value
+end
+
+function check_rule(value)
+    local target = value["target"]
+    if target == "MARK" then
+        if value["set_mark"] == nil and value["set_xmark"] == nil then
+            return false, "set_mark or set_xmark is required for MARK"
+        end
+    end
+
+    return true, value
+end
+
+function check_icmp_type(value)
+    return utils.in_array(value, {"address-mask-reply", "address-mask-request ", "any", "communication-prohibited",
+        "destination-unreachable", "echo-reply", "echo-request", "fragmentation-needed", "host-precedence-violation",
+        "host-prohibited", "host-redirect", "host-unknown", "host-unreachable", "ip-header-bad", "network-prohibited",
+        "network-redirect", "network-unknown", "network-unreachable", "parameter-problem", "ping", "pong",
+        "port-unreachable", "precedence-cutoff", "protocol-unreachable", "redirect", "required-option-missing",
+        "router-advertisement", "router-solicitation", "source-quench", "source-route-failed", "time-exceeded",
+        "timestamp-reply", "timestamp-request", "TOS-host-redirect", "TOS-host-unreachable", "TOS-network-redirect",
+        "TOS-network-unreachable", "ttl-exceeded", "ttl-zero-during-reassembly", "ttl-zero-during-transit"})
+end
+
+-- Request Handler
+function handle_request()
+    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, firewall_processor)
+    end
+end
+
+-- Zone APIs
+-- check if zone is used by rule, forwarding or redirect
+function is_zone_used(name)
+    local is_used = false
+
+    for i,v in pairs(zone_checker) do
+        local section_name = v["name"]
+        local checker = v["checker"]
+        uci:foreach(uci_conf, section_name,
+            function(section)
+                for j=1, #checker do
+                    if name == section[checker[j]] then
+                        is_used = true
+                        return false
+                    end
+                end
+            end
+        )
+
+        if is_used then
+            break
+        end
+    end
+
+    return is_used
+end
+
+function is_zone_available(name)
+    local zone = utils.get_object(_M, firewall_processor, "zone", name)
+    if zone == nil then
+        return false, "Zone[" .. name .. "] is not definied"
+    end
+
+    return true, name
+end
+
+-- get /zones
+function get_zones()
+    utils.handle_get_objects("zones", uci_conf, "zone", zone_validator)
+end
+
+-- delete a zone
+function delete_zone(name, check_used)
+    -- check whether zone is defined
+    local zone = utils.get_object(_M, firewall_processor, "zone", name)
+    if zone == nil then
+        return false, 404, "zone " .. name .. " is not defined"
+    end
+
+    if check_used == nil then
+        check_used = true
+    else
+        check_used = false
+    end
+
+    -- Todo: check whether the zone is used by a rule
+    if check_used == true and is_zone_used(name) then
+        return false, 400, "zone " .. name .. " is used"
+    end
+
+    -- delete zone
+    uci:foreach(uci_conf, "zone",
+        function (section)
+            if name == section[".name"] or name == section["name"] then
+                uci:delete(uci_conf, section[".name"])
+            end
+        end
+    )
+
+    -- commit change
+    uci:save(uci_conf)
+    uci:commit(uci_conf)
+
+    return true
+end
+
+-- update a zone
+function update_zone(zone)
+    local name = zone.name
+    res, code, msg = delete_zone(name, false)
+    if res == true then
+        return utils.create_object(_M, firewall_processor, "zone", zone)
+    end
+
+    return false, code, msg
+end
+
+-- Redirect APIs
+-- get /redirects
+function get_redirects()
+    utils.handle_get_objects("redirects", uci_conf, "redirect", redirect_validator)
+end
+
+-- Rule APIs
+-- get /rules
+function get_rules()
+    utils.handle_get_objects("rules", uci_conf, "rule", rule_validator)
+end
+
+-- Forwarding APIs
+-- get /forwardings
+function get_forwardings()
+    utils.handle_get_objects("forwardings", uci_conf, "forwarding", forwarding_validator)
+end
diff --git a/cnf/build/rest_v1/index.lua b/cnf/build/rest_v1/index.lua
new file mode 100644 (file)
index 0000000..3a11e92
--- /dev/null
@@ -0,0 +1,16 @@
+-- Licensed to the public under the GNU General Public License v2.
+
+module("luci.controller.rest_v1.index", package.seeall)
+
+function index()
+    ver = "v1"
+    entry({"sdewan", ver}, call("help")).dependent = false
+    entry({"sdewan", "mwan3", ver}, call("help")).dependent = false
+    entry({"sdewan", "firewall", ver}, call("help")).dependent = false
+    entry({"sdewan", "ipsec", ver}, call("help")).dependent = false
+end
+
+function help()
+    luci.http.prepare_content("application/json")
+    luci.http.write('{"message":"sdewan restful API service v1"}')
+end
diff --git a/cnf/build/rest_v1/ipsec_rest.lua b/cnf/build/rest_v1/ipsec_rest.lua
new file mode 100644 (file)
index 0000000..a158941
--- /dev/null
@@ -0,0 +1,265 @@
+-- Licensed to the public under the GNU General Public License v2.
+
+module("luci.controller.rest_v1.ipsec_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"
+
+uci_conf = "ipsec"
+
+proposal_validator = {
+    {name="name"},
+    {name="encryption_algorithm", validator=function(value) return true, value end, message="invalid encryption_algorithm"},
+    {name="hash_algorithm", validator=function(value) return true, value end, message="invalid hash_algorithm"},
+    {name="dh_group", validator=function(value) return true, value end, message="invalid dh_group"},
+}
+
+connection_validator = {
+    config_type=function(value) return value["type"] end,
+    {name="name"},
+    {name="type", required=true, validator=function(value) return utils.in_array(value, {"tunnel", "transport"}) end, load_func=function(value) return value[".type"] end, save_func=function(value) return true, "" end, message="invalid type"},
+    {name="mode"},
+    {name="local_subnet"},
+    {name="local_nat"},
+    {name="local_sourceip"},
+    {name="local_updown"},
+    {name="local_firewall"},
+    {name="remote_subnet"},
+    {name="remote_sourceip"},
+    {name="remote_updown"},
+    {name="remote_firewall"},
+    {name="crypto_proposal", is_list=true, item_validator=function(value) return is_proposal_available(value) end, message="invalid crypto_proposal"},
+}
+
+site_validator = {
+    config_type="remote",
+    {name="name"},
+    {name="gateway"},
+    {name="pre_shared_key"},
+    {name="authentication_method"},
+    {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="force_crypto_proposal"},
+    {name="local_public_cert",
+        load_func=function(value) return load_cert(value["local_public_cert"]) end,
+        save_func=function(value) return save_cert(value["local_public_cert"], "/tmp/" .. value["name"] .. "_public.cert") end,
+        delete_func=function(value) return delete_cert(value["local_public_cert"]) end},
+    {name="local_private_cert",
+        load_func=function(value) return load_cert(value["local_private_cert"]) end,
+        save_func=function(value) return save_cert(value["local_private_cert"], "/tmp/" .. value["name"] .. "_private.cert") end,
+        delete_func=function(value) return delete_cert(value["local_private_cert"]) end},
+    {name="shared_ca"},
+    {name="connections", item_validator=connection_validator, message="invalid connection",
+        load_func=function(value) return load_connection(value) end,
+        save_func=function(value) return save_connection(value) end,},
+}
+
+ipsec_processor = {
+    proposal={update="update_proposal", delete="delete_proposal", validator=proposal_validator},
+    site={validator=site_validator},
+    configuration=uci_conf
+}
+
+proposal_checker = {
+    {name="remote", checker={"crypto_proposal"}},
+    {name="tunnel", checker={"crypto_proposal"}},
+    {name="transport", checker={"crypto_proposal"}},
+}
+
+function index()
+    ver = "v1"
+    configuration = "ipsec"
+    entry({"sdewan", configuration, ver, "proposals"}, call("get_proposals"))
+    entry({"sdewan", configuration, ver, "sites"}, call("get_sites"))
+    entry({"sdewan", configuration, ver, "proposal"}, call("handle_request")).leaf = true
+    entry({"sdewan", configuration, ver, "site"}, call("handle_request")).leaf = true
+end
+
+-- Request Handler
+function handle_request()
+    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, ipsec_processor)
+    end
+end
+
+function save_cert(content, path)
+    local file = io.open(path, "w")
+    if file == nil then
+        return false, "Can not generate cert at: " .. path
+    end
+
+    file:write(content)
+    file:close()
+
+    return true, path
+end
+
+function load_cert(path)
+    if path == nil then
+        return nil
+    end
+    content = path
+    local file = io.open(path, "rb")
+    if file ~= nil then
+        content = file:read "*a"
+        file:close()
+    end
+    return content
+end
+
+function delete_cert(path)
+    if path ~= nil then
+        os.remove(path)
+    end
+end
+
+function add_to_key_list(arr, key, value)
+    local sub_arr = nil
+    for i=1, #arr do
+        if key == arr[i].option then
+            sub_arr = arr[i].values
+        end
+    end
+
+    if sub_arr == nil then
+        sub_arr = {}
+        arr[#arr+1] = {option=key, values=sub_arr}
+    end
+    sub_arr[#sub_arr+1] = value
+end
+
+-- load uci configuration as format: {{section="tunnel/transport", name="section_name"},}
+function load_connection(value)
+    local ret_value={}
+    local tunnels = value["tunnel"]
+    if tunnels ~= nil and #tunnels > 0 then
+        for i=1, #tunnels do
+            ret_value[#ret_value+1]={section="tunnel", name=tunnels[i]}
+        end
+    end
+    local transports = value["transport"]
+    if transports ~= nil and #transports > 0 then
+        for i=1, #transports do
+            ret_value[#ret_value+1]={section="transport", name=transports[i]}
+        end
+    end
+    if #ret_value == 0 then
+        return nil
+    end
+    return ret_value
+end
+
+-- save connections as standard format:
+-- {_standard_format=true, {option="tunnel", values={...}}, {option="transport", values={...}}}
+function save_connection(value)
+    local connections = value["connections"]
+    local ret_value = {_standard_format=true}
+    for i=1, #connections do
+        add_to_key_list(ret_value, connections[i]["type"], connections[i])
+    end
+
+    return true, ret_value
+end
+
+-- Site APIs
+-- get /sites
+function get_sites()
+    utils.handle_get_objects("sites", uci_conf, "remote", site_validator)
+end
+
+-- Proposal APIs
+-- check if proposal is used by connection, site
+function is_proposal_used(name)
+    local is_used = false
+
+    for i,v in pairs(proposal_checker) do
+        local section_name = v["name"]
+        local checker = v["checker"]
+        uci:foreach(uci_conf, section_name,
+            function(section)
+                for j=1, #checker do
+                    for k=1, #section[checker[j]] do
+                        if name == section[checker[j]][k] then
+                            is_used = true
+                            return false
+                        end
+                    end
+                end
+            end
+        )
+
+        if is_used then
+            break
+        end
+    end
+
+    return is_used
+end
+
+function is_proposal_available(name)
+    local proposal = utils.get_object(_M, ipsec_processor, "proposal", name)
+    if proposal == nil then
+        return false, "Proposal[" .. name .. "] is not definied"
+    end
+
+    return true, name
+end
+
+-- get /proposals
+function get_proposals()
+    utils.handle_get_objects("proposals", uci_conf, "proposal", proposal_validator)
+end
+
+-- delete a proposal
+function delete_proposal(name, check_used)
+    -- check whether proposal is defined
+    local proposal = utils.get_object(_M, ipsec_processor, "proposal", name)
+    if proposal == nil then
+        return false, 404, "proposal " .. name .. " is not defined"
+    end
+
+    if check_used == nil then
+        check_used = true
+    else
+        check_used = false
+    end
+
+    -- Todo: check whether the proposal is used
+    if check_used == true and is_proposal_used(name) then
+        return false, 400, "proposal " .. name .. " is used"
+    end
+
+    -- delete proposal
+    uci:foreach(uci_conf, "proposal",
+        function (section)
+            if name == section[".name"] or name == section["name"] then
+                uci:delete(uci_conf, section[".name"])
+            end
+        end
+    )
+
+    -- commit change
+    uci:save(uci_conf)
+    uci:commit(uci_conf)
+
+    return true
+end
+
+-- update a proposal
+function update_proposal(proposal)
+    local name = proposal.name
+    res, code, msg = delete_proposal(name, false)
+    if res == true then
+        return utils.create_object(_M, ipsec_processor, "proposal", proposal)
+    end
+
+    return false, code, msg
+end
diff --git a/cnf/build/rest_v1/mwan3_rest.lua b/cnf/build/rest_v1/mwan3_rest.lua
new file mode 100644 (file)
index 0000000..dd801b0
--- /dev/null
@@ -0,0 +1,237 @@
+-- Licensed to the public under the GNU General Public License v2.
+
+module("luci.controller.rest_v1.mwan3_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"
+
+policy_member_validator = {
+    {name="interface", required=true, validator=function(value) return is_interface_available(value) end, message="invalid interface"},
+    {name="metric", required=true, validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="metric should be greater than 0"},
+    {name="weight", required=true, validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="weight should be greater than 0"}
+}
+
+policy_validator = {
+    {name="name"},
+    {name="members", required=true, item_validator=policy_member_validator, message="Incorrect policy member"}
+}
+
+rule_validator = {
+    {name="name"},
+    {name="policy", required=true, target="use_policy", validator=function(value) return is_policy_available(value) end, message="invalid policy"},
+    {name="src_ip", validator=function(value) return utils.is_valid_ip(value) end, message="invalid ip address"},
+    {name="src_port", validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="invalid src_port"},
+    {name="dest_ip", validator=function(value) return utils.is_valid_ip(value) end, message="invalid ip address"},
+    {name="dest_port", validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="invalid dest_port"},
+    {name="proto", validator=function(value) return utils.in_array(value, {"tcp", "udp", "icmp", "all"}) end, message="wrong proto"},
+    {name="family", validator=function(value) return utils.in_array(value, {"ipv4", "ipv6", "all"}) end, message="wrong family"},
+    {name="sticky", validator=function(value) return utils.in_array(value, {"0", "1"}) end, message="wrong sticky"},
+    {name="timeout", validator=function(value) return utils.is_integer_and_in_range(value, 0) end, message="invalid timeout"}
+}
+
+mwan3_processor = {
+    policy={get="get_policy", update="update_policy", create="create_policy", delete="delete_policy", validator=policy_validator},
+    rule={validator=rule_validator},
+    configuration="mwan3"
+}
+
+function index()
+    ver = "v1"
+    entry({"sdewan", "mwan3", ver, "policies"}, call("get_policies"))
+    entry({"sdewan", "mwan3", ver, "rules"}, call("get_rules"))
+    entry({"sdewan", "mwan3", ver, "policy"}, call("handle_request")).leaf = true
+    entry({"sdewan", "mwan3", ver, "rule"}, call("handle_request")).leaf = true
+end
+
+-- Request Handler
+function handle_request()
+    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, mwan3_processor)
+    end
+end
+
+-- check interface
+function is_interface_available(name)
+    local interfaces = uci:get_all("mwan3", name)
+    if interfaces == nil then
+        return false, "Interface[" .. name .. "] is not definied"
+    end
+
+    return true
+end
+
+-- check policy
+function is_policy_available(name)
+    local policy = uci:get_all("mwan3", name)
+    if policy == nil then
+        return false, "Policy[" .. name .. "] is not definied"
+    end
+
+    return true
+end
+
+-- policy APIs
+-- get a policy
+function get_policy(name)
+    local members = uci:get_all("mwan3", name)
+    if members == nil then
+        return nil
+    end
+    members = members["use_member"]
+
+    local policy = {}
+    policy["name"] = name
+    policy["members"] = {}
+
+    for i=1, #members do
+        local member_name = members[i]
+        local member_data = uci:get_all("mwan3", member_name)
+        if not (member_data == nil) then
+            local member = {}
+            member["name"] = member_name
+            utils.set_data("mwan3", policy_member_validator, member_data, member)
+            policy["members"][i] = member
+        end
+    end
+
+    return policy
+end
+
+-- get /policies
+function get_policies()
+    if not (utils.validate_req_method("GET")) then
+        return
+    end
+
+    local res = {}
+    res["policies"] = {}
+
+    local index = 1
+    uci:foreach("mwan3", "policy",
+        function (section)
+            policy = get_policy(section[".name"])
+            if not (policy == nil) then
+                res["policies"][index] = policy
+                index = index + 1
+            end
+        end
+    )
+
+    utils.response_object(res)
+end
+
+-- create a policy
+function create_policy(policy)
+    -- check whether policy is exist
+    local policy_name = policy.name
+    local exist_policy_obj = uci:get_all("mwan3", policy_name)
+    if exist_policy_obj ~= nil then
+        return false, 409, "Conflict"
+    end
+
+    -- check member interfaces
+    local member_interface_names = {}
+    local members = policy.members
+    for i=1, #members do
+        if utils.in_array(policy_name .. "_" .. members[i].interface, member_interface_names) then
+            return false, 400, "Same interface is used in different members"
+        end
+        member_interface_names[i] = policy_name .. "_" .. members[i].interface
+    end
+
+    -- create member
+    for i=1, #members do
+        uci:section("mwan3", "member", member_interface_names[i], {
+            interface = members[i].interface,
+            metric = members[i].metric,
+            weight = members[i].weight})
+    end
+
+    -- create policy
+    uci:section("mwan3", "policy", policy_name)
+    uci:set_list("mwan3", policy_name, "use_member", member_interface_names)
+
+    -- commit change
+    uci:save("mwan3")
+    uci:commit("mwan3")
+
+    return true
+end
+
+-- check if policy is used by rule
+function is_policy_used(name)
+    local is_used = false
+
+    uci:foreach("mwan3", "rule",
+        function (section)
+            rule = utils.get_object(_M, mwan3_processor, "rule", section[".name"])
+            if not (rule == nil) then
+                if rule.policy == name then
+                    is_used = true
+                    return false
+                end
+            end
+        end
+    )
+
+    return is_used
+end
+
+-- delete a policy
+function delete_policy(name, check_used)
+    -- check whether policy is defined
+    local policy = get_policy(name)
+    if policy == nil then
+        return false, 404, "policy " .. name .. " is not defined"
+    end
+
+    if check_used == nil then
+        check_used = true
+    else
+        check_used = false
+    end
+
+    -- Todo: check whether the policy is used by a rule
+    if check_used == true and is_policy_used(name) then
+        return false, 400, "policy " .. name .. " is used"
+    end
+
+    -- delete policy
+    uci:delete("mwan3", name)
+
+    -- delete members
+    local members = policy.members
+    for i=1, #members do
+        uci:delete("mwan3", members[i].name)
+    end
+
+    -- commit change
+    uci:save("mwan3")
+    uci:commit("mwan3")
+
+    return true
+end
+
+-- update a policy
+function update_policy(policy)
+    local name = policy.name
+    res, code, msg = delete_policy(name, false)
+    if res == true then
+        return create_policy(policy)
+    end
+
+    return false, code, msg
+end
+
+-- Rule APIs
+-- get /rules
+function get_rules()
+    utils.handle_get_objects("rules", "mwan3", "rule", rule_validator)
+end
diff --git a/cnf/build/rest_v1/service.lua b/cnf/build/rest_v1/service.lua
new file mode 100644 (file)
index 0000000..6a54deb
--- /dev/null
@@ -0,0 +1,89 @@
+-- Licensed to the public under the GNU General Public License v2.
+
+module("luci.controller.rest_v1.service", package.seeall)
+
+json = require "luci.jsonc"
+io = require "io"
+sys = require "luci.sys"
+utils = require "luci.controller.rest_v1.utils"
+
+available_services = {"mwan3", "firewall", "ipsec"}
+action_tables = {
+    mwan3 = {command="/etc/init.d/mwan3", reload="restart"},
+    firewall = {command="/etc/init.d/firewall", reload_command="fw3"},
+    ipsec = {command="/etc/init.d/ipsec", reload="restart"}
+}
+
+executeservice_validator = {
+    {name="action", required=true, validator=function(value) return utils.in_array(value, {"start", "stop", "restart", "reload"}) end, message="wrong action"}
+}
+
+function index()
+    ver = "v1"
+    entry({"sdewan", ver, "services"}, call("getServices"))
+    entry({"sdewan", ver, "service"}, call("executeService")).leaf = true
+end
+
+function getServices()
+    if not (utils.validate_req_method("GET")) then
+        return
+    end
+
+    local res = {}
+    res["services"] = available_services
+
+    utils.response_object(res)
+end
+
+function getservicename()
+    local uri_list = utils.get_URI_list(6)
+    if uri_list == nil then
+        return nil
+    end
+
+    local service = uri_list[#uri_list]
+    if not utils.in_array(service, available_services) then
+        utils.response_error(400, "Bad request URI")
+        return nil
+    end
+
+    return service
+end
+
+function executeService()
+    -- check request method
+    if not (utils.validate_req_method("PUT")) then
+        return
+    end
+
+    -- get service name
+    local service = getservicename()
+    if service == nil then
+        return
+    end
+
+    -- check content
+    local body_obj = utils.get_and_validate_body_object(executeservice_validator)
+    if body_obj == nil then
+        return
+    end
+
+    local action = body_obj["action"]
+
+    local exec_command = action_tables[service][action .. "_command"]
+    if exec_command == nil then
+        exec_command = action_tables[service]["command"]
+    end
+    local exec_action = action_tables[service][action]
+    if exec_action == nil then
+        exec_action  = action
+    end
+
+    exec_command = exec_command .. " " .. exec_action
+
+    -- execute command
+    utils.log("Execute Command: %s" % exec_command)
+    sys.exec(exec_command)
+
+    utils.response_success()
+end
diff --git a/cnf/build/rest_v1/utils.lua b/cnf/build/rest_v1/utils.lua
new file mode 100644 (file)
index 0000000..5e35951
--- /dev/null
@@ -0,0 +1,777 @@
+-- Licensed to the public under the GNU General Public License v2.
+
+module("luci.controller.rest_v1.utils", package.seeall)
+
+local json = require "luci.jsonc"
+local uci = require "luci.model.uci"
+REQUEST_METHOD = "REQUEST_METHOD"
+
+function index()
+end
+
+function log(obj)
+    return io.stderr:write(tostring(obj) .. "\n")
+end
+
+function printTableKeys(t, prefix)
+    for key, value in pairs(t) do
+        if string.sub(key,1,string.len(prefix)) == prefix then
+            log(key)
+        end
+    end
+end
+
+-- check whether s starts with prefix
+function start_with(s, prefix)
+    return (string.find(s, "^" .. prefix) ~= nil)
+end
+
+-- check ip
+function is_match(s, reg)
+    local ret = string.match(s, reg)
+    if s == ret then
+        return true
+    end
+    return false
+end
+
+function is_valid_ip_address(s)
+    local array = {}
+    local reg = string.format("([^%s]+)", ".")
+    for item in string.gmatch(s, reg) do
+        if is_match(item, "(25[0-5])") or is_match(item, "(2[0-4]%d)") or is_match(item, "([01]?%d?%d)") then
+            table.insert(array, item)
+        else
+            return false
+        end
+    end
+
+    if #array ~= 4 then
+        return false
+    end
+
+    return true, s
+end
+
+function is_valid_ip(s)
+    local array = {}
+    local reg = string.format("([^%s]+)", "/")
+    for item in string.gmatch(s, reg) do
+        table.insert(array, item)
+    end
+
+    if #array == 1 then
+        -- check ip address
+        return is_valid_ip_address(array[1])
+    else
+        if #array == 2 then
+            if is_valid_ip_address(array[1]) then
+                -- check mask
+                if is_integer_and_in_range(array[2], -1, 33) then
+                    return true, s
+                end
+            end
+        end
+    end
+
+    return false
+end
+
+function is_valid_mac(s)
+    local array = {}
+    local reg = string.format("([^%s]+)", ":")
+    for item in string.gmatch(s, reg) do
+        if is_match(item, "([a-fA-F0-9][a-fA-F0-9])") then
+            table.insert(array, item)
+        else
+            return false
+        end
+    end
+
+    if #array == 6 then
+        -- check mac
+        return true, s
+    end
+
+    return false
+end
+
+-- trim a string
+function trim(s)
+    return s:match("^%s*(.-)%s*$")
+end
+
+-- split a string based on sep
+function split_and_trim(str, sep)
+    local array = {}
+    local reg = string.format("([^%s]+)", sep)
+    for item in string.gmatch(str, reg) do
+        item_trimed = trim(item)
+        if string.len(item_trimed) > 0 then
+            table.insert(array, item_trimed)
+        end
+    end
+    return array
+end
+
+-- Check whether value1 is equal to value2
+function equal(value1, value2)
+    return value1 == value2
+end
+
+-- Check whether value is in values array
+function in_array(value, values)
+    for i=1, #values do
+        if (value == values[i]) then
+            return true, value
+        end
+    end
+    return false
+end
+
+-- Check whether value is an integer and in special range
+function is_integer_and_in_range(value, min_value, max_value)
+    if(type(value) == "string") then
+        local num_value = tonumber(value)
+        if not (num_value == nil) then
+            local int_value = math.floor(num_value)
+            if int_value == num_value then
+                if (min_value ~= nil) then
+                    if (int_value <= min_value) then
+                        return false
+                    end
+                end
+                if (max_value ~= nil) then
+                    if (int_value >= max_value) then
+                        return false
+                    end
+                end
+
+                return true
+            end
+        end
+    end
+
+    return false
+end
+
+-- Rest API handler function
+handles_table = {
+    PUT = "handle_put",
+    GET = "handle_get",
+    POST = "handle_post",
+    DELETE = "handle_delete"
+}
+
+function get_validator_type(validator)
+    config_type = validator["config_type"]
+    if config_type ~= nil and type(config_type) == "function" then
+        config_type = nil
+    end
+
+    return config_type
+end
+
+-- set target from src based on data validator definition
+function get_uci_section(configuration, validator, object_type, name)
+    local object_data = uci:get_all(configuration, name)
+    if object_data == nil then
+        -- check if name defined as option
+        local obj = {}
+        local found = false
+        uci:foreach(configuration, object_type,
+            function (section)
+                if name == section["name"] then
+                    set_data(configuration, validator, section, obj)
+                    found = true
+                    return false
+                end
+            end
+        )
+
+        if found == true then
+            return obj
+        else
+            return nil
+        end
+    else
+        -- name is defined as section name
+        local obj = {}
+        obj["name"] = name
+        set_data(configuration, validator, object_data, obj)
+
+        return obj
+    end
+end
+
+function set_data(configuration, data_validator, src, target)
+    for i,v in pairs(data_validator) do
+        if(type(v) == "table") then
+            local name = v["name"]
+            local src_name = v["target"]
+            local is_list = v["is_list"]
+            if is_list == nil then
+                is_list = false
+            end
+            if src_name == nil then
+                src_name = name
+            end
+            local value = src[src_name]
+            if v["load_func"] ~= nil and type(v["load_func"]) == "function" then
+                value = v["load_func"](src)
+            end
+            if value ~= nil then
+                if is_list and type(value) ~= "table" then
+                    value = { value }
+                end
+
+                if v["item_validator"] ~= nil and type(v["item_validator"]) == "table" then
+                    array_value = {}
+                    local index = 1
+                    for j=1, #value do
+                        local item_obj = nil
+                        if value[j].section ~= nil then
+                            item_obj = get_uci_section(configuration, v["item_validator"], value[j].section, value[j].name)
+                        else
+                            item_obj = get_uci_section(configuration, v["item_validator"], get_validator_type(v["item_validator"]), value[j])
+                        end
+                        if item_obj ~= nil then
+                            array_value[index] = item_obj
+                            index = index + 1
+                        end
+                    end
+
+                    target[name] = array_value
+                else
+                    if v["validator"] ~= nil and type(v["validator"]) == "table" then
+                        if value.section ~= nil then
+                            target[name] = get_uci_section(configuration, value.section, value.name)
+                        else
+                            target[name] = get_uci_section(configuration, v["validator"], get_validator_type(v["validator"]), value)
+                        end
+                    else
+                        target[name] = value
+                    end
+                end
+            end
+        end
+    end
+end
+
+-- get
+function get_objects(type_names, configuration, type_name, validator)
+    local res = {}
+    res[type_names] = {}
+
+    local index = 1
+    uci:foreach(configuration, type_name,
+        function (section)
+            local obj = {}
+            obj["name"] = section[".name"]
+            set_data(configuration, validator, section, obj)
+            res[type_names][index] = obj
+            index = index + 1
+        end
+    )
+
+    return res
+end
+
+function handle_get_objects(type_names, configuration, type_name, validator)
+    if not (validate_req_method("GET")) then
+        return
+    end
+
+    response_object(get_objects(type_names, configuration, type_name, validator))
+end
+
+function get_object(module_table, processors, object_type, name)
+    if processors ~= nil and processors[object_type] ~= nil
+       and processors[object_type]["get"] ~= nil
+       and module_table[processors[object_type]["get"]] ~= nil then
+            return module_table[processors[object_type]["get"]](name)
+    else
+        return get_uci_section(processors["configuration"], processors[object_type].validator, object_type, name)
+    end
+end
+
+function handle_get(module_table, processors)
+    local uri_list = get_URI_list(7)
+    if uri_list == nil then
+        return
+    end
+
+    local object_type = uri_list[#uri_list-1]
+    local name = uri_list[#uri_list]
+    local obj = get_object(module_table, processors, object_type, name)
+
+    if obj == nil then
+        response_error(404, "Cannot find " .. object_type .. "[" .. name .. "]" )
+        return
+    end
+
+    response_object(obj)
+end
+
+-- put
+function update_object(module_table, processors, object_type, obj)
+    if processors ~= nil and processors[object_type] ~= nil 
+       and processors[object_type]["update"] ~= nil 
+       and module_table[processors[object_type]["update"]] ~= nil then
+            return module_table[processors[object_type]["update"]](obj)
+    else
+        local name = obj.name
+        res, code, msg = delete_object(module_table, processors, object_type, name)
+        if res == true then
+            return create_object(module_table, processors, object_type, obj)
+        end
+
+        return false, code, msg
+    end
+end
+
+function handle_put(module_table, processors)
+    local uri_list = get_URI_list(7)
+    if uri_list == nil then
+        return
+    end
+
+    local object_type = uri_list[#uri_list-1]
+    local name = uri_list[#uri_list]
+    -- check content and get object
+    local obj = get_and_validate_body_object(processors[object_type].validator)
+    if obj == nil then
+        return
+    end
+    obj.name = name
+
+    res, code, msg = update_object(module_table, processors, object_type, obj)
+    if res == false then
+        response_error(code, msg)
+    else
+        response_object(get_object(module_table, processors, object_type, name))
+    end
+end
+
+-- post
+function create_uci_section(configuration, validator, object_type, obj)
+    -- check whether object is exist
+    local obj_name = obj.name
+    local exist_obj = uci:get_all(configuration, obj_name)
+    if exist_obj ~= nil then
+        return false, 409, "Conflict"
+    end
+
+    -- create object
+    local create_section_name = validator["create_section_name"]
+    if create_section_name == nil then
+        create_section_name = true
+    end
+
+    local obj_section = nil
+    local obj_section_type = object_type
+    local obj_section_config_type = validator["config_type"]
+    if obj_section_config_type ~= nil then
+        if type(obj_section_config_type) == "function" then
+            obj_section_type = obj_section_config_type(obj)
+        else
+            obj_section_type = obj_section_config_type
+        end
+    end
+    if create_section_name == true then
+        obj_section = uci:section(configuration, obj_section_type, obj_name)
+    else
+        obj_section = uci:section(configuration, obj_section_type)
+    end
+
+    for i,v in pairs(validator) do
+        if(type(v) == "table") then
+            local name = v["name"]
+            if create_section_name == false or name ~= "name" then
+                if(obj[name] ~= nil) then
+                    local target_name = v["target"]
+                    local use_target_name = false
+                    if(target_name == nil) then
+                        target_name = name
+                    else
+                        use_target_name = true
+                    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)
+                        if res == false then
+                            return res, obj_value
+                        end
+                    end
+                    if type(obj_value) == "table" then
+                        -- Support creating multiple options/sections:
+                        -- change obj_value to standard format: {{option="option_name", values={...}},}
+                        if obj_value._standard_format == nil then
+                            obj_value = {{option=target_name, values=obj_value},}
+                        end
+                        for j=1, #obj_value do
+                            local option_name = obj_value[j].option
+                            local section_obj = obj_value[j].values
+                            if v["item_validator"] ~= nil and type(v["item_validator"]) == "table" then
+                                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])
+                                    if res == false then
+                                        return res, msg
+                                    end
+                                end
+                                uci:set_list(configuration, obj_section, option_name, sub_obj_names)
+                            else
+                                uci:set_list(configuration, obj_section, option_name, section_obj)
+                            end
+                        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)
+                            if res == false then
+                                return res, msg
+                            end
+                            uci:set(configuration, obj_section, target_name, obj_value.name)
+                        else
+                            if obj_value ~= "" then
+                                uci:set(configuration, obj_section, target_name, obj_value)
+                            end
+                        end
+                    end
+                end
+            end
+        end
+    end
+
+    return true
+end
+
+function create_object(module_table, processors, object_type, obj)
+    if processors ~= nil and processors[object_type] ~= nil
+       and processors[object_type]["create"] ~= nil
+       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)
+
+        if res == false then
+            uci:revert(processors["configuration"])
+            return res, msg
+        end
+
+        -- commit change
+        uci:save(processors["configuration"])
+        uci:commit(processors["configuration"])
+
+        return true
+    end
+end
+
+function handle_post(module_table, processors)
+    local uri_list = get_URI_list(6)
+    if uri_list == nil then
+        return
+    end
+
+    local object_type = uri_list[#uri_list]
+    -- check content and get policy object
+    local obj = get_and_validate_body_object(processors[object_type].validator)
+    if obj == nil then
+        return
+    end
+
+    -- check name is not empty
+    local name = obj.name
+    if name ~= nil and type(name) == "string" then
+        name = trim(name)
+    else
+        name = nil
+    end
+    if name == nil or name == "" then
+        response_error(400, "Field[name] checked failed: required")
+        return
+    end
+
+    res, code, msg = create_object(module_table, processors, object_type, obj)
+    if res == false then
+        response_error(code, msg)
+    else
+        response_object(get_object(module_table, processors, object_type, name), 201)
+    end
+end
+
+-- delete
+function delete_uci_section(configuration, validator, obj, object_type)
+    local name = obj["name"]
+    local obj_section_config_type = validator["config_type"]
+    if obj_section_config_type ~= nil then
+        if type(obj_section_config_type) == "function" then
+            object_type = obj_section_config_type(obj)
+        else
+            object_type = obj_section_config_type
+        end
+    end
+
+    -- delete depedency uci section
+    for i,v in pairs(validator) do
+        if(type(v) == "table") then
+            if v["item_validator"] ~= nil and type(v["item_validator"]) == "table" then
+                for j=1, #obj[v["name"]] do
+                    local sub_obj = obj[v["name"]][j]
+                    delete_uci_section(configuration, v["item_validator"], obj[v["name"]][j], v["name"])
+                end
+            else
+                if v["validator"] ~= nil and type(v["validator"]) == "table" then
+                    delete_uci_section(configuration, v["validator"], obj[v["name"]], v["name"])
+                end
+            end
+        end
+    end
+
+    -- delete uci section
+    uci:foreach(configuration, object_type,
+        function (section)
+            if name == section[".name"] or name == section["name"] then
+                for i,v in pairs(validator) do
+                    if(type(v) == "table") then
+                        if v["delete_func"] ~= nil and type(v["delete_func"]) == "function" then
+                            v["delete_func"](section)
+                        end
+                    end
+                end
+                uci:delete(configuration, section[".name"])
+            end
+        end
+    )
+end
+
+function delete_object(module_table, processors, object_type, name)
+    if processors ~= nil and processors[object_type] ~= nil
+       and processors[object_type]["delete"] ~= nil
+       and module_table[processors[object_type]["delete"]] ~= nil then
+            return module_table[processors[object_type]["delete"]](name)
+    else
+        local obj = get_object(module_table, processors, object_type, name)
+        if obj == nil then
+            return false, 404, object_type .. " " .. name .. " is not defined"
+        end
+
+        delete_uci_section(processors["configuration"], processors[object_type].validator, obj, object_type)
+
+        -- commit change
+        uci:save(processors["configuration"])
+        uci:commit(processors["configuration"])
+
+        return true
+    end
+end
+
+function handle_delete(module_table, processors)
+    local uri_list = get_URI_list(7)
+    if uri_list == nil then
+        return
+    end
+
+    local object_type = uri_list[#uri_list-1]
+    local name = uri_list[#uri_list]
+
+    res, code, msg = delete_object(module_table, processors, object_type, name)
+    if res == false then
+        response_error(code, msg)
+    else
+        response_success()
+    end
+end
+
+-- Validate src with validator then set target (array)
+function validate_and_set_data_array(validator, src)
+    if #src == 0 then
+        return false, "Not an array"
+    end
+
+    local ret_obj = {}
+    for i=1, #src do
+        local res = false
+        local ret_obj_item = nil
+        if type(validator) == "function" then
+            res, ret_obj_item = validator(src[i])
+        else
+            res, ret_obj_item = validate_and_set_data(validator, src[i])
+        end
+
+        if not res then
+            return false, ret_obj_item
+        end
+        ret_obj[i] = ret_obj_item
+    end
+
+    return true, ret_obj
+end
+
+-- Validate src with validator then set target (set data field with default value if applied)
+-- return:
+--   res: validate result
+--   ret_obj: copy of src based on validator definition(res=true) or error message(res=false)
+function validate_and_set_data(validator, src)
+    local target = {}
+    for i,v in pairs(validator) do
+        if(type(v) == "table") then
+            local name = v["name"]
+            local val_func = v["validator"]
+            local item_val_func = v["item_validator"]
+            local error_message = v["message"]
+            local required = v["required"]
+            local default = v["default"]
+            local target_name = name
+
+            if required == nil then
+                required = false
+            end
+
+            if error_message == nil then
+                error_message = ""
+            end
+
+            local value = src[name]
+            if value ~= nil and type(value) == "string" then
+                value = trim(value)
+            end
+
+            if value ~= nil and value ~= "" then
+                local res = true
+                local ret_obj = nil
+                if val_func ~= nil then
+                    if type(val_func) == "function" then
+                        res, ret_obj = val_func(value)
+                    else
+                        res, ret_obj = validate_and_set_data(val_func, value)
+                    end
+                else
+                    if item_val_func ~= nil then
+                        res, ret_obj = validate_and_set_data_array(item_val_func, value)
+                    end
+                end
+
+                if res == false then
+                    if ret_obj ~= nil and ret_obj ~= "" then
+                        return false, "Field[" .. name .. "] checked failed: " .. error_message .. " [" .. ret_obj .. "]"
+                    else
+                        return false, "Field[" .. name .. "] checked failed: " .. error_message
+                    end
+                else
+                    if ret_obj ~= nil then
+                        value = ret_obj
+                    end
+                end
+
+                target[target_name] = value
+            else
+                if required then
+                    return false, "Field[" .. name .. "] checked failed: required"
+                else
+                    -- set default value
+                    if default ~= nil then
+                        target[target_name] = default
+                    end
+                end
+            end
+        end
+    end
+
+    -- check with object_validator
+    local object_validator = validator["object_validator"]
+    if object_validator ~= nil then
+        return object_validator(target)
+    end
+
+    return true, target
+end
+
+-- parse URI
+function get_URI_list(required_lenth)
+    local uri = luci.http.getenv("REQUEST_URI")
+    local uri_list = split_and_trim(uri, "/")
+    if not (#uri_list == required_lenth) then
+        response_error(400, "Bad request URI")
+        return nil
+    end
+
+    return uri_list
+end
+
+-- get request body
+function get_request_body_object()
+    local content, size = luci.http.content()
+    local content_obj, err = json.parse(content)
+    if not (err == nil) then
+        response_error(400, "Body parse error: " .. err)
+    end
+
+    return content_obj
+end
+
+-- get and validate body object
+function get_and_validate_body_object(validator)
+    local body_obj = get_request_body_object()
+    if body_obj == nil then
+        return nil
+    end
+
+    local res, res_obj = validate_and_set_data(validator, body_obj)
+    if not res then
+        response_error(400, res_obj)
+        return nil
+    end
+
+    return res_obj
+end
+
+-- get request method
+function get_req_method()
+    return luci.http.getenv("REQUEST_METHOD")
+end
+
+-- check request method
+function validate_req_method(method)
+    local res = equal(get_req_method(), method)
+    if not res then
+        response_error(405, "Method Not Allowed")
+    end
+
+    return res
+end
+
+-- response with error
+function response_error(code, message)
+    if message == nil then
+        message = ""
+    end
+    luci.http.status(code, message)
+    luci.http.prepare_content("text/plain")
+    luci.http.write('{"message":"' .. message .. '"}')
+end
+
+-- response with json object
+function response_object(obj, code)
+    if code ~= nil then
+        luci.http.status(code, "")
+    end
+    luci.http.prepare_content("application/json")
+    luci.http.write_json(obj)
+end
+
+-- response success message
+function response_success(code)
+    local message = '{"message":"success"}'
+    if code ~= nil then
+        luci.http.status(code, message)
+    end
+       response_message(message)
+end
+
+-- response with message
+function response_message(message)
+    luci.http.prepare_content("text/plain")
+    luci.http.write(message)
+end
\ No newline at end of file
diff --git a/cnf/build/set_proxy b/cnf/build/set_proxy
new file mode 100644 (file)
index 0000000..1ad2350
--- /dev/null
@@ -0,0 +1,2 @@
+# set docker proxy  with below line
+#docker_proxy=
diff --git a/cnf/build/system b/cnf/build/system
new file mode 100644 (file)
index 0000000..5165430
--- /dev/null
@@ -0,0 +1,7 @@
+config system
+    option log_file '/var/log/mylog'
+    option timezone 'UTC'
+    option ttylogin '0'
+    option log_size '64'
+    option urandom_seed '0'
+EOF
diff --git a/cnf/test/openwrtclient_fw_test.go b/cnf/test/openwrtclient_fw_test.go
new file mode 100644 (file)
index 0000000..e692e61
--- /dev/null
@@ -0,0 +1,1138 @@
+package openwrt
+
+import (
+    "openwrt"
+    "reflect"
+    "testing"
+    "flag"
+    "encoding/json"
+    "fmt"
+    "os"
+)
+
+var fw openwrt.FirewallClient
+var available_zone_1 string
+var available_zone_2 string
+
+func TestMain(m *testing.M) {
+    servIp := flag.String("ip", "10.244.0.18", "SDEWAN CNF Management IP Address")
+    flag.Parse()
+
+    client := openwrt.NewOpenwrtClient(*servIp, "root", "")
+    fw = openwrt.FirewallClient{client}
+    available_zone_1 = "wan"
+    available_zone_2 = "lan"
+
+    os.Exit(m.Run())
+}
+
+// Error handler
+func handleError(t *testing.T, err error, name string, expectedErr bool, errorCode int) {
+    if (err != nil) {
+        if (expectedErr) {
+            switch err.(type) {
+            case *openwrt.OpenwrtError:
+                if(errorCode != err.(*openwrt.OpenwrtError).Code) {
+                    t.Errorf("Test case '%s': expected '%d', but got '%d'", name, errorCode, err.(*openwrt.OpenwrtError).Code)
+                } else {
+                    fmt.Printf("%s\n", err.(*openwrt.OpenwrtError).Message)
+                }
+            default:
+                t.Errorf("Test case '%s': expected openwrt.OpenwrtError, but got '%s'", name, reflect.TypeOf(err).String())
+            }
+        } else {
+            t.Errorf("Test case '%s': expected success, but got '%s'", name, reflect.TypeOf(err).String())
+        }
+    } else {
+        if (expectedErr) {
+            t.Errorf("Test case '%s': expected error code '%d', but success", name, errorCode)
+        }
+    }
+}
+
+func printError(err error) {
+    switch err.(type) {
+    case *openwrt.OpenwrtError:
+        fmt.Printf("%s\n", err.(*openwrt.OpenwrtError).Message)
+    default:
+        fmt.Printf("%s\n", reflect.TypeOf(err).String())
+    }
+}
+
+// Firewall Zone API Test
+func TestGetZones(t *testing.T) {
+    res, err := fw.GetZones()
+    if res == nil {
+        printError(err)
+        t.Errorf("Test case GetZones: can not get firewall zones")
+        return
+    }
+
+    if len(res.Zones) == 0 {
+        fmt.Printf("Test case GetZones: no zone defined")
+        return
+    }
+
+    p_data, _ := json.Marshal(res)
+    fmt.Printf("%s\n", string(p_data))
+}
+
+func TestGetZone(t *testing.T) {
+    tcases := []struct {
+        name string
+        zone string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "GetAvailableZone",
+            zone: available_zone_1,
+        },
+        {
+            name: "GetFoolRule",
+            zone: "foo_zone",
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+       for _, tcase := range tcases {
+        _, err := fw.GetZone(tcase.zone)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestCreateFoolZone(t *testing.T) {
+    tcases := []struct {
+        name string
+        zone openwrt.SdewanFirewallZone
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "EmptyName",
+            zone: openwrt.SdewanFirewallZone{Name:" "},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidNetwork",
+            zone: openwrt.SdewanFirewallZone{Name:"test_zone", Network:[]string{"fool_name"}},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidMasq",
+            zone: openwrt.SdewanFirewallZone{Name:"test_zone", Masq:"2"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidMasqSrc",
+            zone: openwrt.SdewanFirewallZone{Name:"test_zone", MasqSrc:[]string{"256.0.0.0/0"}},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidMasqDest",
+            zone: openwrt.SdewanFirewallZone{Name:"test_zone", MasqDest:[]string{"256.0.0.0/0"}},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidMasqAllowInvalid",
+            zone: openwrt.SdewanFirewallZone{Name:"test_zone", MasqAllowInvalid:"2"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidMtuFix",
+            zone: openwrt.SdewanFirewallZone{Name:"test_zone", MtuFix:"2"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidInput",
+            zone: openwrt.SdewanFirewallZone{Name:"test_zone", Input:"FOOL"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidForward",
+            zone: openwrt.SdewanFirewallZone{Name:"test_zone", Forward:"FOOL"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidOutput",
+            zone: openwrt.SdewanFirewallZone{Name:"test_zone", Output:"FOOL"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidFamily",
+            zone: openwrt.SdewanFirewallZone{Name:"test_zone", Family:"FOOL"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidSubnet",
+            zone: openwrt.SdewanFirewallZone{Name:"test_zone", Subnet:[]string{"256.0.0.0/0"}},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := fw.CreateZone(tcase.zone)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestDeleteFoolZone(t *testing.T) {
+    tcases := []struct {
+        name string
+        zone string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "EmptyName",
+            zone: "",
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "FoolName",
+            zone: "fool_name",
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        err := fw.DeleteZone(tcase.zone)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestDeleteAvailableZone(t *testing.T) {
+    var err error
+    var zone_name = "test_zone_1"
+    var redirect_name = "test_redirect_1"
+    var zone = openwrt.SdewanFirewallZone{Name:zone_name, Network:[]string{"wan", "lan"}, Input:"REJECT", Masq:"1"}
+    var redirect = openwrt.SdewanFirewallRedirect{Name:redirect_name, Src:zone_name, SrcDPort:"22000", Dest:"lan", DestPort:"22", Proto:"tcp", Target:"DNAT"}
+
+    // create zone
+    _, err = fw.GetZone(zone_name)
+    if (err == nil) {
+        err = fw.DeleteZone(zone_name)
+        if (err != nil) {
+            printError(err)
+            t.Errorf("Test case TestDeleteAvailableZone: failed to delete zone '%s'", zone_name)
+            return
+        }
+    }
+
+    _, err = fw.CreateZone(zone)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestDeleteAvailableZone: failed to create zone '%s'", zone_name)
+        return
+    }
+
+    // create redirect
+    _, err = fw.GetRedirect(redirect_name)
+    if (err == nil) {
+        err = fw.DeleteRedirect(redirect_name)
+        if (err != nil) {
+            printError(err)
+            t.Errorf("Test case TestDeleteAvailableZone: failed to delete redirect '%s'", redirect_name)
+            return
+        }
+    }
+
+    _, err = fw.CreateRedirect(redirect)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestDeleteAvailableZone: failed to create redirect '%s'", redirect_name)
+        return
+    }
+
+    // try to delete a used zone
+    err = fw.DeleteZone(zone_name)
+    if (err != nil) {
+        printError(err)
+    } else {
+        t.Errorf("Test case TestDeleteAvailableZone: error to delete zone '%s'", zone_name)
+        return
+    }
+
+    // clean
+    err = fw.DeleteRedirect(redirect_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestDeleteAvailableZone: failed to delete redirect '%s'", redirect_name)
+        return
+    }
+
+    err = fw.DeleteZone(zone_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestDeleteAvailableZone: failed to delete zone '%s'", zone_name)
+        return
+    }
+}
+
+func TestUpdateFoolZone(t *testing.T) {
+    tcases := []struct {
+        name string
+        zone openwrt.SdewanFirewallZone
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "FoolName",
+            zone: openwrt.SdewanFirewallZone{Name:"fool_name"},
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := fw.UpdateZone(tcase.zone)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestZone(t *testing.T) {
+    var err error
+    var ret_zone *openwrt.SdewanFirewallZone
+
+    var zone_name = "test_zone"
+    var zone = openwrt.SdewanFirewallZone{Name:zone_name, Network:[]string{"wan", "lan"}, Input:"REJECT", Masq:"1"}
+    var update_zone = openwrt.SdewanFirewallZone{Name:zone_name, Network:[]string{"lan"}, Input:"ACCEPT", MasqSrc:[]string{"!0.0.0.0/0", "172.16.1.1"}, Subnet:[]string{"172.16.0.1/24"}}
+
+    _, err = fw.GetZone(zone_name)
+    if (err == nil) {
+        err = fw.DeleteZone(zone_name)
+        if (err != nil) {
+            printError(err)
+            t.Errorf("Test case TestZone: failed to delete zone '%s'", zone_name)
+            return
+        }
+    }
+
+    // Create zone
+    ret_zone, err = fw.CreateZone(zone)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestZone: failed to create zone '%s'", zone_name)
+        return
+    } else {
+       p_data, _ := json.Marshal(ret_zone)
+        fmt.Printf("Created Zone: %s\n", string(p_data))
+    }
+
+    ret_zone, err = fw.GetZone(zone_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestZone: failed to get created zone")
+        return
+    } else {
+        if( ret_zone.Input != "REJECT" ) {
+            t.Errorf("Test case TestZone: failed to create zone")
+            return
+        }
+    }
+
+    // Update zone
+    ret_zone, err = fw.UpdateZone(update_zone)
+    if (err != nil) {
+        printError(err)
+       t.Errorf("Test case TestZone: failed to update zone '%s'", zone_name)
+        return
+    } else {
+        p_data, _ := json.Marshal(ret_zone)
+        fmt.Printf("Updated Zone: %s\n", string(p_data))
+    }
+
+    ret_zone, err = fw.GetZone(zone_name)
+    if (err != nil) {
+        t.Errorf("Test case TestZone: failed to get updated zone")
+        return
+    } else {
+        if( ret_zone.Input != "ACCEPT" ) {
+            t.Errorf("Test case TestZone: failed to update zone")
+            return
+        }
+    }
+
+    // Delete zone
+    err = fw.DeleteZone(zone_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestZone: failed to delete zone '%s'", zone_name)
+        return
+    }
+
+    ret_zone, err = fw.GetZone(zone_name)
+    if (err == nil) {
+        t.Errorf("Test case TestZone: failed to delete zone")
+        return
+    }
+}
+
+// Firewall Forwarding API Test
+func TestGetForwardings(t *testing.T) {
+    res, err := fw.GetForwardings()
+    if res == nil {
+        printError(err)
+        t.Errorf("Test case GetForwardings: can not get firewall forwardings")
+        return
+    }
+
+    if len(res.Forwardings) == 0 {
+        fmt.Printf("Test case GetForwardings: no forwarding defined")
+        return
+    }
+
+    p_data, _ := json.Marshal(res)
+    fmt.Printf("%s\n", string(p_data))
+}
+
+func TestGetForwarding(t *testing.T) {
+    tcases := []struct {
+        name string
+        forwarding string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "GetFoolForwarding",
+            forwarding: "foo_forwarding",
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := fw.GetForwarding(tcase.forwarding)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestCreateFoolForwarding(t *testing.T) {
+    tcases := []struct {
+        name string
+        forwarding openwrt.SdewanFirewallForwarding
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "EmptyName",
+            forwarding: openwrt.SdewanFirewallForwarding{Name:" "},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "NoSrc",
+            forwarding: openwrt.SdewanFirewallForwarding{Name:"test_forwarding", Dest: available_zone_1},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidSrc",
+            forwarding: openwrt.SdewanFirewallForwarding{Name:"test_forwarding", Src:"fool_zone", Dest: available_zone_1},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "NoDest",
+            forwarding: openwrt.SdewanFirewallForwarding{Name:"test_forwarding", Src: available_zone_1},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidDest",
+            forwarding: openwrt.SdewanFirewallForwarding{Name:"test_forwarding", Src:available_zone_1, Dest:"fool_zone"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidFamily",
+            forwarding: openwrt.SdewanFirewallForwarding{Name:"test_forwarding", Family:"fool_family", Src:available_zone_1, Dest:available_zone_2},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := fw.CreateForwarding(tcase.forwarding)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestDeleteFoolForwarding(t *testing.T) {
+    tcases := []struct {
+        name string
+        forwarding string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "EmptyName",
+            forwarding: "",
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "FoolName",
+            forwarding: "fool_name",
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        err := fw.DeleteForwarding(tcase.forwarding)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestUpdateFoolForwarding(t *testing.T) {
+    tcases := []struct {
+        name string
+        forwarding openwrt.SdewanFirewallForwarding
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "FoolName",
+            forwarding: openwrt.SdewanFirewallForwarding{Name:"fool_name", Src:available_zone_1, Dest:available_zone_2},
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := fw.UpdateForwarding(tcase.forwarding)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestForwarding(t *testing.T) {
+    var err error
+    var ret_forwarding *openwrt.SdewanFirewallForwarding
+
+    var forwarding_name = "test_forwarding"
+    var forwarding = openwrt.SdewanFirewallForwarding{Name:forwarding_name, Src:available_zone_1, Dest:available_zone_2}
+    var update_forwarding = openwrt.SdewanFirewallForwarding{Name:forwarding_name, Src:available_zone_2, Dest:available_zone_1}
+
+    _, err = fw.GetForwarding(forwarding_name)
+    if (err == nil) {
+        err = fw.DeleteForwarding(forwarding_name)
+        if (err != nil) {
+            printError(err)
+            t.Errorf("Test case TestForwarding: failed to delete forwarding '%s'", forwarding_name)
+            return
+        }
+    }
+
+    // Create forwarding
+    ret_forwarding, err = fw.CreateForwarding(forwarding)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestForwarding: failed to create forwarding '%s'", forwarding_name)
+        return
+    } else {
+       p_data, _ := json.Marshal(ret_forwarding)
+        fmt.Printf("Created Forwarding: %s\n", string(p_data))
+    }
+
+    ret_forwarding, err = fw.GetForwarding(forwarding_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestForwarding: failed to get created forwarding")
+        return
+    } else {
+        if( ret_forwarding.Dest != available_zone_2 ) {
+            t.Errorf("Test case TestForwarding: failed to create forwarding")
+            return
+        }
+    }
+
+    // Update forwarding
+    ret_forwarding, err = fw.UpdateForwarding(update_forwarding)
+    if (err != nil) {
+        printError(err)
+       t.Errorf("Test case TestForwarding: failed to update forwarding '%s'", forwarding_name)
+        return
+    } else {
+        p_data, _ := json.Marshal(ret_forwarding)
+        fmt.Printf("Updated Forwarding: %s\n", string(p_data))
+    }
+
+    ret_forwarding, err = fw.GetForwarding(forwarding_name)
+    if (err != nil) {
+        t.Errorf("Test case TestForwarding: failed to get updated forwarding")
+        return
+    } else {
+        if( ret_forwarding.Dest != available_zone_1 ) {
+            t.Errorf("Test case TestForwarding: failed to update forwarding")
+            return
+        }
+    }
+
+    // Delete forwarding
+    err = fw.DeleteForwarding(forwarding_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestForwarding: failed to delete forwarding '%s'", forwarding_name)
+        return
+    }
+
+    ret_forwarding, err = fw.GetForwarding(forwarding_name)
+    if (err == nil) {
+        t.Errorf("Test case TestForwarding: failed to delete forwarding")
+        return
+    }
+}
+
+// Firewall Redirect API Test
+func TestGetRedirects(t *testing.T) {
+    res, err := fw.GetRedirects()
+    if res == nil {
+        printError(err)
+        t.Errorf("Test case GetRedirects: can not get firewall redirects")
+        return
+    }
+
+    if len(res.Redirects) == 0 {
+        fmt.Printf("Test case GetRedirects: no redirect defined")
+        return
+    }
+
+    p_data, _ := json.Marshal(res)
+    fmt.Printf("%s\n", string(p_data))
+}
+
+func TestGetRedirect(t *testing.T) {
+    tcases := []struct {
+        name string
+        redirect string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "GetFoolRedirect",
+            redirect: "foo_redirect",
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := fw.GetRedirect(tcase.redirect)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestCreateFoolRedirect(t *testing.T) {
+    tcases := []struct {
+        name string
+        redirect openwrt.SdewanFirewallRedirect
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "EmptyName",
+            redirect: openwrt.SdewanFirewallRedirect{Name:" "},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "No Src for DNAT",
+            redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", Dest:available_zone_2, DestPort:"22", Proto:"tcp", Target:"DNAT"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "No SrcDIp for SNAT",
+            redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", Dest:available_zone_2, DestPort:"22", Proto:"tcp", Target:"SNAT"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "No Dest for SNAT",
+            redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", SrcDIp:"192.168.1.1", DestPort:"22", Proto:"tcp", Target:"SNAT"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid Src",
+            redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", Src:"fool_zone"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid SrcIp",
+            redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", SrcIp:"192.168.1.300"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid SrcDIp",
+            redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", SrcDIp:"192.168.1.300"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid SrcMac",
+            redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", SrcMac:"00:00"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid SrcPort",
+            redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", SrcPort:"1a"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid SrcDPort",
+            redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", SrcDPort:"1a"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid Proto",
+            redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", Proto:"fool_protocol"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid Dest",
+            redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", Dest:"fool_zone"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid DestIp",
+            redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", DestIp:"192.168.1a.1"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid DestPort",
+            redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", DestPort:"0"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid Target",
+            redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", Target:"fool_NAT"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid Family",
+            redirect: openwrt.SdewanFirewallRedirect{Name:"test_redirect", Family:"fool_family"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := fw.CreateRedirect(tcase.redirect)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestDeleteFoolRedirect(t *testing.T) {
+    tcases := []struct {
+        name string
+        redirect string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "EmptyName",
+            redirect: "",
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "FoolName",
+            redirect: "fool_name",
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        err := fw.DeleteRedirect(tcase.redirect)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestUpdateFoolRedirect(t *testing.T) {
+    tcases := []struct {
+        name string
+        redirect openwrt.SdewanFirewallRedirect
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "FoolName",
+            redirect: openwrt.SdewanFirewallRedirect{Name:"fool_name", Src:available_zone_1, SrcDPort:"22000", Dest:available_zone_2, DestPort:"22", Proto:"tcp", Target:"DNAT"},
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := fw.UpdateRedirect(tcase.redirect)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestRedirect(t *testing.T) {
+    var err error
+    var ret_redirect *openwrt.SdewanFirewallRedirect
+
+    var redirect_name = "test_redirect"
+    var redirect = openwrt.SdewanFirewallRedirect{Name:redirect_name, Src:available_zone_1, SrcDPort:"22000", Dest:available_zone_2, DestPort:"22", Proto:"tcp", Target:"DNAT"}
+    var update_redirect = openwrt.SdewanFirewallRedirect{Name:redirect_name, Src:available_zone_1, SrcDPort:"22001", Dest:available_zone_2, DestPort:"22", Proto:"tcp", Target:"DNAT"}
+
+    _, err = fw.GetRedirect(redirect_name)
+    if (err == nil) {
+        err = fw.DeleteRedirect(redirect_name)
+        if (err != nil) {
+            printError(err)
+            t.Errorf("Test case TestRedirect: failed to delete redirect '%s'", redirect_name)
+            return
+        }
+    }
+
+    // Create redirect
+    ret_redirect, err = fw.CreateRedirect(redirect)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestRedirect: failed to create redirect '%s'", redirect_name)
+        return
+    } else {
+       p_data, _ := json.Marshal(ret_redirect)
+        fmt.Printf("Created Redirect: %s\n", string(p_data))
+    }
+
+    ret_redirect, err = fw.GetRedirect(redirect_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestRedirect: failed to get created redirect")
+        return
+    } else {
+        if( ret_redirect.SrcDPort != "22000" ) {
+            t.Errorf("Test case TestRedirect: failed to create redirect")
+            return
+        }
+    }
+
+    // Update redirect
+    ret_redirect, err = fw.UpdateRedirect(update_redirect)
+    if (err != nil) {
+        printError(err)
+       t.Errorf("Test case TestRedirect: failed to update redirect '%s'", redirect_name)
+        return
+    } else {
+        p_data, _ := json.Marshal(ret_redirect)
+        fmt.Printf("Updated Redirect: %s\n", string(p_data))
+    }
+
+    ret_redirect, err = fw.GetRedirect(redirect_name)
+    if (err != nil) {
+        t.Errorf("Test case TestRedirect: failed to get updated redirect")
+        return
+    } else {
+        if( ret_redirect.SrcDPort != "22001" ) {
+            t.Errorf("Test case TestRedirect: failed to update redirect")
+            return
+        }
+    }
+
+    // Delete redirect
+    err = fw.DeleteRedirect(redirect_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestRedirect: failed to delete redirect '%s'", redirect_name)
+        return
+    }
+
+    ret_redirect, err = fw.GetRedirect(redirect_name)
+    if (err == nil) {
+        t.Errorf("Test case TestRedirect: failed to delete redirect")
+        return
+    }
+}
+
+// Firewall Rule API Test
+func TestGetRules(t *testing.T) {
+    res, err := fw.GetRules()
+    if res == nil {
+        printError(err)
+        t.Errorf("Test case GetRules: can not get firewall rules")
+        return
+    }
+
+    if len(res.Rules) == 0 {
+        fmt.Printf("Test case GetRules: no rule defined")
+        return
+    }
+
+    p_data, _ := json.Marshal(res)
+    fmt.Printf("%s\n", string(p_data))
+}
+
+func TestGetRule(t *testing.T) {
+    tcases := []struct {
+        name string
+        rule string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "GetFoolRule",
+            rule: "foo_rule",
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := fw.GetRule(tcase.rule)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestCreateFoolRule(t *testing.T) {
+    tcases := []struct {
+        name string
+        rule openwrt.SdewanFirewallRule
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "EmptyName",
+            rule: openwrt.SdewanFirewallRule{Name:" "},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "No SetMark and SetXmark for DNAT",
+            rule: openwrt.SdewanFirewallRule{Name:"test_rule", Target:"MARK"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid SRC",
+            rule: openwrt.SdewanFirewallRule{Name:"test_rule", Src:"fool_zone"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid SrcIp",
+            rule: openwrt.SdewanFirewallRule{Name:"test_rule", SrcIp:"191.11.aa.10"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Valid SrcMac",
+            rule: openwrt.SdewanFirewallRule{Name:"test_rule", SrcMac:"01:02:a0:0F:aC:EE"},
+        },
+        {
+            name: "Invalid SrcMac",
+            rule: openwrt.SdewanFirewallRule{Name:"test_rule", SrcMac:"11:rt:00:00:00:00"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid SrcPort",
+            rule: openwrt.SdewanFirewallRule{Name:"test_rule", SrcPort:"0"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid Proto",
+            rule: openwrt.SdewanFirewallRule{Name:"test_rule", Proto:"fool_proto"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid IcmpType",
+            rule: openwrt.SdewanFirewallRule{Name:"test_rule", IcmpType:[]string{"network-unreachable", "address-mask-reply", "fool_icmp_type"}},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid Dest",
+            rule: openwrt.SdewanFirewallRule{Name:"test_rule", Dest:"fool_zone"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid DestIp",
+            rule: openwrt.SdewanFirewallRule{Name:"test_rule", DestIp:"192.168.1.2.1"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid DestPort",
+            rule: openwrt.SdewanFirewallRule{Name:"test_rule", DestPort:"aa"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid Target",
+            rule: openwrt.SdewanFirewallRule{Name:"test_rule", Target:"fool_target"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Invalid Family",
+            rule: openwrt.SdewanFirewallRule{Name:"test_rule", Family:"fool_family"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := fw.CreateRule(tcase.rule)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestDeleteFoolRule(t *testing.T) {
+    tcases := []struct {
+        name string
+        rule string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "EmptyName",
+            rule: "",
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "FoolName",
+            rule: "fool_name",
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        err := fw.DeleteRule(tcase.rule)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestUpdateFoolRule(t *testing.T) {
+    tcases := []struct {
+        name string
+        rule openwrt.SdewanFirewallRule
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "FoolName",
+            rule: openwrt.SdewanFirewallRule{Name:"fool_name", Src:available_zone_1, Proto:"udp", DestPort:"68", Target:"REJECT", Family:"ipv4"},
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := fw.UpdateRule(tcase.rule)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestRule(t *testing.T) {
+    var err error
+    var ret_rule *openwrt.SdewanFirewallRule
+
+    var rule_name = "test_rule"
+    var rule = openwrt.SdewanFirewallRule{Name:rule_name, Src:available_zone_1, Proto:"udp", DestPort:"68", Target:"REJECT", Family:"ipv4"}
+    var update_rule = openwrt.SdewanFirewallRule{Name:rule_name, Src:available_zone_1, IcmpType:[]string{"host-redirect", "echo-request"}, Proto:"udp", DestPort:"68", Target:"ACCEPT", Family:"ipv4"}
+
+    _, err = fw.GetRule(rule_name)
+    if (err == nil) {
+        err = fw.DeleteRule(rule_name)
+        if (err != nil) {
+            printError(err)
+            t.Errorf("Test case TestRule: failed to delete rule '%s'", rule_name)
+            return
+        }
+    }
+
+    // Create rule
+    ret_rule, err = fw.CreateRule(rule)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestRule: failed to create rule '%s'", rule_name)
+        return
+    } else {
+       p_data, _ := json.Marshal(ret_rule)
+        fmt.Printf("Created Rule: %s\n", string(p_data))
+    }
+
+    ret_rule, err = fw.GetRule(rule_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestRule: failed to get created rule")
+        return
+    } else {
+        if( ret_rule.Target != "REJECT" ) {
+            t.Errorf("Test case TestRule: failed to create rule")
+            return
+        }
+    }
+
+    // Update rule
+    ret_rule, err = fw.UpdateRule(update_rule)
+    if (err != nil) {
+        printError(err)
+       t.Errorf("Test case TestRule: failed to update rule '%s'", rule_name)
+        return
+    } else {
+        p_data, _ := json.Marshal(ret_rule)
+        fmt.Printf("Updated Rule: %s\n", string(p_data))
+    }
+
+    ret_rule, err = fw.GetRule(rule_name)
+    if (err != nil) {
+        t.Errorf("Test case TestRule: failed to get updated rule")
+        return
+    } else {
+        if( ret_rule.Target != "ACCEPT" ) {
+            t.Errorf("Test case TestRule: failed to update rule")
+            return
+        }
+    }
+
+    // Delete rule
+    err = fw.DeleteRule(rule_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestRule: failed to delete rule '%s'", rule_name)
+        return
+    }
+
+    ret_rule, err = fw.GetRule(rule_name)
+    if (err == nil) {
+        t.Errorf("Test case TestRule: failed to delete rule")
+        return
+    }
+}
diff --git a/cnf/test/openwrtclient_ipsec_test.go b/cnf/test/openwrtclient_ipsec_test.go
new file mode 100644 (file)
index 0000000..5500e30
--- /dev/null
@@ -0,0 +1,605 @@
+package openwrt
+
+import (
+    "openwrt"
+    "reflect"
+    "testing"
+    "flag"
+    "encoding/json"
+    "fmt"
+    "os"
+)
+
+var ic openwrt.IpsecClient
+var available_proposal_1 string
+var available_proposal_2 string
+var available_site string
+
+func TestMain(m *testing.M) {
+    servIp := flag.String("ip", "10.244.0.18", "SDEWAN CNF Management IP Address")
+    flag.Parse()
+
+    var err error
+    client := openwrt.NewOpenwrtClient(*servIp, "root", "")
+    ic = openwrt.IpsecClient{client}
+    available_proposal_1 = "test_proposal1"
+    available_proposal_2 = "test_proposal2"
+    available_site = "test_default_site"
+
+    // create default proposals
+    var proposall = openwrt.SdewanIpsecProposal{Name:available_proposal_1, EncryptionAlgorithm:"aes128", HashAlgorithm:"sha256", DhGroup:"modp3072",}
+    var proposal2 = openwrt.SdewanIpsecProposal{Name:available_proposal_2, EncryptionAlgorithm:"aes256", HashAlgorithm:"sha128", DhGroup:"modp4096",}
+
+    _, err = ic.GetProposal(available_proposal_1)
+    if (err != nil) {
+        // Create proposal
+        _, err = ic.CreateProposal(proposall)
+        if (err != nil) {
+            printError(err)
+            return
+        }
+    }
+
+    _, err = ic.GetProposal(available_proposal_2)
+    if (err != nil) {
+        // Create proposal
+        _, err = ic.CreateProposal(proposal2)
+        if (err != nil) {
+            printError(err)
+            return
+        }
+    }
+
+    // create default site
+    var site = openwrt.SdewanIpsecSite{
+        Name:available_site,
+        Gateway:"10.0.1.2",
+        PreSharedKey:"test_key",
+        AuthenticationMethod:"psk",
+        LocalIdentifier:"C=CH, O=strongSwan, CN=peer",
+        RemoteIdentifier:"C=CH, O=strongSwan, CN=peerB",
+        ForceCryptoProposal:"true",
+        LocalPublicCert:"public cert\npublic cert value",
+        CryptoProposal:[]string{available_proposal_1},
+        Connections:[]openwrt.SdewanIpsecConnection{{
+                Name:available_site+"_conn",
+                Type:"tunnel",
+                Mode:"start",
+                LocalSubnet:"192.168.1.1/24",
+                LocalSourceip:"10.0.1.1",
+                RemoteSubnet:"192.168.0.1/24",
+                RemoteSourceip:"10.0.1.2",
+                CryptoProposal:[]string{available_proposal_1, available_proposal_2},
+            },
+        },
+    }
+
+    _, err = ic.GetSite(available_site)
+    if (err == nil) {
+        // Update site
+        _, err = ic.UpdateSite(site)
+        if (err != nil) {
+            printError(err)
+            return
+        }
+    } else {
+        // Create site
+        _, err = ic.CreateSite(site)
+        if (err != nil) {
+            printError(err)
+            return
+        }
+    }
+
+    var ret = m.Run()
+
+    // clean
+    ic.DeleteSite(available_site)
+    ic.DeleteProposal(available_proposal_1)
+    ic.DeleteProposal(available_proposal_2)
+
+    os.Exit(ret)
+}
+
+// Error handler
+func handleError(t *testing.T, err error, name string, expectedErr bool, errorCode int) {
+    if (err != nil) {
+        if (expectedErr) {
+            switch err.(type) {
+            case *openwrt.OpenwrtError:
+                if(errorCode != err.(*openwrt.OpenwrtError).Code) {
+                    t.Errorf("Test case '%s': expected '%d', but got '%d'", name, errorCode, err.(*openwrt.OpenwrtError).Code)
+                } else {
+                    fmt.Printf("%s\n", err.(*openwrt.OpenwrtError).Message)
+                }
+            default:
+                t.Errorf("Test case '%s': expected openwrt.OpenwrtError, but got '%s'", name, reflect.TypeOf(err).String())
+            }
+        } else {
+            t.Errorf("Test case '%s': expected success, but got '%s'", name, reflect.TypeOf(err).String())
+        }
+    } else {
+        if (expectedErr) {
+            t.Errorf("Test case '%s': expected error code '%d', but success", name, errorCode)
+        }
+    }
+}
+
+func printError(err error) {
+    switch err.(type) {
+    case *openwrt.OpenwrtError:
+        fmt.Printf("%s\n", err.(*openwrt.OpenwrtError).Message)
+    default:
+        fmt.Printf("%s\n", reflect.TypeOf(err).String())
+    }
+}
+
+// IpSec Site API Test
+func TestGetSites(t *testing.T) {
+    res, err := ic.GetSites()
+    if res == nil {
+        printError(err)
+        t.Errorf("Test case GetSites: can not get IpSec sites")
+        return
+    }
+
+    if len(res.Sites) == 0 {
+        fmt.Printf("Test case GetSites: no site found")
+        return
+    }
+
+    p_data, _ := json.Marshal(res)
+    fmt.Printf("%s\n", string(p_data))
+}
+
+func TestGetSite(t *testing.T) {
+    tcases := []struct {
+        name string
+        site string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "GetAvailableSite",
+            site: available_site,
+        },
+        {
+            name: "GetFoolSite",
+            site: "foo_site",
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := ic.GetSite(tcase.site)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestCreateFoolSite(t *testing.T) {
+    tcases := []struct {
+        name string
+        site openwrt.SdewanIpsecSite
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "EmptyName",
+            site: openwrt.SdewanIpsecSite{Name:" "},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidConnectionType",
+            site: openwrt.SdewanIpsecSite{Name:"test_site",
+                Connections:[]openwrt.SdewanIpsecConnection{{Name:"test_connection_name", Type:"fool_type",},},},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidCryptoProtocolInConnection",
+            site: openwrt.SdewanIpsecSite{Name:"test_site",
+                Connections:[]openwrt.SdewanIpsecConnection{{Name:"test_connection_name", Type:"tunnel", CryptoProposal:[]string{available_proposal_1, "fool_protocol"}},},},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "InvalidCryptoProtocolInSite",
+            site: openwrt.SdewanIpsecSite{Name:"test_site", CryptoProposal:[]string{available_proposal_1, "fool_protocol"},},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := ic.CreateSite(tcase.site)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestDeleteFoolSite(t *testing.T) {
+    tcases := []struct {
+        name string
+        site string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "EmptyName",
+            site: "",
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "FoolName",
+            site: "fool_name",
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        err := ic.DeleteSite(tcase.site)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestUpdateFoolSite(t *testing.T) {
+    tcases := []struct {
+        name string
+        site openwrt.SdewanIpsecSite
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "FoolName",
+            site: openwrt.SdewanIpsecSite{Name:"fool_name"},
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := ic.UpdateSite(tcase.site)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestSite(t *testing.T) {
+    var err error
+    var ret_site *openwrt.SdewanIpsecSite
+
+    var site_name = "test_site"
+    var conn_name1 = "test_name1"
+    var conn_name2 = "test_name2"
+    var conn_name3 = "test_name3"
+    var site = openwrt.SdewanIpsecSite{
+        Name:site_name,
+        Gateway:"10.0.1.2",
+        PreSharedKey:"test_key",
+        AuthenticationMethod:"psk",
+        LocalIdentifier:"C=CH, O=strongSwan, CN=peer",
+        RemoteIdentifier:"C=CH, O=strongSwan, CN=peerB",
+        ForceCryptoProposal:"true",
+        CryptoProposal:[]string{available_proposal_1},
+        Connections:[]openwrt.SdewanIpsecConnection{{
+                Name:conn_name1,
+                Type:"tunnel",
+                Mode:"start",
+                LocalSubnet:"192.168.1.1/24",
+                LocalSourceip:"10.0.1.1",
+                RemoteSubnet:"192.168.0.1/24",
+                RemoteSourceip:"10.0.1.2",
+                CryptoProposal:[]string{available_proposal_1, available_proposal_2},
+            },
+        },
+    }
+
+    var update_site = openwrt.SdewanIpsecSite{
+        Name:site_name,
+        Gateway:"10.0.21.2",
+        PreSharedKey:"test_key_2",
+        AuthenticationMethod:"psk",
+        LocalIdentifier:"C=CH, O=strongSwan, CN=peer",
+        RemoteIdentifier:"C=CH, O=strongSwan, CN=peerB",
+        ForceCryptoProposal:"true",
+        CryptoProposal:[]string{available_proposal_1, available_proposal_2},
+        Connections:[]openwrt.SdewanIpsecConnection{{
+                Name:conn_name1,
+                Type:"tunnel",
+                Mode:"start",
+                LocalSubnet:"192.168.21.1/24",
+                LocalSourceip:"10.0.1.1",
+                RemoteSubnet:"192.168.0.1/24",
+                RemoteSourceip:"10.0.21.2",
+                CryptoProposal:[]string{available_proposal_2},
+            },
+            {
+                Name:conn_name2,
+                Type:"transport",
+                Mode:"start",
+                LocalSubnet:"192.168.31.1/24",
+                LocalSourceip:"10.0.11.1",
+                RemoteSubnet:"192.168.10.1/24",
+                RemoteSourceip:"10.0.31.2",
+                CryptoProposal:[]string{available_proposal_1, available_proposal_2},
+            },
+            {
+                Name:conn_name3,
+                Type:"tunnel",
+                Mode:"start",
+                LocalSubnet:"192.168.41.1/24",
+                LocalSourceip:"10.0.11.1",
+                RemoteSubnet:"192.168.10.1/24",
+                RemoteSourceip:"10.0.41.2",
+                CryptoProposal:[]string{available_proposal_1},
+            },
+        },
+    }
+
+    _, err = ic.GetSite(site_name)
+    if (err == nil) {
+        err = ic.DeleteSite(site_name)
+        if (err != nil) {
+            printError(err)
+            t.Errorf("Test case TestSite: failed to delete site '%s'", site_name)
+            return
+        }
+    }
+
+    // Create site
+    ret_site, err = ic.CreateSite(site)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestSite: failed to create site '%s'", site_name)
+        return
+    } else {
+        p_data, _ := json.Marshal(ret_site)
+        fmt.Printf("Created Site: %s\n", string(p_data))
+    }
+
+    ret_site, err = ic.GetSite(site_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestSite: failed to get created site")
+        return
+    } else {
+        if( len(ret_site.Connections) != 1 || ret_site.Connections[0].LocalSubnet != "192.168.1.1/24" ) {
+            t.Errorf("Test case TestSite: failed to create site")
+            return
+        }
+    }
+
+    // Update site
+    ret_site, err = ic.UpdateSite(update_site)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestSite: failed to update site '%s'", site_name)
+        return
+    } else {
+        p_data, _ := json.Marshal(ret_site)
+        fmt.Printf("Updated Site: %s\n", string(p_data))
+    }
+
+    ret_site, err = ic.GetSite(site_name)
+    if (err != nil) {
+        t.Errorf("Test case TestSite: failed to get updated site")
+        return
+    } else {
+        if( len(ret_site.Connections) != 3 || ret_site.Connections[0].LocalSubnet != "192.168.21.1/24" ) {
+            t.Errorf("Test case TestSite: failed to update site")
+            return
+        }
+    }
+
+    // Delete site
+    err = ic.DeleteSite(site_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestSite: failed to delete site '%s'", site_name)
+        return
+    }
+
+    ret_site, err = ic.GetSite(site_name)
+    if (err == nil) {
+        t.Errorf("Test case TestSite: failed to delete site")
+        return
+    }
+}
+
+// IpSec Proposal API Test
+func TestGetProposals(t *testing.T) {
+    res, err := ic.GetProposals()
+    if res == nil {
+        printError(err)
+        t.Errorf("Test case TestGetProposals: can not get IpSec proposals")
+        return
+    }
+
+    if len(res.Proposals) == 0 {
+        fmt.Printf("Test case TestGetProposals: no proposal found")
+        return
+    }
+
+    p_data, _ := json.Marshal(res)
+    fmt.Printf("%s\n", string(p_data))
+}
+
+func TestGetProposal(t *testing.T) {
+    tcases := []struct {
+        name string
+        proposal string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "GetAvailableProposal",
+            proposal: available_proposal_1,
+        },
+        {
+            name: "GetFoolProposal",
+            proposal: "foo_proposal",
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := ic.GetProposal(tcase.proposal)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestCreateFoolProposal(t *testing.T) {
+    tcases := []struct {
+        name string
+        proposal openwrt.SdewanIpsecProposal
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "EmptyName",
+            proposal: openwrt.SdewanIpsecProposal{Name:" "},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := ic.CreateProposal(tcase.proposal)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestDeleteFoolProposal(t *testing.T) {
+    tcases := []struct {
+        name string
+        proposal string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "EmptyName",
+            proposal: "",
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "DeleteInUsedProposal",
+            proposal: available_proposal_1,
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "FoolName",
+            proposal: "fool_name",
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        err := ic.DeleteProposal(tcase.proposal)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestUpdateFoolProposal(t *testing.T) {
+    tcases := []struct {
+        name string
+        proposal openwrt.SdewanIpsecProposal
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "FoolName",
+            proposal: openwrt.SdewanIpsecProposal{Name:"fool_name"},
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := ic.UpdateProposal(tcase.proposal)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestProposal(t *testing.T) {
+    var err error
+    var ret_proposal *openwrt.SdewanIpsecProposal
+
+    var proposal_name = "test_proposal"
+    var proposal = openwrt.SdewanIpsecProposal{Name:proposal_name,EncryptionAlgorithm:"aes128", HashAlgorithm:"sha256", DhGroup:"modp3072",}
+    var update_proposal = openwrt.SdewanIpsecProposal{Name:proposal_name,EncryptionAlgorithm:"aes256", HashAlgorithm:"sha128", DhGroup:"modp3072",}
+
+    _, err = ic.GetProposal(proposal_name)
+    if (err == nil) {
+        err = ic.DeleteProposal(proposal_name)
+        if (err != nil) {
+            printError(err)
+            t.Errorf("Test case TestProposal: failed to delete proposal '%s'", proposal_name)
+            return
+        }
+    }
+
+    // Create proposal
+    ret_proposal, err = ic.CreateProposal(proposal)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestProposal: failed to create proposal '%s'", proposal_name)
+        return
+    } else {
+        p_data, _ := json.Marshal(ret_proposal)
+        fmt.Printf("Created Proposal: %s\n", string(p_data))
+    }
+
+    ret_proposal, err = ic.GetProposal(proposal_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestProposal: failed to get created proposal")
+        return
+    } else {
+        if( ret_proposal.EncryptionAlgorithm != "aes128" ) {
+            t.Errorf("Test case TestProposal: failed to create proposal")
+            return
+        }
+    }
+
+    // Update proposal
+    ret_proposal, err = ic.UpdateProposal(update_proposal)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestProposal: failed to update proposal '%s'", proposal_name)
+        return
+    } else {
+        p_data, _ := json.Marshal(ret_proposal)
+        fmt.Printf("Updated Proposal: %s\n", string(p_data))
+    }
+
+    ret_proposal, err = ic.GetProposal(proposal_name)
+    if (err != nil) {
+        t.Errorf("Test case TestProposal: failed to get updated proposal")
+        return
+    } else {
+        if( ret_proposal.EncryptionAlgorithm != "aes256" ) {
+            t.Errorf("Test case TestProposal: failed to update proposal")
+            return
+        }
+    }
+
+    // Delete proposal
+    err = ic.DeleteProposal(proposal_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestProposal: failed to delete proposal '%s'", proposal_name)
+        return
+    }
+
+    ret_proposal, err = ic.GetProposal(proposal_name)
+    if (err == nil) {
+        t.Errorf("Test case TestProposal: failed to delete proposal")
+        return
+    }
+}
diff --git a/cnf/test/openwrtclient_test.go b/cnf/test/openwrtclient_test.go
new file mode 100644 (file)
index 0000000..17299b4
--- /dev/null
@@ -0,0 +1,606 @@
+package openwrt
+
+import (
+    "openwrt"
+    "reflect"
+    "testing"
+    "flag"
+    "encoding/json"
+    "fmt"
+    "os"
+)
+
+var mwan3 openwrt.Mwan3Client
+var service openwrt.ServiceClient
+var available_policy string
+var available_interface string
+var available_interfaceb string
+
+func TestMain(m *testing.M) {
+    servIp := flag.String("ip", "10.244.0.18", "SDEWAN CNF Management IP Address")
+    flag.Parse()
+
+    client := openwrt.NewOpenwrtClient(*servIp, "root", "")
+    mwan3 = openwrt.Mwan3Client{client}
+    service = openwrt.ServiceClient{client}
+    available_policy = "balanced"
+    available_interface = "wan"
+    available_interfaceb = "wanb"
+
+    os.Exit(m.Run())
+}
+
+// Error handler
+func handleError(t *testing.T, err error, name string, expectedErr bool, errorCode int) {
+    if (err != nil) {
+        if (expectedErr) {
+            switch err.(type) {
+            case *openwrt.OpenwrtError:
+                if(errorCode != err.(*openwrt.OpenwrtError).Code) {
+                    t.Errorf("Test case '%s': expected '%d', but got '%d'", name, errorCode, err.(*openwrt.OpenwrtError).Code)
+                } else {
+                    fmt.Printf("%s\n", err.(*openwrt.OpenwrtError).Message)
+                }
+            default:
+                t.Errorf("Test case '%s': expected openwrt.OpenwrtError, but got '%s'", name, reflect.TypeOf(err).String())
+            }
+        } else {
+            t.Errorf("Test case '%s': expected success, but got '%s'", name, reflect.TypeOf(err).String())
+        }
+    } else {
+        if (expectedErr) {
+            t.Errorf("Test case '%s': expected error code '%d', but success", name, errorCode)
+        }
+    }
+}
+
+func printError(err error) {
+    switch err.(type) {
+    case *openwrt.OpenwrtError:
+        fmt.Printf("%s\n", err.(*openwrt.OpenwrtError).Message)
+    default:
+        fmt.Printf("%s\n", reflect.TypeOf(err).String())
+    }
+}
+
+// Service API Test
+func TestGetServices(t *testing.T) {
+    res, _ := service.GetAvailableServices()
+
+    if res == nil {
+        t.Errorf("Test case GetServices: no available services")
+        return
+    }
+
+    if !reflect.DeepEqual(res.Services, []string{"mwan3", "firewall", "ipsec"}) {
+        t.Errorf("Test case GetServices: error available services returned")
+    }
+}
+
+func TestExecuteService(t *testing.T) {
+    tcases := []struct {
+        name string
+        service string
+        action string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "Foo_Service",
+            service: "foo_service",
+            action: "restart",
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Foo_action",
+            service: "mwan3",
+            action: "foo_action",
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+    }
+    for _, tcase := range tcases {
+        _, err := service.ExecuteService(tcase.service, tcase.action)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+// MWAN3 Rule API Test
+func TestGetInterfaceStatus(t *testing.T) {
+    res, _ := mwan3.GetInterfaceStatus()
+    if res == nil {
+        t.Errorf("Test case GetInterfaceStatus: can not get interfaces status")
+    }
+}
+
+func TestGetRules(t *testing.T) {
+    res, _ := mwan3.GetRules()
+    if res == nil {
+        t.Errorf("Test case GetRules: can not get mwan3 rules")
+        return
+    }
+
+    if len(res.Rules) == 0 {
+        t.Errorf("Test case GetRules: no rule defined")
+        return
+    }
+
+    p_data, _ := json.Marshal(res)
+    fmt.Printf("%s\n", string(p_data))
+}
+
+func TestGetRule(t *testing.T) {
+    tcases := []struct {
+        name string
+        rule string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "GetAvailableRule",
+            rule: "default_rule",
+        },
+        {
+            name: "GetFoolRule",
+            rule: "foo_rule",
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := mwan3.GetRule(tcase.rule)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestCreateFoolRule(t *testing.T) {
+    tcases := []struct {
+        name string
+        rule openwrt.SdewanRule
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "EmptyName",
+            rule: openwrt.SdewanRule{Name:" ", Policy: available_policy},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "FoolPolicy",
+            rule: openwrt.SdewanRule{Name:" ", Policy: "fool_policy"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Wrong_src_ip",
+            rule: openwrt.SdewanRule{Name:" ", Policy: available_policy, SrcIp: "10.200.200.500"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Wrong_src_port",
+            rule: openwrt.SdewanRule{Name:" ", Policy: available_policy, SrcPort: "-1"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Wrong_dest_ip",
+            rule: openwrt.SdewanRule{Name:" ", Policy: available_policy, DestIp: "10.200"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Wrong_dest_port",
+            rule: openwrt.SdewanRule{Name:" ", Policy: available_policy, DestPort: "0"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Wrong_proto",
+            rule: openwrt.SdewanRule{Name:" ", Policy: available_policy, Proto: "fool_protocol"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Wrong_family",
+            rule: openwrt.SdewanRule{Name:" ", Policy: available_policy, Family: "fool_family"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Wrong_sticky",
+            rule: openwrt.SdewanRule{Name:" ", Policy: available_policy, Sticky: "2"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Wrong_timeout",
+            rule: openwrt.SdewanRule{Name:" ", Policy: available_policy, Timeout: "-1"},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := mwan3.CreateRule(tcase.rule)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestDeleteFoolRule(t *testing.T) {
+    tcases := []struct {
+        name string
+        rule string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "EmptyName",
+            rule: "",
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "FoolName",
+            rule: "fool_name",
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        err := mwan3.DeleteRule(tcase.rule)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestUpdateFoolRule(t *testing.T) {
+    tcases := []struct {
+        name string
+        rule openwrt.SdewanRule
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "FoolName",
+            rule: openwrt.SdewanRule{Name:"fool_name", Policy: available_policy},
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := mwan3.UpdateRule(tcase.rule)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestRule(t *testing.T) {
+    var err error
+    var ret_rule *openwrt.SdewanRule
+
+    var rule_name = "test_rule"
+    var rule = openwrt.SdewanRule{Name:rule_name, Policy: available_policy, SrcIp: "10.10.10.10/24", SrcPort: "80"}
+    var update_rule = openwrt.SdewanRule{Name:rule_name, Policy: available_policy, SrcIp: "100.100.100.100/24", SrcPort: "8000", DestIp: "172.172.172.172/16", DestPort: "8080"}
+
+    _, err = mwan3.GetRule(rule_name)
+    if (err == nil) {
+        err = mwan3.DeleteRule(rule_name)
+        if (err != nil) {
+            printError(err)
+            t.Errorf("Test case TestRule: failed to delete rule '%s'", rule_name)
+            return
+        }
+    }
+
+    // Create rule
+    ret_rule, err = mwan3.CreateRule(rule)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestRule: failed to create rule '%s'", rule_name)
+        return
+    } else {
+        p_data, _ := json.Marshal(ret_rule)
+        fmt.Printf("Created Rule: %s\n", string(p_data))
+    }
+
+    ret_rule, err = mwan3.GetRule(rule_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestRule: failed to get created rule")
+        return
+    } else {
+        if( ret_rule.SrcPort != "80" ) {
+            t.Errorf("Test case TestRule: failed to create rule")
+            return
+        }
+    }
+
+    // Update rule
+    ret_rule, err = mwan3.UpdateRule(update_rule)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestRule: failed to update rule '%s'", rule_name)
+        return
+    } else {
+        p_data, _ := json.Marshal(ret_rule)
+        fmt.Printf("Updated Rule: %s\n", string(p_data))
+    }
+
+    ret_rule, err = mwan3.GetRule(rule_name)
+    if (err != nil) {
+        t.Errorf("Test case TestRule: failed to get updated rule")
+        return
+    } else {
+        if( ret_rule.SrcIp != "100.100.100.100/24" ) {
+            t.Errorf("Test case TestRule: failed to update rule")
+            return
+        }
+    }
+
+    // Delete rule
+    err = mwan3.DeleteRule(rule_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestRule: failed to delete rule '%s'", rule_name)
+        return
+    }
+
+    ret_rule, err = mwan3.GetRule(rule_name)
+    if (err == nil) {
+        t.Errorf("Test case TestRule: failed to delete rule")
+        return
+    }
+}
+
+// MWAN3 Policy API Tests
+func TestGetPolicies(t *testing.T) {
+    res, _ := mwan3.GetPolicies()
+    if res == nil {
+        t.Errorf("Test case GetPolicies: can not get mwan3 policies")
+        return
+    }
+
+    if len(res.Policies) == 0 {
+        t.Errorf("Test case GetPolicies: no policy defined")
+        return
+    }
+
+    p_data, _ := json.Marshal(res)
+    fmt.Printf("%s\n", string(p_data))
+}
+
+func TestGetPolicy(t *testing.T) {
+    tcases := []struct {
+        name string
+        policy string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "GetAvailablePolicy",
+            policy: available_policy,
+        },
+        {
+            name: "GetFoolPolicy",
+            policy: "foo_policy",
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := mwan3.GetPolicy(tcase.policy)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestCreateFoolPolicy(t *testing.T) {
+    tcases := []struct {
+        name string
+        policy openwrt.SdewanPolicy
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "EmptyName",
+            policy: openwrt.SdewanPolicy{Name:" ", Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"1", Weight:"2"}}},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "EmptyMember",
+            policy: openwrt.SdewanPolicy{Name:"policy1", Members:[]openwrt.SdewanMember{}},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "Policy-Conflict",
+            policy: openwrt.SdewanPolicy{Name:"wan_only", Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"1", Weight:"2"}}},
+            expectedErr: true,
+            expectedErrCode: 409,
+        },
+        {
+            name: "WrongMember-interface",
+            policy: openwrt.SdewanPolicy{Name:"policy1", Members:[]openwrt.SdewanMember{{Interface:""}}},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "WrongMember-interface-name",
+            policy: openwrt.SdewanPolicy{Name:"policy1", Members:[]openwrt.SdewanMember{{Interface:"fool-name", Metric:"1", Weight:"2"}}},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "WrongMember-duplicate-interface",
+            policy: openwrt.SdewanPolicy{Name:"policy1", Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"1", Weight:"2"}, {Interface:available_interface, Metric:"2", Weight:"3"}}},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "WrongMember-Metric",
+            policy: openwrt.SdewanPolicy{Name:"policy1", Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:""}}},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "WrongMember-Weight",
+            policy: openwrt.SdewanPolicy{Name:"policy1", Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"2"}}},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "WrongMember-Metric",
+            policy: openwrt.SdewanPolicy{Name:"policy1", Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"-1", Weight:"2"}}},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "WrongMember-Weight",
+            policy: openwrt.SdewanPolicy{Name:"policy1", Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"2", Weight:"0"}}},
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := mwan3.CreatePolicy(tcase.policy)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestDeleteFoolPolicy(t *testing.T) {
+    tcases := []struct {
+        name string
+        policy string
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "EmptyName",
+            policy: "",
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+        {
+            name: "FoolName",
+            policy: "fool_name",
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+        {
+            name: "Delete-InUsePolicy",
+            policy: available_policy,
+            expectedErr: true,
+            expectedErrCode: 400,
+        },
+    }
+
+    for _, tcase := range tcases {
+        err := mwan3.DeletePolicy(tcase.policy)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestUpdateFoolPolicy(t *testing.T) {
+    tcases := []struct {
+        name string
+        policy openwrt.SdewanPolicy
+        expectedErr bool
+        expectedErrCode int
+    }{
+        {
+            name: "FoolName",
+            policy: openwrt.SdewanPolicy{Name:"fool_name", Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"1", Weight:"2"}}},
+            expectedErr: true,
+            expectedErrCode: 404,
+        },
+    }
+
+    for _, tcase := range tcases {
+        _, err := mwan3.UpdatePolicy(tcase.policy)
+        handleError(t, err, tcase.name, tcase.expectedErr, tcase.expectedErrCode)
+    }
+}
+
+func TestPolicy(t *testing.T) {
+    var err error
+    var ret_policy *openwrt.SdewanPolicy
+
+    var policy_name = "test_policy"
+    var policy = openwrt.SdewanPolicy{Name:policy_name, Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"1", Weight:"2"}}}
+    var update_policy = openwrt.SdewanPolicy{Name:policy_name, Members:[]openwrt.SdewanMember{{Interface:available_interface, Metric:"2", Weight:"3"}, {Interface:available_interfaceb, Metric:"2", Weight:"3"}}}
+
+    _, err = mwan3.GetPolicy(policy_name)
+    if (err == nil) {
+        err = mwan3.DeletePolicy(policy_name)
+        if (err != nil) {
+            printError(err)
+            t.Errorf("Test case TestPolicy: failed to delete policy '%s'", policy_name)
+            return
+        }
+    }
+
+    // Create policy
+    ret_policy, err = mwan3.CreatePolicy(policy)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestPolicy: failed to create policy '%s'", policy_name)
+        return
+    } else {
+        p_data, _ := json.Marshal(ret_policy)
+        fmt.Printf("Created Policy: %s\n", string(p_data))
+    }
+
+    ret_policy, err = mwan3.GetPolicy(policy_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestPolicy: failed to get created policy")
+        return
+    } else {
+        if( len(ret_policy.Members) != 1 || ret_policy.Members[0].Metric != "1" ) {
+            t.Errorf("Test case TestPolicy: failed to create policy")
+            return
+        }
+    }
+
+    // Update policy
+    ret_policy, err = mwan3.UpdatePolicy(update_policy)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestPolicy: failed to update policy '%s'", policy_name)
+        return
+    } else {
+        p_data, _ := json.Marshal(ret_policy)
+        fmt.Printf("Updated Policy: %s\n", string(p_data))
+    }
+
+    ret_policy, err = mwan3.GetPolicy(policy_name)
+    if (err != nil) {
+        t.Errorf("Test case TestPolicy: failed to get updated policy")
+        return
+    } else {
+        if( len(ret_policy.Members) != 2 || ret_policy.Members[1].Metric != "2" ) {
+            t.Errorf("Test case TestPolicy: failed to update policy")
+            return
+        }
+    }
+
+    // Delete policy
+    err = mwan3.DeletePolicy(policy_name)
+    if (err != nil) {
+        printError(err)
+        t.Errorf("Test case TestPolicy: failed to delete policy '%s'", policy_name)
+        return
+    }
+
+    ret_policy, err = mwan3.GetPolicy(policy_name)
+    if (err == nil) {
+        t.Errorf("Test case TestPolicy: failed to delete policy")
+        return
+    }
+}
diff --git a/cnf/test/test.go b/cnf/test/test.go
new file mode 100644 (file)
index 0000000..c353c16
--- /dev/null
@@ -0,0 +1,163 @@
+package main
+
+import (
+    "openwrt"
+    "fmt"
+    "encoding/json"
+    "strconv"
+)
+
+func testGetServices(serv *openwrt.ServiceClient) {
+    res, err := serv.GetAvailableServices()
+    if(err != nil) {
+        switch err.(type) {
+            case *openwrt.OpenwrtError:
+                fmt.Printf("Openwrt Error: %s\n", err.Error())
+                fmt.Printf("Openwrt Error: %d\n", err.(*openwrt.OpenwrtError).Code)
+            default:
+                fmt.Printf("%s\n", err.Error())
+        }
+    } else {
+        servs, err := json.Marshal(res)
+        if(err != nil) {
+            fmt.Printf("%s\n", err.Error())
+        } else {
+            fmt.Printf("%s\n", string(servs))
+        }
+    }
+}
+
+func testPutService(serv *openwrt.ServiceClient) {
+    res, err := serv.ExecuteService("mwan3", "start")
+    if(err != nil) {
+        switch err.(type) {
+            case *openwrt.OpenwrtError:
+                fmt.Printf("Openwrt Error: %s\n", err.Error())
+                fmt.Printf("Openwrt Error: %d\n", err.(*openwrt.OpenwrtError).Code)
+            default:
+                fmt.Printf("%s\n", err.Error())
+        }
+    } else {
+        fmt.Printf("Result: %s\n", strconv.FormatBool(res))
+    }
+}
+
+func testGetInterfaceStatus(mwan3 *openwrt.Mwan3Client) {
+    is, err := mwan3.GetInterfaceStatus()
+    if(err != nil) {
+        switch err.(type) {
+            case *openwrt.OpenwrtError:
+                fmt.Printf("Openwrt Error: %s\n", err.Error())
+                fmt.Printf("Openwrt Error: %d\n", err.(*openwrt.OpenwrtError).Code)
+            default:
+                fmt.Printf("%s\n", err.Error())
+        }
+    } else {
+        fmt.Printf("Wan interface status: %s\n", is.Interfaces["wan"].Status)
+        is_data, err := json.Marshal(is)
+        if(err != nil) {
+            fmt.Printf("%s\n", err.Error())
+        } else {
+            fmt.Printf("%s\n", string(is_data))
+        }
+    }
+
+    is = nil
+}
+
+func testGetPolicies(mwan3 *openwrt.Mwan3Client) {
+    res, err := mwan3.GetPolicies()
+    if(err != nil) {
+        switch err.(type) {
+            case *openwrt.OpenwrtError:
+                fmt.Printf("Openwrt Error: %s\n", err.Error())
+                fmt.Printf("Openwrt Error: %d\n", err.(*openwrt.OpenwrtError).Code)
+            default:
+                fmt.Printf("%s\n", err.Error())
+        }
+    } else {
+        p_data, err := json.Marshal(res)
+        if(err != nil) {
+            fmt.Printf("%s\n", err.Error())
+        } else {
+            fmt.Printf("%s\n", string(p_data))
+        }
+    }
+}
+
+func testGetPolicy(mwan3 *openwrt.Mwan3Client, policy string) {
+    res, err := mwan3.GetPolicy(policy)
+    if(err != nil) {
+        switch err.(type) {
+            case *openwrt.OpenwrtError:
+                fmt.Printf("Openwrt Error: %s\n", err.Error())
+                fmt.Printf("Openwrt Error: %d\n", err.(*openwrt.OpenwrtError).Code)
+            default:
+                fmt.Printf("%s\n", err.Error())
+        }
+    } else {
+        p_data, err := json.Marshal(res)
+        if(err != nil) {
+            fmt.Printf("%s\n", err.Error())
+        } else {
+            fmt.Printf("%s\n", string(p_data))
+        }
+    }
+}
+
+func testGetRules(mwan3 *openwrt.Mwan3Client) {
+    res, err := mwan3.GetRules()
+    if(err != nil) {
+        switch err.(type) {
+            case *openwrt.OpenwrtError:
+                fmt.Printf("Openwrt Error: %s\n", err.Error())
+                fmt.Printf("Openwrt Error: %d\n", err.(*openwrt.OpenwrtError).Code)
+            default:
+                fmt.Printf("%s\n", err.Error())
+        }
+    } else {
+        p_data, err := json.Marshal(res)
+        if(err != nil) {
+            fmt.Printf("%s\n", err.Error())
+        } else {
+            fmt.Printf("%s\n", string(p_data))
+        }
+    }
+}
+
+func testGetRule(mwan3 *openwrt.Mwan3Client, rule string) {
+    res, err := mwan3.GetRule(rule)
+    if(err != nil) {
+        switch err.(type) {
+            case *openwrt.OpenwrtError:
+                fmt.Printf("Openwrt Error: %s\n", err.Error())
+                fmt.Printf("Openwrt Error: %d\n", err.(*openwrt.OpenwrtError).Code)
+            default:
+                fmt.Printf("%s\n", err.Error())
+        }
+    } else {
+        p_data, err := json.Marshal(res)
+        if(err != nil) {
+            fmt.Printf("%s\n", err.Error())
+        } else {
+            fmt.Printf("%s\n", string(p_data))
+        }
+    }
+}
+
+func main() {
+    client := openwrt.NewOpenwrtClient("10.244.0.18", "root", "")
+    mwan3 := openwrt.Mwan3Client{OpenwrtClient: client}
+    service := openwrt.ServiceClient{client}
+
+    testGetServices(&service)
+
+    testGetInterfaceStatus(&mwan3)
+    testGetPolicies(&mwan3)
+    testGetPolicy(&mwan3, "balanced")
+    testGetPolicy(&mwan3, "foo_policy")
+
+    testGetRules(&mwan3)
+    testGetRule(&mwan3, "https")
+    testGetRule(&mwan3, "foo_rule")
+}
diff --git a/openwrt/firewall.go b/openwrt/firewall.go
new file mode 100644 (file)
index 0000000..58a36e1
--- /dev/null
@@ -0,0 +1,437 @@
+package openwrt
+
+import (
+    "encoding/json"
+)
+
+const (
+    firewallBaseURL = "sdewan/firewall/v1/"
+)
+
+type FirewallClient struct {
+    OpenwrtClient *openwrtClient
+}
+
+// Firewall Zones
+type SdewanFirewallZone struct {
+    Name string `json:"name"`
+    Network []string `json:"network"`
+    Masq string `json:"masq"`
+    MasqSrc []string `json:"masq_src"`
+    MasqDest []string `json:"masq_dest"`
+    MasqAllowInvalid string `json:"masq_allow_invalid"`
+    MtuFix string `json:"mtu_fix"`
+    Input string `json:"input"`
+    Forward string `json:"forward"`
+    Output string `json:"output"`
+    Family string `json:"family"`
+    Subnet []string `json:"subnet"`
+    ExtraSrc string `json:"extra_src"`
+    ExtraDest string `json:"etra_dest"`
+}
+
+type SdewanFirewallZones struct {
+    Zones []SdewanFirewallZone `json:"zones"`
+}
+
+// Firewall Forwarding
+type SdewanFirewallForwarding struct {
+    Name string `json:"name"`
+    Src string `json:"src"`
+    Dest string `json:"dest"`
+    Family string `json:"family"`
+}
+
+type SdewanFirewallForwardings struct {
+    Forwardings []SdewanFirewallForwarding `json:"forwardings"`
+}
+
+// Firewall Rule
+type SdewanFirewallRule struct {
+    Name string `json:"name"`
+    Src string `json:"src"`
+    SrcIp string `json:"src_ip"`
+    SrcMac string `json:"src_mac"`
+    SrcPort string `json:"src_port"`
+    Proto string `json:"proto"`
+    IcmpType []string `json:"icmp_type"`
+    Dest string `json:"dest"`
+    DestIp string `json:"dest_ip"`
+    DestPort string `json:"dest_port"`
+    Mark string `json:"mark"`
+    Target string `json:"target"`
+    SetMark string `json:"set_mark"`
+    SetXmark string `json:"set_xmark"`
+    Family string `json:"family"`
+    Extra string `json:"extra"`
+}
+
+type SdewanFirewallRules struct {
+    Rules []SdewanFirewallRule `json:"rules"`
+}
+
+// Firewall Redirect
+type SdewanFirewallRedirect struct {
+    Name string `json:"name"`
+    Src string `json:"src"`
+    SrcIp string `json:"src_ip"`
+    SrcDIp string `json:"src_dip"`
+    SrcMac string `json:"src_mac"`
+    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"`
+    Mark string `json:"mark"`
+    Target string `json:"target"`
+    Family string `json:"family"`
+}
+
+type SdewanFirewallRedirects struct {
+    Redirects []SdewanFirewallRedirect `json:"redirects"`
+}
+
+// Zone APIs
+// get zones
+func (f *FirewallClient) GetZones() (*SdewanFirewallZones, error) {
+    var response string
+    var err error
+    response, err = f.OpenwrtClient.Get(firewallBaseURL + "zones")
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanFirewallZones SdewanFirewallZones
+    err = json.Unmarshal([]byte(response), &sdewanFirewallZones)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanFirewallZones, nil
+}
+
+// get zone
+func (m *FirewallClient) GetZone(zone string) (*SdewanFirewallZone, error) {
+    var response string
+    var err error
+    response, err = m.OpenwrtClient.Get(firewallBaseURL + "zone/" + zone)
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanFirewallZone SdewanFirewallZone
+    err = json.Unmarshal([]byte(response), &sdewanFirewallZone)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanFirewallZone, nil
+}
+
+// create zone
+func (m *FirewallClient) CreateZone(zone SdewanFirewallZone) (*SdewanFirewallZone, error) {
+    var response string
+    var err error
+    zone_obj, _ := json.Marshal(zone)
+    response, err = m.OpenwrtClient.Post(firewallBaseURL + "zone", string(zone_obj))
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanFirewallZone SdewanFirewallZone
+    err = json.Unmarshal([]byte(response), &sdewanFirewallZone)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanFirewallZone, nil
+}
+
+// delete zone
+func (m *FirewallClient) DeleteZone(zone_name string) (error) {
+    _, err := m.OpenwrtClient.Delete(firewallBaseURL + "zone/" + zone_name)
+    if (err != nil) {
+        return err
+    }
+
+    return nil
+}
+
+// update zone
+func (m *FirewallClient) UpdateZone(zone SdewanFirewallZone) (*SdewanFirewallZone, error) {
+    var response string
+    var err error
+    zone_obj, _ := json.Marshal(zone)
+    zone_name := zone.Name
+    response, err = m.OpenwrtClient.Put(firewallBaseURL + "zone/" + zone_name, string(zone_obj))
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanFirewallZone SdewanFirewallZone
+    err = json.Unmarshal([]byte(response), &sdewanFirewallZone)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanFirewallZone, nil
+}
+
+// Rule APIs
+// get rules
+func (f *FirewallClient) GetRules() (*SdewanFirewallRules, error) {
+    var response string
+    var err error
+    response, err = f.OpenwrtClient.Get(firewallBaseURL + "rules")
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanFirewallRules SdewanFirewallRules
+    err = json.Unmarshal([]byte(response), &sdewanFirewallRules)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanFirewallRules, nil
+}
+
+// get rule
+func (m *FirewallClient) GetRule(rule string) (*SdewanFirewallRule, error) {
+    var response string
+    var err error
+    response, err = m.OpenwrtClient.Get(firewallBaseURL + "rule/" + rule)
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanFirewallRule SdewanFirewallRule
+    err = json.Unmarshal([]byte(response), &sdewanFirewallRule)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanFirewallRule, nil
+}
+
+// create rule
+func (m *FirewallClient) CreateRule(rule SdewanFirewallRule) (*SdewanFirewallRule, error) {
+    var response string
+    var err error
+    rule_obj, _ := json.Marshal(rule)
+    response, err = m.OpenwrtClient.Post(firewallBaseURL + "rule", string(rule_obj))
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanFirewallRule SdewanFirewallRule
+    err = json.Unmarshal([]byte(response), &sdewanFirewallRule)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanFirewallRule, nil
+}
+
+// delete rule
+func (m *FirewallClient) DeleteRule(rule_name string) (error) {
+    _, err := m.OpenwrtClient.Delete(firewallBaseURL + "rule/" + rule_name)
+    if (err != nil) {
+        return err
+    }
+
+    return nil
+}
+
+// update rule
+func (m *FirewallClient) UpdateRule(rule SdewanFirewallRule) (*SdewanFirewallRule, error) {
+    var response string
+    var err error
+    rule_obj, _ := json.Marshal(rule)
+    rule_name := rule.Name
+    response, err = m.OpenwrtClient.Put(firewallBaseURL + "rule/" + rule_name, string(rule_obj))
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanFirewallRule SdewanFirewallRule
+    err = json.Unmarshal([]byte(response), &sdewanFirewallRule)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanFirewallRule, nil
+}
+
+// Forwarding APIs
+// get forwardings
+func (f *FirewallClient) GetForwardings() (*SdewanFirewallForwardings, error) {
+    var response string
+    var err error
+    response, err = f.OpenwrtClient.Get(firewallBaseURL + "forwardings")
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanFirewallForwardings SdewanFirewallForwardings
+    err = json.Unmarshal([]byte(response), &sdewanFirewallForwardings)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanFirewallForwardings, nil
+}
+
+// get forwarding
+func (m *FirewallClient) GetForwarding(forwarding string) (*SdewanFirewallForwarding, error) {
+    var response string
+    var err error
+    response, err = m.OpenwrtClient.Get(firewallBaseURL + "forwarding/" + forwarding)
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanFirewallForwarding SdewanFirewallForwarding
+    err = json.Unmarshal([]byte(response), &sdewanFirewallForwarding)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanFirewallForwarding, nil
+}
+
+// create forwarding
+func (m *FirewallClient) CreateForwarding(forwarding SdewanFirewallForwarding) (*SdewanFirewallForwarding, error) {
+    var response string
+    var err error
+    forwarding_obj, _ := json.Marshal(forwarding)
+    response, err = m.OpenwrtClient.Post(firewallBaseURL + "forwarding", string(forwarding_obj))
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanFirewallForwarding SdewanFirewallForwarding
+    err = json.Unmarshal([]byte(response), &sdewanFirewallForwarding)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanFirewallForwarding, nil
+}
+
+// delete forwarding
+func (m *FirewallClient) DeleteForwarding(forwarding_name string) (error) {
+    _, err := m.OpenwrtClient.Delete(firewallBaseURL + "forwarding/" + forwarding_name)
+    if (err != nil) {
+        return err
+    }
+
+    return nil
+}
+
+// update forwarding
+func (m *FirewallClient) UpdateForwarding(forwarding SdewanFirewallForwarding) (*SdewanFirewallForwarding, error) {
+    var response string
+    var err error
+    forwarding_obj, _ := json.Marshal(forwarding)
+    forwarding_name := forwarding.Name
+    response, err = m.OpenwrtClient.Put(firewallBaseURL + "forwarding/" + forwarding_name, string(forwarding_obj))
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanFirewallForwarding SdewanFirewallForwarding
+    err = json.Unmarshal([]byte(response), &sdewanFirewallForwarding)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanFirewallForwarding, nil
+}
+
+// Redirect APIs
+// get redirects
+func (f *FirewallClient) GetRedirects() (*SdewanFirewallRedirects, error) {
+    var response string
+    var err error
+    response, err = f.OpenwrtClient.Get(firewallBaseURL + "redirects")
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanFirewallRedirects SdewanFirewallRedirects
+    err = json.Unmarshal([]byte(response), &sdewanFirewallRedirects)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanFirewallRedirects, nil
+}
+
+// get redirect
+func (m *FirewallClient) GetRedirect(redirect string) (*SdewanFirewallRedirect, error) {
+    var response string
+    var err error
+    response, err = m.OpenwrtClient.Get(firewallBaseURL + "redirect/" + redirect)
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanFirewallRedirect SdewanFirewallRedirect
+    err = json.Unmarshal([]byte(response), &sdewanFirewallRedirect)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanFirewallRedirect, nil
+}
+
+// create redirect
+func (m *FirewallClient) CreateRedirect(redirect SdewanFirewallRedirect) (*SdewanFirewallRedirect, error) {
+    var response string
+    var err error
+    redirect_obj, _ := json.Marshal(redirect)
+    response, err = m.OpenwrtClient.Post(firewallBaseURL + "redirect", string(redirect_obj))
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanFirewallRedirect SdewanFirewallRedirect
+    err = json.Unmarshal([]byte(response), &sdewanFirewallRedirect)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanFirewallRedirect, nil
+}
+
+// delete redirect
+func (m *FirewallClient) DeleteRedirect(redirect_name string) (error) {
+    _, err := m.OpenwrtClient.Delete(firewallBaseURL + "redirect/" + redirect_name)
+    if (err != nil) {
+        return err
+    }
+
+    return nil
+}
+
+// update redirect
+func (m *FirewallClient) UpdateRedirect(redirect SdewanFirewallRedirect) (*SdewanFirewallRedirect, error) {
+    var response string
+    var err error
+    redirect_obj, _ := json.Marshal(redirect)
+    redirect_name := redirect.Name
+    response, err = m.OpenwrtClient.Put(firewallBaseURL + "redirect/" + redirect_name, string(redirect_obj))
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanFirewallRedirect SdewanFirewallRedirect
+    err = json.Unmarshal([]byte(response), &sdewanFirewallRedirect)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanFirewallRedirect, nil
+}
diff --git a/openwrt/ipsec.go b/openwrt/ipsec.go
new file mode 100644 (file)
index 0000000..7a1eb34
--- /dev/null
@@ -0,0 +1,233 @@
+package openwrt
+
+import (
+    "encoding/json"
+)
+
+const (
+    ipsecBaseURL = "sdewan/ipsec/v1/"
+)
+
+type IpsecClient struct {
+    OpenwrtClient *openwrtClient
+}
+
+// Proposals
+type SdewanIpsecProposal struct {
+    Name string `json:"name"`
+    EncryptionAlgorithm string `json:"encryption_algorithm"`
+    HashAlgorithm string `json:"hash_algorithm"`
+    DhGroup string `json:"dh_group"`
+}
+
+type SdewanIpsecProposals struct {
+    Proposals []SdewanIpsecProposal `json:"proposals"`
+}
+
+// Sites
+type SdewanIpsecConnection struct {
+    Name string `json:"name"`
+    Type string `json:"type"`
+    Mode string `json:"mode"`
+    LocalSubnet string `json:"local_subnet"`
+    LocalNat string `json:"local_nat"`
+    LocalSourceip string `json:"local_sourceip"`
+    LocalUpdown string `json:"local_updown"`
+    LocalFirewall string `json:"local_firewall"`
+    RemoteSubnet string `json:"remote_subnet"`
+    RemoteSourceip string `json:"remote_sourceip"`
+    RemoteUpdown string `json:"remote_updown"`
+    RemoteFirewall string `json:"remote_firewall"`
+    CryptoProposal []string `json:"crypto_proposal"`
+}
+
+type SdewanIpsecSite struct {
+    Name string `json:"name"`
+    Gateway string `json:"gateway"`
+    PreSharedKey string `json:"pre_shared_key"`
+    AuthenticationMethod string `json:"authentication_method"`
+    LocalIdentifier string `json:"local_identifier"`
+    RemoteIdentifier string `json:"remote_identifier"`
+    CryptoProposal []string `json:"crypto_proposal"`
+    ForceCryptoProposal string `json:"force_crypto_proposal"`
+    LocalPublicCert string `json:"local_public_cert"`
+    LocalPrivateCert string `json:"local_private_cert"`
+    SharedCa string `json:"shared_ca"`
+    Connections []SdewanIpsecConnection `json:"connections"`
+}
+
+type SdewanIpsecSites struct {
+    Sites []SdewanIpsecSite `json:"sites"`
+}
+
+// Proposal APIs
+// get proposals
+func (f *IpsecClient) GetProposals() (*SdewanIpsecProposals, error) {
+    var response string
+    var err error
+    response, err = f.OpenwrtClient.Get(ipsecBaseURL + "proposals")
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanIpsecProposals SdewanIpsecProposals
+    err = json.Unmarshal([]byte(response), &sdewanIpsecProposals)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanIpsecProposals, nil
+}
+
+// get proposal
+func (m *IpsecClient) GetProposal(proposal string) (*SdewanIpsecProposal, error) {
+    var response string
+    var err error
+    response, err = m.OpenwrtClient.Get(ipsecBaseURL + "proposal/" + proposal)
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanIpsecProposal SdewanIpsecProposal
+    err = json.Unmarshal([]byte(response), &sdewanIpsecProposal)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanIpsecProposal, nil
+}
+
+// create proposal
+func (m *IpsecClient) CreateProposal(proposal SdewanIpsecProposal) (*SdewanIpsecProposal, error) {
+    var response string
+    var err error
+    proposal_obj, _ := json.Marshal(proposal)
+    response, err = m.OpenwrtClient.Post(ipsecBaseURL + "proposal", string(proposal_obj))
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanIpsecProposal SdewanIpsecProposal
+    err = json.Unmarshal([]byte(response), &sdewanIpsecProposal)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanIpsecProposal, nil
+}
+
+// delete proposal
+func (m *IpsecClient) DeleteProposal(proposal_name string) (error) {
+    _, err := m.OpenwrtClient.Delete(ipsecBaseURL + "proposal/" + proposal_name)
+    if (err != nil) {
+        return err
+    }
+
+    return nil
+}
+
+// update proposal
+func (m *IpsecClient) UpdateProposal(proposal SdewanIpsecProposal) (*SdewanIpsecProposal, error) {
+    var response string
+    var err error
+    proposal_obj, _ := json.Marshal(proposal)
+    proposal_name := proposal.Name
+    response, err = m.OpenwrtClient.Put(ipsecBaseURL + "proposal/" + proposal_name, string(proposal_obj))
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanIpsecProposal SdewanIpsecProposal
+    err = json.Unmarshal([]byte(response), &sdewanIpsecProposal)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanIpsecProposal, nil
+}
+
+// Site APIs
+// get sites
+func (f *IpsecClient) GetSites() (*SdewanIpsecSites, error) {
+    var response string
+    var err error
+    response, err = f.OpenwrtClient.Get(ipsecBaseURL + "sites")
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanIpsecSites SdewanIpsecSites
+    err = json.Unmarshal([]byte(response), &sdewanIpsecSites)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanIpsecSites, nil
+}
+
+// get site
+func (m *IpsecClient) GetSite(site string) (*SdewanIpsecSite, error) {
+    var response string
+    var err error
+    response, err = m.OpenwrtClient.Get(ipsecBaseURL + "site/" + site)
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanIpsecSite SdewanIpsecSite
+    err = json.Unmarshal([]byte(response), &sdewanIpsecSite)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanIpsecSite, nil
+}
+
+// create site
+func (m *IpsecClient) CreateSite(site SdewanIpsecSite) (*SdewanIpsecSite, error) {
+    var response string
+    var err error
+    site_obj, _ := json.Marshal(site)
+    response, err = m.OpenwrtClient.Post(ipsecBaseURL + "site", string(site_obj))
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanIpsecSite SdewanIpsecSite
+    err = json.Unmarshal([]byte(response), &sdewanIpsecSite)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanIpsecSite, nil
+}
+
+// delete site
+func (m *IpsecClient) DeleteSite(site_name string) (error) {
+    _, err := m.OpenwrtClient.Delete(ipsecBaseURL + "site/" + site_name)
+    if (err != nil) {
+        return err
+    }
+
+    return nil
+}
+
+// update site
+func (m *IpsecClient) UpdateSite(site SdewanIpsecSite) (*SdewanIpsecSite, error) {
+    var response string
+    var err error
+    site_obj, _ := json.Marshal(site)
+    site_name := site.Name
+    response, err = m.OpenwrtClient.Put(ipsecBaseURL + "site/" + site_name, string(site_obj))
+    if (err != nil) {
+        return nil, err
+    }
+
+    var sdewanIpsecSite SdewanIpsecSite
+    err = json.Unmarshal([]byte(response), &sdewanIpsecSite)
+    if (err != nil) {
+        return nil, err
+    }
+
+    return &sdewanIpsecSite, nil
+}
index 04e35aa..d1f9295 100644 (file)
--- a/readme.md
+++ b/readme.md
@@ -12,7 +12,7 @@ develop the firewall and the ipsec functions. Mwan3Conf is validated by k8s
 api admission webhook.
 
 For each created Sdewan instance, the controller creates a pod, a configmap
-and a service for the instance. The pod runs openswrt which provides network
+and a service for the instance. The pod runs openwrt which provides network
 services, i.e. sdwan, firewall, ipsec etc.
 
 The configmap stores the network interface information and the entrypoint.sh.
@@ -51,6 +51,14 @@ The simple installation steps:
 1. kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v0.11.0/cert-manager.yaml
 2. kubectl apply -f sdewan-deploy.yaml
 
+## Create Sdewan CNF docker image
+1. update build/set_proxy file with required proxy for docker build
+2. execute below commands to generate Sdewan CNF docker image which tagged with 'openwrt-1806-mwan3'
+```
+cd build
+sudo bash build_image.sh
+```
+
 ## References
 
 - https://book.kubebuilder.io/