On Mon, Jan 16, 2023 at 10:17 AM Adrián Moreno <amore...@redhat.com> wrote:
>
> From: Adrian Moreno <amore...@redhat.com>
>
> IPFIX templates have to be sent for each Observation Domain ID.
> Currently, a timer is kept at each dpif_ipfix_exporter to send them.
> This works fine for per-bridge sampling where there is only one
> Observation Domain ID per exporter. However, this is does not work for
> per-flow sampling where more than one Observation Domain IDs can be
> specified by the controller. In this case, ovs-vswitchd will only send
> template information for one (arbitrary) DomainID.
>
> Fix per-flow sampling by using an hmap to keep a timer for each
> Observation Domain ID.
>
> Signed-off-by: Adrian Moreno <amore...@redhat.com>
>
> ---
> - v1:
>   - Added unit test.
>   - Drop domain_id from "struct dpif_ipfix_domain" since the hashmap
>   already uses it as index.
>   - Added OVS_REQUES(mutex) to dpif_ipfix_exporter_find_domain.
> ---
>  ofproto/ofproto-dpif-ipfix.c | 127 ++++++++++++++++++++++++++++-------
>  tests/system-traffic.at      |  51 ++++++++++++++
>  2 files changed, 155 insertions(+), 23 deletions(-)
>
> diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c
> index 742eed399..fa71fda0f 100644
> --- a/ofproto/ofproto-dpif-ipfix.c
> +++ b/ofproto/ofproto-dpif-ipfix.c
> @@ -124,11 +124,18 @@ struct dpif_ipfix_port {
>      uint32_t ifindex;
>  };
>
> +struct dpif_ipfix_domain {
> +    struct hmap_node hmap_node; /* In struct dpif_ipfix_exporter's domains. 
> */
> +    time_t last_template_set_time;
> +};
> +
>  struct dpif_ipfix_exporter {
>      uint32_t exporter_id; /* Exporting Process identifier */
>      struct collectors *collectors;
>      uint32_t seq_number;
> -    time_t last_template_set_time;
> +    struct hmap domains; /* Contains struct dpif_ipfix_domain indexed by
> +                            observation domain id. */
> +    time_t last_stats_sent_time;
>      struct hmap cache_flow_key_map;  /* ipfix_flow_cache_entry. */
>      struct ovs_list cache_flow_start_timestamp_list;  /* 
> ipfix_flow_cache_entry. */
>      uint32_t cache_active_timeout;  /* In seconds. */
> @@ -617,6 +624,9 @@ static void get_export_time_now(uint64_t *, uint32_t *);
>
>  static void dpif_ipfix_cache_expire_now(struct dpif_ipfix_exporter *, bool);
>
> +static void dpif_ipfix_exporter_del_domain(struct dpif_ipfix_exporter * ,
> +                                           struct dpif_ipfix_domain * );
> +
>  static bool
>  ofproto_ipfix_bridge_exporter_options_equal(
>      const struct ofproto_ipfix_bridge_exporter_options *a,
> @@ -697,13 +707,14 @@ dpif_ipfix_exporter_init(struct dpif_ipfix_exporter 
> *exporter)
>      exporter->exporter_id = ++exporter_total_count;
>      exporter->collectors = NULL;
>      exporter->seq_number = 1;
> -    exporter->last_template_set_time = 0;
> +    exporter->last_stats_sent_time = 0;
>      hmap_init(&exporter->cache_flow_key_map);
>      ovs_list_init(&exporter->cache_flow_start_timestamp_list);
>      exporter->cache_active_timeout = 0;
>      exporter->cache_max_flows = 0;
>      exporter->virtual_obs_id = NULL;
>      exporter->virtual_obs_len = 0;
> +    hmap_init(&exporter->domains);
>
>      memset(&exporter->ipfix_global_stats, 0,
>             sizeof(struct dpif_ipfix_global_stats));
> @@ -711,6 +722,7 @@ dpif_ipfix_exporter_init(struct dpif_ipfix_exporter 
> *exporter)
>
>  static void
>  dpif_ipfix_exporter_clear(struct dpif_ipfix_exporter *exporter)
> +    OVS_REQUIRES(mutex)
>  {
>      /* Flush the cache with flow end reason "forced end." */
>      dpif_ipfix_cache_expire_now(exporter, true);
> @@ -719,22 +731,29 @@ dpif_ipfix_exporter_clear(struct dpif_ipfix_exporter 
> *exporter)
>      exporter->exporter_id = 0;
>      exporter->collectors = NULL;
>      exporter->seq_number = 1;
> -    exporter->last_template_set_time = 0;
> +    exporter->last_stats_sent_time = 0;
>      exporter->cache_active_timeout = 0;
>      exporter->cache_max_flows = 0;
>      free(exporter->virtual_obs_id);
>      exporter->virtual_obs_id = NULL;
>      exporter->virtual_obs_len = 0;
>
> +    struct dpif_ipfix_domain *dom;
> +    HMAP_FOR_EACH_SAFE (dom, hmap_node, &exporter->domains) {
> +        dpif_ipfix_exporter_del_domain(exporter, dom);
> +    }
> +
>      memset(&exporter->ipfix_global_stats, 0,
>             sizeof(struct dpif_ipfix_global_stats));
>  }
>
>  static void
>  dpif_ipfix_exporter_destroy(struct dpif_ipfix_exporter *exporter)
> +    OVS_REQUIRES(mutex)
>  {
>      dpif_ipfix_exporter_clear(exporter);
>      hmap_destroy(&exporter->cache_flow_key_map);
> +    hmap_destroy(&exporter->domains);
>  }
>
>  static bool
> @@ -742,7 +761,7 @@ dpif_ipfix_exporter_set_options(struct 
> dpif_ipfix_exporter *exporter,
>                                  const struct sset *targets,
>                                  const uint32_t cache_active_timeout,
>                                  const uint32_t cache_max_flows,
> -                                const char *virtual_obs_id)
> +                                const char *virtual_obs_id) 
> OVS_REQUIRES(mutex)
>  {
>      size_t virtual_obs_len;
>      collectors_destroy(exporter->collectors);
> @@ -769,6 +788,37 @@ dpif_ipfix_exporter_set_options(struct 
> dpif_ipfix_exporter *exporter,
>      return true;
>  }
>
> +static struct dpif_ipfix_domain *
> +dpif_ipfix_exporter_find_domain(const struct dpif_ipfix_exporter *exporter,
> +                                uint32_t domain_id) OVS_REQUIRES(mutex)
> +{
> +    struct dpif_ipfix_domain *dom;
> +    HMAP_FOR_EACH_WITH_HASH (dom, hmap_node, hash_int(domain_id, 0),
> +                             &exporter->domains) {
> +        return dom;
> +    }
> +    return NULL;
> +}
> +
> +static struct dpif_ipfix_domain *
> +dpif_ipfix_exporter_insert_domain(struct dpif_ipfix_exporter *exporter,
> +                                  const uint32_t domain_id) 
> OVS_REQUIRES(mutex)
> +{
> +    struct dpif_ipfix_domain *dom = xmalloc(sizeof *dom);
> +    dom->last_template_set_time = 0;
> +    hmap_insert(&exporter->domains, &dom->hmap_node, hash_int(domain_id, 0));
> +    return dom;
> +}
> +
> +static void
> +dpif_ipfix_exporter_del_domain(struct dpif_ipfix_exporter *exporter,
> +                               struct dpif_ipfix_domain *dom)
> +    OVS_REQUIRES(mutex)
> +{
> +    hmap_remove(&exporter->domains, &dom->hmap_node);
> +    free(dom);
> +}
> +
>  static struct dpif_ipfix_port *
>  dpif_ipfix_find_port(const struct dpif_ipfix *di,
>                       odp_port_t odp_port) OVS_REQUIRES(mutex)
> @@ -909,6 +959,7 @@ dpif_ipfix_bridge_exporter_init(struct 
> dpif_ipfix_bridge_exporter *exporter)
>
>  static void
>  dpif_ipfix_bridge_exporter_clear(struct dpif_ipfix_bridge_exporter *exporter)
> +    OVS_REQUIRES(mutex)
>  {
>      dpif_ipfix_exporter_clear(&exporter->exporter);
>      ofproto_ipfix_bridge_exporter_options_destroy(exporter->options);
> @@ -918,6 +969,7 @@ dpif_ipfix_bridge_exporter_clear(struct 
> dpif_ipfix_bridge_exporter *exporter)
>
>  static void
>  dpif_ipfix_bridge_exporter_destroy(struct dpif_ipfix_bridge_exporter 
> *exporter)
> +    OVS_REQUIRES(mutex)
>  {
>      dpif_ipfix_bridge_exporter_clear(exporter);
>      dpif_ipfix_exporter_destroy(&exporter->exporter);
> @@ -927,7 +979,7 @@ static void
>  dpif_ipfix_bridge_exporter_set_options(
>      struct dpif_ipfix_bridge_exporter *exporter,
>      const struct ofproto_ipfix_bridge_exporter_options *options,
> -    bool *options_changed)
> +    bool *options_changed) OVS_REQUIRES(mutex)
>  {
>      if (!options || sset_is_empty(&options->targets)) {
>          /* No point in doing any work if there are no targets. */
> @@ -1003,6 +1055,7 @@ dpif_ipfix_flow_exporter_init(struct 
> dpif_ipfix_flow_exporter *exporter)
>
>  static void
>  dpif_ipfix_flow_exporter_clear(struct dpif_ipfix_flow_exporter *exporter)
> +    OVS_REQUIRES(mutex)
>  {
>      dpif_ipfix_exporter_clear(&exporter->exporter);
>      ofproto_ipfix_flow_exporter_options_destroy(exporter->options);
> @@ -1011,6 +1064,7 @@ dpif_ipfix_flow_exporter_clear(struct 
> dpif_ipfix_flow_exporter *exporter)
>
>  static void
>  dpif_ipfix_flow_exporter_destroy(struct dpif_ipfix_flow_exporter *exporter)
> +    OVS_REQUIRES(mutex)
>  {
>      dpif_ipfix_flow_exporter_clear(exporter);
>      dpif_ipfix_exporter_destroy(&exporter->exporter);
> @@ -1020,7 +1074,7 @@ static bool
>  dpif_ipfix_flow_exporter_set_options(
>      struct dpif_ipfix_flow_exporter *exporter,
>      const struct ofproto_ipfix_flow_exporter_options *options,
> -    bool *options_changed)
> +    bool *options_changed) OVS_REQUIRES(mutex)
>  {
>      if (sset_is_empty(&options->targets)) {
>          /* No point in doing any work if there are no targets. */
> @@ -1071,6 +1125,7 @@ dpif_ipfix_flow_exporter_set_options(
>  static void
>  remove_flow_exporter(struct dpif_ipfix *di,
>                       struct dpif_ipfix_flow_exporter_map_node *node)
> +                     OVS_REQUIRES(mutex)
>  {
>      hmap_remove(&di->flow_exporter_map, &node->node);
>      dpif_ipfix_flow_exporter_destroy(&node->exporter);
> @@ -2000,6 +2055,7 @@ static void
>  ipfix_cache_update(struct dpif_ipfix_exporter *exporter,
>                     struct ipfix_flow_cache_entry *entry,
>                     enum ipfix_sampled_packet_type sampled_pkt_type)
> +                   OVS_REQUIRES(mutex)
>  {
>      struct ipfix_flow_cache_entry *old_entry;
>      size_t current_flows = 0;
> @@ -2811,14 +2867,36 @@ dpif_ipfix_flow_sample(struct dpif_ipfix *di, const 
> struct dp_packet *packet,
>      ovs_mutex_unlock(&mutex);
>  }
>
> +static bool
> +dpif_ipfix_should_send_template(struct dpif_ipfix_exporter *exporter,
> +                                const uint32_t observation_domain_id,
> +                                const uint32_t export_time_sec)
> +    OVS_REQUIRES(mutex)
> +{
> +    struct dpif_ipfix_domain *domain;
> +    domain = dpif_ipfix_exporter_find_domain(exporter,
> +                                             observation_domain_id);
> +    if (!domain) {
> +        /* First time we see this obs_domain_id. */
> +        domain = dpif_ipfix_exporter_insert_domain(exporter,
> +                                                   observation_domain_id);
> +    }
> +
> +    if ((domain->last_template_set_time + IPFIX_TEMPLATE_INTERVAL)
> +        <= export_time_sec) {
> +        domain->last_template_set_time = export_time_sec;
> +        return true;
> +    }
> +    return false;
> +}
> +
>  static void
>  dpif_ipfix_cache_expire(struct dpif_ipfix_exporter *exporter,
>                          bool forced_end, const uint64_t export_time_usec,
> -                        const uint32_t export_time_sec)
> +                        const uint32_t export_time_sec) OVS_REQUIRES(mutex)
>  {
>      struct ipfix_flow_cache_entry *entry;
>      uint64_t max_flow_start_timestamp_usec;
> -    bool template_msg_sent = false;
>      enum ipfix_flow_end_reason flow_end_reason;
>
>      if (ovs_list_is_empty(&exporter->cache_flow_start_timestamp_list)) {
> @@ -2844,25 +2922,28 @@ dpif_ipfix_cache_expire(struct dpif_ipfix_exporter 
> *exporter,
>              break;
>          }
>
> -        ovs_list_remove(&entry->cache_flow_start_timestamp_list_node);
> -        hmap_remove(&exporter->cache_flow_key_map,
> -                    &entry->flow_key_map_node);
> +        /* XXX: Make frequency of the (Options) Template and Exporter Process
> +         * Statistics transmission configurable.
> +         * Cf. IETF RFC 5101 Section 4.3. and 10.3.6. */
> +        if ((exporter->last_stats_sent_time + IPFIX_TEMPLATE_INTERVAL)
> +             <= export_time_sec) {
> +            exporter->last_stats_sent_time = export_time_sec;
> +            ipfix_send_exporter_data_msg(exporter, export_time_sec);
> +        }
>
> -         /* XXX: Make frequency of the (Options) Template and Exporter 
> Process
> -          * Statistics transmission configurable.
> -          * Cf. IETF RFC 5101 Section 4.3. and 10.3.6. */
> -        if (!template_msg_sent
> -            && (exporter->last_template_set_time + IPFIX_TEMPLATE_INTERVAL)
> -                <= export_time_sec) {
> +        if (dpif_ipfix_should_send_template(exporter,
> +                                            entry->flow_key.obs_domain_id,
> +                                            export_time_sec)) {
> +            VLOG_DBG("Sending templates for ObservationDomainID %"PRIu32,
> +                     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;
> -
> -            /* Send Exporter Process Statistics. */
> -            ipfix_send_exporter_data_msg(exporter, export_time_sec);
>          }
>
> +        ovs_list_remove(&entry->cache_flow_start_timestamp_list_node);
> +        hmap_remove(&exporter->cache_flow_key_map,
> +                    &entry->flow_key_map_node);
> +
>          /* XXX: Group multiple data records for the same obs domain id
>           * into the same message. */
>          ipfix_send_data_msg(exporter, export_time_sec, entry, 
> flow_end_reason);
> @@ -2883,7 +2964,7 @@ get_export_time_now(uint64_t *export_time_usec, 
> uint32_t *export_time_sec)
>
>  static void
>  dpif_ipfix_cache_expire_now(struct dpif_ipfix_exporter *exporter,
> -                            bool forced_end)
> +                            bool forced_end) OVS_REQUIRES(mutex)
>  {
>      uint64_t export_time_usec;
>      uint32_t export_time_sec;
> diff --git a/tests/system-traffic.at b/tests/system-traffic.at
> index 08c78ff57..84e3f090e 100644
> --- a/tests/system-traffic.at
> +++ b/tests/system-traffic.at
> @@ -7488,3 +7488,54 @@ OVS_WAIT_UNTIL([cat p2.pcap | grep -E "0x0050: *0000 
> *0000 *5002 *2000 *b85e *00
>
>  OVS_TRAFFIC_VSWITCHD_STOP
>  AT_CLEANUP
> +
> +AT_SETUP([ipfix - muliple domains])
> +AT_SKIP_IF([test $HAVE_TCPDUMP = no])
> +OVS_TRAFFIC_VSWITCHD_START()
> +
> +ADD_NAMESPACES(at_ns0, at_ns1)
> +
> +ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
> +ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24")
> +
> +dnl Create a dummy IPFIX collector just in localhost so that port seems open.
> +OVS_DAEMONIZE([nc -l -u 127.0.0.1 5555 > /dev/null], [nc.pid])
> +
> +OVS_DAEMONIZE([tcpdump -n -i lo udp port 5555 -w ipfix.pcap], [tcpdump.pid])
> +sleep 1
> +
> +dnl Configure a Flow Sample Collector Set with id = 1.
> +AT_CHECK([ovs-vsctl -- --id=@br get Bridge br0 \
> +                    -- --id=@i create IPFIX targets=\"127.0.0.1:5555\" \
> +                    -- create Flow_Sample_Collector_Set bridge=@br id=1 
> ipfix=@i],
> +         [ignore], [ignore])
> +
> +dnl Push flows that will allow communication between the network namespaces
> +dnl and sample all ICMP traffic with multiple observation domain IDs.
> +AT_DATA([flows.txt], [dnl
> +table=0,priority=100,in_port=ovs-p0,ip,icmp 
> actions=sample(probability=65535,collector_set_id=1,obs_domain_id=1),NORMAL
> +table=0,priority=100,in_port=ovs-p1,ip,icmp 
> actions=sample(probability=65535,collector_set_id=1,obs_domain_id=2),NORMAL
> +table=0,priority=0,actions=NORMAL
> +])
> +AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
> +
> +NS_CHECK_EXEC([at_ns0], [ping -q -c 3 -i 0.3 -w 2 10.1.1.2 | FORMAT_PING], 
> [0], [dnl
> +3 packets transmitted, 3 received, 0% packet loss, time 0ms
> +])
> +
> +sleep 1
> +kill $(cat tcpdump.pid)

Great unit test here, but I actually found that this was a race
condition. Tcpdump may not completely die and write the file by the
time that the next tcpdump reads it. I think this is why the test
failed on intel CI as well. I propose the following line:

wait $(cat tcpdump.pid)

> +
> +dnl Check templates (Flow ID = 0x02) where sent to ObservationDomain 1.
> +AT_CHECK([test $(tcpdump -r ipfix.pcap --count "udp port 5555 and 
> udp[[20:4]] = 0x00000001  and udp[[24:2]] = 0x02" 2>/dev/null | tr -d 
> "packets") -gt 0])
> +dnl Check that exactly 3 samples (Flow ID != 0x02) where sent to 
> ObservationDomain 1.
> +AT_CHECK([test $(tcpdump -r ipfix.pcap --count "udp port 5555 and 
> udp[[20:4]] = 0x00000001 and udp[[24:2]] != 0x02" 2>/dev/null | tr -d 
> "packets") -eq 3])
> +
> +dnl Check templates (Flow ID = 0x02) where sent to ObservationDomain 2.
> +AT_CHECK([test $(tcpdump -r ipfix.pcap --count "udp port 5555 and 
> udp[[20:4]] = 0x00000002  and udp[[24:2]] = 0x02" 2>/dev/null | tr -d 
> "packets") -gt 0])
> +dnl Check that exactly 3 samples (Flow ID != 0x02) where sent to 
> ObservationDomain 2.
> +AT_CHECK([test $(tcpdump -r ipfix.pcap --count "udp port 5555 and 
> udp[[20:4]] = 0x00000002 and udp[[24:2]] != 0x02" 2>/dev/null | tr -d 
> "packets") -eq 3])
> +
> +OVS_TRAFFIC_VSWITCHD_STOP
> +AT_CLEANUP
> +
> --
> 2.38.1
>

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to