On Thu, Nov 21, 2024 at 07:02:43PM +0100, Sven Eckelmann wrote:
> On Thursday, 21 November 2024 16:07:24 CET Remi Pommarel wrote:
> > So the patch would be quite similar, only tt->tt.changes_list_lock will
> > be taken sooner in batadv_tt_tvlv_container_update().
> >
> > That will fix the ADD between two read situation as you described
> > though.
> >
> > Do you still prefer this option ?
>
> I can't speak for Antonio but I think I would prefer for the fix the current
> version. The locking one would end up again with nested spinlocks and maybe
> more refactoring. And it might be nicer for the stable backports to have less
> noise in the patch.
I tend to second that, because if I understand correctly, even if
tt.changes_list_lock is held sooner in batadv_tt_tvlv_container_update()
the scenario Antonio described could still happen. Indeed if the ADD (or
even DEL for that matter) happens between VLAN table CRC computation
(batadv_tt_local_update_crc()) and the lock, neighbor will have CRC
mismatch and send TT_REQUEST anyway. The race window would be smaller
but still here.
So if I am not mistaken, the only solution to eliminate the race
completely would be to compute CRC while holding the lock, and this I
don't really like.
>
> Btw. just noticed that the code (not in your change - but overall) for the
> filling of diff entries could have been something like:
>
> --- a/net/batman-adv/translation-table.c
> +++ b/net/batman-adv/translation-table.c
> @@ -980,6 +980,7 @@ static void batadv_tt_tvlv_container_update(struct
> batadv_priv *bat_priv)
> int tt_diff_entries_count = 0;
> bool drop_changes = false;
> size_t tt_extra_len = 0;
> + LIST_HEAD(tt_diffs);
> u16 tvlv_len;
>
> tt_diff_entries_num = READ_ONCE(bat_priv->tt.local_changes);
> @@ -1011,9 +1012,10 @@ static void batadv_tt_tvlv_container_update(struct
> batadv_priv *bat_priv)
>
> spin_lock_bh(&bat_priv->tt.changes_list_lock);
> bat_priv->tt.local_changes = 0;
> + list_splice_init(&bat_priv->tt.changes_list, &tt_diffs);
> + spin_unlock_bh(&bat_priv->tt.changes_list_lock);
>
> - list_for_each_entry_safe(entry, safe, &bat_priv->tt.changes_list,
> - list) {
> + list_for_each_entry_safe(entry, safe, &tt_diffs, list) {
> if (tt_diff_entries_count < tt_diff_entries_num) {
> memcpy(tt_change + tt_diff_entries_count,
> &entry->change,
> @@ -1023,7 +1025,6 @@ static void batadv_tt_tvlv_container_update(struct
> batadv_priv *bat_priv)
> list_del(&entry->list);
> kmem_cache_free(batadv_tt_change_cache, entry);
> }
> - spin_unlock_bh(&bat_priv->tt.changes_list_lock);
>
> tt_extra_len = batadv_tt_len(tt_diff_entries_num -
> tt_diff_entries_count);
>
>
> And then you can also move this before "tt_diff_entries_num = ..." and
> save the corresponding bat_priv->tt.local_changes for the spliced list to the
> inside the lock also in a local variable. And then operate on this variable
> for the other decisions. Of course, you must still clean the local list in
> case of an error. Which of course would slightly change the behavior in case
> of an allocation error in batadv_tt_prepare_tvlv_local_data (which would
> previously kept the list as it was).
>
> But if it would be done like this then we could also remove the READ_ONCE and
> not introduce the WRITE_ONCE - just because local_changes is only touched
> inside a locked area (see changes_list_lock).
>
> Please double check these statements - this was just a simple brain dump.
Yes that would be a much more elegant way to handle it. Unfortunately,
if I don't miss anything, the WRITE_ONCE/READ_ONCE would still be
needed because batadv_tt_local_commit_changes_nolock() has to load
tt.local_changes out of the lock to check if it needs to purge client
and recompute CRCs.
Thanks,
--
Remi