From c8563768b83241c7c8e1d666e57fffeeab729103 Mon Sep 17 00:00:00 2001 From: Leonardo Di Giovanna Date: Thu, 17 Feb 2022 16:23:43 +0100 Subject: [PATCH 1/3] Increase pcn-router ARP table dimension The pcn-router ARP table dimension was not sufficient to handle a number of client pods greater than 32 in the Polykube network provider use case. Update the dimension of the ARP table to a reasonably high value. Signed-off-by: Leonardo Di Giovanna --- src/services/pcn-router/src/Router_dp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/pcn-router/src/Router_dp.c b/src/services/pcn-router/src/Router_dp.c index 150ce5834..68f5e5c9e 100644 --- a/src/services/pcn-router/src/Router_dp.c +++ b/src/services/pcn-router/src/Router_dp.c @@ -30,7 +30,7 @@ #define CHECK_MAC_DST #define ROUTING_TABLE_DIM 256 #define ROUTER_PORT_N 32 -#define ARP_TABLE_DIM 32 +#define ARP_TABLE_DIM 1024 #define MAX_SECONDARY_ADDRESSES 5 // also defined in Ports.h #define TYPE_NOLOCALINTERFACE 0 // used to compare the 'type' field in the rt_v #define TYPE_LOCALINTERFACE 1 From bf98773b5190cd0965fdb87d23676f7799e1924c Mon Sep 17 00:00:00 2001 From: Leonardo Di Giovanna Date: Tue, 21 Jun 2022 14:35:28 +0200 Subject: [PATCH 2/3] Add MULTI port mode to pcn-loadbalancer-rp Signed-off-by: Leonardo Di Giovanna --- .../pcn-loadbalancer-rp/loadbalancer-rp.md | 15 +- .../pcn-loadbalancer-rp/datamodel/lbrp.yang | 18 +- src/services/pcn-loadbalancer-rp/src/Lbrp.cpp | 263 +++++++++++++----- src/services/pcn-loadbalancer-rp/src/Lbrp.h | 30 +- .../pcn-loadbalancer-rp/src/Lbrp_dp.c | 260 ++++++++++++----- .../pcn-loadbalancer-rp/src/Ports.cpp | 15 +- src/services/pcn-loadbalancer-rp/src/Ports.h | 6 + .../pcn-loadbalancer-rp/src/Service.cpp | 7 +- .../pcn-loadbalancer-rp/src/SrcIpRewrite.cpp | 10 +- .../pcn-loadbalancer-rp/src/api/LbrpApi.cpp | 85 ++++++ .../pcn-loadbalancer-rp/src/api/LbrpApi.h | 4 + .../src/api/LbrpApiImpl.cpp | 74 +++++ .../pcn-loadbalancer-rp/src/api/LbrpApiImpl.h | 4 + .../src/interface/LbrpInterface.h | 11 +- .../src/interface/PortsInterface.h | 6 + .../src/serializer/LbrpJsonObject.cpp | 47 ++++ .../src/serializer/LbrpJsonObject.h | 20 +- .../src/serializer/PortsJsonObject.cpp | 26 ++ .../src/serializer/PortsJsonObject.h | 9 + .../pcn-loadbalancer-rp/test/helpers.bash | 40 +++ .../pcn-loadbalancer-rp/test/test_multi.sh | 43 +++ .../pcn-loadbalancer-rp/test/test_single.sh | 40 +++ .../pcn-loadbalancer-rp/test/test_weight.sh | 60 ---- 23 files changed, 879 insertions(+), 214 deletions(-) create mode 100644 src/services/pcn-loadbalancer-rp/test/helpers.bash create mode 100755 src/services/pcn-loadbalancer-rp/test/test_multi.sh create mode 100755 src/services/pcn-loadbalancer-rp/test/test_single.sh delete mode 100755 src/services/pcn-loadbalancer-rp/test/test_weight.sh diff --git a/Documentation/services/pcn-loadbalancer-rp/loadbalancer-rp.md b/Documentation/services/pcn-loadbalancer-rp/loadbalancer-rp.md index 40d3efdb6..5fe4cc4ab 100644 --- a/Documentation/services/pcn-loadbalancer-rp/loadbalancer-rp.md +++ b/Documentation/services/pcn-loadbalancer-rp/loadbalancer-rp.md @@ -2,15 +2,17 @@ This service implements a ``Reverse Proxy Load Balancer``. -According to the algorithm, incoming IP packets are delivered to the real servers by replacing their IP destination address with the one of the real server, chosen by the load balancing logic. Hence, IP address rewriting is performed in both directions, for traffic coming from the Internet and the reverse. -Packet are hashed to determine which is the correct backend; the hashing function guarantees that all packets belonging to the same TCP/UDP session will always be terminated to the same backend server. +According to the algorithm, incoming IP packets are delivered to the real servers by replacing their IP destination address with the one of the real server, chosen by the load balancing logic. Hence, IP address rewriting is performed in both directions, for traffic coming from the Internet and the reverse. Packet are hashed to determine which is the correct backend; the hashing function guarantees that all packets belonging to the same TCP/UDP session will always be terminated to the same backend server. -Unknown packets (e.g., ARP; IPv6) are simply forwarded as they are. +This service supports two different port types (FRONTEND and BACKEND) and two different port modes (SINGLE and MULTI). Depending on the port mode, the cube can have one or more FRONTEND ports: in SINGLE port mode (which is the default one), only a single FRONTEND port is supported, whereas multiple FRONTEND ports are supported in MULTI port mode. The MULTI port mode is specifically designed in order to allow the service to work properly as part of our Kubernetes networking solution (please see [polykube](https://github.com/polycube-network/polykube) to get more information about it). Regardless the port mode, only a BACKEND port is supported. +If a packet coming from a FRONTEND port is directed to a service, DNAT is performed on it; the corresponding reverse natting operation is performed for packets coming from backends and on the way back to clients. ARP packets are forwarded as they are (in MULTI port mode, if the packet is an ARP request from the BACKEND port, it is forwarded to the right FRONTEND port). Unknown packets (e.g., IPv6) are simply forwarded as they are (in MULTI port mode, if the packet comes from the BACKEND port, it is flooded to all the FRONTEND ports). ## Features +- Support for different port modes (SINGLE and MULTI) +- Support for multiple frontend ports (in MULTI port mode) - Support for multiple virtual services (with multiple ``vip:protocol:port`` tuples) - Support for ICMP Echo Request, hence enabling to ``ping`` virtual servers - Session affinity: a TCP session is always terminated to the same backend server even in case the number of backend servers changes at run-time (e.g., a new backend is added) @@ -21,8 +23,11 @@ Unknown packets (e.g., ARP; IPv6) are simply forwarded as they are. ## Limitations +- In SINGLE port mode, only two ports are supported (a FRONTEND and a BACKEND port) +- In MULTI port mode, multiple FRONTEND port are supported but only a single BACKEND port can exists +- In MULTI port mode, an IPv4 address must be configured on FRONTEND port creation in order to allow packets to flow back to the frontend clients +- In MULTI port mode, the supported topology is the one leveraged in the [polykube](https://github.com/polycube-network/polykube) Kubernetes networking solution -- Supports only two interfaces ## How to use @@ -38,7 +43,7 @@ Each backend supports a ``weight`` that determines how incoming sessions are dis A set of ``virtual services``, which are specified by a Virtual IP address, protocol and a port (``vip:protocol:port``), are mapped to a given set of ``backend services``, actually running on multiple real servers. -Hence, this service exports two network interfaces: +Hence, in SINGLE port mode, this service exports two network interfaces: - Frontend port: connects the LB to the clients that connect to the virtual service, likely running on the public Internet - Backend port: connects the LB to to backend servers diff --git a/src/services/pcn-loadbalancer-rp/datamodel/lbrp.yang b/src/services/pcn-loadbalancer-rp/datamodel/lbrp.yang index 84022e62f..dedfa106a 100644 --- a/src/services/pcn-loadbalancer-rp/datamodel/lbrp.yang +++ b/src/services/pcn-loadbalancer-rp/datamodel/lbrp.yang @@ -26,11 +26,25 @@ module lbrp { mandatory true; description "Type of the LB port (e.g. FRONTEND or BACKEND)"; } + leaf ip { + type inet:ipv4-address; + description "IP address of the client interface (only for FRONTEND port)"; + polycube-base:cli-example "10.10.1.1"; + } + } + } + + leaf port_mode { + type enumeration { + enum SINGLE; + enum MULTI; } + default SINGLE; + description "LB mode of operation. 'SINGLE' is optimized for working with a single FRONTEND port. 'MULTI' allows to manage multiple FRONTEND port"; } container src-ip-rewrite { - description "If configured, when a client request arrives to the LB, the source IP addrress is replaced with another IP address from the 'new' range"; + description "If configured, when a client request arrives to the LB, the source IP address is replaced with another IP address from the 'new' range"; leaf ip-range { type inet:ipv4-prefix; @@ -51,7 +65,7 @@ module lbrp { leaf name { type string; description "Service name related to the backend server of the pool is connected to"; - polycube-base:cli-example "Service-nigx"; + polycube-base:cli-example "Service-nginx"; } leaf vip { diff --git a/src/services/pcn-loadbalancer-rp/src/Lbrp.cpp b/src/services/pcn-loadbalancer-rp/src/Lbrp.cpp index 8ad71db9c..46fff87bb 100644 --- a/src/services/pcn-loadbalancer-rp/src/Lbrp.cpp +++ b/src/services/pcn-loadbalancer-rp/src/Lbrp.cpp @@ -23,18 +23,26 @@ #include using namespace Tins; +using namespace polycube::service; + +const std::string Lbrp::EBPF_IP_TO_FRONTEND_PORT_MAP = "ip_to_frontend_port"; Lbrp::Lbrp(const std::string name, const LbrpJsonObject &conf) - : Cube(conf.getBase(), {lbrp_code}, {}) { + : Cube(conf.getBase(), {Lbrp::buildLbrpCode(lbrp_code, conf.getPortMode())}, + {}), lbrp_code_{Lbrp::buildLbrpCode(lbrp_code, conf.getPortMode())}, + port_mode_{conf.getPortMode()} { logger()->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [Lbrp] [%n] [%l] %v"); - logger()->info("Creating Lbrp instance"); + logger()->info("Creating Lbrp instance in {0} port mode", + LbrpJsonObject::LbrpPortModeEnum_to_string(port_mode_)); addServiceList(conf.getService()); addSrcIpRewrite(conf.getSrcIpRewrite()); addPortsList(conf.getPorts()); } -Lbrp::~Lbrp() {} +Lbrp::~Lbrp() { + logger()->info("Destroying Lbrp instance"); +} void Lbrp::update(const LbrpJsonObject &conf) { // This method updates all the object/parameter in Lbrp object specified in @@ -83,68 +91,114 @@ LbrpJsonObject Lbrp::toJsonObject() { return conf; } -void Lbrp::packet_in(Ports &port, polycube::service::PacketInMetadata &md, - const std::vector &packet) { - EthernetII eth(&packet[0], packet.size()); - - auto b_port = getBackendPort(); - if (!b_port) - return; - - // send the original packet - logger()->debug("Sending original ARP reply to backend port: {}", - b_port->name()); - b_port->send_packet_out(eth); - - // send a second packet for the virtual src IPs - ARP &arp = eth.rfind_pdu(); +std::string Lbrp::buildLbrpCode(std::string const &lbrp_code, + LbrpPortModeEnum port_mode) { + if (port_mode == LbrpPortModeEnum::SINGLE) { + return "#define SINGLE_PORT_MODE 1\n" + lbrp_code; + } + return "#define SINGLE_PORT_MODE 0\n" + lbrp_code; +} - uint32_t ip = uint32_t(arp.sender_ip_addr()); - ip &= htonl(src_ip_rewrite_->mask); - ip |= htonl(src_ip_rewrite_->net); +void Lbrp::flood_packet(Ports &port, PacketInMetadata &md, + const std::vector &packet) { + EthernetII p(&packet[0], packet.size()); - IPv4Address addr(ip); - arp.sender_ip_addr(addr); + for (auto &it: get_ports()) { + if (it->name() == port.name()) { + continue; + } + it->send_packet_out(p); + logger()->trace("Sent pkt to port {0} as result of flooding", it->name()); + } +} - logger()->debug("Sending modified ARP reply to backend port: {}", - b_port->name()); - b_port->send_packet_out(eth); +void Lbrp::packet_in(Ports &port, polycube::service::PacketInMetadata &md, + const std::vector &packet) { + try { + switch (static_cast(md.reason)) { + case SlowPathReason::ARP_REPLY: { + logger()->debug("Received pkt in slowpath - reason: ARP_REPLY"); + EthernetII eth(&packet[0], packet.size()); + + auto b_port = getBackendPort(); + if (!b_port) + return; + + // send the original packet + logger()->debug("Sending original ARP reply to backend port: {}", + b_port->name()); + b_port->send_packet_out(eth); + + // send a second packet for the virtual src IPs + ARP &arp = eth.rfind_pdu(); + + uint32_t ip = uint32_t(arp.sender_ip_addr()); + ip &= htonl(src_ip_rewrite_->mask); + ip |= htonl(src_ip_rewrite_->net); + + IPv4Address addr(ip); + arp.sender_ip_addr(addr); + + logger()->debug("Sending modified ARP reply to backend port: {}", + b_port->name()); + b_port->send_packet_out(eth); + break; + } + case SlowPathReason::FLOODING: { + logger()->debug("Received pkt in slowpath - reason: FLOODING"); + flood_packet(port, md, packet); + break; + } + default: { + logger()->error("Not valid reason {0} received", md.reason); + } + } + } catch (const std::exception &e) { + logger()->error("Exception during slowpath packet processing: '{0}'", + e.what()); + } } void Lbrp::reloadCodeWithNewPorts() { uint16_t frontend_port = 0; uint16_t backend_port = 1; - for (auto &it : get_ports()) { - switch (it->getType()) { - case PortsTypeEnum::FRONTEND: + for (auto &it: get_ports()) { + if (it->getType() == PortsTypeEnum::FRONTEND) { frontend_port = it->index(); - break; - case PortsTypeEnum::BACKEND: - backend_port = it->index(); - break; - } + } else backend_port = it->index(); } - logger()->debug("Reloading code with frontend port: {0} and backend port {1}", + logger()->debug("Reloading code with FRONTEND port {0} and BACKEND port {1}", frontend_port, backend_port); std::string frontend_port_str("#define FRONTEND_PORT " + std::to_string(frontend_port)); std::string backend_port_str("#define BACKEND_PORT " + std::to_string(backend_port)); - reload(frontend_port_str + "\n" + backend_port_str + "\n" + lbrp_code); + reload(frontend_port_str + "\n" + backend_port_str + "\n" + lbrp_code_); logger()->trace("New lbrp code loaded"); } -std::shared_ptr Lbrp::getFrontendPort() { - for (auto &it : get_ports()) { +void Lbrp::reloadCodeWithNewBackendPort(uint16_t backend_port_index) { + logger()->debug("Reloading code with BACKEND port {0}", backend_port_index); + std::string backend_port_str("#define BACKEND_PORT " + + std::to_string(backend_port_index)); + + reload(backend_port_str + "\n" + lbrp_code_); + + logger()->trace("New lbrp code loaded"); +} + +std::vector> Lbrp::getFrontendPorts() { + std::vector> frontend_ports; + for (auto &it: get_ports()) { if (it->getType() == PortsTypeEnum::FRONTEND) { - return it; + frontend_ports.push_back(it); } } - return nullptr; + return frontend_ports; } std::shared_ptr Lbrp::getBackendPort() { @@ -164,41 +218,89 @@ std::vector> Lbrp::getPortsList() { return get_ports(); } -void Lbrp::addPorts(const std::string &name, const PortsJsonObject &conf) { +void Lbrp::addPortsInSinglePortMode(const std::string &name, + const PortsJsonObject &conf) { + PortsTypeEnum type = conf.getType(); + auto ip_is_set = conf.ipIsSet(); if (get_ports().size() == 2) { - logger()->warn("Reached maximum number of ports"); throw std::runtime_error("Reached maximum number of ports"); } - try { - switch (conf.getType()) { - case PortsTypeEnum::FRONTEND: - if (getFrontendPort() != nullptr) { - logger()->warn("There is already a FRONTEND port"); - throw std::runtime_error("There is already a FRONTEND port"); - } - break; - case PortsTypeEnum::BACKEND: - if (getBackendPort() != nullptr) { - logger()->warn("There is already a BACKEND port"); - throw std::runtime_error("There is already a BACKEND port"); - } - break; + if (type == PortsTypeEnum::FRONTEND) { + if (getFrontendPorts().size() == 1) { + throw std::runtime_error("There is already a FRONTEND port. Only a " + "FRONTEND port is allowed in SINGLE port mode"); } - } catch (std::runtime_error &e) { - logger()->warn("Error when adding the port {0}", name); - logger()->warn("Error message: {0}", e.what()); - throw; + if (ip_is_set) { + throw std::runtime_error("The ip address in not allow in SINGLE port " + "mode for a FRONTEND port"); + } + } else { + if (getBackendPort() != nullptr) throw std::runtime_error("There is " + "already a BACKEND port"); + if (ip_is_set) throw std::runtime_error("The ip address in not allowed " + "in BACKEND port"); } - add_port(name, conf); + if (get_ports().size() == 2) reloadCodeWithNewPorts(); +} - if (get_ports().size() == 2) { - logger()->info("Reloading code because of the new port"); - reloadCodeWithNewPorts(); +void Lbrp::addPortsInMultiPortMode(const std::string &name, + const PortsJsonObject &conf) { + PortsTypeEnum type = conf.getType(); + auto ip_is_set = conf.ipIsSet(); + + if (type == PortsTypeEnum::FRONTEND) { + if (!ip_is_set) { + throw std::runtime_error("The IP address is mandatory in MULTI port " + "mode for a FRONTEND port"); + } + auto ip = conf.getIp(); + auto found = frontend_ip_set_.find(ip) != frontend_ip_set_.end(); + if (found) { + throw std::runtime_error("A FRONTEND port with the provided IP " + "address (" + ip + ") already exist"); + } + if (!frontend_ip_set_.insert(ip).second) { + throw std::runtime_error("Failed to set the IP address for the new " + "FRONTEND port"); + } + try { + auto created_port = add_port(name, conf); + auto ip_to_frontend_port_table = get_hash_table( + EBPF_IP_TO_FRONTEND_PORT_MAP); + ip_to_frontend_port_table.set( + utils::ip_string_to_nbo_uint(created_port->getIp()), + created_port->index() + ); + } catch (std::exception &ex) { + frontend_ip_set_.erase(ip); + throw; + } + } else { + if (getBackendPort() != nullptr) { + throw std::runtime_error("There is already a BACKEND port"); + } + if (ip_is_set) { + throw std::runtime_error("The ip address in not allowed in BACKEND port"); + } + auto created_port = add_port(name, conf); + reloadCodeWithNewBackendPort(created_port->index()); } +} - logger()->info("New port created with name {0}", name); +void Lbrp::addPorts(const std::string &name, const PortsJsonObject &conf) { + try { + if (port_mode_ == LbrpPortModeEnum::SINGLE) { + addPortsInSinglePortMode(name, conf); + } + else addPortsInMultiPortMode(name, conf); + } catch (std::runtime_error &ex) { + logger()->warn("Failed to add port {0}: {1}", name, ex.what()); + throw; + } + + logger()->info("Created new Port with name {0}", name); } void Lbrp::addPortsList(const std::vector &conf) { @@ -215,7 +317,27 @@ void Lbrp::replacePorts(const std::string &name, const PortsJsonObject &conf) { } void Lbrp::delPorts(const std::string &name) { - remove_port(name); + try { + auto port = get_port(name); + if (this->port_mode_ == LbrpPortModeEnum::MULTI && + port->getType() == PortsTypeEnum::FRONTEND) { + auto ip = port->getIp(); + + auto ip_to_frontend_port_table = get_hash_table( + EBPF_IP_TO_FRONTEND_PORT_MAP); + ip_to_frontend_port_table.remove(utils::ip_string_to_nbo_uint(ip)); + + if (this->frontend_ip_set_.erase(ip) == 0) { + throw std::runtime_error( + "Failed to delete the port IP address for the new FRONTEND port"); + } + } + remove_port(name); + } catch (std::exception &ex) { + logger()->error("Failed to delete port {0}: {1}", name, ex.what()); + throw; + } + logger()->info("Deleted Lbrp port"); } void Lbrp::delPortsList() { @@ -225,6 +347,15 @@ void Lbrp::delPortsList() { } } +LbrpPortModeEnum Lbrp::getPortMode() { + return port_mode_; +} + +void Lbrp::setPortMode(const LbrpPortModeEnum &value) { + logger()->warn("Port mode cannot be changed at runtime"); + throw std::runtime_error("Port mode cannot be changed at runtime"); +} + std::shared_ptr Lbrp::getSrcIpRewrite() { return src_ip_rewrite_; } diff --git a/src/services/pcn-loadbalancer-rp/src/Lbrp.h b/src/services/pcn-loadbalancer-rp/src/Lbrp.h index f6d375bec..a5bf267e6 100644 --- a/src/services/pcn-loadbalancer-rp/src/Lbrp.h +++ b/src/services/pcn-loadbalancer-rp/src/Lbrp.h @@ -28,18 +28,26 @@ #include "Service.h" #include "SrcIpRewrite.h" #include "hash_tuple.h" +#include +#include using namespace io::swagger::server::model; using polycube::service::CubeType; +enum class SlowPathReason { ARP_REPLY = 0, FLOODING = 1 }; + class Lbrp : public polycube::service::Cube, public LbrpInterface { friend class Ports; friend class Service; friend class SrcIpRewrite; public: + static std::string buildLbrpCode(std::string const& lbrp_code, LbrpPortModeEnum port_mode); Lbrp(const std::string name, const LbrpJsonObject &conf); virtual ~Lbrp(); + + void flood_packet(Ports &port, polycube::service::PacketInMetadata &md, + const std::vector &packet); void packet_in(Ports &port, polycube::service::PacketInMetadata &md, const std::vector &packet) override; @@ -65,7 +73,8 @@ class Lbrp : public polycube::service::Cube, public LbrpInterface { void delServiceList() override; /// - /// + /// If configured, when a client request arrives to the LB, the source IP + /// address is replaced with another IP address from the 'new' range /// std::shared_ptr getSrcIpRewrite() override; void addSrcIpRewrite(const SrcIpRewriteJsonObject &value) override; @@ -77,6 +86,10 @@ class Lbrp : public polycube::service::Cube, public LbrpInterface { /// std::shared_ptr getPorts(const std::string &name) override; std::vector> getPortsList() override; + void addPortsInSinglePortMode(const std::string &name, + const PortsJsonObject &conf); + void addPortsInMultiPortMode(const std::string &name, + const PortsJsonObject &conf); void addPorts(const std::string &name, const PortsJsonObject &conf) override; void addPortsList(const std::vector &conf) override; void replacePorts(const std::string &name, @@ -84,11 +97,24 @@ class Lbrp : public polycube::service::Cube, public LbrpInterface { void delPorts(const std::string &name) override; void delPortsList() override; + /// + /// LB mode of operation. + /// 'SINGLE' is optimized for working with a single FRONTEND port. + /// 'MULTI' allows to manage multiple FRONTEND port. + /// + LbrpPortModeEnum getPortMode() override; + void setPortMode(const LbrpPortModeEnum &value) override; + void reloadCodeWithNewPorts(); - std::shared_ptr getFrontendPort(); + void reloadCodeWithNewBackendPort(uint16_t backend_port_index); + std::vector> getFrontendPorts(); std::shared_ptr getBackendPort(); private: + static const std::string EBPF_IP_TO_FRONTEND_PORT_MAP; + std::set frontend_ip_set_; std::unordered_map service_map_; std::shared_ptr src_ip_rewrite_; + LbrpPortModeEnum port_mode_; + std::string lbrp_code_; }; diff --git a/src/services/pcn-loadbalancer-rp/src/Lbrp_dp.c b/src/services/pcn-loadbalancer-rp/src/Lbrp_dp.c index 17ab76e8d..b37353cd5 100644 --- a/src/services/pcn-loadbalancer-rp/src/Lbrp_dp.c +++ b/src/services/pcn-loadbalancer-rp/src/Lbrp_dp.c @@ -49,6 +49,7 @@ #define MAX_SERVICES 1024 #define MAX_SESSIONS 65536 +#define MAX_FRONTENDS 1024 #define IP_CSUM_OFFSET (sizeof(struct eth_hdr) + offsetof(struct iphdr, check)) #define TCP_CSUM_OFFSET \ @@ -62,14 +63,21 @@ offsetof(struct icmphdr, checksum)) #define IS_PSEUDO 0x10 +#ifndef SINGLE_PORT_MODE +#define SINGLE_PORT_MODE 0 +#endif + #ifndef BACKEND_PORT -#define BACKEND_PORT 1 +#define BACKEND_PORT 0 #endif #ifndef FRONTEND_PORT -#define FRONTEND_PORT 0 +#define FRONTEND_PORT 1 #endif +#define REASON_ARP_REPLY 0x0 +#define REASON_FLOODING 0x1 + struct eth_hdr { __be64 dst : 48; __be64 src : 48; @@ -185,6 +193,8 @@ BPF_F_TABLE("lpm_trie", struct src_ip_r_key, struct src_ip_r_value, BPF_TABLE("hash", struct backend, struct vip, backend_to_service, MAX_SERVICES); +BPF_TABLE("hash", __be32, u16, ip_to_frontend_port, MAX_FRONTENDS); + /* * This function is used to get the backend ip after the LoadBalancing. * For each packet that handled by the load balancer, we save the hash of @@ -213,8 +223,6 @@ static inline struct backend *get_bck_ip(struct CTXTYPE *ctx, __be32 ip_src, // select the backend id __u32 id = check % pool_size + 1; - pcn_log(ctx, LOG_TRACE, "Selected backend with id: %d", id); - struct vip v_key = {}; v_key.ip = ip_dst; @@ -222,18 +230,27 @@ static inline struct backend *get_bck_ip(struct CTXTYPE *ctx, __be32 ip_src, v_key.proto = proto; v_key.index = id; - pcn_log(ctx, LOG_TRACE, - "Backend lookup ip: %I, port: %P, proto: 0x%04x, index: %d", ip_dst, - port_dst, proto, id); + pcn_log( + ctx, LOG_TRACE, + "Selected backend index for the service - (ip: %I, port: %P, proto: 0x%04x) (index: %d)", + ip_dst, port_dst, proto, id + ); // Now, from the backend ID, let's get the actual backend server (IP/port) struct backend *bck_value = services.lookup(&v_key); if (!bck_value) { + pcn_log( + ctx, LOG_ERR, + "Failed to retrieve backend for the service - (ip: %I, port: %P, proto: 0x%04x) (index: %d)", + ip_dst, port_dst, proto, id + ); return 0; } - pcn_log(ctx, LOG_TRACE, "Found backend with ip: %I and port: %P", - bck_value->ip, bck_value->port); + pcn_log(ctx, LOG_TRACE, + "Retrieved backend for the given index - (index: %d) (be_ip: %I, be_port: %P)", + id, bck_value->ip, bck_value->port + ); // check if src ip rewrite applies for this packet, if yes, do not create a // new entry in the session table @@ -281,6 +298,25 @@ static inline void checksum(struct CTXTYPE *ctx, __u16 old_port, __u16 new_port, } } +static inline int send_to_frontend(struct CTXTYPE *ctx, struct pkt_metadata *md, __be32 ip_daddr) { + if (SINGLE_PORT_MODE) { + pcn_log(ctx, LOG_TRACE, "Sent pkt to the FRONTEND port - (out_port: %d)", + FRONTEND_PORT); + return pcn_pkt_redirect(ctx, md, FRONTEND_PORT); + } + u16 *fe_port = ip_to_frontend_port.lookup(&ip_daddr); + if (!fe_port) { + pcn_log(ctx, LOG_ERR, "Failed FRONTEND port lookup - (ip_dst: %I)", ip_daddr); + return pcn_pkt_drop(ctx, md); + } + pcn_log( + ctx, LOG_TRACE, + "Sent pkt to the FRONTEND port associated with the destination - (ip_dst: %I) (out_port: %d)", + ip_daddr, *fe_port + ); + return pcn_pkt_redirect(ctx, md, *fe_port); +} + static __always_inline int handle_rx(struct CTXTYPE *ctx, struct pkt_metadata *md) { __u16 source = 0; @@ -293,8 +329,12 @@ static __always_inline int handle_rx(struct CTXTYPE *ctx, struct eth_hdr *eth = data; - if (data + sizeof(*eth) > data_end) - goto DROP; + if (data + sizeof(*eth) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped Ethernet pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); + return RX_DROP; + } switch (eth->proto) { case htons(ETH_P_IP): @@ -307,8 +347,12 @@ static __always_inline int handle_rx(struct CTXTYPE *ctx, IP:; struct iphdr *ip = data + sizeof(struct eth_hdr); - if (data + sizeof(struct eth_hdr) + sizeof(struct iphdr) > data_end) - goto DROP; + if (data + sizeof(struct eth_hdr) + sizeof(struct iphdr) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped IP pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); + return RX_DROP; + } struct udphdr *udp = data + sizeof(struct eth_hdr) + sizeof(struct iphdr); struct tcphdr *tcp = data + sizeof(struct eth_hdr) + sizeof(struct iphdr); @@ -327,32 +371,48 @@ IP:; case IPPROTO_TCP: goto TCP; default: - goto DROP; + pcn_log(ctx, LOG_TRACE, + "Dropped IP pkt - (in_port: %d) (reason: unsupported_type) (ip_proto: %d)", + md->in_port, ip->protocol); + return RX_DROP; } ICMP: - if (data + sizeof(*eth) + sizeof(*ip) + sizeof(*icmp) > data_end) + if (data + sizeof(*eth) + sizeof(*ip) + sizeof(*icmp) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped ICMP pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); return RX_DROP; + } // Only manage ICMP Request and Reply - if (!((icmp->type == ICMP_ECHO) || (icmp->type == ICMP_ECHOREPLY))) - goto DROP; + if (!((icmp->type == ICMP_ECHO) || (icmp->type == ICMP_ECHOREPLY))) { + pcn_log(ctx, LOG_TRACE, + "Dropped ICMP pkt - (in_port: %d) (reason: unsupported_type) (icmp_type: %d)", + md->in_port, icmp->type); + return RX_DROP; + } source = icmp->un.echo.id; dest = 0x0; ip_proto = htons(IPPROTO_ICMP); - pcn_log(ctx, LOG_DEBUG, "received ICMP %I --> %I", ip->saddr, ip->daddr); + pcn_log(ctx, LOG_DEBUG, "Received ICMP pkt - (ip_src: %I, ip_dst: %I)", + ip->saddr, ip->daddr); l4csum_offset = ICMP_CSUM_OFFSET; goto LB; UDP: - if (data + sizeof(*eth) + sizeof(*ip) + sizeof(*udp) > data_end) + if (data + sizeof(*eth) + sizeof(*ip) + sizeof(*udp) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped UDP pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); return RX_DROP; + } - pcn_log(ctx, LOG_TRACE, "received UDP: %I:%P --> %I:%P", ip->saddr, - udp->source, ip->daddr, udp->dest); + pcn_log(ctx, LOG_TRACE, "Received UDP pkt - (src: %I:%P, dst: %I:%P)", + ip->saddr, udp->source, ip->daddr, udp->dest); source = udp->source; dest = udp->dest; @@ -362,11 +422,15 @@ IP:; goto LB; TCP: - if (data + sizeof(*eth) + sizeof(*ip) + sizeof(*tcp) > data_end) + if (data + sizeof(*eth) + sizeof(*ip) + sizeof(*tcp) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped TCP pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); return RX_DROP; + } - pcn_log(ctx, LOG_TRACE, "received TCP: %I:%P --> %I:%P", ip->saddr, - tcp->source, ip->daddr, tcp->dest); + pcn_log(ctx, LOG_TRACE, "Received TCP pkt - (src: %I:%P, dst: %I:%P)", + ip->saddr, tcp->source, ip->daddr, tcp->dest); source = tcp->source; dest = tcp->dest; @@ -381,11 +445,12 @@ LB:; __u32 new_port = dest; // Currently, the "backend port" is the one connected to the backends - // So, this packet is coming from the fronted and is returning back to the + // So, this packet is coming from a frontend and is returning to the // originating Pod. - if (md->in_port == FRONTEND_PORT) { - pcn_log(ctx, LOG_TRACE, "Packet arrived in the frontend port"); + if (md->in_port != BACKEND_PORT) { + pcn_log(ctx, LOG_TRACE, + "Received pkt is from a FRONTEND port - (in_port %d)", md->in_port); struct vip v_key = {}; @@ -403,7 +468,8 @@ LB:; // packet as is. if (!backend_value) { pcn_log(ctx, LOG_TRACE, - "Service lookup failed. Redirected as is to backend port"); + "Failed service lookup: redirected as is to BACKEND port - (out_port: %d)", + BACKEND_PORT); return pcn_pkt_redirect(ctx, md, BACKEND_PORT); } @@ -412,7 +478,10 @@ LB:; dest, ip_proto, backend_value->port); if (!bck_value) { - goto DROP; + pcn_log(ctx, LOG_TRACE, + "Dropped pkt from FRONTEND port - (in_port: %d) (reason: failed_bck_lookup)", + md->in_port); + return RX_DROP; } ip->daddr = bck_value->ip; @@ -424,11 +493,11 @@ LB:; ip->saddr &= bpf_htonl(v->mask); ip->saddr |= bpf_htonl(v->net); new_sip = ip->saddr; - pcn_log(ctx, LOG_TRACE, "src ip rewritten to %I", ip->saddr); } - pcn_log(ctx, LOG_TRACE, "redirected to %I:%P --> %I:%P", ip->saddr, source, - bck_value->ip, bck_value->port); + pcn_log(ctx, LOG_TRACE, + "Pkt DNATted and redirected to BACKEND port - (src: %I:%P, new_dst: %I:%P)", + ip->saddr, source, bck_value->ip, bck_value->port); // We are using l4csum_offset here, since it is equivalent of the protocol if (l4csum_offset == TCP_CSUM_OFFSET) { @@ -440,7 +509,8 @@ LB:; checksum(ctx, old_port, bck_value->port, old_ip, bck_value->ip, old_sip, new_sip, UDP_CSUM_OFFSET); } else { - pcn_log(ctx, LOG_TRACE, "translated existing ICMP session as %I --> %I", + pcn_log(ctx, LOG_TRACE, + "Translated existing ICMP session - (old_ip: %I, new_ip: %I)", new_ip, ip->daddr); pcn_l3_csum_replace(ctx, IP_CSUM_OFFSET, old_ip, bck_value->ip, 4); } @@ -455,7 +525,9 @@ LB:; * SESSION TABLE */ - pcn_log(ctx, LOG_TRACE, "Packet comes from backend port: %I", ip->daddr); + pcn_log(ctx, LOG_TRACE, + "Received pkt is from the BACKEND port - (in_port %d)", + md->in_port); __u32 dst_ip_ = ip->daddr; @@ -469,8 +541,10 @@ LB:; struct vip *vip_v = backend_to_service.lookup(&key); if (!vip_v) { - pcn_log(ctx, LOG_ERR, "Reverse backend lookup failed"); - goto DROP; + pcn_log(ctx, LOG_TRACE, + "Dropped pkt from BACKEND port - (in_port: %d) (reason: failed_rev_bck_lookup)", + md->in_port); + return RX_DROP; } old_ip = ip->daddr; @@ -482,10 +556,9 @@ LB:; new_sip = vip_v->ip; new_ip = ip->daddr; new_port = vip_v->port; - pcn_log( - ctx, LOG_TRACE, - "srcIpRewrite found. translate ip src %I dst %I --> src %I dst %I", - old_sip, old_ip, new_sip, new_ip); + pcn_log(ctx, LOG_TRACE, + "Found SrcIpRewrite: translation - (src: %I, dst: %I) (new_src: %I, new_dst: %I)", + old_sip, old_ip, new_sip, new_ip); } else { struct sessions sessions_key = {}; sessions_key.ip_src = ip->daddr; @@ -502,7 +575,8 @@ LB:; __u32 check = jhash((const void *)&sessions_key, sizeof(struct sessions), JHASH_INITVAL); - pcn_log(ctx, LOG_TRACE, "Check in session table %lu", check); + pcn_log(ctx, LOG_TRACE, "Check in session table - (session_id: %lu)", + check); // Let's check if the this session is present in the session table struct vip *rev_proxy = hash_session.lookup(&check); @@ -511,8 +585,9 @@ LB:; // LB, // we have to translate IP addresses back to their original value if (!rev_proxy) { - pcn_log(ctx, LOG_TRACE, "redirected as is to frontend port"); - return pcn_pkt_redirect(ctx, md, FRONTEND_PORT); + pcn_log(ctx, LOG_TRACE, + "Failed session lookup: trying to redirect to a proper FRONTEND port"); + return send_to_frontend(ctx, md, ip->daddr); } old_ip = ip->saddr; @@ -521,41 +596,74 @@ LB:; new_port = rev_proxy->port; } + u16 fe_port; + if (SINGLE_PORT_MODE) + fe_port = FRONTEND_PORT; + else { + __be32 fe_ip = ip->daddr; + u16 *fe_port_ptr = ip_to_frontend_port.lookup(&fe_ip); + if (!fe_port_ptr) { + pcn_log(ctx, LOG_ERR, + "Dropped packet from BACKEND port - (in_port: %d) (reason: failed_frontend_port_lookup", + md->in_port); + return RX_DROP; + } + + fe_port = *fe_port_ptr; + } + + pcn_log( + ctx, LOG_TRACE, + "Found FRONTEND port for pkt destination - (dst_ip: %I) (out_port: %d)", + ip->daddr, fe_port); + if (l4csum_offset == TCP_CSUM_OFFSET) { old_port = tcp->source; tcp->source = new_port; pcn_log(ctx, LOG_TRACE, - "translated existing TCP session as %I:%P --> %I:%P", new_ip, - tcp->source, ip->daddr, dest); + "Translated existing TCP session - (new_src: %I:%P, dst: %I:%P)", + new_ip, tcp->source, ip->daddr, dest); checksum(ctx, old_port, new_port, old_ip, new_ip, old_sip, new_sip, TCP_CSUM_OFFSET); } else if (l4csum_offset == UDP_CSUM_OFFSET) { old_port = udp->source; udp->source = new_port; pcn_log(ctx, LOG_TRACE, - "translated existing UDP session as %I:%P --> %I:%P", new_ip, - new_port, ip->daddr, dest); + "Translated existing UDP session - (new_src: %I:%P, dst: %I:%P)", + new_ip, new_port, ip->daddr, dest); checksum(ctx, old_port, new_port, old_ip, new_ip, old_sip, new_sip, UDP_CSUM_OFFSET); } else { - pcn_log(ctx, LOG_TRACE, "translated existing ICMP session as %I --> %I", + pcn_log(ctx, LOG_TRACE, + "Translated existing ICMP session - (new_ip_src: %I, ip_dst: %I)", new_ip, ip->daddr); pcn_l3_csum_replace(ctx, IP_CSUM_OFFSET, old_ip, new_ip, 4); } - - return pcn_pkt_redirect(ctx, md, FRONTEND_PORT); + pcn_log(ctx, LOG_TRACE, "Redirected pkt to FRONTEND port - (out_port: %d)", + fe_port); + return pcn_pkt_redirect(ctx, md, fe_port); } ARP:; struct arp_hdr *arp = data + sizeof(*eth); - if (data + sizeof(*eth) + sizeof(*arp) > data_end) - goto DROP; + if (data + sizeof(*eth) + sizeof(*arp) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped ARP pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); + return RX_DROP; + } // if the packet comes from the backend port, check if this // is an ARP request for one of the virtual src IPs, if yes, change // it and send to the frontend port if (md->in_port == BACKEND_PORT) { + pcn_log(ctx, LOG_TRACE, + "Received ARP pkt from BACKEND port - (in_port: %d)", md->in_port); if (arp->ar_op == bpf_htons(ARPOP_REQUEST)) { + pcn_log( + ctx, LOG_TRACE, + "Received ARP pkt is an ARP request - (in_port: %d) (arp_opcode: %d)", + md->in_port, bpf_htons(arp->ar_op)); struct src_ip_r_key k = {32, arp->ar_tip}; struct src_ip_r_value *v = src_ip_rewrite.lookup(&k); if (v && v->sense == FROM_BACKEND) { @@ -563,34 +671,52 @@ ARP:; arp->ar_tip |= bpf_htonl(v->net); } } - return pcn_pkt_redirect(ctx, md, FRONTEND_PORT); - } else if (md->in_port == FRONTEND_PORT) { + return send_to_frontend(ctx, md, arp->ar_tip); + } else { + pcn_log(ctx, LOG_TRACE, + "Received ARP pkt from FRONTEND port - (in_port: %d)", md->in_port); if (arp->ar_op == bpf_htons(ARPOP_REPLY)) { + pcn_log( + ctx, LOG_TRACE, + "Received ARP pkt is an ARP reply - (in_port: %d) (arp_opcode: %d)", + md->in_port, arp->ar_op); struct src_ip_r_key k = {32, arp->ar_sip}; struct src_ip_r_value *v = src_ip_rewrite.lookup(&k); if (v && v->sense == FROM_FRONTEND) { - // send to the slowpath, it'll send to copies of this + // send to the slowpath, it'll send two copies of this // - the original one // - one for the virtual IPs - pcn_pkt_controller(ctx, md, 0); + pcn_log(ctx, LOG_TRACE, + "Sent received ARP reply to slowpath - (in_port: %d)", + md->in_port); + pcn_pkt_controller(ctx, md, REASON_ARP_REPLY); return RX_DROP; } } + pcn_log(ctx, LOG_TRACE, + "Redirected ARP pkt to BACKEND port - (in_port: %d) (out_port: %d)", + md->in_port, BACKEND_PORT); return pcn_pkt_redirect(ctx, md, BACKEND_PORT); - } else { - goto DROP; } NOIP: - pcn_log(ctx, LOG_TRACE, "received a non-ip packet (in_port %d) (proto 0x%x)", - md->in_port, bpf_htons(eth->proto)); - - if (md->in_port == BACKEND_PORT) - return pcn_pkt_redirect(ctx, md, FRONTEND_PORT); - else if (md->in_port == FRONTEND_PORT) + if (md->in_port == BACKEND_PORT) { + if (SINGLE_PORT_MODE) { + pcn_log(ctx, LOG_TRACE, + "Received non-ip pkt from BACKEND port: redirected to FRONTEND port - (in_port: %d) (proto: 0x%x) (out_port: %d)", + md->in_port, bpf_htons(eth->proto), FRONTEND_PORT); + return pcn_pkt_redirect(ctx, md, FRONTEND_PORT); + } + pcn_log(ctx, LOG_TRACE, + "Received non-ip pkt from BACKEND port: sent to slowpath - (in_port: %d) (proto: 0x%x)", + md->in_port, bpf_htons(eth->proto)); + return pcn_pkt_controller(ctx, md, REASON_FLOODING); + } else { + pcn_log(ctx, LOG_TRACE, + "Received non-ip pkt from FRONTEND port: redirected to BACKEND port - (in_port: %d) (proto: 0x%x) (out_port: %d)", + md->in_port, bpf_htons(eth->proto), BACKEND_PORT); return pcn_pkt_redirect(ctx, md, BACKEND_PORT); + } -DROP: - pcn_log(ctx, LOG_TRACE, "DROP packet (port = %d)", md->in_port); return RX_DROP; -} +} \ No newline at end of file diff --git a/src/services/pcn-loadbalancer-rp/src/Ports.cpp b/src/services/pcn-loadbalancer-rp/src/Ports.cpp index 0ec1b5079..912e66e01 100644 --- a/src/services/pcn-loadbalancer-rp/src/Ports.cpp +++ b/src/services/pcn-loadbalancer-rp/src/Ports.cpp @@ -25,7 +25,11 @@ Ports::Ports(polycube::service::Cube &parent, : Port(port), parent_(static_cast(parent)) { logger()->info("Creating Ports instance"); - port_type_ = conf.getType(); + auto type = conf.getType(); + auto ipIsSet = conf.ipIsSet(); + + port_type_ = type; + if (ipIsSet) ip_ = conf.getIp(); } Ports::~Ports() {} @@ -46,6 +50,7 @@ PortsJsonObject Ports::toJsonObject() { conf.setBase(Port::to_json()); conf.setType(getType()); + conf.setIp(getIp()); return conf; } @@ -60,6 +65,14 @@ void Ports::setType(const PortsTypeEnum &value) { throw std::runtime_error("Error: Port type cannot be changed at runtime."); } +std::string Ports::getIp() { + return ip_; +} + +void Ports::setIp(const std::string &value) { + ip_ = value; +} + std::shared_ptr Ports::logger() { return parent_.logger(); } diff --git a/src/services/pcn-loadbalancer-rp/src/Ports.h b/src/services/pcn-loadbalancer-rp/src/Ports.h index 5edaf8509..7e26f8eac 100644 --- a/src/services/pcn-loadbalancer-rp/src/Ports.h +++ b/src/services/pcn-loadbalancer-rp/src/Ports.h @@ -45,7 +45,13 @@ class Ports : public polycube::service::Port, public PortsInterface { PortsTypeEnum getType() override; void setType(const PortsTypeEnum &value) override; + /// + /// IP address of the client interface (only for FRONTEND port) + /// + std::string getIp() override; + void setIp(const std::string &value) override; private: Lbrp &parent_; PortsTypeEnum port_type_; + std::string ip_; }; diff --git a/src/services/pcn-loadbalancer-rp/src/Service.cpp b/src/services/pcn-loadbalancer-rp/src/Service.cpp index a78d4ff41..07c929bb4 100644 --- a/src/services/pcn-loadbalancer-rp/src/Service.cpp +++ b/src/services/pcn-loadbalancer-rp/src/Service.cpp @@ -525,13 +525,12 @@ void Service::delBackend(const std::string &ip) { service_backends_.erase(ip); removeBackendFromServiceMatrix(ip); - if (service_backends_.size() != 0) - updateConsistentHashMap(); - - if (service_backends_.size() == 0) { + if (service_backends_.empty()) { // If there are no backends let's remove all entries from the eBPF map backend_matrix_.clear(); removeServiceFromKernelMap(); + } else { + updateConsistentHashMap(); } } diff --git a/src/services/pcn-loadbalancer-rp/src/SrcIpRewrite.cpp b/src/services/pcn-loadbalancer-rp/src/SrcIpRewrite.cpp index a4067012e..9a8c9dfd5 100644 --- a/src/services/pcn-loadbalancer-rp/src/SrcIpRewrite.cpp +++ b/src/services/pcn-loadbalancer-rp/src/SrcIpRewrite.cpp @@ -28,7 +28,7 @@ enum { SrcIpRewrite::SrcIpRewrite(Lbrp &parent, const SrcIpRewriteJsonObject &conf) : parent_(parent) { - logger()->info("[Constructor]Creating SrcIpRewrite instance"); + logger()->info("[Constructor] Creating SrcIpRewrite instance"); if (conf.newIpRangeIsSet()) { setNewIpRange(conf.getNewIpRange()); @@ -48,12 +48,12 @@ void SrcIpRewrite::update(const SrcIpRewriteJsonObject &conf) { setIpRange(conf.getIpRange()); setNewIpRange(conf.getNewIpRange()); - std::string old_ip_net = ip_range.substr(0, ip_range.find("/")); + std::string old_ip_net = ip_range.substr(0, ip_range.find('/')); std::string old_mask_len = - ip_range.substr(ip_range.find("/") + 1, std::string::npos); - std::string new_ip_net = new_ip_range.substr(0, new_ip_range.find("/")); + ip_range.substr(ip_range.find('/') + 1, std::string::npos); + std::string new_ip_net = new_ip_range.substr(0, new_ip_range.find('/')); std::string new_mask_len = - new_ip_range.substr(new_ip_range.find("/") + 1, std::string::npos); + new_ip_range.substr(new_ip_range.find('/') + 1, std::string::npos); // TODO: change datamodel to fix it if (old_mask_len != new_mask_len) { diff --git a/src/services/pcn-loadbalancer-rp/src/api/LbrpApi.cpp b/src/services/pcn-loadbalancer-rp/src/api/LbrpApi.cpp index 5e98a6a6f..0083fdbb3 100644 --- a/src/services/pcn-loadbalancer-rp/src/api/LbrpApi.cpp +++ b/src/services/pcn-loadbalancer-rp/src/api/LbrpApi.cpp @@ -525,6 +525,23 @@ Response read_lbrp_list_by_id_handler( } } +Response read_lbrp_port_mode_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + + auto x = read_lbrp_port_mode_by_id(unique_name); + nlohmann::json response_body; + response_body = LbrpJsonObject::LbrpPortModeEnum_to_string(x); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + Response read_lbrp_ports_by_id_handler( const char *name, const Key *keys, size_t num_keys ) { @@ -550,6 +567,31 @@ Response read_lbrp_ports_by_id_handler( } } +Response read_lbrp_ports_ip_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_portsName; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "ports_name")) { + unique_portsName = std::string { keys[i].value.string }; + break; + } + } + + + try { + + auto x = read_lbrp_ports_ip_by_id(unique_name, unique_portsName); + nlohmann::json response_body; + response_body = x; + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + Response read_lbrp_ports_list_by_id_handler( const char *name, const Key *keys, size_t num_keys ) { @@ -1299,6 +1341,23 @@ Response update_lbrp_list_by_id_handler( } } +Response update_lbrp_port_mode_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + LbrpPortModeEnum unique_value_ = LbrpJsonObject::string_to_LbrpPortModeEnum(request_body); + update_lbrp_port_mode_by_id(unique_name, unique_value_); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + Response update_lbrp_ports_by_id_handler( const char *name, const Key *keys, size_t num_keys , @@ -1327,6 +1386,32 @@ Response update_lbrp_ports_by_id_handler( } } +Response update_lbrp_ports_ip_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + std::string unique_portsName; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "ports_name")) { + unique_portsName = std::string { keys[i].value.string }; + break; + } + } + + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // The conversion is done automatically by the json library + std::string unique_value = request_body; + update_lbrp_ports_ip_by_id(unique_name, unique_portsName, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + Response update_lbrp_ports_list_by_id_handler( const char *name, const Key *keys, size_t num_keys , diff --git a/src/services/pcn-loadbalancer-rp/src/api/LbrpApi.h b/src/services/pcn-loadbalancer-rp/src/api/LbrpApi.h index 3c9c6e515..966ff649d 100644 --- a/src/services/pcn-loadbalancer-rp/src/api/LbrpApi.h +++ b/src/services/pcn-loadbalancer-rp/src/api/LbrpApi.h @@ -55,7 +55,9 @@ Response delete_lbrp_service_list_by_id_handler(const char *name, const Key *key Response delete_lbrp_src_ip_rewrite_by_id_handler(const char *name, const Key *keys, size_t num_keys); Response read_lbrp_by_id_handler(const char *name, const Key *keys, size_t num_keys); Response read_lbrp_list_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_lbrp_port_mode_by_id_handler(const char *name, const Key *keys, size_t num_keys); Response read_lbrp_ports_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_lbrp_ports_ip_by_id_handler(const char *name, const Key *keys, size_t num_keys); Response read_lbrp_ports_list_by_id_handler(const char *name, const Key *keys, size_t num_keys); Response read_lbrp_ports_type_by_id_handler(const char *name, const Key *keys, size_t num_keys); Response read_lbrp_service_backend_by_id_handler(const char *name, const Key *keys, size_t num_keys); @@ -79,7 +81,9 @@ Response replace_lbrp_service_list_by_id_handler(const char *name, const Key *ke Response replace_lbrp_src_ip_rewrite_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); Response update_lbrp_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); Response update_lbrp_list_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_lbrp_port_mode_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); Response update_lbrp_ports_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_lbrp_ports_ip_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); Response update_lbrp_ports_list_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); Response update_lbrp_ports_type_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); Response update_lbrp_service_backend_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); diff --git a/src/services/pcn-loadbalancer-rp/src/api/LbrpApiImpl.cpp b/src/services/pcn-loadbalancer-rp/src/api/LbrpApiImpl.cpp index b3b5b3cff..2ce03ef36 100644 --- a/src/services/pcn-loadbalancer-rp/src/api/LbrpApiImpl.cpp +++ b/src/services/pcn-loadbalancer-rp/src/api/LbrpApiImpl.cpp @@ -371,6 +371,23 @@ read_lbrp_by_id(const std::string &name) { } +/** +* @brief Read port_mode by ID +* +* Read operation of resource: port_mode* +* +* @param[in] name ID of name +* +* Responses: +* LbrpPortModeEnum +*/ +LbrpPortModeEnum +read_lbrp_port_mode_by_id(const std::string &name) { + auto lbrp = get_cube(name); + return lbrp->getPortMode(); + +} + /** * @brief Read ports by ID * @@ -389,6 +406,25 @@ read_lbrp_ports_by_id(const std::string &name, const std::string &portsName) { } +/** +* @brief Read ip by ID +* +* Read operation of resource: ip* +* +* @param[in] name ID of name +* @param[in] portsName ID of ports_name +* +* Responses: +* std::string +*/ +std::string +read_lbrp_ports_ip_by_id(const std::string &name, const std::string &portsName) { + auto lbrp = get_cube(name); + auto ports = lbrp->getPorts(portsName); + return ports->getIp(); + +} + /** * @brief Read ports by ID * @@ -825,6 +861,24 @@ update_lbrp_list_by_id(const std::vector &value) { throw std::runtime_error("Method not supported"); } +/** +* @brief Update port_mode by ID +* +* Update operation of resource: port_mode* +* +* @param[in] name ID of name +* @param[in] value lbrp mode of operation. 'MULTI' allows to manage multiple FRONTEND port. 'SINGLE' is optimized for working with a single FRONTEND port +* +* Responses: +* +*/ +void +update_lbrp_port_mode_by_id(const std::string &name, const LbrpPortModeEnum &value) { + auto lbrp = get_cube(name); + + return lbrp->setPortMode(value); +} + /** * @brief Update ports by ID * @@ -845,6 +899,26 @@ update_lbrp_ports_by_id(const std::string &name, const std::string &portsName, c ports->update(value); } +/** +* @brief Update ip by ID +* +* Update operation of resource: ip* +* +* @param[in] name ID of name +* @param[in] portsName ID of ports_name +* @param[in] value IP address of the client interface (only for FRONTEND port) +* +* Responses: +* +*/ +void +update_lbrp_ports_ip_by_id(const std::string &name, const std::string &portsName, const std::string &value) { + auto lbrp = get_cube(name); + auto ports = lbrp->getPorts(portsName); + + return ports->setIp(value); +} + /** * @brief Update ports by ID * diff --git a/src/services/pcn-loadbalancer-rp/src/api/LbrpApiImpl.h b/src/services/pcn-loadbalancer-rp/src/api/LbrpApiImpl.h index 6bcedfd32..f0dde2329 100644 --- a/src/services/pcn-loadbalancer-rp/src/api/LbrpApiImpl.h +++ b/src/services/pcn-loadbalancer-rp/src/api/LbrpApiImpl.h @@ -59,7 +59,9 @@ namespace LbrpApiImpl { void delete_lbrp_src_ip_rewrite_by_id(const std::string &name); LbrpJsonObject read_lbrp_by_id(const std::string &name); std::vector read_lbrp_list_by_id(); + LbrpPortModeEnum read_lbrp_port_mode_by_id(const std::string &name); PortsJsonObject read_lbrp_ports_by_id(const std::string &name, const std::string &portsName); + std::string read_lbrp_ports_ip_by_id(const std::string &name, const std::string &portsName); std::vector read_lbrp_ports_list_by_id(const std::string &name); PortsTypeEnum read_lbrp_ports_type_by_id(const std::string &name, const std::string &portsName); ServiceBackendJsonObject read_lbrp_service_backend_by_id(const std::string &name, const std::string &vip, const uint16_t &vport, const ServiceProtoEnum &proto, const std::string &ip); @@ -83,7 +85,9 @@ namespace LbrpApiImpl { void replace_lbrp_src_ip_rewrite_by_id(const std::string &name, const SrcIpRewriteJsonObject &value); void update_lbrp_by_id(const std::string &name, const LbrpJsonObject &value); void update_lbrp_list_by_id(const std::vector &value); + void update_lbrp_port_mode_by_id(const std::string &name, const LbrpPortModeEnum &value); void update_lbrp_ports_by_id(const std::string &name, const std::string &portsName, const PortsJsonObject &value); + void update_lbrp_ports_ip_by_id(const std::string &name, const std::string &portsName, const std::string &value); void update_lbrp_ports_list_by_id(const std::string &name, const std::vector &value); void update_lbrp_ports_type_by_id(const std::string &name, const std::string &portsName, const PortsTypeEnum &value); void update_lbrp_service_backend_by_id(const std::string &name, const std::string &vip, const uint16_t &vport, const ServiceProtoEnum &proto, const std::string &ip, const ServiceBackendJsonObject &value); diff --git a/src/services/pcn-loadbalancer-rp/src/interface/LbrpInterface.h b/src/services/pcn-loadbalancer-rp/src/interface/LbrpInterface.h index efefc1284..f394d7dc4 100644 --- a/src/services/pcn-loadbalancer-rp/src/interface/LbrpInterface.h +++ b/src/services/pcn-loadbalancer-rp/src/interface/LbrpInterface.h @@ -46,7 +46,16 @@ class LbrpInterface { virtual void delPortsList() = 0; /// - /// + /// LB mode of operation. + /// 'SINGLE' is optimized for working with a single FRONTEND port. + /// 'MULTI' allows to manage multiple FRONTEND port. + /// + virtual LbrpPortModeEnum getPortMode() = 0; + virtual void setPortMode(const LbrpPortModeEnum &value) = 0; + + /// + /// If configured, when a client request arrives to the LB, the source IP + /// address is replaced with another IP address from the 'new' range /// virtual std::shared_ptr getSrcIpRewrite() = 0; virtual void addSrcIpRewrite(const SrcIpRewriteJsonObject &value) = 0; diff --git a/src/services/pcn-loadbalancer-rp/src/interface/PortsInterface.h b/src/services/pcn-loadbalancer-rp/src/interface/PortsInterface.h index 66f754640..71eb63889 100644 --- a/src/services/pcn-loadbalancer-rp/src/interface/PortsInterface.h +++ b/src/services/pcn-loadbalancer-rp/src/interface/PortsInterface.h @@ -36,5 +36,11 @@ class PortsInterface { /// virtual PortsTypeEnum getType() = 0; virtual void setType(const PortsTypeEnum &value) = 0; + + /// + /// IP address of the client interface (only for FRONTEND port) + /// + virtual std::string getIp() = 0; + virtual void setIp(const std::string &value) = 0; }; diff --git a/src/services/pcn-loadbalancer-rp/src/serializer/LbrpJsonObject.cpp b/src/services/pcn-loadbalancer-rp/src/serializer/LbrpJsonObject.cpp index 7e42064bc..68190cf06 100644 --- a/src/services/pcn-loadbalancer-rp/src/serializer/LbrpJsonObject.cpp +++ b/src/services/pcn-loadbalancer-rp/src/serializer/LbrpJsonObject.cpp @@ -25,6 +25,8 @@ namespace model { LbrpJsonObject::LbrpJsonObject() { m_nameIsSet = false; m_portsIsSet = false; + m_portMode = LbrpPortModeEnum::SINGLE; + m_portModeIsSet = true; m_srcIpRewriteIsSet = false; m_serviceIsSet = false; } @@ -33,6 +35,7 @@ LbrpJsonObject::LbrpJsonObject(const nlohmann::json &val) : JsonObjectBase(val) { m_nameIsSet = false; m_portsIsSet = false; + m_portModeIsSet = false; m_srcIpRewriteIsSet = false; m_serviceIsSet = false; @@ -50,6 +53,10 @@ LbrpJsonObject::LbrpJsonObject(const nlohmann::json &val) : m_portsIsSet = true; } + if (val.count("port_mode")) { + setPortMode(string_to_LbrpPortModeEnum(val.at("port_mode").get())); + } + if (val.count("src-ip-rewrite")) { if (!val["src-ip-rewrite"].is_null()) { SrcIpRewriteJsonObject newItem { val["src-ip-rewrite"] }; @@ -88,6 +95,10 @@ nlohmann::json LbrpJsonObject::toJson() const { } } + if (m_portModeIsSet) { + val["port_mode"] = LbrpPortModeEnum_to_string(m_portMode); + } + if (m_srcIpRewriteIsSet) { val["src-ip-rewrite"] = JsonObjectBase::toJson(m_srcIpRewrite); } @@ -139,6 +150,42 @@ void LbrpJsonObject::unsetPorts() { m_portsIsSet = false; } + +LbrpPortModeEnum LbrpJsonObject::getPortMode() const { + return m_portMode; +} + +void LbrpJsonObject::setPortMode(LbrpPortModeEnum value) { + m_portMode = value; + m_portModeIsSet = true; +} + +bool LbrpJsonObject::portModeIsSet() const { + return m_portModeIsSet; +} + +void LbrpJsonObject::unsetPortMode() { + m_portModeIsSet = false; +} + +std::string LbrpJsonObject::LbrpPortModeEnum_to_string(const LbrpPortModeEnum &value){ + switch(value) { + case LbrpPortModeEnum::SINGLE: + return std::string("single"); + case LbrpPortModeEnum::MULTI: + return std::string("multi"); + default: + throw std::runtime_error("Bad Lbrp portMode"); + } +} + +LbrpPortModeEnum LbrpJsonObject::string_to_LbrpPortModeEnum(const std::string &str){ + if (JsonObjectBase::iequals("single", str)) + return LbrpPortModeEnum::SINGLE; + if (JsonObjectBase::iequals("multi", str)) + return LbrpPortModeEnum::MULTI; + throw std::runtime_error("Lbrp portMode is invalid"); +} SrcIpRewriteJsonObject LbrpJsonObject::getSrcIpRewrite() const { return m_srcIpRewrite; } diff --git a/src/services/pcn-loadbalancer-rp/src/serializer/LbrpJsonObject.h b/src/services/pcn-loadbalancer-rp/src/serializer/LbrpJsonObject.h index 0a281978a..e6a22593f 100644 --- a/src/services/pcn-loadbalancer-rp/src/serializer/LbrpJsonObject.h +++ b/src/services/pcn-loadbalancer-rp/src/serializer/LbrpJsonObject.h @@ -34,6 +34,9 @@ namespace swagger { namespace server { namespace model { +enum class LbrpPortModeEnum { + SINGLE, MULTI +}; /// /// @@ -62,7 +65,20 @@ class LbrpJsonObject : public JsonObjectBase { void unsetPorts(); /// - /// + /// LB mode of operation. + /// 'SINGLE' is optimized for working with a single FRONTEND port. + /// 'MULTI' allows to manage multiple FRONTEND port. + /// + LbrpPortModeEnum getPortMode() const; + void setPortMode(LbrpPortModeEnum value); + bool portModeIsSet() const; + void unsetPortMode(); + static std::string LbrpPortModeEnum_to_string(const LbrpPortModeEnum &value); + static LbrpPortModeEnum string_to_LbrpPortModeEnum(const std::string &str); + + /// + /// If configured, when a client request arrives to the LB, the source IP + /// address is replaced with another IP address from the 'new' range /// SrcIpRewriteJsonObject getSrcIpRewrite() const; void setSrcIpRewrite(SrcIpRewriteJsonObject value); @@ -82,6 +98,8 @@ class LbrpJsonObject : public JsonObjectBase { bool m_nameIsSet; std::vector m_ports; bool m_portsIsSet; + LbrpPortModeEnum m_portMode; + bool m_portModeIsSet; SrcIpRewriteJsonObject m_srcIpRewrite; bool m_srcIpRewriteIsSet; std::vector m_service; diff --git a/src/services/pcn-loadbalancer-rp/src/serializer/PortsJsonObject.cpp b/src/services/pcn-loadbalancer-rp/src/serializer/PortsJsonObject.cpp index c3f3cef0e..ddf7065ff 100644 --- a/src/services/pcn-loadbalancer-rp/src/serializer/PortsJsonObject.cpp +++ b/src/services/pcn-loadbalancer-rp/src/serializer/PortsJsonObject.cpp @@ -25,12 +25,14 @@ namespace model { PortsJsonObject::PortsJsonObject() { m_nameIsSet = false; m_typeIsSet = false; + m_ipIsSet = false; } PortsJsonObject::PortsJsonObject(const nlohmann::json &val) : JsonObjectBase(val) { m_nameIsSet = false; m_typeIsSet = false; + m_ipIsSet = false; if (val.count("name")) { @@ -40,6 +42,10 @@ PortsJsonObject::PortsJsonObject(const nlohmann::json &val) : if (val.count("type")) { setType(string_to_PortsTypeEnum(val.at("type").get())); } + + if (val.count("ip")) { + setIp(val.at("ip").get()); + } } nlohmann::json PortsJsonObject::toJson() const { @@ -56,6 +62,10 @@ nlohmann::json PortsJsonObject::toJson() const { val["type"] = PortsTypeEnum_to_string(m_type); } + if (m_ipIsSet) { + val["ip"] = m_ip; + } + return val; } @@ -107,6 +117,22 @@ PortsTypeEnum PortsJsonObject::string_to_PortsTypeEnum(const std::string &str){ return PortsTypeEnum::BACKEND; throw std::runtime_error("Ports type is invalid"); } +std::string PortsJsonObject::getIp() const { + return m_ip; +} + +void PortsJsonObject::setIp(std::string value) { + m_ip = value; + m_ipIsSet = true; +} + +bool PortsJsonObject::ipIsSet() const { + return m_ipIsSet; +} + +void PortsJsonObject::unsetIp() { + m_ipIsSet = false; +} } } diff --git a/src/services/pcn-loadbalancer-rp/src/serializer/PortsJsonObject.h b/src/services/pcn-loadbalancer-rp/src/serializer/PortsJsonObject.h index 7bba54c50..f6325bad7 100644 --- a/src/services/pcn-loadbalancer-rp/src/serializer/PortsJsonObject.h +++ b/src/services/pcn-loadbalancer-rp/src/serializer/PortsJsonObject.h @@ -60,11 +60,20 @@ class PortsJsonObject : public JsonObjectBase { static std::string PortsTypeEnum_to_string(const PortsTypeEnum &value); static PortsTypeEnum string_to_PortsTypeEnum(const std::string &str); + /// + /// IP address of the client interface (only for FRONTEND port) + /// + std::string getIp() const; + void setIp(std::string value); + bool ipIsSet() const; + void unsetIp(); private: std::string m_name; bool m_nameIsSet; PortsTypeEnum m_type; bool m_typeIsSet; + std::string m_ip; + bool m_ipIsSet; }; } diff --git a/src/services/pcn-loadbalancer-rp/test/helpers.bash b/src/services/pcn-loadbalancer-rp/test/helpers.bash new file mode 100644 index 000000000..886cc3071 --- /dev/null +++ b/src/services/pcn-loadbalancer-rp/test/helpers.bash @@ -0,0 +1,40 @@ +function create_veth { + # Create new namespace + sudo ip netns add ns${i} + # Add new veth interface + sudo ip link add veth${i} type veth peer name veth${i}_ netns ns${i} + # Enable veth on both root and newly created namespace + sudo ip netns exec ns${i} ip link set dev veth${i}_ up + sudo ip link set dev veth${i} up +} + +function test_weight { + # Test service and service backends creation and deletion. Test also different backend weights + polycubectl lbrp lb0 service add 10.0.0.1 80 TCP name=my-service + polycubectl lbrp lb0 service 10.0.0.1 80 TCP backend add 192.178.1.11 name=backend1 port=80 weight=20 + polycubectl lbrp lb0 service 10.0.0.1 80 TCP backend add 192.178.1.12 name=backend2 port=80 weight=30 + polycubectl lbrp lb0 service 10.0.0.1 80 TCP backend add 192.178.1.13 name=backend3 port=80 weight=20 + polycubectl lbrp lb0 service 10.0.0.1 80 TCP backend show + polycubectl lbrp lb0 service 10.0.0.1 80 TCP show + polycubectl lbrp lb0 show + polycubectl lbrp lb0 service 10.0.0.1 80 TCP backend del 192.178.1.11 + polycubectl lbrp lb0 service 10.0.0.1 80 TCP backend del 192.178.1.13 + polycubectl lbrp lb0 service 10.0.0.1 80 TCP backend del 192.178.1.12 + polycubectl lbrp lb0 service 10.0.0.1 80 TCP backend show + polycubectl lbrp lb0 service 10.0.0.1 80 TCP show + polycubectl lbrp lb0 service del 10.0.0.1 80 TCP + polycubectl lbrp lb0 show +} + +function test_service_reachability { + polycubectl lb0 service add 10.0.0.1 80 ALL name=my-service + polycubectl lb0 service 10.0.0.1 80 tcp backend add 192.178.1.2 port=80 weight=20 name=backend-tcp + polycubectl lb0 service 10.0.0.1 80 udp backend add 192.178.1.2 port=80 weight=20 name=backend-udp + polycubectl lb0 service 10.0.0.1 0 icmp backend add 192.178.1.2 port=0 weight=20 name=backend-icmp + + sudo ip netns exec ns1 ping 10.0.0.1 -c 2 -w 2 + + polycubectl lbrp lb0 service del 10.0.0.1 80 TCP + + polycubectl lbrp lb0 show +} \ No newline at end of file diff --git a/src/services/pcn-loadbalancer-rp/test/test_multi.sh b/src/services/pcn-loadbalancer-rp/test/test_multi.sh new file mode 100755 index 000000000..4881dcfbd --- /dev/null +++ b/src/services/pcn-loadbalancer-rp/test/test_multi.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# include helper.bash file: used to provide some common function across testing scripts +source "${BASH_SOURCE%/*}/helpers.bash" + +function cleanup { + set +e + polycubectl lbrp del lb0 + for i in `seq 1 3`; do + sudo ip link del veth${i} + sudo ip netns del ns${i} + done +} +trap cleanup EXIT + +set -x +set -e + +polycubectl lbrp add lb0 port_mode=MULTI loglevel=TRACE + +for i in `seq 1 3`; do + create_veth ${i} + + if [[ "$i" -eq 3 ]]; then + sudo ip netns exec ns${i} ip addr add 192.178.1.254/24 dev veth${i}_ + polycubectl lb0 ports add to_ns${i} type=BACKEND peer=veth${i} + else + sudo ip netns exec ns${i} ip addr add 192.178.1.${i}/32 dev veth${i}_ peer 192.178.1.254/32 + sudo ip netns exec ns${i} ip route add default via 192.178.1.254 + polycubectl lb0 ports add to_ns${i} type=FRONTEND peer=veth${i} ip=192.178.1.${i} + fi +done + + +# Test basic connectivity +sudo ip netns exec ns1 ping 192.178.1.254 -c 2 -w 2 +sudo ip netns exec ns2 ping 192.178.1.254 -c 2 -w 2 +sudo ip netns exec ns3 ping 192.178.1.1 -c 2 -w 2 +sudo ip netns exec ns3 ping 192.178.1.2 -c 2 -w 2 + +test_weight + +test_service_reachability diff --git a/src/services/pcn-loadbalancer-rp/test/test_single.sh b/src/services/pcn-loadbalancer-rp/test/test_single.sh new file mode 100755 index 000000000..3dc5e944a --- /dev/null +++ b/src/services/pcn-loadbalancer-rp/test/test_single.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# include helper.bash file: used to provide some common function across testing scripts +source "${BASH_SOURCE%/*}/helpers.bash" + +function cleanup { + set +e + polycubectl lbrp del lb0 + for i in `seq 1 2`; do + sudo ip link del veth${i} + sudo ip netns del ns${i} + done +} +trap cleanup EXIT + +set -x +set -e + +polycubectl lbrp add lb0 port_mode=SINGLE loglevel=TRACE + +for i in `seq 1 2`; do + create_veth ${i} + sudo ip netns exec ns${i} ip addr add 192.178.1.${i}/24 dev veth${i}_ + if [[ "$i" -eq 2 ]]; then + polycubectl lb0 ports add to_ns${i} type=BACKEND peer=veth${i} + else + polycubectl lb0 ports add to_ns${i} type=FRONTEND peer=veth${i} + fi +done + +# Add client default route in order to test connectivity +sudo ip netns exec ns1 ip route add default via 192.178.1.2 + +# Test basic connectivity +sudo ip netns exec ns1 ping 192.178.1.2 -c 2 -w 2 +sudo ip netns exec ns2 ping 192.178.1.1 -c 2 -w 2 + +test_weight + +test_service_reachability diff --git a/src/services/pcn-loadbalancer-rp/test/test_weight.sh b/src/services/pcn-loadbalancer-rp/test/test_weight.sh deleted file mode 100755 index f8a3a58d2..000000000 --- a/src/services/pcn-loadbalancer-rp/test/test_weight.sh +++ /dev/null @@ -1,60 +0,0 @@ -echo " #####Test##### " - -polycubectl lbrp add lb1 loglevel=TRACE -polycubectl lbrp lb1 service add 192.168.0.1 80 TCP name=service_1 -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend add 10.0.0.1 name=backend1 port=80 weight=20 -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend add 10.0.0.2 name=backend2 port=80 weight=30 -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend add 10.0.0.3 name=backend3 port=80 weight=20 -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend add 10.0.0.4 name=backend4 port=80 - -sleep 10 - -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend add 10.0.0.5 name=backend5 port=80 weight=100 -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend add 10.0.0.6 name=backend6 port=80 weight=40 - -sleep 10 - -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend add 10.0.0.7 name=backend7 port=80 -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend add 10.0.0.8 name=backend8 port=80 weight=20 - -sleep 10 - -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend del 10.0.0.2 -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend del 10.0.0.5 -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend del 10.0.0.1 -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend del 10.0.0.3 -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend del 10.0.0.4 -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend del 10.0.0.6 -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend del 10.0.0.8 -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend del 10.0.0.7 - - -sleep 10 - -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend show -polycubectl lbrp lb1 service 192.168.0.1 80 TCP show - - - -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend add 10.0.0.3 name=backend3 port=80 weight=20 -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend add 10.0.0.4 name=backend4 port=80 - -sleep 10 - -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend add 10.0.0.5 name=backend5 port=80 weight=60 -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend add 10.0.0.6 name=backend6 port=80 weight=40 - -polycubectl lbrp lb1 service 192.168.0.1 80 TCP backend show -polycubectl lbrp lb1 service 192.168.0.1 80 TCP show - -sleep 5 - -polycubectl lbrp lb1 service del 192.168.0.1 80 TCP - -polycubectl lbrp lb1 show - - - - - - From 76616a02e20c0e080f6f32b88dbb5a38f371b446 Mon Sep 17 00:00:00 2001 From: Leonardo Di Giovanna Date: Tue, 21 Jun 2022 13:55:34 +0200 Subject: [PATCH 3/3] Add pcn-k8sdispatcher service Signed-off-by: Leonardo Di Giovanna --- AUTHORS | 1 + .../services/pcn-k8sdispatcher/egress.png | Bin 0 -> 54042 bytes .../services/pcn-k8sdispatcher/ingress.png | Bin 0 -> 63865 bytes .../pcn-k8sdispatcher/k8sdispatcher.md | 40 + scripts/install.sh | 6 +- src/services/CMakeLists.txt | 1 + .../pcn-k8sdispatcher/.swagger-codegen-ignore | 13 + src/services/pcn-k8sdispatcher/CMakeLists.txt | 5 + .../datamodel/k8sdispatcher.yang | 173 +++ .../pcn-k8sdispatcher/src/CMakeLists.txt | 50 + .../pcn-k8sdispatcher/src/HashTuple.h | 42 + .../src/K8sdispatcher-lib.cpp | 21 + .../pcn-k8sdispatcher/src/K8sdispatcher.cpp | 497 +++++++ .../pcn-k8sdispatcher/src/K8sdispatcher.h | 139 ++ .../pcn-k8sdispatcher/src/K8sdispatcher_dp.c | 521 +++++++ .../pcn-k8sdispatcher/src/NodeportRule.cpp | 99 ++ .../pcn-k8sdispatcher/src/NodeportRule.h | 59 + src/services/pcn-k8sdispatcher/src/Ports.cpp | 57 + src/services/pcn-k8sdispatcher/src/Ports.h | 45 + .../pcn-k8sdispatcher/src/SessionRule.cpp | 106 ++ .../pcn-k8sdispatcher/src/SessionRule.h | 111 ++ src/services/pcn-k8sdispatcher/src/Utils.cpp | 129 ++ src/services/pcn-k8sdispatcher/src/Utils.h | 48 + .../src/api/K8sdispatcherApi.cpp | 1271 +++++++++++++++++ .../src/api/K8sdispatcherApi.h | 86 ++ .../src/api/K8sdispatcherApiImpl.cpp | 855 +++++++++++ .../src/api/K8sdispatcherApiImpl.h | 90 ++ .../src/base/K8sdispatcherBase.cpp | 171 +++ .../src/base/K8sdispatcherBase.h | 91 ++ .../src/base/NodeportRuleBase.cpp | 42 + .../src/base/NodeportRuleBase.h | 65 + .../pcn-k8sdispatcher/src/base/PortsBase.cpp | 41 + .../pcn-k8sdispatcher/src/base/PortsBase.h | 59 + .../src/base/SessionRuleBase.cpp | 45 + .../src/base/SessionRuleBase.h | 94 ++ .../src/serializer/JsonObjectBase.cpp | 69 + .../src/serializer/JsonObjectBase.h | 55 + .../serializer/K8sdispatcherJsonObject.cpp | 239 ++++ .../src/serializer/K8sdispatcherJsonObject.h | 108 ++ .../src/serializer/NodeportRuleJsonObject.cpp | 186 +++ .../src/serializer/NodeportRuleJsonObject.h | 96 ++ .../src/serializer/PortsJsonObject.cpp | 136 ++ .../src/serializer/PortsJsonObject.h | 79 + .../src/serializer/SessionRuleJsonObject.cpp | 375 +++++ .../src/serializer/SessionRuleJsonObject.h | 162 +++ src/services/pcn-k8sdispatcher/test/test.sh | 24 + 46 files changed, 6600 insertions(+), 2 deletions(-) create mode 100644 Documentation/services/pcn-k8sdispatcher/egress.png create mode 100644 Documentation/services/pcn-k8sdispatcher/ingress.png create mode 100644 Documentation/services/pcn-k8sdispatcher/k8sdispatcher.md create mode 100644 src/services/pcn-k8sdispatcher/.swagger-codegen-ignore create mode 100644 src/services/pcn-k8sdispatcher/CMakeLists.txt create mode 100644 src/services/pcn-k8sdispatcher/datamodel/k8sdispatcher.yang create mode 100644 src/services/pcn-k8sdispatcher/src/CMakeLists.txt create mode 100644 src/services/pcn-k8sdispatcher/src/HashTuple.h create mode 100644 src/services/pcn-k8sdispatcher/src/K8sdispatcher-lib.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/K8sdispatcher.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/K8sdispatcher.h create mode 100644 src/services/pcn-k8sdispatcher/src/K8sdispatcher_dp.c create mode 100644 src/services/pcn-k8sdispatcher/src/NodeportRule.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/NodeportRule.h create mode 100644 src/services/pcn-k8sdispatcher/src/Ports.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/Ports.h create mode 100644 src/services/pcn-k8sdispatcher/src/SessionRule.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/SessionRule.h create mode 100644 src/services/pcn-k8sdispatcher/src/Utils.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/Utils.h create mode 100644 src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApi.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApi.h create mode 100644 src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApiImpl.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApiImpl.h create mode 100644 src/services/pcn-k8sdispatcher/src/base/K8sdispatcherBase.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/base/K8sdispatcherBase.h create mode 100644 src/services/pcn-k8sdispatcher/src/base/NodeportRuleBase.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/base/NodeportRuleBase.h create mode 100644 src/services/pcn-k8sdispatcher/src/base/PortsBase.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/base/PortsBase.h create mode 100644 src/services/pcn-k8sdispatcher/src/base/SessionRuleBase.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/base/SessionRuleBase.h create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/JsonObjectBase.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/JsonObjectBase.h create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/K8sdispatcherJsonObject.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/K8sdispatcherJsonObject.h create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/NodeportRuleJsonObject.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/NodeportRuleJsonObject.h create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/PortsJsonObject.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/PortsJsonObject.h create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/SessionRuleJsonObject.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/SessionRuleJsonObject.h create mode 100755 src/services/pcn-k8sdispatcher/test/test.sh diff --git a/AUTHORS b/AUTHORS index dd5f4889b..b5b9ad058 100644 --- a/AUTHORS +++ b/AUTHORS @@ -10,6 +10,7 @@ off on commits in the Polycube repository: Gianluca Scopelliti gianlu.1033@gmail.com Giuseppe Ognibebe ognibenegiuseppe8@gmail.com Jianwen Pi jianwpi@gmail.com + Leonardo Di Giovanna leonardodigiovanna1@gmail.com Matteo Bertrone m.bertrone@gmail.com Mauricio Vásquez Bernal mauriciovasquezbernal@gmail.com Nico Caprioli nico.caprioli@gmail.com diff --git a/Documentation/services/pcn-k8sdispatcher/egress.png b/Documentation/services/pcn-k8sdispatcher/egress.png new file mode 100644 index 0000000000000000000000000000000000000000..761c89309528c10a7a097637b45026192f1621bd GIT binary patch literal 54042 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 literal 0 HcmV?d00001 diff --git a/Documentation/services/pcn-k8sdispatcher/ingress.png b/Documentation/services/pcn-k8sdispatcher/ingress.png new file mode 100644 index 0000000000000000000000000000000000000000..59aa9b2732a9ac2887626ae72c7c08ccc89e15ee GIT binary patch literal 63865 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ð9rB4GP^WOA-m39^_jNh7b$|`3^_;o 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 W@%XqWM(pn*1%70r3!xkzaP}`2yj)uV literal 0 HcmV?d00001 diff --git a/Documentation/services/pcn-k8sdispatcher/k8sdispatcher.md b/Documentation/services/pcn-k8sdispatcher/k8sdispatcher.md new file mode 100644 index 000000000..2e8f50af1 --- /dev/null +++ b/Documentation/services/pcn-k8sdispatcher/k8sdispatcher.md @@ -0,0 +1,40 @@ +# K8dispatcher + +The ``pcn-k8sdispatcher`` service is specifically designed as part of our Kubernetes networking solution (please see [polykube](https://github.com/polycube-network/polykube) to get more information about it). The service provides an eBPF implementation of a custom NAT: it performs different actions depending on the type and on the direction of the traffic. + +For Egress Traffic, the following flow chart can be used to explain the functioning of the service: + +![K8sdispatcher egress flow chart](egress.png) + +The Egress Traffic is the traffic generated by Pods and directed to the external world. +This traffic can be generated by an internal Pod that wants to contact the external world or as a response to an +external world request. For this traffic, the service maintains an egress session table containing information +about the active egress sessions. The first time a Pod wants to contact the external world, no active egress session +will be present in the table: in this scenario, the service performs SNAT, replacing the address of the Pod +with the address of the node, and creates entries in the ingress and egress session table accordingly. +If the outgoing traffic is generated as a response to an external request, it can only be originated as a response to +a request made to a NodePort Service. For traffic related to NodePort Services with a CLUSTER ExternalTrafficPolicy, +if an egress session table hit happens, the destination IP address and port are replaced accordingly to the session data. +The traffic related to NodePort Services with a LOCAL ExternalTrafficPolicy is forwarded as it is to the next cube. + +For Ingress Traffic, the following flow chart can be used to explain the functioning of the service: + +![K8sdispatcher ingress flow chart](ingress.png) + +The Ingress Traffic can be differentiated in traffic directed to the host (either directly or because it needs VxLAN +processing) and traffic directed to Pods. The traffic directed to Pods can be the traffic generated by an external host +trying to contact a NodePort service or the return traffic generated by an external host providing a response to an +internal Pod request. The service uses an ingress session table containing all the active ingress sessions. +If a session table hit happens, the service apply NAT according to the session data. If no session table entry is +associated with the incoming packet, the service tries to determine if a NodePort rule matches the packet +characteristics. In case of no NodePort rule matching, the packet is sent to the Linux stack for further processing. +In case of NodePort rule matching, different actions are applied according to the ExternalTrafficPolicy of the +Kubernetes NodePort Service associated to the rule. If the policy is LOCAL, the traffic is allowed to reach only +backend Pods located on the current node: in this case the packet can proceed towards the Pod without modifications. +In case the policy is CLUSTER, the packet can also reach backend Pods located on other nodes: since later in +the chain the packet will be processed by a load balancer and the return packet will have to transit through +the same load balancer, SNAT is applied by replacing the source IP address with a specific reserved address belonging +to the Pod CIDR of the node on which the k8sdispatcher is deployed. In this way the two nodes (the one that +receives the request and the one running the selected backend Pod) will exchange the packets of the flow over +the VxLAN interconnect. In this latter case, corresponding session entries are stored into the ingress and egress +sessions tables. \ No newline at end of file diff --git a/scripts/install.sh b/scripts/install.sh index d3ade0dce..5275260f7 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -125,7 +125,8 @@ if [ "$MODE" == "pcn-iptables" ]; then -DENABLE_SERVICE_SIMPLEFORWARDER=OFF \ -DENABLE_SERVICE_TRANSPARENTHELLOWORLD=OFF \ -DENABLE_SERVICE_SYNFLOOD=OFF \ - -DENABLE_SERVICE_PACKETCAPTURE=OFF + -DENABLE_SERVICE_PACKETCAPTURE=OFF \ + -DENABLE_SERVICE_K8SDISPATCHER=OFF elif [ "$MODE" == "pcn-k8s" ]; then cmake .. -DENABLE_SERVICE_BRIDGE=OFF \ -DENABLE_SERVICE_DDOSMITIGATOR=ON \ @@ -143,7 +144,8 @@ elif [ "$MODE" == "pcn-k8s" ]; then -DENABLE_SERVICE_SIMPLEFORWARDER=OFF \ -DENABLE_SERVICE_TRANSPARENTHELLOWORLD=OFF \ -DENABLE_SERVICE_SYNFLOOD=OFF \ - -DENABLE_SERVICE_PACKETCAPTURE=ON + -DENABLE_SERVICE_PACKETCAPTURE=ON \ + -DENABLE_SERVICE_K8SDISPATCHER=OFF else cmake .. -DENABLE_PCN_IPTABLES=ON fi diff --git a/src/services/CMakeLists.txt b/src/services/CMakeLists.txt index 55ca7e4ef..5f450cd5c 100644 --- a/src/services/CMakeLists.txt +++ b/src/services/CMakeLists.txt @@ -37,6 +37,7 @@ add_service(transparenthelloworld pcn-transparent-helloworld) add_service(synflood pcn-synflood) add_service(packetcapture pcn-packetcapture) add_service(dynmon pcn-dynmon) +add_service(k8sdispatcher pcn-k8sdispatcher) # save string to create code that load the services SET_PROPERTY(GLOBAL PROPERTY LOAD_SERVICES_ ${LOAD_SERVICES}) diff --git a/src/services/pcn-k8sdispatcher/.swagger-codegen-ignore b/src/services/pcn-k8sdispatcher/.swagger-codegen-ignore new file mode 100644 index 000000000..1bc8b75ce --- /dev/null +++ b/src/services/pcn-k8sdispatcher/.swagger-codegen-ignore @@ -0,0 +1,13 @@ +# Swagger Codegen Ignore +# Generated by swagger-codegen https://github.com/swagger-api/swagger-codegen + +# Use this file to prevent files from being overwritten by the generator. + +.swagger-codegen-ignore + +src/*.cpp +src/*.h + +!src/*Interface.h +!src/*JsonObject.h +!src/*JsonObject.cpp diff --git a/src/services/pcn-k8sdispatcher/CMakeLists.txt b/src/services/pcn-k8sdispatcher/CMakeLists.txt new file mode 100644 index 000000000..9857094f7 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required (VERSION 3.2) + +set (CMAKE_CXX_STANDARD 11) + +add_subdirectory(src) diff --git a/src/services/pcn-k8sdispatcher/datamodel/k8sdispatcher.yang b/src/services/pcn-k8sdispatcher/datamodel/k8sdispatcher.yang new file mode 100644 index 000000000..ae36d590d --- /dev/null +++ b/src/services/pcn-k8sdispatcher/datamodel/k8sdispatcher.yang @@ -0,0 +1,173 @@ +module k8sdispatcher { + yang-version 1.1; + namespace "http://polycube.network/k8sdispatcher"; + prefix "k8sdispatcher"; + + import polycube-base { prefix "polycube-base"; } + import polycube-standard-base { prefix "polycube-standard-base"; } + + import ietf-inet-types { prefix "inet"; } + + organization "Polycube open source project"; + description "YANG data model for the Polycube K8s Dispatcher"; + + polycube-base:service-description "K8s Dispatcher Service"; + polycube-base:service-version "2.0.0"; + polycube-base:service-name "k8sdispatcher"; + polycube-base:service-min-kernel-version "4.14.0"; + + typedef l4-proto { + type enumeration { + enum "TCP" { + value 6; + description "The TCP protocol type"; + } + enum "UDP" { + value 17; + description "The UDP protocol type"; + } + enum "ICMP" { + value 1; + description "The ICMP protocol type"; + } + } + description "L4 protocol"; + } + + uses "polycube-standard-base:standard-base-yang-module" { + augment ports { + leaf type { + type enumeration { + enum BACKEND { description "Port connected to the internal CNI topology"; } + enum FRONTEND { description "Port connected to the node NIC"; } + } + description "Type of the K8s Dispatcher cube port (e.g. BACKEND or FRONTEND)"; + mandatory true; + polycube-base:init-only-config; + } + leaf ip { + type inet:ipv4-address; + description "IP address of the node interface (only for FRONTEND port)"; + polycube-base:cli-example "10.10.1.1"; + polycube-base:init-only-config; + } + } + } + + leaf internal-src-ip { + type inet:ipv4-address; + description "Internal source IP address used for natting incoming packets directed to Kubernetes Services with a CLUSTER external traffic policy"; + mandatory true; + polycube-base:cli-example "10.10.1.1"; + polycube-base:init-only-config; + } + + leaf nodeport-range { + type string; + description "Port range used for NodePort Services"; + default "30000-32767"; + polycube-base:cli-example "30000-32767"; + } + + list session-rule { + key "direction src-ip dst-ip src-port dst-port proto"; + description "Session entry related to a specific traffic direction"; + config false; + + leaf direction { + type enumeration { + enum INGRESS { + description "Direction of traffic going from the internal topology to the external world"; + } + enum EGRESS { + description "Direction of traffic going from the external world to the internal CNI topology"; + } + } + description "Session entry direction (e.g. INGRESS or EGRESS)"; + } + leaf src-ip { + type inet:ipv4-address; + description "Session entry source IP address"; + } + leaf dst-ip { + type inet:ipv4-address; + description "Session entry destination IP address"; + } + leaf src-port { + type inet:port-number; + description "Session entry source L4 port number"; + } + leaf dst-port { + type inet:port-number; + description "Session entry destination L4 port number"; + } + leaf proto { + type l4-proto; + description "Session entry L4 protocol"; + polycube-base:cli-example "TCP, UDP, ICMP"; + } + + leaf new-ip { + type inet:ipv4-address; + description "Translated IP address"; + config false; + } + leaf new-port { + type inet:port-number; + description "Translated L4 port number"; + config false; + } + leaf operation { + type enumeration { + enum XLATE_SRC { description "The source IP and port are replaced"; } + enum XLATE_DST { description "The destination IP and port are replaced"; } + } + description "Operation applied on the original packet"; + config false; + } + leaf originating-rule { + type enumeration { + enum POD_TO_EXT { + description "Traffic related to communication between a Pod and the external world"; + } + enum NODEPORT_CLUSTER { + description "Traffic related to communication involving a NodePort Service with having a CLUSTER external traffic policy"; + } + } + description "Rule originating the session entry"; + config false; + } + } + + list nodeport-rule { + key "nodeport-port proto"; + description "NodePort rule associated with a Kubernetes NodePort Service"; + + leaf nodeport-port { + type inet:port-number; + description "NodePort rule nodeport port number"; + polycube-base:cli-example "30500"; + } + leaf proto { + type l4-proto; + description "NodePort rule L4 protocol"; + polycube-base:cli-example "TCP, UDP, ICMP"; + } + + leaf external-traffic-policy { + type enumeration { + enum LOCAL { description "Incoming traffic is allowed to be served only by local backends"; } + enum CLUSTER { description "Incoming traffic is allowed to be served by any backend of the cluster"; } + } + default CLUSTER; + description "The external traffic policy of the Kubernetes NodePort Service"; + } + leaf rule-name { + type string; + description "An optional name for the NodePort rule"; + polycube-base:cli-example "my-nodeport-rule"; + polycube-base:init-only-config; + } + } +} + diff --git a/src/services/pcn-k8sdispatcher/src/CMakeLists.txt b/src/services/pcn-k8sdispatcher/src/CMakeLists.txt new file mode 100644 index 000000000..7acb5ccfb --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/CMakeLists.txt @@ -0,0 +1,50 @@ +include(${PROJECT_SOURCE_DIR}/cmake/LoadFileAsVariable.cmake) + +aux_source_directory(serializer SERIALIZER_SOURCES) +aux_source_directory(api API_SOURCES) +aux_source_directory(base BASE_SOURCES) + +include_directories(serializer) + +if (NOT DEFINED POLYCUBE_STANDALONE_SERVICE OR POLYCUBE_STANDALONE_SERVICE) + find_package(PkgConfig REQUIRED) + pkg_check_modules(POLYCUBE libpolycube) + include_directories(${POLYCUBE_INCLUDE_DIRS}) +endif (NOT DEFINED POLYCUBE_STANDALONE_SERVICE OR POLYCUBE_STANDALONE_SERVICE) + +# Needed to load files as variables +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +add_library(pcn-k8sdispatcher SHARED + ${SERIALIZER_SOURCES} + ${API_SOURCES} + ${BASE_SOURCES} + K8sdispatcher.cpp + NodeportRule.cpp + Ports.cpp + SessionRule.cpp + K8sdispatcher-lib.cpp + Utils.cpp) + +# load ebpf datapath code a variable +load_file_as_variable(pcn-k8sdispatcher + K8sdispatcher_dp.c + k8sdispatcher_code) + +# load datamodel in a variable +load_file_as_variable(pcn-k8sdispatcher + ../datamodel/k8sdispatcher.yang + k8sdispatcher_datamodel) + +target_link_libraries(pcn-k8sdispatcher ${POLYCUBE_LIBRARIES}) + +# Specify shared library install directory + +set(CMAKE_INSTALL_LIBDIR /usr/lib) + +install( + TARGETS + pcn-k8sdispatcher + DESTINATION + "${CMAKE_INSTALL_LIBDIR}" +) diff --git a/src/services/pcn-k8sdispatcher/src/HashTuple.h b/src/services/pcn-k8sdispatcher/src/HashTuple.h new file mode 100644 index 000000000..e72cb2897 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/HashTuple.h @@ -0,0 +1,42 @@ +#include +// function has to live in the std namespace +// so that it is picked up by argument-dependent name lookup (ADL). +namespace std { +namespace { +// Code from boost +// Reciprocal of the golden ratio helps spread entropy +// and handles duplicates. +// See Mike Seymour in magic-numbers-in-boosthash-combine: +// https://stackoverflow.com/questions/4948780 + +template +inline void hash_combine(std::size_t &seed, T const &v) { + seed ^= hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +// Recursive template code derived from Matthieu M. +template ::value - 1> +struct HashValueImpl { + static void apply(size_t &seed, Tuple const &tuple) { + HashValueImpl::apply(seed, tuple); + hash_combine(seed, get(tuple)); + } +}; + +template +struct HashValueImpl { + static void apply(size_t &seed, Tuple const &tuple) { + hash_combine(seed, get<0>(tuple)); + } +}; +} + +template +struct hash> { + size_t operator()(std::tuple const &tt) const { + size_t seed = 0; + HashValueImpl>::apply(seed, tt); + return seed; + } +}; +} \ No newline at end of file diff --git a/src/services/pcn-k8sdispatcher/src/K8sdispatcher-lib.cpp b/src/services/pcn-k8sdispatcher/src/K8sdispatcher-lib.cpp new file mode 100644 index 000000000..8c9bc73b6 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/K8sdispatcher-lib.cpp @@ -0,0 +1,21 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +#include "api/K8sdispatcherApiImpl.h" +#include "../datamodel/k8sdispatcher.h" // generated from datamodel + +#define SERVICE_PYANG_GIT "" +#define SERVICE_SWAGGER_CODEGEN_GIT "GIT_REPO_ID" + +#include + +extern "C" const char *data_model() { + return k8sdispatcher_datamodel.c_str(); +} diff --git a/src/services/pcn-k8sdispatcher/src/K8sdispatcher.cpp b/src/services/pcn-k8sdispatcher/src/K8sdispatcher.cpp new file mode 100644 index 000000000..3bd8c588b --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/K8sdispatcher.cpp @@ -0,0 +1,497 @@ +/* + * Copyright 2022 The Polycube Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "K8sdispatcher.h" + +#include +#include + +#include "K8sdispatcher_dp.h" +#include "Utils.h" + +using namespace Tins; +namespace poly_utils = polycube::service::utils; + +const std::string K8sdispatcher::EBPF_EGRESS_SESSION_TABLE = + "egress_session_table"; +const std::string K8sdispatcher::EBPF_INGRESS_SESSION_TABLE = + "ingress_session_table"; +const std::string K8sdispatcher::EBPF_NPR_TABLE_MAP = "npr_table"; + +K8sdispatcher::K8sdispatcher(const std::string name, + const K8sdispatcherJsonObject &conf) + : Cube(conf.getBase(), {k8sdispatcher_code}, {}), + K8sdispatcherBase(name), + internalSrcIp_{conf.getInternalSrcIp()}, + internalSrcIpNboInt_{poly_utils::ip_string_to_nbo_uint(internalSrcIp_)}, + nodeportRange_{"30000-32767"}, + nodeportRangeTuple_{30000, 32767}, + nodeIpNboInt_{0} { + logger()->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [K8sDispatcher] [%n] [%l] %v"); + logger()->info("Creating K8sDispatcher instance"); + + if (conf.nodeportRangeIsSet()) { + this->setNodeportRange(conf.getNodeportRange()); + } + this->addNodeportRuleList(conf.getNodeportRule()); + this->addPortsList(conf.getPorts()); + + logger()->trace("Created K8sDispatcher instance"); +} + +K8sdispatcher::~K8sdispatcher() { + logger()->info("Destroying K8sDispatcher instance"); +} + +void K8sdispatcher::packet_in(Ports &port, + polycube::service::PacketInMetadata &md, + const std::vector &packet) { + try { + switch (static_cast(md.reason)) { + case SlowPathReason::ARP_REPLY: { + EthernetII pkt(&packet[0], packet.size()); + auto backendPort = this->getBackendPort(); + if (backendPort != nullptr) { + backendPort->send_packet_out(pkt); + } + break; + } + default: { + logger()->error("Not valid slow path reason {} received", md.reason); + } + } + } catch (const std::exception &e) { + logger()->error("Exception during slow path packet processing: {}", + e.what()); + } +} + +std::shared_ptr K8sdispatcher::getPorts(const std::string &name) { + return this->get_port(name); +} + +std::vector> K8sdispatcher::getPortsList() { + return this->get_ports(); +} + +void K8sdispatcher::addPorts(const std::string &name, + const PortsJsonObject &conf) { + try { + PortsTypeEnum type = conf.getType(); + + // consistency check + if (get_ports().size() == 2) { + throw std::runtime_error("Reached maximum number of ports"); + } + if (type == PortsTypeEnum::FRONTEND) { + if (this->getFrontendPort() != nullptr) { + throw std::runtime_error("There is already a FRONTEND port"); + } + } else { + if (this->getBackendPort() != nullptr) { + throw std::runtime_error("There is already a BACKEND port"); + } + } + + add_port(name, conf); + + // save node IP if port_type == FRONTEND + if (type == PortsTypeEnum::FRONTEND) { + this->nodeIpNboInt_ = poly_utils::ip_string_to_nbo_uint(conf.getIp()); + } + // reload service dataplane if the configuration is complete + if (get_ports().size() == 2) { + this->reloadConfig(); + } + } catch (std::runtime_error &ex) { + logger()->error("Failed to add port {0}: {1}", name, ex.what()); + throw std::runtime_error("Failed to add port"); + } +} + +void K8sdispatcher::addPortsList(const std::vector &conf) { + for (auto &i : conf) { + std::string name_ = i.getName(); + this->addPorts(name_, i); + } +} + +void K8sdispatcher::replacePorts(const std::string &name, + const PortsJsonObject &conf) { + logger()->error("K8sdispatcher::delPorts: Method not allowed"); + throw std::runtime_error{"Method not allowed"}; +} + +void K8sdispatcher::delPorts(const std::string &name) { + logger()->error("K8sdispatcher::delPorts: Method not allowed"); + throw std::runtime_error{"Method not allowed"}; +} + +void K8sdispatcher::delPortsList() { + logger()->error("K8sdispatcher::delPorts: Method not allowed"); + throw std::runtime_error{"Method not allowed"}; +} + +std::string K8sdispatcher::getInternalSrcIp() { + return this->internalSrcIp_; +} + +std::string K8sdispatcher::getNodeportRange() { + return this->nodeportRange_; +} + +void K8sdispatcher::setNodeportRange(const std::string &value) { + // Return immediately if the provided NodePort port range is the same of the + // already configured one + if (this->nodeportRange_ == value) { + return; + } + + // Parsing lower and upper port numbers + uint16_t low; + uint16_t high; + int res = std::sscanf(value.c_str(), "%hu-%hu", &low, &high); + if (res != 2) { + logger()->error("Failed to parse {} NodePort port range", value); + throw std::runtime_error("Failed to parse NodePort port range"); + } + + if (low >= high) { + logger()->error("Invalid {} NodePort port range", value); + throw std::runtime_error("Invalid NodePort port range"); + } + + try { + auto npr_table = get_hash_table( + K8sdispatcher::EBPF_NPR_TABLE_MAP); + + for (auto const &rule : this->getNodeportRuleList()) { + uint16_t port = rule->getNodeportPort(); + if (port < low || port > high) { + L4ProtoEnum proto = rule->getProto(); + nt_k npr_key{.port = htons(port), + .proto = utils::L4ProtoEnum_to_int(proto)}; + npr_table.remove(npr_key); + + if (this->nodePortRuleMap_.erase(NodeportKey(port, proto)) != 1) { + std::runtime_error{"Failed to delete NodePort rule from user map"}; + } + } + } + } catch (std::runtime_error &ex) { + logger()->error("Failed to flush out-of-range NodePort rules: {}", + ex.what()); + throw std::runtime_error("Failed to flush out-of-range NodePort rules"); + } + + this->nodeportRangeTuple_ = std::make_pair(low, high); + this->nodeportRange_ = value; +} + +std::shared_ptr K8sdispatcher::getSessionRule( + const SessionRuleDirectionEnum &direction, const std::string &srcIp, + const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, + const L4ProtoEnum &proto) { + try { + auto table = get_hash_table( + direction == SessionRuleDirectionEnum::EGRESS + ? K8sdispatcher::EBPF_EGRESS_SESSION_TABLE + : K8sdispatcher::EBPF_INGRESS_SESSION_TABLE); + st_k map_key{.src_ip = poly_utils::ip_string_to_nbo_uint(srcIp), + .dst_ip = poly_utils::ip_string_to_nbo_uint(dstIp), + .src_port = htons(srcPort), + .dst_port = htons(dstPort), + .proto = utils::L4ProtoEnum_to_int(proto)}; + + st_v value = table.get(map_key); + + std::string newIp = poly_utils::nbo_uint_to_ip_string(value.new_ip); + uint16_t newPort = ntohs(value.new_port); + SessionRuleOperationEnum operation = + utils::int_to_SessionRuleOperationEnum(value.operation); + SessionRuleOriginatingRuleEnum originatingRule = + utils::int_to_SessionRuleOriginatingRuleEnum(value.originating_rule); + + return std::make_shared(*this, direction, srcIp, dstIp, + srcPort, dstPort, proto, newIp, + newPort, operation, originatingRule); + } catch (std::exception &ex) { + logger()->error("Failed to get session rule: {}", ex.what()); + throw std::runtime_error("Failed to get session rule"); + } +} + +std::vector> K8sdispatcher::getSessionRuleList() { + std::vector> rules; + std::vector tableNames{ + K8sdispatcher::EBPF_EGRESS_SESSION_TABLE, + K8sdispatcher::EBPF_INGRESS_SESSION_TABLE}; + try { + for (auto &tableName : tableNames) { + auto table = get_hash_table(tableName); + auto direction = tableName == K8sdispatcher::EBPF_INGRESS_SESSION_TABLE + ? SessionRuleDirectionEnum::INGRESS + : SessionRuleDirectionEnum::EGRESS; + for (auto &entry : table.get_all()) { + auto key = entry.first; + auto value = entry.second; + + auto rule = std::make_shared( + *this, direction, poly_utils::nbo_uint_to_ip_string(key.src_ip), + poly_utils::nbo_uint_to_ip_string(key.dst_ip), ntohs(key.src_port), + ntohs(key.dst_port), utils::int_to_L4ProtoEnum(key.proto), + poly_utils::nbo_uint_to_ip_string(value.new_ip), + ntohs(value.new_port), + utils::int_to_SessionRuleOperationEnum(value.operation), + utils::int_to_SessionRuleOriginatingRuleEnum( + value.originating_rule)); + + rules.push_back(rule); + } + } + } catch (std::exception &ex) { + logger()->error("Failed to get session rules list: {0}", ex.what()); + throw std::runtime_error("Failed to get session rules list"); + } + return rules; +} + +void K8sdispatcher::addSessionRule( + const SessionRuleDirectionEnum &direction, const std::string &srcIp, + const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, + const L4ProtoEnum &proto, const SessionRuleJsonObject &conf) { + logger()->error("K8sdispatcher::addSessionRule: Method not allowed"); + throw std::runtime_error{"Method not allowed"}; +} + +void K8sdispatcher::addSessionRuleList( + const std::vector &conf) { + logger()->error("K8sdispatcher::addSessionRuleList: Method not allowed"); + throw std::runtime_error{"Method not allowed"}; +} + +void K8sdispatcher::replaceSessionRule( + const SessionRuleDirectionEnum &direction, const std::string &srcIp, + const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, + const L4ProtoEnum &proto, const SessionRuleJsonObject &conf) { + logger()->error("K8sdispatcher::replaceSessionRule: Method not allowed"); + throw std::runtime_error{"Method not allowed"}; +} + +void K8sdispatcher::delSessionRule(const SessionRuleDirectionEnum &direction, + const std::string &srcIp, + const std::string &dstIp, + const uint16_t &srcPort, + const uint16_t &dstPort, + const L4ProtoEnum &proto) { + logger()->error("K8sdispatcher::delSessionRule: Method not allowed"); + throw std::runtime_error{"Method not allowed"}; +} + +void K8sdispatcher::delSessionRuleList() { + logger()->error("K8sdispatcher::delSessionRuleList: Method not allowed"); + throw std::runtime_error{"Method not allowed"}; +} + +std::shared_ptr K8sdispatcher::getNodeportRule( + const uint16_t &nodeportPort, const L4ProtoEnum &proto) { + NodeportKey key = NodeportKey(nodeportPort, proto); + auto it = nodePortRuleMap_.find(key); + if (it == nodePortRuleMap_.end()) { + logger()->info("No NodePort rule associated with key ({0}, {1})", + nodeportPort, + NodeportRuleJsonObject::L4ProtoEnum_to_string(proto)); + std::runtime_error{"No NodePort rule associated with the provided key"}; + } + return it->second; +} + +std::vector> +K8sdispatcher::getNodeportRuleList() { + std::vector> rules; + for (auto &entry : nodePortRuleMap_) { + rules.push_back(entry.second); + } + return rules; +} + +void K8sdispatcher::addNodeportRule(const uint16_t &nodeportPort, + const L4ProtoEnum &proto, + const NodeportRuleJsonObject &conf) { + logger()->trace("Received a request to add a NodePort rule"); + NodeportKey key = NodeportKey(nodeportPort, proto); + + if (nodeportPort < this->nodeportRangeTuple_.first || + nodeportPort > this->nodeportRangeTuple_.second) { + logger()->error( + "The NodePort rule port is not contained in the valid range"); + throw std::runtime_error( + "The NodePort rule port is not contained in the valid range"); + } + + if (this->nodePortRuleMap_.count(key)) { + logger()->error("The NodePort rule already exists"); + throw std::runtime_error("The NodePort rule already exists"); + } + + auto rule = std::make_shared(*this, conf); + if (!nodePortRuleMap_.insert(std::make_pair(key, rule)).second) { + logger()->error("Failed to add the NodePort rule"); + throw std::runtime_error("Failed to add the NodePort rule"); + } + + try { + logger()->trace("Retrieving NodePort rules kernel map"); + auto npr_table = + get_hash_table(K8sdispatcher::EBPF_NPR_TABLE_MAP); + logger()->trace("Retrieved NodePort rules kernel map"); + nt_k npr_key{ + .port = htons(nodeportPort), + .proto = utils::L4ProtoEnum_to_int(proto), + }; + nt_v npr_value{.external_traffic_policy = + utils::NodeportRuleExternalTrafficPolicyEnum_to_int( + conf.getExternalTrafficPolicy())}; + + logger()->trace("Storing NodePort rule in NodePort rules kernel map"); + npr_table.set(npr_key, npr_value); + logger()->trace("Stored NodePort rule in NodePort rules kernel map"); + } catch (std::exception &ex) { + logger()->warn("Failed to store NodePort rule in kernel map: {}", + ex.what()); + if (this->nodePortRuleMap_.erase(key) != 1) { + logger()->error("Broken user space NodePort rule map"); + }; + throw std::runtime_error("Failed to store NodePort rule in kernel map"); + } + logger()->info("Added NodePort rule"); +} + +void K8sdispatcher::addNodeportRuleList( + const std::vector &conf) { + for (auto &i : conf) { + this->addNodeportRule(i.getNodeportPort(), i.getProto(), i); + } +} + +void K8sdispatcher::updateNodeportRuleList( + const std::vector &conf) { + logger()->trace("Received request for NodePort rules updating"); + try { + for (auto &i : conf) { + auto nodeportRule = getNodeportRule(i.getNodeportPort(), i.getProto()); + nodeportRule->update(i); + } + } catch (std::exception &ex) { + logger()->error("Failed update NodePort rule: {}", ex.what()); + throw std::runtime_error("Failed to update NodePort rule"); + } + logger()->info("Updated NodePort rules"); +} + +void K8sdispatcher::replaceNodeportRule(const uint16_t &nodeportPort, + const L4ProtoEnum &proto, + const NodeportRuleJsonObject &conf) { + this->delNodeportRule(nodeportPort, proto); + this->addNodeportRule(nodeportPort, proto, conf); +} + +void K8sdispatcher::replaceNodeportRuleList( + const std::vector &conf) { + this->delNodeportRuleList(); + this->addNodeportRuleList(conf); +} + +void K8sdispatcher::delNodeportRule(const uint16_t &nodeportPort, + const L4ProtoEnum &proto) { + logger()->trace("Received a request to delete a NodePort rule"); + NodeportKey key = NodeportKey(nodeportPort, proto); + + if (!this->nodePortRuleMap_.count(key)) { + logger()->info("No NodePort rule associated with key ({0}, {1})", + nodeportPort, + NodeportRuleJsonObject::L4ProtoEnum_to_string(proto)); + std::runtime_error{"No NodePort rule associated with the provided key"}; + } + + try { + logger()->trace("Retrieving NodePort rules kernel map"); + auto npr_table = + get_hash_table(K8sdispatcher::EBPF_NPR_TABLE_MAP); + logger()->trace("Retrieved NodePort rules kernel map"); + nt_k npr_key{.port = htons(nodeportPort), + .proto = utils::L4ProtoEnum_to_int(proto)}; + + npr_table.remove(npr_key); + + if (this->nodePortRuleMap_.erase(key) == 0) { + std::runtime_error{"No NodePort rule associated with the provided key"}; + } + } catch (std::exception &ex) { + logger()->error("Failed to delete NodePort rule: {}", ex.what()); + throw std::runtime_error("Failed to delete NodePort rule"); + } + logger()->info("Deleted NodePort rule"); +} + +void K8sdispatcher::delNodeportRuleList() { + for (auto &it : nodePortRuleMap_) { + NodeportKey key = it.first; + this->delNodeportRule(std::get<0>(key), std::get<1>(key)); + } +} + +void K8sdispatcher::reloadConfig() { + std::string flags; + + uint16_t frontend = 0; + uint16_t backend = 0; + for (auto &it : get_ports()) { + if (it->getType() == PortsTypeEnum::FRONTEND) + frontend = it->index(); + else + backend = it->index(); + } + flags += "#define FRONTEND_PORT " + std::to_string(frontend) + "\n"; + flags += "#define BACKEND_PORT " + std::to_string(backend) + "\n"; + flags += "#define NODE_IP " + std::to_string(this->nodeIpNboInt_) + "\n"; + flags += + "#define INTERNAL_SRC_IP " + std::to_string(this->internalSrcIpNboInt_); + + logger()->trace("Reloading code with the following flags:\n{}", flags); + + reload(flags + '\n' + k8sdispatcher_code); + + logger()->trace("Reloaded K8sDispatcher code"); +} + +std::shared_ptr K8sdispatcher::getFrontendPort() { + for (auto &it : get_ports()) { + if (it->getType() == PortsTypeEnum::FRONTEND) { + return it; + } + } + return nullptr; +} + +std::shared_ptr K8sdispatcher::getBackendPort() { + for (auto &it : get_ports()) { + if (it->getType() == PortsTypeEnum::BACKEND) { + return it; + } + } + return nullptr; +} \ No newline at end of file diff --git a/src/services/pcn-k8sdispatcher/src/K8sdispatcher.h b/src/services/pcn-k8sdispatcher/src/K8sdispatcher.h new file mode 100644 index 000000000..462776a3d --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/K8sdispatcher.h @@ -0,0 +1,139 @@ +/* + * Copyright 2022 The Polycube Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "base/K8sdispatcherBase.h" +#include "SessionRule.h" +#include "NodeportRule.h" +#include "Ports.h" +#include "HashTuple.h" + +/* definitions copied from datapath */ +struct nt_k { + uint16_t port; + uint8_t proto; +} __attribute__((packed)); +struct nt_v { + uint16_t external_traffic_policy; +} __attribute__((packed)); + +enum class SlowPathReason { ARP_REPLY = 0 }; + +using namespace polycube::service::model; + +class K8sdispatcher : public K8sdispatcherBase { + public: + K8sdispatcher(const std::string name, const K8sdispatcherJsonObject &conf); + virtual ~K8sdispatcher(); + + void packet_in(Ports &port, polycube::service::PacketInMetadata &md, + const std::vector &packet) override; + + /// + /// Entry of the ports table + /// + std::shared_ptr getPorts(const std::string &name) override; + std::vector> getPortsList() override; + void addPorts(const std::string &name, const PortsJsonObject &conf) override; + void addPortsList(const std::vector &conf) override; + void replacePorts(const std::string &name, + const PortsJsonObject &conf) override; + void delPorts(const std::string &name) override; + void delPortsList() override; + + /// + /// Internal source IP address used for natting incoming packets directed to + /// Kubernetes Services with a CLUSTER external traffic policy + /// + std::string getInternalSrcIp() override; + + /// + /// Port range used for NodePort Services + /// + std::string getNodeportRange() override; + void setNodeportRange(const std::string &value) override; + + /// + /// Session entry related to a specific traffic direction + /// + std::shared_ptr getSessionRule( + const SessionRuleDirectionEnum &direction, const std::string &srcIp, + const std::string &dstIp, const uint16_t &srcPort, + const uint16_t &dstPort, const L4ProtoEnum &proto) override; + std::vector> getSessionRuleList() override; + void addSessionRule(const SessionRuleDirectionEnum &direction, + const std::string &srcIp, const std::string &dstIp, + const uint16_t &srcPort, const uint16_t &dstPort, + const L4ProtoEnum &proto, + const SessionRuleJsonObject &conf) override; + void addSessionRuleList( + const std::vector &conf) override; + void replaceSessionRule(const SessionRuleDirectionEnum &direction, + const std::string &srcIp, const std::string &dstIp, + const uint16_t &srcPort, const uint16_t &dstPort, + const L4ProtoEnum &proto, + const SessionRuleJsonObject &conf) override; + void delSessionRule(const SessionRuleDirectionEnum &direction, + const std::string &srcIp, const std::string &dstIp, + const uint16_t &srcPort, const uint16_t &dstPort, + const L4ProtoEnum &proto) override; + void delSessionRuleList() override; + + /// + /// NodePort rule associated with a Kubernetes NodePort Service + /// + std::shared_ptr getNodeportRule( + const uint16_t &nodeportPort, const L4ProtoEnum &proto) override; + std::vector> getNodeportRuleList() override; + void addNodeportRule(const uint16_t &nodeportPort, const L4ProtoEnum &proto, + const NodeportRuleJsonObject &conf) override; + void addNodeportRuleList( + const std::vector &conf) override; + void updateNodeportRuleList( + const std::vector &conf) override; + void replaceNodeportRule(const uint16_t &nodeportPort, + const L4ProtoEnum &proto, + const NodeportRuleJsonObject &conf) override; + virtual void replaceNodeportRuleList( + const std::vector &conf) override; + void delNodeportRule(const uint16_t &nodeportPort, + const L4ProtoEnum &proto) override; + void delNodeportRuleList() override; + + static const std::string EBPF_EGRESS_SESSION_TABLE; + static const std::string EBPF_INGRESS_SESSION_TABLE; + static const std::string EBPF_NPR_TABLE_MAP; + + typedef std::tuple NodeportKey; + + private: + std::string internalSrcIp_; + uint32_t internalSrcIpNboInt_; + std::string nodeportRange_; + std::pair nodeportRangeTuple_; + + uint32_t nodeIpNboInt_; + + std::unordered_map> + nodePortRuleMap_; + + void reloadConfig(); + + std::shared_ptr getFrontendPort(); + + std::shared_ptr getBackendPort(); +}; diff --git a/src/services/pcn-k8sdispatcher/src/K8sdispatcher_dp.c b/src/services/pcn-k8sdispatcher/src/K8sdispatcher_dp.c new file mode 100644 index 000000000..dc0569e8a --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/K8sdispatcher_dp.c @@ -0,0 +1,521 @@ +/* + * Copyright 2022 The Polycube Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef FRONTEND_PORT +#define FRONTEND_PORT 0 +#endif + +#ifndef BACKEND_PORT +#define BACKEND_PORT 0 +#endif + +#ifndef NODE_IP +#define NODE_IP 0 +#endif + +#ifndef INTERNAL_SRC_IP +#define INTERNAL_SRC_IP 0 +#endif + +#define SESSION_MAP_DIM 32768 + +enum OPERATION_TYPE { OP_XLATE_SRC = 0, OP_XLATE_DST = 1 }; + +enum ORIGINATING_RULE_TYPE { RULE_POD_TO_EXT = 0, RULE_NODEPORT_CLUSTER = 1 }; + +enum EXTERNAL_TRAFFIC_POLICY_TYPE { ETP_LOCAL = 0, ETP_CLUSTER = 1 }; + +enum SLOWPATH_REASON { REASON_ARP_REPLY = 0 }; + +#define IP_CSUM_OFFSET (sizeof(struct eth_hdr) + offsetof(struct iphdr, check)) +#define UDP_CSUM_OFFSET \ + (sizeof(struct eth_hdr) + sizeof(struct iphdr) + \ + offsetof(struct udphdr, check)) +#define TCP_CSUM_OFFSET \ + (sizeof(struct eth_hdr) + sizeof(struct iphdr) + \ + offsetof(struct tcphdr, check)) +#define ICMP_CSUM_OFFSET \ + (sizeof(struct eth_hdr) + sizeof(struct iphdr) + \ + offsetof(struct icmphdr, checksum)) +#define IS_PSEUDO 0x10 + +struct eth_hdr { + __be64 dst : 48; + __be64 src : 48; + __be16 proto; +} __attribute__((packed)); + +struct arp_hdr { + __be16 ar_hrd; /* format of hardware address */ + __be16 ar_pro; /* format of protocol address */ + unsigned char ar_hln; /* length of hardware address */ + unsigned char ar_pln; /* length of protocol address */ + __be16 ar_op; /* ARP opcode (command) */ + __be64 ar_sha : 48; /* sender hardware address */ + __be32 ar_sip; /* sender IP address */ + __be64 ar_tha : 48; /* target hardware address */ + __be32 ar_tip; /* target IP address */ +} __attribute__((packed)); + +// Session table +struct st_k { + uint32_t src_ip; + uint32_t dst_ip; + uint16_t src_port; + uint16_t dst_port; + uint8_t proto; +} __attribute__((packed)); +struct st_v { + uint32_t new_ip; + uint16_t new_port; + uint8_t operation; + uint8_t originating_rule; +} __attribute__((packed)); +BPF_TABLE("lru_hash", struct st_k, struct st_v, egress_session_table, + SESSION_MAP_DIM); +BPF_TABLE("lru_hash", struct st_k, struct st_v, ingress_session_table, + SESSION_MAP_DIM); + +// NodePort rules table +struct nt_k { + uint16_t port; + uint8_t proto; +} __attribute__((packed)); +; +struct nt_v { + uint16_t external_traffic_policy; +} __attribute__((packed)); +; + +BPF_F_TABLE("hash", struct nt_k, struct nt_v, npr_table, 1024, + BPF_F_NO_PREALLOC); + +// Port numbers +struct free_port_entry { + uint16_t free_port; + struct bpf_spin_lock lock; +}; + +BPF_TABLE("array", uint32_t, struct free_port_entry, free_port_map, 1); + +static inline __be16 get_free_port() { + uint32_t key = 0; + uint16_t free_port = 0; + struct free_port_entry *entry = free_port_map.lookup(&key); + if (!entry) { // never happen for array + return 0; + } + bpf_spin_lock(&entry->lock); + if (entry->free_port < 1024 || entry->free_port == 65535) { + entry->free_port = 1024; + } + free_port = entry->free_port; + entry->free_port++; + bpf_spin_unlock(&entry->lock); + return bpf_htons(free_port); +} + +static int handle_rx(struct CTXTYPE *ctx, struct pkt_metadata *md) { + // Disable data plane if initialization has not been completed yet + if (FRONTEND_PORT == BACKEND_PORT) { + return RX_DROP; + } + // NAT processing happens in 4 steps: + // 1) packet parsing + // 2) session table lookup + // 3) rule lookup + // 4) packet modification + + // 1) Parse packet + void *data = (void *)(long)ctx->data; + void *data_end = (void *)(long)ctx->data_end; + + struct eth_hdr *eth = data; + if ((void *)(eth + 1) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped Ethernet pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); + return RX_DROP; + } + + switch (eth->proto) { + case htons(ETH_P_IP): + goto IP; // ipv4 packet + case htons(ETH_P_ARP): { + goto ARP; // ARP packet + } + default: + if (md->in_port == BACKEND_PORT) { + pcn_log(ctx, LOG_TRACE, + "Received unsupported pkt from BACKEND port: sent to stack - " + "(in_port: %d) (proto: 0x%x)", + md->in_port, bpf_htons(eth->proto)); + } else { + pcn_log(ctx, LOG_TRACE, + "Received unsupported pkt from FRONTEND port: sent to stack - " + "(in_port: %d) (proto: 0x%x)", + md->in_port, bpf_htons(eth->proto)); + } + return RX_OK; + } + +IP:; + struct iphdr *ip = (void *)(eth + 1); + if ((void *)(ip + 1) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped IP pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); + return RX_DROP; + } + + // check if ip dest is for this host + if (md->in_port == FRONTEND_PORT && ip->daddr != NODE_IP) { + pcn_log(ctx, LOG_TRACE, + "Pkt coming from FRONTEND port is not for host: sent to stack - " + "(in_port: %d) (dst_ip: %I)", + md->in_port, ip->daddr); + return RX_OK; + } + + // Extract L3 packet data + uint32_t src_ip = ip->saddr; + uint32_t dst_ip = ip->daddr; + uint8_t proto = ip->protocol; + + // Extract L4 segment data + uint16_t src_port = 0, dst_port = 0; + // The following pointers are used to update a TCP port, a UDP port or the + // ICMP echo id in the same way regardless the l4 protocol; same for the + // checksum offset + uint16_t *src_port_ptr = NULL, *dst_port_ptr = NULL; + int l4_csum_offset = 0; + + switch (ip->protocol) { + case IPPROTO_TCP: { + struct tcphdr *tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped TCP pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); + return RX_DROP; + } + + src_port = tcp->source; + dst_port = tcp->dest; + src_port_ptr = &tcp->source; + dst_port_ptr = &tcp->dest; + l4_csum_offset = TCP_CSUM_OFFSET; + break; + } + case IPPROTO_UDP: { + struct udphdr *udp = (void *)(ip + 1); + if ((void *)(udp + 1) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped UDP pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); + return RX_DROP; + } + + src_port = udp->source; + dst_port = udp->dest; + src_port_ptr = &udp->source; + dst_port_ptr = &udp->dest; + l4_csum_offset = UDP_CSUM_OFFSET; + break; + } + case IPPROTO_ICMP: { + struct icmphdr *icmp = (void *)(ip + 1); + if ((void *)(icmp + 1) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped ICMP pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); + return RX_DROP; + } + + // Consider the ICMP ID as a "port" number for easier handling + src_port = icmp->un.echo.id; + dst_port = icmp->un.echo.id; + src_port_ptr = &icmp->un.echo.id; + dst_port_ptr = &icmp->un.echo.id; + l4_csum_offset = ICMP_CSUM_OFFSET; + break; + } + default: { + pcn_log(ctx, LOG_TRACE, + "Dropped IP pkt - (in_port: %d) (reason: unsupported_type) " + "(ip_proto: %d)", + md->in_port, ip->protocol); + return RX_DROP; + } + } + + // 2) Packet parsed, start session table lookup + // NAT data + uint32_t new_ip = 0; + uint16_t new_port = 0; + uint8_t operation = 0; + struct st_k session_key = {.src_ip = src_ip, + .dst_ip = dst_ip, + .src_port = src_port, + .dst_port = dst_port, + .proto = proto}; + struct st_v *session_entry = (md->in_port == BACKEND_PORT) + ? egress_session_table.lookup(&session_key) + : ingress_session_table.lookup(&session_key); + if (session_entry) { + // Extract NAT data + new_ip = session_entry->new_ip; + new_port = session_entry->new_port; + operation = session_entry->operation; + goto APPLY_NAT; + } + + uint8_t originating_rule = 0; + + // 3) Session table miss, start rule lookup + if (md->in_port == BACKEND_PORT) { // inside -> outside + // Check egress rules + + // Rule: RESP_NP_LOCAL + // Response for a NodePort Svc with externalTrafficPolicy=LOCAL => FORWARD + if (src_ip == NODE_IP) { + pcn_log(ctx, LOG_TRACE, + "Matched egress rule RESP_NP_LOCAL: redirected pkt as is to " + "FRONTEND port - (src: %I:%P, dst: %I:%P)", + src_ip, src_port, dst_ip, dst_port); + return pcn_pkt_redirect(ctx, md, FRONTEND_PORT); + } + + // Rule: POD_TO_EXT + // A Pod want to reach the external world => SNAT + new_ip = NODE_IP; + new_port = get_free_port(); + operation = OP_XLATE_SRC; + originating_rule = RULE_POD_TO_EXT; + pcn_log(ctx, LOG_TRACE, + "Matched egress rule POD_TO_EXT: chosen SNAT - (src: %I:%P, dst: " + "%I:%P)", + src_ip, src_port, dst_ip, dst_port); + } else { // outside -> inside + // Check ingress rules + + struct nt_k npr_key = {.port = dst_port, .proto = proto}; + struct nt_v *npr_value = npr_table.lookup(&npr_key); + + // Incoming packet doesn't match any NodePort rule => stack + if (npr_value == NULL) { + pcn_log(ctx, LOG_TRACE, + "No ingress rule match: sent to stack - (src: %I:%P, dst: %I:%P)", + src_ip, src_port, dst_ip, dst_port); + return RX_OK; + } + + // Rule: REQ_NP_LOCAL + // Request for a NodePort Svc with externalTrafficPolicy=LOCAL => FORWARD + if (npr_value->external_traffic_policy == ETP_LOCAL) { + pcn_log(ctx, LOG_TRACE, + "Matched ingress rule REQ_NP_LOCAL: redirected pkt as is to " + "BACKEND port - (src: %I:%P, dst: %I:%P)", + src_ip, src_port, dst_ip, dst_port); + return pcn_pkt_redirect(ctx, md, BACKEND_PORT); + } + + // Rule: REQ_NP_CLUSTER + // Request for a NodePort Svc with externalTrafficPolicy=CLUSTER => SNAT + new_ip = INTERNAL_SRC_IP; + new_port = get_free_port(); + operation = OP_XLATE_SRC; + originating_rule = RULE_NODEPORT_CLUSTER; + + pcn_log( + ctx, LOG_TRACE, + "Matched egress rule REQ_NP_CLUSTER: chosen SNAT - (src: %I:%P, dst: " + "%I:%P)", + src_ip, src_port, dst_ip, dst_port); + } + + // No session table exist for the packet but a rule matched, so both sessions + // tables must be updated. In the following, the forward table is intended + // to be the table that handles the sessions in the same direction of the + // actual packet; similarly, the reverse table is intended to be + // the table that handles the sessions in the opposite direction of the + // actual packet. Example: + // packet coming from the BACKEND port + // - forward table = egress session table + // - reverse table = ingress session table + + // Regardless the type of NAT, the forward table key always match the original + // packet quintuple data. Moreover, the forward table value always stores + // the new ip, the new port and the same kind of operation that will be + // applied on the actual packet. The originating rule is determined during + // the rules scan + struct st_k forward_key = {.src_ip = src_ip, + .dst_ip = dst_ip, + .src_port = src_port, + .dst_port = dst_port, + .proto = proto}; + struct st_v forward_value = {.new_ip = new_ip, + .new_port = new_port, + .operation = operation, + .originating_rule = originating_rule}; + + // The reverse table key and value depend on the kind of operation that will + // be applied on the actual packet + struct st_k reverse_key = {0, 0, 0, 0, 0}; + struct st_v reverse_value = {0, 0}; + + if (operation == OP_XLATE_SRC) { + reverse_key.src_ip = dst_ip; + reverse_key.dst_ip = new_ip; + // During the NAT operation, the ICMP ID will be replaced with the value + // of "new port": this means that for the reverse traffic, the session will + // be identified by a packet having both "source port" and "destination port" + // equal to "new port" + reverse_key.src_port = (proto == IPPROTO_ICMP) ? new_port : dst_port; + reverse_key.dst_port = new_port; + + reverse_value.new_ip = src_ip; + reverse_value.new_port = src_port; + reverse_value.operation = OP_XLATE_DST; + } else { + reverse_key.src_ip = new_ip; + reverse_key.dst_ip = src_ip; + reverse_key.src_port = new_port; + // During the NAT operation, the ICMP ID will be replaced with the value + // of "new port": this means that for the reverse traffic, the session will + // be identified by a packet having both "source port" and "destination port" + // equal to "new port" + reverse_key.dst_port = (proto == IPPROTO_ICMP) ? new_port : src_port; + + reverse_value.new_ip = dst_ip; + reverse_value.new_port = dst_port; + reverse_value.operation = OP_XLATE_SRC; + } + // The protocol type in the reverse table key and the originating rule type + // in the reverse table value do not depend on the operation applied on the + // actual packet + reverse_key.proto = proto; + reverse_value.originating_rule = originating_rule; + + if (md->in_port == BACKEND_PORT) { + egress_session_table.update(&forward_key, &forward_value); + ingress_session_table.update(&reverse_key, &reverse_value); + } else { + ingress_session_table.update(&forward_key, &forward_value); + egress_session_table.update(&reverse_key, &reverse_value); + } + + pcn_log(ctx, LOG_TRACE, "Created new session - (src: %I:%P, dst: %I:%P)", + src_ip, src_port, dst_ip, dst_port); + +APPLY_NAT:; + // Save old values in order to update checksums after the NAT is applied + uint32_t old_ip; + uint16_t old_port; + if (operation == OP_XLATE_SRC) { + old_ip = src_ip; + old_port = src_port; + ip->saddr = new_ip; + *src_port_ptr = new_port; + pcn_log(ctx, LOG_TRACE, "Applied SNAT - (src: %I:%P, new_src: %I:%P)", + old_ip, old_port, new_ip, new_port); + } else { + old_ip = dst_ip; + old_port = dst_port; + ip->daddr = new_ip; + *dst_port_ptr = new_port; + pcn_log(ctx, LOG_TRACE, "Applied DNAT - (dst: %I:%P, new_dst: %I:%P)", + old_ip, old_port, new_ip, new_port); + } + + // Update checksums + if (proto != IPPROTO_ICMP) { // only for UDP and TCP + pcn_l4_csum_replace(ctx, l4_csum_offset, old_ip, new_ip, IS_PSEUDO | 4); + } + pcn_l4_csum_replace(ctx, l4_csum_offset, old_port, new_port, 2); + pcn_l3_csum_replace(ctx, IP_CSUM_OFFSET, old_ip, new_ip, 4); + + // Session tables have been updated (if needed) + // The packet has been modified (if needed) + // Nothing more to do, forward the packet + return pcn_pkt_redirect( + ctx, md, md->in_port == BACKEND_PORT ? FRONTEND_PORT : BACKEND_PORT); + +ARP:; + struct arp_hdr *arp = (void *)(eth + 1); + if ((void *)(arp + 1) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped ARP pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); + return RX_DROP; + } + + if (md->in_port == BACKEND_PORT) { // inside -> outside + // If a packet coming from the backend port + // - is an ARP request => it is sent to the frontend port + // - otherwise it is dropped + if (arp->ar_op == bpf_htons(ARPOP_REQUEST)) { + pcn_log(ctx, LOG_TRACE, + "Received ARP request pkt from BACKEND port: redirected to " + "FRONTEND port - (in_port: %d) (out_port: %d)", + md->in_port, FRONTEND_PORT); + return pcn_pkt_redirect(ctx, md, FRONTEND_PORT); + } + pcn_log(ctx, LOG_TRACE, + "Dropped non-request ARP pkt coming from BACKEND port - (in_port: " + "%d) (arp_opcode: %d)", + md->in_port, bpf_htons(arp->ar_op)); + } else { // outside -> inside + // If a packet coming from the frontend port + // - is an ARP request => it is sent to the stack + // - is an ARP reply => a copy is sent to the slowpath and the original one + // to the stack + // - otherwise it is dropped + if (arp->ar_op == bpf_ntohs(ARPOP_REQUEST)) { + pcn_log(ctx, LOG_TRACE, + "Received ARP request pkt from FRONTEND port: sent to the stack " + "- (in_port: %d)", + md->in_port); + return RX_OK; + } else if (arp->ar_op == bpf_htons(ARPOP_REPLY)) { + pcn_log(ctx, LOG_TRACE, + "Received ARP reply pkt from FRONTEND port: sent a copy to " + "slow path and a copy to the stack - (in_port: %d)", + md->in_port); + // a copy is sent to the backend port through the slowpath and a copy is + // sent to the stack + pcn_pkt_controller(ctx, md, REASON_ARP_REPLY); + return RX_OK; + } + pcn_log(ctx, LOG_TRACE, + "Dropped non-reply ARP pkt coming from FRONTEND port - (in_port: " + "%d) (arp_opcode: %d)", + md->in_port, bpf_htons(arp->ar_op)); + } + return RX_DROP; +} \ No newline at end of file diff --git a/src/services/pcn-k8sdispatcher/src/NodeportRule.cpp b/src/services/pcn-k8sdispatcher/src/NodeportRule.cpp new file mode 100644 index 000000000..c0e7eaa99 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/NodeportRule.cpp @@ -0,0 +1,99 @@ +/* + * Copyright 2022 The Polycube Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "NodeportRule.h" + +#include "K8sdispatcher.h" +#include "Utils.h" + +NodeportRule::NodeportRule(K8sdispatcher &parent, + const NodeportRuleJsonObject &conf) + : NodeportRuleBase(parent), + parent_{parent}, + nodeportPort_{conf.nodeportPortIsSet() + ? conf.getNodeportPort() + : throw std::runtime_error{"NodePort rule port" + "must be provided"}}, + proto_{ + conf.protoIsSet() + ? conf.getProto() + : throw std:: + runtime_error{"NodePort rule protocol must be provided"}}, + externalTrafficPolicy_{ + conf.externalTrafficPolicyIsSet() + ? conf.getExternalTrafficPolicy() + : NodeportRuleExternalTrafficPolicyEnum::CLUSTER}, + ruleName_{conf.getRuleName()} { + logger()->info("Creating NodeportRule instance"); + if (this->proto_ == L4ProtoEnum::ICMP) { + throw std::runtime_error{"NodePort rule protocol cannot be ICMP"}; + } +} + +NodeportRule::~NodeportRule() { + logger()->info("Destroying NodeportRule instance"); +} + +uint16_t NodeportRule::getNodeportPort() { + return this->nodeportPort_; +} + +L4ProtoEnum NodeportRule::getProto() { + return this->proto_; +} + +NodeportRuleExternalTrafficPolicyEnum NodeportRule::getExternalTrafficPolicy() { + return this->externalTrafficPolicy_; +} + +void NodeportRule::setExternalTrafficPolicy( + const NodeportRuleExternalTrafficPolicyEnum &value) { + logger()->trace( + "Received a request to update NodePort rule external traffic policy"); + try { + logger()->trace("Retrieving NodePort rules kernel map"); + auto npr_table = this->parent_.get_hash_table( + K8sdispatcher::EBPF_NPR_TABLE_MAP); + logger()->trace("Retrieved NodePort rules kernel map"); + + struct nt_k npr_key { + .port = htons(this->nodeportPort_), + .proto = utils::L4ProtoEnum_to_int(this->proto_), + }; + struct nt_v npr_value { + .external_traffic_policy = + utils::NodeportRuleExternalTrafficPolicyEnum_to_int(value) + }; + + logger()->trace( + "Updating NodePort rule external traffic policy in NodePort rules " + "kernel map"); + npr_table.set(npr_key, npr_value); + this->externalTrafficPolicy_ = value; + logger()->trace( + "Updated NodePort rule external traffic policy in NodePort rules " + "kernel map"); + } catch (std::exception &ex) { + logger()->error("Failed to update NodePort rule in kernel map: {}", + ex.what()); + throw std::runtime_error("Failed to update NodePort rule in kernel map"); + } + logger()->trace("Updated NodePort rule external traffic policy"); +} + +std::string NodeportRule::getRuleName() { + return this->ruleName_; +} \ No newline at end of file diff --git a/src/services/pcn-k8sdispatcher/src/NodeportRule.h b/src/services/pcn-k8sdispatcher/src/NodeportRule.h new file mode 100644 index 000000000..4233c213a --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/NodeportRule.h @@ -0,0 +1,59 @@ +/* +* Copyright 2022 The Polycube Authors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. + */ + +#pragma once + +#include "../base/NodeportRuleBase.h" + +class K8sdispatcher; + +using namespace polycube::service::model; + +class NodeportRule : public NodeportRuleBase { + public: + NodeportRule(K8sdispatcher &parent, const NodeportRuleJsonObject &conf); + virtual ~NodeportRule(); + + /// + /// NodePort rule nodeport port number + /// + uint16_t getNodeportPort() override; + + /// + /// NodePort rule L4 protocol + /// + L4ProtoEnum getProto() override; + + /// + /// The external traffic policy of the Kubernetes NodePort Service + /// + NodeportRuleExternalTrafficPolicyEnum getExternalTrafficPolicy() override; + void setExternalTrafficPolicy(const NodeportRuleExternalTrafficPolicyEnum &value) override; + + /// + /// An optional name for the NodePort rule + /// + std::string getRuleName() override; + + private: + K8sdispatcher& parent_; + + uint16_t nodeportPort_; + L4ProtoEnum proto_; + + NodeportRuleExternalTrafficPolicyEnum externalTrafficPolicy_; + std::string ruleName_; +}; diff --git a/src/services/pcn-k8sdispatcher/src/Ports.cpp b/src/services/pcn-k8sdispatcher/src/Ports.cpp new file mode 100644 index 000000000..6d7e402f9 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/Ports.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 2022 The Polycube Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Ports.h" +#include "K8sdispatcher.h" +#include "Utils.h" + + +Ports::Ports(polycube::service::Cube &parent, + std::shared_ptr port, + const PortsJsonObject &conf) + : PortsBase(parent, port), type_{conf.getType()} { + logger()->info("Creating Ports instance"); + auto ipIsSet = conf.ipIsSet(); + if (this->type_ == PortsTypeEnum::FRONTEND) { + if (!ipIsSet) { + throw std::runtime_error( + "The IP address is mandatory for a FRONTEND port"); + } + + auto ip = conf.getIp(); + if (!utils::is_valid_ipv4_str(ip)) { + throw std::runtime_error{"Invalid IPv4 address"}; + } + this->ip_ = ip; + } else { + if (ipIsSet) { + throw std::runtime_error( + "The IP address in not allowed for a BACKEND port"); + } + } +} + +Ports::~Ports() { + logger()->info("Destroying Ports instance"); +} + +PortsTypeEnum Ports::getType() { + return this->type_; +} + +std::string Ports::getIp() { + return this->ip_; +} \ No newline at end of file diff --git a/src/services/pcn-k8sdispatcher/src/Ports.h b/src/services/pcn-k8sdispatcher/src/Ports.h new file mode 100644 index 000000000..5383a7cd2 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/Ports.h @@ -0,0 +1,45 @@ +/* + * Copyright 2022 The Polycube Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../base/PortsBase.h" + +class K8sdispatcher; + +using namespace polycube::service::model; + +class Ports : public PortsBase { + public: + Ports(polycube::service::Cube &parent, + std::shared_ptr port, + const PortsJsonObject &conf); + virtual ~Ports(); + + /// + /// Type of the K8s Dispatcher cube port (e.g. BACKEND or FRONTEND) + /// + PortsTypeEnum getType() override; + + /// + /// IP address of the node interface (only for FRONTEND port) + /// + std::string getIp() override; + + private: + PortsTypeEnum type_; + std::string ip_; +}; diff --git a/src/services/pcn-k8sdispatcher/src/SessionRule.cpp b/src/services/pcn-k8sdispatcher/src/SessionRule.cpp new file mode 100644 index 000000000..4d011a5ec --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/SessionRule.cpp @@ -0,0 +1,106 @@ +/* + * Copyright 2022 The Polycube Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SessionRule.h" +#include "K8sdispatcher.h" +#include "Utils.h" + +SessionRule::SessionRule(K8sdispatcher &parent, + const SessionRuleJsonObject &conf) + : SessionRule{parent, + conf.getDirection(), + conf.getSrcIp(), + conf.getDstIp(), + conf.getSrcPort(), + conf.getDstPort(), + conf.getProto(), + conf.getNewIp(), + conf.getNewPort(), + conf.getOperation(), + conf.getOriginatingRule()} {} + +SessionRule::SessionRule(K8sdispatcher &parent, + SessionRuleDirectionEnum direction, std::string srcIp, + std::string dstIp, uint16_t srcPort, uint16_t dstPort, + L4ProtoEnum proto, std::string newIp, uint16_t newPort, + SessionRuleOperationEnum operation, + SessionRuleOriginatingRuleEnum originatingRule) + : SessionRuleBase(parent), + direction_{direction}, + srcIp_{srcIp}, + dstIp_{dstIp}, + srcPort_{srcPort}, + dstPort_{dstPort}, + proto_{proto}, + newIp_{newIp}, + newPort_{newPort}, + operation_{operation}, + originatingRule_{originatingRule} { + logger()->info("Creating SessionRule instance"); + if (!utils::is_valid_ipv4_str(srcIp)) { + throw std::runtime_error{"Invalid source IPv4 address"}; + } + if (!utils::is_valid_ipv4_str(dstIp)) { + throw std::runtime_error{"Invalid destination IPv4 address"}; + } + if (!utils::is_valid_ipv4_str(newIp)) { + throw std::runtime_error{"Invalid new IPv4 address"}; + } +} + +SessionRule::~SessionRule() { + logger()->info("Destroying SessionRule instance"); +} + +SessionRuleDirectionEnum SessionRule::getDirection() { + return this->direction_; +} + +std::string SessionRule::getSrcIp() { + return this->srcIp_; +} + +std::string SessionRule::getDstIp() { + return this->dstIp_; +} + +uint16_t SessionRule::getSrcPort() { + return this->srcPort_; +} + +uint16_t SessionRule::getDstPort() { + return this->dstPort_; +} + +L4ProtoEnum SessionRule::getProto() { + return this->proto_; +} + +std::string SessionRule::getNewIp() { + return this->newIp_; +} + +uint16_t SessionRule::getNewPort() { + return this->newPort_; +} + +SessionRuleOperationEnum SessionRule::getOperation() { + return this->operation_; +} + +SessionRuleOriginatingRuleEnum SessionRule::getOriginatingRule() { + return this->originatingRule_; +} \ No newline at end of file diff --git a/src/services/pcn-k8sdispatcher/src/SessionRule.h b/src/services/pcn-k8sdispatcher/src/SessionRule.h new file mode 100644 index 000000000..f25b44fed --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/SessionRule.h @@ -0,0 +1,111 @@ +/* + * Copyright 2022 The Polycube Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../base/SessionRuleBase.h" + +struct st_k { + uint32_t src_ip; + uint32_t dst_ip; + uint16_t src_port; + uint16_t dst_port; + uint8_t proto; +} __attribute__((packed)); +struct st_v { + uint32_t new_ip; + uint16_t new_port; + uint8_t operation; + uint8_t originating_rule; +} __attribute__((packed)); + +class K8sdispatcher; + +using namespace polycube::service::model; + +class SessionRule : public SessionRuleBase { + public: + SessionRule(K8sdispatcher &parent, const SessionRuleJsonObject &conf); + SessionRule(K8sdispatcher &parent, SessionRuleDirectionEnum direction, + std::string srcIp, std::string dstIp, uint16_t srcPort, + uint16_t dstPort, L4ProtoEnum proto, std::string newIp, + uint16_t newPort, SessionRuleOperationEnum operation, + SessionRuleOriginatingRuleEnum originatingRule); + virtual ~SessionRule(); + + /// + /// Session entry direction (e.g. INGRESS or EGRESS) + /// + SessionRuleDirectionEnum getDirection() override; + + /// + /// Session entry source IP address + /// + std::string getSrcIp() override; + + /// + /// Session entry destination IP address + /// + std::string getDstIp() override; + + /// + /// Session entry source L4 port number + /// + uint16_t getSrcPort() override; + + /// + /// Session entry destination L4 port number + /// + uint16_t getDstPort() override; + + /// + /// Session entry L4 protocol + /// + L4ProtoEnum getProto() override; + + /// + /// Translated IP address + /// + std::string getNewIp() override; + + /// + /// Translated L4 port number + /// + uint16_t getNewPort() override; + + /// + /// Operation applied on the original packet + /// + SessionRuleOperationEnum getOperation() override; + + /// + /// Rule originating the session entry + /// + SessionRuleOriginatingRuleEnum getOriginatingRule() override; + + private: + SessionRuleDirectionEnum direction_; + std::string srcIp_; + std::string dstIp_; + uint16_t srcPort_; + uint16_t dstPort_; + L4ProtoEnum proto_; + + std::string newIp_; + uint16_t newPort_; + SessionRuleOperationEnum operation_; + SessionRuleOriginatingRuleEnum originatingRule_; +}; diff --git a/src/services/pcn-k8sdispatcher/src/Utils.cpp b/src/services/pcn-k8sdispatcher/src/Utils.cpp new file mode 100644 index 000000000..ad0ef2a60 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/Utils.cpp @@ -0,0 +1,129 @@ +/* + * Copyright 2022 The Polycube Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Utils.h" + +#include "base/K8sdispatcherBase.h" + +uint8_t utils::L4ProtoEnum_to_int(L4ProtoEnum proto) { + switch (proto) { + case L4ProtoEnum::TCP: + return 6; + case L4ProtoEnum::UDP: + return 17; + case L4ProtoEnum::ICMP: + return 1; + default: + throw std::runtime_error("Bad proto"); + } +} + +L4ProtoEnum utils::int_to_L4ProtoEnum(uint8_t proto) { + switch (proto) { + case 6: + return L4ProtoEnum::TCP; + case 17: + return L4ProtoEnum::UDP; + case 1: + return L4ProtoEnum::ICMP; + default: + throw std::runtime_error("Bad proto number"); + } +} + +uint8_t utils::SessionRuleOperationEnum_to_int( + SessionRuleOperationEnum operation) { + switch (operation) { + case SessionRuleOperationEnum::XLATE_SRC: + return 0; + case SessionRuleOperationEnum::XLATE_DST: + return 1; + default: + throw std::runtime_error("Bad operation"); + } +} + +SessionRuleOperationEnum utils::int_to_SessionRuleOperationEnum( + const uint8_t operation) { + switch (operation) { + case 0: + return SessionRuleOperationEnum::XLATE_SRC; + case 1: + return SessionRuleOperationEnum::XLATE_DST; + default: + throw std::runtime_error("Bad operation number"); + } +} + +uint8_t utils::SessionRuleOriginatingRuleEnum_to_int( + SessionRuleOriginatingRuleEnum originatingRule) { + switch (originatingRule) { + case SessionRuleOriginatingRuleEnum::POD_TO_EXT: + return 0; + case SessionRuleOriginatingRuleEnum::NODEPORT_CLUSTER: + return 1; + default: + throw std::runtime_error("Bad originating rule"); + } +} + +SessionRuleOriginatingRuleEnum utils::int_to_SessionRuleOriginatingRuleEnum( + const uint8_t originatingRule) { + switch (originatingRule) { + case 0: + return SessionRuleOriginatingRuleEnum::POD_TO_EXT; + case 1: + return SessionRuleOriginatingRuleEnum::NODEPORT_CLUSTER; + default: + throw std::runtime_error("Bad originating rule number"); + } +} + +uint8_t utils::NodeportRuleExternalTrafficPolicyEnum_to_int( + NodeportRuleExternalTrafficPolicyEnum externalTrafficPolicy) { + switch (externalTrafficPolicy) { + case NodeportRuleExternalTrafficPolicyEnum::LOCAL: + return 0; + case NodeportRuleExternalTrafficPolicyEnum::CLUSTER: + return 1; + default: + throw std::runtime_error("Bad external traffic policy"); + } +} + +NodeportRuleExternalTrafficPolicyEnum +utils::int_to_NodeportRuleExternalTrafficPolicyEnum( + const uint8_t externalTrafficPolicy) { + switch (externalTrafficPolicy) { + case 0: + return NodeportRuleExternalTrafficPolicyEnum::LOCAL; + case 1: + return NodeportRuleExternalTrafficPolicyEnum::CLUSTER; + default: + throw std::runtime_error("Bad external traffic policy number"); + } +} + +bool utils::is_valid_ipv4_str(std::string const &ip) { + try { + // if the provided IPv4 address is not valid, the following call + // will throw and exception + polycube::service::utils::ip_string_to_nbo_uint(ip); + return true; + } catch (...) { + return false; + } +} \ No newline at end of file diff --git a/src/services/pcn-k8sdispatcher/src/Utils.h b/src/services/pcn-k8sdispatcher/src/Utils.h new file mode 100644 index 000000000..7fc180cc0 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/Utils.h @@ -0,0 +1,48 @@ +/* + * Copyright 2022 The Polycube Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef POLYCUBE_UTILS_H +#define POLYCUBE_UTILS_H + +#include "base/NodeportRuleBase.h" +#include "base/SessionRuleBase.h" + +namespace utils { +// conversion functions +uint8_t L4ProtoEnum_to_int(L4ProtoEnum proto); +L4ProtoEnum int_to_L4ProtoEnum(const uint8_t proto); + +uint8_t SessionRuleOperationEnum_to_int(SessionRuleOperationEnum operation); +SessionRuleOperationEnum int_to_SessionRuleOperationEnum( + const uint8_t operation); + +uint8_t SessionRuleOriginatingRuleEnum_to_int( + SessionRuleOriginatingRuleEnum originatingRule); +SessionRuleOriginatingRuleEnum int_to_SessionRuleOriginatingRuleEnum( + const uint8_t originatingRule); + +uint8_t NodeportRuleExternalTrafficPolicyEnum_to_int( + NodeportRuleExternalTrafficPolicyEnum externalTrafficPolicy); +NodeportRuleExternalTrafficPolicyEnum +int_to_NodeportRuleExternalTrafficPolicyEnum( + const uint8_t externalTrafficPolicy); + +// helper functions +bool is_valid_ipv4_str(std::string const &ip); + +} // namespace utils + +#endif // POLYCUBE_UTILS_H diff --git a/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApi.cpp b/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApi.cpp new file mode 100644 index 000000000..be5e216c4 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApi.cpp @@ -0,0 +1,1271 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + +#include "K8sdispatcherApi.h" +#include "K8sdispatcherApiImpl.h" + +using namespace polycube::service::model; +using namespace polycube::service::api::K8sdispatcherApiImpl; + +#ifdef __cplusplus +extern "C" { +#endif + +Response create_k8sdispatcher_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + K8sdispatcherJsonObject unique_value { request_body }; + + unique_value.setName(unique_name); + create_k8sdispatcher_by_id(unique_name, unique_value); + return { kCreated, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response create_k8sdispatcher_nodeport_rule_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + uint16_t unique_nodeportPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "nodeport-port")) { + unique_nodeportPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = NodeportRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + NodeportRuleJsonObject unique_value { request_body }; + + unique_value.setNodeportPort(unique_nodeportPort); + unique_value.setProto(unique_proto_); + create_k8sdispatcher_nodeport_rule_by_id(unique_name, unique_nodeportPort, unique_proto_, unique_value); + return { kCreated, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response create_k8sdispatcher_nodeport_rule_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + // Getting the body param + std::vector unique_value; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + std::vector unique_value; + for (auto &j : request_body) { + NodeportRuleJsonObject a { j }; + unique_value.push_back(a); + } + create_k8sdispatcher_nodeport_rule_list_by_id(unique_name, unique_value); + return { kCreated, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response create_k8sdispatcher_ports_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + std::string unique_portsName; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "ports_name")) { + unique_portsName = std::string { keys[i].value.string }; + break; + } + } + + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + PortsJsonObject unique_value { request_body }; + + unique_value.setName(unique_portsName); + create_k8sdispatcher_ports_by_id(unique_name, unique_portsName, unique_value); + return { kCreated, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response create_k8sdispatcher_ports_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + // Getting the body param + std::vector unique_value; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + std::vector unique_value; + for (auto &j : request_body) { + PortsJsonObject a { j }; + unique_value.push_back(a); + } + create_k8sdispatcher_ports_list_by_id(unique_name, unique_value); + return { kCreated, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response delete_k8sdispatcher_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + delete_k8sdispatcher_by_id(unique_name); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response delete_k8sdispatcher_nodeport_rule_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + uint16_t unique_nodeportPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "nodeport-port")) { + unique_nodeportPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = NodeportRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + delete_k8sdispatcher_nodeport_rule_by_id(unique_name, unique_nodeportPort, unique_proto_); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response delete_k8sdispatcher_nodeport_rule_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + delete_k8sdispatcher_nodeport_rule_list_by_id(unique_name); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response delete_k8sdispatcher_ports_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_portsName; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "ports_name")) { + unique_portsName = std::string { keys[i].value.string }; + break; + } + } + + + try { + delete_k8sdispatcher_ports_by_id(unique_name, unique_portsName); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response delete_k8sdispatcher_ports_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + delete_k8sdispatcher_ports_list_by_id(unique_name); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + + auto x = read_k8sdispatcher_by_id(unique_name); + nlohmann::json response_body; + response_body = x.toJson(); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_internal_src_ip_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + + auto x = read_k8sdispatcher_internal_src_ip_by_id(unique_name); + nlohmann::json response_body; + response_body = x; + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + + + try { + + auto x = read_k8sdispatcher_list_by_id(); + nlohmann::json response_body; + for (auto &i : x) { + response_body += i.toJson(); + } + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_nodeport_range_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + + auto x = read_k8sdispatcher_nodeport_range_by_id(unique_name); + nlohmann::json response_body; + response_body = x; + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_nodeport_rule_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + uint16_t unique_nodeportPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "nodeport-port")) { + unique_nodeportPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = NodeportRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + + auto x = read_k8sdispatcher_nodeport_rule_by_id(unique_name, unique_nodeportPort, unique_proto_); + nlohmann::json response_body; + response_body = x.toJson(); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + uint16_t unique_nodeportPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "nodeport-port")) { + unique_nodeportPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = NodeportRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + + auto x = read_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id(unique_name, unique_nodeportPort, unique_proto_); + nlohmann::json response_body; + response_body = NodeportRuleJsonObject::NodeportRuleExternalTrafficPolicyEnum_to_string(x); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_nodeport_rule_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + + auto x = read_k8sdispatcher_nodeport_rule_list_by_id(unique_name); + nlohmann::json response_body; + for (auto &i : x) { + response_body += i.toJson(); + } + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_nodeport_rule_rule_name_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + uint16_t unique_nodeportPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "nodeport-port")) { + unique_nodeportPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = NodeportRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + + auto x = read_k8sdispatcher_nodeport_rule_rule_name_by_id(unique_name, unique_nodeportPort, unique_proto_); + nlohmann::json response_body; + response_body = x; + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_ports_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_portsName; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "ports_name")) { + unique_portsName = std::string { keys[i].value.string }; + break; + } + } + + + try { + + auto x = read_k8sdispatcher_ports_by_id(unique_name, unique_portsName); + nlohmann::json response_body; + response_body = x.toJson(); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_ports_ip_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_portsName; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "ports_name")) { + unique_portsName = std::string { keys[i].value.string }; + break; + } + } + + + try { + + auto x = read_k8sdispatcher_ports_ip_by_id(unique_name, unique_portsName); + nlohmann::json response_body; + response_body = x; + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_ports_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + + auto x = read_k8sdispatcher_ports_list_by_id(unique_name); + nlohmann::json response_body; + for (auto &i : x) { + response_body += i.toJson(); + } + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_ports_type_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_portsName; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "ports_name")) { + unique_portsName = std::string { keys[i].value.string }; + break; + } + } + + + try { + + auto x = read_k8sdispatcher_ports_type_by_id(unique_name, unique_portsName); + nlohmann::json response_body; + response_body = PortsJsonObject::PortsTypeEnum_to_string(x); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_session_rule_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_direction; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "direction")) { + unique_direction = std::string { keys[i].value.string }; + break; + } + } + auto unique_direction_ = SessionRuleJsonObject::string_to_SessionRuleDirectionEnum(unique_direction); + + std::string unique_srcIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-ip")) { + unique_srcIp = std::string { keys[i].value.string }; + break; + } + } + + std::string unique_dstIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-ip")) { + unique_dstIp = std::string { keys[i].value.string }; + break; + } + } + + uint16_t unique_srcPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-port")) { + unique_srcPort = keys[i].value.uint16; + break; + } + } + + uint16_t unique_dstPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-port")) { + unique_dstPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = SessionRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + + auto x = read_k8sdispatcher_session_rule_by_id(unique_name, unique_direction_, unique_srcIp, unique_dstIp, unique_srcPort, unique_dstPort, unique_proto_); + nlohmann::json response_body; + response_body = x.toJson(); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_session_rule_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + + auto x = read_k8sdispatcher_session_rule_list_by_id(unique_name); + nlohmann::json response_body; + for (auto &i : x) { + response_body += i.toJson(); + } + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_session_rule_new_ip_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_direction; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "direction")) { + unique_direction = std::string { keys[i].value.string }; + break; + } + } + auto unique_direction_ = SessionRuleJsonObject::string_to_SessionRuleDirectionEnum(unique_direction); + + std::string unique_srcIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-ip")) { + unique_srcIp = std::string { keys[i].value.string }; + break; + } + } + + std::string unique_dstIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-ip")) { + unique_dstIp = std::string { keys[i].value.string }; + break; + } + } + + uint16_t unique_srcPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-port")) { + unique_srcPort = keys[i].value.uint16; + break; + } + } + + uint16_t unique_dstPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-port")) { + unique_dstPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = SessionRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + + auto x = read_k8sdispatcher_session_rule_new_ip_by_id(unique_name, unique_direction_, unique_srcIp, unique_dstIp, unique_srcPort, unique_dstPort, unique_proto_); + nlohmann::json response_body; + response_body = x; + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_session_rule_new_port_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_direction; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "direction")) { + unique_direction = std::string { keys[i].value.string }; + break; + } + } + auto unique_direction_ = SessionRuleJsonObject::string_to_SessionRuleDirectionEnum(unique_direction); + + std::string unique_srcIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-ip")) { + unique_srcIp = std::string { keys[i].value.string }; + break; + } + } + + std::string unique_dstIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-ip")) { + unique_dstIp = std::string { keys[i].value.string }; + break; + } + } + + uint16_t unique_srcPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-port")) { + unique_srcPort = keys[i].value.uint16; + break; + } + } + + uint16_t unique_dstPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-port")) { + unique_dstPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = SessionRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + + auto x = read_k8sdispatcher_session_rule_new_port_by_id(unique_name, unique_direction_, unique_srcIp, unique_dstIp, unique_srcPort, unique_dstPort, unique_proto_); + nlohmann::json response_body; + response_body = x; + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_session_rule_operation_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_direction; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "direction")) { + unique_direction = std::string { keys[i].value.string }; + break; + } + } + auto unique_direction_ = SessionRuleJsonObject::string_to_SessionRuleDirectionEnum(unique_direction); + + std::string unique_srcIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-ip")) { + unique_srcIp = std::string { keys[i].value.string }; + break; + } + } + + std::string unique_dstIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-ip")) { + unique_dstIp = std::string { keys[i].value.string }; + break; + } + } + + uint16_t unique_srcPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-port")) { + unique_srcPort = keys[i].value.uint16; + break; + } + } + + uint16_t unique_dstPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-port")) { + unique_dstPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = SessionRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + + auto x = read_k8sdispatcher_session_rule_operation_by_id(unique_name, unique_direction_, unique_srcIp, unique_dstIp, unique_srcPort, unique_dstPort, unique_proto_); + nlohmann::json response_body; + response_body = SessionRuleJsonObject::SessionRuleOperationEnum_to_string(x); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_session_rule_originating_rule_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_direction; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "direction")) { + unique_direction = std::string { keys[i].value.string }; + break; + } + } + auto unique_direction_ = SessionRuleJsonObject::string_to_SessionRuleDirectionEnum(unique_direction); + + std::string unique_srcIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-ip")) { + unique_srcIp = std::string { keys[i].value.string }; + break; + } + } + + std::string unique_dstIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-ip")) { + unique_dstIp = std::string { keys[i].value.string }; + break; + } + } + + uint16_t unique_srcPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-port")) { + unique_srcPort = keys[i].value.uint16; + break; + } + } + + uint16_t unique_dstPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-port")) { + unique_dstPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = SessionRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + + auto x = read_k8sdispatcher_session_rule_originating_rule_by_id(unique_name, unique_direction_, unique_srcIp, unique_dstIp, unique_srcPort, unique_dstPort, unique_proto_); + nlohmann::json response_body; + response_body = SessionRuleJsonObject::SessionRuleOriginatingRuleEnum_to_string(x); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response replace_k8sdispatcher_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + K8sdispatcherJsonObject unique_value { request_body }; + + unique_value.setName(unique_name); + replace_k8sdispatcher_by_id(unique_name, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response replace_k8sdispatcher_nodeport_rule_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + uint16_t unique_nodeportPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "nodeport-port")) { + unique_nodeportPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = NodeportRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + NodeportRuleJsonObject unique_value { request_body }; + + unique_value.setNodeportPort(unique_nodeportPort); + unique_value.setProto(unique_proto_); + replace_k8sdispatcher_nodeport_rule_by_id(unique_name, unique_nodeportPort, unique_proto_, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response replace_k8sdispatcher_nodeport_rule_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + // Getting the body param + std::vector unique_value; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + std::vector unique_value; + for (auto &j : request_body) { + NodeportRuleJsonObject a { j }; + unique_value.push_back(a); + } + replace_k8sdispatcher_nodeport_rule_list_by_id(unique_name, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response replace_k8sdispatcher_ports_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + std::string unique_portsName; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "ports_name")) { + unique_portsName = std::string { keys[i].value.string }; + break; + } + } + + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + PortsJsonObject unique_value { request_body }; + + unique_value.setName(unique_portsName); + replace_k8sdispatcher_ports_by_id(unique_name, unique_portsName, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response replace_k8sdispatcher_ports_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + // Getting the body param + std::vector unique_value; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + std::vector unique_value; + for (auto &j : request_body) { + PortsJsonObject a { j }; + unique_value.push_back(a); + } + replace_k8sdispatcher_ports_list_by_id(unique_name, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response update_k8sdispatcher_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + K8sdispatcherJsonObject unique_value { request_body }; + + unique_value.setName(unique_name); + update_k8sdispatcher_by_id(unique_name, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response update_k8sdispatcher_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + + // Getting the body param + std::vector unique_value; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + std::vector unique_value; + for (auto &j : request_body) { + K8sdispatcherJsonObject a { j }; + unique_value.push_back(a); + } + update_k8sdispatcher_list_by_id(unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response update_k8sdispatcher_nodeport_range_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // The conversion is done automatically by the json library + std::string unique_value = request_body; + update_k8sdispatcher_nodeport_range_by_id(unique_name, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response update_k8sdispatcher_nodeport_rule_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + uint16_t unique_nodeportPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "nodeport-port")) { + unique_nodeportPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = NodeportRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + NodeportRuleJsonObject unique_value { request_body }; + + unique_value.setNodeportPort(unique_nodeportPort); + unique_value.setProto(unique_proto_); + update_k8sdispatcher_nodeport_rule_by_id(unique_name, unique_nodeportPort, unique_proto_, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response update_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + uint16_t unique_nodeportPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "nodeport-port")) { + unique_nodeportPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = NodeportRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + NodeportRuleExternalTrafficPolicyEnum unique_value_ = NodeportRuleJsonObject::string_to_NodeportRuleExternalTrafficPolicyEnum(request_body); + update_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id(unique_name, unique_nodeportPort, unique_proto_, unique_value_); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response update_k8sdispatcher_nodeport_rule_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + // Getting the body param + std::vector unique_value; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + std::vector unique_value; + for (auto &j : request_body) { + NodeportRuleJsonObject a { j }; + unique_value.push_back(a); + } + update_k8sdispatcher_nodeport_rule_list_by_id(unique_name, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response update_k8sdispatcher_ports_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + std::string unique_portsName; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "ports_name")) { + unique_portsName = std::string { keys[i].value.string }; + break; + } + } + + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + PortsJsonObject unique_value { request_body }; + + unique_value.setName(unique_portsName); + update_k8sdispatcher_ports_by_id(unique_name, unique_portsName, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response update_k8sdispatcher_ports_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + // Getting the body param + std::vector unique_value; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + std::vector unique_value; + for (auto &j : request_body) { + PortsJsonObject a { j }; + unique_value.push_back(a); + } + update_k8sdispatcher_ports_list_by_id(unique_name, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + + +Response k8sdispatcher_list_by_id_help( + const char *name, const Key *keys, size_t num_keys) { + + nlohmann::json val = read_k8sdispatcher_list_by_id_get_list(); + + return { kOk, ::strdup(val.dump().c_str()) }; +} + +Response k8sdispatcher_nodeport_rule_list_by_id_help( + const char *name, const Key *keys, size_t num_keys) { + // Getting the path params + std::string unique_name { name }; + nlohmann::json val = read_k8sdispatcher_nodeport_rule_list_by_id_get_list(unique_name); + + return { kOk, ::strdup(val.dump().c_str()) }; +} + +Response k8sdispatcher_ports_list_by_id_help( + const char *name, const Key *keys, size_t num_keys) { + // Getting the path params + std::string unique_name { name }; + nlohmann::json val = read_k8sdispatcher_ports_list_by_id_get_list(unique_name); + + return { kOk, ::strdup(val.dump().c_str()) }; +} + +Response k8sdispatcher_session_rule_list_by_id_help( + const char *name, const Key *keys, size_t num_keys) { + // Getting the path params + std::string unique_name { name }; + nlohmann::json val = read_k8sdispatcher_session_rule_list_by_id_get_list(unique_name); + + return { kOk, ::strdup(val.dump().c_str()) }; +} + +#ifdef __cplusplus +} +#endif + diff --git a/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApi.h b/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApi.h new file mode 100644 index 000000000..90287533f --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApi.h @@ -0,0 +1,86 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* K8sdispatcherApi.h +* +*/ + +#pragma once + +#define POLYCUBE_SERVICE_NAME "k8sdispatcher" + + +#include "polycube/services/response.h" +#include "polycube/services/shared_lib_elements.h" + +#include "K8sdispatcherJsonObject.h" +#include "NodeportRuleJsonObject.h" +#include "PortsJsonObject.h" +#include "SessionRuleJsonObject.h" +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +Response create_k8sdispatcher_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response create_k8sdispatcher_nodeport_rule_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response create_k8sdispatcher_nodeport_rule_list_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response create_k8sdispatcher_ports_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response create_k8sdispatcher_ports_list_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response delete_k8sdispatcher_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response delete_k8sdispatcher_nodeport_rule_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response delete_k8sdispatcher_nodeport_rule_list_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response delete_k8sdispatcher_ports_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response delete_k8sdispatcher_ports_list_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_internal_src_ip_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_list_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_nodeport_range_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_nodeport_rule_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_nodeport_rule_list_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_nodeport_rule_rule_name_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_ports_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_ports_ip_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_ports_list_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_ports_type_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_session_rule_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_session_rule_list_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_session_rule_new_ip_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_session_rule_new_port_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_session_rule_operation_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_session_rule_originating_rule_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response replace_k8sdispatcher_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response replace_k8sdispatcher_nodeport_rule_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response replace_k8sdispatcher_nodeport_rule_list_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response replace_k8sdispatcher_ports_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response replace_k8sdispatcher_ports_list_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_k8sdispatcher_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_k8sdispatcher_list_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_k8sdispatcher_nodeport_range_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_k8sdispatcher_nodeport_rule_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_k8sdispatcher_nodeport_rule_list_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_k8sdispatcher_ports_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_k8sdispatcher_ports_list_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); + +Response k8sdispatcher_list_by_id_help(const char *name, const Key *keys, size_t num_keys); +Response k8sdispatcher_nodeport_rule_list_by_id_help(const char *name, const Key *keys, size_t num_keys); +Response k8sdispatcher_ports_list_by_id_help(const char *name, const Key *keys, size_t num_keys); +Response k8sdispatcher_session_rule_list_by_id_help(const char *name, const Key *keys, size_t num_keys); + + +#ifdef __cplusplus +} +#endif + diff --git a/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApiImpl.cpp b/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApiImpl.cpp new file mode 100644 index 000000000..a4bf15212 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApiImpl.cpp @@ -0,0 +1,855 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + +#include "K8sdispatcherApiImpl.h" + +namespace polycube { +namespace service { +namespace api { + +using namespace polycube::service::model; + +namespace K8sdispatcherApiImpl { +namespace { +std::unordered_map> cubes; +std::mutex cubes_mutex; + +std::shared_ptr get_cube(const std::string &name) { + std::lock_guard guard(cubes_mutex); + auto iter = cubes.find(name); + if (iter == cubes.end()) { + throw std::runtime_error("Cube " + name + " does not exist"); + } + + return iter->second; +} + +} + +void create_k8sdispatcher_by_id(const std::string &name, const K8sdispatcherJsonObject &jsonObject) { + { + // check if name is valid before creating it + std::lock_guard guard(cubes_mutex); + if (cubes.count(name) != 0) { + throw std::runtime_error("There is already a cube with name " + name); + } + } + auto ptr = std::make_shared(name, jsonObject); + std::unordered_map>::iterator iter; + bool inserted; + + std::lock_guard guard(cubes_mutex); + std::tie(iter, inserted) = cubes.emplace(name, std::move(ptr)); + + if (!inserted) { + throw std::runtime_error("There is already a cube with name " + name); + } +} + +void replace_k8sdispatcher_by_id(const std::string &name, const K8sdispatcherJsonObject &bridge){ + throw std::runtime_error("Method not supported!"); +} + +void delete_k8sdispatcher_by_id(const std::string &name) { + std::lock_guard guard(cubes_mutex); + if (cubes.count(name) == 0) { + throw std::runtime_error("Cube " + name + " does not exist"); + } + cubes.erase(name); +} + +std::vector read_k8sdispatcher_list_by_id() { + std::vector jsonObject_vect; + for(auto &i : cubes) { + auto m = get_cube(i.first); + jsonObject_vect.push_back(m->toJsonObject()); + } + return jsonObject_vect; +} + +std::vector> read_k8sdispatcher_list_by_id_get_list() { + std::vector> r; + for (auto &x : cubes) { + nlohmann::fifo_map m; + m["name"] = x.first; + r.push_back(std::move(m)); + } + return r; +} + +/** +* @brief Create nodeport-rule by ID +* +* Create operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* @param[in] nodeportPort ID of nodeport-port +* @param[in] proto ID of proto +* @param[in] value nodeport-rulebody object +* +* Responses: +* +*/ +void +create_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &value) { + auto k8sdispatcher = get_cube(name); + + return k8sdispatcher->addNodeportRule(nodeportPort, proto, value); +} + +/** +* @brief Create nodeport-rule by ID +* +* Create operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* @param[in] value nodeport-rulebody object +* +* Responses: +* +*/ +void +create_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name, const std::vector &value) { + auto k8sdispatcher = get_cube(name); + k8sdispatcher->addNodeportRuleList(value); +} + +/** +* @brief Create ports by ID +* +* Create operation of resource: ports* +* +* @param[in] name ID of name +* @param[in] portsName ID of ports_name +* @param[in] value portsbody object +* +* Responses: +* +*/ +void +create_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName, const PortsJsonObject &value) { + auto k8sdispatcher = get_cube(name); + + return k8sdispatcher->addPorts(portsName, value); +} + +/** +* @brief Create ports by ID +* +* Create operation of resource: ports* +* +* @param[in] name ID of name +* @param[in] value portsbody object +* +* Responses: +* +*/ +void +create_k8sdispatcher_ports_list_by_id(const std::string &name, const std::vector &value) { + auto k8sdispatcher = get_cube(name); + k8sdispatcher->addPortsList(value); +} + +/** +* @brief Delete nodeport-rule by ID +* +* Delete operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* @param[in] nodeportPort ID of nodeport-port +* @param[in] proto ID of proto +* +* Responses: +* +*/ +void +delete_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + + return k8sdispatcher->delNodeportRule(nodeportPort, proto); +} + +/** +* @brief Delete nodeport-rule by ID +* +* Delete operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* +* Responses: +* +*/ +void +delete_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name) { + auto k8sdispatcher = get_cube(name); + k8sdispatcher->delNodeportRuleList(); +} + +/** +* @brief Delete ports by ID +* +* Delete operation of resource: ports* +* +* @param[in] name ID of name +* @param[in] portsName ID of ports_name +* +* Responses: +* +*/ +void +delete_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName) { + auto k8sdispatcher = get_cube(name); + + return k8sdispatcher->delPorts(portsName); +} + +/** +* @brief Delete ports by ID +* +* Delete operation of resource: ports* +* +* @param[in] name ID of name +* +* Responses: +* +*/ +void +delete_k8sdispatcher_ports_list_by_id(const std::string &name) { + auto k8sdispatcher = get_cube(name); + k8sdispatcher->delPortsList(); +} + +/** +* @brief Read k8sdispatcher by ID +* +* Read operation of resource: k8sdispatcher* +* +* @param[in] name ID of name +* +* Responses: +* K8sdispatcherJsonObject +*/ +K8sdispatcherJsonObject +read_k8sdispatcher_by_id(const std::string &name) { + return get_cube(name)->toJsonObject(); + +} + +/** +* @brief Read internal-src-ip by ID +* +* Read operation of resource: internal-src-ip* +* +* @param[in] name ID of name +* +* Responses: +* std::string +*/ +std::string +read_k8sdispatcher_internal_src_ip_by_id(const std::string &name) { + auto k8sdispatcher = get_cube(name); + return k8sdispatcher->getInternalSrcIp(); + +} + +/** +* @brief Read nodeport-range by ID +* +* Read operation of resource: nodeport-range* +* +* @param[in] name ID of name +* +* Responses: +* std::string +*/ +std::string +read_k8sdispatcher_nodeport_range_by_id(const std::string &name) { + auto k8sdispatcher = get_cube(name); + return k8sdispatcher->getNodeportRange(); + +} + +/** +* @brief Read nodeport-rule by ID +* +* Read operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* @param[in] nodeportPort ID of nodeport-port +* @param[in] proto ID of proto +* +* Responses: +* NodeportRuleJsonObject +*/ +NodeportRuleJsonObject +read_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + return k8sdispatcher->getNodeportRule(nodeportPort, proto)->toJsonObject(); + +} + +/** +* @brief Read external-traffic-policy by ID +* +* Read operation of resource: external-traffic-policy* +* +* @param[in] name ID of name +* @param[in] nodeportPort ID of nodeport-port +* @param[in] proto ID of proto +* +* Responses: +* NodeportRuleExternalTrafficPolicyEnum +*/ +NodeportRuleExternalTrafficPolicyEnum +read_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + auto nodeportRule = k8sdispatcher->getNodeportRule(nodeportPort, proto); + return nodeportRule->getExternalTrafficPolicy(); + +} + +/** +* @brief Read nodeport-rule by ID +* +* Read operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* +* Responses: +* std::vector +*/ +std::vector +read_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name) { + auto k8sdispatcher = get_cube(name); + auto &&nodeportRule = k8sdispatcher->getNodeportRuleList(); + std::vector m; + for(auto &i : nodeportRule) + m.push_back(i->toJsonObject()); + return m; +} + +/** +* @brief Read rule-name by ID +* +* Read operation of resource: rule-name* +* +* @param[in] name ID of name +* @param[in] nodeportPort ID of nodeport-port +* @param[in] proto ID of proto +* +* Responses: +* std::string +*/ +std::string +read_k8sdispatcher_nodeport_rule_rule_name_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + auto nodeportRule = k8sdispatcher->getNodeportRule(nodeportPort, proto); + return nodeportRule->getRuleName(); + +} + +/** +* @brief Read ports by ID +* +* Read operation of resource: ports* +* +* @param[in] name ID of name +* @param[in] portsName ID of ports_name +* +* Responses: +* PortsJsonObject +*/ +PortsJsonObject +read_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName) { + auto k8sdispatcher = get_cube(name); + return k8sdispatcher->getPorts(portsName)->toJsonObject(); + +} + +/** +* @brief Read ip by ID +* +* Read operation of resource: ip* +* +* @param[in] name ID of name +* @param[in] portsName ID of ports_name +* +* Responses: +* std::string +*/ +std::string +read_k8sdispatcher_ports_ip_by_id(const std::string &name, const std::string &portsName) { + auto k8sdispatcher = get_cube(name); + auto ports = k8sdispatcher->getPorts(portsName); + return ports->getIp(); + +} + +/** +* @brief Read ports by ID +* +* Read operation of resource: ports* +* +* @param[in] name ID of name +* +* Responses: +* std::vector +*/ +std::vector +read_k8sdispatcher_ports_list_by_id(const std::string &name) { + auto k8sdispatcher = get_cube(name); + auto &&ports = k8sdispatcher->getPortsList(); + std::vector m; + for(auto &i : ports) + m.push_back(i->toJsonObject()); + return m; +} + +/** +* @brief Read type by ID +* +* Read operation of resource: type* +* +* @param[in] name ID of name +* @param[in] portsName ID of ports_name +* +* Responses: +* PortsTypeEnum +*/ +PortsTypeEnum +read_k8sdispatcher_ports_type_by_id(const std::string &name, const std::string &portsName) { + auto k8sdispatcher = get_cube(name); + auto ports = k8sdispatcher->getPorts(portsName); + return ports->getType(); + +} + +/** +* @brief Read session-rule by ID +* +* Read operation of resource: session-rule* +* +* @param[in] name ID of name +* @param[in] direction ID of direction +* @param[in] srcIp ID of src-ip +* @param[in] dstIp ID of dst-ip +* @param[in] srcPort ID of src-port +* @param[in] dstPort ID of dst-port +* @param[in] proto ID of proto +* +* Responses: +* SessionRuleJsonObject +*/ +SessionRuleJsonObject +read_k8sdispatcher_session_rule_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + return k8sdispatcher->getSessionRule(direction, srcIp, dstIp, srcPort, dstPort, proto)->toJsonObject(); + +} + +/** +* @brief Read session-rule by ID +* +* Read operation of resource: session-rule* +* +* @param[in] name ID of name +* +* Responses: +* std::vector +*/ +std::vector +read_k8sdispatcher_session_rule_list_by_id(const std::string &name) { + auto k8sdispatcher = get_cube(name); + auto &&sessionRule = k8sdispatcher->getSessionRuleList(); + std::vector m; + for(auto &i : sessionRule) + m.push_back(i->toJsonObject()); + return m; +} + +/** +* @brief Read new-ip by ID +* +* Read operation of resource: new-ip* +* +* @param[in] name ID of name +* @param[in] direction ID of direction +* @param[in] srcIp ID of src-ip +* @param[in] dstIp ID of dst-ip +* @param[in] srcPort ID of src-port +* @param[in] dstPort ID of dst-port +* @param[in] proto ID of proto +* +* Responses: +* std::string +*/ +std::string +read_k8sdispatcher_session_rule_new_ip_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + auto sessionRule = k8sdispatcher->getSessionRule(direction, srcIp, dstIp, srcPort, dstPort, proto); + return sessionRule->getNewIp(); + +} + +/** +* @brief Read new-port by ID +* +* Read operation of resource: new-port* +* +* @param[in] name ID of name +* @param[in] direction ID of direction +* @param[in] srcIp ID of src-ip +* @param[in] dstIp ID of dst-ip +* @param[in] srcPort ID of src-port +* @param[in] dstPort ID of dst-port +* @param[in] proto ID of proto +* +* Responses: +* uint16_t +*/ +uint16_t +read_k8sdispatcher_session_rule_new_port_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + auto sessionRule = k8sdispatcher->getSessionRule(direction, srcIp, dstIp, srcPort, dstPort, proto); + return sessionRule->getNewPort(); + +} + +/** +* @brief Read operation by ID +* +* Read operation of resource: operation* +* +* @param[in] name ID of name +* @param[in] direction ID of direction +* @param[in] srcIp ID of src-ip +* @param[in] dstIp ID of dst-ip +* @param[in] srcPort ID of src-port +* @param[in] dstPort ID of dst-port +* @param[in] proto ID of proto +* +* Responses: +* SessionRuleOperationEnum +*/ +SessionRuleOperationEnum +read_k8sdispatcher_session_rule_operation_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + auto sessionRule = k8sdispatcher->getSessionRule(direction, srcIp, dstIp, srcPort, dstPort, proto); + return sessionRule->getOperation(); + +} + +/** +* @brief Read originating-rule by ID +* +* Read operation of resource: originating-rule* +* +* @param[in] name ID of name +* @param[in] direction ID of direction +* @param[in] srcIp ID of src-ip +* @param[in] dstIp ID of dst-ip +* @param[in] srcPort ID of src-port +* @param[in] dstPort ID of dst-port +* @param[in] proto ID of proto +* +* Responses: +* SessionRuleOriginatingRuleEnum +*/ +SessionRuleOriginatingRuleEnum +read_k8sdispatcher_session_rule_originating_rule_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + auto sessionRule = k8sdispatcher->getSessionRule(direction, srcIp, dstIp, srcPort, dstPort, proto); + return sessionRule->getOriginatingRule(); + +} + +/** +* @brief Replace nodeport-rule by ID +* +* Replace operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* @param[in] nodeportPort ID of nodeport-port +* @param[in] proto ID of proto +* @param[in] value nodeport-rulebody object +* +* Responses: +* +*/ +void +replace_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &value) { + auto k8sdispatcher = get_cube(name); + + return k8sdispatcher->replaceNodeportRule(nodeportPort, proto, value); +} + +/** +* @brief Replace nodeport-rule by ID +* +* Replace operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* @param[in] value nodeport-rulebody object +* +* Responses: +* +*/ +void +replace_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name, const std::vector &value) { + auto k8sdispatcher = get_cube(name); + return k8sdispatcher->replaceNodeportRuleList(value); +} + +/** +* @brief Replace ports by ID +* +* Replace operation of resource: ports* +* +* @param[in] name ID of name +* @param[in] portsName ID of ports_name +* @param[in] value portsbody object +* +* Responses: +* +*/ +void +replace_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName, const PortsJsonObject &value) { + auto k8sdispatcher = get_cube(name); + + return k8sdispatcher->replacePorts(portsName, value); +} + +/** +* @brief Replace ports by ID +* +* Replace operation of resource: ports* +* +* @param[in] name ID of name +* @param[in] value portsbody object +* +* Responses: +* +*/ +void +replace_k8sdispatcher_ports_list_by_id(const std::string &name, const std::vector &value) { + throw std::runtime_error("Method not supported"); +} + +/** +* @brief Update k8sdispatcher by ID +* +* Update operation of resource: k8sdispatcher* +* +* @param[in] name ID of name +* @param[in] value k8sdispatcherbody object +* +* Responses: +* +*/ +void +update_k8sdispatcher_by_id(const std::string &name, const K8sdispatcherJsonObject &value) { + auto k8sdispatcher = get_cube(name); + + return k8sdispatcher->update(value); +} + +/** +* @brief Update k8sdispatcher by ID +* +* Update operation of resource: k8sdispatcher* +* +* @param[in] value k8sdispatcherbody object +* +* Responses: +* +*/ +void +update_k8sdispatcher_list_by_id(const std::vector &value) { + throw std::runtime_error("Method not supported"); +} + +/** +* @brief Update nodeport-range by ID +* +* Update operation of resource: nodeport-range* +* +* @param[in] name ID of name +* @param[in] value Port range used for NodePort Services +* +* Responses: +* +*/ +void +update_k8sdispatcher_nodeport_range_by_id(const std::string &name, const std::string &value) { + auto k8sdispatcher = get_cube(name); + + return k8sdispatcher->setNodeportRange(value); +} + +/** +* @brief Update nodeport-rule by ID +* +* Update operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* @param[in] nodeportPort ID of nodeport-port +* @param[in] proto ID of proto +* @param[in] value nodeport-rulebody object +* +* Responses: +* +*/ +void +update_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &value) { + auto k8sdispatcher = get_cube(name); + auto nodeportRule = k8sdispatcher->getNodeportRule(nodeportPort, proto); + + return nodeportRule->update(value); +} + +/** +* @brief Update external-traffic-policy by ID +* +* Update operation of resource: external-traffic-policy* +* +* @param[in] name ID of name +* @param[in] nodeportPort ID of nodeport-port +* @param[in] proto ID of proto +* @param[in] value The external traffic policy of the Kubernetes NodePort Service +* +* Responses: +* +*/ +void +update_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleExternalTrafficPolicyEnum &value) { + auto k8sdispatcher = get_cube(name); + auto nodeportRule = k8sdispatcher->getNodeportRule(nodeportPort, proto); + + return nodeportRule->setExternalTrafficPolicy(value); +} + +/** +* @brief Update nodeport-rule by ID +* +* Update operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* @param[in] value nodeport-rulebody object +* +* Responses: +* +*/ +void +update_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name, const std::vector &value) { + auto k8sdispatcher = get_cube(name); + return k8sdispatcher->updateNodeportRuleList(value); +} + +/** +* @brief Update ports by ID +* +* Update operation of resource: ports* +* +* @param[in] name ID of name +* @param[in] portsName ID of ports_name +* @param[in] value portsbody object +* +* Responses: +* +*/ +void +update_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName, const PortsJsonObject &value) { + auto k8sdispatcher = get_cube(name); + auto ports = k8sdispatcher->getPorts(portsName); + + return ports->update(value); +} + +/** +* @brief Update ports by ID +* +* Update operation of resource: ports* +* +* @param[in] name ID of name +* @param[in] value portsbody object +* +* Responses: +* +*/ +void +update_k8sdispatcher_ports_list_by_id(const std::string &name, const std::vector &value) { + throw std::runtime_error("Method not supported"); +} + + + +/* + * help related + */ + +std::vector> read_k8sdispatcher_nodeport_rule_list_by_id_get_list(const std::string &name) { + std::vector> r; + auto &&k8sdispatcher = get_cube(name); + + auto &&nodeportRule = k8sdispatcher->getNodeportRuleList(); + for(auto &i : nodeportRule) { + nlohmann::fifo_map keys; + + keys["nodeportPort"] = std::to_string(i->getNodeportPort()); + keys["proto"] = NodeportRuleJsonObject::L4ProtoEnum_to_string(i->getProto()); + + r.push_back(keys); + } + return r; +} + +std::vector> read_k8sdispatcher_ports_list_by_id_get_list(const std::string &name) { + std::vector> r; + auto &&k8sdispatcher = get_cube(name); + + auto &&ports = k8sdispatcher->getPortsList(); + for(auto &i : ports) { + nlohmann::fifo_map keys; + + keys["name"] = i->getName(); + + r.push_back(keys); + } + return r; +} + +std::vector> read_k8sdispatcher_session_rule_list_by_id_get_list(const std::string &name) { + std::vector> r; + auto &&k8sdispatcher = get_cube(name); + + auto &&sessionRule = k8sdispatcher->getSessionRuleList(); + for(auto &i : sessionRule) { + nlohmann::fifo_map keys; + + keys["direction"] = SessionRuleJsonObject::SessionRuleDirectionEnum_to_string(i->getDirection()); + keys["srcIp"] = i->getSrcIp(); + keys["dstIp"] = i->getDstIp(); + keys["srcPort"] = std::to_string(i->getSrcPort()); + keys["dstPort"] = std::to_string(i->getDstPort()); + keys["proto"] = SessionRuleJsonObject::L4ProtoEnum_to_string(i->getProto()); + + r.push_back(keys); + } + return r; +} + + +} + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApiImpl.h b/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApiImpl.h new file mode 100644 index 000000000..15333cfc5 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApiImpl.h @@ -0,0 +1,90 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* K8sdispatcherApiImpl.h +* +* +*/ + +#pragma once + + +#include +#include +#include +#include "../K8sdispatcher.h" + +#include "K8sdispatcherJsonObject.h" +#include "NodeportRuleJsonObject.h" +#include "PortsJsonObject.h" +#include "SessionRuleJsonObject.h" +#include + +namespace polycube { +namespace service { +namespace api { + +using namespace polycube::service::model; + +namespace K8sdispatcherApiImpl { + void create_k8sdispatcher_by_id(const std::string &name, const K8sdispatcherJsonObject &value); + void create_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &value); + void create_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name, const std::vector &value); + void create_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName, const PortsJsonObject &value); + void create_k8sdispatcher_ports_list_by_id(const std::string &name, const std::vector &value); + void delete_k8sdispatcher_by_id(const std::string &name); + void delete_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto); + void delete_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name); + void delete_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName); + void delete_k8sdispatcher_ports_list_by_id(const std::string &name); + K8sdispatcherJsonObject read_k8sdispatcher_by_id(const std::string &name); + std::string read_k8sdispatcher_internal_src_ip_by_id(const std::string &name); + std::vector read_k8sdispatcher_list_by_id(); + std::string read_k8sdispatcher_nodeport_range_by_id(const std::string &name); + NodeportRuleJsonObject read_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto); + NodeportRuleExternalTrafficPolicyEnum read_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto); + std::vector read_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name); + std::string read_k8sdispatcher_nodeport_rule_rule_name_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto); + PortsJsonObject read_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName); + std::string read_k8sdispatcher_ports_ip_by_id(const std::string &name, const std::string &portsName); + std::vector read_k8sdispatcher_ports_list_by_id(const std::string &name); + PortsTypeEnum read_k8sdispatcher_ports_type_by_id(const std::string &name, const std::string &portsName); + SessionRuleJsonObject read_k8sdispatcher_session_rule_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto); + std::vector read_k8sdispatcher_session_rule_list_by_id(const std::string &name); + std::string read_k8sdispatcher_session_rule_new_ip_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto); + uint16_t read_k8sdispatcher_session_rule_new_port_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto); + SessionRuleOperationEnum read_k8sdispatcher_session_rule_operation_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto); + SessionRuleOriginatingRuleEnum read_k8sdispatcher_session_rule_originating_rule_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto); + void replace_k8sdispatcher_by_id(const std::string &name, const K8sdispatcherJsonObject &value); + void replace_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &value); + void replace_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name, const std::vector &value); + void replace_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName, const PortsJsonObject &value); + void replace_k8sdispatcher_ports_list_by_id(const std::string &name, const std::vector &value); + void update_k8sdispatcher_by_id(const std::string &name, const K8sdispatcherJsonObject &value); + void update_k8sdispatcher_list_by_id(const std::vector &value); + void update_k8sdispatcher_nodeport_range_by_id(const std::string &name, const std::string &value); + void update_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &value); + void update_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleExternalTrafficPolicyEnum &value); + void update_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name, const std::vector &value); + void update_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName, const PortsJsonObject &value); + void update_k8sdispatcher_ports_list_by_id(const std::string &name, const std::vector &value); + + /* help related */ + std::vector> read_k8sdispatcher_list_by_id_get_list(); + std::vector> read_k8sdispatcher_nodeport_rule_list_by_id_get_list(const std::string &name); + std::vector> read_k8sdispatcher_ports_list_by_id_get_list(const std::string &name); + std::vector> read_k8sdispatcher_session_rule_list_by_id_get_list(const std::string &name); + +} +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/base/K8sdispatcherBase.cpp b/src/services/pcn-k8sdispatcher/src/base/K8sdispatcherBase.cpp new file mode 100644 index 000000000..8eb540dbe --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/base/K8sdispatcherBase.cpp @@ -0,0 +1,171 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + +#include "K8sdispatcherBase.h" + +K8sdispatcherBase::K8sdispatcherBase(const std::string name) { + logger()->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [K8sdispatcher] [%n] [%l] %v"); +} + + + +K8sdispatcherBase::~K8sdispatcherBase() {} + +void K8sdispatcherBase::update(const K8sdispatcherJsonObject &conf) { + set_conf(conf.getBase()); + + if (conf.portsIsSet()) { + for (auto &i : conf.getPorts()) { + auto name = i.getName(); + auto m = getPorts(name); + m->update(i); + } + } + if (conf.nodeportRangeIsSet()) { + setNodeportRange(conf.getNodeportRange()); + } + if (conf.sessionRuleIsSet()) { + for (auto &i : conf.getSessionRule()) { + auto direction = i.getDirection(); + auto srcIp = i.getSrcIp(); + auto dstIp = i.getDstIp(); + auto srcPort = i.getSrcPort(); + auto dstPort = i.getDstPort(); + auto proto = i.getProto(); + auto m = getSessionRule(direction, srcIp, dstIp, srcPort, dstPort, proto); + m->update(i); + } + } + if (conf.nodeportRuleIsSet()) { + for (auto &i : conf.getNodeportRule()) { + auto nodeportPort = i.getNodeportPort(); + auto proto = i.getProto(); + auto m = getNodeportRule(nodeportPort, proto); + m->update(i); + } + } +} + +K8sdispatcherJsonObject K8sdispatcherBase::toJsonObject() { + K8sdispatcherJsonObject conf; + conf.setBase(to_json()); + + conf.setName(getName()); + for (auto &i : getPortsList()) { + conf.addPorts(i->toJsonObject()); + } + conf.setInternalSrcIp(getInternalSrcIp()); + conf.setNodeportRange(getNodeportRange()); + for(auto &i : getSessionRuleList()) { + conf.addSessionRule(i->toJsonObject()); + } + for(auto &i : getNodeportRuleList()) { + conf.addNodeportRule(i->toJsonObject()); + } + + return conf; +} +void K8sdispatcherBase::addPortsList(const std::vector &conf) { + for (auto &i : conf) { + std::string name_ = i.getName(); + addPorts(name_, i); + } +} + +void K8sdispatcherBase::replacePorts(const std::string &name, const PortsJsonObject &conf) { + delPorts(name); + std::string name_ = conf.getName(); + addPorts(name_, conf); +} + +void K8sdispatcherBase::delPortsList() { + auto elements = getPortsList(); + for (auto &i : elements) { + std::string name_ = i->getName(); + delPorts(name_); + } +} + +void K8sdispatcherBase::addPorts(const std::string &name, const PortsJsonObject &conf) { + add_port(name, conf); +} + +void K8sdispatcherBase::delPorts(const std::string &name) { + remove_port(name); +} + +std::shared_ptr K8sdispatcherBase::getPorts(const std::string &name) { + return get_port(name); +} + +std::vector> K8sdispatcherBase::getPortsList() { + return get_ports(); +} +void K8sdispatcherBase::addSessionRuleList(const std::vector &conf) { + for (auto &i : conf) { + SessionRuleDirectionEnum direction_ = i.getDirection(); + std::string srcIp_ = i.getSrcIp(); + std::string dstIp_ = i.getDstIp(); + uint16_t srcPort_ = i.getSrcPort(); + uint16_t dstPort_ = i.getDstPort(); + L4ProtoEnum proto_ = i.getProto(); + addSessionRule(direction_, srcIp_, dstIp_, srcPort_, dstPort_, proto_, i); + } +} + +void K8sdispatcherBase::replaceSessionRule(const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto, const SessionRuleJsonObject &conf) { + delSessionRule(direction, srcIp, dstIp, srcPort, dstPort, proto); + SessionRuleDirectionEnum direction_ = conf.getDirection(); + std::string srcIp_ = conf.getSrcIp(); + std::string dstIp_ = conf.getDstIp(); + uint16_t srcPort_ = conf.getSrcPort(); + uint16_t dstPort_ = conf.getDstPort(); + L4ProtoEnum proto_ = conf.getProto(); + addSessionRule(direction_, srcIp_, dstIp_, srcPort_, dstPort_, proto_, conf); +} + +void K8sdispatcherBase::delSessionRuleList() { + auto elements = getSessionRuleList(); + for (auto &i : elements) { + SessionRuleDirectionEnum direction_ = i->getDirection(); + std::string srcIp_ = i->getSrcIp(); + std::string dstIp_ = i->getDstIp(); + uint16_t srcPort_ = i->getSrcPort(); + uint16_t dstPort_ = i->getDstPort(); + L4ProtoEnum proto_ = i->getProto(); + delSessionRule(direction_, srcIp_, dstIp_, srcPort_, dstPort_, proto_); + } +} +void K8sdispatcherBase::addNodeportRuleList(const std::vector &conf) { + for (auto &i : conf) { + uint16_t nodeportPort_ = i.getNodeportPort(); + L4ProtoEnum proto_ = i.getProto(); + addNodeportRule(nodeportPort_, proto_, i); + } +} + +void K8sdispatcherBase::replaceNodeportRule(const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &conf) { + delNodeportRule(nodeportPort, proto); + uint16_t nodeportPort_ = conf.getNodeportPort(); + L4ProtoEnum proto_ = conf.getProto(); + addNodeportRule(nodeportPort_, proto_, conf); +} + +void K8sdispatcherBase::delNodeportRuleList() { + auto elements = getNodeportRuleList(); + for (auto &i : elements) { + uint16_t nodeportPort_ = i->getNodeportPort(); + L4ProtoEnum proto_ = i->getProto(); + delNodeportRule(nodeportPort_, proto_); + } +} + + diff --git a/src/services/pcn-k8sdispatcher/src/base/K8sdispatcherBase.h b/src/services/pcn-k8sdispatcher/src/base/K8sdispatcherBase.h new file mode 100644 index 000000000..375b025e1 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/base/K8sdispatcherBase.h @@ -0,0 +1,91 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* K8sdispatcherBase.h +* +* +*/ + +#pragma once + +#include "../serializer/K8sdispatcherJsonObject.h" + +#include "../NodeportRule.h" +#include "../Ports.h" +#include "../SessionRule.h" + +#include "polycube/services/cube.h" +#include "polycube/services/port.h" + + + +#include "polycube/services/utils.h" +#include "polycube/services/fifo_map.hpp" + +#include + +using namespace polycube::service::model; + + +class K8sdispatcherBase: public virtual polycube::service::Cube { + public: + K8sdispatcherBase(const std::string name); + + virtual ~K8sdispatcherBase(); + virtual void update(const K8sdispatcherJsonObject &conf); + virtual K8sdispatcherJsonObject toJsonObject(); + + /// + /// Entry of the ports table + /// + virtual std::shared_ptr getPorts(const std::string &name); + virtual std::vector> getPortsList(); + virtual void addPorts(const std::string &name, const PortsJsonObject &conf); + virtual void addPortsList(const std::vector &conf); + virtual void replacePorts(const std::string &name, const PortsJsonObject &conf); + virtual void delPorts(const std::string &name); + virtual void delPortsList(); + + /// + /// Internal source IP address used for natting incoming packets directed to Kubernetes Services with a CLUSTER external traffic policy + /// + virtual std::string getInternalSrcIp() = 0; + + /// + /// Port range used for NodePort Services + /// + virtual std::string getNodeportRange() = 0; + virtual void setNodeportRange(const std::string &value) = 0; + + /// + /// Session entry related to a specific traffic direction + /// + virtual std::shared_ptr getSessionRule(const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto) = 0; + virtual std::vector> getSessionRuleList() = 0; + virtual void addSessionRule(const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto, const SessionRuleJsonObject &conf) = 0; + virtual void addSessionRuleList(const std::vector &conf); + virtual void replaceSessionRule(const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto, const SessionRuleJsonObject &conf); + virtual void delSessionRule(const SessionRuleDirectionEnum &direction,const std::string &srcIp,const std::string &dstIp,const uint16_t &srcPort,const uint16_t &dstPort,const L4ProtoEnum &proto) = 0; + virtual void delSessionRuleList(); + + /// + /// NodePort rule associated with a Kubernetes NodePort Service + /// + virtual std::shared_ptr getNodeportRule(const uint16_t &nodeportPort, const L4ProtoEnum &proto) = 0; + virtual std::vector> getNodeportRuleList() = 0; + virtual void addNodeportRule(const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &conf) = 0; + virtual void addNodeportRuleList(const std::vector &conf); + virtual void updateNodeportRuleList(const std::vector &conf) = 0; + virtual void replaceNodeportRule(const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &conf); + virtual void replaceNodeportRuleList(const std::vector &conf) = 0; + virtual void delNodeportRule(const uint16_t &nodeportPort,const L4ProtoEnum &proto) = 0; + virtual void delNodeportRuleList(); +}; diff --git a/src/services/pcn-k8sdispatcher/src/base/NodeportRuleBase.cpp b/src/services/pcn-k8sdispatcher/src/base/NodeportRuleBase.cpp new file mode 100644 index 000000000..97be76fea --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/base/NodeportRuleBase.cpp @@ -0,0 +1,42 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + +#include "NodeportRuleBase.h" +#include "../K8sdispatcher.h" + + +NodeportRuleBase::NodeportRuleBase(K8sdispatcher &parent) + : parent_(parent) {} + +NodeportRuleBase::~NodeportRuleBase() {} + +void NodeportRuleBase::update(const NodeportRuleJsonObject &conf) { + + if (conf.externalTrafficPolicyIsSet()) { + setExternalTrafficPolicy(conf.getExternalTrafficPolicy()); + } +} + +NodeportRuleJsonObject NodeportRuleBase::toJsonObject() { + NodeportRuleJsonObject conf; + + conf.setNodeportPort(getNodeportPort()); + conf.setProto(getProto()); + conf.setExternalTrafficPolicy(getExternalTrafficPolicy()); + conf.setRuleName(getRuleName()); + + return conf; +} + +std::shared_ptr NodeportRuleBase::logger() { + return parent_.logger(); +} + diff --git a/src/services/pcn-k8sdispatcher/src/base/NodeportRuleBase.h b/src/services/pcn-k8sdispatcher/src/base/NodeportRuleBase.h new file mode 100644 index 000000000..e819bf274 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/base/NodeportRuleBase.h @@ -0,0 +1,65 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* NodeportRuleBase.h +* +* +*/ + +#pragma once + +#include "../serializer/NodeportRuleJsonObject.h" + + + + + + +#include + +using namespace polycube::service::model; + +class K8sdispatcher; + +class NodeportRuleBase { + public: + + NodeportRuleBase(K8sdispatcher &parent); + + virtual ~NodeportRuleBase(); + virtual void update(const NodeportRuleJsonObject &conf); + virtual NodeportRuleJsonObject toJsonObject(); + + /// + /// NodePort rule nodeport port number + /// + virtual uint16_t getNodeportPort() = 0; + + /// + /// NodePort rule L4 protocol + /// + virtual L4ProtoEnum getProto() = 0; + + /// + /// The external traffic policy of the Kubernetes NodePort Service + /// + virtual NodeportRuleExternalTrafficPolicyEnum getExternalTrafficPolicy() = 0; + virtual void setExternalTrafficPolicy(const NodeportRuleExternalTrafficPolicyEnum &value) = 0; + + /// + /// An optional name for the NodePort rule + /// + virtual std::string getRuleName() = 0; + + std::shared_ptr logger(); + protected: + K8sdispatcher &parent_; +}; diff --git a/src/services/pcn-k8sdispatcher/src/base/PortsBase.cpp b/src/services/pcn-k8sdispatcher/src/base/PortsBase.cpp new file mode 100644 index 000000000..d55efb130 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/base/PortsBase.cpp @@ -0,0 +1,41 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + +#include "PortsBase.h" +#include "../K8sdispatcher.h" + +PortsBase::PortsBase(polycube::service::Cube &parent, + std::shared_ptr port) + : Port(port), parent_(dynamic_cast(parent)) {} + + +PortsBase::~PortsBase() {} + +void PortsBase::update(const PortsJsonObject &conf) { + set_conf(conf.getBase()); + +} + +PortsJsonObject PortsBase::toJsonObject() { + PortsJsonObject conf; + conf.setBase(to_json()); + + conf.setName(getName()); + conf.setType(getType()); + conf.setIp(getIp()); + + return conf; +} + +std::shared_ptr PortsBase::logger() { + return parent_.logger(); +} + diff --git a/src/services/pcn-k8sdispatcher/src/base/PortsBase.h b/src/services/pcn-k8sdispatcher/src/base/PortsBase.h new file mode 100644 index 000000000..33cf3163a --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/base/PortsBase.h @@ -0,0 +1,59 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* PortsBase.h +* +* +*/ + +#pragma once + +#include "../serializer/PortsJsonObject.h" + + + + +#include "polycube/services/cube.h" +#include "polycube/services/port.h" + +#include "polycube/services/utils.h" +#include "polycube/services/fifo_map.hpp" + +#include + +using namespace polycube::service::model; + +class K8sdispatcher; +class Ports; + +class PortsBase: public polycube::service::Port { + public: + PortsBase(polycube::service::Cube &parent, + std::shared_ptr port); + + virtual ~PortsBase(); + virtual void update(const PortsJsonObject &conf); + virtual PortsJsonObject toJsonObject(); + + /// + /// Type of the K8s Dispatcher cube port (e.g. BACKEND or FRONTEND) + /// + virtual PortsTypeEnum getType() = 0; + + /// + /// IP address of the node interface (only for FRONTEND port) + /// + virtual std::string getIp() = 0; + + std::shared_ptr logger(); + protected: + K8sdispatcher &parent_; +}; diff --git a/src/services/pcn-k8sdispatcher/src/base/SessionRuleBase.cpp b/src/services/pcn-k8sdispatcher/src/base/SessionRuleBase.cpp new file mode 100644 index 000000000..2b5a8a3f4 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/base/SessionRuleBase.cpp @@ -0,0 +1,45 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + +#include "SessionRuleBase.h" +#include "../K8sdispatcher.h" + + +SessionRuleBase::SessionRuleBase(K8sdispatcher &parent) + : parent_(parent) {} + +SessionRuleBase::~SessionRuleBase() {} + +void SessionRuleBase::update(const SessionRuleJsonObject &conf) { + +} + +SessionRuleJsonObject SessionRuleBase::toJsonObject() { + SessionRuleJsonObject conf; + + conf.setDirection(getDirection()); + conf.setSrcIp(getSrcIp()); + conf.setDstIp(getDstIp()); + conf.setSrcPort(getSrcPort()); + conf.setDstPort(getDstPort()); + conf.setProto(getProto()); + conf.setNewIp(getNewIp()); + conf.setNewPort(getNewPort()); + conf.setOperation(getOperation()); + conf.setOriginatingRule(getOriginatingRule()); + + return conf; +} + +std::shared_ptr SessionRuleBase::logger() { + return parent_.logger(); +} + diff --git a/src/services/pcn-k8sdispatcher/src/base/SessionRuleBase.h b/src/services/pcn-k8sdispatcher/src/base/SessionRuleBase.h new file mode 100644 index 000000000..90e2ab09a --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/base/SessionRuleBase.h @@ -0,0 +1,94 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* SessionRuleBase.h +* +* +*/ + +#pragma once + +#include "../serializer/SessionRuleJsonObject.h" + + + + + + +#include + +using namespace polycube::service::model; + +class K8sdispatcher; + +class SessionRuleBase { + public: + + SessionRuleBase(K8sdispatcher &parent); + + virtual ~SessionRuleBase(); + virtual void update(const SessionRuleJsonObject &conf); + virtual SessionRuleJsonObject toJsonObject(); + + /// + /// Session entry direction (e.g. INGRESS or EGRESS) + /// + virtual SessionRuleDirectionEnum getDirection() = 0; + + /// + /// Session entry source IP address + /// + virtual std::string getSrcIp() = 0; + + /// + /// Session entry destination IP address + /// + virtual std::string getDstIp() = 0; + + /// + /// Session entry source L4 port number + /// + virtual uint16_t getSrcPort() = 0; + + /// + /// Session entry destination L4 port number + /// + virtual uint16_t getDstPort() = 0; + + /// + /// Session entry L4 protocol + /// + virtual L4ProtoEnum getProto() = 0; + + /// + /// Translated IP address + /// + virtual std::string getNewIp() = 0; + + /// + /// Translated L4 port number + /// + virtual uint16_t getNewPort() = 0; + + /// + /// Operation applied on the original packet + /// + virtual SessionRuleOperationEnum getOperation() = 0; + + /// + /// Rule originating the session entry + /// + virtual SessionRuleOriginatingRuleEnum getOriginatingRule() = 0; + + std::shared_ptr logger(); + protected: + K8sdispatcher &parent_; +}; diff --git a/src/services/pcn-k8sdispatcher/src/serializer/JsonObjectBase.cpp b/src/services/pcn-k8sdispatcher/src/serializer/JsonObjectBase.cpp new file mode 100644 index 000000000..2296417c0 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/JsonObjectBase.cpp @@ -0,0 +1,69 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +#include "JsonObjectBase.h" + +namespace polycube { +namespace service { +namespace model { + +JsonObjectBase::JsonObjectBase(const nlohmann::json &base) : base_(base) {} + +bool JsonObjectBase::iequals(const std::string &a, const std::string &b) { + if(a.size() != b.size()) + return false; + for (unsigned int i = 0; i < a.size(); i++){ + if(tolower(a[i]) != tolower(b[i])) + return false; + } + return true; +} + +std::string JsonObjectBase::toJson(const std::string& value) { + return value; +} + +std::string JsonObjectBase::toJson(const std::time_t& value) { + char buf[sizeof "2011-10-08T07:07:09Z"]; + strftime(buf, sizeof buf, "%FT%TZ", gmtime(&value)); + return buf; +} + +int32_t JsonObjectBase::toJson(int32_t value) { + return value; +} + +int64_t JsonObjectBase::toJson(int64_t value) { + return value; +} + +double JsonObjectBase::toJson(double value) { + return value; +} + +bool JsonObjectBase::toJson(bool value) { + return value; +} + +nlohmann::json JsonObjectBase::toJson(const JsonObjectBase &content) { + return content.toJson(); +} + +const nlohmann::json &JsonObjectBase::getBase() const { + return base_; +} + +void JsonObjectBase::setBase(const nlohmann::json &base) { + base_ = base; +} + +} +} +} diff --git a/src/services/pcn-k8sdispatcher/src/serializer/JsonObjectBase.h b/src/services/pcn-k8sdispatcher/src/serializer/JsonObjectBase.h new file mode 100644 index 000000000..bda8934b9 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/JsonObjectBase.h @@ -0,0 +1,55 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* JsonObjectBase.h +* +* This is the base class for all model classes +*/ + +#pragma once + + +#include "polycube/services/json.hpp" +#include "polycube/services/fifo_map.hpp" +#include +#include + +namespace polycube { +namespace service { +namespace model { + +class JsonObjectBase { + public: + JsonObjectBase() = default; + JsonObjectBase(const nlohmann::json &base); + virtual ~JsonObjectBase() = default; + + virtual nlohmann::json toJson() const = 0; + + static bool iequals(const std::string &a, const std::string &b); + static std::string toJson(const std::string& value); + static std::string toJson(const std::time_t& value); + static int32_t toJson(int32_t value); + static int64_t toJson(int64_t value); + static double toJson(double value); + static bool toJson(bool value); + static nlohmann::json toJson(const JsonObjectBase &content); + + const nlohmann::json &getBase() const; + void setBase(const nlohmann::json &base); + + private: + nlohmann::json base_; +}; + +} +} +} diff --git a/src/services/pcn-k8sdispatcher/src/serializer/K8sdispatcherJsonObject.cpp b/src/services/pcn-k8sdispatcher/src/serializer/K8sdispatcherJsonObject.cpp new file mode 100644 index 000000000..9ce528e74 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/K8sdispatcherJsonObject.cpp @@ -0,0 +1,239 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + + +#include "K8sdispatcherJsonObject.h" +#include + +namespace polycube { +namespace service { +namespace model { + +K8sdispatcherJsonObject::K8sdispatcherJsonObject() { + m_nameIsSet = false; + m_portsIsSet = false; + m_internalSrcIpIsSet = false; + m_nodeportRange = "30000-32767"; + m_nodeportRangeIsSet = true; + m_sessionRuleIsSet = false; + m_nodeportRuleIsSet = false; +} + +K8sdispatcherJsonObject::K8sdispatcherJsonObject(const nlohmann::json &val) : + JsonObjectBase(val) { + m_nameIsSet = false; + m_portsIsSet = false; + m_internalSrcIpIsSet = false; + m_nodeportRangeIsSet = false; + m_sessionRuleIsSet = false; + m_nodeportRuleIsSet = false; + + + if (val.count("name")) { + setName(val.at("name").get()); + } + + if (val.count("ports")) { + for (auto& item : val["ports"]) { + PortsJsonObject newItem{ item }; + m_ports.push_back(newItem); + } + + m_portsIsSet = true; + } + + if (val.count("internal-src-ip")) { + setInternalSrcIp(val.at("internal-src-ip").get()); + } + + if (val.count("nodeport-range")) { + setNodeportRange(val.at("nodeport-range").get()); + } + + if (val.count("session-rule")) { + for (auto& item : val["session-rule"]) { + SessionRuleJsonObject newItem{ item }; + m_sessionRule.push_back(newItem); + } + + m_sessionRuleIsSet = true; + } + + if (val.count("nodeport-rule")) { + for (auto& item : val["nodeport-rule"]) { + NodeportRuleJsonObject newItem{ item }; + m_nodeportRule.push_back(newItem); + } + + m_nodeportRuleIsSet = true; + } +} + +nlohmann::json K8sdispatcherJsonObject::toJson() const { + nlohmann::json val = nlohmann::json::object(); + if (!getBase().is_null()) { + val.update(getBase()); + } + + if (m_nameIsSet) { + val["name"] = m_name; + } + + { + nlohmann::json jsonArray; + for (auto& item : m_ports) { + jsonArray.push_back(JsonObjectBase::toJson(item)); + } + + if (jsonArray.size() > 0) { + val["ports"] = jsonArray; + } + } + + if (m_internalSrcIpIsSet) { + val["internal-src-ip"] = m_internalSrcIp; + } + + if (m_nodeportRangeIsSet) { + val["nodeport-range"] = m_nodeportRange; + } + + { + nlohmann::json jsonArray; + for (auto& item : m_sessionRule) { + jsonArray.push_back(JsonObjectBase::toJson(item)); + } + + if (jsonArray.size() > 0) { + val["session-rule"] = jsonArray; + } + } + + { + nlohmann::json jsonArray; + for (auto& item : m_nodeportRule) { + jsonArray.push_back(JsonObjectBase::toJson(item)); + } + + if (jsonArray.size() > 0) { + val["nodeport-rule"] = jsonArray; + } + } + + return val; +} + +std::string K8sdispatcherJsonObject::getName() const { + return m_name; +} + +void K8sdispatcherJsonObject::setName(std::string value) { + m_name = value; + m_nameIsSet = true; +} + +bool K8sdispatcherJsonObject::nameIsSet() const { + return m_nameIsSet; +} + + + +const std::vector& K8sdispatcherJsonObject::getPorts() const{ + return m_ports; +} + +void K8sdispatcherJsonObject::addPorts(PortsJsonObject value) { + m_ports.push_back(value); + m_portsIsSet = true; +} + + +bool K8sdispatcherJsonObject::portsIsSet() const { + return m_portsIsSet; +} + +void K8sdispatcherJsonObject::unsetPorts() { + m_portsIsSet = false; +} + +std::string K8sdispatcherJsonObject::getInternalSrcIp() const { + return m_internalSrcIp; +} + +void K8sdispatcherJsonObject::setInternalSrcIp(std::string value) { + m_internalSrcIp = value; + m_internalSrcIpIsSet = true; +} + +bool K8sdispatcherJsonObject::internalSrcIpIsSet() const { + return m_internalSrcIpIsSet; +} + + + +std::string K8sdispatcherJsonObject::getNodeportRange() const { + return m_nodeportRange; +} + +void K8sdispatcherJsonObject::setNodeportRange(std::string value) { + m_nodeportRange = value; + m_nodeportRangeIsSet = true; +} + +bool K8sdispatcherJsonObject::nodeportRangeIsSet() const { + return m_nodeportRangeIsSet; +} + +void K8sdispatcherJsonObject::unsetNodeportRange() { + m_nodeportRangeIsSet = false; +} + +const std::vector& K8sdispatcherJsonObject::getSessionRule() const{ + return m_sessionRule; +} + +void K8sdispatcherJsonObject::addSessionRule(SessionRuleJsonObject value) { + m_sessionRule.push_back(value); + m_sessionRuleIsSet = true; +} + + +bool K8sdispatcherJsonObject::sessionRuleIsSet() const { + return m_sessionRuleIsSet; +} + +void K8sdispatcherJsonObject::unsetSessionRule() { + m_sessionRuleIsSet = false; +} + +const std::vector& K8sdispatcherJsonObject::getNodeportRule() const{ + return m_nodeportRule; +} + +void K8sdispatcherJsonObject::addNodeportRule(NodeportRuleJsonObject value) { + m_nodeportRule.push_back(value); + m_nodeportRuleIsSet = true; +} + + +bool K8sdispatcherJsonObject::nodeportRuleIsSet() const { + return m_nodeportRuleIsSet; +} + +void K8sdispatcherJsonObject::unsetNodeportRule() { + m_nodeportRuleIsSet = false; +} + + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/serializer/K8sdispatcherJsonObject.h b/src/services/pcn-k8sdispatcher/src/serializer/K8sdispatcherJsonObject.h new file mode 100644 index 000000000..d0f80cef9 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/K8sdispatcherJsonObject.h @@ -0,0 +1,108 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* K8sdispatcherJsonObject.h +* +* +*/ + +#pragma once + + +#include "JsonObjectBase.h" + +#include "SessionRuleJsonObject.h" +#include "NodeportRuleJsonObject.h" +#include "PortsJsonObject.h" +#include +#include "polycube/services/cube.h" + +namespace polycube { +namespace service { +namespace model { + + +/// +/// +/// +class K8sdispatcherJsonObject : public JsonObjectBase { +public: + K8sdispatcherJsonObject(); + K8sdispatcherJsonObject(const nlohmann::json &json); + ~K8sdispatcherJsonObject() final = default; + nlohmann::json toJson() const final; + + + /// + /// Name of the k8sdispatcher service + /// + std::string getName() const; + void setName(std::string value); + bool nameIsSet() const; + + /// + /// Entry of the ports table + /// + const std::vector& getPorts() const; + void addPorts(PortsJsonObject value); + bool portsIsSet() const; + void unsetPorts(); + + /// + /// Internal source IP address used for natting incoming packets directed to Kubernetes Services with a CLUSTER external traffic policy + /// + std::string getInternalSrcIp() const; + void setInternalSrcIp(std::string value); + bool internalSrcIpIsSet() const; + + /// + /// Port range used for NodePort Services + /// + std::string getNodeportRange() const; + void setNodeportRange(std::string value); + bool nodeportRangeIsSet() const; + void unsetNodeportRange(); + + /// + /// Session entry related to a specific traffic direction + /// + const std::vector& getSessionRule() const; + void addSessionRule(SessionRuleJsonObject value); + bool sessionRuleIsSet() const; + void unsetSessionRule(); + + /// + /// NodePort rule associated with a Kubernetes NodePort Service + /// + const std::vector& getNodeportRule() const; + void addNodeportRule(NodeportRuleJsonObject value); + bool nodeportRuleIsSet() const; + void unsetNodeportRule(); + +private: + std::string m_name; + bool m_nameIsSet; + std::vector m_ports; + bool m_portsIsSet; + std::string m_internalSrcIp; + bool m_internalSrcIpIsSet; + std::string m_nodeportRange; + bool m_nodeportRangeIsSet; + std::vector m_sessionRule; + bool m_sessionRuleIsSet; + std::vector m_nodeportRule; + bool m_nodeportRuleIsSet; +}; + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/serializer/NodeportRuleJsonObject.cpp b/src/services/pcn-k8sdispatcher/src/serializer/NodeportRuleJsonObject.cpp new file mode 100644 index 000000000..43894f2dd --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/NodeportRuleJsonObject.cpp @@ -0,0 +1,186 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + + +#include "NodeportRuleJsonObject.h" +#include + +namespace polycube { +namespace service { +namespace model { + +NodeportRuleJsonObject::NodeportRuleJsonObject() { + m_nodeportPortIsSet = false; + m_protoIsSet = false; + m_externalTrafficPolicy = NodeportRuleExternalTrafficPolicyEnum::CLUSTER; + m_externalTrafficPolicyIsSet = true; + m_ruleNameIsSet = false; +} + +NodeportRuleJsonObject::NodeportRuleJsonObject(const nlohmann::json &val) : + JsonObjectBase(val) { + m_nodeportPortIsSet = false; + m_protoIsSet = false; + m_externalTrafficPolicyIsSet = false; + m_ruleNameIsSet = false; + + + if (val.count("nodeport-port")) { + setNodeportPort(val.at("nodeport-port").get()); + } + + if (val.count("proto")) { + setProto(string_to_L4ProtoEnum(val.at("proto").get())); + } + + if (val.count("external-traffic-policy")) { + setExternalTrafficPolicy(string_to_NodeportRuleExternalTrafficPolicyEnum(val.at("external-traffic-policy").get())); + } + + if (val.count("rule-name")) { + setRuleName(val.at("rule-name").get()); + } +} + +nlohmann::json NodeportRuleJsonObject::toJson() const { + nlohmann::json val = nlohmann::json::object(); + if (!getBase().is_null()) { + val.update(getBase()); + } + + if (m_nodeportPortIsSet) { + val["nodeport-port"] = m_nodeportPort; + } + + if (m_protoIsSet) { + val["proto"] = L4ProtoEnum_to_string(m_proto); + } + + if (m_externalTrafficPolicyIsSet) { + val["external-traffic-policy"] = NodeportRuleExternalTrafficPolicyEnum_to_string(m_externalTrafficPolicy); + } + + if (m_ruleNameIsSet) { + val["rule-name"] = m_ruleName; + } + + return val; +} + +uint16_t NodeportRuleJsonObject::getNodeportPort() const { + return m_nodeportPort; +} + +void NodeportRuleJsonObject::setNodeportPort(uint16_t value) { + m_nodeportPort = value; + m_nodeportPortIsSet = true; +} + +bool NodeportRuleJsonObject::nodeportPortIsSet() const { + return m_nodeportPortIsSet; +} + + + +L4ProtoEnum NodeportRuleJsonObject::getProto() const { + return m_proto; +} + +void NodeportRuleJsonObject::setProto(L4ProtoEnum value) { + m_proto = value; + m_protoIsSet = true; +} + +bool NodeportRuleJsonObject::protoIsSet() const { + return m_protoIsSet; +} + + + +std::string NodeportRuleJsonObject::L4ProtoEnum_to_string(const L4ProtoEnum &value){ + switch(value) { + case L4ProtoEnum::TCP: + return std::string("tcp"); + case L4ProtoEnum::UDP: + return std::string("udp"); + case L4ProtoEnum::ICMP: + return std::string("icmp"); + default: + throw std::runtime_error("Bad NodeportRule proto"); + } +} + +L4ProtoEnum NodeportRuleJsonObject::string_to_L4ProtoEnum(const std::string &str){ + if (JsonObjectBase::iequals("tcp", str)) + return L4ProtoEnum::TCP; + if (JsonObjectBase::iequals("udp", str)) + return L4ProtoEnum::UDP; + if (JsonObjectBase::iequals("icmp", str)) + return L4ProtoEnum::ICMP; + throw std::runtime_error("NodeportRule proto is invalid"); +} +NodeportRuleExternalTrafficPolicyEnum NodeportRuleJsonObject::getExternalTrafficPolicy() const { + return m_externalTrafficPolicy; +} + +void NodeportRuleJsonObject::setExternalTrafficPolicy(NodeportRuleExternalTrafficPolicyEnum value) { + m_externalTrafficPolicy = value; + m_externalTrafficPolicyIsSet = true; +} + +bool NodeportRuleJsonObject::externalTrafficPolicyIsSet() const { + return m_externalTrafficPolicyIsSet; +} + +void NodeportRuleJsonObject::unsetExternalTrafficPolicy() { + m_externalTrafficPolicyIsSet = false; +} + +std::string NodeportRuleJsonObject::NodeportRuleExternalTrafficPolicyEnum_to_string(const NodeportRuleExternalTrafficPolicyEnum &value){ + switch(value) { + case NodeportRuleExternalTrafficPolicyEnum::LOCAL: + return std::string("local"); + case NodeportRuleExternalTrafficPolicyEnum::CLUSTER: + return std::string("cluster"); + default: + throw std::runtime_error("Bad NodeportRule externalTrafficPolicy"); + } +} + +NodeportRuleExternalTrafficPolicyEnum NodeportRuleJsonObject::string_to_NodeportRuleExternalTrafficPolicyEnum(const std::string &str){ + if (JsonObjectBase::iequals("local", str)) + return NodeportRuleExternalTrafficPolicyEnum::LOCAL; + if (JsonObjectBase::iequals("cluster", str)) + return NodeportRuleExternalTrafficPolicyEnum::CLUSTER; + throw std::runtime_error("NodeportRule externalTrafficPolicy is invalid"); +} +std::string NodeportRuleJsonObject::getRuleName() const { + return m_ruleName; +} + +void NodeportRuleJsonObject::setRuleName(std::string value) { + m_ruleName = value; + m_ruleNameIsSet = true; +} + +bool NodeportRuleJsonObject::ruleNameIsSet() const { + return m_ruleNameIsSet; +} + +void NodeportRuleJsonObject::unsetRuleName() { + m_ruleNameIsSet = false; +} + + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/serializer/NodeportRuleJsonObject.h b/src/services/pcn-k8sdispatcher/src/serializer/NodeportRuleJsonObject.h new file mode 100644 index 000000000..9f3838c2d --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/NodeportRuleJsonObject.h @@ -0,0 +1,96 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* NodeportRuleJsonObject.h +* +* +*/ + +#pragma once + + +#include "JsonObjectBase.h" + + +namespace polycube { +namespace service { +namespace model { + +#ifndef L4PROTOENUM +#define L4PROTOENUM +enum class L4ProtoEnum { + TCP, UDP, ICMP +}; +#endif +enum class NodeportRuleExternalTrafficPolicyEnum { + LOCAL, CLUSTER +}; + +/// +/// +/// +class NodeportRuleJsonObject : public JsonObjectBase { +public: + NodeportRuleJsonObject(); + NodeportRuleJsonObject(const nlohmann::json &json); + ~NodeportRuleJsonObject() final = default; + nlohmann::json toJson() const final; + + + /// + /// NodePort rule nodeport port number + /// + uint16_t getNodeportPort() const; + void setNodeportPort(uint16_t value); + bool nodeportPortIsSet() const; + + /// + /// NodePort rule L4 protocol + /// + L4ProtoEnum getProto() const; + void setProto(L4ProtoEnum value); + bool protoIsSet() const; + static std::string L4ProtoEnum_to_string(const L4ProtoEnum &value); + static L4ProtoEnum string_to_L4ProtoEnum(const std::string &str); + + /// + /// The external traffic policy of the Kubernetes NodePort Service + /// + NodeportRuleExternalTrafficPolicyEnum getExternalTrafficPolicy() const; + void setExternalTrafficPolicy(NodeportRuleExternalTrafficPolicyEnum value); + bool externalTrafficPolicyIsSet() const; + void unsetExternalTrafficPolicy(); + static std::string NodeportRuleExternalTrafficPolicyEnum_to_string(const NodeportRuleExternalTrafficPolicyEnum &value); + static NodeportRuleExternalTrafficPolicyEnum string_to_NodeportRuleExternalTrafficPolicyEnum(const std::string &str); + + /// + /// An optional name for the NodePort rule + /// + std::string getRuleName() const; + void setRuleName(std::string value); + bool ruleNameIsSet() const; + void unsetRuleName(); + +private: + uint16_t m_nodeportPort; + bool m_nodeportPortIsSet; + L4ProtoEnum m_proto; + bool m_protoIsSet; + NodeportRuleExternalTrafficPolicyEnum m_externalTrafficPolicy; + bool m_externalTrafficPolicyIsSet; + std::string m_ruleName; + bool m_ruleNameIsSet; +}; + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/serializer/PortsJsonObject.cpp b/src/services/pcn-k8sdispatcher/src/serializer/PortsJsonObject.cpp new file mode 100644 index 000000000..e9bd6a543 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/PortsJsonObject.cpp @@ -0,0 +1,136 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + + +#include "PortsJsonObject.h" +#include + +namespace polycube { +namespace service { +namespace model { + +PortsJsonObject::PortsJsonObject() { + m_nameIsSet = false; + m_typeIsSet = false; + m_ipIsSet = false; +} + +PortsJsonObject::PortsJsonObject(const nlohmann::json &val) : + JsonObjectBase(val) { + m_nameIsSet = false; + m_typeIsSet = false; + m_ipIsSet = false; + + + if (val.count("name")) { + setName(val.at("name").get()); + } + + if (val.count("type")) { + setType(string_to_PortsTypeEnum(val.at("type").get())); + } + + if (val.count("ip")) { + setIp(val.at("ip").get()); + } +} + +nlohmann::json PortsJsonObject::toJson() const { + nlohmann::json val = nlohmann::json::object(); + if (!getBase().is_null()) { + val.update(getBase()); + } + + if (m_nameIsSet) { + val["name"] = m_name; + } + + if (m_typeIsSet) { + val["type"] = PortsTypeEnum_to_string(m_type); + } + + if (m_ipIsSet) { + val["ip"] = m_ip; + } + + return val; +} + +std::string PortsJsonObject::getName() const { + return m_name; +} + +void PortsJsonObject::setName(std::string value) { + m_name = value; + m_nameIsSet = true; +} + +bool PortsJsonObject::nameIsSet() const { + return m_nameIsSet; +} + + + +PortsTypeEnum PortsJsonObject::getType() const { + return m_type; +} + +void PortsJsonObject::setType(PortsTypeEnum value) { + m_type = value; + m_typeIsSet = true; +} + +bool PortsJsonObject::typeIsSet() const { + return m_typeIsSet; +} + + + +std::string PortsJsonObject::PortsTypeEnum_to_string(const PortsTypeEnum &value){ + switch(value) { + case PortsTypeEnum::BACKEND: + return std::string("backend"); + case PortsTypeEnum::FRONTEND: + return std::string("frontend"); + default: + throw std::runtime_error("Bad Ports type"); + } +} + +PortsTypeEnum PortsJsonObject::string_to_PortsTypeEnum(const std::string &str){ + if (JsonObjectBase::iequals("backend", str)) + return PortsTypeEnum::BACKEND; + if (JsonObjectBase::iequals("frontend", str)) + return PortsTypeEnum::FRONTEND; + throw std::runtime_error("Ports type is invalid"); +} +std::string PortsJsonObject::getIp() const { + return m_ip; +} + +void PortsJsonObject::setIp(std::string value) { + m_ip = value; + m_ipIsSet = true; +} + +bool PortsJsonObject::ipIsSet() const { + return m_ipIsSet; +} + +void PortsJsonObject::unsetIp() { + m_ipIsSet = false; +} + + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/serializer/PortsJsonObject.h b/src/services/pcn-k8sdispatcher/src/serializer/PortsJsonObject.h new file mode 100644 index 000000000..f15b2aab1 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/PortsJsonObject.h @@ -0,0 +1,79 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* PortsJsonObject.h +* +* +*/ + +#pragma once + + +#include "JsonObjectBase.h" + +#include + +namespace polycube { +namespace service { +namespace model { + +enum class PortsTypeEnum { + BACKEND, FRONTEND +}; + +/// +/// +/// +class PortsJsonObject : public JsonObjectBase { +public: + PortsJsonObject(); + PortsJsonObject(const nlohmann::json &json); + ~PortsJsonObject() final = default; + nlohmann::json toJson() const final; + + + /// + /// Port Name + /// + std::string getName() const; + void setName(std::string value); + bool nameIsSet() const; + + /// + /// Type of the K8s Dispatcher cube port (e.g. BACKEND or FRONTEND) + /// + PortsTypeEnum getType() const; + void setType(PortsTypeEnum value); + bool typeIsSet() const; + static std::string PortsTypeEnum_to_string(const PortsTypeEnum &value); + static PortsTypeEnum string_to_PortsTypeEnum(const std::string &str); + + /// + /// IP address of the node interface (only for FRONTEND port) + /// + std::string getIp() const; + void setIp(std::string value); + bool ipIsSet() const; + void unsetIp(); + +private: + std::string m_name; + bool m_nameIsSet; + PortsTypeEnum m_type; + bool m_typeIsSet; + std::string m_ip; + bool m_ipIsSet; +}; + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/serializer/SessionRuleJsonObject.cpp b/src/services/pcn-k8sdispatcher/src/serializer/SessionRuleJsonObject.cpp new file mode 100644 index 000000000..225afbc0c --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/SessionRuleJsonObject.cpp @@ -0,0 +1,375 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + + +#include "SessionRuleJsonObject.h" +#include + +namespace polycube { +namespace service { +namespace model { + +SessionRuleJsonObject::SessionRuleJsonObject() { + m_directionIsSet = false; + m_srcIpIsSet = false; + m_dstIpIsSet = false; + m_srcPortIsSet = false; + m_dstPortIsSet = false; + m_protoIsSet = false; + m_newIpIsSet = false; + m_newPortIsSet = false; + m_operationIsSet = false; + m_originatingRuleIsSet = false; +} + +SessionRuleJsonObject::SessionRuleJsonObject(const nlohmann::json &val) : + JsonObjectBase(val) { + m_directionIsSet = false; + m_srcIpIsSet = false; + m_dstIpIsSet = false; + m_srcPortIsSet = false; + m_dstPortIsSet = false; + m_protoIsSet = false; + m_newIpIsSet = false; + m_newPortIsSet = false; + m_operationIsSet = false; + m_originatingRuleIsSet = false; + + + if (val.count("direction")) { + setDirection(string_to_SessionRuleDirectionEnum(val.at("direction").get())); + } + + if (val.count("src-ip")) { + setSrcIp(val.at("src-ip").get()); + } + + if (val.count("dst-ip")) { + setDstIp(val.at("dst-ip").get()); + } + + if (val.count("src-port")) { + setSrcPort(val.at("src-port").get()); + } + + if (val.count("dst-port")) { + setDstPort(val.at("dst-port").get()); + } + + if (val.count("proto")) { + setProto(string_to_L4ProtoEnum(val.at("proto").get())); + } + + if (val.count("new-ip")) { + setNewIp(val.at("new-ip").get()); + } + + if (val.count("new-port")) { + setNewPort(val.at("new-port").get()); + } + + if (val.count("operation")) { + setOperation(string_to_SessionRuleOperationEnum(val.at("operation").get())); + } + + if (val.count("originating-rule")) { + setOriginatingRule(string_to_SessionRuleOriginatingRuleEnum(val.at("originating-rule").get())); + } +} + +nlohmann::json SessionRuleJsonObject::toJson() const { + nlohmann::json val = nlohmann::json::object(); + if (!getBase().is_null()) { + val.update(getBase()); + } + + if (m_directionIsSet) { + val["direction"] = SessionRuleDirectionEnum_to_string(m_direction); + } + + if (m_srcIpIsSet) { + val["src-ip"] = m_srcIp; + } + + if (m_dstIpIsSet) { + val["dst-ip"] = m_dstIp; + } + + if (m_srcPortIsSet) { + val["src-port"] = m_srcPort; + } + + if (m_dstPortIsSet) { + val["dst-port"] = m_dstPort; + } + + if (m_protoIsSet) { + val["proto"] = L4ProtoEnum_to_string(m_proto); + } + + if (m_newIpIsSet) { + val["new-ip"] = m_newIp; + } + + if (m_newPortIsSet) { + val["new-port"] = m_newPort; + } + + if (m_operationIsSet) { + val["operation"] = SessionRuleOperationEnum_to_string(m_operation); + } + + if (m_originatingRuleIsSet) { + val["originating-rule"] = SessionRuleOriginatingRuleEnum_to_string(m_originatingRule); + } + + return val; +} + +SessionRuleDirectionEnum SessionRuleJsonObject::getDirection() const { + return m_direction; +} + +void SessionRuleJsonObject::setDirection(SessionRuleDirectionEnum value) { + m_direction = value; + m_directionIsSet = true; +} + +bool SessionRuleJsonObject::directionIsSet() const { + return m_directionIsSet; +} + + + +std::string SessionRuleJsonObject::SessionRuleDirectionEnum_to_string(const SessionRuleDirectionEnum &value){ + switch(value) { + case SessionRuleDirectionEnum::INGRESS: + return std::string("ingress"); + case SessionRuleDirectionEnum::EGRESS: + return std::string("egress"); + default: + throw std::runtime_error("Bad SessionRule direction"); + } +} + +SessionRuleDirectionEnum SessionRuleJsonObject::string_to_SessionRuleDirectionEnum(const std::string &str){ + if (JsonObjectBase::iequals("ingress", str)) + return SessionRuleDirectionEnum::INGRESS; + if (JsonObjectBase::iequals("egress", str)) + return SessionRuleDirectionEnum::EGRESS; + throw std::runtime_error("SessionRule direction is invalid"); +} +std::string SessionRuleJsonObject::getSrcIp() const { + return m_srcIp; +} + +void SessionRuleJsonObject::setSrcIp(std::string value) { + m_srcIp = value; + m_srcIpIsSet = true; +} + +bool SessionRuleJsonObject::srcIpIsSet() const { + return m_srcIpIsSet; +} + + + +std::string SessionRuleJsonObject::getDstIp() const { + return m_dstIp; +} + +void SessionRuleJsonObject::setDstIp(std::string value) { + m_dstIp = value; + m_dstIpIsSet = true; +} + +bool SessionRuleJsonObject::dstIpIsSet() const { + return m_dstIpIsSet; +} + + + +uint16_t SessionRuleJsonObject::getSrcPort() const { + return m_srcPort; +} + +void SessionRuleJsonObject::setSrcPort(uint16_t value) { + m_srcPort = value; + m_srcPortIsSet = true; +} + +bool SessionRuleJsonObject::srcPortIsSet() const { + return m_srcPortIsSet; +} + + + +uint16_t SessionRuleJsonObject::getDstPort() const { + return m_dstPort; +} + +void SessionRuleJsonObject::setDstPort(uint16_t value) { + m_dstPort = value; + m_dstPortIsSet = true; +} + +bool SessionRuleJsonObject::dstPortIsSet() const { + return m_dstPortIsSet; +} + + + +L4ProtoEnum SessionRuleJsonObject::getProto() const { + return m_proto; +} + +void SessionRuleJsonObject::setProto(L4ProtoEnum value) { + m_proto = value; + m_protoIsSet = true; +} + +bool SessionRuleJsonObject::protoIsSet() const { + return m_protoIsSet; +} + + + +std::string SessionRuleJsonObject::L4ProtoEnum_to_string(const L4ProtoEnum &value){ + switch(value) { + case L4ProtoEnum::TCP: + return std::string("tcp"); + case L4ProtoEnum::UDP: + return std::string("udp"); + case L4ProtoEnum::ICMP: + return std::string("icmp"); + default: + throw std::runtime_error("Bad SessionRule proto"); + } +} + +L4ProtoEnum SessionRuleJsonObject::string_to_L4ProtoEnum(const std::string &str){ + if (JsonObjectBase::iequals("tcp", str)) + return L4ProtoEnum::TCP; + if (JsonObjectBase::iequals("udp", str)) + return L4ProtoEnum::UDP; + if (JsonObjectBase::iequals("icmp", str)) + return L4ProtoEnum::ICMP; + throw std::runtime_error("SessionRule proto is invalid"); +} +std::string SessionRuleJsonObject::getNewIp() const { + return m_newIp; +} + +void SessionRuleJsonObject::setNewIp(std::string value) { + m_newIp = value; + m_newIpIsSet = true; +} + +bool SessionRuleJsonObject::newIpIsSet() const { + return m_newIpIsSet; +} + +void SessionRuleJsonObject::unsetNewIp() { + m_newIpIsSet = false; +} + +uint16_t SessionRuleJsonObject::getNewPort() const { + return m_newPort; +} + +void SessionRuleJsonObject::setNewPort(uint16_t value) { + m_newPort = value; + m_newPortIsSet = true; +} + +bool SessionRuleJsonObject::newPortIsSet() const { + return m_newPortIsSet; +} + +void SessionRuleJsonObject::unsetNewPort() { + m_newPortIsSet = false; +} + +SessionRuleOperationEnum SessionRuleJsonObject::getOperation() const { + return m_operation; +} + +void SessionRuleJsonObject::setOperation(SessionRuleOperationEnum value) { + m_operation = value; + m_operationIsSet = true; +} + +bool SessionRuleJsonObject::operationIsSet() const { + return m_operationIsSet; +} + +void SessionRuleJsonObject::unsetOperation() { + m_operationIsSet = false; +} + +std::string SessionRuleJsonObject::SessionRuleOperationEnum_to_string(const SessionRuleOperationEnum &value){ + switch(value) { + case SessionRuleOperationEnum::XLATE_SRC: + return std::string("xlate_src"); + case SessionRuleOperationEnum::XLATE_DST: + return std::string("xlate_dst"); + default: + throw std::runtime_error("Bad SessionRule operation"); + } +} + +SessionRuleOperationEnum SessionRuleJsonObject::string_to_SessionRuleOperationEnum(const std::string &str){ + if (JsonObjectBase::iequals("xlate_src", str)) + return SessionRuleOperationEnum::XLATE_SRC; + if (JsonObjectBase::iequals("xlate_dst", str)) + return SessionRuleOperationEnum::XLATE_DST; + throw std::runtime_error("SessionRule operation is invalid"); +} +SessionRuleOriginatingRuleEnum SessionRuleJsonObject::getOriginatingRule() const { + return m_originatingRule; +} + +void SessionRuleJsonObject::setOriginatingRule(SessionRuleOriginatingRuleEnum value) { + m_originatingRule = value; + m_originatingRuleIsSet = true; +} + +bool SessionRuleJsonObject::originatingRuleIsSet() const { + return m_originatingRuleIsSet; +} + +void SessionRuleJsonObject::unsetOriginatingRule() { + m_originatingRuleIsSet = false; +} + +std::string SessionRuleJsonObject::SessionRuleOriginatingRuleEnum_to_string(const SessionRuleOriginatingRuleEnum &value){ + switch(value) { + case SessionRuleOriginatingRuleEnum::POD_TO_EXT: + return std::string("pod_to_ext"); + case SessionRuleOriginatingRuleEnum::NODEPORT_CLUSTER: + return std::string("nodeport_cluster"); + default: + throw std::runtime_error("Bad SessionRule originatingRule"); + } +} + +SessionRuleOriginatingRuleEnum SessionRuleJsonObject::string_to_SessionRuleOriginatingRuleEnum(const std::string &str){ + if (JsonObjectBase::iequals("pod_to_ext", str)) + return SessionRuleOriginatingRuleEnum::POD_TO_EXT; + if (JsonObjectBase::iequals("nodeport_cluster", str)) + return SessionRuleOriginatingRuleEnum::NODEPORT_CLUSTER; + throw std::runtime_error("SessionRule originatingRule is invalid"); +} + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/serializer/SessionRuleJsonObject.h b/src/services/pcn-k8sdispatcher/src/serializer/SessionRuleJsonObject.h new file mode 100644 index 000000000..3452f2591 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/SessionRuleJsonObject.h @@ -0,0 +1,162 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* SessionRuleJsonObject.h +* +* +*/ + +#pragma once + + +#include "JsonObjectBase.h" + + +namespace polycube { +namespace service { +namespace model { + +enum class SessionRuleDirectionEnum { + INGRESS, EGRESS +}; +#ifndef L4PROTOENUM +#define L4PROTOENUM +enum class L4ProtoEnum { + TCP, UDP, ICMP +}; +#endif +enum class SessionRuleOperationEnum { + XLATE_SRC, XLATE_DST +}; +enum class SessionRuleOriginatingRuleEnum { + POD_TO_EXT, NODEPORT_CLUSTER +}; + +/// +/// +/// +class SessionRuleJsonObject : public JsonObjectBase { +public: + SessionRuleJsonObject(); + SessionRuleJsonObject(const nlohmann::json &json); + ~SessionRuleJsonObject() final = default; + nlohmann::json toJson() const final; + + + /// + /// Session entry direction (e.g. INGRESS or EGRESS) + /// + SessionRuleDirectionEnum getDirection() const; + void setDirection(SessionRuleDirectionEnum value); + bool directionIsSet() const; + static std::string SessionRuleDirectionEnum_to_string(const SessionRuleDirectionEnum &value); + static SessionRuleDirectionEnum string_to_SessionRuleDirectionEnum(const std::string &str); + + /// + /// Session entry source IP address + /// + std::string getSrcIp() const; + void setSrcIp(std::string value); + bool srcIpIsSet() const; + + /// + /// Session entry destination IP address + /// + std::string getDstIp() const; + void setDstIp(std::string value); + bool dstIpIsSet() const; + + /// + /// Session entry source L4 port number + /// + uint16_t getSrcPort() const; + void setSrcPort(uint16_t value); + bool srcPortIsSet() const; + + /// + /// Session entry destination L4 port number + /// + uint16_t getDstPort() const; + void setDstPort(uint16_t value); + bool dstPortIsSet() const; + + /// + /// Session entry L4 protocol + /// + L4ProtoEnum getProto() const; + void setProto(L4ProtoEnum value); + bool protoIsSet() const; + static std::string L4ProtoEnum_to_string(const L4ProtoEnum &value); + static L4ProtoEnum string_to_L4ProtoEnum(const std::string &str); + + /// + /// Translated IP address + /// + std::string getNewIp() const; + void setNewIp(std::string value); + bool newIpIsSet() const; + void unsetNewIp(); + + /// + /// Translated L4 port number + /// + uint16_t getNewPort() const; + void setNewPort(uint16_t value); + bool newPortIsSet() const; + void unsetNewPort(); + + /// + /// Operation applied on the original packet + /// + SessionRuleOperationEnum getOperation() const; + void setOperation(SessionRuleOperationEnum value); + bool operationIsSet() const; + void unsetOperation(); + static std::string SessionRuleOperationEnum_to_string(const SessionRuleOperationEnum &value); + static SessionRuleOperationEnum string_to_SessionRuleOperationEnum(const std::string &str); + + /// + /// Rule originating the session entry + /// + SessionRuleOriginatingRuleEnum getOriginatingRule() const; + void setOriginatingRule(SessionRuleOriginatingRuleEnum value); + bool originatingRuleIsSet() const; + void unsetOriginatingRule(); + static std::string SessionRuleOriginatingRuleEnum_to_string(const SessionRuleOriginatingRuleEnum &value); + static SessionRuleOriginatingRuleEnum string_to_SessionRuleOriginatingRuleEnum(const std::string &str); + +private: + SessionRuleDirectionEnum m_direction; + bool m_directionIsSet; + std::string m_srcIp; + bool m_srcIpIsSet; + std::string m_dstIp; + bool m_dstIpIsSet; + uint16_t m_srcPort; + bool m_srcPortIsSet; + uint16_t m_dstPort; + bool m_dstPortIsSet; + L4ProtoEnum m_proto; + bool m_protoIsSet; + std::string m_newIp; + bool m_newIpIsSet; + uint16_t m_newPort; + bool m_newPortIsSet; + SessionRuleOperationEnum m_operation; + bool m_operationIsSet; + SessionRuleOriginatingRuleEnum m_originatingRule; + bool m_originatingRuleIsSet; +}; + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/test/test.sh b/src/services/pcn-k8sdispatcher/test/test.sh new file mode 100755 index 000000000..be221f807 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/test/test.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +function cleanup { + set +e + polycubectl k8sdispatcher del k0 +} +trap cleanup EXIT + +set -x +set -e + +polycubectl k8sdispatcher add k0 internal-src-ip=3.3.1.1 loglevel=TRACE +polycubectl k0 show +polycubectl k0 ports show +polycubectl k0 nodeport-rule show +polycubectl k0 session-rule show +polycubectl k0 ports add to_frontend type=FRONTEND ip=10.10.10.10 +polycubectl k0 show ports to_frontend +polycubectl k0 ports add to_backend type=BACKEND +polycubectl k0 show ports to_backend +polycubectl k0 nodeport-rule add 32000 TCP external-traffic-policy=LOCAL rule-name=my-rule +polycubectl k0 show nodeport-rule 32000 TCP +polycubectl k0 set nodeport-range=32000-32500 +polycubectl k0 show nodeport-range