Merge branch 'decouple-receive-and-transmit-enablement-in-team-driver'
Marc Harvey says:
====================
Decouple receive and transmit enablement in team driver
Allow independent control over receive and transmit enablement states
for aggregated ports in the team driver.
The motivation is that IEE 802.3ad LACP "independent control" can't
be implemented for the team driver currently. This was added to the
bonding driver in commit 240fd40552 ("bonding: Add independent
control state machine").
This series also has a few patches that add tests to show that the old
coupled enablement still works and that the new decoupled enablement
works as intended (4, 5, and 10).
There are three patches with small fixes as well, with the goal of
making the final decoupling patch clearer (1, 2, and 3).
Signed-off-by: Marc Harvey <marcharvey@google.com>
====================
Link: https://patch.msgid.link/20260409-teaming-driver-internal-v7-0-f47e7589685d@google.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
master
commit
c7211b6e83
|
|
@ -87,7 +87,7 @@ static void team_lower_state_changed(struct team_port *port)
|
|||
struct netdev_lag_lower_state_info info;
|
||||
|
||||
info.link_up = port->linkup;
|
||||
info.tx_enabled = team_port_enabled(port);
|
||||
info.tx_enabled = team_port_tx_enabled(port);
|
||||
netdev_lower_state_changed(port->dev, &info);
|
||||
}
|
||||
|
||||
|
|
@ -532,13 +532,13 @@ static void team_adjust_ops(struct team *team)
|
|||
* correct ops are always set.
|
||||
*/
|
||||
|
||||
if (!team->en_port_count || !team_is_mode_set(team) ||
|
||||
if (!team->tx_en_port_count || !team_is_mode_set(team) ||
|
||||
!team->mode->ops->transmit)
|
||||
team->ops.transmit = team_dummy_transmit;
|
||||
else
|
||||
team->ops.transmit = team->mode->ops->transmit;
|
||||
|
||||
if (!team->en_port_count || !team_is_mode_set(team) ||
|
||||
if (!team->rx_en_port_count || !team_is_mode_set(team) ||
|
||||
!team->mode->ops->receive)
|
||||
team->ops.receive = team_dummy_receive;
|
||||
else
|
||||
|
|
@ -734,7 +734,7 @@ static rx_handler_result_t team_handle_frame(struct sk_buff **pskb)
|
|||
|
||||
port = team_port_get_rcu(skb->dev);
|
||||
team = port->team;
|
||||
if (!team_port_enabled(port)) {
|
||||
if (!team_port_rx_enabled(port)) {
|
||||
if (is_link_local_ether_addr(eth_hdr(skb)->h_dest))
|
||||
/* link-local packets are mostly useful when stack receives them
|
||||
* with the link they arrive on.
|
||||
|
|
@ -831,7 +831,7 @@ static bool team_queue_override_port_has_gt_prio_than(struct team_port *port,
|
|||
return true;
|
||||
if (port->priority > cur->priority)
|
||||
return false;
|
||||
if (port->index < cur->index)
|
||||
if (port->tx_index < cur->tx_index)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -876,7 +876,7 @@ static void __team_queue_override_enabled_check(struct team *team)
|
|||
static void team_queue_override_port_prio_changed(struct team *team,
|
||||
struct team_port *port)
|
||||
{
|
||||
if (!port->queue_id || !team_port_enabled(port))
|
||||
if (!port->queue_id || !team_port_tx_enabled(port))
|
||||
return;
|
||||
__team_queue_override_port_del(team, port);
|
||||
__team_queue_override_port_add(team, port);
|
||||
|
|
@ -887,7 +887,7 @@ static void team_queue_override_port_change_queue_id(struct team *team,
|
|||
struct team_port *port,
|
||||
u16 new_queue_id)
|
||||
{
|
||||
if (team_port_enabled(port)) {
|
||||
if (team_port_tx_enabled(port)) {
|
||||
__team_queue_override_port_del(team, port);
|
||||
port->queue_id = new_queue_id;
|
||||
__team_queue_override_port_add(team, port);
|
||||
|
|
@ -927,57 +927,164 @@ static bool team_port_find(const struct team *team,
|
|||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable/disable port by adding to enabled port hashlist and setting
|
||||
* port->index (Might be racy so reader could see incorrect ifindex when
|
||||
* processing a flying packet, but that is not a problem). Write guarded
|
||||
* by RTNL.
|
||||
*/
|
||||
static void team_port_enable(struct team *team,
|
||||
struct team_port *port)
|
||||
static void __team_port_enable_rx(struct team *team,
|
||||
struct team_port *port)
|
||||
{
|
||||
if (team_port_enabled(port))
|
||||
team->rx_en_port_count++;
|
||||
WRITE_ONCE(port->rx_enabled, true);
|
||||
}
|
||||
|
||||
static void __team_port_disable_rx(struct team *team,
|
||||
struct team_port *port)
|
||||
{
|
||||
team->rx_en_port_count--;
|
||||
WRITE_ONCE(port->rx_enabled, false);
|
||||
}
|
||||
|
||||
static void team_port_enable_rx(struct team *team,
|
||||
struct team_port *port)
|
||||
{
|
||||
if (team_port_rx_enabled(port))
|
||||
return;
|
||||
port->index = team->en_port_count++;
|
||||
hlist_add_head_rcu(&port->hlist,
|
||||
team_port_index_hash(team, port->index));
|
||||
|
||||
__team_port_enable_rx(team, port);
|
||||
team_adjust_ops(team);
|
||||
team_queue_override_port_add(team, port);
|
||||
if (team->ops.port_enabled)
|
||||
team->ops.port_enabled(team, port);
|
||||
team_notify_peers(team);
|
||||
team_mcast_rejoin(team);
|
||||
}
|
||||
|
||||
static void team_port_disable_rx(struct team *team,
|
||||
struct team_port *port)
|
||||
{
|
||||
if (!team_port_rx_enabled(port))
|
||||
return;
|
||||
|
||||
__team_port_disable_rx(team, port);
|
||||
team_adjust_ops(team);
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable just TX on the port by adding to tx-enabled port hashlist and
|
||||
* setting port->tx_index (Might be racy so reader could see incorrect
|
||||
* ifindex when processing a flying packet, but that is not a problem).
|
||||
* Write guarded by RTNL.
|
||||
*/
|
||||
static void __team_port_enable_tx(struct team *team,
|
||||
struct team_port *port)
|
||||
{
|
||||
WRITE_ONCE(port->tx_index, team->tx_en_port_count);
|
||||
WRITE_ONCE(team->tx_en_port_count, team->tx_en_port_count + 1);
|
||||
hlist_add_head_rcu(&port->tx_hlist,
|
||||
team_tx_port_index_hash(team, port->tx_index));
|
||||
}
|
||||
|
||||
static void team_port_enable_tx(struct team *team,
|
||||
struct team_port *port)
|
||||
{
|
||||
if (team_port_tx_enabled(port))
|
||||
return;
|
||||
|
||||
__team_port_enable_tx(team, port);
|
||||
team_adjust_ops(team);
|
||||
team_queue_override_port_add(team, port);
|
||||
|
||||
/* Don't rejoin multicast, since this port might not be receiving. */
|
||||
team_notify_peers(team);
|
||||
team_lower_state_changed(port);
|
||||
}
|
||||
|
||||
static void __reconstruct_port_hlist(struct team *team, int rm_index)
|
||||
{
|
||||
int i;
|
||||
struct hlist_head *tx_port_index_hash;
|
||||
struct team_port *port;
|
||||
int i;
|
||||
|
||||
for (i = rm_index + 1; i < team->en_port_count; i++) {
|
||||
port = team_get_port_by_index(team, i);
|
||||
hlist_del_rcu(&port->hlist);
|
||||
port->index--;
|
||||
hlist_add_head_rcu(&port->hlist,
|
||||
team_port_index_hash(team, port->index));
|
||||
for (i = rm_index + 1; i < team->tx_en_port_count; i++) {
|
||||
port = team_get_port_by_tx_index(team, i);
|
||||
hlist_del_rcu(&port->tx_hlist);
|
||||
WRITE_ONCE(port->tx_index, port->tx_index - 1);
|
||||
tx_port_index_hash = team_tx_port_index_hash(team,
|
||||
port->tx_index);
|
||||
hlist_add_head_rcu(&port->tx_hlist, tx_port_index_hash);
|
||||
}
|
||||
}
|
||||
|
||||
static void __team_port_disable_tx(struct team *team,
|
||||
struct team_port *port)
|
||||
{
|
||||
if (team->ops.port_tx_disabled)
|
||||
team->ops.port_tx_disabled(team, port);
|
||||
|
||||
hlist_del_rcu(&port->tx_hlist);
|
||||
__reconstruct_port_hlist(team, port->tx_index);
|
||||
|
||||
WRITE_ONCE(port->tx_index, -1);
|
||||
WRITE_ONCE(team->tx_en_port_count, team->tx_en_port_count - 1);
|
||||
}
|
||||
|
||||
static void team_port_disable_tx(struct team *team,
|
||||
struct team_port *port)
|
||||
{
|
||||
if (!team_port_tx_enabled(port))
|
||||
return;
|
||||
|
||||
__team_port_disable_tx(team, port);
|
||||
|
||||
team_queue_override_port_del(team, port);
|
||||
team_adjust_ops(team);
|
||||
team_lower_state_changed(port);
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable TX AND RX on the port.
|
||||
*/
|
||||
static void team_port_enable(struct team *team,
|
||||
struct team_port *port)
|
||||
{
|
||||
bool rx_was_enabled;
|
||||
bool tx_was_enabled;
|
||||
|
||||
if (team_port_enabled(port))
|
||||
return;
|
||||
|
||||
rx_was_enabled = team_port_rx_enabled(port);
|
||||
tx_was_enabled = team_port_tx_enabled(port);
|
||||
|
||||
if (!rx_was_enabled)
|
||||
__team_port_enable_rx(team, port);
|
||||
if (!tx_was_enabled)
|
||||
__team_port_enable_tx(team, port);
|
||||
|
||||
team_adjust_ops(team);
|
||||
if (!tx_was_enabled)
|
||||
team_queue_override_port_add(team, port);
|
||||
team_notify_peers(team);
|
||||
if (!rx_was_enabled)
|
||||
team_mcast_rejoin(team);
|
||||
if (!tx_was_enabled)
|
||||
team_lower_state_changed(port);
|
||||
}
|
||||
|
||||
static void team_port_disable(struct team *team,
|
||||
struct team_port *port)
|
||||
{
|
||||
if (!team_port_enabled(port))
|
||||
bool rx_was_enabled = team_port_rx_enabled(port);
|
||||
bool tx_was_enabled = team_port_tx_enabled(port);
|
||||
|
||||
if (!tx_was_enabled && !rx_was_enabled)
|
||||
return;
|
||||
if (team->ops.port_disabled)
|
||||
team->ops.port_disabled(team, port);
|
||||
hlist_del_rcu(&port->hlist);
|
||||
__reconstruct_port_hlist(team, port->index);
|
||||
port->index = -1;
|
||||
team->en_port_count--;
|
||||
team_queue_override_port_del(team, port);
|
||||
|
||||
if (tx_was_enabled) {
|
||||
__team_port_disable_tx(team, port);
|
||||
team_queue_override_port_del(team, port);
|
||||
}
|
||||
if (rx_was_enabled)
|
||||
__team_port_disable_rx(team, port);
|
||||
|
||||
team_adjust_ops(team);
|
||||
team_lower_state_changed(port);
|
||||
|
||||
if (tx_was_enabled)
|
||||
team_lower_state_changed(port);
|
||||
}
|
||||
|
||||
static int team_port_enter(struct team *team, struct team_port *port)
|
||||
|
|
@ -1245,7 +1352,7 @@ static int team_port_add(struct team *team, struct net_device *port_dev,
|
|||
netif_addr_unlock_bh(dev);
|
||||
}
|
||||
|
||||
port->index = -1;
|
||||
WRITE_ONCE(port->tx_index, -1);
|
||||
list_add_tail_rcu(&port->list, &team->port_list);
|
||||
team_port_enable(team, port);
|
||||
netdev_compute_master_upper_features(dev, true);
|
||||
|
|
@ -1430,6 +1537,46 @@ static int team_port_en_option_set(struct team *team,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void team_port_rx_en_option_get(struct team *team,
|
||||
struct team_gsetter_ctx *ctx)
|
||||
{
|
||||
struct team_port *port = ctx->info->port;
|
||||
|
||||
ctx->data.bool_val = team_port_rx_enabled(port);
|
||||
}
|
||||
|
||||
static int team_port_rx_en_option_set(struct team *team,
|
||||
struct team_gsetter_ctx *ctx)
|
||||
{
|
||||
struct team_port *port = ctx->info->port;
|
||||
|
||||
if (ctx->data.bool_val)
|
||||
team_port_enable_rx(team, port);
|
||||
else
|
||||
team_port_disable_rx(team, port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void team_port_tx_en_option_get(struct team *team,
|
||||
struct team_gsetter_ctx *ctx)
|
||||
{
|
||||
struct team_port *port = ctx->info->port;
|
||||
|
||||
ctx->data.bool_val = team_port_tx_enabled(port);
|
||||
}
|
||||
|
||||
static int team_port_tx_en_option_set(struct team *team,
|
||||
struct team_gsetter_ctx *ctx)
|
||||
{
|
||||
struct team_port *port = ctx->info->port;
|
||||
|
||||
if (ctx->data.bool_val)
|
||||
team_port_enable_tx(team, port);
|
||||
else
|
||||
team_port_disable_tx(team, port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void team_user_linkup_option_get(struct team *team,
|
||||
struct team_gsetter_ctx *ctx)
|
||||
{
|
||||
|
|
@ -1551,6 +1698,20 @@ static const struct team_option team_options[] = {
|
|||
.getter = team_port_en_option_get,
|
||||
.setter = team_port_en_option_set,
|
||||
},
|
||||
{
|
||||
.name = "rx_enabled",
|
||||
.type = TEAM_OPTION_TYPE_BOOL,
|
||||
.per_port = true,
|
||||
.getter = team_port_rx_en_option_get,
|
||||
.setter = team_port_rx_en_option_set,
|
||||
},
|
||||
{
|
||||
.name = "tx_enabled",
|
||||
.type = TEAM_OPTION_TYPE_BOOL,
|
||||
.per_port = true,
|
||||
.getter = team_port_tx_en_option_get,
|
||||
.setter = team_port_tx_en_option_set,
|
||||
},
|
||||
{
|
||||
.name = "user_linkup",
|
||||
.type = TEAM_OPTION_TYPE_BOOL,
|
||||
|
|
@ -1596,7 +1757,7 @@ static int team_init(struct net_device *dev)
|
|||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < TEAM_PORT_HASHENTRIES; i++)
|
||||
INIT_HLIST_HEAD(&team->en_port_hlist[i]);
|
||||
INIT_HLIST_HEAD(&team->tx_en_port_hlist[i]);
|
||||
INIT_LIST_HEAD(&team->port_list);
|
||||
err = team_queue_override_init(team);
|
||||
if (err)
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ static struct team_port *lb_hash_select_tx_port(struct team *team,
|
|||
{
|
||||
int port_index = team_num_to_port_index(team, hash);
|
||||
|
||||
return team_get_port_by_index_rcu(team, port_index);
|
||||
return team_get_port_by_tx_index_rcu(team, port_index);
|
||||
}
|
||||
|
||||
/* Hash to port mapping select tx port */
|
||||
|
|
@ -380,7 +380,7 @@ static int lb_tx_hash_to_port_mapping_set(struct team *team,
|
|||
|
||||
list_for_each_entry(port, &team->port_list, list) {
|
||||
if (ctx->data.u32_val == port->dev->ifindex &&
|
||||
team_port_enabled(port)) {
|
||||
team_port_tx_enabled(port)) {
|
||||
rcu_assign_pointer(LB_HTPM_PORT_BY_HASH(lb_priv, hash),
|
||||
port);
|
||||
return 0;
|
||||
|
|
@ -655,7 +655,7 @@ static void lb_port_leave(struct team *team, struct team_port *port)
|
|||
free_percpu(lb_port_priv->pcpu_stats);
|
||||
}
|
||||
|
||||
static void lb_port_disabled(struct team *team, struct team_port *port)
|
||||
static void lb_port_tx_disabled(struct team *team, struct team_port *port)
|
||||
{
|
||||
lb_tx_hash_to_port_mapping_null_port(team, port);
|
||||
}
|
||||
|
|
@ -665,7 +665,7 @@ static const struct team_mode_ops lb_mode_ops = {
|
|||
.exit = lb_exit,
|
||||
.port_enter = lb_port_enter,
|
||||
.port_leave = lb_port_leave,
|
||||
.port_disabled = lb_port_disabled,
|
||||
.port_tx_disabled = lb_port_tx_disabled,
|
||||
.receive = lb_receive,
|
||||
.transmit = lb_transmit,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ static bool rnd_transmit(struct team *team, struct sk_buff *skb)
|
|||
struct team_port *port;
|
||||
int port_index;
|
||||
|
||||
port_index = get_random_u32_below(team->en_port_count);
|
||||
port = team_get_port_by_index_rcu(team, port_index);
|
||||
port_index = get_random_u32_below(READ_ONCE(team->tx_en_port_count));
|
||||
port = team_get_port_by_tx_index_rcu(team, port_index);
|
||||
if (unlikely(!port))
|
||||
goto drop;
|
||||
port = team_get_first_port_txable_rcu(team, port);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ static bool rr_transmit(struct team *team, struct sk_buff *skb)
|
|||
|
||||
port_index = team_num_to_port_index(team,
|
||||
rr_priv(team)->sent_packets++);
|
||||
port = team_get_port_by_index_rcu(team, port_index);
|
||||
port = team_get_port_by_tx_index_rcu(team, port_index);
|
||||
if (unlikely(!port))
|
||||
goto drop;
|
||||
port = team_get_first_port_txable_rcu(team, port);
|
||||
|
|
|
|||
|
|
@ -27,10 +27,11 @@ struct team;
|
|||
|
||||
struct team_port {
|
||||
struct net_device *dev;
|
||||
struct hlist_node hlist; /* node in enabled ports hash list */
|
||||
struct hlist_node tx_hlist; /* node in tx-enabled ports hash list */
|
||||
struct list_head list; /* node in ordinary list */
|
||||
struct team *team;
|
||||
int index; /* index of enabled port. If disabled, it's set to -1 */
|
||||
int tx_index; /* index of tx enabled port. If disabled, -1 */
|
||||
bool rx_enabled;
|
||||
|
||||
bool linkup; /* either state.linkup or user.linkup */
|
||||
|
||||
|
|
@ -75,14 +76,24 @@ static inline struct team_port *team_port_get_rcu(const struct net_device *dev)
|
|||
return rcu_dereference(dev->rx_handler_data);
|
||||
}
|
||||
|
||||
static inline bool team_port_rx_enabled(struct team_port *port)
|
||||
{
|
||||
return READ_ONCE(port->rx_enabled);
|
||||
}
|
||||
|
||||
static inline bool team_port_tx_enabled(struct team_port *port)
|
||||
{
|
||||
return READ_ONCE(port->tx_index) != -1;
|
||||
}
|
||||
|
||||
static inline bool team_port_enabled(struct team_port *port)
|
||||
{
|
||||
return port->index != -1;
|
||||
return team_port_rx_enabled(port) && team_port_tx_enabled(port);
|
||||
}
|
||||
|
||||
static inline bool team_port_txable(struct team_port *port)
|
||||
{
|
||||
return port->linkup && team_port_enabled(port);
|
||||
return port->linkup && team_port_tx_enabled(port);
|
||||
}
|
||||
|
||||
static inline bool team_port_dev_txable(const struct net_device *port_dev)
|
||||
|
|
@ -121,8 +132,7 @@ struct team_mode_ops {
|
|||
int (*port_enter)(struct team *team, struct team_port *port);
|
||||
void (*port_leave)(struct team *team, struct team_port *port);
|
||||
void (*port_change_dev_addr)(struct team *team, struct team_port *port);
|
||||
void (*port_enabled)(struct team *team, struct team_port *port);
|
||||
void (*port_disabled)(struct team *team, struct team_port *port);
|
||||
void (*port_tx_disabled)(struct team *team, struct team_port *port);
|
||||
};
|
||||
|
||||
extern int team_modeop_port_enter(struct team *team, struct team_port *port);
|
||||
|
|
@ -191,10 +201,11 @@ struct team {
|
|||
const struct header_ops *header_ops_cache;
|
||||
|
||||
/*
|
||||
* List of enabled ports and their count
|
||||
* List of tx-enabled ports and counts of rx and tx-enabled ports.
|
||||
*/
|
||||
int en_port_count;
|
||||
struct hlist_head en_port_hlist[TEAM_PORT_HASHENTRIES];
|
||||
int tx_en_port_count;
|
||||
int rx_en_port_count;
|
||||
struct hlist_head tx_en_port_hlist[TEAM_PORT_HASHENTRIES];
|
||||
|
||||
struct list_head port_list; /* list of all ports */
|
||||
|
||||
|
|
@ -238,41 +249,43 @@ static inline int team_dev_queue_xmit(struct team *team, struct team_port *port,
|
|||
return dev_queue_xmit(skb);
|
||||
}
|
||||
|
||||
static inline struct hlist_head *team_port_index_hash(struct team *team,
|
||||
int port_index)
|
||||
static inline struct hlist_head *team_tx_port_index_hash(struct team *team,
|
||||
int tx_port_index)
|
||||
{
|
||||
return &team->en_port_hlist[port_index & (TEAM_PORT_HASHENTRIES - 1)];
|
||||
unsigned int list_entry = tx_port_index & (TEAM_PORT_HASHENTRIES - 1);
|
||||
|
||||
return &team->tx_en_port_hlist[list_entry];
|
||||
}
|
||||
|
||||
static inline struct team_port *team_get_port_by_index(struct team *team,
|
||||
int port_index)
|
||||
static inline struct team_port *team_get_port_by_tx_index(struct team *team,
|
||||
int tx_port_index)
|
||||
{
|
||||
struct hlist_head *head = team_tx_port_index_hash(team, tx_port_index);
|
||||
struct team_port *port;
|
||||
struct hlist_head *head = team_port_index_hash(team, port_index);
|
||||
|
||||
hlist_for_each_entry(port, head, hlist)
|
||||
if (port->index == port_index)
|
||||
hlist_for_each_entry(port, head, tx_hlist)
|
||||
if (port->tx_index == tx_port_index)
|
||||
return port;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline int team_num_to_port_index(struct team *team, unsigned int num)
|
||||
{
|
||||
int en_port_count = READ_ONCE(team->en_port_count);
|
||||
int tx_en_port_count = READ_ONCE(team->tx_en_port_count);
|
||||
|
||||
if (unlikely(!en_port_count))
|
||||
if (unlikely(!tx_en_port_count))
|
||||
return 0;
|
||||
return num % en_port_count;
|
||||
return num % tx_en_port_count;
|
||||
}
|
||||
|
||||
static inline struct team_port *team_get_port_by_index_rcu(struct team *team,
|
||||
int port_index)
|
||||
static inline struct team_port *team_get_port_by_tx_index_rcu(struct team *team,
|
||||
int tx_port_index)
|
||||
{
|
||||
struct hlist_head *head = team_tx_port_index_hash(team, tx_port_index);
|
||||
struct team_port *port;
|
||||
struct hlist_head *head = team_port_index_hash(team, port_index);
|
||||
|
||||
hlist_for_each_entry_rcu(port, head, hlist)
|
||||
if (port->index == port_index)
|
||||
hlist_for_each_entry_rcu(port, head, tx_hlist)
|
||||
if (READ_ONCE(port->tx_index) == tx_port_index)
|
||||
return port;
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,18 @@
|
|||
# Makefile for net selftests
|
||||
|
||||
TEST_PROGS := \
|
||||
decoupled_enablement.sh \
|
||||
dev_addr_lists.sh \
|
||||
non_ether_header_ops.sh \
|
||||
options.sh \
|
||||
propagation.sh \
|
||||
refleak.sh \
|
||||
teamd_activebackup.sh \
|
||||
transmit_failover.sh \
|
||||
# end of TEST_PROGS
|
||||
|
||||
TEST_INCLUDES := \
|
||||
team_lib.sh \
|
||||
../bonding/lag_lib.sh \
|
||||
../../../net/forwarding/lib.sh \
|
||||
../../../net/in_netns.sh \
|
||||
|
|
|
|||
|
|
@ -6,4 +6,8 @@ CONFIG_NETDEVSIM=m
|
|||
CONFIG_NET_IPGRE=y
|
||||
CONFIG_NET_TEAM=y
|
||||
CONFIG_NET_TEAM_MODE_ACTIVEBACKUP=y
|
||||
CONFIG_NET_TEAM_MODE_BROADCAST=y
|
||||
CONFIG_NET_TEAM_MODE_LOADBALANCE=y
|
||||
CONFIG_NET_TEAM_MODE_RANDOM=y
|
||||
CONFIG_NET_TEAM_MODE_ROUNDROBIN=y
|
||||
CONFIG_VETH=y
|
||||
|
|
|
|||
|
|
@ -0,0 +1,249 @@
|
|||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
# These tests verify the decoupled RX and TX enablement of team driver member
|
||||
# interfaces.
|
||||
#
|
||||
# Topology
|
||||
#
|
||||
# +---------------------+ NS1
|
||||
# | test_team1 |
|
||||
# | | |
|
||||
# | eth0 |
|
||||
# | | |
|
||||
# | | |
|
||||
# +---------------------+
|
||||
# |
|
||||
# +---------------------+ NS2
|
||||
# | | |
|
||||
# | | |
|
||||
# | eth0 |
|
||||
# | | |
|
||||
# | test_team2 |
|
||||
# +---------------------+
|
||||
|
||||
export ALL_TESTS="
|
||||
team_test_tx_enablement
|
||||
team_test_rx_enablement
|
||||
"
|
||||
|
||||
test_dir="$(dirname "$0")"
|
||||
# shellcheck disable=SC1091
|
||||
source "${test_dir}/../../../net/lib.sh"
|
||||
# shellcheck disable=SC1091
|
||||
source "${test_dir}/team_lib.sh"
|
||||
|
||||
NS1=""
|
||||
NS2=""
|
||||
export NODAD="nodad"
|
||||
PREFIX_LENGTH="64"
|
||||
NS1_IP="fd00::1"
|
||||
NS2_IP="fd00::2"
|
||||
NS1_IP4="192.168.0.1"
|
||||
NS2_IP4="192.168.0.2"
|
||||
MEMBERS=("eth0")
|
||||
PING_COUNT=5
|
||||
PING_TIMEOUT_S=1
|
||||
PING_INTERVAL=0.1
|
||||
|
||||
while getopts "4" opt; do
|
||||
case $opt in
|
||||
4)
|
||||
echo "IPv4 mode selected."
|
||||
export NODAD=
|
||||
PREFIX_LENGTH="24"
|
||||
NS1_IP="${NS1_IP4}"
|
||||
NS2_IP="${NS2_IP4}"
|
||||
;;
|
||||
\?)
|
||||
echo "Invalid option: -$OPTARG" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This has to be sourced after opts are gathered...
|
||||
export REQUIRE_MZ=no
|
||||
export NUM_NETIFS=0
|
||||
# shellcheck disable=SC1091
|
||||
source "${test_dir}/../../../net/forwarding/lib.sh"
|
||||
|
||||
# Create the network namespaces, veth pair, and team devices in the specified
|
||||
# mode.
|
||||
# Globals:
|
||||
# RET - Used by test infra, set by `check_err` functions.
|
||||
# Arguments:
|
||||
# mode - The team driver mode to use for the team devices.
|
||||
environment_create()
|
||||
{
|
||||
trap cleanup_all_ns EXIT
|
||||
setup_ns ns1 ns2
|
||||
NS1="${NS_LIST[0]}"
|
||||
NS2="${NS_LIST[1]}"
|
||||
|
||||
# Create the interfaces.
|
||||
ip -n "${NS1}" link add eth0 type veth peer name eth0 netns "${NS2}"
|
||||
ip -n "${NS1}" link add test_team1 type team
|
||||
ip -n "${NS2}" link add test_team2 type team
|
||||
|
||||
# Set up the receiving network namespace's team interface.
|
||||
setup_team "${NS2}" test_team2 roundrobin "${NS2_IP}" \
|
||||
"${PREFIX_LENGTH}" "${MEMBERS[@]}"
|
||||
}
|
||||
|
||||
# Set a particular option value for team or team port.
|
||||
# Arguments:
|
||||
# namespace - The namespace name that has the team.
|
||||
# option_name - The option name to set.
|
||||
# option_value - The value to set the option to.
|
||||
# team_name - The name of team to set the option for.
|
||||
# member_name - The (optional) optional name of the member port.
|
||||
set_option_value()
|
||||
{
|
||||
local namespace="$1"
|
||||
local option_name="$2"
|
||||
local option_value="$3"
|
||||
local team_name="$4"
|
||||
local member_name="$5"
|
||||
local port_flag="--port=${member_name}"
|
||||
|
||||
ip netns exec "${namespace}" teamnl "${team_name}" setoption \
|
||||
"${option_name}" "${option_value}" "${port_flag}"
|
||||
return $?
|
||||
}
|
||||
|
||||
# Send some pings and return the ping command return value.
|
||||
try_ping()
|
||||
{
|
||||
ip netns exec "${NS1}" ping -i "${PING_INTERVAL}" -c "${PING_COUNT}" \
|
||||
"${NS2_IP}" -W "${PING_TIMEOUT_S}"
|
||||
}
|
||||
|
||||
# Checks tcpdump output from net/forwarding lib, and checks if there are any
|
||||
# ICMP(4 or 6) packets.
|
||||
# Arguments:
|
||||
# interface - The interface name to search for.
|
||||
# ip_address - The destination IP address (4 or 6) to search for.
|
||||
did_interface_receive_icmp()
|
||||
{
|
||||
local interface="$1"
|
||||
local ip_address="$2"
|
||||
local packet_count
|
||||
|
||||
packet_count=$(tcpdump_show "$interface" | grep -c \
|
||||
"> ${ip_address}: ICMP")
|
||||
echo "Packet count for ${interface} was ${packet_count}"
|
||||
|
||||
if [[ "$packet_count" -gt 0 ]]; then
|
||||
true
|
||||
else
|
||||
false
|
||||
fi
|
||||
}
|
||||
|
||||
# Test JUST tx enablement with a given mode.
|
||||
# Globals:
|
||||
# RET - Used by test infra, set by `check_err` functions.
|
||||
# Arguments:
|
||||
# mode - The mode to set the team interfaces to.
|
||||
team_test_mode_tx_enablement()
|
||||
{
|
||||
local mode="$1"
|
||||
export RET=0
|
||||
|
||||
# Set up the sender team with the correct mode.
|
||||
setup_team "${NS1}" test_team1 "${mode}" "${NS1_IP}" \
|
||||
"${PREFIX_LENGTH}" "${MEMBERS[@]}"
|
||||
check_err $? "Failed to set up sender team"
|
||||
|
||||
### Scenario 1: Member interface initially enabled.
|
||||
# Expect ping to pass
|
||||
try_ping
|
||||
check_err $? "Ping failed when TX enabled"
|
||||
|
||||
### Scenario 2: One tx-side interface disabled.
|
||||
# Expect ping to fail.
|
||||
set_option_value "${NS1}" tx_enabled false test_team1 eth0
|
||||
check_err $? "Failed to disable TX"
|
||||
tcpdump_start eth0 "${NS2}"
|
||||
try_ping
|
||||
check_fail $? "Ping succeeded when TX disabled"
|
||||
tcpdump_stop eth0
|
||||
# Expect no packets to be transmitted, since TX is disabled.
|
||||
did_interface_receive_icmp eth0 "${NS2_IP}"
|
||||
check_fail $? "eth0 IS transmitting when TX disabled"
|
||||
tcpdump_cleanup eth0
|
||||
|
||||
### Scenario 3: The interface has tx re-enabled.
|
||||
# Expect ping to pass.
|
||||
set_option_value "${NS1}" tx_enabled true test_team1 eth0
|
||||
check_err $? "Failed to reenable TX"
|
||||
try_ping
|
||||
check_err $? "Ping failed when TX reenabled"
|
||||
|
||||
log_test "TX failover of '${mode}' test"
|
||||
}
|
||||
|
||||
# Test JUST rx enablement with a given mode.
|
||||
# Globals:
|
||||
# RET - Used by test infra, set by `check_err` functions.
|
||||
# Arguments:
|
||||
# mode - The mode to set the team interfaces to.
|
||||
team_test_mode_rx_enablement()
|
||||
{
|
||||
local mode="$1"
|
||||
export RET=0
|
||||
|
||||
# Set up the sender team with the correct mode.
|
||||
setup_team "${NS1}" test_team1 "${mode}" "${NS1_IP}" \
|
||||
"${PREFIX_LENGTH}" "${MEMBERS[@]}"
|
||||
check_err $? "Failed to set up sender team"
|
||||
|
||||
### Scenario 1: Member interface initially enabled.
|
||||
# Expect ping to pass
|
||||
try_ping
|
||||
check_err $? "Ping failed when RX enabled"
|
||||
|
||||
### Scenario 2: One rx-side interface disabled.
|
||||
# Expect ping to fail.
|
||||
set_option_value "${NS1}" rx_enabled false test_team1 eth0
|
||||
check_err $? "Failed to disable RX"
|
||||
tcpdump_start eth0 "${NS2}"
|
||||
try_ping
|
||||
check_fail $? "Ping succeeded when RX disabled"
|
||||
tcpdump_stop eth0
|
||||
# Expect packets to be transmitted, since only RX is disabled.
|
||||
did_interface_receive_icmp eth0 "${NS2_IP}"
|
||||
check_err $? "eth0 not transmitting when RX disabled"
|
||||
tcpdump_cleanup eth0
|
||||
|
||||
### Scenario 3: The interface has rx re-enabled.
|
||||
# Expect ping to pass.
|
||||
set_option_value "${NS1}" rx_enabled true test_team1 eth0
|
||||
check_err $? "Failed to reenable RX"
|
||||
try_ping
|
||||
check_err $? "Ping failed when RX reenabled"
|
||||
|
||||
log_test "RX failover of '${mode}' test"
|
||||
}
|
||||
|
||||
team_test_tx_enablement()
|
||||
{
|
||||
team_test_mode_tx_enablement broadcast
|
||||
team_test_mode_tx_enablement roundrobin
|
||||
team_test_mode_tx_enablement random
|
||||
}
|
||||
|
||||
team_test_rx_enablement()
|
||||
{
|
||||
team_test_mode_rx_enablement broadcast
|
||||
team_test_mode_rx_enablement roundrobin
|
||||
team_test_mode_rx_enablement random
|
||||
}
|
||||
|
||||
require_command teamnl
|
||||
require_command tcpdump
|
||||
require_command ping
|
||||
environment_create
|
||||
tests_run
|
||||
exit "${EXIT_STATUS}"
|
||||
|
|
@ -11,10 +11,14 @@ if [[ $# -eq 0 ]]; then
|
|||
exit $?
|
||||
fi
|
||||
|
||||
ALL_TESTS="
|
||||
export ALL_TESTS="
|
||||
team_test_options
|
||||
team_test_enabled_implicit_changes
|
||||
team_test_rx_enabled_implicit_changes
|
||||
team_test_tx_enabled_implicit_changes
|
||||
"
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source "${test_dir}/../../../net/lib.sh"
|
||||
|
||||
TEAM_PORT="team0"
|
||||
|
|
@ -176,12 +180,105 @@ team_test_options()
|
|||
team_test_option mcast_rejoin_count 0 5
|
||||
team_test_option mcast_rejoin_interval 0 5
|
||||
team_test_option enabled true false "${MEMBER_PORT}"
|
||||
team_test_option rx_enabled true false "${MEMBER_PORT}"
|
||||
team_test_option tx_enabled true false "${MEMBER_PORT}"
|
||||
team_test_option user_linkup true false "${MEMBER_PORT}"
|
||||
team_test_option user_linkup_enabled true false "${MEMBER_PORT}"
|
||||
team_test_option priority 10 20 "${MEMBER_PORT}"
|
||||
team_test_option queue_id 0 1 "${MEMBER_PORT}"
|
||||
}
|
||||
|
||||
team_test_enabled_implicit_changes()
|
||||
{
|
||||
export RET=0
|
||||
|
||||
attach_port_if_specified "${MEMBER_PORT}"
|
||||
check_err $? "Couldn't attach ${MEMBER_PORT} to master"
|
||||
|
||||
# Set enabled to true.
|
||||
set_and_check_get enabled true "--port=${MEMBER_PORT}"
|
||||
check_err $? "Failed to set 'enabled' to true"
|
||||
|
||||
# Show that both rx enabled and tx enabled are true.
|
||||
get_and_check_value rx_enabled true "--port=${MEMBER_PORT}"
|
||||
check_err $? "'Rx_enabled' wasn't implicitly set to true"
|
||||
get_and_check_value tx_enabled true "--port=${MEMBER_PORT}"
|
||||
check_err $? "'Tx_enabled' wasn't implicitly set to true"
|
||||
|
||||
# Set enabled to false.
|
||||
set_and_check_get enabled false "--port=${MEMBER_PORT}"
|
||||
check_err $? "Failed to set 'enabled' to false"
|
||||
|
||||
# Show that both rx enabled and tx enabled are false.
|
||||
get_and_check_value rx_enabled false "--port=${MEMBER_PORT}"
|
||||
check_err $? "'Rx_enabled' wasn't implicitly set to false"
|
||||
get_and_check_value tx_enabled false "--port=${MEMBER_PORT}"
|
||||
check_err $? "'Tx_enabled' wasn't implicitly set to false"
|
||||
|
||||
log_test "'Enabled' implicit changes"
|
||||
}
|
||||
|
||||
team_test_rx_enabled_implicit_changes()
|
||||
{
|
||||
export RET=0
|
||||
|
||||
attach_port_if_specified "${MEMBER_PORT}"
|
||||
check_err $? "Couldn't attach ${MEMBER_PORT} to master"
|
||||
|
||||
# Set enabled to true.
|
||||
set_and_check_get enabled true "--port=${MEMBER_PORT}"
|
||||
check_err $? "Failed to set 'enabled' to true"
|
||||
|
||||
# Set rx_enabled to false.
|
||||
set_and_check_get rx_enabled false "--port=${MEMBER_PORT}"
|
||||
check_err $? "Failed to set 'rx_enabled' to false"
|
||||
|
||||
# Show that enabled is false.
|
||||
get_and_check_value enabled false "--port=${MEMBER_PORT}"
|
||||
check_err $? "'enabled' wasn't implicitly set to false"
|
||||
|
||||
# Set rx_enabled to true.
|
||||
set_and_check_get rx_enabled true "--port=${MEMBER_PORT}"
|
||||
check_err $? "Failed to set 'rx_enabled' to true"
|
||||
|
||||
# Show that enabled is true.
|
||||
get_and_check_value enabled true "--port=${MEMBER_PORT}"
|
||||
check_err $? "'enabled' wasn't implicitly set to true"
|
||||
|
||||
log_test "'Rx_enabled' implicit changes"
|
||||
}
|
||||
|
||||
team_test_tx_enabled_implicit_changes()
|
||||
{
|
||||
export RET=0
|
||||
|
||||
attach_port_if_specified "${MEMBER_PORT}"
|
||||
check_err $? "Couldn't attach ${MEMBER_PORT} to master"
|
||||
|
||||
# Set enabled to true.
|
||||
set_and_check_get enabled true "--port=${MEMBER_PORT}"
|
||||
check_err $? "Failed to set 'enabled' to true"
|
||||
|
||||
# Set tx_enabled to false.
|
||||
set_and_check_get tx_enabled false "--port=${MEMBER_PORT}"
|
||||
check_err $? "Failed to set 'tx_enabled' to false"
|
||||
|
||||
# Show that enabled is false.
|
||||
get_and_check_value enabled false "--port=${MEMBER_PORT}"
|
||||
check_err $? "'enabled' wasn't implicitly set to false"
|
||||
|
||||
# Set tx_enabled to true.
|
||||
set_and_check_get tx_enabled true "--port=${MEMBER_PORT}"
|
||||
check_err $? "Failed to set 'tx_enabled' to true"
|
||||
|
||||
# Show that enabled is true.
|
||||
get_and_check_value enabled true "--port=${MEMBER_PORT}"
|
||||
check_err $? "'enabled' wasn't implicitly set to true"
|
||||
|
||||
log_test "'Tx_enabled' implicit changes"
|
||||
}
|
||||
|
||||
|
||||
require_command teamnl
|
||||
setup
|
||||
tests_run
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
timeout=300
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
test_dir="$(dirname "$0")"
|
||||
export REQUIRE_MZ=no
|
||||
export NUM_NETIFS=0
|
||||
# shellcheck disable=SC1091
|
||||
source "${test_dir}/../../../net/forwarding/lib.sh"
|
||||
|
||||
TCP_PORT="43434"
|
||||
|
||||
# Create a team interface inside of a given network namespace with a given
|
||||
# mode, members, and IP address.
|
||||
# Arguments:
|
||||
# namespace - Network namespace to put the team interface into.
|
||||
# team - The name of the team interface to setup.
|
||||
# mode - The team mode of the interface.
|
||||
# ip_address - The IP address to assign to the team interface.
|
||||
# prefix_length - The prefix length for the IP address subnet.
|
||||
# $@ - members - The member interfaces of the aggregation.
|
||||
setup_team()
|
||||
{
|
||||
local namespace=$1
|
||||
local team=$2
|
||||
local mode=$3
|
||||
local ip_address=$4
|
||||
local prefix_length=$5
|
||||
shift 5
|
||||
local members=("$@")
|
||||
|
||||
# Prerequisite: team must have no members
|
||||
for member in "${members[@]}"; do
|
||||
ip -n "${namespace}" link set "${member}" nomaster
|
||||
done
|
||||
|
||||
# Prerequisite: team must have no address in order to set it
|
||||
# shellcheck disable=SC2086
|
||||
ip -n "${namespace}" addr del "${ip_address}/${prefix_length}" \
|
||||
${NODAD} dev "${team}"
|
||||
|
||||
echo "Setting team in ${namespace} to mode ${mode}"
|
||||
|
||||
if ! ip -n "${namespace}" link set "${team}" down; then
|
||||
echo "Failed to bring team device down"
|
||||
return 1
|
||||
fi
|
||||
if ! ip netns exec "${namespace}" teamnl "${team}" setoption mode \
|
||||
"${mode}"; then
|
||||
echo "Failed to set ${team} mode to '${mode}'"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Aggregate the members into teams.
|
||||
for member in "${members[@]}"; do
|
||||
ip -n "${namespace}" link set "${member}" master "${team}"
|
||||
done
|
||||
|
||||
# Bring team devices up and give them addresses.
|
||||
if ! ip -n "${namespace}" link set "${team}" up; then
|
||||
echo "Failed to set ${team} up"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
if ! ip -n "${namespace}" addr add "${ip_address}/${prefix_length}" \
|
||||
${NODAD} dev "${team}"; then
|
||||
echo "Failed to give ${team} IP address in ${namespace}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# This is global used to keep track of the sender's iperf3 process, so that it
|
||||
# can be terminated.
|
||||
declare sender_pid
|
||||
|
||||
# Start sending and receiving TCP traffic with iperf3.
|
||||
# Globals:
|
||||
# sender_pid - The process ID of the iperf3 sender process. Used to kill it
|
||||
# later.
|
||||
start_listening_and_sending()
|
||||
{
|
||||
ip netns exec "${NS2}" iperf3 -s -p "${TCP_PORT}" --logfile /dev/null &
|
||||
# Wait for server to become reachable before starting client.
|
||||
slowwait 5 ip netns exec "${NS1}" iperf3 -c "${NS2_IP}" -p \
|
||||
"${TCP_PORT}" -t 1 --logfile /dev/null
|
||||
ip netns exec "${NS1}" iperf3 -c "${NS2_IP}" -p "${TCP_PORT}" -b 1M -l \
|
||||
1K -t 0 --logfile /dev/null &
|
||||
sender_pid=$!
|
||||
}
|
||||
|
||||
# Stop sending TCP traffic with iperf3.
|
||||
# Globals:
|
||||
# sender_pid - The process ID of the iperf3 sender process.
|
||||
stop_sending_and_listening()
|
||||
{
|
||||
kill "${sender_pid}" && wait "${sender_pid}" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Monitor for TCP traffic with Tcpdump, save results to temp files.
|
||||
# Arguments:
|
||||
# namespace - The network namespace to run tcpdump inside of.
|
||||
# $@ - interfaces - The interfaces to listen to.
|
||||
save_tcpdump_outputs()
|
||||
{
|
||||
local namespace=$1
|
||||
shift 1
|
||||
local interfaces=("$@")
|
||||
|
||||
for interface in "${interfaces[@]}"; do
|
||||
tcpdump_start "${interface}" "${namespace}"
|
||||
done
|
||||
|
||||
sleep 1
|
||||
|
||||
for interface in "${interfaces[@]}"; do
|
||||
tcpdump_stop_nosleep "${interface}"
|
||||
done
|
||||
}
|
||||
|
||||
clear_tcpdump_outputs()
|
||||
{
|
||||
local interfaces=("$@")
|
||||
|
||||
for interface in "${interfaces[@]}"; do
|
||||
tcpdump_cleanup "${interface}"
|
||||
done
|
||||
}
|
||||
|
||||
# Read Tcpdump output, determine packet counts.
|
||||
# Arguments:
|
||||
# interface - The name of the interface to count packets for.
|
||||
# ip_address - The destination IP address.
|
||||
did_interface_receive()
|
||||
{
|
||||
local interface="$1"
|
||||
local ip_address="$2"
|
||||
local packet_count
|
||||
|
||||
packet_count=$(tcpdump_show "$interface" | grep -c \
|
||||
"> ${ip_address}.${TCP_PORT}")
|
||||
echo "Packet count for ${interface} was ${packet_count}"
|
||||
|
||||
if [[ "${packet_count}" -gt 0 ]]; then
|
||||
true
|
||||
else
|
||||
false
|
||||
fi
|
||||
}
|
||||
|
||||
# Return true if the given interface in the given namespace does NOT receive
|
||||
# traffic over a 1 second period.
|
||||
# Arguments:
|
||||
# interface - The name of the interface.
|
||||
# ip_address - The destination IP address.
|
||||
# namespace - The name of the namespace that the interface is in.
|
||||
check_no_traffic()
|
||||
{
|
||||
local interface="$1"
|
||||
local ip_address="$2"
|
||||
local namespace="$3"
|
||||
local rc
|
||||
|
||||
save_tcpdump_outputs "${namespace}" "${interface}"
|
||||
did_interface_receive "${interface}" "${ip_address}"
|
||||
rc=$?
|
||||
|
||||
clear_tcpdump_outputs "${interface}"
|
||||
|
||||
if [[ "${rc}" -eq 0 ]]; then
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
# These tests verify that teamd is able to enable and disable ports via the
|
||||
# active backup runner.
|
||||
#
|
||||
# Topology:
|
||||
#
|
||||
# +-------------------------+ NS1
|
||||
# | test_team1 |
|
||||
# | + |
|
||||
# | eth0 | eth1 |
|
||||
# | +---+---+ |
|
||||
# | | | |
|
||||
# +-------------------------+
|
||||
# | |
|
||||
# +-------------------------+ NS2
|
||||
# | | | |
|
||||
# | +-------+ |
|
||||
# | eth0 | eth1 |
|
||||
# | + |
|
||||
# | test_team2 |
|
||||
# +-------------------------+
|
||||
|
||||
export ALL_TESTS="teamd_test_active_backup"
|
||||
|
||||
test_dir="$(dirname "$0")"
|
||||
# shellcheck disable=SC1091
|
||||
source "${test_dir}/../../../net/lib.sh"
|
||||
# shellcheck disable=SC1091
|
||||
source "${test_dir}/team_lib.sh"
|
||||
|
||||
NS1=""
|
||||
NS2=""
|
||||
export NODAD="nodad"
|
||||
PREFIX_LENGTH="64"
|
||||
NS1_IP="fd00::1"
|
||||
NS2_IP="fd00::2"
|
||||
NS1_IP4="192.168.0.1"
|
||||
NS2_IP4="192.168.0.2"
|
||||
NS1_TEAMD_CONF=""
|
||||
NS2_TEAMD_CONF=""
|
||||
NS1_TEAMD_PID=""
|
||||
NS2_TEAMD_PID=""
|
||||
|
||||
while getopts "4" opt; do
|
||||
case $opt in
|
||||
4)
|
||||
echo "IPv4 mode selected."
|
||||
export NODAD=
|
||||
PREFIX_LENGTH="24"
|
||||
NS1_IP="${NS1_IP4}"
|
||||
NS2_IP="${NS2_IP4}"
|
||||
;;
|
||||
\?)
|
||||
echo "Invalid option: -${OPTARG}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
teamd_config_create()
|
||||
{
|
||||
local runner=$1
|
||||
local dev=$2
|
||||
local conf
|
||||
|
||||
conf=$(mktemp)
|
||||
|
||||
cat > "${conf}" <<-EOF
|
||||
{
|
||||
"device": "${dev}",
|
||||
"runner": {"name": "${runner}"},
|
||||
"ports": {
|
||||
"eth0": {},
|
||||
"eth1": {}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
echo "${conf}"
|
||||
}
|
||||
|
||||
# Create the network namespaces, veth pair, and team devices in the specified
|
||||
# runner.
|
||||
# Globals:
|
||||
# RET - Used by test infra, set by `check_err` functions.
|
||||
# Arguments:
|
||||
# runner - The Teamd runner to use for the Team devices.
|
||||
environment_create()
|
||||
{
|
||||
local runner=$1
|
||||
|
||||
echo "Setting up two-link aggregation for runner ${runner}"
|
||||
echo "Teamd version is: $(teamd --version)"
|
||||
trap environment_destroy EXIT
|
||||
|
||||
setup_ns ns1 ns2
|
||||
NS1="${NS_LIST[0]}"
|
||||
NS2="${NS_LIST[1]}"
|
||||
|
||||
for link in $(seq 0 1); do
|
||||
ip -n "${NS1}" link add "eth${link}" type veth peer name \
|
||||
"eth${link}" netns "${NS2}"
|
||||
check_err $? "Failed to create veth pair"
|
||||
done
|
||||
|
||||
NS1_TEAMD_CONF=$(teamd_config_create "${runner}" "test_team1")
|
||||
NS2_TEAMD_CONF=$(teamd_config_create "${runner}" "test_team2")
|
||||
echo "Conf files are ${NS1_TEAMD_CONF} and ${NS2_TEAMD_CONF}"
|
||||
|
||||
ip netns exec "${NS1}" teamd -d -f "${NS1_TEAMD_CONF}"
|
||||
check_err $? "Failed to create team device in ${NS1}"
|
||||
NS1_TEAMD_PID=$(pgrep -f "teamd -d -f ${NS1_TEAMD_CONF}")
|
||||
|
||||
ip netns exec "${NS2}" teamd -d -f "${NS2_TEAMD_CONF}"
|
||||
check_err $? "Failed to create team device in ${NS2}"
|
||||
NS2_TEAMD_PID=$(pgrep -f "teamd -d -f ${NS2_TEAMD_CONF}")
|
||||
|
||||
echo "Created team devices"
|
||||
echo "Teamd PIDs are ${NS1_TEAMD_PID} and ${NS2_TEAMD_PID}"
|
||||
|
||||
ip -n "${NS1}" link set test_team1 up
|
||||
check_err $? "Failed to set test_team1 up in ${NS1}"
|
||||
ip -n "${NS2}" link set test_team2 up
|
||||
check_err $? "Failed to set test_team2 up in ${NS2}"
|
||||
|
||||
ip -n "${NS1}" addr add "${NS1_IP}/${PREFIX_LENGTH}" "${NODAD}" dev \
|
||||
test_team1
|
||||
check_err $? "Failed to add address to team device in ${NS1}"
|
||||
ip -n "${NS2}" addr add "${NS2_IP}/${PREFIX_LENGTH}" "${NODAD}" dev \
|
||||
test_team2
|
||||
check_err $? "Failed to add address to team device in ${NS2}"
|
||||
|
||||
slowwait 2 timeout 0.5 ip netns exec "${NS1}" ping -W 1 -c 1 "${NS2_IP}"
|
||||
}
|
||||
|
||||
# Tear down the environment: kill teamd and delete network namespaces.
|
||||
environment_destroy()
|
||||
{
|
||||
echo "Tearing down two-link aggregation"
|
||||
|
||||
rm "${NS1_TEAMD_CONF}"
|
||||
rm "${NS2_TEAMD_CONF}"
|
||||
|
||||
# First, try graceful teamd teardown.
|
||||
ip netns exec "${NS1}" teamd -k -t test_team1
|
||||
ip netns exec "${NS2}" teamd -k -t test_team2
|
||||
|
||||
# If teamd can't be killed gracefully, then sigkill.
|
||||
if kill -0 "${NS1_TEAMD_PID}" 2>/dev/null; then
|
||||
echo "Sending sigkill to teamd for test_team1"
|
||||
kill -9 "${NS1_TEAMD_PID}"
|
||||
rm -f /var/run/teamd/test_team1.{pid,sock}
|
||||
fi
|
||||
if kill -0 "${NS2_TEAMD_PID}" 2>/dev/null; then
|
||||
echo "Sending sigkill to teamd for test_team2"
|
||||
kill -9 "${NS2_TEAMD_PID}"
|
||||
rm -f /var/run/teamd/test_team2.{pid,sock}
|
||||
fi
|
||||
cleanup_all_ns
|
||||
}
|
||||
|
||||
# Change the active port for an active-backup mode team.
|
||||
# Arguments:
|
||||
# namespace - The network namespace that the team is in.
|
||||
# team - The name of the team.
|
||||
# active_port - The port to make active.
|
||||
set_active_port()
|
||||
{
|
||||
local namespace=$1
|
||||
local team=$2
|
||||
local active_port=$3
|
||||
|
||||
ip netns exec "${namespace}" teamdctl "${team}" state item set \
|
||||
runner.active_port "${active_port}"
|
||||
slowwait 2 bash -c "ip netns exec ${namespace} teamdctl ${team} state \
|
||||
item get runner.active_port | grep -q ${active_port}"
|
||||
}
|
||||
|
||||
# Wait for an interface to stop receiving traffic. If it keeps receiving traffic
|
||||
# for the duration of the timeout, then return an error.
|
||||
# Arguments:
|
||||
# - namespace - The network namespace that the interface is in.
|
||||
# - interface - The name of the interface.
|
||||
wait_to_stop_receiving()
|
||||
{
|
||||
local namespace=$1
|
||||
local interface=$2
|
||||
|
||||
echo "Waiting for ${interface} in ${namespace} to stop receiving"
|
||||
slowwait 10 check_no_traffic "${interface}" "${NS2_IP}" \
|
||||
"${namespace}"
|
||||
}
|
||||
|
||||
# Test that active backup runner can change active ports.
|
||||
# Globals:
|
||||
# RET - Used by test infra, set by `check_err` functions.
|
||||
teamd_test_active_backup()
|
||||
{
|
||||
export RET=0
|
||||
|
||||
start_listening_and_sending
|
||||
|
||||
### Scenario 1: Don't manually set active port, just make sure team
|
||||
# works.
|
||||
save_tcpdump_outputs "${NS2}" test_team2
|
||||
did_interface_receive test_team2 "${NS2_IP}"
|
||||
check_err $? "Traffic did not reach team interface in NS2."
|
||||
clear_tcpdump_outputs test_team2
|
||||
|
||||
### Scenario 2: Choose active port.
|
||||
set_active_port "${NS1}" test_team1 eth1
|
||||
set_active_port "${NS2}" test_team2 eth1
|
||||
|
||||
wait_to_stop_receiving "${NS2}" eth0
|
||||
save_tcpdump_outputs "${NS2}" eth0 eth1
|
||||
did_interface_receive eth0 "${NS2_IP}"
|
||||
check_fail $? "eth0 IS transmitting when inactive"
|
||||
did_interface_receive eth1 "${NS2_IP}"
|
||||
check_err $? "eth1 not transmitting when active"
|
||||
clear_tcpdump_outputs eth0 eth1
|
||||
|
||||
### Scenario 3: Change active port.
|
||||
set_active_port "${NS1}" test_team1 eth0
|
||||
set_active_port "${NS2}" test_team2 eth0
|
||||
|
||||
wait_to_stop_receiving "${NS2}" eth1
|
||||
save_tcpdump_outputs "${NS2}" eth0 eth1
|
||||
did_interface_receive eth0 "${NS2_IP}"
|
||||
check_err $? "eth0 not transmitting when active"
|
||||
did_interface_receive eth1 "${NS2_IP}"
|
||||
check_fail $? "eth1 IS transmitting when inactive"
|
||||
clear_tcpdump_outputs eth0 eth1
|
||||
|
||||
log_test "teamd active backup runner test"
|
||||
|
||||
stop_sending_and_listening
|
||||
}
|
||||
|
||||
require_command teamd
|
||||
require_command teamdctl
|
||||
require_command iperf3
|
||||
require_command tcpdump
|
||||
environment_create activebackup
|
||||
tests_run
|
||||
exit "${EXIT_STATUS}"
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
# These tests verify the basic failover capability of the team driver via the
|
||||
# `enabled` team driver option across different team driver modes. This does not
|
||||
# rely on teamd, and instead just uses teamnl to set the `enabled` option
|
||||
# directly.
|
||||
#
|
||||
# Topology:
|
||||
#
|
||||
# +-------------------------+ NS1
|
||||
# | test_team1 |
|
||||
# | + |
|
||||
# | eth0 | eth1 |
|
||||
# | +---+---+ |
|
||||
# | | | |
|
||||
# +-------------------------+
|
||||
# | |
|
||||
# +-------------------------+ NS2
|
||||
# | | | |
|
||||
# | +-------+ |
|
||||
# | eth0 | eth1 |
|
||||
# | + |
|
||||
# | test_team2 |
|
||||
# +-------------------------+
|
||||
|
||||
export ALL_TESTS="team_test_failover"
|
||||
|
||||
test_dir="$(dirname "$0")"
|
||||
# shellcheck disable=SC1091
|
||||
source "${test_dir}/../../../net/lib.sh"
|
||||
# shellcheck disable=SC1091
|
||||
source "${test_dir}/team_lib.sh"
|
||||
|
||||
NS1=""
|
||||
NS2=""
|
||||
export NODAD="nodad"
|
||||
PREFIX_LENGTH="64"
|
||||
NS1_IP="fd00::1"
|
||||
NS2_IP="fd00::2"
|
||||
NS1_IP4="192.168.0.1"
|
||||
NS2_IP4="192.168.0.2"
|
||||
MEMBERS=("eth0" "eth1")
|
||||
|
||||
while getopts "4" opt; do
|
||||
case $opt in
|
||||
4)
|
||||
echo "IPv4 mode selected."
|
||||
export NODAD=
|
||||
PREFIX_LENGTH="24"
|
||||
NS1_IP="${NS1_IP4}"
|
||||
NS2_IP="${NS2_IP4}"
|
||||
;;
|
||||
\?)
|
||||
echo "Invalid option: -$OPTARG" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Create the network namespaces, veth pair, and team devices in the specified
|
||||
# mode.
|
||||
# Globals:
|
||||
# RET - Used by test infra, set by `check_err` functions.
|
||||
# Arguments:
|
||||
# mode - The team driver mode to use for the team devices.
|
||||
environment_create()
|
||||
{
|
||||
trap cleanup_all_ns EXIT
|
||||
setup_ns ns1 ns2
|
||||
NS1="${NS_LIST[0]}"
|
||||
NS2="${NS_LIST[1]}"
|
||||
|
||||
# Create the interfaces.
|
||||
ip -n "${NS1}" link add eth0 type veth peer name eth0 netns "${NS2}"
|
||||
ip -n "${NS1}" link add eth1 type veth peer name eth1 netns "${NS2}"
|
||||
ip -n "${NS1}" link add test_team1 type team
|
||||
ip -n "${NS2}" link add test_team2 type team
|
||||
|
||||
# Set up the receiving network namespace's team interface.
|
||||
setup_team "${NS2}" test_team2 roundrobin "${NS2_IP}" \
|
||||
"${PREFIX_LENGTH}" "${MEMBERS[@]}"
|
||||
}
|
||||
|
||||
|
||||
# Check that failover works for a specific team driver mode.
|
||||
# Globals:
|
||||
# RET - Used by test infra, set by `check_err` functions.
|
||||
# Arguments:
|
||||
# mode - The mode to set the team interfaces to.
|
||||
team_test_mode_failover()
|
||||
{
|
||||
local mode="$1"
|
||||
export RET=0
|
||||
|
||||
# Set up the sender team with the correct mode.
|
||||
setup_team "${NS1}" test_team1 "${mode}" "${NS1_IP}" \
|
||||
"${PREFIX_LENGTH}" "${MEMBERS[@]}"
|
||||
check_err $? "Failed to set up sender team"
|
||||
|
||||
start_listening_and_sending
|
||||
|
||||
### Scenario 1: All interfaces initially enabled.
|
||||
save_tcpdump_outputs "${NS2}" "${MEMBERS[@]}"
|
||||
did_interface_receive eth0 "${NS2_IP}"
|
||||
check_err $? "eth0 not transmitting when both links enabled"
|
||||
did_interface_receive eth1 "${NS2_IP}"
|
||||
check_err $? "eth1 not transmitting when both links enabled"
|
||||
clear_tcpdump_outputs "${MEMBERS[@]}"
|
||||
|
||||
### Scenario 2: One tx-side interface disabled.
|
||||
ip netns exec "${NS1}" teamnl test_team1 setoption enabled false \
|
||||
--port=eth1
|
||||
slowwait 2 bash -c "ip netns exec ${NS1} teamnl test_team1 getoption \
|
||||
enabled --port=eth1 | grep -q false"
|
||||
|
||||
save_tcpdump_outputs "${NS2}" "${MEMBERS[@]}"
|
||||
did_interface_receive eth0 "${NS2_IP}"
|
||||
check_err $? "eth0 not transmitting when enabled"
|
||||
did_interface_receive eth1 "${NS2_IP}"
|
||||
check_fail $? "eth1 IS transmitting when disabled"
|
||||
clear_tcpdump_outputs "${MEMBERS[@]}"
|
||||
|
||||
### Scenario 3: The interface is re-enabled.
|
||||
ip netns exec "${NS1}" teamnl test_team1 setoption enabled true \
|
||||
--port=eth1
|
||||
slowwait 2 bash -c "ip netns exec ${NS1} teamnl test_team1 getoption \
|
||||
enabled --port=eth1 | grep -q true"
|
||||
|
||||
save_tcpdump_outputs "${NS2}" "${MEMBERS[@]}"
|
||||
did_interface_receive eth0 "${NS2_IP}"
|
||||
check_err $? "eth0 not transmitting when both links enabled"
|
||||
did_interface_receive eth1 "${NS2_IP}"
|
||||
check_err $? "eth1 not transmitting when both links enabled"
|
||||
clear_tcpdump_outputs "${MEMBERS[@]}"
|
||||
|
||||
log_test "Failover of '${mode}' test"
|
||||
|
||||
# Clean up
|
||||
stop_sending_and_listening
|
||||
}
|
||||
|
||||
team_test_failover()
|
||||
{
|
||||
team_test_mode_failover broadcast
|
||||
team_test_mode_failover roundrobin
|
||||
team_test_mode_failover random
|
||||
# Don't test `activebackup` or `loadbalance` modes, since they are too
|
||||
# complicated for just setting `enabled` to work. They use more than
|
||||
# the `enabled` option for transmit.
|
||||
}
|
||||
|
||||
require_command teamnl
|
||||
require_command iperf3
|
||||
require_command tcpdump
|
||||
environment_create
|
||||
tests_run
|
||||
exit "${EXIT_STATUS}"
|
||||
|
|
@ -1750,12 +1750,17 @@ tcpdump_start()
|
|||
sleep 1
|
||||
}
|
||||
|
||||
tcpdump_stop()
|
||||
tcpdump_stop_nosleep()
|
||||
{
|
||||
local if_name=$1
|
||||
local pid=${cappid[$if_name]}
|
||||
|
||||
$ns_cmd kill "$pid" && wait "$pid"
|
||||
}
|
||||
|
||||
tcpdump_stop()
|
||||
{
|
||||
tcpdump_stop_nosleep "$1"
|
||||
sleep 1
|
||||
}
|
||||
|
||||
|
|
@ -1770,7 +1775,7 @@ tcpdump_show()
|
|||
{
|
||||
local if_name=$1
|
||||
|
||||
tcpdump -e -n -r ${capfile[$if_name]} 2>&1
|
||||
tcpdump -e -nn -r ${capfile[$if_name]} 2>&1
|
||||
}
|
||||
|
||||
# return 0 if the packet wasn't seen on host2_if or 1 if it was
|
||||
|
|
|
|||
|
|
@ -224,6 +224,19 @@ setup_ns()
|
|||
NS_LIST+=("${ns_list[@]}")
|
||||
}
|
||||
|
||||
in_all_ns()
|
||||
{
|
||||
local ret=0
|
||||
local ns_list=("${NS_LIST[@]}")
|
||||
|
||||
for ns in "${ns_list[@]}"; do
|
||||
ip netns exec "${ns}" "$@"
|
||||
(( ret = ret || $? ))
|
||||
done
|
||||
|
||||
return "${ret}"
|
||||
}
|
||||
|
||||
# Create netdevsim with given id and net namespace.
|
||||
create_netdevsim() {
|
||||
local id="$1"
|
||||
|
|
|
|||
Loading…
Reference in New Issue