#define IPPROTO_ICMP 1
#define IPPROTO_IGMP 2
#define IPPROTO_UDP 17
+#define IPPROTO_GRE 47
#define IPPROTO_TCP 6
#define IPPROTO_RSVD 0xff
struct dp_upcall_info upcall;
const struct nlattr *a;
int rem;
+ struct ovs_tunnel_info info;
upcall.cmd = OVS_PACKET_CMD_ACTION;
upcall.userdata = NULL;
upcall.portid = 0;
+ upcall.egress_tun_info = NULL;
for (a = nla_data(attr), rem = nla_len(attr); rem > 0;
a = nla_next(a, &rem)) {
case OVS_USERSPACE_ATTR_PID:
upcall.portid = nla_get_u32(a);
break;
+
+ case OVS_USERSPACE_ATTR_EGRESS_TUN_PORT: {
+ /* Get out tunnel info. */
+ struct vport *vport;
+
+ vport = ovs_vport_rcu(dp, nla_get_u32(a));
+ if (vport) {
+ int err;
+
+ err = ovs_vport_get_egress_tun_info(vport, skb,
+ &info);
+ if (!err)
+ upcall.egress_tun_info = &info;
+ }
+ break;
}
+
+ } /* End of switch. */
}
return ovs_dp_upcall(dp, skb, &upcall);
upcall.cmd = OVS_PACKET_CMD_MISS;
upcall.userdata = NULL;
upcall.portid = ovs_vport_find_upcall_portid(p, skb);
+ upcall.egress_tun_info = NULL;
ovs_dp_upcall(dp, skb, &upcall);
consume_skb(skb);
stats_counter = &stats->n_missed;
return err;
}
-static size_t upcall_msg_size(const struct nlattr *userdata,
+static size_t upcall_msg_size(const struct dp_upcall_info *upcall_info,
unsigned int hdrlen)
{
size_t size = NLMSG_ALIGN(sizeof(struct ovs_header))
+ nla_total_size(ovs_key_attr_size()); /* OVS_PACKET_ATTR_KEY */
/* OVS_PACKET_ATTR_USERDATA */
- if (userdata)
- size += NLA_ALIGN(userdata->nla_len);
+ if (upcall_info->userdata)
+ size += NLA_ALIGN(upcall_info->userdata->nla_len);
+
+ /* OVS_PACKET_ATTR_EGRESS_TUN_KEY */
+ if (upcall_info->egress_tun_info)
+ size += nla_total_size(ovs_tun_key_attr_size());
return size;
}
else
hlen = skb->len;
- len = upcall_msg_size(upcall_info->userdata, hlen);
+ len = upcall_msg_size(upcall_info, hlen);
user_skb = genlmsg_new_unicast(len, &info, GFP_ATOMIC);
if (!user_skb) {
err = -ENOMEM;
nla_len(upcall_info->userdata),
nla_data(upcall_info->userdata));
+ if (upcall_info->egress_tun_info) {
+ nla = nla_nest_start(user_skb, OVS_PACKET_ATTR_EGRESS_TUN_KEY);
+ err = ovs_nla_put_egress_tunnel_key(user_skb,
+ upcall_info->egress_tun_info);
+ BUG_ON(err);
+ nla_nest_end(user_skb, nla);
+ }
+
/* Only reserve room for attribute header, packet data is added
* in skb_zerocopy() */
if (!(nla = nla_reserve(user_skb, OVS_PACKET_ATTR_PACKET, 0))) {
* @portid: Netlink PID to which packet should be sent. If @portid is 0 then no
* packet is sent and the packet is accounted in the datapath's @n_lost
* counter.
+ * @egress_tun_info: If nonnull, becomes %OVS_PACKET_ATTR_EGRESS_TUN_KEY.
*/
struct dp_upcall_info {
const struct nlattr *userdata;
u32 portid;
u8 cmd;
+ const struct ovs_tunnel_info *egress_tun_info;
};
/**
/* Used to memset ovs_key_ipv4_tunnel padding. */
#define OVS_TUNNEL_KEY_SIZE \
- (offsetof(struct ovs_key_ipv4_tunnel, ipv4_ttl) + \
- FIELD_SIZEOF(struct ovs_key_ipv4_tunnel, ipv4_ttl))
+ (offsetof(struct ovs_key_ipv4_tunnel, tp_dst) + \
+ FIELD_SIZEOF(struct ovs_key_ipv4_tunnel, tp_dst))
struct ovs_key_ipv4_tunnel {
__be64 tun_id;
__be16 tun_flags;
u8 ipv4_tos;
u8 ipv4_ttl;
+ __be16 tp_src;
+ __be16 tp_dst;
} __packed __aligned(4); /* Minimize padding. */
struct ovs_tunnel_info {
FIELD_SIZEOF(struct sw_flow_key, tun_opts) - \
opt_len)
-static inline void ovs_flow_tun_info_init(struct ovs_tunnel_info *tun_info,
- const struct iphdr *iph, __be64 tun_id,
- __be16 tun_flags,
- struct geneve_opt *opts,
- u8 opts_len)
+static inline void __ovs_flow_tun_info_init(struct ovs_tunnel_info *tun_info,
+ __be32 saddr, __be32 daddr,
+ u8 tos, u8 ttl,
+ __be16 tp_src,
+ __be16 tp_dst,
+ __be64 tun_id,
+ __be16 tun_flags,
+ struct geneve_opt *opts,
+ u8 opts_len)
{
tun_info->tunnel.tun_id = tun_id;
- tun_info->tunnel.ipv4_src = iph->saddr;
- tun_info->tunnel.ipv4_dst = iph->daddr;
- tun_info->tunnel.ipv4_tos = iph->tos;
- tun_info->tunnel.ipv4_ttl = iph->ttl;
+ tun_info->tunnel.ipv4_src = saddr;
+ tun_info->tunnel.ipv4_dst = daddr;
+ tun_info->tunnel.ipv4_tos = tos;
+ tun_info->tunnel.ipv4_ttl = ttl;
tun_info->tunnel.tun_flags = tun_flags;
- /* clear struct padding. */
- memset((unsigned char *) &tun_info->tunnel + OVS_TUNNEL_KEY_SIZE, 0,
- sizeof(tun_info->tunnel) - OVS_TUNNEL_KEY_SIZE);
+ /* For the tunnel types on the top of IPsec, the tp_src and tp_dst of
+ * the upper tunnel are used.
+ * E.g: GRE over IPSEC, the tp_src and tp_port are zero.
+ */
+ tun_info->tunnel.tp_src = tp_src;
+ tun_info->tunnel.tp_dst = tp_dst;
+
+ /* Clear struct padding. */
+ if (sizeof(tun_info->tunnel) != OVS_TUNNEL_KEY_SIZE)
+ memset((unsigned char *) &tun_info->tunnel +
+ OVS_TUNNEL_KEY_SIZE,
+ 0, sizeof(tun_info->tunnel) - OVS_TUNNEL_KEY_SIZE);
tun_info->options = opts;
tun_info->options_len = opts_len;
}
+static inline void ovs_flow_tun_info_init(struct ovs_tunnel_info *tun_info,
+ const struct iphdr *iph,
+ __be16 tp_src,
+ __be16 tp_dst,
+ __be64 tun_id,
+ __be16 tun_flags,
+ struct geneve_opt *opts,
+ u8 opts_len)
+{
+ __ovs_flow_tun_info_init(tun_info, iph->saddr, iph->daddr,
+ iph->tos, iph->ttl,
+ tp_src, tp_dst,
+ tun_id, tun_flags,
+ opts, opts_len);
+}
+
#define OVS_SW_FLOW_KEY_METADATA_SIZE \
(offsetof(struct sw_flow_key, recirc_id) + \
FIELD_SIZEOF(struct sw_flow_key, recirc_id))
-
struct sw_flow_key {
u8 tun_opts[255];
u8 tun_opts_len;
return true;
}
+size_t ovs_tun_key_attr_size(void)
+{
+ /* Whenever adding new OVS_TUNNEL_KEY_ FIELDS, we should consider
+ * updating this function. */
+ return nla_total_size(8) /* OVS_TUNNEL_KEY_ATTR_ID */
+ + nla_total_size(4) /* OVS_TUNNEL_KEY_ATTR_IPV4_SRC */
+ + nla_total_size(4) /* OVS_TUNNEL_KEY_ATTR_IPV4_DST */
+ + nla_total_size(1) /* OVS_TUNNEL_KEY_ATTR_TOS */
+ + nla_total_size(1) /* OVS_TUNNEL_KEY_ATTR_TTL */
+ + nla_total_size(0) /* OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT */
+ + nla_total_size(0) /* OVS_TUNNEL_KEY_ATTR_CSUM */
+ + nla_total_size(0) /* OVS_TUNNEL_KEY_ATTR_OAM */
+ + nla_total_size(256) /* OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS */
+ + nla_total_size(2) /* OVS_TUNNEL_KEY_ATTR_TP_SRC */
+ + nla_total_size(2); /* OVS_TUNNEL_KEY_ATTR_TP_DST */
+}
+
size_t ovs_key_attr_size(void)
{
/* Whenever adding new OVS_KEY_ FIELDS, we should consider
return nla_total_size(4) /* OVS_KEY_ATTR_PRIORITY */
+ nla_total_size(0) /* OVS_KEY_ATTR_TUNNEL */
- + nla_total_size(8) /* OVS_TUNNEL_KEY_ATTR_ID */
- + nla_total_size(4) /* OVS_TUNNEL_KEY_ATTR_IPV4_SRC */
- + nla_total_size(4) /* OVS_TUNNEL_KEY_ATTR_IPV4_DST */
- + nla_total_size(1) /* OVS_TUNNEL_KEY_ATTR_TOS */
- + nla_total_size(1) /* OVS_TUNNEL_KEY_ATTR_TTL */
- + nla_total_size(0) /* OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT */
- + nla_total_size(0) /* OVS_TUNNEL_KEY_ATTR_CSUM */
- + nla_total_size(0) /* OVS_TUNNEL_KEY_ATTR_OAM */
- + nla_total_size(256) /* OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS */
+ + ovs_tun_key_attr_size()
+ nla_total_size(4) /* OVS_KEY_ATTR_IN_PORT */
+ nla_total_size(4) /* OVS_KEY_ATTR_SKB_MARK */
+ nla_total_size(4) /* OVS_KEY_ATTR_DP_HASH */
[OVS_TUNNEL_KEY_ATTR_TTL] = 1,
[OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT] = 0,
[OVS_TUNNEL_KEY_ATTR_CSUM] = 0,
+ [OVS_TUNNEL_KEY_ATTR_TP_SRC] = sizeof(u16),
+ [OVS_TUNNEL_KEY_ATTR_TP_DST] = sizeof(u16),
[OVS_TUNNEL_KEY_ATTR_OAM] = 0,
[OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS] = -1,
};
case OVS_TUNNEL_KEY_ATTR_CSUM:
tun_flags |= TUNNEL_CSUM;
break;
+ case OVS_TUNNEL_KEY_ATTR_TP_SRC:
+ SW_FLOW_KEY_PUT(match, tun_key.tp_src,
+ nla_get_be16(a), is_mask);
+ break;
+ case OVS_TUNNEL_KEY_ATTR_TP_DST:
+ SW_FLOW_KEY_PUT(match, tun_key.tp_dst,
+ nla_get_be16(a), is_mask);
+ break;
case OVS_TUNNEL_KEY_ATTR_OAM:
tun_flags |= TUNNEL_OAM;
break;
return 0;
}
-static int ipv4_tun_to_nlattr(struct sk_buff *skb,
- const struct ovs_key_ipv4_tunnel *output,
- const struct geneve_opt *tun_opts,
- int swkey_tun_opts_len)
+static int __ipv4_tun_to_nlattr(struct sk_buff *skb,
+ const struct ovs_key_ipv4_tunnel *output,
+ const struct geneve_opt *tun_opts,
+ int swkey_tun_opts_len)
{
- struct nlattr *nla;
-
- nla = nla_nest_start(skb, OVS_KEY_ATTR_TUNNEL);
- if (!nla)
- return -EMSGSIZE;
-
if (output->tun_flags & TUNNEL_KEY &&
nla_put_be64(skb, OVS_TUNNEL_KEY_ATTR_ID, output->tun_id))
return -EMSGSIZE;
if ((output->tun_flags & TUNNEL_CSUM) &&
nla_put_flag(skb, OVS_TUNNEL_KEY_ATTR_CSUM))
return -EMSGSIZE;
+ if (output->tp_src &&
+ nla_put_be16(skb, OVS_TUNNEL_KEY_ATTR_TP_SRC, output->tp_src))
+ return -EMSGSIZE;
+ if (output->tp_dst &&
+ nla_put_be16(skb, OVS_TUNNEL_KEY_ATTR_TP_DST, output->tp_dst))
+ return -EMSGSIZE;
if ((output->tun_flags & TUNNEL_OAM) &&
nla_put_flag(skb, OVS_TUNNEL_KEY_ATTR_OAM))
return -EMSGSIZE;
swkey_tun_opts_len, tun_opts))
return -EMSGSIZE;
+ return 0;
+}
+
+
+static int ipv4_tun_to_nlattr(struct sk_buff *skb,
+ const struct ovs_key_ipv4_tunnel *output,
+ const struct geneve_opt *tun_opts,
+ int swkey_tun_opts_len)
+{
+ struct nlattr *nla;
+ int err;
+
+ nla = nla_nest_start(skb, OVS_KEY_ATTR_TUNNEL);
+ if (!nla)
+ return -EMSGSIZE;
+
+ err = __ipv4_tun_to_nlattr(skb, output, tun_opts, swkey_tun_opts_len);
+ if (err)
+ return err;
+
nla_nest_end(skb, nla);
return 0;
}
+int ovs_nla_put_egress_tunnel_key(struct sk_buff *skb,
+ const struct ovs_tunnel_info *egress_tun_info)
+{
+ return __ipv4_tun_to_nlattr(skb, &egress_tun_info->tunnel,
+ egress_tun_info->options,
+ egress_tun_info->options_len);
+}
static int metadata_from_nlattrs(struct sw_flow_match *match, u64 *attrs,
const struct nlattr **a, bool is_mask)
static const struct nla_policy userspace_policy[OVS_USERSPACE_ATTR_MAX + 1] = {
[OVS_USERSPACE_ATTR_PID] = {.type = NLA_U32 },
[OVS_USERSPACE_ATTR_USERDATA] = {.type = NLA_UNSPEC },
+ [OVS_USERSPACE_ATTR_EGRESS_TUN_PORT] = {.type = NLA_U32 },
};
struct nlattr *a[OVS_USERSPACE_ATTR_MAX + 1];
int error;
#include "flow.h"
+size_t ovs_tun_key_attr_size(void);
size_t ovs_key_attr_size(void);
void ovs_match_init(struct sw_flow_match *match,
int ovs_nla_get_match(struct sw_flow_match *match,
const struct nlattr *,
const struct nlattr *);
+int ovs_nla_put_egress_tunnel_key(struct sk_buff *,
+ const struct ovs_tunnel_info *);
int ovs_nla_copy_actions(const struct nlattr *attr,
const struct sw_flow_key *key,
* notification if the %OVS_ACTION_ATTR_USERSPACE action specified an
* %OVS_USERSPACE_ATTR_USERDATA attribute, with the same length and content
* specified there.
+ * @OVS_PACKET_ATTR_EGRESS_TUN_KEY: Present for an %OVS_PACKET_CMD_ACTION
+ * notification if the %OVS_ACTION_ATTR_USERSPACE action specified an
+ * %OVS_USERSPACE_ATTR_EGRESS_TUN_PORT attribute, which is sent only if the
+ * output port is actually a tunnel port. Contains the output tunnel key
+ * extracted from the packet as nested %OVS_TUNNEL_KEY_ATTR_* attributes.
*
* These attributes follow the &struct ovs_header within the Generic Netlink
* payload for %OVS_PACKET_* commands.
OVS_PACKET_ATTR_KEY, /* Nested OVS_KEY_ATTR_* attributes. */
OVS_PACKET_ATTR_ACTIONS, /* Nested OVS_ACTION_ATTR_* attributes. */
OVS_PACKET_ATTR_USERDATA, /* OVS_ACTION_ATTR_USERSPACE arg. */
+ OVS_PACKET_ATTR_EGRESS_TUN_KEY, /* Nested OVS_TUNNEL_KEY_ATTR_*
+ attributes. */
__OVS_PACKET_ATTR_MAX
};
OVS_TUNNEL_KEY_ATTR_CSUM, /* No argument. CSUM packet. */
OVS_TUNNEL_KEY_ATTR_OAM, /* No argument, OAM frame. */
OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS, /* Array of Geneve options */
+ OVS_TUNNEL_KEY_ATTR_TP_SRC, /* be16 src Transport Port. */
+ OVS_TUNNEL_KEY_ATTR_TP_DST, /* be16 dst Transport Port. */
__OVS_TUNNEL_KEY_ATTR_MAX
};
#define OVS_TUNNEL_KEY_ATTR_MAX (__OVS_TUNNEL_KEY_ATTR_MAX - 1)
* message should be sent. Required.
* @OVS_USERSPACE_ATTR_USERDATA: If present, its variable-length argument is
* copied to the %OVS_PACKET_CMD_ACTION message as %OVS_PACKET_ATTR_USERDATA.
+ * @OVS_USERSPACE_ATTR_EGRESS_TUN_PORT: If present, u32 output port to get
+ * tunnel info.
*/
enum ovs_userspace_attr {
OVS_USERSPACE_ATTR_UNSPEC,
OVS_USERSPACE_ATTR_PID, /* u32 Netlink PID to receive upcalls. */
OVS_USERSPACE_ATTR_USERDATA, /* Optional user-specified cookie. */
+ OVS_USERSPACE_ATTR_EGRESS_TUN_PORT, /* Optional, u32 output port
+ * to get tunnel info. */
__OVS_USERSPACE_ATTR_MAX
};
(geneveh->critical ? TUNNEL_CRIT_OPT : 0);
key = vni_to_tunnel_id(geneveh->vni);
- ovs_flow_tun_info_init(&tun_info, ip_hdr(skb), key, flags,
+ ovs_flow_tun_info_init(&tun_info, ip_hdr(skb),
+ udp_hdr(skb)->source, udp_hdr(skb)->dest,
+ key, flags,
geneveh->options, opts_len);
ovs_vport_receive(vport_from_priv(geneve_port), skb, &tun_info);
return geneve_port->name;
}
+static int geneve_get_egress_tun_info(struct vport *vport, struct sk_buff *skb,
+ struct ovs_tunnel_info *egress_tun_info)
+{
+ struct geneve_port *geneve_port = geneve_vport(vport);
+
+ /*
+ * Get tp_src and tp_dst, refert to geneve_build_header().
+ */
+ return ovs_tunnel_get_egress_info(egress_tun_info,
+ ovs_dp_get_net(vport->dp),
+ OVS_CB(skb)->egress_tun_info,
+ IPPROTO_UDP, skb->mark,
+ vxlan_src_port(1, USHRT_MAX, skb),
+ inet_sport(geneve_port->sock->sk));
+
+}
+
const struct vport_ops ovs_geneve_vport_ops = {
- .type = OVS_VPORT_TYPE_GENEVE,
- .create = geneve_tnl_create,
- .destroy = geneve_tnl_destroy,
- .get_name = geneve_get_name,
- .get_options = geneve_get_options,
- .send = geneve_send,
+ .type = OVS_VPORT_TYPE_GENEVE,
+ .create = geneve_tnl_create,
+ .destroy = geneve_tnl_destroy,
+ .get_name = geneve_get_name,
+ .get_options = geneve_get_options,
+ .send = geneve_send,
+ .get_egress_tun_info = geneve_get_egress_tun_info,
};
return PACKET_REJECT;
key = key_to_tunnel_id(tpi->key, tpi->seq);
- ovs_flow_tun_info_init(&tun_info, ip_hdr(skb), key,
+ ovs_flow_tun_info_init(&tun_info, ip_hdr(skb), 0, 0, key,
filter_tnl_flags(tpi->flags), NULL, 0);
ovs_vport_receive(vport, skb, &tun_info);
return __send(vport, skb, hlen, 0, 0);
}
+static int gre_get_egress_tun_info(struct vport *vport, struct sk_buff *skb,
+ struct ovs_tunnel_info *egress_tun_info)
+{
+ return ovs_tunnel_get_egress_info(egress_tun_info,
+ ovs_dp_get_net(vport->dp),
+ OVS_CB(skb)->egress_tun_info,
+ IPPROTO_GRE, skb->mark, 0, 0);
+}
+
const struct vport_ops ovs_gre_vport_ops = {
- .type = OVS_VPORT_TYPE_GRE,
- .create = gre_create,
- .destroy = gre_tnl_destroy,
- .get_name = gre_get_name,
- .send = gre_send,
+ .type = OVS_VPORT_TYPE_GRE,
+ .create = gre_create,
+ .destroy = gre_tnl_destroy,
+ .get_name = gre_get_name,
+ .send = gre_send,
+ .get_egress_tun_info = gre_get_egress_tun_info,
};
/* GRE64 vport. */
}
const struct vport_ops ovs_gre64_vport_ops = {
- .type = OVS_VPORT_TYPE_GRE64,
- .create = gre64_create,
- .destroy = gre64_tnl_destroy,
- .get_name = gre_get_name,
- .send = gre64_send,
+ .type = OVS_VPORT_TYPE_GRE64,
+ .create = gre64_create,
+ .destroy = gre64_tnl_destroy,
+ .get_name = gre_get_name,
+ .send = gre64_send,
+ .get_egress_tun_info = gre_get_egress_tun_info,
};
#endif
/* Save outer tunnel values */
iph = ip_hdr(skb);
- ovs_flow_tun_info_init(&tun_info, iph, key, TUNNEL_KEY, NULL, 0);
+ ovs_flow_tun_info_init(&tun_info, iph,
+ udp_hdr(skb)->source, udp_hdr(skb)->dest,
+ key, TUNNEL_KEY, NULL, 0);
/* Drop non-IP inner packets */
inner_iph = (struct iphdr *)(lisph + 1);
return lisp_port->name;
}
+static int lisp_get_egress_tun_info(struct vport *vport, struct sk_buff *skb,
+ struct ovs_tunnel_info *egress_tun_info)
+{
+ struct net *net = ovs_dp_get_net(vport->dp);
+ struct lisp_port *lisp_port = lisp_vport(vport);
+
+ if (skb->protocol != htons(ETH_P_IP) &&
+ skb->protocol != htons(ETH_P_IPV6)) {
+ return -EINVAL;
+ }
+
+ /*
+ * Get tp_src and tp_dst, refert to lisp_build_header().
+ */
+ return ovs_tunnel_get_egress_info(egress_tun_info, net,
+ OVS_CB(skb)->egress_tun_info,
+ IPPROTO_UDP, skb->mark,
+ htons(get_src_port(net, skb)),
+ lisp_port->dst_port);
+}
+
const struct vport_ops ovs_lisp_vport_ops = {
- .type = OVS_VPORT_TYPE_LISP,
- .create = lisp_tnl_create,
- .destroy = lisp_tnl_destroy,
- .get_name = lisp_get_name,
- .get_options = lisp_get_options,
- .send = lisp_send,
+ .type = OVS_VPORT_TYPE_LISP,
+ .create = lisp_tnl_create,
+ .destroy = lisp_tnl_destroy,
+ .get_name = lisp_get_name,
+ .get_options = lisp_get_options,
+ .send = lisp_send,
+ .get_egress_tun_info = lisp_get_egress_tun_info,
};
/* Save outer tunnel values */
iph = ip_hdr(skb);
key = cpu_to_be64(ntohl(vx_vni) >> 8);
- ovs_flow_tun_info_init(&tun_info, iph, key, TUNNEL_KEY, NULL, 0);
+ ovs_flow_tun_info_init(&tun_info, iph,
+ udp_hdr(skb)->source, udp_hdr(skb)->dest,
+ key, TUNNEL_KEY, NULL, 0);
ovs_vport_receive(vport, skb, &tun_info);
}
return err;
}
+static int vxlan_get_egress_tun_info(struct vport *vport, struct sk_buff *skb,
+ struct ovs_tunnel_info *egress_tun_info)
+{
+ struct net *net = ovs_dp_get_net(vport->dp);
+ struct vxlan_port *vxlan_port = vxlan_vport(vport);
+ __be16 dst_port = inet_sport(vxlan_port->vs->sock->sk);
+ __be16 src_port;
+ int port_min;
+ int port_max;
+
+ inet_get_local_port_range(net, &port_min, &port_max);
+ src_port = vxlan_src_port(port_min, port_max, skb);
+
+ return ovs_tunnel_get_egress_info(egress_tun_info, net,
+ OVS_CB(skb)->egress_tun_info,
+ IPPROTO_UDP, skb->mark,
+ src_port, dst_port);
+}
+
static const char *vxlan_get_name(const struct vport *vport)
{
struct vxlan_port *vxlan_port = vxlan_vport(vport);
}
const struct vport_ops ovs_vxlan_vport_ops = {
- .type = OVS_VPORT_TYPE_VXLAN,
- .create = vxlan_tnl_create,
- .destroy = vxlan_tnl_destroy,
- .get_name = vxlan_get_name,
- .get_options = vxlan_get_options,
- .send = vxlan_tnl_send,
+ .type = OVS_VPORT_TYPE_VXLAN,
+ .create = vxlan_tnl_create,
+ .destroy = vxlan_tnl_destroy,
+ .get_name = vxlan_get_name,
+ .get_options = vxlan_get_options,
+ .send = vxlan_tnl_send,
+ .get_egress_tun_info = vxlan_get_egress_tun_info,
};
call_rcu(&vport->rcu, free_vport_rcu);
}
+
+int ovs_tunnel_get_egress_info(struct ovs_tunnel_info *egress_tun_info,
+ struct net *net,
+ const struct ovs_tunnel_info *tun_info,
+ u8 ipproto,
+ u32 skb_mark,
+ __be16 tp_src,
+ __be16 tp_dst)
+{
+ const struct ovs_key_ipv4_tunnel *tun_key;
+ struct rtable *rt;
+ __be32 saddr;
+
+ if (unlikely(!tun_info))
+ return -EINVAL;
+
+ tun_key = &tun_info->tunnel;
+ saddr = tun_key->ipv4_src;
+ /* Route lookup to get srouce IP address: saddr.
+ * The process may need to be changed if the corresponding process
+ * in vports ops changed.
+ */
+ rt = find_route(net,
+ &saddr,
+ tun_key->ipv4_dst,
+ ipproto,
+ tun_key->ipv4_tos,
+ skb_mark);
+ if (IS_ERR(rt))
+ return PTR_ERR(rt);
+
+ ip_rt_put(rt);
+
+ /* Generate egress_tun_info based on tun_info,
+ * saddr, tp_src and tp_dst
+ */
+ __ovs_flow_tun_info_init(egress_tun_info,
+ saddr, tun_key->ipv4_dst,
+ tun_key->ipv4_tos,
+ tun_key->ipv4_ttl,
+ tp_src, tp_dst,
+ tun_key->tun_id,
+ tun_key->tun_flags,
+ tun_info->options,
+ tun_info->options_len);
+
+ return 0;
+}
+
+int ovs_vport_get_egress_tun_info(struct vport *vport, struct sk_buff *skb,
+ struct ovs_tunnel_info *info)
+{
+ /* get_egress_tun_info() is only implemented on tunnel ports. */
+ if (unlikely(!vport->ops->get_egress_tun_info))
+ return -EINVAL;
+
+ return vport->ops->get_egress_tun_info(vport, skb, info);
+}
int ovs_vport_send(struct vport *, struct sk_buff *);
+int ovs_tunnel_get_egress_info(struct ovs_tunnel_info *egress_tun_info,
+ struct net *net,
+ const struct ovs_tunnel_info *tun_info,
+ u8 ipproto,
+ u32 skb_mark,
+ __be16 tp_src,
+ __be16 tp_dst);
+int ovs_vport_get_egress_tun_info(struct vport *vport, struct sk_buff *skb,
+ struct ovs_tunnel_info *info);
+
/* The following definitions are for implementers of vport devices: */
struct vport_err_stats {
* @get_name: Get the device's name.
* @send: Send a packet on the device. Returns the length of the packet sent,
* zero for dropped packets or negative for error.
+ * @get_egress_tun_info: Get the egress tunnel 5-tuple and other info for a packet.
*/
struct vport_ops {
enum ovs_vport_type type;
const char *(*get_name)(const struct vport *);
int (*send)(struct vport *, struct sk_buff *);
+
+ int (*get_egress_tun_info)(struct vport *, struct sk_buff *,
+ struct ovs_tunnel_info *);
};
enum vport_err_type {
#define IPPROTO_UDP 17
#define IPPROTO_ROUTING 43
#define IPPROTO_FRAGMENT 44
+#define IPPROTO_GRE 47
#define IPPROTO_AH 51
#define IPPROTO_ICMPV6 58
#define IPPROTO_NONE 59
/* OVS_PACKET_CMD_ACTION only. */
[OVS_PACKET_ATTR_USERDATA] = { .type = NL_A_UNSPEC, .optional = true },
+ [OVS_PACKET_ATTR_EGRESS_TUN_KEY] = { .type = NL_A_NESTED, .optional = true },
};
struct ovs_header *ovs_header;
nl_attr_get(a[OVS_PACKET_ATTR_KEY]));
upcall->key_len = nl_attr_get_size(a[OVS_PACKET_ATTR_KEY]);
upcall->userdata = a[OVS_PACKET_ATTR_USERDATA];
+ upcall->out_tun_key = a[OVS_PACKET_ATTR_EGRESS_TUN_KEY];
/* Allow overwriting the netlink attribute header without reallocating. */
ofpbuf_use_stub(&upcall->packet,
/* DPIF_UC_ACTION only. */
struct nlattr *userdata; /* Argument to OVS_ACTION_ATTR_USERSPACE. */
+ struct nlattr *out_tun_key; /* Output tunnel key. */
};
/* A callback to process an upcall, currently implemented only by dpif-netdev.
[OVS_USERSPACE_ATTR_PID] = { .type = NL_A_U32 },
[OVS_USERSPACE_ATTR_USERDATA] = { .type = NL_A_UNSPEC,
.optional = true },
+ [OVS_USERSPACE_ATTR_EGRESS_TUN_PORT] = { .type = NL_A_U32,
+ .optional = true },
};
struct nlattr *a[ARRAY_SIZE(ovs_userspace_policy)];
const struct nlattr *userdata_attr;
+ const struct nlattr *tunnel_out_port_attr;
if (!nl_parse_nested(attr, ovs_userspace_policy, a, ARRAY_SIZE(a))) {
ds_put_cstr(ds, "userspace(error)");
cookie.flow_sample.obs_point_id);
} else if (userdata_len >= sizeof cookie.ipfix
&& cookie.type == USER_ACTION_COOKIE_IPFIX) {
- ds_put_format(ds, ",ipfix");
+ ds_put_format(ds, ",ipfix(output_port=%"PRIu32")",
+ cookie.ipfix.output_odp_port);
} else {
userdata_unspec = true;
}
}
}
+ tunnel_out_port_attr = a[OVS_USERSPACE_ATTR_EGRESS_TUN_PORT];
+ if (tunnel_out_port_attr) {
+ ds_put_format(ds, ",tunnel_out_port=%"PRIu32,
+ nl_attr_get_u32(tunnel_out_port_attr));
+ }
+
ds_put_char(ds, ')');
}
}
}
+/* Separate out parse_odp_userspace_action() function. */
static int
-parse_odp_action(const char *s, const struct simap *port_names,
- struct ofpbuf *actions)
+parse_odp_userspace_action(const char *s, struct ofpbuf *actions)
{
- {
- uint32_t port;
- int n;
+ uint32_t pid;
+ union user_action_cookie cookie;
+ struct ofpbuf buf;
+ odp_port_t tunnel_out_port;
+ int n = -1;
+ void *user_data = NULL;
+ size_t user_data_size = 0;
- if (ovs_scan(s, "%"SCNi32"%n", &port, &n)) {
- nl_msg_put_u32(actions, OVS_ACTION_ATTR_OUTPUT, port);
- return n;
- }
- }
-
- if (port_names) {
- int len = strcspn(s, delimiters);
- struct simap_node *node;
-
- node = simap_find_len(port_names, s, len);
- if (node) {
- nl_msg_put_u32(actions, OVS_ACTION_ATTR_OUTPUT, node->data);
- return len;
- }
+ if (!ovs_scan(s, "userspace(pid=%"SCNi32"%n", &pid, &n)) {
+ return -EINVAL;
}
{
- uint32_t pid;
uint32_t output;
uint32_t probability;
uint32_t collector_set_id;
uint32_t obs_domain_id;
uint32_t obs_point_id;
int vid, pcp;
- int n = -1;
-
- if (ovs_scan(s, "userspace(pid=%"SCNi32")%n", &pid, &n)) {
- odp_put_userspace_action(pid, NULL, 0, actions);
- return n;
- } else if (ovs_scan(s, "userspace(pid=%"SCNi32",sFlow(vid=%i,"
- "pcp=%i,output=%"SCNi32"))%n",
- &pid, &vid, &pcp, &output, &n)) {
- union user_action_cookie cookie;
+ int n1 = -1;
+ if (ovs_scan(&s[n], ",sFlow(vid=%i,"
+ "pcp=%i,output=%"SCNi32")%n",
+ &vid, &pcp, &output, &n1)) {
uint16_t tci;
+ n += n1;
tci = vid | (pcp << VLAN_PCP_SHIFT);
if (tci) {
tci |= VLAN_CFI;
cookie.type = USER_ACTION_COOKIE_SFLOW;
cookie.sflow.vlan_tci = htons(tci);
cookie.sflow.output = output;
- odp_put_userspace_action(pid, &cookie, sizeof cookie.sflow,
- actions);
- return n;
- } else if (ovs_scan(s, "userspace(pid=%"SCNi32",slow_path%n",
- &pid, &n)) {
- union user_action_cookie cookie;
+ user_data = &cookie;
+ user_data_size = sizeof cookie.sflow;
+ } else if (ovs_scan(&s[n], ",slow_path%n",
+ &n1)) {
int res;
+ n += n1;
cookie.type = USER_ACTION_COOKIE_SLOW_PATH;
cookie.slow_path.unused = 0;
cookie.slow_path.reason = 0;
return res;
}
n += res;
- if (s[n] != ')') {
- return -EINVAL;
- }
- n++;
- odp_put_userspace_action(pid, &cookie, sizeof cookie.slow_path,
- actions);
- return n;
- } else if (ovs_scan(s, "userspace(pid=%"SCNi32","
- "flow_sample(probability=%"SCNi32","
+ user_data = &cookie;
+ user_data_size = sizeof cookie.slow_path;
+ } else if (ovs_scan(&s[n], ",flow_sample(probability=%"SCNi32","
"collector_set_id=%"SCNi32","
"obs_domain_id=%"SCNi32","
- "obs_point_id=%"SCNi32"))%n",
- &pid, &probability, &collector_set_id,
- &obs_domain_id, &obs_point_id, &n)) {
- union user_action_cookie cookie;
+ "obs_point_id=%"SCNi32")%n",
+ &probability, &collector_set_id,
+ &obs_domain_id, &obs_point_id, &n1)) {
+ n += n1;
cookie.type = USER_ACTION_COOKIE_FLOW_SAMPLE;
cookie.flow_sample.probability = probability;
cookie.flow_sample.collector_set_id = collector_set_id;
cookie.flow_sample.obs_domain_id = obs_domain_id;
cookie.flow_sample.obs_point_id = obs_point_id;
- odp_put_userspace_action(pid, &cookie, sizeof cookie.flow_sample,
- actions);
- return n;
- } else if (ovs_scan(s, "userspace(pid=%"SCNi32",ipfix)%n", &pid, &n)) {
- union user_action_cookie cookie;
-
+ user_data = &cookie;
+ user_data_size = sizeof cookie.flow_sample;
+ } else if (ovs_scan(&s[n], ",ipfix(output_port=%"SCNi32")%n",
+ &output, &n1) ) {
+ n += n1;
cookie.type = USER_ACTION_COOKIE_IPFIX;
- odp_put_userspace_action(pid, &cookie, sizeof cookie.ipfix,
- actions);
- return n;
- } else if (ovs_scan(s, "userspace(pid=%"SCNi32",userdata(%n",
- &pid, &n)) {
- struct ofpbuf buf;
+ cookie.ipfix.output_odp_port = u32_to_odp(output);
+ user_data = &cookie;
+ user_data_size = sizeof cookie.ipfix;
+ } else if (ovs_scan(&s[n], ",userdata(%n",
+ &n1)) {
char *end;
+ n += n1;
ofpbuf_init(&buf, 16);
end = ofpbuf_put_hex(&buf, &s[n], NULL);
- if (end[0] == ')' && end[1] == ')') {
- odp_put_userspace_action(pid, ofpbuf_data(&buf), ofpbuf_size(&buf), actions);
- ofpbuf_uninit(&buf);
- return (end + 2) - s;
+ if (end[0] != ')') {
+ return -EINVAL;
}
+ user_data = ofpbuf_data(&buf);
+ user_data_size = ofpbuf_size(&buf);
+ n = (end + 1) - s;
}
}
+ {
+ int n1 = -1;
+ if (ovs_scan(&s[n], ",tunnel_out_port=%"SCNi32")%n",
+ &tunnel_out_port, &n1)) {
+ odp_put_userspace_action(pid, user_data, user_data_size, tunnel_out_port, actions);
+ return n + n1;
+ } else if (s[n] == ')') {
+ odp_put_userspace_action(pid, user_data, user_data_size, ODPP_NONE, actions);
+ return n + 1;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int
+parse_odp_action(const char *s, const struct simap *port_names,
+ struct ofpbuf *actions)
+{
+ {
+ uint32_t port;
+ int n;
+
+ if (ovs_scan(s, "%"SCNi32"%n", &port, &n)) {
+ nl_msg_put_u32(actions, OVS_ACTION_ATTR_OUTPUT, port);
+ return n;
+ }
+ }
+
+ if (port_names) {
+ int len = strcspn(s, delimiters);
+ struct simap_node *node;
+
+ node = simap_find_len(port_names, s, len);
+ if (node) {
+ nl_msg_put_u32(actions, OVS_ACTION_ATTR_OUTPUT, node->data);
+ return len;
+ }
+ }
+
+ if (!strncmp(s, "userspace(", 10)) {
+ return parse_odp_userspace_action(s, actions);
+ }
+
if (!strncmp(s, "set(", 4)) {
size_t start_ofs;
int retval;
case OVS_TUNNEL_KEY_ATTR_TTL: return 1;
case OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT: return 0;
case OVS_TUNNEL_KEY_ATTR_CSUM: return 0;
+ case OVS_TUNNEL_KEY_ATTR_TP_SRC: return 2;
+ case OVS_TUNNEL_KEY_ATTR_TP_DST: return 2;
case OVS_TUNNEL_KEY_ATTR_OAM: return 0;
case OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS: return -2;
case __OVS_TUNNEL_KEY_ATTR_MAX:
case OVS_TUNNEL_KEY_ATTR_CSUM:
tun->flags |= FLOW_TNL_F_CSUM;
break;
+ case OVS_TUNNEL_KEY_ATTR_TP_SRC:
+ tun->tp_src = nl_attr_get_be16(a);
+ break;
+ case OVS_TUNNEL_KEY_ATTR_TP_DST:
+ tun->tp_dst = nl_attr_get_be16(a);
+ break;
case OVS_TUNNEL_KEY_ATTR_OAM:
tun->flags |= FLOW_TNL_F_OAM;
break;
if (tun_key->flags & FLOW_TNL_F_CSUM) {
nl_msg_put_flag(a, OVS_TUNNEL_KEY_ATTR_CSUM);
}
+ if (tun_key->tp_src) {
+ nl_msg_put_be16(a, OVS_TUNNEL_KEY_ATTR_TP_SRC, tun_key->tp_src);
+ }
+ if (tun_key->tp_dst) {
+ nl_msg_put_be16(a, OVS_TUNNEL_KEY_ATTR_TP_DST, tun_key->tp_dst);
+ }
if (tun_key->flags & FLOW_TNL_F_OAM) {
nl_msg_put_flag(a, OVS_TUNNEL_KEY_ATTR_OAM);
}
size_t
odp_put_userspace_action(uint32_t pid,
const void *userdata, size_t userdata_size,
+ odp_port_t tunnel_out_port,
struct ofpbuf *odp_actions)
{
size_t userdata_ofs;
} else {
userdata_ofs = 0;
}
+ if (tunnel_out_port != ODPP_NONE) {
+ nl_msg_put_odp_port(odp_actions, OVS_USERSPACE_ATTR_EGRESS_TUN_PORT,
+ tunnel_out_port);
+ }
nl_msg_end_nested(odp_actions, offset);
return userdata_ofs;
} flow_sample;
struct {
- uint16_t type; /* USER_ACTION_COOKIE_IPFIX. */
+ uint16_t type; /* USER_ACTION_COOKIE_IPFIX. */
+ odp_port_t output_odp_port; /* The output odp port. */
} ipfix;
};
BUILD_ASSERT_DECL(sizeof(union user_action_cookie) == 16);
size_t odp_put_userspace_action(uint32_t pid,
const void *userdata, size_t userdata_size,
+ odp_port_t tunnel_out_port,
struct ofpbuf *odp_actions);
void odp_put_tunnel_action(const struct flow_tnl *tunnel,
struct ofpbuf *odp_actions);
uint16_t flags;
uint8_t ip_tos;
uint8_t ip_ttl;
+ ovs_be16 tp_src;
+ ovs_be16 tp_dst;
};
/* Unfortunately, a "struct flow" sometimes has to handle OpenFlow port
};
#define PKT_METADATA_INITIALIZER(PORT) \
- (struct pkt_metadata){ 0, 0, { 0, 0, 0, 0, 0, 0}, 0, 0, {(PORT)} }
+ (struct pkt_metadata){ .in_port.odp_port = PORT }
bool dpid_from_string(const char *s, uint64_t *dpidp);
ofproto/ipfix-entities.def: ofproto/ipfix.xml ofproto/ipfix-gen-entities
$(run_python) $(srcdir)/ofproto/ipfix-gen-entities $< > $@.tmp
mv $@.tmp $@
+
+# IPFIX enterprise entity definition macros.
+EXTRA_DIST += ofproto/ipfix-enterprise-entities.def
--- /dev/null
+/* IPFIX enterprise entities. */
+#ifndef IPFIX_ENTERPRISE_ENTITY
+#define IPFIX_ENTERPRISE_ENTITY(ENUM, ID, SIZE, NAME, ENTERPRISE)
+#endif
+
+#define IPFIX_ENTERPRISE_VMWARE 6876
+
+IPFIX_ENTERPRISE_ENTITY(TUNNEL_TYPE, 891, 1, tunnelType, IPFIX_ENTERPRISE_VMWARE)
+IPFIX_ENTERPRISE_ENTITY(TUNNEL_KEY, 892, 0, tunnelKey, IPFIX_ENTERPRISE_VMWARE)
+IPFIX_ENTERPRISE_ENTITY(TUNNEL_SOURCE_IPV4_ADDRESS, 893, 4, tunnelSourceIPv4Address, IPFIX_ENTERPRISE_VMWARE)
+IPFIX_ENTERPRISE_ENTITY(TUNNEL_DESTINATION_IPV4_ADDRESS, 894, 4, tunnelDestinationIPv4Address, IPFIX_ENTERPRISE_VMWARE)
+IPFIX_ENTERPRISE_ENTITY(TUNNEL_PROTOCOL_IDENTIFIER, 895, 1, tunnelProtocolIdentifier, IPFIX_ENTERPRISE_VMWARE)
+IPFIX_ENTERPRISE_ENTITY(TUNNEL_SOURCE_TRANSPORT_PORT, 896, 2, tunnelSourceTransportPort, IPFIX_ENTERPRISE_VMWARE)
+IPFIX_ENTERPRISE_ENTITY(TUNNEL_DESTINATION_TRANSPORT_PORT, 897, 2, tunnelDestinationTransportPort, IPFIX_ENTERPRISE_VMWARE)
+
+#undef IPFIX_ENTERPRISE_ENTITY
#include "list.h"
#include "ofpbuf.h"
#include "ofproto.h"
+#include "ofproto-dpif.h"
#include "packets.h"
#include "poll-loop.h"
#include "sset.h"
/* Cf. IETF RFC 5101 Section 10.3.4. */
#define IPFIX_DEFAULT_COLLECTOR_PORT 4739
+/* The standard layer2SegmentId (ID 351) element is included in vDS to send
+ * the VxLAN tunnel's VNI. It is 64-bit long, the most significant byte is
+ * used to indicate the type of tunnel (0x01 = VxLAN, 0x02 = GRE) and the three
+ * least significant bytes hold the value of the layer 2 overlay network
+ * segment identifier: a 24-bit VxLAN tunnel's VNI or a 24-bit GRE tunnel's
+ * TNI. This is not compatible with GRE-64, as implemented in OVS, as its
+ * tunnel IDs are 64-bit.
+ *
+ * Two new enterprise information elements are defined which are similar to
+ * laryerSegmentId but support 64-bit IDs:
+ * tunnelType (ID 891) and tunnelKey (ID 892).
+ *
+ * The enum dpif_ipfix_tunnel_type is to declare the types supported in the
+ * tunnelType element.
+ * The number of ipfix tunnel types includes two reserverd types: 0x04 and 0x06.
+ */
+enum dpif_ipfix_tunnel_type {
+ DPIF_IPFIX_TUNNEL_UNKNOWN = 0x00,
+ DPIF_IPFIX_TUNNEL_VXLAN = 0x01,
+ DPIF_IPFIX_TUNNEL_GRE = 0x02,
+ DPIF_IPFIX_TUNNEL_LISP = 0x03,
+ DPIF_IPFIX_TUNNEL_IPSEC_GRE = 0x05,
+ DPIF_IPFIX_TUNNEL_GENEVE = 0x07,
+ NUM_DPIF_IPFIX_TUNNEL
+};
+
+struct dpif_ipfix_port {
+ struct hmap_node hmap_node; /* In struct dpif_ipfix's "tunnel_ports" hmap. */
+ struct ofport *ofport; /* To retrieve port stats. */
+ odp_port_t odp_port;
+ enum dpif_ipfix_tunnel_type tunnel_type;
+ uint8_t tunnel_key_length;
+};
+
struct dpif_ipfix_exporter {
struct collectors *collectors;
uint32_t seq_number;
struct dpif_ipfix {
struct dpif_ipfix_bridge_exporter bridge_exporter;
struct hmap flow_exporter_map; /* dpif_ipfix_flow_exporter_map_node. */
+ struct hmap tunnel_ports; /* Contains "struct dpif_ipfix_port"s.
+ * It makes tunnel port lookups faster in
+ * sampling upcalls. */
struct ovs_refcount ref_cnt;
};
IPFIX_PROTO_L4_ICMP,
NUM_IPFIX_PROTO_L4
};
+enum ipfix_proto_tunnel {
+ IPFIX_PROTO_NOT_TUNNELED = 0,
+ IPFIX_PROTO_TUNNELED, /* Support gre, lisp and vxlan. */
+ NUM_IPFIX_PROTO_TUNNEL
+};
/* Any Template ID > 255 is usable for Template Records. */
#define IPFIX_TEMPLATE_ID_MIN 256
BUILD_ASSERT_DECL(sizeof(struct ipfix_template_record_header) == 4);
enum ipfix_entity_id {
+/* standard IPFIX elements */
#define IPFIX_ENTITY(ENUM, ID, SIZE, NAME) IPFIX_ENTITY_ID_##ENUM = ID,
#include "ofproto/ipfix-entities.def"
+/* non-standard IPFIX elements */
+#define IPFIX_SET_ENTERPRISE(v) (((v) | 0x8000))
+#define IPFIX_ENTERPRISE_ENTITY(ENUM, ID, SIZE, NAME, ENTERPRISE) \
+ IPFIX_ENTITY_ID_##ENUM = IPFIX_SET_ENTERPRISE(ID),
+#include "ofproto/ipfix-enterprise-entities.def"
};
enum ipfix_entity_size {
+/* standard IPFIX elements */
#define IPFIX_ENTITY(ENUM, ID, SIZE, NAME) IPFIX_ENTITY_SIZE_##ENUM = SIZE,
#include "ofproto/ipfix-entities.def"
+/* non-standard IPFIX elements */
+#define IPFIX_ENTERPRISE_ENTITY(ENUM, ID, SIZE, NAME, ENTERPRISE) \
+ IPFIX_ENTITY_SIZE_##ENUM = SIZE,
+#include "ofproto/ipfix-enterprise-entities.def"
+};
+
+enum ipfix_entity_enterprise {
+/* standard IPFIX elements */
+#define IPFIX_ENTITY(ENUM, ID, SIZE, NAME) IPFIX_ENTITY_ENTERPRISE_##ENUM = 0,
+#include "ofproto/ipfix-entities.def"
+/* non-standard IPFIX elements */
+#define IPFIX_ENTERPRISE_ENTITY(ENUM, ID, SIZE, NAME, ENTERPRISE) \
+ IPFIX_ENTITY_ENTERPRISE_##ENUM = ENTERPRISE,
+#include "ofproto/ipfix-enterprise-entities.def"
};
OVS_PACKED(
struct ipfix_template_field_specifier {
ovs_be16 element_id; /* IPFIX_ENTITY_ID_*. */
- ovs_be16 field_length; /* Length of the field's value, in bytes. */
- /* No Enterprise ID, since only standard element IDs are specified. */
+ ovs_be16 field_length; /* Length of the field's value, in bytes.
+ * For Variable-Length element, it should be 65535.
+ */
+ ovs_be32 enterprise; /* Enterprise number */
});
-BUILD_ASSERT_DECL(sizeof(struct ipfix_template_field_specifier) == 4);
+BUILD_ASSERT_DECL(sizeof(struct ipfix_template_field_specifier) == 8);
+
+/* Cf. IETF RFC 5102 Section 5.11.6. */
+enum ipfix_flow_direction {
+ INGRESS_FLOW = 0x00,
+ EGRESS_FLOW = 0x01
+};
/* Part of data record flow key for common metadata and Ethernet entities. */
OVS_PACKED(
struct ipfix_data_record_flow_key_common {
ovs_be32 observation_point_id; /* OBSERVATION_POINT_ID */
+ uint8_t flow_direction; /* FLOW_DIRECTION */
uint8_t source_mac_address[6]; /* SOURCE_MAC_ADDRESS */
uint8_t destination_mac_address[6]; /* DESTINATION_MAC_ADDRESS */
ovs_be16 ethernet_type; /* ETHERNET_TYPE */
uint8_t ethernet_header_length; /* ETHERNET_HEADER_LENGTH */
});
-BUILD_ASSERT_DECL(sizeof(struct ipfix_data_record_flow_key_common) == 19);
+BUILD_ASSERT_DECL(sizeof(struct ipfix_data_record_flow_key_common) == 20);
/* Part of data record flow key for VLAN entities. */
OVS_PACKED(
});
BUILD_ASSERT_DECL(sizeof(struct ipfix_data_record_flow_key_icmp) == 2);
+/* For the tunnel type that is on the top of IPSec, the protocol identifier
+ * of the upper tunnel type is used.
+ */
+static uint8_t tunnel_protocol[NUM_DPIF_IPFIX_TUNNEL] = {
+ 0, /* reserved */
+ IPPROTO_UDP, /* DPIF_IPFIX_TUNNEL_VXLAN */
+ IPPROTO_GRE, /* DPIF_IPFIX_TUNNEL_GRE */
+ IPPROTO_UDP, /* DPIF_IPFIX_TUNNEL_LISP*/
+ 0 , /* reserved */
+ IPPROTO_GRE, /* DPIF_IPFIX_TUNNEL_IPSEC_GRE */
+ 0 , /* reserved */
+ IPPROTO_UDP, /* DPIF_IPFIX_TUNNEL_GENEVE*/
+};
+
+OVS_PACKED(
+struct ipfix_data_record_flow_key_tunnel {
+ ovs_be32 tunnel_source_ipv4_address; /* TUNNEL_SOURCE_IPV4_ADDRESS */
+ ovs_be32 tunnel_destination_ipv4_address; /* TUNNEL_DESTINATION_IPV4_ADDRESS */
+ uint8_t tunnel_protocol_identifier; /* TUNNEL_PROTOCOL_IDENTIFIER */
+ ovs_be16 tunnel_source_transport_port; /* TUNNEL_SOURCE_TRANSPORT_PORT */
+ ovs_be16 tunnel_destination_transport_port; /* TUNNEL_DESTINATION_TRANSPORT_PORT */
+ uint8_t tunnel_type; /* TUNNEL_TYPE */
+ uint8_t tunnel_key_length; /* length of TUNNEL_KEY */
+ uint8_t tunnel_key[]; /* data of TUNNEL_KEY */
+});
+BUILD_ASSERT_DECL(sizeof(struct ipfix_data_record_flow_key_tunnel) == 15);
+
/* Cf. IETF RFC 5102 Section 5.11.3. */
enum ipfix_flow_end_reason {
IDLE_TIMEOUT = 0x01,
});
BUILD_ASSERT_DECL(sizeof(struct ipfix_data_record_aggregated_ip) == 32);
+/*
+ * support tunnel key for:
+ * VxLAN: 24-bit VIN,
+ * GRE: 32- or 64-bit key,
+ * LISP: 24-bit instance ID
+ */
+#define MAX_TUNNEL_KEY_LEN 8
+
#define MAX_FLOW_KEY_LEN \
(sizeof(struct ipfix_data_record_flow_key_common) \
+ sizeof(struct ipfix_data_record_flow_key_vlan) \
+ MAX(sizeof(struct ipfix_data_record_flow_key_ipv4), \
sizeof(struct ipfix_data_record_flow_key_ipv6)) \
+ MAX(sizeof(struct ipfix_data_record_flow_key_icmp), \
- sizeof(struct ipfix_data_record_flow_key_transport)))
+ sizeof(struct ipfix_data_record_flow_key_transport)) \
+ + sizeof(struct ipfix_data_record_flow_key_tunnel) \
+ + MAX_TUNNEL_KEY_LEN)
#define MAX_DATA_RECORD_LEN \
(MAX_FLOW_KEY_LEN \
(sizeof(struct ipfix_set_header) \
+ MAX_DATA_RECORD_LEN)
-/* Max length of an IPFIX message. Arbitrarily set to accomodate low
+/* Max length of an IPFIX message. Arbitrarily set to accommodate low
* MTU. */
#define MAX_MESSAGE_LEN 1024
&& a->sampling_rate == b->sampling_rate
&& a->cache_active_timeout == b->cache_active_timeout
&& a->cache_max_flows == b->cache_max_flows
+ && a->enable_tunnel_sampling == b->enable_tunnel_sampling
+ && a->enable_input_sampling == b->enable_input_sampling
+ && a->enable_output_sampling == b->enable_output_sampling
&& sset_equals(&a->targets, &b->targets));
}
return true;
}
+static struct dpif_ipfix_port *
+dpif_ipfix_find_port(const struct dpif_ipfix *di,
+ odp_port_t odp_port) OVS_REQUIRES(mutex)
+{
+ struct dpif_ipfix_port *dip;
+
+ HMAP_FOR_EACH_IN_BUCKET (dip, hmap_node, hash_odp_port(odp_port),
+ &di->tunnel_ports) {
+ if (dip->odp_port == odp_port) {
+ return dip;
+ }
+ }
+ return NULL;
+}
+
+static void
+dpif_ipfix_del_port(struct dpif_ipfix *di,
+ struct dpif_ipfix_port *dip)
+ OVS_REQUIRES(mutex)
+{
+ hmap_remove(&di->tunnel_ports, &dip->hmap_node);
+ free(dip);
+}
+
+void
+dpif_ipfix_add_tunnel_port(struct dpif_ipfix *di, struct ofport *ofport,
+ odp_port_t odp_port) OVS_EXCLUDED(mutex)
+{
+ struct dpif_ipfix_port *dip;
+ const char *type;
+
+ ovs_mutex_lock(&mutex);
+ dip = dpif_ipfix_find_port(di, odp_port);
+ if (dip) {
+ dpif_ipfix_del_port(di, dip);
+ }
+
+ type = netdev_get_type(ofport->netdev);
+ if (type == NULL) {
+ goto out;
+ }
+
+ /* Add to table of tunnel ports. */
+ dip = xmalloc(sizeof *dip);
+ dip->ofport = ofport;
+ dip->odp_port = odp_port;
+ if (strcmp(type, "gre") == 0) {
+ /* 32-bit key gre */
+ dip->tunnel_type = DPIF_IPFIX_TUNNEL_GRE;
+ dip->tunnel_key_length = 4;
+ } else if (strcmp(type, "gre64") == 0) {
+ /* 64-bit key gre */
+ dip->tunnel_type = DPIF_IPFIX_TUNNEL_GRE;
+ dip->tunnel_key_length = 8;
+ } else if (strcmp(type, "ipsec_gre") == 0) {
+ /* 32-bit key ipsec_gre */
+ dip->tunnel_type = DPIF_IPFIX_TUNNEL_IPSEC_GRE;
+ dip->tunnel_key_length = 4;
+ } else if (strcmp(type, "ipsec_gre64") == 0) {
+ /* 64-bit key ipsec_gre */
+ dip->tunnel_type = DPIF_IPFIX_TUNNEL_IPSEC_GRE;
+ dip->tunnel_key_length = 8;
+ } else if (strcmp(type, "vxlan") == 0) {
+ dip->tunnel_type = DPIF_IPFIX_TUNNEL_VXLAN;
+ dip->tunnel_key_length = 3;
+ } else if (strcmp(type, "lisp") == 0) {
+ dip->tunnel_type = DPIF_IPFIX_TUNNEL_LISP;
+ dip->tunnel_key_length = 3;
+ } else if (strcmp(type, "geneve") == 0) {
+ dip->tunnel_type = DPIF_IPFIX_TUNNEL_GENEVE;
+ dip->tunnel_key_length = 3;
+ } else {
+ free(dip);
+ goto out;
+ }
+ hmap_insert(&di->tunnel_ports, &dip->hmap_node, hash_odp_port(odp_port));
+
+out:
+ ovs_mutex_unlock(&mutex);
+}
+
+void
+dpif_ipfix_del_tunnel_port(struct dpif_ipfix *di, odp_port_t odp_port)
+ OVS_EXCLUDED(mutex)
+{
+ struct dpif_ipfix_port *dip;
+ ovs_mutex_lock(&mutex);
+ dip = dpif_ipfix_find_port(di, odp_port);
+ if (dip) {
+ dpif_ipfix_del_port(di, dip);
+ }
+ ovs_mutex_unlock(&mutex);
+}
+
+bool
+dpif_ipfix_get_tunnel_port(const struct dpif_ipfix *di, odp_port_t odp_port)
+ OVS_EXCLUDED(mutex)
+{
+ struct dpif_ipfix_port *dip;
+ ovs_mutex_lock(&mutex);
+ dip = dpif_ipfix_find_port(di, odp_port);
+ ovs_mutex_unlock(&mutex);
+ return dip != NULL;
+}
+
static void
dpif_ipfix_bridge_exporter_init(struct dpif_ipfix_bridge_exporter *exporter)
{
di = xzalloc(sizeof *di);
dpif_ipfix_bridge_exporter_init(&di->bridge_exporter);
hmap_init(&di->flow_exporter_map);
+ hmap_init(&di->tunnel_ports);
ovs_refcount_init(&di->ref_cnt);
return di;
}
return ret;
}
+bool
+dpif_ipfix_get_bridge_exporter_input_sampling(const struct dpif_ipfix *di)
+ OVS_EXCLUDED(mutex)
+{
+ bool ret = true;
+ ovs_mutex_lock(&mutex);
+ if (di->bridge_exporter.options) {
+ ret = di->bridge_exporter.options->enable_input_sampling;
+ }
+ ovs_mutex_unlock(&mutex);
+ return ret;
+}
+
+bool
+dpif_ipfix_get_bridge_exporter_output_sampling(const struct dpif_ipfix *di)
+ OVS_EXCLUDED(mutex)
+{
+ bool ret = true;
+ ovs_mutex_lock(&mutex);
+ if (di->bridge_exporter.options) {
+ ret = di->bridge_exporter.options->enable_output_sampling;
+ }
+ ovs_mutex_unlock(&mutex);
+ return ret;
+}
+
+bool
+dpif_ipfix_get_bridge_exporter_tunnel_sampling(const struct dpif_ipfix *di)
+ OVS_EXCLUDED(mutex)
+{
+ bool ret = false;
+ ovs_mutex_lock(&mutex);
+ if (di->bridge_exporter.options) {
+ ret = di->bridge_exporter.options->enable_tunnel_sampling;
+ }
+ ovs_mutex_unlock(&mutex);
+ return ret;
+}
+
static void
dpif_ipfix_clear(struct dpif_ipfix *di) OVS_REQUIRES(mutex)
{
struct dpif_ipfix_flow_exporter_map_node *exp_node, *exp_next;
+ struct dpif_ipfix_port *dip, *next;
dpif_ipfix_bridge_exporter_clear(&di->bridge_exporter);
dpif_ipfix_flow_exporter_destroy(&exp_node->exporter);
free(exp_node);
}
+
+ HMAP_FOR_EACH_SAFE (dip, next, hmap_node, &di->tunnel_ports) {
+ dpif_ipfix_del_port(di, dip);
+ }
}
void
dpif_ipfix_clear(di);
dpif_ipfix_bridge_exporter_destroy(&di->bridge_exporter);
hmap_destroy(&di->flow_exporter_map);
+ hmap_destroy(&di->tunnel_ports);
free(di);
ovs_mutex_unlock(&mutex);
}
static uint16_t
ipfix_get_template_id(enum ipfix_proto_l2 l2, enum ipfix_proto_l3 l3,
- enum ipfix_proto_l4 l4)
+ enum ipfix_proto_l4 l4, enum ipfix_proto_tunnel tunnel)
{
uint16_t template_id;
template_id = l2;
template_id = template_id * NUM_IPFIX_PROTO_L3 + l3;
template_id = template_id * NUM_IPFIX_PROTO_L4 + l4;
+ template_id = template_id * NUM_IPFIX_PROTO_TUNNEL + tunnel;
return IPFIX_TEMPLATE_ID_MIN + template_id;
}
static void
ipfix_define_template_entity(enum ipfix_entity_id id,
- enum ipfix_entity_size size, struct ofpbuf *msg)
+ enum ipfix_entity_size size,
+ enum ipfix_entity_enterprise enterprise,
+ struct ofpbuf *msg)
{
struct ipfix_template_field_specifier *field;
+ size_t field_size;
- field = ofpbuf_put_zeros(msg, sizeof *field);
+ if (enterprise) {
+ field_size = sizeof *field;
+ } else {
+ /* No enterprise number */
+ field_size = sizeof *field - sizeof(ovs_be32);
+ }
+ field = ofpbuf_put_zeros(msg, field_size);
field->element_id = htons(id);
- field->field_length = htons(size);
+ if (size) {
+ field->field_length = htons(size);
+ } else {
+ /* RFC 5101, Section 7. Variable-Length Information Element */
+ field->field_length = OVS_BE16_MAX;
+ }
+ if (enterprise) {
+ field->enterprise = htonl(enterprise);
+ }
+
}
static uint16_t
ipfix_define_template_fields(enum ipfix_proto_l2 l2, enum ipfix_proto_l3 l3,
- enum ipfix_proto_l4 l4, struct ofpbuf *msg)
+ enum ipfix_proto_l4 l4, enum ipfix_proto_tunnel tunnel,
+ struct ofpbuf *msg)
{
uint16_t count = 0;
#define DEF(ID) \
{ \
ipfix_define_template_entity(IPFIX_ENTITY_ID_##ID, \
- IPFIX_ENTITY_SIZE_##ID, msg); \
+ IPFIX_ENTITY_SIZE_##ID, \
+ IPFIX_ENTITY_ENTERPRISE_##ID, msg); \
count++; \
}
/* 1. Flow key. */
DEF(OBSERVATION_POINT_ID);
+ DEF(FLOW_DIRECTION);
/* Common Ethernet entities. */
DEF(SOURCE_MAC_ADDRESS);
}
}
+ if (tunnel != IPFIX_PROTO_NOT_TUNNELED) {
+ DEF(TUNNEL_SOURCE_IPV4_ADDRESS);
+ DEF(TUNNEL_DESTINATION_IPV4_ADDRESS);
+ DEF(TUNNEL_PROTOCOL_IDENTIFIER);
+ DEF(TUNNEL_SOURCE_TRANSPORT_PORT);
+ DEF(TUNNEL_DESTINATION_TRANSPORT_PORT);
+ DEF(TUNNEL_TYPE);
+ DEF(TUNNEL_KEY);
+ }
+
/* 2. Flow aggregated data. */
DEF(FLOW_START_DELTA_MICROSECONDS);
DEF(MAXIMUM_IP_TOTAL_LENGTH);
}
+
#undef DEF
return count;
}
static void
-ipfix_send_template_msg(struct dpif_ipfix_exporter *exporter,
- uint32_t export_time_sec, uint32_t obs_domain_id)
+ipfix_init_template_msg(void *msg_stub, uint32_t export_time_sec,
+ uint32_t seq_number, uint32_t obs_domain_id,
+ struct ofpbuf *msg, size_t *set_hdr_offset)
+{
+ struct ipfix_set_header *set_hdr;
+
+ ofpbuf_use_stub(msg, msg_stub, sizeof msg_stub);
+
+ ipfix_init_header(export_time_sec, seq_number, obs_domain_id, msg);
+ *set_hdr_offset = ofpbuf_size(msg);
+
+ /* Add a Template Set. */
+ set_hdr = ofpbuf_put_zeros(msg, sizeof *set_hdr);
+ set_hdr->set_id = htons(IPFIX_SET_ID_TEMPLATE);
+}
+
+static void
+ipfix_send_template_msg(const struct collectors *collectors,
+ struct ofpbuf *msg, size_t set_hdr_offset)
+{
+ struct ipfix_set_header *set_hdr;
+
+ /* Send template message. */
+ set_hdr = (struct ipfix_set_header*)
+ ((uint8_t*)ofpbuf_data(msg) + set_hdr_offset);
+ set_hdr->length = htons(ofpbuf_size(msg) - set_hdr_offset);
+
+ ipfix_send_msg(collectors, msg);
+
+ ofpbuf_uninit(msg);
+}
+
+static void
+ipfix_send_template_msgs(struct dpif_ipfix_exporter *exporter,
+ uint32_t export_time_sec, uint32_t obs_domain_id)
{
uint64_t msg_stub[DIV_ROUND_UP(MAX_MESSAGE_LEN, 8)];
struct ofpbuf msg;
size_t set_hdr_offset, tmpl_hdr_offset;
- struct ipfix_set_header *set_hdr;
struct ipfix_template_record_header *tmpl_hdr;
uint16_t field_count;
enum ipfix_proto_l2 l2;
enum ipfix_proto_l3 l3;
enum ipfix_proto_l4 l4;
+ enum ipfix_proto_tunnel tunnel;
- ofpbuf_use_stub(&msg, msg_stub, sizeof msg_stub);
-
- ipfix_init_header(export_time_sec, exporter->seq_number, obs_domain_id,
- &msg);
- set_hdr_offset = ofpbuf_size(&msg);
-
- /* Add a Template Set. */
- set_hdr = ofpbuf_put_zeros(&msg, sizeof *set_hdr);
- set_hdr->set_id = htons(IPFIX_SET_ID_TEMPLATE);
-
+ ipfix_init_template_msg(msg_stub, export_time_sec, exporter->seq_number,
+ obs_domain_id, &msg, &set_hdr_offset);
/* Define one template for each possible combination of
* protocols. */
for (l2 = 0; l2 < NUM_IPFIX_PROTO_L2; l2++) {
l4 != IPFIX_PROTO_L4_UNKNOWN) {
continue;
}
- tmpl_hdr_offset = ofpbuf_size(&msg);
- tmpl_hdr = ofpbuf_put_zeros(&msg, sizeof *tmpl_hdr);
- tmpl_hdr->template_id = htons(
- ipfix_get_template_id(l2, l3, l4));
- field_count = ipfix_define_template_fields(l2, l3, l4, &msg);
- tmpl_hdr = (struct ipfix_template_record_header*)
- ((uint8_t*)ofpbuf_data(&msg) + tmpl_hdr_offset);
- tmpl_hdr->field_count = htons(field_count);
+ for (tunnel = 0; tunnel < NUM_IPFIX_PROTO_TUNNEL; tunnel++) {
+ /* When the size of the template packet reaches
+ * MAX_MESSAGE_LEN(1024), send it out.
+ * And then reinitialize the msg to construct a new
+ * packet for the following templates.
+ */
+ if (ofpbuf_size(&msg) >= MAX_MESSAGE_LEN) {
+ /* Send template message. */
+ ipfix_send_template_msg(exporter->collectors,
+ &msg, set_hdr_offset);
+
+ /* Reinitialize the template msg. */
+ ipfix_init_template_msg(msg_stub, export_time_sec,
+ exporter->seq_number,
+ obs_domain_id, &msg,
+ &set_hdr_offset);
+ }
+
+ tmpl_hdr_offset = ofpbuf_size(&msg);
+ tmpl_hdr = ofpbuf_put_zeros(&msg, sizeof *tmpl_hdr);
+ tmpl_hdr->template_id = htons(
+ ipfix_get_template_id(l2, l3, l4, tunnel));
+ field_count =
+ ipfix_define_template_fields(l2, l3, l4, tunnel, &msg);
+ tmpl_hdr = (struct ipfix_template_record_header*)
+ ((uint8_t*)ofpbuf_data(&msg) + tmpl_hdr_offset);
+ tmpl_hdr->field_count = htons(field_count);
+ }
}
}
}
- set_hdr = (struct ipfix_set_header*)((uint8_t*)ofpbuf_data(&msg) + set_hdr_offset);
- set_hdr->length = htons(ofpbuf_size(&msg) - set_hdr_offset);
+ /* Send template message. */
+ ipfix_send_template_msg(exporter->collectors, &msg, set_hdr_offset);
/* XXX: Add Options Template Sets, at least to define a Flow Keys
* Option Template. */
- ipfix_send_msg(exporter->collectors, &msg);
-
- ofpbuf_uninit(&msg);
}
static inline uint32_t
ipfix_cache_entry_init(struct ipfix_flow_cache_entry *entry,
const struct ofpbuf *packet, const struct flow *flow,
uint64_t packet_delta_count, uint32_t obs_domain_id,
- uint32_t obs_point_id)
+ uint32_t obs_point_id, odp_port_t output_odp_port,
+ const struct dpif_ipfix_port *tunnel_port,
+ const struct flow_tnl *tunnel_key)
{
struct ipfix_flow_key *flow_key;
struct ofpbuf msg;
enum ipfix_proto_l2 l2;
enum ipfix_proto_l3 l3;
enum ipfix_proto_l4 l4;
+ enum ipfix_proto_tunnel tunnel = IPFIX_PROTO_NOT_TUNNELED;
uint8_t ethernet_header_length;
uint16_t ethernet_total_length;
l4 = IPFIX_PROTO_L4_UNKNOWN;
}
+ if (tunnel_port && tunnel_key) {
+ tunnel = IPFIX_PROTO_TUNNELED;
+ }
+
flow_key->obs_domain_id = obs_domain_id;
- flow_key->template_id = ipfix_get_template_id(l2, l3, l4);
+ flow_key->template_id = ipfix_get_template_id(l2, l3, l4, tunnel);
/* The fields defined in the ipfix_data_record_* structs and sent
* below must match exactly the templates defined in
data_common = ofpbuf_put_zeros(&msg, sizeof *data_common);
data_common->observation_point_id = htonl(obs_point_id);
+ data_common->flow_direction =
+ (output_odp_port == ODPP_NONE) ? INGRESS_FLOW : EGRESS_FLOW;
memcpy(data_common->source_mac_address, flow->dl_src,
sizeof flow->dl_src);
memcpy(data_common->destination_mac_address, flow->dl_dst,
data_icmp->icmp_code = ntohs(flow->tp_dst) & 0xff;
}
+ if (tunnel == IPFIX_PROTO_TUNNELED) {
+ struct ipfix_data_record_flow_key_tunnel *data_tunnel;
+ const uint8_t *tun_id;
+
+ data_tunnel = ofpbuf_put_zeros(&msg, sizeof *data_tunnel +
+ tunnel_port->tunnel_key_length);
+ data_tunnel->tunnel_source_ipv4_address = tunnel_key->ip_src;
+ data_tunnel->tunnel_destination_ipv4_address = tunnel_key->ip_dst;
+ /* The tunnel_protocol_identifier is from tunnel_proto array, which
+ * contains protocol_identifiers of each tunnel type.
+ * For the tunnel type on the top of IPSec, which uses the protocol
+ * identifier of the upper tunnel type is used, the tcp_src and tcp_dst
+ * are decided based on the protocol identifiers.
+ * E.g:
+ * The protocol identifier of DPIF_IPFIX_TUNNEL_IPSEC_GRE is IPPROTO_GRE,
+ * and both tp_src and tp_dst are zero.
+ */
+ data_tunnel->tunnel_protocol_identifier =
+ tunnel_protocol[tunnel_port->tunnel_type];
+ data_tunnel->tunnel_source_transport_port = tunnel_key->tp_src;
+ data_tunnel->tunnel_destination_transport_port = tunnel_key->tp_dst;
+ data_tunnel->tunnel_type = tunnel_port->tunnel_type;
+ data_tunnel->tunnel_key_length = tunnel_port->tunnel_key_length;
+ /* tun_id is in network order, and tunnel key is in low bits. */
+ tun_id = (const uint8_t *) &tunnel_key->tun_id;
+ memcpy(data_tunnel->tunnel_key,
+ &tun_id[8 - tunnel_port->tunnel_key_length],
+ tunnel_port->tunnel_key_length);
+ }
+
flow_key->flow_key_msg_part_size = ofpbuf_size(&msg);
{
dpif_ipfix_sample(struct dpif_ipfix_exporter *exporter,
const struct ofpbuf *packet, const struct flow *flow,
uint64_t packet_delta_count, uint32_t obs_domain_id,
- uint32_t obs_point_id)
+ uint32_t obs_point_id, odp_port_t output_odp_port,
+ const struct dpif_ipfix_port *tunnel_port,
+ const struct flow_tnl *tunnel_key)
{
struct ipfix_flow_cache_entry *entry;
/* Create a flow cache entry from the sample. */
entry = xmalloc(sizeof *entry);
ipfix_cache_entry_init(entry, packet, flow, packet_delta_count,
- obs_domain_id, obs_point_id);
+ obs_domain_id, obs_point_id,
+ output_odp_port, tunnel_port, tunnel_key);
ipfix_cache_update(exporter, entry);
}
void
dpif_ipfix_bridge_sample(struct dpif_ipfix *di, const struct ofpbuf *packet,
- const struct flow *flow) OVS_EXCLUDED(mutex)
+ const struct flow *flow,
+ odp_port_t input_odp_port, odp_port_t output_odp_port,
+ const struct flow_tnl *output_tunnel_key)
+ OVS_EXCLUDED(mutex)
{
uint64_t packet_delta_count;
+ const struct flow_tnl *tunnel_key = NULL;
+ struct dpif_ipfix_port * tunnel_port = NULL;
ovs_mutex_lock(&mutex);
/* Use the sampling probability as an approximation of the number
* of matched packets. */
packet_delta_count = UINT32_MAX / di->bridge_exporter.probability;
+ if (di->bridge_exporter.options->enable_tunnel_sampling) {
+ if (output_odp_port == ODPP_NONE && flow->tunnel.ip_dst) {
+ /* Input tunnel. */
+ tunnel_key = &flow->tunnel;
+ tunnel_port = dpif_ipfix_find_port(di, input_odp_port);
+ }
+ if (output_odp_port != ODPP_NONE && output_tunnel_key) {
+ /* Output tunnel, output_tunnel_key must be valid. */
+ tunnel_key = output_tunnel_key;
+ tunnel_port = dpif_ipfix_find_port(di, output_odp_port);
+ }
+ }
dpif_ipfix_sample(&di->bridge_exporter.exporter, packet, flow,
packet_delta_count,
di->bridge_exporter.options->obs_domain_id,
- di->bridge_exporter.options->obs_point_id);
+ di->bridge_exporter.options->obs_point_id,
+ output_odp_port, tunnel_port, tunnel_key);
ovs_mutex_unlock(&mutex);
}
node = dpif_ipfix_find_flow_exporter_map_node(di, collector_set_id);
if (node) {
dpif_ipfix_sample(&node->exporter.exporter, packet, flow,
- packet_delta_count, obs_domain_id, obs_point_id);
+ packet_delta_count, obs_domain_id, obs_point_id,
+ ODPP_NONE, NULL, NULL);
}
ovs_mutex_unlock(&mutex);
}
if (!template_msg_sent
&& (exporter->last_template_set_time + IPFIX_TEMPLATE_INTERVAL)
<= export_time_sec) {
- ipfix_send_template_msg(exporter, export_time_sec,
- entry->flow_key.obs_domain_id);
+ ipfix_send_template_msgs(exporter, export_time_sec,
+ entry->flow_key.obs_domain_id);
exporter->last_template_set_time = export_time_sec;
template_msg_sent = true;
}
#include <stddef.h>
#include <stdint.h>
+#include <stdbool.h>
+#include "lib/odp-util.h"
struct flow;
struct ofpbuf;
struct ofproto_ipfix_bridge_exporter_options;
struct ofproto_ipfix_flow_exporter_options;
+struct flow_tnl;
+struct ofport;
struct dpif_ipfix *dpif_ipfix_create(void);
struct dpif_ipfix *dpif_ipfix_ref(const struct dpif_ipfix *);
void dpif_ipfix_unref(struct dpif_ipfix *);
+void dpif_ipfix_add_tunnel_port(struct dpif_ipfix *, struct ofport *, odp_port_t);
+void dpif_ipfix_del_tunnel_port(struct dpif_ipfix *, odp_port_t);
+
uint32_t dpif_ipfix_get_bridge_exporter_probability(const struct dpif_ipfix *);
+bool dpif_ipfix_get_bridge_exporter_tunnel_sampling(const struct dpif_ipfix *);
+bool dpif_ipfix_get_bridge_exporter_input_sampling(const struct dpif_ipfix *);
+bool dpif_ipfix_get_bridge_exporter_output_sampling(const struct dpif_ipfix *);
+bool dpif_ipfix_get_tunnel_port(const struct dpif_ipfix *, odp_port_t);
void dpif_ipfix_set_options(
struct dpif_ipfix *,
const struct ofproto_ipfix_bridge_exporter_options *,
const struct ofproto_ipfix_flow_exporter_options *, size_t);
void dpif_ipfix_bridge_sample(struct dpif_ipfix *, const struct ofpbuf *,
- const struct flow *);
+ const struct flow *,
+ odp_port_t, odp_port_t, const struct flow_tnl *);
void dpif_ipfix_flow_sample(struct dpif_ipfix *, const struct ofpbuf *,
const struct flow *, uint32_t, uint16_t, uint32_t,
uint32_t);
/* Not used by the upcall callback interface. */
const struct nlattr *key; /* Datapath flow key. */
size_t key_len; /* Datapath flow key length. */
+ const struct nlattr *out_tun_key; /* Datapath output tunnel key. */
};
/* 'udpif_key's are responsible for tracking the little bit of state udpif
upcall->key = dupcall->key;
upcall->key_len = dupcall->key_len;
+ upcall->out_tun_key = dupcall->out_tun_key;
+
if (vsp_adjust_flow(upcall->ofproto, &flow, &dupcall->packet)) {
upcall->vsp_adjusted = true;
}
? ODPP_NONE
: odp_in_port;
pid = dpif_port_get_pid(udpif->dpif, port, flow_hash_5tuple(flow, 0));
- odp_put_userspace_action(pid, &cookie, sizeof cookie.slow_path, buf);
+ odp_put_userspace_action(pid, &cookie, sizeof cookie.slow_path, ODPP_NONE,
+ buf);
}
static int
upcall->key = NULL;
upcall->key_len = 0;
+ upcall->out_tun_key = NULL;
+
return 0;
}
case IPFIX_UPCALL:
if (upcall->ipfix) {
- dpif_ipfix_bridge_sample(upcall->ipfix, packet, flow);
+ union user_action_cookie cookie;
+ struct flow_tnl output_tunnel_key;
+
+ memset(&cookie, 0, sizeof cookie);
+ memcpy(&cookie, nl_attr_get(userdata), sizeof cookie.ipfix);
+
+ if (upcall->out_tun_key) {
+ memset(&output_tunnel_key, 0, sizeof output_tunnel_key);
+ odp_tun_key_from_attr(upcall->out_tun_key,
+ &output_tunnel_key);
+ }
+ dpif_ipfix_bridge_sample(upcall->ipfix, packet, flow,
+ flow->in_port.odp_port,
+ cookie.ipfix.output_odp_port,
+ upcall->out_tun_key ?
+ &output_tunnel_key : NULL);
}
break;
const struct flow *flow,
const uint32_t probability,
const union user_action_cookie *cookie,
- const size_t cookie_size)
+ const size_t cookie_size,
+ const odp_port_t tunnel_out_port)
{
size_t sample_offset, actions_offset;
odp_port_t odp_port;
pid = dpif_port_get_pid(xbridge->dpif, odp_port,
flow_hash_5tuple(flow, 0));
cookie_offset = odp_put_userspace_action(pid, cookie, cookie_size,
- odp_actions);
+ tunnel_out_port, odp_actions);
nl_msg_end_nested(odp_actions, actions_offset);
nl_msg_end_nested(odp_actions, sample_offset);
odp_port == ODPP_NONE ? 0 : 1, &cookie);
return compose_sample_action(xbridge, odp_actions, flow, probability,
- &cookie, sizeof cookie.sflow);
+ &cookie, sizeof cookie.sflow, ODPP_NONE);
}
static void
}
static void
-compose_ipfix_cookie(union user_action_cookie *cookie)
+compose_ipfix_cookie(union user_action_cookie *cookie,
+ odp_port_t output_odp_port)
{
cookie->type = USER_ACTION_COOKIE_IPFIX;
+ cookie->ipfix.output_odp_port = output_odp_port;
}
/* Compose SAMPLE action for IPFIX bridge sampling. */
static void
compose_ipfix_action(const struct xbridge *xbridge,
struct ofpbuf *odp_actions,
- const struct flow *flow)
+ const struct flow *flow,
+ odp_port_t output_odp_port)
{
uint32_t probability;
union user_action_cookie cookie;
+ odp_port_t tunnel_out_port = ODPP_NONE;
if (!xbridge->ipfix || flow->in_port.ofp_port == OFPP_NONE) {
return;
}
+ /* For input case, output_odp_port is ODPP_NONE, which is an invalid port
+ * number. */
+ if (output_odp_port == ODPP_NONE &&
+ !dpif_ipfix_get_bridge_exporter_input_sampling(xbridge->ipfix)) {
+ return;
+ }
+
+ /* For output case, output_odp_port is valid*/
+ if (output_odp_port != ODPP_NONE) {
+ if (!dpif_ipfix_get_bridge_exporter_output_sampling(xbridge->ipfix)) {
+ return;
+ }
+ /* If tunnel sampling is enabled, put an additional option attribute:
+ * OVS_USERSPACE_ATTR_TUNNEL_OUT_PORT
+ */
+ if (dpif_ipfix_get_bridge_exporter_tunnel_sampling(xbridge->ipfix) &&
+ dpif_ipfix_get_tunnel_port(xbridge->ipfix, output_odp_port) ) {
+ tunnel_out_port = output_odp_port;
+ }
+ }
+
probability = dpif_ipfix_get_bridge_exporter_probability(xbridge->ipfix);
- compose_ipfix_cookie(&cookie);
+ compose_ipfix_cookie(&cookie, output_odp_port);
compose_sample_action(xbridge, odp_actions, flow, probability,
- &cookie, sizeof cookie.ipfix);
+ &cookie, sizeof cookie.ipfix, tunnel_out_port);
}
/* SAMPLE action for sFlow must be first action in any given list of
add_ipfix_action(struct xlate_ctx *ctx)
{
compose_ipfix_action(ctx->xbridge, ctx->xout->odp_actions,
- &ctx->xin->flow);
+ &ctx->xin->flow, ODPP_NONE);
+}
+
+static void
+add_ipfix_output_action(struct xlate_ctx *ctx, odp_port_t port)
+{
+ compose_ipfix_action(ctx->xbridge, ctx->xout->odp_actions,
+ &ctx->xin->flow, port);
}
/* Fix SAMPLE action according to data collected while composing ODP actions.
nl_msg_put_u32(ctx->xout->odp_actions, OVS_ACTION_ATTR_RECIRC,
xr->recirc_id);
} else {
+ add_ipfix_output_action(ctx, out_port);
nl_msg_put_odp_port(ctx->xout->odp_actions, OVS_ACTION_ATTR_OUTPUT,
out_port);
}
compose_flow_sample_cookie(os->probability, os->collector_set_id,
os->obs_domain_id, os->obs_point_id, &cookie);
compose_sample_action(ctx->xbridge, ctx->xout->odp_actions, &ctx->xin->flow,
- probability, &cookie, sizeof cookie.flow_sample);
+ probability, &cookie, sizeof cookie.flow_sample,
+ ODPP_NONE);
}
static bool
netflow_unref(ofproto->netflow);
dpif_sflow_unref(ofproto->sflow);
+ dpif_ipfix_unref(ofproto->ipfix);
hmap_destroy(&ofproto->bundles);
mac_learning_unref(ofproto->ml);
mcast_snooping_unref(ofproto->ms);
if (netdev_get_tunnel_config(netdev)) {
tnl_port_add(port, port->up.netdev, port->odp_port);
port->is_tunnel = true;
+ if (ofproto->ipfix) {
+ dpif_ipfix_add_tunnel_port(ofproto->ipfix, port_, port->odp_port);
+ }
} else {
/* Sanity-check that a mapping doesn't already exist. This
* shouldn't happen for non-tunnel ports. */
ovs_rwlock_unlock(&ofproto->backer->odp_to_ofport_lock);
}
+ if (port->is_tunnel && ofproto->ipfix) {
+ dpif_ipfix_del_tunnel_port(ofproto->ipfix, port->odp_port);
+ }
+
tnl_port_del(port);
sset_find_and_delete(&ofproto->ports, devname);
sset_find_and_delete(&ofproto->ghost_ports, devname);
struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
struct dpif_ipfix *di = ofproto->ipfix;
bool has_options = bridge_exporter_options || flow_exporters_options;
+ bool new_di = false;
if (has_options && !di) {
di = ofproto->ipfix = dpif_ipfix_create();
+ new_di = true;
}
if (di) {
di, bridge_exporter_options, flow_exporters_options,
n_flow_exporters_options);
+ /* Add tunnel ports only when a new ipfix created */
+ if (new_di == true) {
+ struct ofport_dpif *ofport;
+ HMAP_FOR_EACH (ofport, up.hmap_node, &ofproto->up.ports) {
+ if (ofport->is_tunnel == true) {
+ dpif_ipfix_add_tunnel_port(di, &ofport->up, ofport->odp_port);
+ }
+ }
+ }
+
if (!has_options) {
dpif_ipfix_unref(di);
ofproto->ipfix = NULL;
uint32_t obs_point_id; /* Bridge-wide Observation Point ID. */
uint32_t cache_active_timeout;
uint32_t cache_max_flows;
+ bool enable_tunnel_sampling;
+ bool enable_input_sampling;
+ bool enable_output_sampling;
};
struct ofproto_ipfix_flow_exporter_options {
FLOW_TNL_F_KEY);
wc->masks.tunnel.ip_tos = UINT8_MAX;
wc->masks.tunnel.ip_ttl = UINT8_MAX;
+ /* The tp_src and tp_dst members in flow_tnl are set to be always
+ * wildcarded, not to unwildcard them here. */
+ wc->masks.tunnel.tp_src = 0;
+ wc->masks.tunnel.tp_dst = 0;
memset(&wc->masks.pkt_mark, 0xff, sizeof wc->masks.pkt_mark);
AT_DATA([actions.txt], [dnl
1,2,3
userspace(pid=555666777)
+userspace(pid=555666777,tunnel_out_port=10)
userspace(pid=6633,sFlow(vid=9,pcp=7,output=10))
+userspace(pid=6633,sFlow(vid=9,pcp=7,output=10),tunnel_out_port=10)
userspace(pid=9765,slow_path())
+userspace(pid=9765,slow_path(),tunnel_out_port=10)
userspace(pid=9765,slow_path(cfm))
+userspace(pid=9765,slow_path(cfm),tunnel_out_port=10)
userspace(pid=1234567,userdata(0102030405060708090a0b0c0d0e0f))
+userspace(pid=1234567,userdata(0102030405060708090a0b0c0d0e0f),tunnel_out_port=10)
userspace(pid=6633,flow_sample(probability=123,collector_set_id=1234,obs_domain_id=2345,obs_point_id=3456))
-userspace(pid=6633,ipfix)
+userspace(pid=6633,flow_sample(probability=123,collector_set_id=1234,obs_domain_id=2345,obs_point_id=3456),tunnel_out_port=10)
+userspace(pid=6633,ipfix(output_port=10))
+userspace(pid=6633,ipfix(output_port=10),tunnel_out_port=10)
set(in_port(2))
set(eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15))
set(eth_type(0x1234))
Configure bridge \fBbr0\fR to send one IPFIX flow record per packet
sample to UDP port 4739 on host 192.168.0.34, with Observation Domain
ID 123 and Observation Point ID 456, a flow cache active timeout of 1
-minute (60 seconds), and a maximum flow cache size of 13 flows:
+minute (60 seconds), maximum flow cache size of 13 flows, and flows
+sampled on output port with tunnel info(sampling on input and output
+port is enabled by default if not disabled) :
.IP
.B "ovs\-vsctl \-\- set Bridge br0 ipfix=@i \(rs"
.IP
-.B "\-\- \-\-id=@i create IPFIX targets=\(rs\(dq192.168.0.34:4739\(rs\(dq obs_domain_id=123 obs_point_id=456 cache_active_timeout=60 cache_max_flows=13"
+.B "\-\- \-\-id=@i create IPFIX targets=\(rs\(dq192.168.0.34:4739\(rs\(dq obs_domain_id=123 obs_point_id=456 cache_active_timeout=60 cache_max_flows=13 \(rs"
+.IP
+.B "other_config:enable-input-sampling=false other_config:enable-tunnel-sampling=true"
.PP
Deconfigure the IPFIX settings from \fBbr0\fR, which also destroys the
IPFIX record (since it is now unreferenced):
if (be_cfg->cache_max_flows) {
be_opts.cache_max_flows = *be_cfg->cache_max_flows;
}
+
+ be_opts.enable_tunnel_sampling = smap_get_bool(&be_cfg->other_config,
+ "enable-tunnel-sampling", true);
+
+ be_opts.enable_input_sampling = !smap_get_bool(&be_cfg->other_config,
+ "enable-input-sampling", false);
+
+ be_opts.enable_output_sampling = !smap_get_bool(&be_cfg->other_config,
+ "enable-output-sampling", false);
}
if (n_fe_opts > 0) {
{"name": "Open_vSwitch",
"version": "7.8.0",
- "cksum": "2676751133 20740",
+ "cksum": "4147598271 20869",
"tables": {
"Open_vSwitch": {
"columns": {
"minInteger": 0,
"maxInteger": 4294967295},
"min": 0, "max": 1}},
+ "other_config": {
+ "type": {"key": "string", "value": "string",
+ "min": 0, "max": "unlimited"}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}}},
disabled.
</column>
+ <column name="other_config" key="enable-tunnel-sampling"
+ type='{"type": "boolean"}'>
+ <p>For per-bridge packet sampling, i.e. when this row is referenced
+ from a <ref table="Bridge"/>, enable sampling and reporting tunnel
+ header 7-tuples in IPFIX flow records. Disabled by default.
+ Ignored for per-flow sampling, i.e. when this row is referenced
+ from a <ref table="Flow_Sample_Collector_Set"/>.</p>
+ <p><em>Please note:</em> The following enterprise entities are
+ currently used when exporting the sampled tunnel info.</p>
+ <dl>
+ <dt>tunnelType:</dt>
+ <dd>
+ <p>ID: 891, and enterprise ID 6876 (VMware).</p>
+ <p>type: unsigned 8-bit interger.</p>
+ <p>data type semantics: identifier.</p>
+ <p>description: Identifier of the layer 2 network overlay network
+ encapsulation type: 0x01 VxLAN, 0x02 GRE, 0x03 LISP, 0x05 IPsec+GRE,
+ 0x07 GENEVE.</p>
+ </dd>
+ <dt>tunnelKey:</dt>
+ <dd>
+ <p>ID: 892, and enterprise ID 6876 (VMware).</p>
+ <p>type: variable-length octetarray.</p>
+ <p>data type semantics: identifier.</p>
+ <p>description: Key which is used for identifying an individual
+ traffic flow within a VxLAN (24-bit VNI), GENEVE(24-bit VNI),
+ GRE (32- or 64-bit key), or LISP (24-bit instance ID) tunnel. The
+ key is encoded in this octetarray as a 3-, 4-, or 8-byte integer
+ ID in network byte order.</p>
+ </dd>
+ <dt>tunnelSourceIPv4Address:</dt>
+ <dd>
+ <p>ID: 893, and enterprise ID 6876 (VMware).</p>
+ <p>type: unsigned 32-bit interger.</p>
+ <p>data type semantics: identifier.</p>
+ <p>description: The IPv4 source address in the tunnel IP packet
+ header.</p>
+ </dd>
+ <dt>tunnelDestinationIPv4Address:</dt>
+ <dd>
+ <p>ID: 894, and enterprise ID 6876 (VMware).</p>
+ <p>type: unsigned 32-bit integer.</p>
+ <p>data type semantics: identifier.</p>
+ <p>description: The IPv4 destination address in the tunnel IP
+ packet header.</p>
+ </dd>
+ <dt>tunnelProtocolIdentifier:</dt>
+ <dd>
+ <p>ID: 895, and enterprise ID 6876 (VMware).</p>
+ <p>type: unsigned 8-bit integer.</p>
+ <p>data type semantics: identifier.</p>
+ <p>description: The value of the protocol number in the tunnel
+ IP packet header. The protocol number identifies the tunnel IP
+ packet payload type.</p>
+ </dd>
+ <dt>tunnelSourceTransportPort:</dt>
+ <dd>
+ <p>ID: 896, and enterprise ID 6876 (VMware).</p>
+ <p>type: unsigned 16-bit integer.</p>
+ <p>data type semantics: identifier.</p>
+ <p>description: The source port identifier in the tunnel transport
+ header. For the transport protocols UDP, TCP, and SCTP, this is
+ the source port number given in the respective header.</p>
+ </dd>
+ <dt>tunnelDestinationTransportPort:</dt>
+ <dd>
+ <p>ID: 897, and enterprise ID 6876 (VMware).</p>
+ <p>type: unsigned 16-bit integer.</p>
+ <p>data type semantics: identifier.</p>
+ <p>description: The destination port identifier in the tunnel
+ transport header. For the transport protocols UDP, TCP, and SCTP,
+ this is the destination port number given in the respective header.
+ </p>
+ </dd>
+ </dl>
+ </column>
+
+ <column name="other_config" key="enable-input-sampling"
+ type='{"type": "boolean"}'>
+ For per-bridge packet sampling, i.e. when this row is referenced
+ from a <ref table="Bridge"/>, enable sampling and reporting flows
+ at bridge port input in IPFIX flow records. Enabled by default.
+ Ignored for per-flow sampling, i.e. when this row is referenced
+ from a <ref table="Flow_Sample_Collector_Set"/>.
+ </column>
+
+ <column name="other_config" key="enable-output-sampling"
+ type='{"type": "boolean"}'>
+ For per-bridge packet sampling, i.e. when this row is referenced
+ from a <ref table="Bridge"/>, enable sampling and reporting flows
+ at bridge port output in IPFIX flow records. Enabled by default.
+ Ignored for per-flow sampling, i.e. when this row is referenced
+ from a <ref table="Flow_Sample_Collector_Set"/>.
+ </column>
+
<group title="Common Columns">
The overall purpose of these columns is described under <code>Common
Columns</code> at the beginning of this document.