| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (C) B.A.T.M.A.N. contributors: |
| * |
| * Linus Lüssing |
| */ |
| |
| #include "multicast.h" |
| #include "main.h" |
| |
| #include <linux/bug.h> |
| #include <linux/build_bug.h> |
| #include <linux/byteorder/generic.h> |
| #include <linux/compiler.h> |
| #include <linux/errno.h> |
| #include <linux/etherdevice.h> |
| #include <linux/gfp.h> |
| #include <linux/if_ether.h> |
| #include <linux/if_vlan.h> |
| #include <linux/ipv6.h> |
| #include <linux/limits.h> |
| #include <linux/netdevice.h> |
| #include <linux/rculist.h> |
| #include <linux/rcupdate.h> |
| #include <linux/skbuff.h> |
| #include <linux/stddef.h> |
| #include <linux/string.h> |
| #include <linux/types.h> |
| #include <uapi/linux/batadv_packet.h> |
| |
| #include "bridge_loop_avoidance.h" |
| #include "originator.h" |
| #include "send.h" |
| #include "translation-table.h" |
| |
| #define batadv_mcast_forw_tracker_for_each_dest(dest, num_dests) \ |
| for (; num_dests; num_dests--, (dest) += ETH_ALEN) |
| |
| #define batadv_mcast_forw_tracker_for_each_dest2(dest1, dest2, num_dests) \ |
| for (; num_dests; num_dests--, (dest1) += ETH_ALEN, (dest2) += ETH_ALEN) |
| |
| /** |
| * batadv_mcast_forw_skb_push() - skb_push and memorize amount of pushed bytes |
| * @skb: the skb to push onto |
| * @size: the amount of bytes to push |
| * @len: stores the total amount of bytes pushed |
| * |
| * Performs an skb_push() onto the given skb and adds the amount of pushed bytes |
| * to the given len pointer. |
| * |
| * Return: the return value of the skb_push() call. |
| */ |
| static void *batadv_mcast_forw_skb_push(struct sk_buff *skb, size_t size, |
| unsigned short *len) |
| { |
| *len += size; |
| return skb_push(skb, size); |
| } |
| |
| /** |
| * batadv_mcast_forw_push_padding() - push 2 padding bytes to skb's front |
| * @skb: the skb to push onto |
| * @tvlv_len: stores the amount of currently pushed TVLV bytes |
| * |
| * Pushes two padding bytes to the front of the given skb. |
| * |
| * Return: On success a pointer to the first byte of the two pushed padding |
| * bytes within the skb. NULL otherwise. |
| */ |
| static char * |
| batadv_mcast_forw_push_padding(struct sk_buff *skb, unsigned short *tvlv_len) |
| { |
| const int pad_len = 2; |
| char *padding; |
| |
| if (skb_headroom(skb) < pad_len) |
| return NULL; |
| |
| padding = batadv_mcast_forw_skb_push(skb, pad_len, tvlv_len); |
| memset(padding, 0, pad_len); |
| |
| return padding; |
| } |
| |
| /** |
| * batadv_mcast_forw_push_est_padding() - push padding bytes if necessary |
| * @skb: the skb to potentially push the padding onto |
| * @count: the (estimated) number of originators the multicast packet needs to |
| * be sent to |
| * @tvlv_len: stores the amount of currently pushed TVLV bytes |
| * |
| * If the number of destination entries is even then this adds two |
| * padding bytes to the end of the tracker TVLV. |
| * |
| * Return: true on success or if no padding is needed, false otherwise. |
| */ |
| static bool |
| batadv_mcast_forw_push_est_padding(struct sk_buff *skb, int count, |
| unsigned short *tvlv_len) |
| { |
| if (!(count % 2) && !batadv_mcast_forw_push_padding(skb, tvlv_len)) |
| return false; |
| |
| return true; |
| } |
| |
| /** |
| * batadv_mcast_forw_orig_entry() - get orig_node from an hlist node |
| * @node: the hlist node to get the orig_node from |
| * @entry_offset: the offset of the hlist node within the orig_node struct |
| * |
| * Return: The orig_node containing the hlist node on success, NULL on error. |
| */ |
| static struct batadv_orig_node * |
| batadv_mcast_forw_orig_entry(struct hlist_node *node, |
| size_t entry_offset) |
| { |
| /* sanity check */ |
| switch (entry_offset) { |
| case offsetof(struct batadv_orig_node, mcast_want_all_ipv4_node): |
| case offsetof(struct batadv_orig_node, mcast_want_all_ipv6_node): |
| case offsetof(struct batadv_orig_node, mcast_want_all_rtr4_node): |
| case offsetof(struct batadv_orig_node, mcast_want_all_rtr6_node): |
| break; |
| default: |
| WARN_ON(1); |
| return NULL; |
| } |
| |
| return (struct batadv_orig_node *)((void *)node - entry_offset); |
| } |
| |
| /** |
| * batadv_mcast_forw_push_dest() - push an originator MAC address onto an skb |
| * @bat_priv: the bat priv with all the soft interface information |
| * @skb: the skb to push the destination address onto |
| * @vid: the vlan identifier |
| * @orig_node: the originator node to get the MAC address from |
| * @num_dests: a pointer to store the number of pushed addresses in |
| * @tvlv_len: stores the amount of currently pushed TVLV bytes |
| * |
| * If the orig_node is a BLA backbone gateway, if there is not enough skb |
| * headroom available or if num_dests is already at its maximum (65535) then |
| * neither the skb nor num_dests is changed. Otherwise the originator's MAC |
| * address is pushed onto the given skb and num_dests incremented by one. |
| * |
| * Return: true if the orig_node is a backbone gateway or if an orig address |
| * was pushed successfully, false otherwise. |
| */ |
| static bool batadv_mcast_forw_push_dest(struct batadv_priv *bat_priv, |
| struct sk_buff *skb, unsigned short vid, |
| struct batadv_orig_node *orig_node, |
| unsigned short *num_dests, |
| unsigned short *tvlv_len) |
| { |
| BUILD_BUG_ON(sizeof_field(struct batadv_tvlv_mcast_tracker, num_dests) |
| != sizeof(__be16)); |
| |
| /* Avoid sending to other BLA gateways - they already got the frame from |
| * the LAN side we share with them. |
| * TODO: Refactor to take BLA into account earlier in mode check. |
| */ |
| if (batadv_bla_is_backbone_gw_orig(bat_priv, orig_node->orig, vid)) |
| return true; |
| |
| if (skb_headroom(skb) < ETH_ALEN || *num_dests == U16_MAX) |
| return false; |
| |
| batadv_mcast_forw_skb_push(skb, ETH_ALEN, tvlv_len); |
| ether_addr_copy(skb->data, orig_node->orig); |
| (*num_dests)++; |
| |
| return true; |
| } |
| |
| /** |
| * batadv_mcast_forw_push_dests_list() - push originators from list onto an skb |
| * @bat_priv: the bat priv with all the soft interface information |
| * @skb: the skb to push the destination addresses onto |
| * @vid: the vlan identifier |
| * @head: the list to gather originators from |
| * @entry_offset: offset of an hlist node in an orig_node structure |
| * @num_dests: a pointer to store the number of pushed addresses in |
| * @tvlv_len: stores the amount of currently pushed TVLV bytes |
| * |
| * Push the MAC addresses of all originators in the given list onto the given |
| * skb. |
| * |
| * Return: true on success, false otherwise. |
| */ |
| static int batadv_mcast_forw_push_dests_list(struct batadv_priv *bat_priv, |
| struct sk_buff *skb, |
| unsigned short vid, |
| struct hlist_head *head, |
| size_t entry_offset, |
| unsigned short *num_dests, |
| unsigned short *tvlv_len) |
| { |
| struct hlist_node *node; |
| struct batadv_orig_node *orig_node; |
| |
| rcu_read_lock(); |
| __hlist_for_each_rcu(node, head) { |
| orig_node = batadv_mcast_forw_orig_entry(node, entry_offset); |
| if (!orig_node || |
| !batadv_mcast_forw_push_dest(bat_priv, skb, vid, orig_node, |
| num_dests, tvlv_len)) { |
| rcu_read_unlock(); |
| return false; |
| } |
| } |
| rcu_read_unlock(); |
| |
| return true; |
| } |
| |
| /** |
| * batadv_mcast_forw_push_tt() - push originators with interest through TT |
| * @bat_priv: the bat priv with all the soft interface information |
| * @skb: the skb to push the destination addresses onto |
| * @vid: the vlan identifier |
| * @num_dests: a pointer to store the number of pushed addresses in |
| * @tvlv_len: stores the amount of currently pushed TVLV bytes |
| * |
| * Push the MAC addresses of all originators which have indicated interest in |
| * this multicast packet through the translation table onto the given skb. |
| * |
| * Return: true on success, false otherwise. |
| */ |
| static bool |
| batadv_mcast_forw_push_tt(struct batadv_priv *bat_priv, struct sk_buff *skb, |
| unsigned short vid, unsigned short *num_dests, |
| unsigned short *tvlv_len) |
| { |
| struct batadv_tt_orig_list_entry *orig_entry; |
| |
| struct batadv_tt_global_entry *tt_global; |
| const u8 *addr = eth_hdr(skb)->h_dest; |
| |
| /* ok */ |
| int ret = true; |
| |
| tt_global = batadv_tt_global_hash_find(bat_priv, addr, vid); |
| if (!tt_global) |
| goto out; |
| |
| rcu_read_lock(); |
| hlist_for_each_entry_rcu(orig_entry, &tt_global->orig_list, list) { |
| if (!batadv_mcast_forw_push_dest(bat_priv, skb, vid, |
| orig_entry->orig_node, |
| num_dests, tvlv_len)) { |
| ret = false; |
| break; |
| } |
| } |
| rcu_read_unlock(); |
| |
| batadv_tt_global_entry_put(tt_global); |
| |
| out: |
| return ret; |
| } |
| |
| /** |
| * batadv_mcast_forw_push_want_all() - push originators with want-all flag |
| * @bat_priv: the bat priv with all the soft interface information |
| * @skb: the skb to push the destination addresses onto |
| * @vid: the vlan identifier |
| * @num_dests: a pointer to store the number of pushed addresses in |
| * @tvlv_len: stores the amount of currently pushed TVLV bytes |
| * |
| * Push the MAC addresses of all originators which have indicated interest in |
| * this multicast packet through the want-all flag onto the given skb. |
| * |
| * Return: true on success, false otherwise. |
| */ |
| static bool batadv_mcast_forw_push_want_all(struct batadv_priv *bat_priv, |
| struct sk_buff *skb, |
| unsigned short vid, |
| unsigned short *num_dests, |
| unsigned short *tvlv_len) |
| { |
| struct hlist_head *head = NULL; |
| size_t offset; |
| int ret; |
| |
| switch (eth_hdr(skb)->h_proto) { |
| case htons(ETH_P_IP): |
| head = &bat_priv->mcast.want_all_ipv4_list; |
| offset = offsetof(struct batadv_orig_node, |
| mcast_want_all_ipv4_node); |
| break; |
| case htons(ETH_P_IPV6): |
| head = &bat_priv->mcast.want_all_ipv6_list; |
| offset = offsetof(struct batadv_orig_node, |
| mcast_want_all_ipv6_node); |
| break; |
| default: |
| return false; |
| } |
| |
| ret = batadv_mcast_forw_push_dests_list(bat_priv, skb, vid, head, |
| offset, num_dests, tvlv_len); |
| if (!ret) |
| return false; |
| |
| return true; |
| } |
| |
| /** |
| * batadv_mcast_forw_push_want_rtr() - push originators with want-router flag |
| * @bat_priv: the bat priv with all the soft interface information |
| * @skb: the skb to push the destination addresses onto |
| * @vid: the vlan identifier |
| * @num_dests: a pointer to store the number of pushed addresses in |
| * @tvlv_len: stores the amount of currently pushed TVLV bytes |
| * |
| * Push the MAC addresses of all originators which have indicated interest in |
| * this multicast packet through the want-all-rtr flag onto the given skb. |
| * |
| * Return: true on success, false otherwise. |
| */ |
| static bool batadv_mcast_forw_push_want_rtr(struct batadv_priv *bat_priv, |
| struct sk_buff *skb, |
| unsigned short vid, |
| unsigned short *num_dests, |
| unsigned short *tvlv_len) |
| { |
| struct hlist_head *head = NULL; |
| size_t offset; |
| int ret; |
| |
| switch (eth_hdr(skb)->h_proto) { |
| case htons(ETH_P_IP): |
| head = &bat_priv->mcast.want_all_rtr4_list; |
| offset = offsetof(struct batadv_orig_node, |
| mcast_want_all_rtr4_node); |
| break; |
| case htons(ETH_P_IPV6): |
| head = &bat_priv->mcast.want_all_rtr6_list; |
| offset = offsetof(struct batadv_orig_node, |
| mcast_want_all_rtr6_node); |
| break; |
| default: |
| return false; |
| } |
| |
| ret = batadv_mcast_forw_push_dests_list(bat_priv, skb, vid, head, |
| offset, num_dests, tvlv_len); |
| if (!ret) |
| return false; |
| |
| return true; |
| } |
| |
| /** |
| * batadv_mcast_forw_scrape() - remove bytes within skb data |
| * @skb: the skb to remove bytes from |
| * @offset: the offset from the skb data from which to scrape |
| * @len: the amount of bytes to scrape starting from the offset |
| * |
| * Scrapes/removes len bytes from the given skb at the given offset from the |
| * skb data. |
| * |
| * Caller needs to ensure that the region from the skb data's start up |
| * to/including the to be removed bytes are linearized. |
| */ |
| static void batadv_mcast_forw_scrape(struct sk_buff *skb, |
| unsigned short offset, |
| unsigned short len) |
| { |
| char *to, *from; |
| |
| SKB_LINEAR_ASSERT(skb); |
| |
| to = skb_pull(skb, len); |
| from = to - len; |
| |
| memmove(to, from, offset); |
| } |
| |
| /** |
| * batadv_mcast_forw_push_scrape_padding() - remove TVLV padding |
| * @skb: the skb to potentially adjust the TVLV's padding on |
| * @tvlv_len: stores the amount of currently pushed TVLV bytes |
| * |
| * Remove two padding bytes from the end of the multicast tracker TVLV, |
| * from before the payload data. |
| * |
| * Caller needs to ensure that the TVLV bytes are linearized. |
| */ |
| static void batadv_mcast_forw_push_scrape_padding(struct sk_buff *skb, |
| unsigned short *tvlv_len) |
| { |
| const int pad_len = 2; |
| |
| batadv_mcast_forw_scrape(skb, *tvlv_len - pad_len, pad_len); |
| *tvlv_len -= pad_len; |
| } |
| |
| /** |
| * batadv_mcast_forw_push_insert_padding() - insert TVLV padding |
| * @skb: the skb to potentially adjust the TVLV's padding on |
| * @tvlv_len: stores the amount of currently pushed TVLV bytes |
| * |
| * Inserts two padding bytes at the end of the multicast tracker TVLV, |
| * before the payload data in the given skb. |
| * |
| * Return: true on success, false otherwise. |
| */ |
| static bool batadv_mcast_forw_push_insert_padding(struct sk_buff *skb, |
| unsigned short *tvlv_len) |
| { |
| unsigned short offset = *tvlv_len; |
| char *to, *from = skb->data; |
| |
| to = batadv_mcast_forw_push_padding(skb, tvlv_len); |
| if (!to) |
| return false; |
| |
| memmove(to, from, offset); |
| memset(to + offset, 0, *tvlv_len - offset); |
| return true; |
| } |
| |
| /** |
| * batadv_mcast_forw_push_adjust_padding() - adjust padding if necessary |
| * @skb: the skb to potentially adjust the TVLV's padding on |
| * @count: the estimated number of originators the multicast packet needs to |
| * be sent to |
| * @num_dests_pushed: the number of originators that were actually added to the |
| * multicast packet's tracker TVLV |
| * @tvlv_len: stores the amount of currently pushed TVLV bytes |
| * |
| * Adjusts the padding in the multicast packet's tracker TVLV depending on the |
| * initially estimated amount of destinations versus the amount of destinations |
| * that were actually added to the tracker TVLV. |
| * |
| * If the initial estimate was correct or at least the oddness was the same then |
| * no padding adjustment is performed. |
| * If the initially estimated number was even, so padding was initially added, |
| * but it turned out to be odd then padding is removed. |
| * If the initially estimated number was odd, so no padding was initially added, |
| * but it turned out to be even then padding is added. |
| * |
| * Return: true if no padding adjustment is needed or the adjustment was |
| * successful, false otherwise. |
| */ |
| static bool |
| batadv_mcast_forw_push_adjust_padding(struct sk_buff *skb, int *count, |
| unsigned short num_dests_pushed, |
| unsigned short *tvlv_len) |
| { |
| int ret = true; |
| |
| if (likely((num_dests_pushed % 2) == (*count % 2))) |
| goto out; |
| |
| /** |
| * estimated even number of destinations, but turned out to be odd |
| * -> remove padding |
| */ |
| if (!(*count % 2) && (num_dests_pushed % 2)) |
| batadv_mcast_forw_push_scrape_padding(skb, tvlv_len); |
| /** |
| * estimated odd number of destinations, but turned out to be even |
| * -> add padding |
| */ |
| else if ((*count % 2) && (!(num_dests_pushed % 2))) |
| ret = batadv_mcast_forw_push_insert_padding(skb, tvlv_len); |
| |
| out: |
| *count = num_dests_pushed; |
| return ret; |
| } |
| |
| /** |
| * batadv_mcast_forw_push_dests() - push originator addresses onto an skb |
| * @bat_priv: the bat priv with all the soft interface information |
| * @skb: the skb to push the destination addresses onto |
| * @vid: the vlan identifier |
| * @is_routable: indicates whether the destination is routable |
| * @count: the number of originators the multicast packet needs to be sent to |
| * @tvlv_len: stores the amount of currently pushed TVLV bytes |
| * |
| * Push the MAC addresses of all originators which have indicated interest in |
| * this multicast packet onto the given skb. |
| * |
| * Return: -ENOMEM if there is not enough skb headroom available. Otherwise, on |
| * success 0. |
| */ |
| static int |
| batadv_mcast_forw_push_dests(struct batadv_priv *bat_priv, struct sk_buff *skb, |
| unsigned short vid, int is_routable, int *count, |
| unsigned short *tvlv_len) |
| { |
| unsigned short num_dests = 0; |
| |
| if (!batadv_mcast_forw_push_est_padding(skb, *count, tvlv_len)) |
| goto err; |
| |
| if (!batadv_mcast_forw_push_tt(bat_priv, skb, vid, &num_dests, |
| tvlv_len)) |
| goto err; |
| |
| if (!batadv_mcast_forw_push_want_all(bat_priv, skb, vid, &num_dests, |
| tvlv_len)) |
| goto err; |
| |
| if (is_routable && |
| !batadv_mcast_forw_push_want_rtr(bat_priv, skb, vid, &num_dests, |
| tvlv_len)) |
| goto err; |
| |
| if (!batadv_mcast_forw_push_adjust_padding(skb, count, num_dests, |
| tvlv_len)) |
| goto err; |
| |
| return 0; |
| err: |
| return -ENOMEM; |
| } |
| |
| /** |
| * batadv_mcast_forw_push_tracker() - push a multicast tracker TVLV header |
| * @skb: the skb to push the tracker TVLV onto |
| * @num_dests: the number of destination addresses to set in the header |
| * @tvlv_len: stores the amount of currently pushed TVLV bytes |
| * |
| * Pushes a multicast tracker TVLV header onto the given skb, including the |
| * generic TVLV header but excluding the destination MAC addresses. |
| * |
| * The provided num_dests value is taken into consideration to set the |
| * num_dests field in the tracker header and to set the appropriate TVLV length |
| * value fields. |
| * |
| * Return: -ENOMEM if there is not enough skb headroom available. Otherwise, on |
| * success 0. |
| */ |
| static int batadv_mcast_forw_push_tracker(struct sk_buff *skb, int num_dests, |
| unsigned short *tvlv_len) |
| { |
| struct batadv_tvlv_mcast_tracker *mcast_tracker; |
| struct batadv_tvlv_hdr *tvlv_hdr; |
| unsigned int tvlv_value_len; |
| |
| if (skb_headroom(skb) < sizeof(*mcast_tracker) + sizeof(*tvlv_hdr)) |
| return -ENOMEM; |
| |
| tvlv_value_len = sizeof(*mcast_tracker) + *tvlv_len; |
| if (tvlv_value_len + sizeof(*tvlv_hdr) > U16_MAX) |
| return -ENOMEM; |
| |
| batadv_mcast_forw_skb_push(skb, sizeof(*mcast_tracker), tvlv_len); |
| mcast_tracker = (struct batadv_tvlv_mcast_tracker *)skb->data; |
| mcast_tracker->num_dests = htons(num_dests); |
| |
| skb_reset_network_header(skb); |
| |
| batadv_mcast_forw_skb_push(skb, sizeof(*tvlv_hdr), tvlv_len); |
| tvlv_hdr = (struct batadv_tvlv_hdr *)skb->data; |
| tvlv_hdr->type = BATADV_TVLV_MCAST_TRACKER; |
| tvlv_hdr->version = 1; |
| tvlv_hdr->len = htons(tvlv_value_len); |
| |
| return 0; |
| } |
| |
| /** |
| * batadv_mcast_forw_push_tvlvs() - push a multicast tracker TVLV onto an skb |
| * @bat_priv: the bat priv with all the soft interface information |
| * @skb: the skb to push the tracker TVLV onto |
| * @vid: the vlan identifier |
| * @is_routable: indicates whether the destination is routable |
| * @count: the number of originators the multicast packet needs to be sent to |
| * @tvlv_len: stores the amount of currently pushed TVLV bytes |
| * |
| * Pushes a multicast tracker TVLV onto the given skb, including the collected |
| * destination MAC addresses and the generic TVLV header. |
| * |
| * Return: -ENOMEM if there is not enough skb headroom available. Otherwise, on |
| * success 0. |
| */ |
| static int |
| batadv_mcast_forw_push_tvlvs(struct batadv_priv *bat_priv, struct sk_buff *skb, |
| unsigned short vid, int is_routable, int count, |
| unsigned short *tvlv_len) |
| { |
| int ret; |
| |
| ret = batadv_mcast_forw_push_dests(bat_priv, skb, vid, is_routable, |
| &count, tvlv_len); |
| if (ret < 0) |
| return ret; |
| |
| ret = batadv_mcast_forw_push_tracker(skb, count, tvlv_len); |
| if (ret < 0) |
| return ret; |
| |
| return 0; |
| } |
| |
| /** |
| * batadv_mcast_forw_push_hdr() - push a multicast packet header onto an skb |
| * @skb: the skb to push the header onto |
| * @tvlv_len: the total TVLV length value to set in the header |
| * |
| * Pushes a batman-adv multicast packet header onto the given skb and sets |
| * the provided total TVLV length value in it. |
| * |
| * Caller needs to ensure enough skb headroom is available. |
| * |
| * Return: -ENOMEM if there is not enough skb headroom available. Otherwise, on |
| * success 0. |
| */ |
| static int |
| batadv_mcast_forw_push_hdr(struct sk_buff *skb, unsigned short tvlv_len) |
| { |
| struct batadv_mcast_packet *mcast_packet; |
| |
| if (skb_headroom(skb) < sizeof(*mcast_packet)) |
| return -ENOMEM; |
| |
| skb_push(skb, sizeof(*mcast_packet)); |
| |
| mcast_packet = (struct batadv_mcast_packet *)skb->data; |
| mcast_packet->version = BATADV_COMPAT_VERSION; |
| mcast_packet->ttl = BATADV_TTL; |
| mcast_packet->packet_type = BATADV_MCAST; |
| mcast_packet->reserved = 0; |
| mcast_packet->tvlv_len = htons(tvlv_len); |
| |
| return 0; |
| } |
| |
| /** |
| * batadv_mcast_forw_scrub_dests() - scrub destinations in a tracker TVLV |
| * @bat_priv: the bat priv with all the soft interface information |
| * @comp_neigh: next hop neighbor to scrub+collect destinations for |
| * @dest: start MAC entry in original skb's tracker TVLV |
| * @next_dest: start MAC entry in to be sent skb's tracker TVLV |
| * @num_dests: number of remaining destination MAC entries to iterate over |
| * |
| * This sorts destination entries into either the original batman-adv |
| * multicast packet or the skb (copy) that is going to be sent to comp_neigh |
| * next. |
| * |
| * In preparation for the next, to be (unicast) transmitted batman-adv multicast |
| * packet skb to be sent to the given neighbor node, tries to collect all |
| * originator MAC addresses that have the given neighbor node as their next hop |
| * in the to be transmitted skb (copy), which next_dest points into. That is we |
| * zero all destination entries in next_dest which do not have comp_neigh as |
| * their next hop. And zero all destination entries in the original skb that |
| * would have comp_neigh as their next hop (to avoid redundant transmissions and |
| * duplicated payload later). |
| */ |
| static void |
| batadv_mcast_forw_scrub_dests(struct batadv_priv *bat_priv, |
| struct batadv_neigh_node *comp_neigh, u8 *dest, |
| u8 *next_dest, u16 num_dests) |
| { |
| struct batadv_neigh_node *next_neigh; |
| |
| /* skip first entry, this is what we are comparing with */ |
| eth_zero_addr(dest); |
| dest += ETH_ALEN; |
| next_dest += ETH_ALEN; |
| num_dests--; |
| |
| batadv_mcast_forw_tracker_for_each_dest2(dest, next_dest, num_dests) { |
| if (is_zero_ether_addr(next_dest)) |
| continue; |
| |
| /* sanity check, we expect unicast destinations */ |
| if (is_multicast_ether_addr(next_dest)) { |
| eth_zero_addr(dest); |
| eth_zero_addr(next_dest); |
| continue; |
| } |
| |
| next_neigh = batadv_orig_to_router(bat_priv, next_dest, NULL); |
| if (!next_neigh) { |
| eth_zero_addr(next_dest); |
| continue; |
| } |
| |
| if (!batadv_compare_eth(next_neigh->addr, comp_neigh->addr)) { |
| eth_zero_addr(next_dest); |
| batadv_neigh_node_put(next_neigh); |
| continue; |
| } |
| |
| /* found an entry for our next packet to transmit, so remove it |
| * from the original packet |
| */ |
| eth_zero_addr(dest); |
| batadv_neigh_node_put(next_neigh); |
| } |
| } |
| |
| /** |
| * batadv_mcast_forw_shrink_fill() - swap slot with next non-zero destination |
| * @slot: the to be filled zero-MAC destination entry in a tracker TVLV |
| * @num_dests_slot: remaining entries in tracker TVLV from/including slot |
| * |
| * Searches for the next non-zero-MAC destination entry in a tracker TVLV after |
| * the given slot pointer. And if found, swaps it with the zero-MAC destination |
| * entry which the slot points to. |
| * |
| * Return: true if slot was swapped/filled successfully, false otherwise. |
| */ |
| static bool batadv_mcast_forw_shrink_fill(u8 *slot, u16 num_dests_slot) |
| { |
| u16 num_dests_filler; |
| u8 *filler; |
| |
| /* sanity check, should not happen */ |
| if (!num_dests_slot) |
| return false; |
| |
| num_dests_filler = num_dests_slot - 1; |
| filler = slot + ETH_ALEN; |
| |
| /* find a candidate to fill the empty slot */ |
| batadv_mcast_forw_tracker_for_each_dest(filler, num_dests_filler) { |
| if (is_zero_ether_addr(filler)) |
| continue; |
| |
| ether_addr_copy(slot, filler); |
| eth_zero_addr(filler); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * batadv_mcast_forw_shrink_pack_dests() - pack destinations of a tracker TVLV |
| * @skb: the batman-adv multicast packet to compact destinations in |
| * |
| * Compacts the originator destination MAC addresses in the multicast tracker |
| * TVLV of the given multicast packet. This is done by moving all non-zero |
| * MAC addresses in direction of the skb head and all zero MAC addresses in skb |
| * tail direction, within the multicast tracker TVLV. |
| * |
| * Return: The number of consecutive zero MAC address destinations which are |
| * now at the end of the multicast tracker TVLV. |
| */ |
| static int batadv_mcast_forw_shrink_pack_dests(struct sk_buff *skb) |
| { |
| struct batadv_tvlv_mcast_tracker *mcast_tracker; |
| unsigned char *skb_net_hdr; |
| u16 num_dests_slot; |
| u8 *slot; |
| |
| skb_net_hdr = skb_network_header(skb); |
| mcast_tracker = (struct batadv_tvlv_mcast_tracker *)skb_net_hdr; |
| num_dests_slot = ntohs(mcast_tracker->num_dests); |
| |
| slot = (u8 *)mcast_tracker + sizeof(*mcast_tracker); |
| |
| batadv_mcast_forw_tracker_for_each_dest(slot, num_dests_slot) { |
| /* find an empty slot */ |
| if (!is_zero_ether_addr(slot)) |
| continue; |
| |
| if (!batadv_mcast_forw_shrink_fill(slot, num_dests_slot)) |
| /* could not find a filler, so we successfully packed |
| * and can stop - and must not reduce num_dests_slot! |
| */ |
| break; |
| } |
| |
| /* num_dests_slot is now the amount of reduced, zeroed |
| * destinations at the end of the tracker TVLV |
| */ |
| return num_dests_slot; |
| } |
| |
| /** |
| * batadv_mcast_forw_shrink_align_offset() - get new alignment offset |
| * @num_dests_old: the old, to be updated amount of destination nodes |
| * @num_dests_reduce: the number of destinations that were removed |
| * |
| * Calculates the amount of potential extra alignment offset that is needed to |
| * adjust the TVLV padding after the change in destination nodes. |
| * |
| * Return: |
| * 0: If no change to padding is needed. |
| * 2: If padding needs to be removed. |
| * -2: If padding needs to be added. |
| */ |
| static short |
| batadv_mcast_forw_shrink_align_offset(unsigned int num_dests_old, |
| unsigned int num_dests_reduce) |
| { |
| /* even amount of removed destinations -> no alignment change */ |
| if (!(num_dests_reduce % 2)) |
| return 0; |
| |
| /* even to odd amount of destinations -> remove padding */ |
| if (!(num_dests_old % 2)) |
| return 2; |
| |
| /* odd to even amount of destinations -> add padding */ |
| return -2; |
| } |
| |
| /** |
| * batadv_mcast_forw_shrink_update_headers() - update shrunk mc packet headers |
| * @skb: the batman-adv multicast packet to update headers of |
| * @num_dests_reduce: the number of destinations that were removed |
| * |
| * This updates any fields of a batman-adv multicast packet that are affected |
| * by the reduced number of destinations in the multicast tracket TVLV. In |
| * particular this updates: |
| * |
| * The num_dest field of the multicast tracker TVLV. |
| * The TVLV length field of the according generic TVLV header. |
| * The batman-adv multicast packet's total TVLV length field. |
| * |
| * Return: The offset in skb's tail direction at which the new batman-adv |
| * multicast packet header needs to start. |
| */ |
| static unsigned int |
| batadv_mcast_forw_shrink_update_headers(struct sk_buff *skb, |
| unsigned int num_dests_reduce) |
| { |
| struct batadv_tvlv_mcast_tracker *mcast_tracker; |
| struct batadv_mcast_packet *mcast_packet; |
| struct batadv_tvlv_hdr *tvlv_hdr; |
| unsigned char *skb_net_hdr; |
| unsigned int offset; |
| short align_offset; |
| u16 num_dests; |
| |
| skb_net_hdr = skb_network_header(skb); |
| mcast_tracker = (struct batadv_tvlv_mcast_tracker *)skb_net_hdr; |
| num_dests = ntohs(mcast_tracker->num_dests); |
| |
| align_offset = batadv_mcast_forw_shrink_align_offset(num_dests, |
| num_dests_reduce); |
| offset = ETH_ALEN * num_dests_reduce + align_offset; |
| num_dests -= num_dests_reduce; |
| |
| /* update tracker header */ |
| mcast_tracker->num_dests = htons(num_dests); |
| |
| /* update tracker's tvlv header's length field */ |
| tvlv_hdr = (struct batadv_tvlv_hdr *)(skb_network_header(skb) - |
| sizeof(*tvlv_hdr)); |
| tvlv_hdr->len = htons(ntohs(tvlv_hdr->len) - offset); |
| |
| /* update multicast packet header's tvlv length field */ |
| mcast_packet = (struct batadv_mcast_packet *)skb->data; |
| mcast_packet->tvlv_len = htons(ntohs(mcast_packet->tvlv_len) - offset); |
| |
| return offset; |
| } |
| |
| /** |
| * batadv_mcast_forw_shrink_move_headers() - move multicast headers by offset |
| * @skb: the batman-adv multicast packet to move headers for |
| * @offset: a non-negative offset to move headers by, towards the skb tail |
| * |
| * Moves the batman-adv multicast packet header, its multicast tracker TVLV and |
| * any TVLVs in between by the given offset in direction towards the tail. |
| */ |
| static void |
| batadv_mcast_forw_shrink_move_headers(struct sk_buff *skb, unsigned int offset) |
| { |
| struct batadv_tvlv_mcast_tracker *mcast_tracker; |
| unsigned char *skb_net_hdr; |
| unsigned int len; |
| u16 num_dests; |
| |
| skb_net_hdr = skb_network_header(skb); |
| mcast_tracker = (struct batadv_tvlv_mcast_tracker *)skb_net_hdr; |
| num_dests = ntohs(mcast_tracker->num_dests); |
| len = skb_network_offset(skb) + sizeof(*mcast_tracker); |
| len += num_dests * ETH_ALEN; |
| |
| batadv_mcast_forw_scrape(skb, len, offset); |
| } |
| |
| /** |
| * batadv_mcast_forw_shrink_tracker() - remove zero addresses in a tracker tvlv |
| * @skb: the batman-adv multicast packet to (potentially) shrink |
| * |
| * Removes all destinations with a zero MAC addresses (00:00:00:00:00:00) from |
| * the given batman-adv multicast packet's tracker TVLV and updates headers |
| * accordingly to maintain a valid batman-adv multicast packet. |
| */ |
| static void batadv_mcast_forw_shrink_tracker(struct sk_buff *skb) |
| { |
| unsigned int offset; |
| u16 dests_reduced; |
| |
| dests_reduced = batadv_mcast_forw_shrink_pack_dests(skb); |
| if (!dests_reduced) |
| return; |
| |
| offset = batadv_mcast_forw_shrink_update_headers(skb, dests_reduced); |
| batadv_mcast_forw_shrink_move_headers(skb, offset); |
| } |
| |
| /** |
| * batadv_mcast_forw_packet() - forward a batman-adv multicast packet |
| * @bat_priv: the bat priv with all the soft interface information |
| * @skb: the received or locally generated batman-adv multicast packet |
| * @local_xmit: indicates that the packet was locally generated and not received |
| * |
| * Parses the tracker TVLV of a batman-adv multicast packet and forwards the |
| * packet as indicated in this TVLV. |
| * |
| * Caller needs to set the skb network header to the start of the multicast |
| * tracker TVLV (excluding the generic TVLV header) and the skb transport header |
| * to the next byte after this multicast tracker TVLV. |
| * |
| * Caller needs to free the skb. |
| * |
| * Return: NET_RX_SUCCESS or NET_RX_DROP on success or a negative error |
| * code on failure. NET_RX_SUCCESS if the received packet is supposed to be |
| * decapsulated and forwarded to the own soft interface, NET_RX_DROP otherwise. |
| */ |
| static int batadv_mcast_forw_packet(struct batadv_priv *bat_priv, |
| struct sk_buff *skb, bool local_xmit) |
| { |
| struct batadv_tvlv_mcast_tracker *mcast_tracker; |
| struct batadv_neigh_node *neigh_node; |
| unsigned long offset, num_dests_off; |
| struct sk_buff *nexthop_skb; |
| unsigned char *skb_net_hdr; |
| bool local_recv = false; |
| unsigned int tvlv_len; |
| bool xmitted = false; |
| u8 *dest, *next_dest; |
| u16 num_dests; |
| int ret; |
| |
| /* (at least) TVLV part needs to be linearized */ |
| SKB_LINEAR_ASSERT(skb); |
| |
| /* check if num_dests is within skb length */ |
| num_dests_off = offsetof(struct batadv_tvlv_mcast_tracker, num_dests); |
| if (num_dests_off > skb_network_header_len(skb)) |
| return -EINVAL; |
| |
| skb_net_hdr = skb_network_header(skb); |
| mcast_tracker = (struct batadv_tvlv_mcast_tracker *)skb_net_hdr; |
| num_dests = ntohs(mcast_tracker->num_dests); |
| |
| dest = (u8 *)mcast_tracker + sizeof(*mcast_tracker); |
| |
| /* check if full tracker tvlv is within skb length */ |
| tvlv_len = sizeof(*mcast_tracker) + ETH_ALEN * num_dests; |
| if (tvlv_len > skb_network_header_len(skb)) |
| return -EINVAL; |
| |
| /* invalidate checksum: */ |
| skb->ip_summed = CHECKSUM_NONE; |
| |
| batadv_mcast_forw_tracker_for_each_dest(dest, num_dests) { |
| if (is_zero_ether_addr(dest)) |
| continue; |
| |
| /* only unicast originator addresses supported */ |
| if (is_multicast_ether_addr(dest)) { |
| eth_zero_addr(dest); |
| continue; |
| } |
| |
| if (batadv_is_my_mac(bat_priv, dest)) { |
| eth_zero_addr(dest); |
| local_recv = true; |
| continue; |
| } |
| |
| neigh_node = batadv_orig_to_router(bat_priv, dest, NULL); |
| if (!neigh_node) { |
| eth_zero_addr(dest); |
| continue; |
| } |
| |
| nexthop_skb = skb_copy(skb, GFP_ATOMIC); |
| if (!nexthop_skb) { |
| batadv_neigh_node_put(neigh_node); |
| return -ENOMEM; |
| } |
| |
| offset = dest - skb->data; |
| next_dest = nexthop_skb->data + offset; |
| |
| batadv_mcast_forw_scrub_dests(bat_priv, neigh_node, dest, |
| next_dest, num_dests); |
| batadv_mcast_forw_shrink_tracker(nexthop_skb); |
| |
| batadv_inc_counter(bat_priv, BATADV_CNT_MCAST_TX); |
| batadv_add_counter(bat_priv, BATADV_CNT_MCAST_TX_BYTES, |
| nexthop_skb->len + ETH_HLEN); |
| xmitted = true; |
| ret = batadv_send_unicast_skb(nexthop_skb, neigh_node); |
| |
| batadv_neigh_node_put(neigh_node); |
| |
| if (ret < 0) |
| return ret; |
| } |
| |
| if (xmitted) { |
| if (local_xmit) { |
| batadv_inc_counter(bat_priv, BATADV_CNT_MCAST_TX_LOCAL); |
| batadv_add_counter(bat_priv, |
| BATADV_CNT_MCAST_TX_LOCAL_BYTES, |
| skb->len - |
| skb_transport_offset(skb)); |
| } else { |
| batadv_inc_counter(bat_priv, BATADV_CNT_MCAST_FWD); |
| batadv_add_counter(bat_priv, BATADV_CNT_MCAST_FWD_BYTES, |
| skb->len + ETH_HLEN); |
| } |
| } |
| |
| if (local_recv) |
| return NET_RX_SUCCESS; |
| else |
| return NET_RX_DROP; |
| } |
| |
| /** |
| * batadv_mcast_forw_tracker_tvlv_handler() - handle an mcast tracker tvlv |
| * @bat_priv: the bat priv with all the soft interface information |
| * @skb: the received batman-adv multicast packet |
| * |
| * Parses the tracker TVLV of an incoming batman-adv multicast packet and |
| * forwards the packet as indicated in this TVLV. |
| * |
| * Caller needs to set the skb network header to the start of the multicast |
| * tracker TVLV (excluding the generic TVLV header) and the skb transport header |
| * to the next byte after this multicast tracker TVLV. |
| * |
| * Caller needs to free the skb. |
| * |
| * Return: NET_RX_SUCCESS or NET_RX_DROP on success or a negative error |
| * code on failure. NET_RX_SUCCESS if the received packet is supposed to be |
| * decapsulated and forwarded to the own soft interface, NET_RX_DROP otherwise. |
| */ |
| int batadv_mcast_forw_tracker_tvlv_handler(struct batadv_priv *bat_priv, |
| struct sk_buff *skb) |
| { |
| return batadv_mcast_forw_packet(bat_priv, skb, false); |
| } |
| |
| /** |
| * batadv_mcast_forw_packet_hdrlen() - multicast packet header length |
| * @num_dests: number of destination nodes |
| * |
| * Calculates the total batman-adv multicast packet header length for a given |
| * number of destination nodes (excluding the outer ethernet frame). |
| * |
| * Return: The calculated total batman-adv multicast packet header length. |
| */ |
| unsigned int batadv_mcast_forw_packet_hdrlen(unsigned int num_dests) |
| { |
| /** |
| * If the number of destination entries is even then we need to add |
| * two byte padding to the tracker TVLV. |
| */ |
| int padding = (!(num_dests % 2)) ? 2 : 0; |
| |
| return padding + num_dests * ETH_ALEN + |
| sizeof(struct batadv_tvlv_mcast_tracker) + |
| sizeof(struct batadv_tvlv_hdr) + |
| sizeof(struct batadv_mcast_packet); |
| } |
| |
| /** |
| * batadv_mcast_forw_expand_head() - expand headroom for an mcast packet |
| * @bat_priv: the bat priv with all the soft interface information |
| * @skb: the multicast packet to send |
| * |
| * Tries to expand an skb's headroom so that its head to tail is 1298 |
| * bytes (minimum IPv6 MTU + vlan ethernet header size) large. |
| * |
| * Return: -EINVAL if the given skb's length is too large or -ENOMEM on memory |
| * allocation failure. Otherwise, on success, zero is returned. |
| */ |
| static int batadv_mcast_forw_expand_head(struct batadv_priv *bat_priv, |
| struct sk_buff *skb) |
| { |
| int hdr_size = VLAN_ETH_HLEN + IPV6_MIN_MTU - skb->len; |
| |
| /* TODO: Could be tightened to actual number of destination nodes? |
| * But it's tricky, number of destinations might have increased since |
| * we last checked. |
| */ |
| if (hdr_size < 0) { |
| /* batadv_mcast_forw_mode_check_count() should ensure we do not |
| * end up here |
| */ |
| WARN_ON(1); |
| return -EINVAL; |
| } |
| |
| if (skb_headroom(skb) < hdr_size && |
| pskb_expand_head(skb, hdr_size, 0, GFP_ATOMIC) < 0) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| |
| /** |
| * batadv_mcast_forw_push() - encapsulate skb in a batman-adv multicast packet |
| * @bat_priv: the bat priv with all the soft interface information |
| * @skb: the multicast packet to encapsulate and send |
| * @vid: the vlan identifier |
| * @is_routable: indicates whether the destination is routable |
| * @count: the number of originators the multicast packet needs to be sent to |
| * |
| * Encapsulates the given multicast packet in a batman-adv multicast packet. |
| * A multicast tracker TVLV with destination originator addresses for any node |
| * that signaled interest in it, that is either via the translation table or the |
| * according want-all flags, is attached accordingly. |
| * |
| * Return: true on success, false otherwise. |
| */ |
| bool batadv_mcast_forw_push(struct batadv_priv *bat_priv, struct sk_buff *skb, |
| unsigned short vid, int is_routable, int count) |
| { |
| unsigned short tvlv_len = 0; |
| int ret; |
| |
| if (batadv_mcast_forw_expand_head(bat_priv, skb) < 0) |
| goto err; |
| |
| skb_reset_transport_header(skb); |
| |
| ret = batadv_mcast_forw_push_tvlvs(bat_priv, skb, vid, is_routable, |
| count, &tvlv_len); |
| if (ret < 0) |
| goto err; |
| |
| ret = batadv_mcast_forw_push_hdr(skb, tvlv_len); |
| if (ret < 0) |
| goto err; |
| |
| return true; |
| |
| err: |
| if (tvlv_len) |
| skb_pull(skb, tvlv_len); |
| |
| return false; |
| } |
| |
| /** |
| * batadv_mcast_forw_mcsend() - send a self prepared batman-adv multicast packet |
| * @bat_priv: the bat priv with all the soft interface information |
| * @skb: the multicast packet to encapsulate and send |
| * |
| * Transmits a batman-adv multicast packet that was locally prepared and |
| * consumes/frees it. |
| * |
| * Return: NET_XMIT_DROP on memory allocation failure. NET_XMIT_SUCCESS |
| * otherwise. |
| */ |
| int batadv_mcast_forw_mcsend(struct batadv_priv *bat_priv, |
| struct sk_buff *skb) |
| { |
| int ret = batadv_mcast_forw_packet(bat_priv, skb, true); |
| |
| if (ret < 0) { |
| kfree_skb(skb); |
| return NET_XMIT_DROP; |
| } |
| |
| consume_skb(skb); |
| return NET_XMIT_SUCCESS; |
| } |