diff options
author | Luca Muscariello <lumuscar@cisco.com> | 2022-03-30 22:29:28 +0200 |
---|---|---|
committer | Mauro Sardara <msardara@cisco.com> | 2022-03-31 19:51:47 +0200 |
commit | c46e5df56b67bb8ea7a068d39324c640084ead2b (patch) | |
tree | eddeb17785938e09bc42eec98ee09b8a28846de6 /hicn-light/src/hicn/core/fib_entry.c | |
parent | 18fa668f25d3cc5463417ce7df6637e31578e898 (diff) |
feat: boostrap hicn 22.02
The current patch provides several new features, improvements,
bug fixes and also complete rewrite of entire components.
- lib
The hicn packet parser has been improved with a new packet
format fully based on UDP. The TCP header is still temporarily
supported but the UDP header will replace completely the new hicn
packet format. Improvements have been made to make sure every
packet parsing operation is made via this library. The current
new header can be used as header between the payload and the
UDP header or as trailer in the UDP surplus area to be tested
when UDP options will start to be used.
- hicn-light
The portable packet forwarder has been completely rewritten from
scratch with the twofold objective to improve performance and
code size but also to drop dependencies such as libparc which is
now removed by the current implementation.
- hicn control
the control library is the agent that is used to program the
packet forwarders via their binary API. This component has
benefited from significant improvements in terms of interaction
model which is now event driven and more robust to failures.
- VPP plugin has been updated to support VPP 22.02
- transport
Major improvement have been made to the RTC protocol, to the
support of IO modules and to the security sub system. Signed
manifests are the default data authenticity and integrity framework.
Confidentiality can be enabled by sharing the encryption key to the
prod/cons layer. The library has been tested with group key based
applications such as broadcast/multicast and real-time on-line
meetings with trusted server keys or MLS.
- testing
Unit testing has been introduced using GoogleTest. One third of
the code base is covered by unit testing with priority on
critical features. Functional testing has also been introduce
using Docker, linux bridging and Robot Framework to define
test with Less Code techniques to facilitate the extension
of the coverage.
Co-authored-by: Mauro Sardara <msardara@cisco.com>
Co-authored-by: Jordan Augé <jordan.auge+fdio@cisco.com>
Co-authored-by: Michele Papalini <micpapal@cisco.com>
Co-authored-by: Angelo Mantellini <manangel@cisco.com>
Co-authored-by: Jacques Samain <jsamain@cisco.com>
Co-authored-by: Olivier Roques <oroques+fdio@cisco.com>
Co-authored-by: Enrico Loparco <eloparco@cisco.com>
Co-authored-by: Giulio Grassi <gigrassi@cisco.com>
Change-Id: I75d0ef70f86d921e3ef503c99271216ff583c215
Signed-off-by: Luca Muscariello <muscariello@ieee.org>
Signed-off-by: Mauro Sardara <msardara@cisco.com>
Diffstat (limited to 'hicn-light/src/hicn/core/fib_entry.c')
-rw-r--r-- | hicn-light/src/hicn/core/fib_entry.c | 588 |
1 files changed, 588 insertions, 0 deletions
diff --git a/hicn-light/src/hicn/core/fib_entry.c b/hicn-light/src/hicn/core/fib_entry.c new file mode 100644 index 000000000..616f12961 --- /dev/null +++ b/hicn-light/src/hicn/core/fib_entry.c @@ -0,0 +1,588 @@ +/* + * Copyright (c) 2021 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> + +#include <hicn/hicn-light/config.h> +#include <hicn/core/fib_entry.h> +#include <hicn/core/strategy.h> +#include <hicn/core/nameBitvector.h> + +#ifdef WITH_MAPME +#include <hicn/core/ticks.h> +#endif /* WITH_MAPME */ + +#ifdef WITH_POLICY +#include <hicn/core/forwarder.h> +#include <hicn/policy.h> + +#ifdef WITH_MAPME +#include <hicn/core/mapme.h> +#endif /* WITH_MAPME */ + +#endif /* WITH_POLICY */ + +#ifdef WITH_POLICY_STATS +#include <hicn/core/policy_stats.h> +#endif /* WITH_POLICY_STATS */ + +fib_entry_t *fib_entry_create(Name *name, strategy_type_t strategy_type, + strategy_options_t *strategy_options, + const forwarder_t *forwarder) { + assert(name); + assert(forwarder); + + fib_entry_t *entry = malloc(sizeof(fib_entry_t)); + if (!entry) goto ERR_MALLOC; + + memset(entry, 0, sizeof(*entry)); + name_Copy(name, &entry->name); + entry->nexthops = NEXTHOPS_EMPTY; + + fib_entry_add_strategy_options(entry, STRATEGY_TYPE_BESTPATH, NULL); + fib_entry_add_strategy_options(entry, STRATEGY_TYPE_REPLICATION, NULL); + fib_entry_set_strategy(entry, strategy_type, strategy_options); + +#ifdef WITH_MAPME + entry->user_data = NULL; + entry->user_data_release = NULL; +#endif /* WITH_MAPME */ + + entry->forwarder = forwarder; + +#ifdef WITH_POLICY + entry->policy = POLICY_EMPTY; +#endif /* WITH_POLICY */ + +#ifdef WITH_POLICY_STATS + entry->policy_stats = POLICY_STATS_EMPTY; + entry->policy_counters = POLICY_COUNTERS_EMPTY; +#endif /* WITH_POLICY_STATS */ + + return entry; + +ERR_MALLOC: + return NULL; +} + +void fib_entry_free(fib_entry_t *entry) { + assert(entry); +#ifdef WITH_MAPME + if (entry->user_data) entry->user_data_release(&entry->user_data); +#endif /* WITH_MAPME */ + free(entry); +} + +void fib_entry_add_strategy_options(fib_entry_t *entry, + strategy_type_t strategy_type, + strategy_options_t *strategy_options) { + // for the moment only the best path and replication strategies support + // strategy options return for the other strategyes + if (strategy_type != STRATEGY_TYPE_BESTPATH && + strategy_type != STRATEGY_TYPE_REPLICATION) + return; + + if (!strategy_options) { + if (strategy_type == STRATEGY_TYPE_BESTPATH) { + entry->strategy.options.bestpath.local_prefixes = NULL; + } else { + entry->strategy.options.replication.local_prefixes = NULL; + } + return; + } + + local_prefixes_t *new_prefixes; + local_prefixes_t *curr_prefixes; + + if (strategy_type == STRATEGY_TYPE_BESTPATH) { + new_prefixes = strategy_options->bestpath.local_prefixes; + curr_prefixes = entry->strategy.options.bestpath.local_prefixes; + + if (!curr_prefixes) { + entry->strategy.options.bestpath.local_prefixes = create_local_prefixes(); + curr_prefixes = entry->strategy.options.bestpath.local_prefixes; + } + } else { + new_prefixes = strategy_options->replication.local_prefixes; + curr_prefixes = entry->strategy.options.replication.local_prefixes; + + if (!curr_prefixes) { + entry->strategy.options.replication.local_prefixes = + create_local_prefixes(); + curr_prefixes = entry->strategy.options.replication.local_prefixes; + } + } + + local_prefixes_add_prefixes(curr_prefixes, new_prefixes); +} + +void fib_entry_set_strategy(fib_entry_t *entry, strategy_type_t strategy_type, + strategy_options_t *strategy_options) { + // Default strategy if undefined + if (!STRATEGY_TYPE_VALID(strategy_type)) strategy_type = STRATEGY_TYPE_RANDOM; + + if (entry->strategy.type == strategy_type) { // startegy alredy set + if (strategy_type != STRATEGY_TYPE_BESTPATH) { + return; // nothing to do + } else { + strategy_initialize(&entry->strategy, entry->forwarder); + return; + } + } + + entry->strategy.type = strategy_type; + if (strategy_options) entry->strategy.options = *strategy_options; + + strategy_initialize(&entry->strategy, entry->forwarder); +} + +#ifdef WITH_POLICY + +nexthops_t *fib_entry_filter_nexthops(fib_entry_t *entry, nexthops_t *nexthops, + unsigned ingress_id, bool prefer_local) { + assert(entry); + assert(nexthops); + + nexthops_reset(nexthops); + + // DEBUG("[fib_entry_filter_nexthops] num=%d/%d ingress_id=%d", + // nexthops_get_curlen(nexthops), nexthops_get_len(nexthops), + // ingress_id); + + /* Filter out ingress, down & administrative down faces */ + const connection_table_t *table = + forwarder_get_connection_table(entry->forwarder); + connection_t *conn; + unsigned nexthop, i; + uint_fast32_t flags; + + hicn_policy_t policy = fib_entry_get_policy(entry); + + nexthops_enumerate(nexthops, i, nexthop, { + conn = connection_table_at(table, nexthop); + nexthops_disable_if(nexthops, i, nexthop == ingress_id); + nexthops_disable_if(nexthops, i, + (connection_get_admin_state(conn) == FACE_STATE_DOWN)); + nexthops_disable_if(nexthops, i, + (connection_get_state(conn) == FACE_STATE_DOWN)); + }); + + // DEBUG("After pruning, num=%d/%d", nexthops_get_curlen(nexthops), + // nexthops_get_len(nexthops)); + + if (prefer_local) { + /* Backup flags and cur_len*/ + flags = nexthops->flags; + size_t cur_len = nexthops_get_curlen(nexthops); + + /* Filter local */ + nexthops_enumerate(nexthops, i, nexthop, { + conn = connection_table_at(table, nexthop); + nexthops_disable_if(nexthops, i, (!connection_is_local(conn))); + }); + + /* Local faces have priority */ + if (nexthops_get_curlen(nexthops) > 0) return nexthops; + + nexthops->flags = flags; + nexthops->cur_elts = cur_len; + } + + /* Filter out local */ + nexthops_enumerate(nexthops, i, nexthop, { + conn = connection_table_at(table, nexthop); + nexthops_disable_if(nexthops, i, (connection_is_local(conn))); + + /* Policy filtering : next hops */ + nexthops_disable_if( + nexthops, i, + (policy.tags[POLICY_TAG_WIRED].state == POLICY_STATE_REQUIRE) && + (!connection_has_tag(conn, POLICY_TAG_WIRED))); + nexthops_disable_if( + nexthops, i, + (policy.tags[POLICY_TAG_WIRED].state == POLICY_STATE_PROHIBIT) && + (connection_has_tag(conn, POLICY_TAG_WIRED))); + nexthops_disable_if( + nexthops, i, + (policy.tags[POLICY_TAG_WIFI].state == POLICY_STATE_REQUIRE) && + (!connection_has_tag(conn, POLICY_TAG_WIFI))); + nexthops_disable_if( + nexthops, i, + (policy.tags[POLICY_TAG_WIFI].state == POLICY_STATE_PROHIBIT) && + (connection_has_tag(conn, POLICY_TAG_WIFI))); + nexthops_disable_if( + nexthops, i, + (policy.tags[POLICY_TAG_CELLULAR].state == POLICY_STATE_REQUIRE) && + (!connection_has_tag(conn, POLICY_TAG_CELLULAR))); + nexthops_disable_if( + nexthops, i, + (policy.tags[POLICY_TAG_CELLULAR].state == POLICY_STATE_PROHIBIT) && + (connection_has_tag(conn, POLICY_TAG_CELLULAR))); + nexthops_disable_if( + nexthops, i, + (policy.tags[POLICY_TAG_TRUSTED].state == POLICY_STATE_REQUIRE) && + (!connection_has_tag(conn, POLICY_TAG_TRUSTED))); + nexthops_disable_if( + nexthops, i, + (policy.tags[POLICY_TAG_TRUSTED].state == POLICY_STATE_PROHIBIT) && + (connection_has_tag(conn, POLICY_TAG_TRUSTED))); + }); + + if (nexthops_get_curlen(nexthops) == 0) { + DEBUG("After REQUIRE/PROHIBIT pruning, num=%d/%d", + nexthops_get_curlen(nexthops), nexthops_get_len(nexthops)); + return nexthops; + } + + /* We have at least one matching next hop, implement heuristic */ + + /* + * As VPN connections might trigger duplicate uses of one interface, we start + * by filtering out interfaces based on trust status. + */ + flags = nexthops->flags; + + if ((policy.tags[POLICY_TAG_TRUSTED].state == POLICY_STATE_REQUIRE) || + (policy.tags[POLICY_TAG_TRUSTED].state == POLICY_STATE_PREFER)) { + /* Try to filter out NON TRUSTED faces */ + nexthops_enumerate(nexthops, i, nexthop, { + conn = connection_table_at(table, nexthop); + nexthops_disable_if(nexthops, i, + (!connection_has_tag(conn, POLICY_TAG_TRUSTED))); + }); + + if ((nexthops_get_curlen(nexthops) == 0) && + (policy.tags[POLICY_TAG_TRUSTED].state == POLICY_STATE_REQUIRE)) { + return nexthops; + } + + } else { + /* Try to filter out TRUSTED faces */ + nexthops_enumerate(nexthops, i, nexthop, { + conn = connection_table_at(table, nexthop); + nexthops_disable_if(nexthops, i, + (connection_has_tag(conn, POLICY_TAG_TRUSTED))); + }); + } + + if (nexthops_get_curlen(nexthops) == 0) nexthops->flags = flags; + + /* Other preferences */ + if (policy.tags[POLICY_TAG_WIRED].state == POLICY_STATE_AVOID) { + nexthops_enumerate(nexthops, i, nexthop, { + conn = connection_table_at(table, nexthop); + nexthops_disable_if(nexthops, i, + connection_has_tag(conn, POLICY_TAG_WIRED)); + }); + if (nexthops_get_curlen(nexthops) == 0) nexthops->flags = flags; + } + if (policy.tags[POLICY_TAG_WIFI].state == POLICY_STATE_AVOID) { + nexthops_enumerate(nexthops, i, nexthop, { + conn = connection_table_at(table, nexthop); + nexthops_disable_if(nexthops, i, + connection_has_tag(conn, POLICY_TAG_WIFI)); + }); + if (nexthops_get_curlen(nexthops) == 0) nexthops->flags = flags; + } + if (policy.tags[POLICY_TAG_CELLULAR].state == POLICY_STATE_AVOID) { + nexthops_enumerate(nexthops, i, nexthop, { + conn = connection_table_at(table, nexthop); + nexthops_disable_if(nexthops, i, + connection_has_tag(conn, POLICY_TAG_CELLULAR)); + }); + if (nexthops_get_curlen(nexthops) == 0) nexthops->flags = flags; + } + + if (policy.tags[POLICY_TAG_WIRED].state == POLICY_STATE_PREFER) { + nexthops_enumerate(nexthops, i, nexthop, { + conn = connection_table_at(table, nexthop); + nexthops_disable_if(nexthops, i, + !connection_has_tag(conn, POLICY_TAG_WIRED)); + }); + if (nexthops_get_curlen(nexthops) == 0) nexthops->flags = flags; + } + if (policy.tags[POLICY_TAG_WIFI].state == POLICY_STATE_PREFER) { + nexthops_enumerate(nexthops, i, nexthop, { + conn = connection_table_at(table, nexthop); + nexthops_disable_if(nexthops, i, + !connection_has_tag(conn, POLICY_TAG_WIFI)); + }); + if (nexthops_get_curlen(nexthops) == 0) nexthops->flags = flags; + } + if (policy.tags[POLICY_TAG_CELLULAR].state == POLICY_STATE_PREFER) { + nexthops_enumerate(nexthops, i, nexthop, { + conn = connection_table_at(table, nexthop); + nexthops_disable_if(nexthops, i, + !connection_has_tag(conn, POLICY_TAG_CELLULAR)); + }); + if (nexthops_get_curlen(nexthops) == 0) nexthops->flags = flags; + } + + DEBUG("[fib_entry_filter_nexthops] before face priority num=%d/%d", + nexthops_get_curlen(nexthops), nexthops_get_len(nexthops)); + + /* Priority */ + uint32_t max_priority = 0; + nexthops_foreach(nexthops, nexthop, { + conn = connection_table_at(table, nexthop); + uint32_t priority = connection_get_priority(conn); + if (priority > max_priority) max_priority = priority; + }); + nexthops_enumerate(nexthops, i, nexthop, { + conn = connection_table_at(table, nexthop); + nexthops_disable_if(nexthops, i, + connection_get_priority(conn) < max_priority); + }); + + DEBUG("[fib_entry_filter_nexthops] result num=%d/%d", + nexthops_get_curlen(nexthops), nexthops_get_len(nexthops)); + + return nexthops; +} + +/* + * Update available next hops following policy update. + * + * The last nexthop parameter is only used if needed, otherwise the pointer to + * fib entry is returned to avoid an useless copy + */ +nexthops_t *fib_entry_get_available_nexthops(fib_entry_t *entry, + unsigned ingress_id, + nexthops_t *new_nexthops) { + // DEBUG("[fib_entry_get_available_nexthops]"); + + connection_table_t *table = forwarder_get_connection_table(entry->forwarder); + + /* + * Give absolute preference to local faces, with no policy, unless + * ingress_id == ~0, which means we are searching faces on which to + * advertise our prefix + */ + if (ingress_id == ~0) { + assert(new_nexthops); + /* We create a nexthop structure based on connections */ + // XXX This should be done close to where it is needed + connection_t *connection; + connection_table_foreach(table, connection, { + if (connection_is_local(connection)) continue; + new_nexthops->elts[nexthops_get_len(new_nexthops)] = + connection_table_get_connection_id(table, connection); + nexthops_inc(new_nexthops); + }); + +#ifdef WITH_POLICY + return fib_entry_filter_nexthops(entry, new_nexthops, ingress_id, false); +#else + return new_nexthops; +#endif + } + +#ifdef WITH_POLICY + return fib_entry_filter_nexthops(entry, fib_entry_get_nexthops(entry), + ingress_id, true); +#else + return fib_entry_get_nexthops(entry); +#endif +} + +hicn_policy_t fib_entry_get_policy(const fib_entry_t *entry) { + return entry->policy; +} + +void fib_entry_set_policy(fib_entry_t *entry, hicn_policy_t policy) { + entry->policy = policy; + +#ifdef WITH_MAPME + /* + * Skip entries that do not correspond to a producer ( / have a locally + * served prefix / have no local connection as next hop) + */ + if (!fib_entry_has_local_nexthop(entry)) return; + mapme_t *mapme = forwarder_get_mapme(entry->forwarder); + mapme_set_all_adjacencies(mapme, entry); +#endif /* WITH_MAPME */ +} + +#endif /* WITH_POLICY */ + +void fib_entry_nexthops_add(fib_entry_t *entry, unsigned nexthop) { + /* + * Nexthop is added to both: + * - fib_entry: it is added to the nexthops_t struct, in the elts array. + * - strategy: only used to eventually initialize internal state, might be + * empty like in the random strategy. + */ + off_t id = nexthops_add(&entry->nexthops, nexthop); + strategy_add_nexthop(&entry->strategy, &entry->nexthops, id); +} + +void fib_entry_nexthops_remove(fib_entry_t *entry, unsigned nexthop) { + off_t id = nexthops_remove(&entry->nexthops, nexthop); + strategy_remove_nexthop(&entry->strategy, &entry->nexthops, id); +} + +nexthops_t *fib_entry_get_nexthops_from_strategy(fib_entry_t *entry, + const msgbuf_t *msgbuf, + bool is_retransmission) { + assert(entry); + assert(msgbuf); + + // DEBUG("[fib_entry_get_nexthops_from_strategy]"); + + const policy_stats_mgr_t *mgr = + forwarder_get_policy_stats_mgr(entry->forwarder); + assert(mgr); + + /* Filtering */ + nexthops_t *nexthops = fib_entry_get_available_nexthops( + entry, msgbuf_get_connection_id(msgbuf), NULL); + if (nexthops_get_curlen(nexthops) == 0) return nexthops; + +#ifdef WITH_POLICY_STATS + /* + * Update statistics about loss rates. We only detect losses upon + * retransmissions, and assume for the computation that the candidate set of + * output faces is the same as previously (i.e. does not take into account + * event such as face up/down, policy update, etc. Otherwise we would need to + * know what was the previous choice ! + */ + if (is_retransmission) + policy_stats_on_retransmission(mgr, &entry->policy_counters, nexthops); +#endif /* WITH_POLICY_STATS */ + + /* + * NOTE: We might want to call a forwarding strategy even with no nexthop to + * take a fallback decision. + */ + if (nexthops_get_curlen(nexthops) == 0) return nexthops; + +#ifdef WITH_POLICY + + /* + * Filter out nexthops with lowest strategy priority. + * Initializing at 0 allows to disable nexthops with a negative priority + */ + unsigned max_priority = 0; + unsigned i; + nexthop_t nexthop; + nexthops_enumerate(nexthops, i, nexthop, { + (void)nexthop; + int priority = nexthops->state[i].priority; + if (priority > max_priority) max_priority = priority; + }); + nexthops_enumerate(nexthops, i, nexthop, { + int priority = nexthops->state[i].priority; + nexthops_disable_if(nexthops, i, (priority < max_priority)); + }); + + /* + * If multipath is disabled, we don't offer much choice to the forwarding + * strategy, but still go through it for accounting purposes. + */ + hicn_policy_t policy = fib_entry_get_policy(entry); + if ((policy.tags[POLICY_TAG_MULTIPATH].state == POLICY_STATE_PROHIBIT) || + (policy.tags[POLICY_TAG_MULTIPATH].state == POLICY_STATE_AVOID)) { + DEBUG( + "[fib_entry_get_nexthops_from_strategy] select single nexthops due to " + "multipath policy"); + nexthops_select_first(nexthops); + } + +#endif /* WITH_POLICY */ + + // DEBUG("[fib_entry_get_nexthops_from_strategy] calling lookup_nethops " + // "on strategy (num=%d)", nexthops_get_len(nexthops)); + return strategy_lookup_nexthops(&entry->strategy, nexthops, msgbuf); +} + +void fib_entry_on_data(fib_entry_t *entry, nexthops_t *data_nexthops, + const msgbuf_t *msgbuf, Ticks pitEntryCreation, + Ticks objReception) { + assert(entry); + assert(data_nexthops); + assert(msgbuf); + +#ifdef WITH_POLICY_STATS + if (pitEntryCreation != 0) { // if pitEntryCreation we no match in the pit + // was found + const policy_stats_mgr_t *mgr = + forwarder_get_policy_stats_mgr(entry->forwarder); + Ticks rtt = objReception - pitEntryCreation; + policy_stats_on_data(mgr, &entry->policy_stats, &entry->policy_counters, + data_nexthops, msgbuf, rtt); + } +#endif /* WITH_POLICY_STATS */ + + if (pitEntryCreation != 0 || + (fib_entry_strategy_type(entry) == STRATEGY_TYPE_BESTPATH)) { + strategy_on_data(&entry->strategy, &entry->nexthops, data_nexthops, msgbuf, + pitEntryCreation, objReception); + } +} + +void fib_entry_on_timeout(fib_entry_t *entry, + const nexthops_t *timeout_nexthops) { + assert(entry); + assert(timeout_nexthops); + +#ifdef WITH_POLICY_STATS + const policy_stats_mgr_t *mgr = + forwarder_get_policy_stats_mgr(entry->forwarder); + policy_stats_on_timeout(mgr, &entry->policy_counters, timeout_nexthops); +#endif /* WITH_POLICY_STATS */ + + strategy_on_timeout(&entry->strategy, &entry->nexthops, timeout_nexthops); +} + +const Name *fib_entry_get_prefix(const fib_entry_t *entry) { + assert(entry); + return &(entry->name); +} + +/* + * Return true if we have at least one local connection as next hop + */ +bool fib_entry_has_local_nexthop(const fib_entry_t *entry) { + connection_table_t *table = forwarder_get_connection_table(entry->forwarder); + + unsigned nexthop; + nexthops_foreach(fib_entry_get_nexthops(entry), nexthop, { + const connection_t *conn = connection_table_at(table, nexthop); + /* Ignore non-local connections */ + if (!connection_is_local(conn)) continue; + return true; + }); + return false; +} + +#ifdef WITH_MAPME + +void *fib_entry_get_user_data(const fib_entry_t *entry) { + assert(entry); + + return entry->user_data; +} + +void fib_entry_set_user_data(fib_entry_t *entry, const void *user_data, + void (*user_data_release)(void **)) { + assert(entry); + assert(user_data); + assert(user_data_release); + + entry->user_data = (void *)user_data; + entry->user_data_release = user_data_release; +} + +#endif /* WITH_MAPME */ |