Skip to content

Commit

Permalink
net: Update an existing TCP congestion control algorithm.
Browse files Browse the repository at this point in the history
This feature lets you immediately transition to another congestion
control algorithm or implementation with the same name.  Once a name
is updated, new connections will apply this new algorithm.

The purpose is to update a customized algorithm implemented in BPF
struct_ops with a new version on the flight.  The following is an
example of using the userspace API implemented in later BPF patches.

   link = bpf_map__attach_struct_ops(skel->maps.ca_update_1);
   .......
   err = bpf_link__update_map(link, skel->maps.ca_update_2);

We first load and register an algorithm implemented in BPF struct_ops,
then swap it out with a new one using the same name. After that, newly
created connections will apply the updated algorithm, while older ones
retain the previous version already applied.

This patch also takes this chance to refactor the ca validation into
the new tcp_validate_congestion_control() function.

Cc: netdev@vger.kernel.org, Eric Dumazet <edumazet@google.com>
Signed-off-by: Kui-Feng Lee <kuifeng@meta.com>
Link: https://lore.kernel.org/r/20230323032405.3735486-3-kuifeng@meta.com
Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org>
  • Loading branch information
Kui-Feng Lee authored and Martin KaFai Lau committed Mar 23, 2023
1 parent b671c20 commit 8fb1a76
Showing 2 changed files with 62 additions and 7 deletions.
3 changes: 3 additions & 0 deletions include/net/tcp.h
Original file line number Diff line number Diff line change
@@ -1117,6 +1117,9 @@ struct tcp_congestion_ops {

int tcp_register_congestion_control(struct tcp_congestion_ops *type);
void tcp_unregister_congestion_control(struct tcp_congestion_ops *type);
int tcp_update_congestion_control(struct tcp_congestion_ops *type,
struct tcp_congestion_ops *old_type);
int tcp_validate_congestion_control(struct tcp_congestion_ops *ca);

void tcp_assign_congestion_control(struct sock *sk);
void tcp_init_congestion_control(struct sock *sk);
66 changes: 59 additions & 7 deletions net/ipv4/tcp_cong.c
Original file line number Diff line number Diff line change
@@ -75,21 +75,29 @@ struct tcp_congestion_ops *tcp_ca_find_key(u32 key)
return NULL;
}

/*
* Attach new congestion control algorithm to the list
* of available options.
*/
int tcp_register_congestion_control(struct tcp_congestion_ops *ca)
int tcp_validate_congestion_control(struct tcp_congestion_ops *ca)
{
int ret = 0;

/* all algorithms must implement these */
if (!ca->ssthresh || !ca->undo_cwnd ||
!(ca->cong_avoid || ca->cong_control)) {
pr_err("%s does not implement required ops\n", ca->name);
return -EINVAL;
}

return 0;
}

/* Attach new congestion control algorithm to the list
* of available options.
*/
int tcp_register_congestion_control(struct tcp_congestion_ops *ca)
{
int ret;

ret = tcp_validate_congestion_control(ca);
if (ret)
return ret;

ca->key = jhash(ca->name, sizeof(ca->name), strlen(ca->name));

spin_lock(&tcp_cong_list_lock);
@@ -130,6 +138,50 @@ void tcp_unregister_congestion_control(struct tcp_congestion_ops *ca)
}
EXPORT_SYMBOL_GPL(tcp_unregister_congestion_control);

/* Replace a registered old ca with a new one.
*
* The new ca must have the same name as the old one, that has been
* registered.
*/
int tcp_update_congestion_control(struct tcp_congestion_ops *ca, struct tcp_congestion_ops *old_ca)
{
struct tcp_congestion_ops *existing;
int ret;

ret = tcp_validate_congestion_control(ca);
if (ret)
return ret;

ca->key = jhash(ca->name, sizeof(ca->name), strlen(ca->name));

spin_lock(&tcp_cong_list_lock);
existing = tcp_ca_find_key(old_ca->key);
if (ca->key == TCP_CA_UNSPEC || !existing || strcmp(existing->name, ca->name)) {
pr_notice("%s not registered or non-unique key\n",
ca->name);
ret = -EINVAL;
} else if (existing != old_ca) {
pr_notice("invalid old congestion control algorithm to replace\n");
ret = -EINVAL;
} else {
/* Add the new one before removing the old one to keep
* one implementation available all the time.
*/
list_add_tail_rcu(&ca->list, &tcp_cong_list);
list_del_rcu(&existing->list);
pr_debug("%s updated\n", ca->name);
}
spin_unlock(&tcp_cong_list_lock);

/* Wait for outstanding readers to complete before the
* module or struct_ops gets removed entirely.
*/
if (!ret)
synchronize_rcu();

return ret;
}

u32 tcp_ca_get_key_by_name(struct net *net, const char *name, bool *ecn_ca)
{
const struct tcp_congestion_ops *ca;

0 comments on commit 8fb1a76

Please sign in to comment.