From 33b81da54acf1236cddf0716005c340f3402ff6d Mon Sep 17 00:00:00 2001 From: Mohsin Kazmi Date: Mon, 19 Nov 2018 00:12:11 +0100 Subject: [PATCH] vom: Add support for redirect contracts in gbp Change-Id: I18543785166811ddbd628d19065d3dfad3f948e9 Signed-off-by: Mohsin Kazmi --- extras/vom/vom/CMakeLists.txt | 2 + extras/vom/vom/gbp_contract.cpp | 48 +++++- extras/vom/vom/gbp_contract.hpp | 16 ++ extras/vom/vom/gbp_contract_cmds.cpp | 50 ++++++- extras/vom/vom/gbp_contract_cmds.hpp | 8 +- extras/vom/vom/gbp_rule.cpp | 221 +++++++++++++++++++++++++++ extras/vom/vom/gbp_rule.hpp | 281 +++++++++++++++++++++++++++++++++++ src/plugins/gbp/gbp.api | 1 + src/plugins/gbp/gbp_api.c | 3 + src/plugins/gbp/gbp_contract.h | 3 +- 10 files changed, 619 insertions(+), 14 deletions(-) create mode 100644 extras/vom/vom/gbp_rule.cpp create mode 100644 extras/vom/vom/gbp_rule.hpp diff --git a/extras/vom/vom/CMakeLists.txt b/extras/vom/vom/CMakeLists.txt index 82c221130f6..7413df8d834 100644 --- a/extras/vom/vom/CMakeLists.txt +++ b/extras/vom/vom/CMakeLists.txt @@ -80,6 +80,7 @@ if(GBP_FILE) gbp_recirc.cpp gbp_route_domain_cmds.cpp gbp_route_domain.cpp + gbp_rule.cpp gbp_subnet_cmds.cpp gbp_subnet.cpp gbp_vxlan.cpp @@ -197,6 +198,7 @@ if(GBP_FILE) gbp_endpoint_group.hpp gbp_recirc.hpp gbp_route_domain.hpp + gbp_rule.hpp gbp_subnet.hpp gbp_vxlan.hpp ) diff --git a/extras/vom/vom/gbp_contract.cpp b/extras/vom/vom/gbp_contract.cpp index 8b27269249c..87b5ed8ff0a 100644 --- a/extras/vom/vom/gbp_contract.cpp +++ b/extras/vom/vom/gbp_contract.cpp @@ -14,6 +14,7 @@ */ #include "vom/gbp_contract.hpp" +#include "vom/api_types.hpp" #include "vom/gbp_contract_cmds.hpp" #include "vom/singular_db_funcs.hpp" @@ -76,7 +77,7 @@ gbp_contract::replay() { if (m_hw) { HW::enqueue(new gbp_contract_cmds::create_cmd( - m_hw, m_src_epg_id, m_dst_epg_id, m_acl->handle())); + m_hw, m_src_epg_id, m_dst_epg_id, m_acl->handle(), m_gbp_rules)); } } @@ -85,20 +86,34 @@ gbp_contract::to_string() const { std::ostringstream s; s << "gbp-contract:[{" << m_src_epg_id << ", " << m_dst_epg_id << "}, " - << m_acl->to_string() << "]"; + << m_acl->to_string(); + if (m_gbp_rules.size()) { + auto it = m_gbp_rules.cbegin(); + while (it != m_gbp_rules.cend()) { + s << it->to_string(); + ++it; + } + } + s << "]"; return (s.str()); } +void +gbp_contract::set_gbp_rules(const gbp_contract::gbp_rules_t& gbp_rules) +{ + m_gbp_rules = gbp_rules; +} + void gbp_contract::update(const gbp_contract& r) { /* - * create the table if it is not yet created - */ + * create the table if it is not yet created + */ if (rc_t::OK != m_hw.rc()) { HW::enqueue(new gbp_contract_cmds::create_cmd( - m_hw, m_src_epg_id, m_dst_epg_id, m_acl->handle())); + m_hw, m_src_epg_id, m_dst_epg_id, m_acl->handle(), m_gbp_rules)); } } @@ -157,7 +172,28 @@ gbp_contract::event_handler::handle_populate(const client_db::key_t& key) gbp_contract gbpc(payload.contract.src_epg, payload.contract.dst_epg, *acl); OM::commit(key, gbpc); - + if (payload.contract.n_rules) { + gbp_contract::gbp_rules_t rules; + for (u8 i = 0; i < payload.contract.n_rules; i++) { + const gbp_rule::action_t action = + gbp_rule::action_t::from_int(payload.contract.rules[i].action); + const gbp_rule::hash_mode_t hm = gbp_rule::hash_mode_t::from_int( + payload.contract.rules[i].nh_set.hash_mode); + gbp_rule::next_hops_t nhs; + for (u8 j = 0; j < payload.contract.rules[i].nh_set.n_nhs; j++) { + gbp_rule::next_hop_t nh( + from_api(payload.contract.rules[i].nh_set.nhs[j].ip), + from_api(payload.contract.rules[i].nh_set.nhs[j].mac), + payload.contract.rules[i].nh_set.nhs[j].bd_id, + payload.contract.rules[i].nh_set.nhs[j].rd_id); + nhs.insert(nh); + } + gbp_rule::next_hop_set_t next_hop_set(hm, nhs); + gbp_rule gr(i, next_hop_set, action); + rules.insert(gr); + } + gbpc.set_gbp_rules(rules); + } VOM_LOG(log_level_t::DEBUG) << "read: " << gbpc.to_string(); } } diff --git a/extras/vom/vom/gbp_contract.hpp b/extras/vom/vom/gbp_contract.hpp index 7a0696de7b3..53f8f3659ec 100644 --- a/extras/vom/vom/gbp_contract.hpp +++ b/extras/vom/vom/gbp_contract.hpp @@ -18,6 +18,7 @@ #include "vom/acl_list.hpp" #include "vom/gbp_endpoint.hpp" +#include "vom/gbp_rule.hpp" #include "vom/interface.hpp" #include "vom/singular_db.hpp" #include "vom/types.hpp" @@ -30,6 +31,11 @@ namespace VOM { class gbp_contract : public object_base { public: + /** + * set of gbp rules + */ + typedef std::set gbp_rules_t; + /** * The key for a contract is the pari of EPG-IDs */ @@ -87,6 +93,11 @@ public: */ std::string to_string() const; + /** + * Set gbp_rules in case of Redirect Contract + */ + void set_gbp_rules(const gbp_rules_t& gbp_rules); + private: /** * Class definition for listeners to OM events @@ -168,6 +179,11 @@ private: */ std::shared_ptr m_acl; + /** + * The gbp rules applied to traffic between the gourps + */ + gbp_rules_t m_gbp_rules; + /** * A map of all bridge_domains */ diff --git a/extras/vom/vom/gbp_contract_cmds.cpp b/extras/vom/vom/gbp_contract_cmds.cpp index f99092451fe..db49f9751f1 100644 --- a/extras/vom/vom/gbp_contract_cmds.cpp +++ b/extras/vom/vom/gbp_contract_cmds.cpp @@ -14,6 +14,7 @@ */ #include "vom/gbp_contract_cmds.hpp" +#include "vom/api_types.hpp" namespace VOM { namespace gbp_contract_cmds { @@ -21,11 +22,13 @@ namespace gbp_contract_cmds { create_cmd::create_cmd(HW::item& item, epg_id_t src_epg_id, epg_id_t dst_epg_id, - const handle_t& acl) + const handle_t& acl, + const gbp_contract::gbp_rules_t& gbp_rules) : rpc_cmd(item) , m_src_epg_id(src_epg_id) , m_dst_epg_id(dst_epg_id) , m_acl(acl) + , m_gbp_rules(gbp_rules) { } @@ -33,20 +36,59 @@ bool create_cmd::operator==(const create_cmd& other) const { return ((m_acl == other.m_acl) && (m_src_epg_id == other.m_src_epg_id) && - (m_dst_epg_id == other.m_dst_epg_id)); + (m_dst_epg_id == other.m_dst_epg_id) && + (m_gbp_rules == other.m_gbp_rules)); } rc_t create_cmd::issue(connection& con) { - msg_t req(con.ctx(), 1, std::ref(*this)); + u8 size = m_gbp_rules.empty() ? 1 : m_gbp_rules.size(); + msg_t req(con.ctx(), size, std::ref(*this)); auto& payload = req.get_request().get_payload(); payload.is_add = 1; payload.contract.acl_index = m_acl.value(); payload.contract.src_epg = m_src_epg_id; payload.contract.dst_epg = m_dst_epg_id; - + if (size > 1) { + u32 ii = 0; + auto it = m_gbp_rules.cbegin(); + payload.contract.n_rules = m_gbp_rules.size(); + while (it != m_gbp_rules.cend()) { + if (it->action() == gbp_rule::action_t::REDIRECT) + payload.contract.rules[ii].action = GBP_API_RULE_REDIRECT; + else if (it->action() == gbp_rule::action_t::PERMIT) + payload.contract.rules[ii].action = GBP_API_RULE_PERMIT; + else + payload.contract.rules[ii].action = GBP_API_RULE_DENY; + + if (it->nhs().getHashMode() == gbp_rule::hash_mode_t::SYMMETRIC) + payload.contract.rules[ii].nh_set.hash_mode = + GBP_API_HASH_MODE_SYMMETRIC; + else if (it->nhs().getHashMode() == gbp_rule::hash_mode_t::SRC_IP) + payload.contract.rules[ii].nh_set.hash_mode = GBP_API_HASH_MODE_SRC_IP; + else + payload.contract.rules[ii].nh_set.hash_mode = GBP_API_HASH_MODE_DST_IP; + + const gbp_rule::next_hops_t& next_hops = it->nhs().getNextHops(); + u8 jj = 0, nh_size = (next_hops.size() > 8) ? 8 : next_hops.size(); + auto nh_it = next_hops.cbegin(); + + payload.contract.rules[ii].nh_set.n_nhs = nh_size; + while (jj < nh_size) { + payload.contract.rules[ii].nh_set.nhs[jj].ip = to_api(nh_it->getIp()); + payload.contract.rules[ii].nh_set.nhs[jj].mac = to_api(nh_it->getMac()); + payload.contract.rules[ii].nh_set.nhs[jj].bd_id = nh_it->getBdId(); + payload.contract.rules[ii].nh_set.nhs[jj].rd_id = nh_it->getRdId(); + ++nh_it; + ++jj; + } + + ++it; + ++ii; + } + } VAPI_CALL(req.execute()); return (wait()); diff --git a/extras/vom/vom/gbp_contract_cmds.hpp b/extras/vom/vom/gbp_contract_cmds.hpp index 7e4447663fd..4f921f6da94 100644 --- a/extras/vom/vom/gbp_contract_cmds.hpp +++ b/extras/vom/vom/gbp_contract_cmds.hpp @@ -25,8 +25,8 @@ namespace VOM { namespace gbp_contract_cmds { /** -* A command class that creates or updates the GBP contract -*/ + * A command class that creates or updates the GBP contract + */ class create_cmd : public rpc_cmd, vapi::Gbp_contract_add_del> { public: @@ -36,7 +36,8 @@ public: create_cmd(HW::item& item, epg_id_t src_epg_id, epg_id_t dst_epg_id, - const handle_t& acl); + const handle_t& acl, + const gbp_contract::gbp_rules_t& gbp_rules); /** * Issue the command to VPP/HW @@ -57,6 +58,7 @@ private: const epg_id_t m_src_epg_id; const epg_id_t m_dst_epg_id; const handle_t m_acl; + const gbp_contract::gbp_rules_t& m_gbp_rules; }; /** diff --git a/extras/vom/vom/gbp_rule.cpp b/extras/vom/vom/gbp_rule.cpp new file mode 100644 index 00000000000..7aa799029ff --- /dev/null +++ b/extras/vom/vom/gbp_rule.cpp @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2018 Cisco and/or its affiliates. + * 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 "vom/gbp_rule.hpp" + +namespace VOM { +gbp_rule::next_hop_t::next_hop_t(const boost::asio::ip::address& ip, + const mac_address_t& mac, + uint32_t bd_id, + uint32_t rd_id) + : m_ip(ip) + , m_mac(mac) + , m_bd_id(bd_id) + , m_rd_id(rd_id) +{ +} + +std::string +gbp_rule::next_hop_t::to_string() const +{ + std::ostringstream s; + + s << "[" + << "ip:" << m_ip << " mac:" << m_mac.to_string() << " bd:" << m_bd_id + << " rd:" << m_rd_id << "]"; + + return (s.str()); +} + +bool +gbp_rule::next_hop_t::operator<(const gbp_rule::next_hop_t& nh) const +{ + return (nh.m_ip < m_ip); +} + +bool +gbp_rule::next_hop_t::operator==(const gbp_rule::next_hop_t& nh) const +{ + return ((m_ip == nh.m_ip) && (m_mac == nh.m_mac) && (m_bd_id == nh.m_bd_id) && + (m_rd_id == nh.m_rd_id)); +} + +const boost::asio::ip::address& +gbp_rule::next_hop_t::getIp() const +{ + return m_ip; +} + +const mac_address_t& +gbp_rule::next_hop_t::getMac() const +{ + return m_mac; +} + +const uint32_t +gbp_rule::next_hop_t::getBdId() const +{ + return m_bd_id; +} + +const uint32_t +gbp_rule::next_hop_t::getRdId() const +{ + return m_rd_id; +} + +const gbp_rule::hash_mode_t gbp_rule::hash_mode_t::SRC_IP(1, "src-ip"); +const gbp_rule::hash_mode_t gbp_rule::hash_mode_t::DST_IP(0, "dst-ip"); +const gbp_rule::hash_mode_t gbp_rule::hash_mode_t::SYMMETRIC(2, "symmetric"); + +gbp_rule::hash_mode_t::hash_mode_t(int v, const std::string s) + : enum_base(v, s) +{ +} + +const gbp_rule::hash_mode_t& +gbp_rule::hash_mode_t::from_int(vapi_enum_gbp_hash_mode i) +{ + if (i == GBP_API_HASH_MODE_SYMMETRIC) + return gbp_rule::hash_mode_t::SYMMETRIC; + else if (i == GBP_API_HASH_MODE_SRC_IP) + return gbp_rule::hash_mode_t::SRC_IP; + + return gbp_rule::hash_mode_t::DST_IP; +} + +gbp_rule::next_hop_set_t::next_hop_set_t(const gbp_rule::hash_mode_t& hm, + gbp_rule::next_hops_t& nhs) + : m_hm(hm) + , m_nhs(nhs) +{ +} + +std::string +gbp_rule::next_hop_set_t::to_string() const +{ + std::ostringstream s; + + s << "hash-mode:" << m_hm.to_string() << " next-hops:["; + auto it = m_nhs.cbegin(); + while (it != m_nhs.cend()) { + s << " " << it->to_string(); + ++it; + } + s << " ] next-hop-size:" << m_nhs.size(); + + return (s.str()); +} + +bool +gbp_rule::next_hop_set_t::operator==(const next_hop_set_t& nhs) const +{ + return ((m_hm == nhs.m_hm) && (m_nhs == nhs.m_nhs)); +} + +const gbp_rule::hash_mode_t& +gbp_rule::next_hop_set_t::getHashMode() const +{ + return m_hm; +} + +const gbp_rule::next_hops_t& +gbp_rule::next_hop_set_t::getNextHops() const +{ + return m_nhs; +} + +const gbp_rule::action_t gbp_rule::action_t::REDIRECT(2, "redirect"); +const gbp_rule::action_t gbp_rule::action_t::PERMIT(1, "permit"); +const gbp_rule::action_t gbp_rule::action_t::DENY(0, "deny"); + +gbp_rule::action_t::action_t(int v, const std::string s) + : enum_base(v, s) +{ +} + +const gbp_rule::action_t& +gbp_rule::action_t::from_int(vapi_enum_gbp_rule_action i) +{ + if (i == GBP_API_RULE_REDIRECT) + return gbp_rule::action_t::REDIRECT; + else if (i == GBP_API_RULE_PERMIT) + return gbp_rule::action_t::PERMIT; + + return gbp_rule::action_t::DENY; +} + +gbp_rule::gbp_rule(uint32_t priority, + const gbp_rule::next_hop_set_t& nhs, + const gbp_rule::action_t& a) + : m_priority(priority) + , m_nhs(nhs) + , m_action(a) +{ +} + +bool +gbp_rule::operator<(const gbp_rule& other) const +{ + return (other.m_priority < m_priority); +} + +bool +gbp_rule::operator==(const gbp_rule& rule) const +{ + return ((m_action == rule.m_action) && (m_nhs == rule.m_nhs) && + (m_priority == rule.m_priority)); +} + +std::string +gbp_rule::to_string() const +{ + std::ostringstream s; + + s << "gbp-rule:[" + << "priority:" << m_priority << " action:" << m_action.to_string() + << " next-hop-set:[" << m_nhs.to_string() << "]]"; + + return (s.str()); +} + +uint32_t +gbp_rule::priority() const +{ + return m_priority; +} + +const gbp_rule::action_t& +gbp_rule::action() const +{ + return m_action; +} + +const gbp_rule::next_hop_set_t& +gbp_rule::nhs() const +{ + return m_nhs; +} +}; // namespace VOM + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "mozilla") + * End: + */ diff --git a/extras/vom/vom/gbp_rule.hpp b/extras/vom/vom/gbp_rule.hpp new file mode 100644 index 00000000000..bda040939fb --- /dev/null +++ b/extras/vom/vom/gbp_rule.hpp @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2018 Cisco and/or its affiliates. + * 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 __VOM_GBP_RULE_H__ +#define __VOM_GBP_RULE_H__ + +#include + +#include "vom/types.hpp" +#include +namespace VOM { +class gbp_rule +{ +public: + /** + * Representation of next hop + */ + struct next_hop_t + { + /** + * Constructor for next_hop_t + */ + next_hop_t(const boost::asio::ip::address& ip, + const mac_address_t& mac, + uint32_t bd_id, + uint32_t rd_id); + + /** + * default destructor + */ + ~next_hop_t() = default; + + /** + * convert to string + */ + std::string to_string() const; + + /** + * less-than operator + */ + bool operator<(const next_hop_t& nh) const; + + /** + * comparison operator (for testing) + */ + bool operator==(const next_hop_t& nh) const; + + /** + * get the IP address + */ + const boost::asio::ip::address& getIp(void) const; + + /** + * get the mac address + */ + const mac_address_t& getMac(void) const; + + /** + * get the bridge domain Id + */ + const uint32_t getBdId(void) const; + + /** + * get the route domain Id + */ + const uint32_t getRdId(void) const; + + private: + /** + * IP address for next hop + */ + const boost::asio::ip::address m_ip; + + /** + * mac address for interface lookup + */ + const mac_address_t m_mac; + + /** + * bridge domain in which redirected endpoints exist + */ + const uint32_t m_bd_id; + + /** + * route domain in which redirected endpoints exist + */ + const uint32_t m_rd_id; + }; + + /** + * hash mode enum + */ + struct hash_mode_t : public enum_base + { + /** + * Flow Hash is calculated based on SRC IP + * in case of load balancing + */ + const static hash_mode_t SRC_IP; + + /** + * Flow hash is calculated based on DST IP + */ + const static hash_mode_t DST_IP; + + /** + * Flow hash is calculated based on SRC IP, + * DST IP and Protocol. SRC IP and DST IP + * addresses are sorted before hash such that + * a same hash is generated in both directions. + */ + const static hash_mode_t SYMMETRIC; + + /** + * create the hash mode from int value + */ + static const hash_mode_t& from_int(vapi_enum_gbp_hash_mode i); + + private: + hash_mode_t(int v, const std::string s); + }; + + /** + * unordered set of next hops + */ + typedef std::set next_hops_t; + + /** + * Representation of set of next hops and + * associated hash mode profile + */ + struct next_hop_set_t + { + /** + * Constructor for next_hop_set_t + */ + next_hop_set_t(const hash_mode_t& hm, next_hops_t& nhs); + + /** + * Destructor for next_hop_set_t + */ + ~next_hop_set_t() = default; + + /** + * convert to string + */ + std::string to_string() const; + + /** + * Comparison operator + */ + bool operator==(const next_hop_set_t& nhs) const; + + /** + * get the hash mode + */ + const hash_mode_t& getHashMode(void) const; + + /** + * get the set of next hops + */ + const next_hops_t& getNextHops(void) const; + + private: + /** + * hash mode for this rule + */ + const hash_mode_t m_hm; + + /** + * set of next hops + */ + const next_hops_t m_nhs; + }; + + /** + * ACL rule action enum + */ + struct action_t : public enum_base + { + /** + * Permit action + */ + const static action_t PERMIT; + + /** + * Deny action + */ + const static action_t DENY; + + /** + * Redirect action + */ + const static action_t REDIRECT; + + /** + * create the action from int value + */ + static const action_t& from_int(vapi_enum_gbp_rule_action i); + + private: + action_t(int v, const std::string s); + }; + + /** + * Construct a new object matching the desried state + */ + gbp_rule(uint32_t priority, const next_hop_set_t& nhs, const action_t& a); + + /** + * Copy Constructor + */ + gbp_rule(const gbp_rule& o) = default; + + /** + * Destructor + */ + ~gbp_rule() = default; + + /** + * convert to string format for debug purposes + */ + std::string to_string() const; + + /** + * less-than operator + */ + bool operator<(const gbp_rule& rule) const; + + /** + * comparison operator (for testing) + */ + bool operator==(const gbp_rule& rule) const; + + /** + * Getters + */ + uint32_t priority() const; + const next_hop_set_t& nhs() const; + const action_t& action() const; + +private: + /** + * Priority. Used to sort the rules in a list in the order + * in which they are applied + */ + uint32_t m_priority; + + /** + * set of next hops along with hash mode profile + */ + const next_hop_set_t m_nhs; + + /** + * Action on match + */ + const action_t m_action; +}; +}; + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "mozilla") + * End: + */ + +#endif diff --git a/src/plugins/gbp/gbp.api b/src/plugins/gbp/gbp.api index 9af8b35b870..6bdcc5d7e0c 100644 --- a/src/plugins/gbp/gbp.api +++ b/src/plugins/gbp/gbp.api @@ -263,6 +263,7 @@ enum gbp_hash_mode { GBP_API_HASH_MODE_SRC_IP, GBP_API_HASH_MODE_DST_IP, + GBP_API_HASH_MODE_SYMMETRIC, }; typedef gbp_next_hop_set diff --git a/src/plugins/gbp/gbp_api.c b/src/plugins/gbp/gbp_api.c index 6a000721a01..4402ec15b8c 100644 --- a/src/plugins/gbp/gbp_api.c +++ b/src/plugins/gbp/gbp_api.c @@ -718,6 +718,9 @@ gbp_hash_mode_decode (vl_api_gbp_hash_mode_t in, gbp_hash_mode_t * out) case GBP_API_HASH_MODE_DST_IP: *out = GBP_HASH_MODE_DST_IP; return (0); + case GBP_API_HASH_MODE_SYMMETRIC: + *out = GBP_HASH_MODE_SYMMETRIC; + return (0); } return (-2); diff --git a/src/plugins/gbp/gbp_contract.h b/src/plugins/gbp/gbp_contract.h index c1073510413..21e7265a82f 100644 --- a/src/plugins/gbp/gbp_contract.h +++ b/src/plugins/gbp/gbp_contract.h @@ -52,7 +52,8 @@ typedef struct gbp_next_hop_t_ #define foreach_gbp_hash_mode \ _(SRC_IP, "src-ip") \ - _(DST_IP, "dst-ip") + _(DST_IP, "dst-ip") \ + _(SYMMETRIC, "symmetric") typedef enum gbp_hash_mode_t_ { -- 2.16.6