int batadv_hardif_min_mtu(struct net_device *soft_iface)
 {
-       const struct batadv_priv *bat_priv = netdev_priv(soft_iface);
+       struct batadv_priv *bat_priv = netdev_priv(soft_iface);
        const struct batadv_hard_iface *hard_iface;
-       /* allow big frames if all devices are capable to do so
-        * (have MTU > 1500 + batadv_max_header_len())
-        */
        int min_mtu = ETH_DATA_LEN;
-       int max_header_len = batadv_max_header_len();
-
-       if (atomic_read(&bat_priv->fragmentation))
-               goto out;
 
        rcu_read_lock();
        list_for_each_entry_rcu(hard_iface, &batadv_hardif_list, list) {
                if (hard_iface->soft_iface != soft_iface)
                        continue;
 
-               min_mtu = min_t(int, hard_iface->net_dev->mtu - max_header_len,
-                               min_mtu);
+               min_mtu = min_t(int, hard_iface->net_dev->mtu, min_mtu);
        }
        rcu_read_unlock();
+
+       atomic_set(&bat_priv->packet_size_max, min_mtu);
+
+       if (atomic_read(&bat_priv->fragmentation) == 0)
+               goto out;
+
+       /* with fragmentation enabled the maximum size of internally generated
+        * packets such as translation table exchanges or tvlv containers, etc
+        * has to be calculated
+        */
+       min_mtu = min_t(int, min_mtu, BATADV_FRAG_MAX_FRAG_SIZE);
+       min_mtu -= sizeof(struct batadv_frag_packet);
+       min_mtu *= BATADV_FRAG_MAX_FRAGMENTS;
+       atomic_set(&bat_priv->packet_size_max, min_mtu);
+
+       /* with fragmentation enabled we can fragment external packets easily */
+       min_mtu = min_t(int, min_mtu, ETH_DATA_LEN);
+
 out:
-       return min_mtu;
+       return min_mtu - batadv_max_header_len();
 }
 
 /* adjusts the MTU if a new interface with a smaller MTU appeared. */
 void batadv_update_min_mtu(struct net_device *soft_iface)
 {
-       int min_mtu;
+       soft_iface->mtu = batadv_hardif_min_mtu(soft_iface);
 
-       min_mtu = batadv_hardif_min_mtu(soft_iface);
-       if (soft_iface->mtu != min_mtu)
-               soft_iface->mtu = min_mtu;
+       /* Check if the local translate table should be cleaned up to match a
+        * new (and smaller) MTU.
+        */
+       batadv_tt_local_resize_to_mtu(soft_iface);
 }
 
 static void
 
        return tt_len / batadv_tt_len(1);
 }
 
