From 4fcfe184d9444ef25d3e5ab5995a06c8bed8b9d2 Mon Sep 17 00:00:00 2001 From: Alberto Leiva Popper Date: Fri, 11 Aug 2023 10:14:35 -0600 Subject: [PATCH] Fix joold advertise Had to rewrite kernelside joold again. New, better design. Implements joold advertise (because it somehow used to be a no-op), while keeping busy looping and packet allocations outside of the spinlock. Deprecates ss-max-payload in favor of ss-max-sessions-per-packet, partly because the latter is more intuitive (hopefully), and partly because the former was trickier with the new implementation. Also, please note that the ss-capacity warning changed: > joold: Too many sessions deferred! I need to drop some; sorry. Also tweaked the documentation a little. For some reason, it was parroting that the channel between joolds is TCP, when it's supposed to be UDP. Also patched some broken links. Fixes #410. --- docs/en/config-atomic.md | 3 +- docs/en/session-synchronization.md | 18 +- docs/en/usr-flags-global.md | 47 ++- src/common/config.c | 1 + src/common/config.h | 14 +- src/common/constants.h | 19 + src/common/global.c | 9 +- src/mod/common/db/global.c | 1 + src/mod/common/joold.c | 419 ++++++++------------ src/mod/common/steps/send_packet.h | 2 +- src/mod/common/types.h | 13 + src/usr/nl/joold.c | 5 - test/unit/joold/joold_test.c | 592 ++++++++++++++++++++++++----- test/unit/types/types_test.c | 110 ++++++ 14 files changed, 880 insertions(+), 373 deletions(-) diff --git a/docs/en/config-atomic.md b/docs/en/config-atomic.md index 6dd203995..4cc0aec95 100644 --- a/docs/en/config-atomic.md +++ b/docs/en/config-atomic.md @@ -176,7 +176,8 @@ Also, `pool6` is mandatory and immutable (as normal). It must be set during inst "ss-flush-asap": true, "ss-flush-deadline": 2000, "ss-capacity": 512, - "ss-max-payload": 1452 + "ss-max-payload": 1452, + "ss-max-sessions-per-packet": 10 }, "pool4": [ diff --git a/docs/en/session-synchronization.md b/docs/en/session-synchronization.md index 13c5437a5..dfff1207a 100644 --- a/docs/en/session-synchronization.md +++ b/docs/en/session-synchronization.md @@ -84,14 +84,14 @@ Why are the daemons necessary? because kernel modules cannot open IP sockets; at Synchronizing sessions is _all_ the daemons do; the traffic redirection part is delegated to other protocols. [Keepalived](http://www.keepalived.org/) is the implementation that takes care of this in the sample configuration below, but any other load balancer should also get the job done. -In this proposed/inauguratory implementation, SS traffic is distributed through an IPv4 or IPv6 unencrypted TCP connection. You might want to cast votes on the issue tracker or propose code if you favor some other solution. +In this proposed/inauguratory implementation, SS traffic is distributed through an IPv4 or IPv6 unencrypted UDP connection. You might want to cast votes on the issue tracker or propose code if you favor some other solution. There are two operation modes in which SS can be used: 1. Active/Passive: One Jool instance serves traffic at any given time, the other ones serve as backup. The load balancer redirects traffic when the current active NAT64 dies. 2. Active/Active: All Jool instances serve traffic. The load balancer distributes traffic so no NAT64 is too heavily encumbered. -Active/Active is discouraged because the session synchronization across Jool instances does not lock and is not instantaneous; if the translating traffic is faster, the session tables can end up desynchronized. Users will perceive this mainly as difficulties opening connections through the translators. +> ![Warning!](../images/warning.svg) Active/Active is discouraged because the session synchronization across Jool instances does not lock and is not instantaneous; if the translating traffic is faster, the session tables can end up desynchronized. Users will perceive this mainly as difficulties opening connections through the translators. It is also important to note that SS is relatively resource-intensive; its traffic is not only _extra_ traffic, but it must also do two full U-turns to userspace before reaching its destination: @@ -167,6 +167,8 @@ This is generally usual boilerplate Jool mumbo jumbo. `2001:db8::4-5` and `192.0 It is important to note that every translator instance must have the same configuration as the other ones before SS is started. Make sure you've manually synchronized pool6, pool4, static BIB entries, the global variables and any other internal Jool configuration you might have. +The clocks don't need to be synchronized. + ### Jool Instance Because forking SS sessions on every translated packet is not free (performance-wise), jool instances are not SS-enabled by default. The fact that the module and the daemon are separate binaries enhances the importance of this fact; starting the daemon is not, by itself, enough to get sessions synchronized. @@ -334,7 +336,7 @@ vrrp_instance VI_1 { 2001:db8::1/96 } - # J is our secondary NAT64; start in the "BACKUP" state. + # K is our secondary NAT64; start in the "BACKUP" state. state BACKUP # Will only upgrade to master if this is the highest priority node that # is alive. @@ -472,11 +474,11 @@ That's all. ### `jool` -1. [`ss-enabled`](usr-flags-global.html#--ss-enabled) -2. [`ss-flush-asap`](usr-flags-global.html#--ss-flush-asap) -3. [`ss-flush-deadline`](usr-flags-global.html#--ss-flush-deadline) -4. [`ss-capacity`](usr-flags-global.html#--ss-capacity) -5. [`ss-max-payload`](usr-flags-global.html#--ss-max-payload) +1. [`ss-enabled`](usr-flags-global.html#ss-enabled) +2. [`ss-flush-asap`](usr-flags-global.html#ss-flush-asap) +3. [`ss-flush-deadline`](usr-flags-global.html#ss-flush-deadline) +4. [`ss-capacity`](usr-flags-global.html#ss-capacity) +5. [`ss-max-sessions-per-packet`](usr-flags-global.html#ss-max-sessions-per-packet) ### `joold` diff --git a/docs/en/usr-flags-global.md b/docs/en/usr-flags-global.md index ada666232..180f10cb8 100644 --- a/docs/en/usr-flags-global.md +++ b/docs/en/usr-flags-global.md @@ -46,6 +46,7 @@ title: global Mode 25. [`ss-flush-deadline`](#ss-flush-deadline) 26. [`ss-capacity`](#ss-capacity) 27. [`ss-max-payload`](#ss-max-payload) + 28. [`ss-max-sessions-per-packet`](#ss-max-sessions-per-packet) ## Description @@ -636,7 +637,7 @@ In the Active/Active scenario in particular, this essentially means that every t In the Active/Passive model, on the other hand, this level of compulsive replication is rather undesired. Since a single big packet is easier to send than the equivalent many smaller ones, it is preferable to queue the session updates and wrap a bunch of them in a single big multicast, thereby reducing the SS packet-to-translated packet ratio and CPU overhead. This is appropriate in Active/Passive mode because the backup NAT64s are not expected to receive traffic in the near future, and losing a few recent queued session updates on crash is no big deal when the sizeable rest of the database has already been dispatched. -Sessions will be queued until the maximum packet size is reached or a timer expires. The maximum packet size is defined by [`ss-max-payload`](#ss-max-payload) and the duration of the timer is [`ss-flush-deadline`](#ss-flush-deadline). +Sessions will be queued until the maximum packet size is reached or a timer expires. The maximum packet size is defined by [`ss-max-sessions-per-packet`](#ss-max-sessions-per-packet) and the duration of the timer is [`ss-flush-deadline`](#ss-flush-deadline). As a rule of thumb, you might think of this option as an "Active/Active vs Active/Passive" switch; in the former this flag is practically mandatory, while in the latter it is needlessly CPU-taxing. (But still legal, which explains the default.) @@ -670,7 +671,7 @@ If SS cannot keep up with the amount of traffic it needs to multicast, this maxi Watch out for this message in the kernel logs: - Joold: Too many sessions deferred! I need to drop some; sorry. + joold: Too many sessions deferred! I need to drop some; sorry. ### `ss-max-payload` @@ -679,23 +680,47 @@ Watch out for this message in the kernel logs: - Modes: Stateful NAT64 only - Source: [Issue 113]({{ site.repository-url }}/issues/113) -Number of bytes per packet Jool can fit SS content (header and sessions) in. +Deprecated; does nothing as of Jool 4.1.11. -`joold` (the daemon) is (aside from a few validations) just a bridge; it receives bytes from the kernel module, wraps them in a TCP packet and sends it to other daemons, who similarly pass the bytes untouched. They are not even aware that those bytes contain sessions. +### `ss-max-sessions-per-packet` + +- Type: Integer +- Default: 10 +- Modes: Stateful NAT64 only +- Source: [Issue 113]({{ site.repository-url }}/issues/113), [issue 410]({{ site.repository-url }}/issues/410) + +`joold` (the daemon) is (aside from a few validations) just a bridge; it receives bytes from the kernel module, wraps them in a UDP packet and sends it to other daemons, who similarly pass the bytes untouched. They are not even aware that those bytes contain sessions. Since fragmentation is undesired, and since the kernel module is (to all intents and purposes) the one that's building the SS packets, it should not exceed the PMTU while doing so. The module has little understanding of the "multicast" network though, so it lacks fancy utilities to compute it. That's where this option comes in. -The default value is based on 1500, the typical minimum MTU one can be forgiven to expect in a controlled network. (The SS "multicast" network.) +`ss-max-sessions-per-packet` is the maximum number of sessions joold will transfer per joold packet. You want to maximize it as much as possible, while avoiding IPv4/IPv6 fragmentation. + +When `ss-flush-asap false`, Jool will pretty much always wait until this number of sessions has been collected before sending a joold packet. On `ss-flush-asap true`, it will tend to send sessions more eagerly, but will still strictly constrain itself to the maximum. -The equation is +The optimal value is `floor((M - I - U - R) / S)`, where - ss-max-payload = MTU - IP header size - UDP header size +1. `M` is the MTU of the path between your joolds (usually 1500), +2. `I` is the size of the header of the IP protocol your joolds will use to exchange sessions (40 for IPv6, 20 for IPv4), +3. `U` is the size of the UDP header (8), +4. `R` is the size of a Netlink attribute header, +5. and `S` is the size of a serialized session. -Since I don't know whether your network is IPv4 or IPv6, the default value was inferred from the following numbers: +`R` should be constant (unless something has gone horribly wrong), but ultimately depends on your kernel version. `S` depends on your Jool version, and should only change between minor updates (ie. when the first or second numbers of Jool's version change). One way to find both is by running Jool's `joold` unit test: - default = 1500 - max(20, 40) - 8 = 1452 +``` +$ cd /path/to/jool/source +$ cd test/unit/joold +$ make +$ sudo make test | head +... +Jool: Netlink attribute header size: 4 +Jool: Serialized session size: 140 +... +``` -In Jool 3.5.0, The joold header spans 16 bytes and each session is 64 bytes long, which means Jool will be able to fit 22 sessions per SS packet by default. +So the default value came out of -Feel free to adjust your MTU to reduce CPU overhead further in Active/Passive setups. (See [`ss-flush-asap`](#ss-flush-asap).) +``` +floor((1500 - max(20, 40) - 8 - 4) / 140) +``` diff --git a/src/common/config.c b/src/common/config.c index ef5df7925..5365fb357 100644 --- a/src/common/config.c +++ b/src/common/config.c @@ -123,6 +123,7 @@ struct nla_policy nat64_globals_policy[JNLAG_COUNT] = { [JNLAG_JOOLD_FLUSH_DEADLINE] = { .type = NLA_U32 }, [JNLAG_JOOLD_CAPACITY] = { .type = NLA_U32 }, [JNLAG_JOOLD_MAX_PAYLOAD] = { .type = NLA_U32 }, + [JNLAG_JOOLD_MAX_SESSIONS_PER_PACKET] = { .type = NLA_U32 }, }; int iname_validate(const char *iname, bool allow_null) diff --git a/src/common/config.h b/src/common/config.h index bc7dc6b6b..4799c0875 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -252,6 +252,7 @@ enum joolnl_attr_global { JNLAG_JOOLD_FLUSH_DEADLINE, JNLAG_JOOLD_CAPACITY, JNLAG_JOOLD_MAX_PAYLOAD, + JNLAG_JOOLD_MAX_SESSIONS_PER_PACKET, /* Needs to be last */ JNLAG_COUNT, @@ -309,6 +310,8 @@ struct joolnlhdr { char iname[INAME_MAX_SIZE]; }; +#define JOOLNL_HDRLEN NLMSG_ALIGN(sizeof(struct joolnlhdr)) + struct config_prefix6 { bool set; /** Please note that this could be garbage; see above. */ @@ -452,22 +455,23 @@ struct joold_config { */ __u32 capacity; + /** Deprecated as of 4.1.11, does nothing. */ + __u32 max_payload; + /** - * Maximum amount of bytes joold should send per packet, excluding - * IP/UDP headers. + * Maximum number of sessions joold should send per packet. * * This exists because userspace joold sends sessions via UDP. UDP is * rather packet-oriented, as opposed to stream-oriented, so it doesn't * discover PMTU and instead tends to fragment when we send too many * sessions per packet. Which is bad. * - * So the user, after figuring out the MTU, can tweak this number to - * prevent fragmentation. + * So the user can tweak this number to prevent fragmentation. * * We should probably handle this ourselves but it sounds like a lot of * code. (I guess I'm missing something.) */ - __u32 max_payload; + __u32 max_sessions_per_pkt; }; /** diff --git a/src/common/constants.h b/src/common/constants.h index 4cecf5ccf..8441bf6a6 100644 --- a/src/common/constants.h +++ b/src/common/constants.h @@ -94,6 +94,25 @@ */ #define DEFAULT_JOOLD_MAX_PAYLOAD 1452 +/** + * This needs to be + * + * floor( + * ( + * Typical MTU + * - max(IPv4 header size, IPv6 header size) + * - UDP header size + * - root attribute header size + * ) / ( + * serialized session size + * ) + * ) + * + * The root attribute header size and serialized session size need to be + * computed the hard way. Run the joold unit test to find them in dmesg. + */ +#define DEFAULT_JOOLD_MAX_SESSIONS_PER_PKT ((1500 - 40 - 8 - 4) / 140) + /* -- IPv6 Pool -- */ #define WELL_KNOWN_PREFIX "64:ff9b::/96" diff --git a/src/common/global.c b/src/common/global.c index f574a8249..88bfde22b 100644 --- a/src/common/global.c +++ b/src/common/global.c @@ -1000,9 +1000,16 @@ static const struct joolnl_global_meta globals_metadata[] = { .id = JNLAG_JOOLD_MAX_PAYLOAD, .name = "ss-max-payload", .type = >_uint32, - .doc = "Maximum amount of bytes joold should send per packet.", + .doc = "Deprecated; does nothing as of Jool 4.1.11.", .offset = offsetof(struct jool_globals, nat64.joold.max_payload), .xt = XT_NAT64, + }, { + .id = JNLAG_JOOLD_MAX_SESSIONS_PER_PACKET, + .name = "ss-max-sessions-per-packet", + .type = >_uint32, + .doc = "Maximum number of sessions to send, per joold packet.", + .offset = offsetof(struct jool_globals, nat64.joold.max_sessions_per_pkt), + .xt = XT_NAT64, }, }; diff --git a/src/mod/common/db/global.c b/src/mod/common/db/global.c index d53e7ba6d..fa1a22137 100644 --- a/src/mod/common/db/global.c +++ b/src/mod/common/db/global.c @@ -110,6 +110,7 @@ int globals_init(struct jool_globals *config, xlator_type type, config->nat64.joold.flush_deadline = 1000 * DEFAULT_JOOLD_DEADLINE; config->nat64.joold.capacity = DEFAULT_JOOLD_CAPACITY; config->nat64.joold.max_payload = DEFAULT_JOOLD_MAX_PAYLOAD; + config->nat64.joold.max_sessions_per_pkt = DEFAULT_JOOLD_MAX_SESSIONS_PER_PKT; break; default: diff --git a/src/mod/common/joold.c b/src/mod/common/joold.c index b22b5d924..0df30490b 100644 --- a/src/mod/common/joold.c +++ b/src/mod/common/joold.c @@ -14,19 +14,9 @@ #define GLOBALS(xlator) (xlator->globals.nat64.joold) -/* - * Remember to include in the user documentation: - * - * - pool4 and static BIB entries have to be synchronized manually. - * - Apparently, users do not actually need to keep clocks in sync. - */ - -struct joold_pkt { - struct sk_buff *skb; /** The packet we'll send to other Jools */ - struct joolnlhdr *jhdr; /** Quick pointer to @skb's Jool header */ - struct nlattr *root; /** Quick pointer to @skb's first attribute */ - bool has_sessions; /* Has at least one session been inserted to @skb? */ - bool full; /** Are we unable to insert more sessions into @skb? */ +struct counted_list { + struct list_head list; + unsigned int count; }; /** @@ -36,27 +26,11 @@ struct joold_pkt { */ #define JQF_ACK_RECEIVED (1 << 0) #define JQF_AD_ONGOING (1 << 1) /** Advertisement requested by user? */ -#define JQF_OFFSET_SET (1 << 2) /** ad.offset set? */ struct joold_queue { - unsigned int flags; /* JQF */ + unsigned int flags; /** JQF */ - /** The packet we'll send to the other Jools. */ - struct joold_pkt pkt; - - struct { - /** Additional sessions that don't fit in @pkt yet. */ - struct list_head list; - /** Number of nodes in @list. */ - unsigned int count; - } deferred; - - struct { - /** IPv4 ID of the session sent in the last packet. */ - struct taddr4_tuple offset; - /** Protocol of the session sent in the last packet. */ - l4_protocol proto; - } ad; /* Advertisement; only if @flags & JQF_AD_ONGOING. */ + struct counted_list deferred; /** Queued sessions */ /** * Jiffy at which the last batch of sessions was sent. @@ -65,16 +39,15 @@ struct joold_queue { */ unsigned long last_flush_time; - /** - * Length in bytes of a packeted session. Needed to enforce - * ss-max-payload. - */ - size_t session_size; - spinlock_t lock; struct kref refs; }; +struct ad_arg { + struct joold_queue *queue; + struct list_head *ready; +}; + /** * A session or group of sessions that need to be transmitted to other Jool * instances in the near future. @@ -88,19 +61,18 @@ struct deferred_session { static struct kmem_cache *deferred_cache; #define ALLOC_DEFERRED \ - wkmem_cache_alloc("joold deferred", deferred_cache, GFP_ATOMIC) + wkmem_cache_alloc("joold session", deferred_cache, GFP_ATOMIC) #define FREE_DEFERRED(deferred) \ - wkmem_cache_free("joold deferred", deferred_cache, deferred) + wkmem_cache_free("joold session", deferred_cache, deferred) -static struct deferred_session *first_deferred(struct joold_queue *queue) +static struct deferred_session *first_deferred(struct list_head *list) { - return list_first_entry(&queue->deferred.list, struct deferred_session, - lh); + return list_first_entry(list, struct deferred_session, lh); } static int joold_setup(void) { - deferred_cache = kmem_cache_create("joold_deferred", + deferred_cache = kmem_cache_create("joold_sessions", sizeof(struct deferred_session), 0, 0, NULL); return deferred_cache ? 0 : -EINVAL; } @@ -113,188 +85,41 @@ void joold_teardown(void) } } -static int joold_pkt_init(struct joold_pkt *pkt, struct xlator *jool) +static void delete_sessions(struct list_head *sessions) { - pkt->skb = genlmsg_new(GLOBALS(jool).max_payload, GFP_ATOMIC); - if (!pkt->skb) - return -ENOMEM; - - pkt->jhdr = genlmsg_put(pkt->skb, 0, 0, jnl_family(), 0, 0); - if (!pkt->jhdr) { - pr_err("genlmsg_put() returned NULL.\n"); - goto kill_packet; - } - - memset(pkt->jhdr, 0, sizeof(*pkt->jhdr)); - memmove(pkt->jhdr->magic, JOOLNL_HDR_MAGIC, JOOLNL_HDR_MAGIC_LEN); - pkt->jhdr->version = cpu_to_be32(xlat_version()); - pkt->jhdr->xt = XT_NAT64; - memcpy(pkt->jhdr->iname, jool->iname, INAME_MAX_SIZE); - - pkt->root = nla_nest_start(pkt->skb, JNLAR_SESSION_ENTRIES); - if (!pkt->root) { - pr_err("Joold packets cannot contain any sessions.\n"); - pkt->jhdr = NULL; - goto kill_packet; - } - - pkt->has_sessions = false; - pkt->full = false; - return 0; - -kill_packet: - kfree_skb(pkt->skb); - pkt->skb = NULL; - return -ENOMEM; -} - -/* Returns true if the session was inserted, false if it didn't fit. */ -static bool put_session(struct xlator *jool, struct session_entry const *session) -{ - struct joold_queue *queue; - struct joold_pkt *pkt; - size_t room; - size_t len_before_put; - int error; - - queue = jool->nat64.joold; - pkt = &queue->pkt; - room = nlmsg_total_size(genlmsg_total_size(GLOBALS(jool).max_payload)); + struct list_head *cursor, *tmp; - /* - * Big pain. - * The kernel sometimes allocates extra room in the skb tail area. - * So we can't trust the nla_put functions to respect max_payload. - * So we have to validate it ourselves. - */ - if (pkt->skb->len + queue->session_size > room) - goto full; - - len_before_put = pkt->skb->len; - error = jnla_put_session(pkt->skb, JNLAL_ENTRY, session); - if (WARN(error, "Ran out of skb room before the allocated limit")) - goto full; - - if (queue->session_size) { - WARN(queue->session_size != pkt->skb->len - len_before_put, - "session size changed: %zu -> %zu", - queue->session_size, - pkt->skb->len - len_before_put); - } else { - queue->session_size = pkt->skb->len - len_before_put; + list_for_each_safe(cursor, tmp, sessions) { + list_del(cursor); + FREE_DEFERRED(list_entry(cursor, struct deferred_session, lh)); } - - pkt->has_sessions = true; - return pkt->full; - -full: - pkt->full = true; - return true; } /* "advertise session," not "add session." Although we're adding it too. */ -static int ad_session(struct session_entry const *session, void *arg) +static int ad_session(struct session_entry const *_session, void *arg) { - struct xlator *jool; - struct joold_queue *queue; + struct counted_list *list; + struct deferred_session *session; - jool = arg; - if (put_session(jool, session)) { - queue = jool->nat64.joold; - queue->flags |= JQF_OFFSET_SET; - queue->ad.offset.src = session->src4; - queue->ad.offset.dst = session->dst4; - return 1; - } + session = ALLOC_DEFERRED; + if (!session) + return -ENOMEM; + session->session = *_session; + list = arg; + list_add_tail(&session->lh, &list->list); + list->count++; return 0; } -/* "advertise sessions," not "add sessions." Although we're adding them too. */ -static void ad_sessions(struct xlator *jool) -{ - struct joold_queue *queue; - struct session_foreach_offset offset, *offset_ptr; - int error; - - queue = jool->nat64.joold; - if (!(queue->flags & JQF_AD_ONGOING)) - return; - - if (queue->flags & JQF_OFFSET_SET) { - offset.offset = queue->ad.offset; - offset.include_offset = true; - offset_ptr = &offset; - } else { - offset_ptr = NULL; - } - - for (; queue->ad.proto <= L4PROTO_ICMP; queue->ad.proto++) { - error = bib_foreach_session(jool, queue->ad.proto, ad_session, - jool, offset_ptr); - if (error > 0) - return; - if (error < 0) { - /* No need to rate-limit; we'll disable the ad. */ - log_warn("joold advertisement interrupted."); - queue->flags &= ~JQF_AD_ONGOING; - return; - } - - queue->flags &= ~JQF_OFFSET_SET; - offset_ptr = NULL; - } - - log_info("joold advertisement done."); - queue->flags &= ~JQF_AD_ONGOING; -} - -static void add_deferred(struct xlator *jool) -{ - struct joold_queue *queue; - struct deferred_session *node; - - queue = jool->nat64.joold; - while (!list_empty(&queue->deferred.list)) { - node = first_deferred(queue); - if (put_session(jool, &node->session)) - return; - list_del(&node->lh); - FREE_DEFERRED(node); - queue->deferred.count--; - } -} - -static void add_session(struct xlator *jool, struct session_entry *session) -{ - struct joold_queue *queue; - struct deferred_session *node; - - if (put_session(jool, session)) { - queue = jool->nat64.joold; - if (queue->deferred.count >= GLOBALS(jool).capacity) { - log_warn_once("Joold: Too many sessions deferred! I need to drop some; sorry."); - return; - } - - node = ALLOC_DEFERRED; - if (node) { - node->session = *session; - list_add_tail(&node->lh, &queue->deferred.list); - queue->deferred.count++; - } /* Else discard it; can't do anything. */ - } -} - static bool should_send(struct xlator *jool) { struct joold_queue *queue; unsigned long deadline; queue = jool->nat64.joold; - if (!queue->pkt.skb) - return false; - if (!queue->pkt.has_sessions) + + if (queue->deferred.count == 0) return false; deadline = msecs_to_jiffies(GLOBALS(jool).flush_deadline); @@ -310,35 +135,57 @@ static bool should_send(struct xlator *jool) if (GLOBALS(jool).flush_asap) return true; - return queue->pkt.full; + return queue->deferred.count >= GLOBALS(jool).max_sessions_per_pkt; +} + +static bool too_many_sessions(struct xlator *jool) +{ + struct joold_queue *queue = jool->nat64.joold; + + if (queue->flags & JQF_AD_ONGOING) + return false; + + return queue->deferred.count >= GLOBALS(jool).capacity; } /** + * Always swallows @session, can be NULL. * Assumes the lock is held. * You have to send_to_userspace(@jool, @prepared) after releasing the spinlock. */ static void send_to_userspace_prepare(struct xlator *jool, - struct session_entry *new_session, - struct joold_pkt *prepared) + struct deferred_session *session, + struct list_head *prepared) { struct joold_queue *queue; + struct list_head *cut; + unsigned int d; queue = jool->nat64.joold; - if (!queue->pkt.skb && joold_pkt_init(&queue->pkt, jool)) { - log_warn_once("joold packet allocation failure. "); - return; - } - ad_sessions(jool); - add_deferred(jool); - add_session(jool, new_session); + if (session) { + if (too_many_sessions(jool)) { + log_warn_once("joold: Too many sessions deferred! I need to drop some; sorry."); + } else { + list_add_tail(&session->lh, &queue->deferred.list); + queue->deferred.count++; + } + } - if (!should_send(jool)) { - prepared->skb = NULL; + if (!should_send(jool)) return; + + if (queue->deferred.count <= GLOBALS(jool).max_sessions_per_pkt) { + cut = queue->deferred.list.prev; + d = queue->deferred.count; + } else { + cut = &queue->deferred.list; + for (d = 0; d < GLOBALS(jool).max_sessions_per_pkt; d++) + cut = cut->next; } - *prepared = queue->pkt; + list_cut_position(prepared, &queue->deferred.list, cut); + queue->deferred.count -= d; /* * BTW: This sucks. @@ -347,21 +194,65 @@ static void send_to_userspace_prepare(struct xlator *jool, * But the alternative is to do the nlcore_send_multicast_message() * with the lock held, and I don't have the stomach for that. */ - memset(&queue->pkt, 0, sizeof(queue->pkt)); queue->flags &= ~JQF_ACK_RECEIVED; + if (queue->deferred.count == 0) + queue->flags &= ~JQF_AD_ONGOING; queue->last_flush_time = jiffies; } /* - * Swallows ownership of @pkt->skb. + * Swallows ownership of the sessions. */ -static void send_to_userspace(struct xlator *jool, struct joold_pkt *pkt) +static void send_to_userspace(struct xlator *jool, struct list_head *sessions) { - if (pkt->skb) { - nla_nest_end(pkt->skb, pkt->root); - genlmsg_end(pkt->skb, pkt->jhdr); - sendpkt_multicast(jool, pkt->skb); + struct sk_buff *skb; + struct joolnlhdr *jhdr; + struct nlattr *root; + struct deferred_session *session; + unsigned int count; + int error; + + if (list_empty(sessions)) + return; + + skb = genlmsg_new(1500, GFP_ATOMIC); + if (!skb) + goto revert_list; + + jhdr = genlmsg_put(skb, 0, 0, jnl_family(), 0, 0); + if (WARN(!jhdr, "genlmsg_put() returned NULL")) + goto revert_skb; + + memset(jhdr, 0, sizeof(*jhdr)); + memcpy(jhdr->magic, JOOLNL_HDR_MAGIC, JOOLNL_HDR_MAGIC_LEN); + jhdr->version = cpu_to_be32(xlat_version()); + jhdr->xt = XT_NAT64; + memcpy(jhdr->iname, jool->iname, INAME_MAX_SIZE); + + root = nla_nest_start(skb, JNLAR_SESSION_ENTRIES); + if (WARN(!root, "nla_nest_start() returned NULL")) + goto revert_skb; + + count = 0; + while (!list_empty(sessions)) { + session = first_deferred(sessions); + error = jnla_put_session(skb, JNLAL_ENTRY, &session->session); + if (WARN(error, "jnla_put_session() returned %d", error)) + goto revert_skb; + list_del(&session->lh); + FREE_DEFERRED(session); + count++; } + + nla_nest_end(skb, root); + genlmsg_end(skb, jhdr); + sendpkt_multicast(jool, skb); + return; + +revert_skb: + kfree_skb(skb); +revert_list: + delete_sessions(sessions); } /** @@ -387,11 +278,9 @@ struct joold_queue *joold_alloc(void) } queue->flags = JQF_ACK_RECEIVED; - memset(&queue->pkt, 0, sizeof(queue->pkt)); INIT_LIST_HEAD(&queue->deferred.list); queue->deferred.count = 0; queue->last_flush_time = jiffies; - queue->session_size = 0; spin_lock_init(&queue->lock); kref_init(&queue->refs); @@ -406,16 +295,8 @@ void joold_get(struct joold_queue *queue) static void joold_release(struct kref *refs) { struct joold_queue *queue; - struct deferred_session *deferred; - queue = container_of(refs, struct joold_queue, refs); - - while (!list_empty(&queue->deferred.list)) { - deferred = first_deferred(queue); - list_del(&deferred->lh); - FREE_DEFERRED(deferred); - } - + delete_sessions(&queue->deferred.list); wkfree(struct joold_queue, queue); } @@ -431,15 +312,21 @@ void joold_put(struct joold_queue *queue) * successfully triggers the creation of a session entry. @session will be sent * to the joold daemon. */ -void joold_add(struct xlator *jool, struct session_entry *session) +void joold_add(struct xlator *jool, struct session_entry *_session) { struct joold_queue *queue; - struct joold_pkt prepared; + struct deferred_session *session; + struct list_head prepared; if (!GLOBALS(jool).enabled) return; + session = ALLOC_DEFERRED; + if (!session) + return; + session->session = *_session; queue = jool->nat64.joold; + INIT_LIST_HEAD(&prepared); spin_lock_bh(&queue->lock); send_to_userspace_prepare(jool, session, &prepared); @@ -481,7 +368,7 @@ static bool add_new_session(struct xlator *jool, struct nlattr *attr) __log_debug(jool, "Adding session!"); - error = jnla_get_session(attr, "Joold session", + error = jnla_get_session(attr, "joold session", &jool->globals.nat64.bib, ¶ms.new); if (error) return false; @@ -537,49 +424,72 @@ int joold_sync(struct xlator *jool, struct nlattr *root) int joold_advertise(struct xlator *jool) { + l4_protocol proto; struct joold_queue *queue; - struct joold_pkt pkt; + struct counted_list sessions; + struct list_head prepared; + int error; if (joold_disabled(jool)) return -EINVAL; + /* Collect the current sessions */ + INIT_LIST_HEAD(&sessions.list); + sessions.count = 0; + for (proto = L4PROTO_TCP; proto <= L4PROTO_ICMP; proto++) { + error = bib_foreach_session(jool, proto, ad_session, &sessions, + NULL); + if (error) { + log_err("joold advertisement interrupted."); + delete_sessions(&sessions.list); + return error; + } + } + + if (sessions.count == 0) + return 0; + queue = jool->nat64.joold; + INIT_LIST_HEAD(&prepared); spin_lock_bh(&queue->lock); if (queue->flags & JQF_AD_ONGOING) { - log_err("joold advertisement already in progress."); spin_unlock_bh(&queue->lock); + delete_sessions(&sessions.list); + log_err("joold advertisement already in progress."); return -EINVAL; } - queue->flags |= JQF_AD_ONGOING; - queue->flags &= ~JQF_OFFSET_SET; - queue->ad.proto = L4PROTO_TCP; - send_to_userspace_prepare(jool, NULL, &pkt); + + list_move_all(&sessions.list, &queue->deferred.list); + queue->deferred.count += sessions.count; + + send_to_userspace_prepare(jool, NULL, &prepared); spin_unlock_bh(&queue->lock); - send_to_userspace(jool, &pkt); + send_to_userspace(jool, &prepared); return 0; } void joold_ack(struct xlator *jool) { struct joold_queue *queue; - struct joold_pkt pkt; + struct list_head prepared; if (joold_disabled(jool)) return; queue = jool->nat64.joold; + INIT_LIST_HEAD(&prepared); spin_lock_bh(&queue->lock); queue->flags |= JQF_ACK_RECEIVED; - send_to_userspace_prepare(jool, NULL, &pkt); + send_to_userspace_prepare(jool, NULL, &prepared); spin_unlock_bh(&queue->lock); - send_to_userspace(jool, &pkt); + send_to_userspace(jool, &prepared); } /** @@ -591,16 +501,17 @@ void joold_ack(struct xlator *jool) void joold_clean(struct xlator *jool) { spinlock_t *lock; - struct joold_pkt pkt; + struct list_head prepared; if (!GLOBALS(jool).enabled) return; lock = &jool->nat64.joold->lock; + INIT_LIST_HEAD(&prepared); spin_lock_bh(lock); - send_to_userspace_prepare(jool, NULL, &pkt); + send_to_userspace_prepare(jool, NULL, &prepared); spin_unlock_bh(lock); - send_to_userspace(jool, &pkt); + send_to_userspace(jool, &prepared); } diff --git a/src/mod/common/steps/send_packet.h b/src/mod/common/steps/send_packet.h index 1b46ff2ad..1f17f762a 100644 --- a/src/mod/common/steps/send_packet.h +++ b/src/mod/common/steps/send_packet.h @@ -24,7 +24,7 @@ verdict sendpkt_send(struct xlation *state); /** - * Joold's multicast packet sender. Sends the packet to the Netlink multicast + * joold's multicast packet sender. Sends the packet to the Netlink multicast * group, not the network. * Separated from joold for mocking reasons. */ diff --git a/src/mod/common/types.h b/src/mod/common/types.h index 42c7fe817..07652953d 100644 --- a/src/mod/common/types.h +++ b/src/mod/common/types.h @@ -175,4 +175,17 @@ bool is_icmp4_info(__u8 type); bool is_icmp6_error(__u8 type); bool is_icmp4_error(__u8 type); +/* Moves all the elements from @src to the tail of @dst. */ +static inline void list_move_all(struct list_head *src, struct list_head *dst) +{ + if (list_empty(src)) + return; + + dst->prev->next = src->next; + src->next->prev = dst->prev; + dst->prev = src->prev; + dst->prev->next = dst; + INIT_LIST_HEAD(src); +} + #endif /* SRC_MOD_COMMON_TYPES_H_ */ diff --git a/src/usr/nl/joold.c b/src/usr/nl/joold.c index bd5a131f5..033961478 100644 --- a/src/usr/nl/joold.c +++ b/src/usr/nl/joold.c @@ -55,11 +55,6 @@ struct jool_result joolnl_joold_advertise(struct joolnl_socket *sk, struct nl_msg *msg; struct jool_result result; - return result_from_error( - 1, - "Sorry; joold advertise is disabled for now." - ); - result = joolnl_alloc_msg(sk, iname, JNLOP_JOOLD_ADVERTISE, 0, &msg); if (result.error) return result; diff --git a/test/unit/joold/joold_test.c b/test/unit/joold/joold_test.c index 8686b9142..0f53a5ff6 100644 --- a/test/unit/joold/joold_test.c +++ b/test/unit/joold/joold_test.c @@ -9,11 +9,8 @@ MODULE_LICENSE(JOOL_LICENSE); MODULE_AUTHOR("Alberto Leiva"); MODULE_DESCRIPTION("joold test."); -static struct session_entry session1; -static struct session_entry session2; -static struct session_entry session3; - -#define JOOLNL_HDRLEN NLMSG_ALIGN(sizeof(struct joolnlhdr)) +/* Test dummies */ +static struct session_entry ss[9]; /********************** Mocks **********************/ @@ -36,11 +33,26 @@ struct genl_family *jnl_family(void) return &family_mock; } +unsigned int foreach_start; +unsigned int foreach_end; + int bib_foreach_session(struct xlator *jool, l4_protocol proto, session_foreach_entry_cb cb, void *cb_arg, struct session_foreach_offset *offset) { - return -EINVAL; + unsigned int s; + int error; + + if (proto != L4PROTO_TCP) + return 0; + + for (s = foreach_start; s < foreach_end; s++) { + error = cb(&ss[s], cb_arg); + if (error) + return error; + } + + return 0; } int bib_add_session(struct xlator *jool, @@ -80,12 +92,11 @@ static void init_session(unsigned int index, struct session_entry *result) result->has_stored = false; } - static int init_sessions(void) { - init_session(1, &session1); - init_session(2, &session2); - init_session(3, &session3); + unsigned int i; + for (i = 0; i < ARRAY_SIZE(ss); i++) + init_session(i, &ss[i]); return 0; } @@ -95,46 +106,11 @@ static struct joold_queue *init_xlator(struct xlator *jool) jool->globals.nat64.joold.flush_asap = false; jool->globals.nat64.joold.flush_deadline = 2000; jool->globals.nat64.joold.capacity = 4; - jool->globals.nat64.joold.max_payload = 1500; + jool->globals.nat64.joold.max_sessions_per_pkt = 3; jool->nat64.joold = joold_alloc(); return jool->nat64.joold; } -/********************** Helpers **********************/ - -static int compute_max_payload(struct xlator *jool, unsigned int nsessions) -{ - struct joold_pkt dummy_pkt; - struct session_entry dummy_session; - size_t basic_size; /* NL header, GNL header, Jool header, root attr */ - size_t session_size; - size_t total_size; - int error; - - error = joold_pkt_init(&dummy_pkt, jool); - if (error) - return error; - - basic_size = dummy_pkt.skb->len; - - memset(&dummy_session, 0, sizeof(dummy_session)); - error = jnla_put_session(dummy_pkt.skb, JNLAL_ENTRY, &dummy_session); - if (error) - goto end; - - session_size = dummy_pkt.skb->len - basic_size; - total_size = NLMSG_ALIGN(sizeof(struct joolnlhdr)) + NLA_HDRLEN - + nsessions * session_size; - - log_info("session size: %zu", sizeof(dummy_session)); - log_info("serialized session size: %zu", session_size); - log_info("total size: %zu", total_size); - - jool->globals.nat64.joold.max_payload = total_size; -end: kfree_skb(dummy_pkt.skb); - return error; -} - /********************** Asserts **********************/ static bool assert_deferred(struct joold_queue *joold, ...) @@ -157,7 +133,7 @@ static bool assert_deferred(struct joold_queue *joold, ...) goto end; } - success &= ASSERT_SESSION(expected, &actual->session, "a"); + success &= ASSERT_SESSION(expected, &actual->session, "listed"); count++; } @@ -168,13 +144,13 @@ static bool assert_deferred(struct joold_queue *joold, ...) goto end; } - success &= ASSERT_UINT(count, joold->deferred.count, "b"); + success &= ASSERT_UINT(count, joold->deferred.count, "count"); end: va_end(args); return success; } -static bool assert_skb_sessions(struct sk_buff *skb, ...) +static bool assert_skb(int garbage, ...) { struct session_entry *expected, actual; struct nlattr *root, *attr; @@ -184,13 +160,19 @@ static bool assert_skb_sessions(struct sk_buff *skb, ...) bool success; int error; - if (!skb) { - log_err("skb is NULL."); - return false; + va_start(args, garbage); + expected = va_arg(args, struct session_entry *); + va_end(args); + + if (expected != NULL) { + if (!ASSERT_NOTNULL(sent, "skb was sent")) + return false; + } else { + return ASSERT_NULL(sent, "skb was not sent"); } - root = nlmsg_attrdata(nlmsg_hdr(skb), GENL_HDRLEN + JOOLNL_HDRLEN); - success = ASSERT_UINT(JNLAR_SESSION_ENTRIES, nla_type(root), "achoo"); + root = nlmsg_attrdata(nlmsg_hdr(sent), GENL_HDRLEN + JOOLNL_HDRLEN); + success = ASSERT_UINT(JNLAR_SESSION_ENTRIES, nla_type(root), "root"); memset(&bibcfg, 0, sizeof(bibcfg)); bibcfg.ttl.tcp_est = 1000 * TCP_EST; @@ -198,7 +180,7 @@ static bool assert_skb_sessions(struct sk_buff *skb, ...) bibcfg.ttl.udp = 1000 * UDP_DEFAULT; bibcfg.ttl.icmp = 1000 * ICMP_DEFAULT; - va_start(args, skb); + va_start(args, garbage); nla_for_each_nested(attr, root, rem) { error = jnla_get_session(attr, "session", &bibcfg, &actual); @@ -210,12 +192,12 @@ static bool assert_skb_sessions(struct sk_buff *skb, ...) expected = va_arg(args, struct session_entry *); if (!expected) { - log_err("Unexpected packet session: " SEPP, SEPA(&actual)); + log_err("Unexpected pkt session: " SEPP, SEPA(&actual)); success = false; goto end; } - success &= ASSERT_SESSION(expected, &actual, "a"); + success &= ASSERT_SESSION(expected, &actual, "packet'd"); } expected = va_arg(args, struct session_entry *); @@ -225,12 +207,55 @@ static bool assert_skb_sessions(struct sk_buff *skb, ...) } end: va_end(args); + kfree_skb(sent); + sent = NULL; return success; } /********************** Unit tests **********************/ -static bool no_flush_asap(void) +/* No assertions, simply prints packet content sizes for future reference. */ +static bool print_sizes(void) +{ + struct sk_buff *skb; + struct joolnlhdr *jhdr; + struct nlattr *root; + struct session_entry dummy_session; + size_t basic_size; /* NL header + GNL header + Jool header */ + size_t root_size; + size_t session_size; + + skb = genlmsg_new(1000, GFP_ATOMIC); + if (!skb) + return true; + + jhdr = genlmsg_put(skb, 0, 0, jnl_family(), 0, 0); + if (WARN(!jhdr, "genlmsg_put() returned NULL")) + goto end; + + basic_size = skb->len; + + root = nla_nest_start(skb, JNLAR_SESSION_ENTRIES); + if (WARN(!root, "nla_nest_start() returned NULL")) + goto end; + + root_size = skb->len - basic_size; + + memset(&dummy_session, 0, sizeof(dummy_session)); + if (jnla_put_session(skb, JNLAL_ENTRY, &dummy_session) != 0) + goto end; + + session_size = skb->len - basic_size - root_size; + + log_info("Kernel headers size: %zu", basic_size); + log_info("Netlink attribute header size: %zu", root_size); + log_info("Serialized session size: %zu", session_size); + +end: kfree_skb(skb); + return true; +} + +static bool test_no_flush_asap(void) { struct xlator jool; struct joold_queue *joold; @@ -239,46 +264,436 @@ static bool no_flush_asap(void) joold = init_xlator(&jool); if (!joold) return false; - if (compute_max_payload(&jool, 2)) - return false; - joold_add(&jool, &session1); + log_info("1"); + joold_add(&jool, &ss[0]); success &= ASSERT_UINT(JQF_ACK_RECEIVED, joold->flags, "flags1"); - success &= ASSERT_NOTNULL(joold->pkt.skb, "pkt1"); - success &= ASSERT_NOTNULL(joold->pkt.jhdr, "jhdr1"); - success &= ASSERT_NOTNULL(joold->pkt.root, "root1"); - success &= ASSERT_TRUE(joold->pkt.has_sessions, "has_sessions1"); - success &= ASSERT_FALSE(joold->pkt.full, "full1"); - success &= assert_deferred(joold, NULL); - success &= ASSERT_NULL(sent, "sent1"); + success &= assert_deferred(joold, &ss[0], NULL); + success &= assert_skb(0, NULL); if (!success) - return false; + goto end; - joold_add(&jool, &session2); + log_info("2"); + joold_add(&jool, &ss[1]); success &= ASSERT_UINT(JQF_ACK_RECEIVED, joold->flags, "flags2"); - success &= ASSERT_NOTNULL(joold->pkt.skb, "pkt2"); - success &= ASSERT_NOTNULL(joold->pkt.jhdr, "jhdr2"); - success &= ASSERT_NOTNULL(joold->pkt.root, "root2"); - success &= ASSERT_TRUE(joold->pkt.has_sessions, "has_sessions2"); - success &= ASSERT_FALSE(joold->pkt.full, "full2"); + success &= assert_deferred(joold, &ss[0], &ss[1], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + log_info("3"); + joold_add(&jool, &ss[2]); + success &= ASSERT_UINT(0, joold->flags, "flags3"); success &= assert_deferred(joold, NULL); - success &= ASSERT_NULL(sent, "sent2"); + success &= assert_skb(0, &ss[0], &ss[1], &ss[2], NULL); if (!success) - return false; + goto end; + + /* Note: ACK not received yet */ + + log_info("4"); + joold_add(&jool, &ss[0]); + success &= ASSERT_UINT(0, joold->flags, "flags1"); + success &= assert_deferred(joold, &ss[0], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + log_info("5"); + joold_add(&jool, &ss[1]); + success &= ASSERT_UINT(0, joold->flags, "flags2"); + success &= assert_deferred(joold, &ss[0], &ss[1], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; - joold_add(&jool, &session3); + log_info("6"); + joold_add(&jool, &ss[2]); success &= ASSERT_UINT(0, joold->flags, "flags3"); - success &= ASSERT_NULL(joold->pkt.skb, "pkt3"); - success &= ASSERT_NULL(joold->pkt.jhdr, "jhdr3"); - success &= ASSERT_NULL(joold->pkt.root, "root3"); - success &= ASSERT_FALSE(joold->pkt.has_sessions, "has_sessions3"); - success &= ASSERT_FALSE(joold->pkt.full, "full3"); - success &= assert_deferred(joold, &session3, NULL); - success &= assert_skb_sessions(sent, &session1, &session2, NULL); + success &= assert_deferred(joold, &ss[0], &ss[1], &ss[2], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + log_info("7"); + joold_add(&jool, &ss[3]); + success &= ASSERT_UINT(0, joold->flags, "flags4"); + success &= assert_deferred(joold, &ss[0], &ss[1], &ss[2], &ss[3], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + /* Capacity exceeded; drop new session */ + log_info("8"); + joold_add(&jool, &ss[4]); + success &= ASSERT_UINT(0, joold->flags, "flags5"); + success &= assert_deferred(joold, &ss[0], &ss[1], &ss[2], &ss[3], NULL); + success &= assert_skb(0, NULL); if (!success) + goto end; + + /* ACK */ + log_info("9"); + joold_ack(&jool); + success &= ASSERT_UINT(0, joold->flags, "flags6"); + success &= assert_deferred(joold, &ss[3], NULL); + success &= assert_skb(0, &ss[0], &ss[1], &ss[2], NULL); + if (!success) + goto end; + + /* ACK again */ + log_info("10"); + joold_ack(&jool); + success &= ASSERT_UINT(JQF_ACK_RECEIVED, joold->flags, "flags7"); + success &= assert_deferred(joold, &ss[3], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + /* Refill; make sure we're still stable after the ACK */ + log_info("11"); + joold_add(&jool, &ss[4]); + success &= ASSERT_UINT(JQF_ACK_RECEIVED, joold->flags, "flags8"); + success &= assert_deferred(joold, &ss[3], &ss[4], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + log_info("12"); + joold_add(&jool, &ss[5]); + success &= ASSERT_UINT(0, joold->flags, "flags9"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, &ss[3], &ss[4], &ss[5], NULL); + if (!success) + goto end; + + /* Try an ACK on an empty joold */ + log_info("13"); + joold_ack(&jool); + success &= ASSERT_UINT(JQF_ACK_RECEIVED, joold->flags, "flags10"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, NULL); + +end: joold_put(joold); + return success; +} + +static bool test_flush_asap(void) +{ + struct xlator jool; + struct joold_queue *joold; + bool success = true; + + joold = init_xlator(&jool); + if (!joold) return false; + jool.globals.nat64.joold.flush_asap = true; - return true; + /* Flush immediately */ + log_info("1"); + joold_add(&jool, &ss[0]); + success &= ASSERT_UINT(0, joold->flags, "flags1"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, &ss[0], NULL); + if (!success) + goto end; + + /* No ACK; postpone flush despite ss-flush-asap */ + log_info("2"); + joold_add(&jool, &ss[1]); + success &= ASSERT_UINT(0, joold->flags, "flags2"); + success &= assert_deferred(joold, &ss[1], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + /* ACK; flush */ + log_info("3"); + joold_ack(&jool); + success &= ASSERT_UINT(0, joold->flags, "flags3"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, &ss[1], NULL); + if (!success) + goto end; + + /* Reach capacity */ + log_info("4"); + joold_add(&jool, &ss[0]); + success &= ASSERT_UINT(0, joold->flags, "flags4"); + success &= assert_deferred(joold, &ss[0], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + log_info("5"); + joold_add(&jool, &ss[1]); + success &= ASSERT_UINT(0, joold->flags, "flags5"); + success &= assert_deferred(joold, &ss[0], &ss[1], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + log_info("6"); + joold_add(&jool, &ss[2]); + success &= ASSERT_UINT(0, joold->flags, "flags6"); + success &= assert_deferred(joold, &ss[0], &ss[1], &ss[2], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + log_info("7"); + joold_add(&jool, &ss[3]); + success &= ASSERT_UINT(0, joold->flags, "flags7"); + success &= assert_deferred(joold, &ss[0], &ss[1], &ss[2], &ss[3], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + /* Capacity reached; drop session */ + log_info("8"); + joold_add(&jool, &ss[4]); + success &= ASSERT_UINT(0, joold->flags, "flags8"); + success &= assert_deferred(joold, &ss[0], &ss[1], &ss[2], &ss[3], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + /* Again */ + log_info("9"); + joold_add(&jool, &ss[5]); + success &= ASSERT_UINT(0, joold->flags, "flags9"); + success &= assert_deferred(joold, &ss[0], &ss[1], &ss[2], &ss[3], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + /* ACK, finally */ + log_info("10"); + joold_ack(&jool); + success &= ASSERT_UINT(0, joold->flags, "flags10"); + success &= assert_deferred(joold, &ss[3], NULL); + success &= assert_skb(0, &ss[0], &ss[1], &ss[2], NULL); + if (!success) + goto end; + + /* Again */ + log_info("11"); + joold_ack(&jool); + success &= ASSERT_UINT(0, joold->flags, "flags11"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, &ss[3], NULL); + if (!success) + goto end; + + /* Again */ + log_info("12"); + joold_ack(&jool); + success &= ASSERT_UINT(JQF_ACK_RECEIVED, joold->flags, "flags12"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + /* Flush, ACK, flush */ + log_info("13"); + joold_add(&jool, &ss[0]); + success &= ASSERT_UINT(0, joold->flags, "flags13"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, &ss[0], NULL); + if (!success) + goto end; + + log_info("14"); + joold_ack(&jool); + success &= ASSERT_UINT(JQF_ACK_RECEIVED, joold->flags, "flags14"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + log_info("15"); + joold_add(&jool, &ss[0]); + success &= ASSERT_UINT(0, joold->flags, "flags15"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, &ss[0], NULL); + +end: joold_put(joold); + return success; +} + +static bool test_advertise(void) +{ + struct xlator jool; + struct joold_queue *joold; + bool success = true; + + joold = init_xlator(&jool); + if (!joold) + goto end; + + /* Empty advertise on startup */ + log_info("1"); + foreach_end = 0; + joold_advertise(&jool); + success &= ASSERT_UINT(JQF_ACK_RECEIVED, joold->flags, "flags1"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + /* Single session advertise */ + log_info("2"); + foreach_end = 1; + joold_advertise(&jool); + success &= ASSERT_UINT(0, joold->flags, "flags2"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, &ss[0], NULL); + if (!success) + goto end; + + /* Single session advertise, postponed because no ACK */ + log_info("3"); + joold_advertise(&jool); + success &= ASSERT_UINT(JQF_AD_ONGOING, joold->flags, "flags3"); + success &= assert_deferred(joold, &ss[0], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + /* ACK */ + log_info("4"); + joold_ack(&jool); + success &= ASSERT_UINT(0, joold->flags, "flags4"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, &ss[0], NULL); + if (!success) + goto end; + + /* Enable JQF_ACK_RECEIVED */ + log_info("5"); + joold_ack(&jool); + success &= ASSERT_UINT(JQF_ACK_RECEIVED, joold->flags, "flags5"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + /* Full packet advertise */ + log_info("6"); + foreach_end = 3; + joold_advertise(&jool); + success &= ASSERT_UINT(0, joold->flags, "flags6"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, &ss[0], &ss[1], &ss[2], NULL); + if (!success) + goto end; + + /* Enable JQF_ACK_RECEIVED */ + log_info("7"); + joold_ack(&jool); + success &= ASSERT_UINT(JQF_ACK_RECEIVED, joold->flags, "flags7"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + /* Advertise enough sessions to need 2 packets */ + log_info("8"); + foreach_end = 4; + joold_advertise(&jool); + success &= ASSERT_UINT(JQF_AD_ONGOING, joold->flags, "flags8"); + success &= assert_deferred(joold, &ss[3], NULL); + success &= assert_skb(0, &ss[0], &ss[1], &ss[2], NULL); + if (!success) + goto end; + + /* Make sure advertises don't stack */ + log_info("9"); + joold_advertise(&jool); + success &= ASSERT_UINT(JQF_AD_ONGOING, joold->flags, "flags9"); + success &= assert_deferred(joold, &ss[3], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + /* Send 2nd packet */ + log_info("10"); + joold_ack(&jool); + success &= ASSERT_UINT(0, joold->flags, "flags10"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, &ss[3], NULL); + if (!success) + goto end; + + /* Large advertise, and joold isn't empty */ + log_info("11"); + joold_add(&jool, &ss[0]); + success &= ASSERT_UINT(0, joold->flags, "flags11"); + success &= assert_deferred(joold, &ss[0], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + log_info("12"); + joold_ack(&jool); + success &= ASSERT_UINT(JQF_ACK_RECEIVED, joold->flags, "flags12"); + success &= assert_deferred(joold, &ss[0], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + log_info("13"); + joold_add(&jool, &ss[1]); + success &= ASSERT_UINT(JQF_ACK_RECEIVED, joold->flags, "flags13"); + success &= assert_deferred(joold, &ss[0], &ss[1], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + log_info("14"); + foreach_start = 2; + foreach_end = 8; + joold_advertise(&jool); + success &= ASSERT_UINT(JQF_AD_ONGOING, joold->flags, "flags14"); + success &= assert_deferred(joold, &ss[3], &ss[4], &ss[5], &ss[6], + &ss[7], NULL); + success &= assert_skb(0, &ss[0], &ss[1], &ss[2], NULL); + if (!success) + goto end; + + log_info("15"); + joold_add(&jool, &ss[8]); + success &= ASSERT_UINT(JQF_AD_ONGOING, joold->flags, "flags15"); + success &= assert_deferred(joold, &ss[3], &ss[4], &ss[5], &ss[6], + &ss[7], &ss[8], NULL); + success &= assert_skb(0, NULL); + if (!success) + goto end; + + log_info("16"); + joold_ack(&jool); + success &= ASSERT_UINT(JQF_AD_ONGOING, joold->flags, "flags16"); + success &= assert_deferred(joold, &ss[6], &ss[7], &ss[8], NULL); + success &= assert_skb(0, &ss[3], &ss[4], &ss[5], NULL); + if (!success) + goto end; + + log_info("17"); + joold_ack(&jool); + success &= ASSERT_UINT(0, joold->flags, "flags17"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, &ss[6], &ss[7], &ss[8], NULL); + if (!success) + goto end; + + log_info("18"); + joold_ack(&jool); + success &= ASSERT_UINT(JQF_ACK_RECEIVED, joold->flags, "flags18"); + success &= assert_deferred(joold, NULL); + success &= assert_skb(0, NULL); + +end: joold_put(joold); + return success; } /********************** Hooks **********************/ @@ -292,7 +707,10 @@ int init_module(void) if (test_group_begin(&test)) return -EINVAL; - test_group_test(&test, no_flush_asap, "ss-flush-asap disabled"); + test_group_test(&test, print_sizes, "print sizes"); + test_group_test(&test, test_no_flush_asap, "ss-flush-asap disabled"); + test_group_test(&test, test_flush_asap, "ss-flush-asap enabled"); + test_group_test(&test, test_advertise, "advertise"); return test_group_end(&test); } diff --git a/test/unit/types/types_test.c b/test/unit/types/types_test.c index 7d25be22f..a8bcf8810 100644 --- a/test/unit/types/types_test.c +++ b/test/unit/types/types_test.c @@ -33,6 +33,115 @@ static bool test_port_range_touches(void) return success; } +static void init_list(struct list_head *list, ...) +{ + struct list_head *cursor; + va_list args; + + INIT_LIST_HEAD(list); + + va_start(args, list); + while ((cursor = va_arg(args, struct list_head *)) != NULL) + list_add_tail(cursor, list); + va_end(args); +} + +static bool assert_list(struct list_head *list, ...) +{ + struct list_head *expected, *actual; + va_list args; + + va_start(args, list); + list_for_each(actual, list) { + expected = va_arg(args, struct list_head *); + if (expected != actual) + goto fail; + } + + expected = va_arg(args, struct list_head *); + if (expected != NULL) + goto fail; + + va_end(args); + return true; + +fail: + va_end(args); + + log_err("Bad list."); + + pr_err(" Expected: "); + va_start(args, list); + while ((expected = va_arg(args, struct list_head *)) != NULL) + pr_cont("%p ", expected); + va_end(args); + pr_cont("\n"); + + pr_err(" Actual : "); + list_for_each(actual, list) + pr_cont("%p ", actual); + + return false; +} + +static bool test_list_move_all(void) +{ + struct list_head node1, node2, node3, node4; + struct list_head src, dst; + bool success = true; + + /* Empty */ + log_info("1"); + init_list(&src, NULL); + init_list(&dst, NULL); + list_move_all(&src, &dst); + success &= assert_list(&src, NULL); + success &= assert_list(&dst, NULL); + if (!success) + return false; + + /* Move 1 to empty */ + log_info("2"); + list_add(&node1, &src); + list_move_all(&src, &dst); + success &= assert_list(&src, NULL); + success &= assert_list(&dst, &node1, NULL); + if (!success) + return false; + + /* Move all to empty */ + log_info("3"); + init_list(&src, &node1, &node2, &node3, &node4, NULL); + init_list(&dst, NULL); + list_move_all(&src, &dst); + success &= assert_list(&src, NULL); + success &= assert_list(&dst, &node1, &node2, &node3, &node4, NULL); + if (!success) + return false; + + /* Move 2 to 1 */ + log_info("4"); + init_list(&src, &node1, &node2, NULL); + init_list(&dst, &node3, NULL); + list_move_all(&src, &dst); + success &= assert_list(&src, NULL); + success &= assert_list(&dst, &node3, &node1, &node2, NULL); + if (!success) + return false; + + /* Move 2 to 2, weird order */ + log_info("5"); + init_list(&src, &node3, &node1, NULL); + init_list(&dst, &node2, &node4, NULL); + list_move_all(&src, &dst); + success &= assert_list(&src, NULL); + success &= assert_list(&dst, &node2, &node4, &node3, &node1, NULL); + if (!success) + return false; + + return success; +} + int init_module(void) { struct test_group test = { @@ -43,6 +152,7 @@ int init_module(void) return -EINVAL; test_group_test(&test, test_port_range_touches, "port range touches function"); + test_group_test(&test, test_list_move_all, "list move all function"); return test_group_end(&test); }