diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index d89d44706269..6c54a8d4be3e 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -198,6 +198,7 @@ int batadv_mesh_init(struct net_device *mesh_iface) spin_lock_init(&bat_priv->tt.roam_list_lock); spin_lock_init(&bat_priv->tt.last_changeset_lock); spin_lock_init(&bat_priv->tt.commit_lock); + spin_lock_init(&bat_priv->tt.reserve_lock); spin_lock_init(&bat_priv->gw.list_lock); #ifdef CONFIG_BATMAN_ADV_MCAST spin_lock_init(&bat_priv->mcast.mla_lock); diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c index 63af21954cf9..1ce707aaf48a 100644 --- a/net/batman-adv/mesh-interface.c +++ b/net/batman-adv/mesh-interface.c @@ -561,6 +561,8 @@ void batadv_meshif_vlan_release(struct kref *ref) hlist_del_rcu(&vlan->list); spin_unlock_bh(&vlan->bat_priv->meshif_vlan_list_lock); + batadv_tt_local_unreserve_vlan(vlan->bat_priv); + kfree_rcu(vlan, rcu); } @@ -614,9 +616,15 @@ int batadv_meshif_create_vlan(struct batadv_priv *bat_priv, unsigned short vid) return -EEXIST; } + if (!batadv_tt_local_reserve_vlan(bat_priv, vid)) { + spin_unlock_bh(&bat_priv->meshif_vlan_list_lock); + return -EMSGSIZE; + } + vlan = kzalloc_obj(*vlan, GFP_ATOMIC); if (!vlan) { spin_unlock_bh(&bat_priv->meshif_vlan_list_lock); + batadv_tt_local_unreserve_vlan(bat_priv); return -ENOMEM; } diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index 481dc6afaaba..3349376a9087 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -234,6 +234,162 @@ batadv_tt_global_hash_find(struct batadv_priv *bat_priv, const u8 *addr, return tt_global_entry; } +/** + * batadv_tt_len() - compute length in bytes of given number of tt changes + * @changes_num: number of tt changes + * + * Return: computed length in bytes. + */ +static int batadv_tt_len(int changes_num) +{ + return changes_num * sizeof(struct batadv_tvlv_tt_change); +} + +/** + * batadv_tt_local_transmit_size() - calculate the size of a full table response + * for a given number of VLANs and local TT entries + * @num_vlan: number of announced VLANs + * @num_entries: number of announced local TT entries + * + * Return: local translation table size in bytes. + */ +static int batadv_tt_local_transmit_size(u16 num_vlan, u16 num_entries) +{ + int hdr_size; + + /* header size of tvlv encapsulated tt response payload */ + hdr_size = sizeof(struct batadv_unicast_tvlv_packet); + hdr_size += sizeof(struct batadv_tvlv_hdr); + hdr_size += sizeof(struct batadv_tvlv_tt_data); + hdr_size += num_vlan * sizeof(struct batadv_tvlv_tt_vlan_data); + + return hdr_size + batadv_tt_len(num_entries); +} + +/** + * batadv_tt_local_reserve() - reserve room in the transmittable local table + * @bat_priv: the bat priv with all the mesh interface information + * @num_vlan: number of VLANs to reserve + * @num_entries: number of local TT entries to reserve + * @table_size: stores the resulting worst case table size in bytes + * + * Add the requested number of VLANs and local TT entries to the reservation + * counters and check whether the local translation table would then still fit + * in a single full table response. The reservation is dropped again when it + * would not. + * + * The reservation has to happen before the related object is allocated. This + * way two parallel allocations cannot both observe enough room for themselves + * and end up with a local table which can no longer be transmitted. + * + * A granted reservation must be returned via batadv_tt_local_unreserve() when + * the related object is released or was never created. + * + * Return: true when the reservation was granted, false otherwise. + */ +static bool batadv_tt_local_reserve(struct batadv_priv *bat_priv, u16 num_vlan, + u16 num_entries, int *table_size) +{ + int packet_size_max = READ_ONCE(bat_priv->packet_size_max); + + scoped_guard(spinlock_bh, &bat_priv->tt.reserve_lock) { + bat_priv->tt.reserved_vlans += num_vlan; + bat_priv->tt.reserved_entries += num_entries; + + *table_size = batadv_tt_local_transmit_size(bat_priv->tt.reserved_vlans, + bat_priv->tt.reserved_entries); + if (*table_size <= packet_size_max) + return true; + + bat_priv->tt.reserved_vlans -= num_vlan; + bat_priv->tt.reserved_entries -= num_entries; + } + + return false; +} + +/** + * batadv_tt_local_unreserve() - return room in the transmittable local table + * @bat_priv: the bat priv with all the mesh interface information + * @num_vlan: number of VLANs to return + * @num_entries: number of local TT entries to return + */ +static void batadv_tt_local_unreserve(struct batadv_priv *bat_priv, + u16 num_vlan, u16 num_entries) +{ + scoped_guard(spinlock_bh, &bat_priv->tt.reserve_lock) { + bat_priv->tt.reserved_vlans -= num_vlan; + bat_priv->tt.reserved_entries -= num_entries; + } +} + +/** + * batadv_tt_local_reserve_entry() - reserve room for a new local TT entry + * @bat_priv: the bat priv with all the mesh interface information + * @addr: the mac address of the client to add + * + * Return: true when the reservation was granted, false otherwise. + */ +static bool batadv_tt_local_reserve_entry(struct batadv_priv *bat_priv, + const u8 *addr) +{ + int table_size; + + if (batadv_tt_local_reserve(bat_priv, 0, 1, &table_size)) + return true; + + net_ratelimited_function(batadv_info, bat_priv->mesh_iface, + "Local translation table size (%i) exceeds maximum packet size (%i); Ignoring new local tt entry: %pM\n", + table_size, + READ_ONCE(bat_priv->packet_size_max), addr); + + return false; +} + +/** + * batadv_tt_local_unreserve_entry() - return the room of a local TT entry + * @bat_priv: the bat priv with all the mesh interface information + */ +static void batadv_tt_local_unreserve_entry(struct batadv_priv *bat_priv) +{ + batadv_tt_local_unreserve(bat_priv, 0, 1); +} + +/** + * batadv_tt_local_reserve_vlan() - reserve room for a new VLAN + * @bat_priv: the bat priv with all the mesh interface information + * @vid: the VLAN identifier + * + * Each VLAN adds a per VLAN header to the full table response and therefore + * has to be reserved before batadv_meshif_create_vlan() allocates it. + * + * Return: true when the reservation was granted, false otherwise. + */ +bool batadv_tt_local_reserve_vlan(struct batadv_priv *bat_priv, + unsigned short vid) +{ + int table_size; + + if (batadv_tt_local_reserve(bat_priv, 1, 0, &table_size)) + return true; + + net_ratelimited_function(batadv_info, bat_priv->mesh_iface, + "Local translation table size (%i) exceeds maximum packet size (%i); Ignoring new VLAN: %d\n", + table_size, READ_ONCE(bat_priv->packet_size_max), + batadv_print_vid(vid)); + + return false; +} + +/** + * batadv_tt_local_unreserve_vlan() - return the room of a VLAN + * @bat_priv: the bat priv with all the mesh interface information + */ +void batadv_tt_local_unreserve_vlan(struct batadv_priv *bat_priv) +{ + batadv_tt_local_unreserve(bat_priv, 1, 0); +} + /** * batadv_tt_local_entry_release() - release tt_local_entry from lists and queue * for free after rcu grace period @@ -246,6 +402,7 @@ static void batadv_tt_local_entry_release(struct kref *ref) tt_local_entry = container_of(ref, struct batadv_tt_local_entry, common.refcount); + batadv_tt_local_unreserve_entry(tt_local_entry->vlan->bat_priv); batadv_meshif_vlan_put(tt_local_entry->vlan); kfree_rcu(tt_local_entry, common.rcu); @@ -550,17 +707,6 @@ static void batadv_tt_local_event(struct batadv_priv *bat_priv, __batadv_tt_local_event(bat_priv, common, flags); } -/** - * batadv_tt_len() - compute length in bytes of given number of tt changes - * @changes_num: number of tt changes - * - * Return: computed length in bytes. - */ -static int batadv_tt_len(int changes_num) -{ - return changes_num * sizeof(struct batadv_tvlv_tt_change); -} - /** * batadv_tt_entries() - compute the number of entries fitting in tt_len bytes * @tt_len: available space @@ -584,7 +730,6 @@ static int batadv_tt_local_table_transmit_size(struct batadv_priv *bat_priv) struct batadv_meshif_vlan *vlan; u16 tt_local_entries = 0; u16 num_vlan = 0; - int hdr_size; rcu_read_lock(); hlist_for_each_entry_rcu(vlan, &bat_priv->meshif_vlan_list, list) { @@ -593,13 +738,7 @@ static int batadv_tt_local_table_transmit_size(struct batadv_priv *bat_priv) } rcu_read_unlock(); - /* header size of tvlv encapsulated tt response payload */ - hdr_size = sizeof(struct batadv_unicast_tvlv_packet); - hdr_size += sizeof(struct batadv_tvlv_hdr); - hdr_size += sizeof(struct batadv_tvlv_tt_data); - hdr_size += num_vlan * sizeof(struct batadv_tvlv_tt_vlan_data); - - return hdr_size + batadv_tt_len(tt_local_entries); + return batadv_tt_local_transmit_size(num_vlan, tt_local_entries); } /** @@ -803,23 +942,15 @@ batadv_tt_local_create(struct net_device *mesh_iface, const u8 *addr, struct batadv_priv *bat_priv = netdev_priv(mesh_iface); struct batadv_tt_local_entry *tt_local; struct batadv_meshif_vlan *vlan; - int packet_size_max; - int table_size; - /* Ignore the client if we cannot send it in a full table response. */ - table_size = batadv_tt_local_table_transmit_size(bat_priv); - table_size += batadv_tt_len(1); - packet_size_max = READ_ONCE(bat_priv->packet_size_max); - if (table_size > packet_size_max) { - net_ratelimited_function(batadv_info, mesh_iface, - "Local translation table size (%i) exceeds maximum packet size (%i); Ignoring new local tt entry: %pM\n", - table_size, packet_size_max, addr); + if (!batadv_tt_local_reserve_entry(bat_priv, addr)) return NULL; - } tt_local = kmem_cache_alloc(batadv_tl_cache, GFP_ATOMIC); - if (!tt_local) + if (!tt_local) { + batadv_tt_local_unreserve_entry(bat_priv); return NULL; + } /* increase the refcounter of the related vlan */ vlan = batadv_meshif_vlan_get(bat_priv, vid); @@ -828,6 +959,7 @@ batadv_tt_local_create(struct net_device *mesh_iface, const u8 *addr, "adding TT local entry %pM to non-existent VLAN %d\n", addr, batadv_print_vid(vid)); kmem_cache_free(batadv_tl_cache, tt_local); + batadv_tt_local_unreserve_entry(bat_priv); return NULL; } diff --git a/net/batman-adv/translation-table.h b/net/batman-adv/translation-table.h index 618d9dbca5ea..ca01d20ee2d7 100644 --- a/net/batman-adv/translation-table.h +++ b/net/batman-adv/translation-table.h @@ -16,6 +16,9 @@ #include int batadv_tt_init(struct batadv_priv *bat_priv); +bool batadv_tt_local_reserve_vlan(struct batadv_priv *bat_priv, + unsigned short vid); +void batadv_tt_local_unreserve_vlan(struct batadv_priv *bat_priv); bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr, unsigned short vid, int ifindex, u32 mark); u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index 67872927cfb5..c19caa84e692 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -1055,6 +1055,25 @@ struct batadv_priv_tt { */ spinlock_t commit_lock; + /** + * @reserved_entries: number of local TT entries which are currently + * allocated or about to be allocated. Together with @reserved_vlans it + * describes the worst case size of a full table response. + */ + u16 reserved_entries; + + /** + * @reserved_vlans: number of mesh interface VLANs which are currently + * allocated or about to be allocated. Together with @reserved_entries + * it describes the worst case size of a full table response. + */ + u16 reserved_vlans; + + /** + * @reserve_lock: lock protecting @reserved_entries & @reserved_vlans + */ + spinlock_t reserve_lock; + /** @work: work queue callback item for translation table purging */ struct delayed_work work; };