+/**
+ * batadv_tt_local_table_transmit_size - calculates the local translation table
+ *  size when transmitted over the air
+ * @bat_priv: the bat priv with all the soft interface information
+ *
+ * Returns local translation table size in bytes.
+ */
+static int batadv_tt_local_table_transmit_size(struct batadv_priv *bat_priv)
+{
+       uint16_t num_vlan = 0, tt_local_entries = 0;
+       struct batadv_softif_vlan *vlan;
+       int hdr_size;
+
+       rcu_read_lock();
+       hlist_for_each_entry_rcu(vlan, &bat_priv->softif_vlan_list, list) {
+               num_vlan++;
+               tt_local_entries += atomic_read(&vlan->tt.num_entries);
+       }
+       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);
+}
+
 static int batadv_tt_local_init(struct batadv_priv *bat_priv)
 {
        if (bat_priv->tt.local_hash)
  * @vid: VLAN identifier
  * @ifindex: index of the interface where the client is connected to (useful to
  *  identify wireless clients)
+ *
+ * Returns true if the client was successfully added, false otherwise.
  */
-void batadv_tt_local_add(struct net_device *soft_iface, const uint8_t *addr,
+bool batadv_tt_local_add(struct net_device *soft_iface, const uint8_t *addr,
                         unsigned short vid, int ifindex)
 {
        struct batadv_priv *bat_priv = netdev_priv(soft_iface);
        struct batadv_tt_global_entry *tt_global;
        struct hlist_head *head;
        struct batadv_tt_orig_list_entry *orig_entry;
-       int hash_added;
-       bool roamed_back = false;
+       int hash_added, table_size, packet_size_max;
+       bool ret = false, roamed_back = false;
 
        tt_local = batadv_tt_local_hash_find(bat_priv, addr, vid);
        tt_global = batadv_tt_global_hash_find(bat_priv, addr, vid);
                goto check_roaming;
        }
 
+       /* 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 = atomic_read(&bat_priv->packet_size_max);
+       if (table_size > packet_size_max) {
+               net_ratelimited_function(batadv_info, soft_iface,
+                                        "Local translation table size (%i) exceeds maximum packet size (%i); Ignoring new local tt entry: %pM\n",
+                                        table_size, packet_size_max, addr);
+               goto out;
+       }
+
        tt_local = kmalloc(sizeof(*tt_local), GFP_ATOMIC);
        if (!tt_local)
                goto out;
                }
        }
 
+       ret = true;
+
 out:
        if (tt_local)
                batadv_tt_local_entry_free_ref(tt_local);
        if (tt_global)
                batadv_tt_global_entry_free_ref(tt_global);
+       return ret;
 }
 
 /**
        return curr_flags;
 }
 
+/**
+ * batadv_tt_local_purge_list - purge inactive tt local entries
+ * @bat_priv: the bat priv with all the soft interface information
+ * @head: pointer to the list containing the local tt entries
+ * @timeout: parameter deciding whether a given tt local entry is considered
+ *  inactive or not
+ */
 static void batadv_tt_local_purge_list(struct batadv_priv *bat_priv,
-                                      struct hlist_head *head)
+                                      struct hlist_head *head,
+                                      int timeout)
 {
        struct batadv_tt_local_entry *tt_local_entry;
        struct batadv_tt_common_entry *tt_common_entry;
                if (tt_local_entry->common.flags & BATADV_TT_CLIENT_PENDING)
                        continue;
 
-               if (!batadv_has_timed_out(tt_local_entry->last_seen,
-                                         BATADV_TT_LOCAL_TIMEOUT))
+               if (!batadv_has_timed_out(tt_local_entry->last_seen, timeout))
                        continue;
 
                batadv_tt_local_set_pending(bat_priv, tt_local_entry,
        }
 }
 
-static void batadv_tt_local_purge(struct batadv_priv *bat_priv)
+/**
+ * batadv_tt_local_purge - purge inactive tt local entries
+ * @bat_priv: the bat priv with all the soft interface information
+ * @timeout: parameter deciding whether a given tt local entry is considered
+ *  inactive or not
+ */
+static void batadv_tt_local_purge(struct batadv_priv *bat_priv,
+                                 int timeout)
 {
        struct batadv_hashtable *hash = bat_priv->tt.local_hash;
        struct hlist_head *head;
                list_lock = &hash->list_locks[i];
 
                spin_lock_bh(list_lock);
-               batadv_tt_local_purge_list(bat_priv, head);
+               batadv_tt_local_purge_list(bat_priv, head, timeout);
                spin_unlock_bh(list_lock);
        }
 }
                                        req_dst_orig_node);
        }
 
+       /* Don't send the response, if larger than fragmented packet. */
+       tt_len = sizeof(struct batadv_unicast_tvlv_packet) + tvlv_len;
+       if (tt_len > atomic_read(&bat_priv->packet_size_max)) {
+               net_ratelimited_function(batadv_info, bat_priv->soft_iface,
+                                        "Ignoring TT_REQUEST from %pM; Response size exceeds max packet size.\n",
+                                        res_dst_orig_node->orig);
+               goto out;
+       }
+
        tvlv_tt_data->flags = BATADV_TT_RESPONSE;
        tvlv_tt_data->ttvn = req_ttvn;
 
        priv_tt = container_of(delayed_work, struct batadv_priv_tt, work);
        bat_priv = container_of(priv_tt, struct batadv_priv, tt);
 
