731 lines
26 KiB
Diff
731 lines
26 KiB
Diff
Ethernet-NSH packet.
|
|
|
|
With this feature(options:nsh-convert=true),when VxLAN-GPE NSH packets (Outer
|
|
MAC header + Outer IP header + UDP header + VxLAN-GPE + NSH + original packet)
|
|
are received by VxLAN-GPE NSH port, the vport will remove Outer MAC header,
|
|
Outer IP header, UDP header, VxLAN-GPE header, and then modify and push the
|
|
outer MAC header. Then the packet with VxLAN-GPE+NSH format is converted to
|
|
Outer MAC header + NSH header + original packet.
|
|
|
|
Signed-off-by: Ricky Li <<A HREF="http://openvswitch.org/mailman/listinfo/dev">ricky.li at intel.com</A>>
|
|
Signed-off-by: Mengke Liu <<A HREF="http://openvswitch.org/mailman/listinfo/dev">mengke.liu at intel.com</A>>
|
|
---
|
|
datapath/linux/compat/include/linux/openvswitch.h | 6 +-
|
|
lib/netdev-vport.c | 109 +++++++++++++++++++++-
|
|
lib/netdev.h | 6 ++
|
|
lib/odp-util.c | 80 ++++++++++++++--
|
|
lib/ovs-router.c | 64 +++++++++++++
|
|
lib/ovs-router.h | 1 +
|
|
lib/packets.h | 13 ++-
|
|
ofproto/ofproto-dpif-xlate.c | 64 ++++++++++++-
|
|
ofproto/tunnel.c | 37 ++++++++
|
|
ofproto/tunnel.h | 5 +
|
|
tests/tunnel.at | 32 +++++++
|
|
11 files changed, 398 insertions(+), 19 deletions(-)
|
|
|
|
diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
|
|
index b8ac152..3d588bb 100644
|
|
--- a/datapath/linux/compat/include/linux/openvswitch.h
|
|
+++ b/datapath/linux/compat/include/linux/openvswitch.h
|
|
@@ -230,6 +230,7 @@ enum ovs_vport_type {
|
|
OVS_VPORT_TYPE_GENEVE, /* Geneve tunnel. */
|
|
OVS_VPORT_TYPE_LISP = 105, /* LISP tunnel */
|
|
OVS_VPORT_TYPE_STT = 106, /* STT tunnel */
|
|
+ OVS_VPORT_TYPE_NSH, /* L2+NSH ENCAP tunnel. */
|
|
__OVS_VPORT_TYPE_MAX
|
|
};
|
|
|
|
@@ -646,7 +647,10 @@ struct ovs_action_push_tnl {
|
|
uint8_t header[TNL_PUSH_HEADER_SIZE];
|
|
};
|
|
|
|
-#define OVS_POP_SPEC_ACTION_NO_DECAP 2
|
|
+enum ovs_pop_spec_action_type {
|
|
+ OVS_POP_SPEC_ACTION_CONVERT_TO_ETH_NSH,
|
|
+ OVS_POP_SPEC_ACTION_NO_DECAP = 2,
|
|
+};
|
|
|
|
/*
|
|
* struct ovs_action_pop_tnl - %OVS_ACTION_ATTR_TUNNEL_POP_SPEC
|
|
diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c
|
|
index d926c00..6e0d5ba 100644
|
|
--- a/lib/netdev-vport.c
|
|
+++ b/lib/netdev-vport.c
|
|
@@ -561,6 +561,23 @@ set_tunnel_config(struct netdev *dev_, const struct smap *args)
|
|
} else {
|
|
tnl_cfg.ip_dst = in_addr.s_addr;
|
|
}
|
|
+ } else if (!strcmp(node->key, "remote_mac")) {
|
|
+ if (!strcmp(node->value, "flow")) {
|
|
+ tnl_cfg.eth_dst_flow = true;
|
|
+ VLOG_ERR("remote_mac doesn't support setting by flow");
|
|
+ return EINVAL;
|
|
+ } else if (eth_addr_from_string(node->value,&tnl_cfg.eth_dst)){
|
|
+ tnl_cfg.eth_dst_present = true;
|
|
+ } else {
|
|
+ VLOG_WARN("%s: bad %s 'remote_mac'", name, type);
|
|
+ return EINVAL;
|
|
+ }
|
|
+ } else if (!strcmp(node->key, "nsh_convert")) {
|
|
+ if (!strcmp(node->value, "true")) {
|
|
+ tnl_cfg.nsh_convert = true;
|
|
+ } else {
|
|
+ tnl_cfg.nsh_convert = false;
|
|
+ }
|
|
} else if (!strcmp(node->key, "tun_nodecap")) {
|
|
if (!strcmp(node->value, "true")) {
|
|
tnl_cfg.tun_nodecap = true;
|
|
@@ -883,6 +900,13 @@ get_tunnel_config(const struct netdev *dev, struct smap *args)
|
|
if (!tnl_cfg.dont_fragment) {
|
|
smap_add(args, "df_default", "false");
|
|
}
|
|
+ if (tnl_cfg.eth_dst_present) {
|
|
+ smap_add_format(args, "remote_mac", ETH_ADDR_FMT, ETH_ADDR_ARGS(tnl_cfg.eth_dst));
|
|
+ }
|
|
+
|
|
+ if (tnl_cfg.nsh_convert) {
|
|
+ smap_add(args, "nsh_convert", "true");
|
|
+ }
|
|
|
|
if (tnl_cfg.tun_nodecap) {
|
|
smap_add(args, "tun_nodecap", "true");
|
|
@@ -1546,6 +1570,84 @@ netdev_vxlan_pop_header(struct dp_packet *packet)
|
|
}
|
|
|
|
static int
|
|
+vxlan_extract_md_convert_to_eth_nsh(struct dp_packet *packet, const struct ovs_action_pop_tnl *data)
|
|
+{
|
|
+ struct pkt_metadata *md = &packet->md;
|
|
+ struct flow_tnl *tnl = &md->tunnel;
|
|
+ struct udp_header *udp;
|
|
+
|
|
+ memset(md, 0, sizeof *md);
|
|
+ if (VXLAN_HLEN > dp_packet_size(packet)) {
|
|
+ return EINVAL;
|
|
+ }
|
|
+
|
|
+ udp = ip_extract_tnl_md(packet, tnl);
|
|
+ if (!udp) {
|
|
+ return EINVAL;
|
|
+ }
|
|
+
|
|
+ if (ntohs(udp->udp_dst) == VXGPE_DST_PORT) {
|
|
+
|
|
+ struct vxgpehdr *vxg = (struct vxgpehdr *) (udp + 1);
|
|
+
|
|
+ if (get_16aligned_be32(&vxg->vx_vni) & htonl(0xff)) {
|
|
+ VLOG_WARN_RL(&err_rl, "invalid vxlan-gpe vni=%#x\n",
|
|
+ ntohl(get_16aligned_be32(&vxg->vx_vni)));
|
|
+ return EINVAL;
|
|
+ }
|
|
+
|
|
+ tnl->tp_src = udp->udp_src;
|
|
+ tnl->tp_dst = udp->udp_dst;
|
|
+ tnl->tun_id = htonll(ntohl(get_16aligned_be32(&vxg->vx_vni)) >> 8);
|
|
+
|
|
+ if (vxg->p == 0x01 && vxg->proto == VXG_P_NSH) {
|
|
+ struct nshhdr *nsh = (struct nshhdr *) (vxg + 1);
|
|
+ struct eth_header *eth = NULL;
|
|
+
|
|
+ tnl->nsp = nsh->b.b2 << 8;
|
|
+ tnl->nsi = nsh->b.svc_idx;
|
|
+ tnl->nshc1 = nsh->c.nshc1;
|
|
+ tnl->nshc2 = nsh->c.nshc2;
|
|
+ tnl->nshc3 = nsh->c.nshc3;
|
|
+ tnl->nshc4 = nsh->c.nshc4;
|
|
+ tnl->flags |= FLOW_TNL_F_NSP;
|
|
+ tnl->flags |= FLOW_TNL_F_NSI;
|
|
+ tnl->flags |= FLOW_TNL_F_NSH_C1 | FLOW_TNL_F_NSH_C2 | \
|
|
+ FLOW_TNL_F_NSH_C3 | FLOW_TNL_F_NSH_C4;
|
|
+ tnl->nsh_flags = NSH_TNL_F_ETHERNET;
|
|
+
|
|
+ dp_packet_reset_packet(packet, VXNSH_HLEN - sizeof (struct nshhdr));
|
|
+ eth = (struct eth_header *) dp_packet_push_uninit(packet, data->header_len);
|
|
+ memcpy(eth, data->header, data->header_len);
|
|
+ eth->eth_type = htons(ETH_TYPE_NSH);
|
|
+ } else {
|
|
+ VLOG_WARN("Unsupported vxlan GPE + NSH format!");
|
|
+ return EINVAL;
|
|
+ }
|
|
+
|
|
+ } else {
|
|
+
|
|
+ struct vxlanhdr *vxh = (struct vxlanhdr *) (udp + 1);
|
|
+
|
|
+ if (get_16aligned_be32(&vxh->vx_flags) != htonl(VXLAN_FLAGS) ||
|
|
+ (get_16aligned_be32(&vxh->vx_vni) & htonl(0xff))) {
|
|
+ VLOG_WARN_RL(&err_rl, "invalid vxlan flags=%#x vni=%#x\n",
|
|
+ ntohl(get_16aligned_be32(&vxh->vx_flags)),
|
|
+ ntohl(get_16aligned_be32(&vxh->vx_vni)));
|
|
+ return EINVAL;
|
|
+ }
|
|
+
|
|
+ tnl->tp_src = udp->udp_src;
|
|
+ tnl->tp_dst = udp->udp_dst;
|
|
+ tnl->tun_id = htonll(ntohl(get_16aligned_be32(&vxh->vx_vni)) >> 8);
|
|
+ dp_packet_reset_packet(packet, VXLAN_HLEN);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+}
|
|
+
|
|
+static int
|
|
vxlan_extract_md_no_decap(struct dp_packet *packet)
|
|
{
|
|
struct pkt_metadata *md = &packet->md;
|
|
@@ -1595,6 +1697,7 @@ vxlan_extract_md_no_decap(struct dp_packet *packet)
|
|
tnl->flags |= FLOW_TNL_F_NSI;
|
|
tnl->flags |= FLOW_TNL_F_NSH_C1 | FLOW_TNL_F_NSH_C2 | \
|
|
FLOW_TNL_F_NSH_C3 | FLOW_TNL_F_NSH_C4;
|
|
+ tnl->tun_len = VXNSH_HLEN;
|
|
tnl->nsh_flags = NSH_TNL_F_NODECAP;
|
|
} else {
|
|
VLOG_WARN("Unsupported vxlan GPE + NSH format!");
|
|
@@ -1606,19 +1709,19 @@ vxlan_extract_md_no_decap(struct dp_packet *packet)
|
|
return 0;
|
|
}
|
|
|
|
-
|
|
static int
|
|
netdev_vxlan_pop_header_spec(struct dp_packet *packet,
|
|
const struct ovs_action_pop_tnl *data)
|
|
{
|
|
- if (data->pop_type == OVS_POP_SPEC_ACTION_NO_DECAP) {
|
|
+ if (data->pop_type == OVS_POP_SPEC_ACTION_CONVERT_TO_ETH_NSH) {
|
|
+ return vxlan_extract_md_convert_to_eth_nsh(packet, data);
|
|
+ } else if (data->pop_type == OVS_POP_SPEC_ACTION_NO_DECAP) {
|
|
return vxlan_extract_md_no_decap(packet);
|
|
}
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
-
|
|
static int
|
|
netdev_vxlan_build_header(const struct netdev *netdev,
|
|
struct ovs_action_push_tnl *data,
|
|
diff --git a/lib/netdev.h b/lib/netdev.h
|
|
index b30c932..26013ef 100644
|
|
--- a/lib/netdev.h
|
|
+++ b/lib/netdev.h
|
|
@@ -150,6 +150,10 @@ struct netdev_tunnel_config {
|
|
bool ipsec;
|
|
bool dont_fragment;
|
|
|
|
+ bool eth_dst_present;
|
|
+ bool eth_dst_flow;
|
|
+ struct eth_addr eth_dst;
|
|
+
|
|
bool in_nshc1_present;
|
|
bool in_nshc1_flow;
|
|
ovs_be32 in_nshc1; /* incoming NSH context c1 */
|
|
@@ -182,6 +186,7 @@ struct netdev_tunnel_config {
|
|
bool out_nshc4_flow;
|
|
ovs_be32 out_nshc4; /* outgoing NSH context c4 */
|
|
|
|
+ bool nsh_convert;
|
|
bool tun_nodecap;
|
|
|
|
};
|
|
@@ -247,6 +252,7 @@ int netdev_pop_header(struct netdev *netdev, struct dp_packet **buffers,
|
|
int netdev_pop_header_spec(struct netdev *netdev,
|
|
struct dp_packet **buffers, int cnt,
|
|
const struct ovs_action_pop_tnl *data);
|
|
+
|
|
/* Hardware address. */
|
|
int netdev_set_etheraddr(struct netdev *, const struct eth_addr mac);
|
|
int netdev_get_etheraddr(const struct netdev *, struct eth_addr *mac);
|
|
diff --git a/lib/odp-util.c b/lib/odp-util.c
|
|
index 190117f..6da2d5b 100644
|
|
--- a/lib/odp-util.c
|
|
+++ b/lib/odp-util.c
|
|
@@ -552,16 +552,22 @@ format_odp_tnl_push_header(struct ds *ds, struct ovs_action_push_tnl *data)
|
|
}
|
|
|
|
static void
|
|
-format_odp_tnl_pop_spec_action(struct ds *ds, const struct nlattr *attr)
|
|
+format_odp_tnl_pop_header(struct ds *ds, struct ovs_action_pop_tnl *data)
|
|
{
|
|
- struct ovs_action_pop_tnl *data;
|
|
+ const struct eth_header *eth;
|
|
|
|
- data = (struct ovs_action_pop_tnl *) nl_attr_get(attr);
|
|
+ eth = (const struct eth_header *)data->header;
|
|
+ if (data->tnl_type == OVS_VPORT_TYPE_NSH) {
|
|
+ const struct nshhdr *nsh = (const struct nshhdr *) (eth + 1);
|
|
|
|
- ds_put_format(ds, "tnl_pop_spec(tnl_port(%"PRIu32"),", data->tnl_port);
|
|
- if (data->pop_type == OVS_POP_SPEC_ACTION_NO_DECAP) {
|
|
- ds_put_format(ds, "pop_type=%"PRIu16")",
|
|
- OVS_POP_SPEC_ACTION_NO_DECAP);
|
|
+ /* Ethernet */
|
|
+ ds_put_format(ds, "header(size=%"PRIu8",type=%"PRIu8",eth(dst=",
|
|
+ data->header_len, data->tnl_type);
|
|
+ ds_put_format(ds, ETH_ADDR_FMT, ETH_ADDR_ARGS(eth->eth_dst));
|
|
+ ds_put_format(ds, ",src=");
|
|
+ ds_put_format(ds, ETH_ADDR_FMT, ETH_ADDR_ARGS(eth->eth_src));
|
|
+ ds_put_format(ds, ",dl_type=0x%04"PRIx16")", ntohs(eth->eth_type));
|
|
+ ds_put_format(ds, "),");
|
|
}
|
|
}
|
|
|
|
@@ -578,6 +584,26 @@ format_odp_tnl_push_action(struct ds *ds, const struct nlattr *attr)
|
|
}
|
|
|
|
static void
|
|
+format_odp_tnl_pop_spec_action(struct ds *ds, const struct nlattr *attr)
|
|
+{
|
|
+ struct ovs_action_pop_tnl *data;
|
|
+
|
|
+ data = (struct ovs_action_pop_tnl *) nl_attr_get(attr);
|
|
+
|
|
+ ds_put_format(ds, "tnl_pop_spec(tnl_port(%"PRIu32"),", data->tnl_port);
|
|
+ if (data->pop_type == OVS_POP_SPEC_ACTION_CONVERT_TO_ETH_NSH) {
|
|
+ ds_put_format(ds, "pop_type=%"PRIu16",",
|
|
+ OVS_POP_SPEC_ACTION_CONVERT_TO_ETH_NSH);
|
|
+ format_odp_tnl_pop_header(ds, data);
|
|
+ ds_put_format(ds, "out_port(%"PRIu32"))", data->out_port);
|
|
+
|
|
+ } else if (data->pop_type == OVS_POP_SPEC_ACTION_NO_DECAP) {
|
|
+ ds_put_format(ds, "pop_type=%"PRIu16")",
|
|
+ OVS_POP_SPEC_ACTION_NO_DECAP);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
format_odp_action(struct ds *ds, const struct nlattr *a)
|
|
{
|
|
int expected_len;
|
|
@@ -1050,11 +1076,8 @@ static int
|
|
ovs_parse_tnl_pop_spec(const char *s, struct ovs_action_pop_tnl *data)
|
|
{
|
|
struct eth_header *eth;
|
|
- struct nshhdr *nsh;
|
|
uint32_t tnl_type = 0, header_len = 0;
|
|
uint16_t dl_type;
|
|
- ovs_be32 nsp, nshc1, nshc2, nshc3, nshc4;
|
|
- uint8_t nsi;
|
|
int n = 0;
|
|
if (!ovs_scan_len(s, &n, "tnl_pop_spec(tnl_port(%"SCNi32"),",
|
|
&data->tnl_port)) {
|
|
@@ -1068,6 +1091,42 @@ ovs_parse_tnl_pop_spec(const char *s, struct ovs_action_pop_tnl *data)
|
|
|
|
if (data->pop_type == OVS_POP_SPEC_ACTION_NO_DECAP) {
|
|
return n;
|
|
+
|
|
+ } else if (data->pop_type == OVS_POP_SPEC_ACTION_CONVERT_TO_ETH_NSH) {
|
|
+
|
|
+ eth = (struct eth_header *) data->header;
|
|
+
|
|
+ if (!ovs_scan_len(s, &n, ",header(size=%"SCNi32",type=%"SCNi32","
|
|
+ "eth(dst="ETH_ADDR_SCAN_FMT",",
|
|
+ &data->header_len,
|
|
+ &data->tnl_type,
|
|
+ ETH_ADDR_SCAN_ARGS(eth->eth_dst))) {
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ if (!ovs_scan_len(s, &n, "src="ETH_ADDR_SCAN_FMT",",
|
|
+ ETH_ADDR_SCAN_ARGS(eth->eth_src))) {
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ if (!ovs_scan_len(s, &n, "dl_type=0x%"SCNx16"),", &dl_type)) {
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ eth->eth_type = htons(dl_type);
|
|
+
|
|
+ tnl_type = OVS_VPORT_TYPE_NSH;
|
|
+ header_len = sizeof *eth;
|
|
+
|
|
+ /* check tunnel meta data. */
|
|
+ if (data->tnl_type != tnl_type) {
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ if (data->header_len != header_len) {
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* Out port */
|
|
+ if (!ovs_scan_len(s, &n, ",out_port(%"SCNi32"))", &data->out_port)) {
|
|
+ return -EINVAL;
|
|
+ }
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
@@ -1075,6 +1134,7 @@ ovs_parse_tnl_pop_spec(const char *s, struct ovs_action_pop_tnl *data)
|
|
return n;
|
|
}
|
|
|
|
+
|
|
static int
|
|
parse_odp_action(const char *s, const struct simap *port_names,
|
|
struct ofpbuf *actions)
|
|
diff --git a/lib/ovs-router.c b/lib/ovs-router.c
|
|
index d6c7652..9f61bac 100644
|
|
--- a/lib/ovs-router.c
|
|
+++ b/lib/ovs-router.c
|
|
@@ -82,6 +82,24 @@ ovs_router_lookup(ovs_be32 ip_dst, char output_bridge[], ovs_be32 *gw)
|
|
return route_table_fallback_lookup(ip_dst, output_bridge, gw);
|
|
}
|
|
|
|
+bool
|
|
+ovs_router_lookup_mac(const struct eth_addr *mac, char output_bridge[])
|
|
+{
|
|
+ const struct cls_rule *cr;
|
|
+ struct flow s_flow;
|
|
+
|
|
+ memset(&s_flow, 0, sizeof (struct flow));
|
|
+ memcpy(s_flow.dl_dst.ea, mac->ea, ETH_ADDR_LEN);
|
|
+ cr = classifier_lookup(&cls,CLS_MAX_VERSION, &s_flow, NULL);
|
|
+ if (cr) {
|
|
+ struct ovs_router_entry *p = ovs_router_entry_cast(cr);
|
|
+
|
|
+ strncpy(output_bridge, p->output_bridge, IFNAMSIZ);
|
|
+ return true;
|
|
+ }
|
|
+ return false;
|
|
+}
|
|
+
|
|
static void
|
|
rt_entry_free(struct ovs_router_entry *p)
|
|
{
|
|
@@ -133,6 +151,36 @@ ovs_router_insert__(uint8_t priority, ovs_be32 ip_dst, uint8_t plen,
|
|
seq_change(tnl_conf_seq);
|
|
}
|
|
|
|
+static void
|
|
+ovs_router_insert_mac__(uint8_t priority, struct eth_addr *mac,
|
|
+ const char output_bridge[])
|
|
+{
|
|
+ const struct cls_rule *cr;
|
|
+ struct ovs_router_entry *p;
|
|
+ struct match s_match;
|
|
+
|
|
+ memset(&s_match, 0, sizeof (struct match));
|
|
+ memcpy(s_match.flow.dl_dst.ea, mac->ea, ETH_ADDR_LEN);
|
|
+
|
|
+ p = xzalloc(sizeof *p);
|
|
+ strncpy(p->output_bridge, output_bridge, IFNAMSIZ);
|
|
+ p->gw = 0;
|
|
+ p->nw_addr = 0;
|
|
+ p->plen = 32;
|
|
+ p->priority = priority;
|
|
+ cls_rule_init(&p->cr, &s_match, priority); /* Longest prefix matches first. */
|
|
+
|
|
+ ovs_mutex_lock(&mutex);
|
|
+ cr = classifier_replace(&cls, &p->cr, CLS_MIN_VERSION, NULL, 0);
|
|
+ ovs_mutex_unlock(&mutex);
|
|
+
|
|
+ if (cr) {
|
|
+ /* An old rule with the same match was displaced. */
|
|
+ ovsrcu_postpone(rt_entry_free, ovs_router_entry_cast(cr));
|
|
+ }
|
|
+ seq_change(tnl_conf_seq);
|
|
+}
|
|
+
|
|
void
|
|
ovs_router_insert(ovs_be32 ip_dst, uint8_t plen, const char output_bridge[],
|
|
ovs_be32 gw)
|
|
@@ -231,6 +279,20 @@ ovs_router_add(struct unixctl_conn *conn, int argc,
|
|
}
|
|
|
|
static void
|
|
+ovs_router_add_mac(struct unixctl_conn *conn, int argc OVS_UNUSED,
|
|
+ const char *argv[], void *aux OVS_UNUSED)
|
|
+{
|
|
+ struct eth_addr mac;
|
|
+
|
|
+ if (eth_addr_from_string(argv[1], &mac)) {
|
|
+ ovs_router_insert_mac__(48, &mac, argv[2]);
|
|
+ unixctl_command_reply(conn, "OK");
|
|
+ } else {
|
|
+ unixctl_command_reply(conn, "Invalid parameters");
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
ovs_router_del(struct unixctl_conn *conn, int argc OVS_UNUSED,
|
|
const char *argv[], void *aux OVS_UNUSED)
|
|
{
|
|
@@ -326,6 +388,8 @@ ovs_router_init(void)
|
|
classifier_init(&cls, NULL);
|
|
unixctl_command_register("ovs/route/add", "ipv4_addr/prefix_len out_br_name gw", 2, 3,
|
|
ovs_router_add, NULL);
|
|
+ unixctl_command_register("ovs/route/addmac", "mac_addr out_br_name", 2, 2,
|
|
+ ovs_router_add_mac, NULL);
|
|
unixctl_command_register("ovs/route/show", "", 0, 0, ovs_router_show, NULL);
|
|
unixctl_command_register("ovs/route/del", "ipv4_addr/prefix_len", 1, 1, ovs_router_del,
|
|
NULL);
|
|
diff --git a/lib/ovs-router.h b/lib/ovs-router.h
|
|
index cc0ebc2..3f5a504 100644
|
|
--- a/lib/ovs-router.h
|
|
+++ b/lib/ovs-router.h
|
|
@@ -23,6 +23,7 @@
|
|
extern "C" {
|
|
#endif
|
|
|
|
+bool ovs_router_lookup_mac(const struct eth_addr *mac, char output_bridge[]);
|
|
bool ovs_router_lookup(ovs_be32 ip_dst, char out_dev[], ovs_be32 *gw);
|
|
void ovs_router_init(void);
|
|
void ovs_router_insert(ovs_be32 ip_dst, uint8_t plen,
|
|
diff --git a/lib/packets.h b/lib/packets.h
|
|
index 87c955a..c586390 100644
|
|
--- a/lib/packets.h
|
|
+++ b/lib/packets.h
|
|
@@ -33,6 +33,8 @@
|
|
struct dp_packet;
|
|
struct ds;
|
|
|
|
+#define ETH_ADDR_LEN 6
|
|
+
|
|
/* Tunnel information used in flow key and metadata. */
|
|
struct flow_tnl {
|
|
ovs_be32 ip_dst;
|
|
@@ -52,7 +54,9 @@ struct flow_tnl {
|
|
ovs_be32 nshc2;
|
|
ovs_be32 nshc3;
|
|
ovs_be32 nshc4;
|
|
- uint8_t pad1[7]; /* Pad to 64 bits. */
|
|
+ struct eth_addr eth_dst;
|
|
+ uint8_t tun_len;
|
|
+ uint8_t pad1[4]; /* Pad to 64 bits. */
|
|
struct tun_metadata metadata;
|
|
};
|
|
|
|
@@ -83,7 +87,9 @@ struct flow_tnl {
|
|
#define FLOW_TNL_F_NSH_C3 (1 << 9)
|
|
#define FLOW_TNL_F_NSH_C4 (1 << 10)
|
|
|
|
-#define NSH_TNL_F_NODECAP (1 << 1)
|
|
+#define NSH_TNL_F_ETHERNET (1 << 0)
|
|
+#define NSH_TNL_F_VXLAN (1 << 1)
|
|
+#define NSH_TNL_F_NODECAP (1 << 2)
|
|
|
|
/* Returns an offset to 'src' covering all the meaningful fields in 'src'. */
|
|
static inline size_t
|
|
@@ -160,8 +166,6 @@ pkt_metadata_init(struct pkt_metadata *md, odp_port_t port)
|
|
|
|
bool dpid_from_string(const char *s, uint64_t *dpidp);
|
|
|
|
-#define ETH_ADDR_LEN 6
|
|
-
|
|
static const struct eth_addr eth_addr_broadcast OVS_UNUSED
|
|
= { { { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } } };
|
|
|
|
@@ -352,6 +356,7 @@ ovs_be32 set_mpls_lse_values(uint8_t ttl, uint8_t tc, uint8_t bos,
|
|
#define ETH_TYPE_RARP 0x8035
|
|
#define ETH_TYPE_MPLS 0x8847
|
|
#define ETH_TYPE_MPLS_MCAST 0x8848
|
|
+#define ETH_TYPE_NSH 0x894f
|
|
|
|
static inline bool eth_type_mpls(ovs_be16 eth_type)
|
|
{
|
|
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
|
|
index 71e255e..bff0a83 100644
|
|
--- a/ofproto/ofproto-dpif-xlate.c
|
|
+++ b/ofproto/ofproto-dpif-xlate.c
|
|
@@ -2690,6 +2690,36 @@ tnl_route_lookup_flow(const struct flow *oflow,
|
|
}
|
|
|
|
static int
|
|
+tnl_outdev_lookup_mac(const struct eth_addr *mac,
|
|
+ struct xport **out_port)
|
|
+{
|
|
+ char out_dev[IFNAMSIZ];
|
|
+ struct xbridge *xbridge;
|
|
+ struct xlate_cfg *xcfg;
|
|
+
|
|
+ if (!ovs_router_lookup_mac(mac, out_dev)) {
|
|
+ return -ENOENT;
|
|
+ }
|
|
+
|
|
+ xcfg = ovsrcu_get(struct xlate_cfg *, &xcfgp);
|
|
+ ovs_assert(xcfg);
|
|
+
|
|
+ HMAP_FOR_EACH (xbridge, hmap_node, &xcfg->xbridges) {
|
|
+ if (!strncmp(xbridge->name, out_dev, IFNAMSIZ)) {
|
|
+ struct xport *port;
|
|
+
|
|
+ HMAP_FOR_EACH (port, ofp_node, &xbridge->xports) {
|
|
+ if (!strncmp(netdev_get_name(port->netdev), out_dev, IFNAMSIZ)) {
|
|
+ *out_port = port;
|
|
+ return 0;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ return -ENOENT;
|
|
+}
|
|
+
|
|
+static int
|
|
compose_table_xlate(struct xlate_ctx *ctx, const struct xport *out_dev,
|
|
struct dp_packet *packet)
|
|
{
|
|
@@ -2795,7 +2825,39 @@ build_tunnel_pop(const struct xlate_ctx *ctx, odp_port_t tunnel_odp_port, struct
|
|
cfg = tnl_port_cfg(tunnel_odp_port, flow);
|
|
|
|
if (cfg) {
|
|
- if (cfg->tun_nodecap) {
|
|
+ if (cfg->nsh_convert && (ntohs(cfg->dst_port) == VXGPE_DST_PORT)) {
|
|
+ struct ovs_action_pop_tnl tnl_pop_data;
|
|
+ struct xport *out_dev = NULL;
|
|
+ struct eth_addr smac;
|
|
+
|
|
+ int err;
|
|
+
|
|
+ err = tnl_outdev_lookup_mac(&cfg->eth_dst, &out_dev);
|
|
+ if (err) {
|
|
+ VLOG_WARN("tnl_outdev_lookup_mac failed...");
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ /* Use mac addr of bridge port of the peer. */
|
|
+ err = netdev_get_etheraddr(out_dev->netdev, &smac);
|
|
+ if (err) {
|
|
+ VLOG_WARN("netdev_get_etheraddr failed...");
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ err = tnl_port_build_nsh_header_odport_popspec(tunnel_odp_port, flow,
|
|
+ &cfg->eth_dst, &smac, &tnl_pop_data);
|
|
+ if (err) {
|
|
+ VLOG_WARN("tnl_port_build_nsh_header failed...");
|
|
+ return err;
|
|
+ }
|
|
+ tnl_pop_data.tnl_port = odp_to_u32(tunnel_odp_port);
|
|
+ tnl_pop_data.out_port = odp_to_u32(out_dev->odp_port);
|
|
+ tnl_pop_data.pop_type = OVS_POP_SPEC_ACTION_CONVERT_TO_ETH_NSH;
|
|
+ tnl_pop_data.tnl_type = OVS_VPORT_TYPE_NSH;
|
|
+ odp_put_tnl_pop_spec_action(ctx->odp_actions, &tnl_pop_data);
|
|
+
|
|
+ } else if (cfg->tun_nodecap) {
|
|
struct ovs_action_pop_tnl tnl_pop_data;
|
|
memset(&tnl_pop_data, 0, sizeof tnl_pop_data);
|
|
|
|
diff --git a/ofproto/tunnel.c b/ofproto/tunnel.c
|
|
index 4606fb6..b0e46e6 100644
|
|
--- a/ofproto/tunnel.c
|
|
+++ b/ofproto/tunnel.c
|
|
@@ -45,6 +45,8 @@ VLOG_DEFINE_THIS_MODULE(tunnel);
|
|
/* skb mark used for IPsec tunnel packets */
|
|
#define IPSEC_MARK 1
|
|
|
|
+#define ETH_NSH_HLEN (sizeof(struct eth_header) + \
|
|
+ sizeof(struct nshhdr))
|
|
struct tnl_match {
|
|
ovs_be64 in_key;
|
|
ovs_be32 in_nsp;
|
|
@@ -568,6 +570,9 @@ tnl_port_cfg(odp_port_t odp_port, struct flow *flow) OVS_EXCLUDED(rwlock)
|
|
cfg = netdev_get_tunnel_config(tnl_port->netdev);
|
|
ovs_assert(cfg);
|
|
|
|
+ if (!cfg->eth_dst_flow) {
|
|
+ memcpy(flow->tunnel.eth_dst.ea, cfg->eth_dst.ea, ETH_ADDR_LEN);
|
|
+ }
|
|
if (!cfg->out_nsp_flow) {
|
|
flow->tunnel.nsp = cfg->out_nsp;
|
|
}
|
|
@@ -602,6 +607,7 @@ out:
|
|
return cfg;
|
|
}
|
|
|
|
+
|
|
static uint32_t
|
|
tnl_hash(struct tnl_match *match)
|
|
{
|
|
@@ -1063,3 +1069,34 @@ tnl_port_build_header(const struct ofport_dpif *ofport,
|
|
|
|
return res;
|
|
}
|
|
+
|
|
+int
|
|
+tnl_port_build_nsh_header_odport_popspec(const odp_port_t odp_port,
|
|
+ const struct flow *tnl_flow OVS_UNUSED,
|
|
+ const struct eth_addr *dmac,
|
|
+ const struct eth_addr *smac,
|
|
+ struct ovs_action_pop_tnl *data)
|
|
+{
|
|
+ struct tnl_port *tnl_port;
|
|
+ struct eth_header *eth;
|
|
+ int res = 0;
|
|
+
|
|
+ fat_rwlock_rdlock(&rwlock);
|
|
+ tnl_port = tnl_find_odp_port(odp_port);
|
|
+ ovs_assert(tnl_port);
|
|
+
|
|
+ /* Build Ethernet and IP headers. */
|
|
+ memset(data->header, 0, sizeof data->header);
|
|
+
|
|
+ eth = (struct eth_header *)data->header;
|
|
+ memcpy(eth->eth_dst.ea, dmac->ea, ETH_ADDR_LEN);
|
|
+ memcpy(eth->eth_src.ea, smac->ea, ETH_ADDR_LEN);
|
|
+ eth->eth_type = htons(ETH_TYPE_NSH);
|
|
+
|
|
+ data->header_len = ETH_NSH_HLEN - sizeof (struct nshhdr);
|
|
+ data->tnl_type = OVS_VPORT_TYPE_NSH;
|
|
+
|
|
+ fat_rwlock_unlock(&rwlock);
|
|
+
|
|
+ return res;
|
|
+}
|
|
diff --git a/ofproto/tunnel.h b/ofproto/tunnel.h
|
|
index 2b608ce..0c51a4e 100644
|
|
--- a/ofproto/tunnel.h
|
|
+++ b/ofproto/tunnel.h
|
|
@@ -59,4 +59,9 @@ int tnl_port_build_header(const struct ofport_dpif *ofport,
|
|
const struct eth_addr dmac,
|
|
const struct eth_addr smac,
|
|
ovs_be32 ip_src, struct ovs_action_push_tnl *data);
|
|
+int tnl_port_build_nsh_header_odport_popspec(const odp_port_t odp_port,
|
|
+ const struct flow *tnl_flow OVS_UNUSED,
|
|
+ const struct eth_addr *dmac,
|
|
+ const struct eth_addr *smac,
|
|
+ struct ovs_action_pop_tnl *data);
|
|
#endif /* tunnel.h */
|
|
diff --git a/tests/tunnel.at b/tests/tunnel.at
|
|
index 851afdc..dc35809 100644
|
|
--- a/tests/tunnel.at
|
|
+++ b/tests/tunnel.at
|
|
@@ -673,6 +673,38 @@ AT_CHECK([tail -1 stdout], [0],
|
|
OVS_VSWITCHD_STOP(["/The Open vSwitch kernel module is probably not loaded/d"])
|
|
AT_CLEANUP
|
|
|
|
+AT_SETUP([tunnel - VXLAN-GPE NSH - nsh_convert from VXLAN-GPE NSH to Ethernet NSH - user space])
|
|
+OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy ofport_request=1 other-config:hwaddr=aa:55:aa:55:00:00])
|
|
+AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy], [0])
|
|
+AT_CHECK([ovs-vsctl add-port int-br p1 -- set interface p1 type=vxlan options:remote_ip=1.1.1.1 options:dst_port=4790 \
|
|
+ options:nsh_convert=true options:nsi=flow options:nsp=flow options:nshc1=flow options:in_key=flow options:remote_mac=00:00:00:11:11:22 ofport_request=2])
|
|
+AT_CHECK([ovs-vsctl add-port int-br p2 -- set Interface p2 type=dummy ofport_request=3])
|
|
+
|
|
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 2.2.2.22/24], [0], [OK
|
|
+])
|
|
+AT_CHECK([ovs-appctl ovs/route/add 1.1.1.1/24 br0], [0], [OK
|
|
+])
|
|
+AT_CHECK([ovs-appctl ovs/route/addmac 00:00:00:11:11:22 br0],[0],[dnl
|
|
+OK
|
|
+])
|
|
+
|
|
+AT_CHECK([ovs-ofctl add-flow br0 action=normal])
|
|
+
|
|
+AT_CHECK([ovs-appctl tnl/ports/show |sort], [0], [dnl
|
|
+Listening ports:
|
|
+vxlan_sys_4790 (4790)
|
|
+])
|
|
+
|
|
+dnl remote_ip p0
|
|
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(3),eth(src=50:54:00:00:00:05,dst=aa:55:aa:55:00:00),eth_type(0x0800),ipv4(src=1.1.1.1,dst=2.2.2.22,proto=17,tos=0,ttl=64,frag=no),udp(src=8,dst=4790)'], [0], [stdout])
|
|
+
|
|
+AT_CHECK([tail -1 stdout], [0],
|
|
+ [Datapath actions: tnl_pop_spec(tnl_port(4790),pop_type=0,header(size=14,type=107,eth(dst=00:00:00:11:11:22,src=aa:55:aa:55:00:00,dl_type=0x894f)),out_port(100))
|
|
+])
|
|
+
|
|
+OVS_VSWITCHD_STOP(["/The Open vSwitch kernel module is probably not loaded/d"])
|
|
+AT_CLEANUP
|
|
+
|
|
AT_SETUP([tunnel - Geneve metadata])
|
|
OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=geneve \
|
|
options:remote_ip=1.1.1.1 ofport_request=1 \
|
|
--
|
|
1.9.3
|
|
|