-       batadv_tt_local_purge(bat_priv);
+       batadv_tt_local_purge(bat_priv, BATADV_TT_LOCAL_TIMEOUT);
        batadv_tt_global_purge(bat_priv);
        batadv_tt_req_purge(bat_priv);
        batadv_tt_roam_purge(bat_priv);
 }
 
 /**
- * batadv_tt_local_commit_changes - commit all pending local tt changes which
- *  have been queued in the time since the last commit
+ * batadv_tt_local_commit_changes_nolock - commit all pending local tt changes
+ *  which have been queued in the time since the last commit
  * @bat_priv: the bat priv with all the soft interface information
+ *
+ * Caller must hold tt->commit_lock.
  */
-void batadv_tt_local_commit_changes(struct batadv_priv *bat_priv)
+static void batadv_tt_local_commit_changes_nolock(struct batadv_priv *bat_priv)
 {
-       spin_lock_bh(&bat_priv->tt.commit_lock);
-
        if (atomic_read(&bat_priv->tt.local_changes) < 1) {
                if (!batadv_atomic_dec_not_zero(&bat_priv->tt.ogm_append_cnt))
                        batadv_tt_tvlv_container_update(bat_priv);
-               goto out;
+               return;
        }
 
        batadv_tt_local_set_flags(bat_priv, BATADV_TT_CLIENT_NEW, false, true);
        /* reset the sending counter */
        atomic_set(&bat_priv->tt.ogm_append_cnt, BATADV_TT_OGM_APPEND_MAX);
        batadv_tt_tvlv_container_update(bat_priv);
+}
 
-out:
+/**
+ * batadv_tt_local_commit_changes - commit all pending local tt changes which
+ *  have been queued in the time since the last commit
+ * @bat_priv: the bat priv with all the soft interface information
+ */
+void batadv_tt_local_commit_changes(struct batadv_priv *bat_priv)
+{
+       spin_lock_bh(&bat_priv->tt.commit_lock);
+       batadv_tt_local_commit_changes_nolock(bat_priv);
        spin_unlock_bh(&bat_priv->tt.commit_lock);
 }
 
        return ret;
 }
 
+/**
+ * batadv_tt_local_resize_to_mtu - resize the local translation table fit the
+ *  maximum packet size that can be transported through the mesh
+ * @soft_iface: netdev struct of the mesh interface
+ *
+ * Remove entries older than 'timeout' and half timeout if more entries need
+ * to be removed.
+ */
+void batadv_tt_local_resize_to_mtu(struct net_device *soft_iface)
+{
+       struct batadv_priv *bat_priv = netdev_priv(soft_iface);
+       int packet_size_max = atomic_read(&bat_priv->packet_size_max);
+       int table_size, timeout = BATADV_TT_LOCAL_TIMEOUT / 2;
+       bool reduced = false;
+
+       spin_lock_bh(&bat_priv->tt.commit_lock);
+
+       while (true) {
+               table_size = batadv_tt_local_table_transmit_size(bat_priv);
+               if (packet_size_max >= table_size)
+                       break;
+
+               batadv_tt_local_purge(bat_priv, timeout);
+               batadv_tt_local_purge_pending_clients(bat_priv);
+
+               timeout /= 2;
+               reduced = true;
+               net_ratelimited_function(batadv_info, soft_iface,
+                                        "Forced to purge local tt entries to fit new maximum fragment MTU (%i)\n",
+                                        packet_size_max);
+       }
+
+       /* commit these changes immediately, to avoid synchronization problem
+        * with the TTVN
+        */
+       if (reduced)
+               batadv_tt_local_commit_changes_nolock(bat_priv);
+
+       spin_unlock_bh(&bat_priv->tt.commit_lock);
+}
+
 /**
  * batadv_tt_tvlv_ogm_handler_v1 - process incoming tt tvlv container
  * @bat_priv: the bat priv with all the soft interface information