Hi,
David S. Miller found some problems in our current codebase which must be fixed before it can be submitted for inclusion into the mainline kernel under net/.
This it the second version of those patches which adds some new ones and corrects some smaller things in the commit messages.
thanks, Sven
my_skb_push provided an easy way to allocate enough headroom in situation were we don't have enough space left and move the data pointer to the new position, but we didn't checked wether we are allowed to write to the new pushed header. This is for example a problem when the skb was cloned and thus doesn't have a private data part.
my_skb_head_push now replaces my_skb_push by using skb_cow_head to provide only a large enough, writable header without testing for the rest of the (maybe shared) data. It will also move the data pointer using skb_push when skb_cow_head doesn't fail.
This should give us enough flexibility in situation were skbs will be queued by underlying layers and still doesn't unnecessarily copy the data in situations when the skb was consumed right away during dev_queue_xmit.
Reported-by: Marek Lindner lindner_marek@yahoo.de Signed-off-by: Sven Eckelmann sven.eckelmann@gmx.de --- batman-adv/send.c | 2 +- batman-adv/soft-interface.c | 18 ++++++------------ batman-adv/soft-interface.h | 2 +- batman-adv/unicast.c | 6 +++--- 4 files changed, 11 insertions(+), 17 deletions(-)
diff --git a/batman-adv/send.c b/batman-adv/send.c index 506994c..fb0bfbf 100644 --- a/batman-adv/send.c +++ b/batman-adv/send.c @@ -76,7 +76,7 @@ int send_skb_packet(struct sk_buff *skb, }
/* push to the ethernet header. */ - if (my_skb_push(skb, sizeof(struct ethhdr)) < 0) + if (my_skb_head_push(skb, sizeof(struct ethhdr)) < 0) goto send_skb_err;
skb_reset_mac_header(skb); diff --git a/batman-adv/soft-interface.c b/batman-adv/soft-interface.c index 44de0a6..2de0209 100644 --- a/batman-adv/soft-interface.c +++ b/batman-adv/soft-interface.c @@ -33,8 +33,6 @@
static uint32_t bcast_seqno = 1; /* give own bcast messages seq numbers to avoid * broadcast storms */ -static int32_t skb_packets; -static int32_t skb_bad_packets;
unsigned char main_if_addr[ETH_ALEN]; static int bat_get_settings(struct net_device *dev, struct ethtool_cmd *cmd); @@ -61,18 +59,14 @@ void set_main_if_addr(uint8_t *addr) memcpy(main_if_addr, addr, ETH_ALEN); }
-int my_skb_push(struct sk_buff *skb, unsigned int len) +int my_skb_head_push(struct sk_buff *skb, unsigned int len) { - int result = 0; + int result;
- skb_packets++; - if (skb_headroom(skb) < len) { - skb_bad_packets++; - result = pskb_expand_head(skb, len, 0, GFP_ATOMIC); + result = skb_cow_head(skb, len);
- if (result < 0) - return result; - } + if (result < 0) + return result;
skb_push(skb, len); return 0; @@ -148,7 +142,7 @@ int interface_tx(struct sk_buff *skb, struct net_device *dev)
/* ethernet packet should be broadcasted */ if (bcast_dst && do_bcast) { - if (my_skb_push(skb, sizeof(struct bcast_packet)) < 0) + if (my_skb_head_push(skb, sizeof(struct bcast_packet)) < 0) goto dropped;
bcast_packet = (struct bcast_packet *)skb->data; diff --git a/batman-adv/soft-interface.h b/batman-adv/soft-interface.h index 6364854..9dbf5fc 100644 --- a/batman-adv/soft-interface.h +++ b/batman-adv/soft-interface.h @@ -26,7 +26,7 @@ void set_main_if_addr(uint8_t *addr); void interface_setup(struct net_device *dev); int interface_tx(struct sk_buff *skb, struct net_device *dev); void interface_rx(struct sk_buff *skb, int hdr_size); -int my_skb_push(struct sk_buff *skb, unsigned int len); +int my_skb_head_push(struct sk_buff *skb, unsigned int len);
extern unsigned char main_if_addr[];
diff --git a/batman-adv/unicast.c b/batman-adv/unicast.c index 76cfccb..64505f7 100644 --- a/batman-adv/unicast.c +++ b/batman-adv/unicast.c @@ -164,8 +164,8 @@ int unicast_send_frag_skb(struct sk_buff *skb, struct bat_priv *bat_priv, frag_skb = dev_alloc_skb(data_len - (data_len / 2) + hdr_len); skb_split(skb, frag_skb, data_len / 2);
- if (my_skb_push(frag_skb, hdr_len) < 0 || - my_skb_push(skb, hdr_len) < 0) + if (my_skb_head_push(frag_skb, hdr_len) < 0 || + my_skb_head_push(skb, hdr_len) < 0) goto drop_frag;
ucast_frag1 = (struct unicast_frag_packet *)skb->data; @@ -245,7 +245,7 @@ int unicast_send_skb(struct sk_buff *skb, struct bat_priv *bat_priv) return unicast_send_frag_skb(skb, bat_priv, batman_if, dstaddr, orig_node);
- if (my_skb_push(skb, sizeof(struct unicast_packet)) < 0) + if (my_skb_head_push(skb, sizeof(struct unicast_packet)) < 0) goto dropped;
unicast_packet = (struct unicast_packet *)skb->data;
batman-adv tries to resend broadcasts on all interfaces up to three times. For each round and each interface it must provide a skb which gets consumed by the sending function.
It is unnecessary to copy the data of each broadcast because the actual data is either not shared or already copied by add_bcast_packet_to_list. So it is enough to just copy the skb control data
Reported-by: David S. Miller davem@davemloft.net Signed-off-by: Sven Eckelmann sven.eckelmann@gmx.de --- batman-adv/send.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/batman-adv/send.c b/batman-adv/send.c index fb0bfbf..524c0a9 100644 --- a/batman-adv/send.c +++ b/batman-adv/send.c @@ -477,7 +477,7 @@ static void send_outstanding_bcast_packet(struct work_struct *work) rcu_read_lock(); list_for_each_entry_rcu(batman_if, &if_list, list) { /* send a copy of the saved skb */ - skb1 = skb_copy(forw_packet->skb, GFP_ATOMIC); + skb1 = skb_clone(forw_packet->skb, GFP_ATOMIC); if (skb1) send_skb_packet(skb1, batman_if, broadcast_addr);
All originator messages are send through aggregation buffers. Those buffers can directly be allocated as skb to reduce the cost of allocation an extra buffer and copying them to a new allocated skb directly before it gets send.
Now only the skb must be cloned in case of send_packet_to_if as it gets called by send_packet for each interface. Non-primary ogms must not cloned at all because they will only send once and the forward_packet structure is freed by send_outstanding_bat_packet afterwards.
Reported-by: David S. Miller davem@davemloft.net Signed-off-by: Sven Eckelmann sven.eckelmann@gmx.de --- batman-adv/aggregation.c | 22 ++++++++++++---------- batman-adv/send.c | 24 ++++++++++++------------ batman-adv/types.h | 1 - 3 files changed, 24 insertions(+), 23 deletions(-)
diff --git a/batman-adv/aggregation.c b/batman-adv/aggregation.c index d738b7a..5896cf2 100644 --- a/batman-adv/aggregation.c +++ b/batman-adv/aggregation.c @@ -39,7 +39,7 @@ static bool can_aggregate_with(struct batman_packet *new_batman_packet, struct forw_packet *forw_packet) { struct batman_packet *batman_packet = - (struct batman_packet *)forw_packet->packet_buff; + (struct batman_packet *)forw_packet->skb->data; int aggregated_bytes = forw_packet->packet_len + packet_len;
/** @@ -106,6 +106,7 @@ static void new_aggregated_packet(unsigned char *packet_buff, { struct forw_packet *forw_packet_aggr; unsigned long flags; + unsigned char *skb_buff; /* FIXME: each batman_if will be attached to a softif */ struct bat_priv *bat_priv = netdev_priv(soft_device);
@@ -125,23 +126,22 @@ static void new_aggregated_packet(unsigned char *packet_buff, return; }
- forw_packet_aggr->packet_buff = kmalloc(MAX_AGGREGATION_BYTES, - GFP_ATOMIC); - if (!forw_packet_aggr->packet_buff) { + forw_packet_aggr->skb = dev_alloc_skb(MAX_AGGREGATION_BYTES + + sizeof(struct ethhdr)); + if (!forw_packet_aggr->skb) { if (!own_packet) atomic_inc(&bat_priv->batman_queue_left); kfree(forw_packet_aggr); return; } + skb_reserve(forw_packet_aggr->skb, sizeof(struct ethhdr));
INIT_HLIST_NODE(&forw_packet_aggr->list);
+ skb_buff = skb_put(forw_packet_aggr->skb, packet_len); forw_packet_aggr->packet_len = packet_len; - memcpy(forw_packet_aggr->packet_buff, - packet_buff, - forw_packet_aggr->packet_len); + memcpy(skb_buff, packet_buff, packet_len);
- forw_packet_aggr->skb = NULL; forw_packet_aggr->own = own_packet; forw_packet_aggr->if_incoming = if_incoming; forw_packet_aggr->num_packets = 0; @@ -171,8 +171,10 @@ static void aggregate(struct forw_packet *forw_packet_aggr, int packet_len, bool direct_link) { - memcpy((forw_packet_aggr->packet_buff + forw_packet_aggr->packet_len), - packet_buff, packet_len); + unsigned char *skb_buff; + + skb_buff = skb_put(forw_packet_aggr->skb, packet_len); + memcpy(skb_buff, packet_buff, packet_len); forw_packet_aggr->packet_len += packet_len; forw_packet_aggr->num_packets++;
diff --git a/batman-adv/send.c b/batman-adv/send.c index 524c0a9..32d989b 100644 --- a/batman-adv/send.c +++ b/batman-adv/send.c @@ -132,14 +132,14 @@ static void send_packet_to_if(struct forw_packet *forw_packet, uint8_t packet_num; int16_t buff_pos; struct batman_packet *batman_packet; + struct sk_buff *skb;
if (batman_if->if_status != IF_ACTIVE) return;
packet_num = 0; buff_pos = 0; - batman_packet = (struct batman_packet *) - (forw_packet->packet_buff); + batman_packet = (struct batman_packet *)forw_packet->skb->data;
/* adjust all flags and log packets */ while (aggregated_packet(buff_pos, @@ -171,12 +171,13 @@ static void send_packet_to_if(struct forw_packet *forw_packet, (batman_packet->num_hna * ETH_ALEN); packet_num++; batman_packet = (struct batman_packet *) - (forw_packet->packet_buff + buff_pos); + (forw_packet->skb->data + buff_pos); }
- send_raw_packet(forw_packet->packet_buff, - forw_packet->packet_len, - batman_if, broadcast_addr); + /* create clone because function is called more than once */ + skb = skb_clone(forw_packet->skb, GFP_ATOMIC); + if (skb) + send_skb_packet(skb, batman_if, broadcast_addr); }
/* send a batman packet */ @@ -186,7 +187,7 @@ static void send_packet(struct forw_packet *forw_packet) struct bat_priv *bat_priv = netdev_priv(soft_device); struct batman_if *batman_if; struct batman_packet *batman_packet = - (struct batman_packet *)(forw_packet->packet_buff); + (struct batman_packet *)(forw_packet->skb->data); unsigned char directlink = (batman_packet->flags & DIRECTLINK ? 1 : 0);
if (!forw_packet->if_incoming) { @@ -212,10 +213,11 @@ static void send_packet(struct forw_packet *forw_packet) batman_packet->ttl, forw_packet->if_incoming->dev, forw_packet->if_incoming->addr_str);
- send_raw_packet(forw_packet->packet_buff, - forw_packet->packet_len, - forw_packet->if_incoming, + /* skb is only used once and than forw_packet is free'd */ + send_skb_packet(forw_packet->skb, forw_packet->if_incoming, broadcast_addr); + forw_packet->skb = NULL; + return; }
@@ -379,7 +381,6 @@ static void forw_packet_free(struct forw_packet *forw_packet) { if (forw_packet->skb) kfree_skb(forw_packet->skb); - kfree(forw_packet->packet_buff); kfree(forw_packet); }
@@ -438,7 +439,6 @@ int add_bcast_packet_to_list(struct sk_buff *skb) skb_reset_mac_header(skb);
forw_packet->skb = skb; - forw_packet->packet_buff = NULL;
/* how often did we send the bcast packet ? */ forw_packet->num_packets = 0; diff --git a/batman-adv/types.h b/batman-adv/types.h index 65c2aa4..73304ee 100644 --- a/batman-adv/types.h +++ b/batman-adv/types.h @@ -171,7 +171,6 @@ struct forw_packet { unsigned long send_time; uint8_t own; struct sk_buff *skb; - unsigned char *packet_buff; uint16_t packet_len; uint32_t direct_link_flags; uint8_t num_packets;
The vis information structure is used in a way that it can be transfered directly as packet. It still had to be copied into a skb because of an extra buffer used for the actual preparation of the data. This is unnecessary and can be replaced by a simple clone instead of an full copy before each send.
This makes also the send_raw_packet function obsolete.
Reported-by: David S. Miller davem@davemloft.net Signed-off-by: Sven Eckelmann sven.eckelmann@gmx.de --- batman-adv/send.c | 17 ----- batman-adv/send.h | 2 - batman-adv/vis.c | 206 ++++++++++++++++++++++++++++++++++------------------- batman-adv/vis.h | 2 +- 4 files changed, 133 insertions(+), 94 deletions(-)
diff --git a/batman-adv/send.c b/batman-adv/send.c index 32d989b..a348733 100644 --- a/batman-adv/send.c +++ b/batman-adv/send.c @@ -105,23 +105,6 @@ send_skb_err: return NET_XMIT_DROP; }
-/* sends a raw packet. */ -void send_raw_packet(unsigned char *pack_buff, int pack_buff_len, - struct batman_if *batman_if, uint8_t *dst_addr) -{ - struct sk_buff *skb; - char *data; - - skb = dev_alloc_skb(pack_buff_len + sizeof(struct ethhdr)); - if (!skb) - return; - data = skb_put(skb, pack_buff_len + sizeof(struct ethhdr)); - memcpy(data + sizeof(struct ethhdr), pack_buff, pack_buff_len); - /* pull back to the batman "network header" */ - skb_pull(skb, sizeof(struct ethhdr)); - send_skb_packet(skb, batman_if, dst_addr); -} - /* Send a packet to a given interface */ static void send_packet_to_if(struct forw_packet *forw_packet, struct batman_if *batman_if) diff --git a/batman-adv/send.h b/batman-adv/send.h index b64c627..0cfe027 100644 --- a/batman-adv/send.h +++ b/batman-adv/send.h @@ -27,8 +27,6 @@ int send_skb_packet(struct sk_buff *skb, struct batman_if *batman_if, uint8_t *dst_addr); -void send_raw_packet(unsigned char *pack_buff, int pack_buff_len, - struct batman_if *batman_if, uint8_t *dst_addr); void schedule_own_packet(struct batman_if *batman_if); void schedule_forward_packet(struct orig_node *orig_node, struct ethhdr *ethhdr, diff --git a/batman-adv/vis.c b/batman-adv/vis.c index 5c825b5..c225e27 100644 --- a/batman-adv/vis.c +++ b/batman-adv/vis.c @@ -44,6 +44,8 @@ _dummy > smallest_signed_int(_dummy); }) #define seq_after(x, y) seq_before(y, x)
+#define MAX_VIS_PACKET_SIZE 1000 + static struct hashtable_t *vis_hash; static DEFINE_SPINLOCK(vis_hash_lock); static DEFINE_SPINLOCK(recv_list_lock); @@ -66,6 +68,7 @@ static void free_info(struct kref *ref) kfree(entry); } spin_unlock_irqrestore(&recv_list_lock, flags); + kfree_skb(info->skb_packet); kfree(info); }
@@ -73,9 +76,12 @@ static void free_info(struct kref *ref) static int vis_info_cmp(void *data1, void *data2) { struct vis_info *d1, *d2; + struct vis_packet *p1, *p2; d1 = data1; d2 = data2; - return compare_orig(d1->packet.vis_orig, d2->packet.vis_orig); + p1 = (struct vis_packet *)d1->skb_packet->data; + p2 = (struct vis_packet *)d2->skb_packet->data; + return compare_orig(p1->vis_orig, p2->vis_orig); }
/* hash function to choose an entry in a hash table of given size */ @@ -83,11 +89,13 @@ static int vis_info_cmp(void *data1, void *data2) static int vis_info_choose(void *data, int size) { struct vis_info *vis_info = data; + struct vis_packet *packet; unsigned char *key; uint32_t hash = 0; size_t i;
- key = vis_info->packet.vis_orig; + packet = (struct vis_packet *)vis_info->skb_packet->data; + key = packet->vis_orig; for (i = 0; i < ETH_ALEN; i++) { hash += key[i]; hash += (hash << 10); @@ -180,6 +188,7 @@ int vis_seq_print_text(struct seq_file *seq, void *offset) HASHIT(hashit); HASHIT(hashit_count); struct vis_info *info; + struct vis_packet *packet; struct vis_info_entry *entries; struct net_device *net_dev = (struct net_device *)seq->private; struct bat_priv *bat_priv = netdev_priv(net_dev); @@ -202,22 +211,22 @@ int vis_seq_print_text(struct seq_file *seq, void *offset) spin_lock_irqsave(&vis_hash_lock, flags); while (hash_iterate(vis_hash, &hashit_count)) { info = hashit_count.bucket->data; + packet = (struct vis_packet *)info->skb_packet->data; entries = (struct vis_info_entry *) - ((char *)info + sizeof(struct vis_info)); + ((char *)packet + sizeof(struct vis_packet));
- for (i = 0; i < info->packet.entries; i++) { + for (i = 0; i < packet->entries; i++) { if (entries[i].quality == 0) continue; vis_data_insert_interface(entries[i].src, &vis_if_list, - compare_orig(entries[i].src, - info->packet.vis_orig)); + compare_orig(entries[i].src, packet->vis_orig)); }
hlist_for_each_entry(entry, pos, &vis_if_list, list) { - buf_size += 18 + 26 * info->packet.entries; + buf_size += 18 + 26 * packet->entries;
/* add primary/secondary records */ - if (compare_orig(entry->addr, info->packet.vis_orig)) + if (compare_orig(entry->addr, packet->vis_orig)) buf_size += vis_data_count_prim_sec(&vis_if_list);
@@ -240,15 +249,15 @@ int vis_seq_print_text(struct seq_file *seq, void *offset)
while (hash_iterate(vis_hash, &hashit)) { info = hashit.bucket->data; + packet = (struct vis_packet *)info->skb_packet->data; entries = (struct vis_info_entry *) - ((char *)info + sizeof(struct vis_info)); + ((char *)packet + sizeof(struct vis_packet));
- for (i = 0; i < info->packet.entries; i++) { + for (i = 0; i < packet->entries; i++) { if (entries[i].quality == 0) continue; vis_data_insert_interface(entries[i].src, &vis_if_list, - compare_orig(entries[i].src, - info->packet.vis_orig)); + compare_orig(entries[i].src, packet->vis_orig)); }
hlist_for_each_entry(entry, pos, &vis_if_list, list) { @@ -256,14 +265,14 @@ int vis_seq_print_text(struct seq_file *seq, void *offset) buff_pos += sprintf(buff + buff_pos, "%s,", tmp_addr_str);
- for (i = 0; i < info->packet.entries; i++) + for (i = 0; i < packet->entries; i++) buff_pos += vis_data_read_entry(buff + buff_pos, &entries[i], entry->addr, entry->primary);
/* add primary/secondary records */ - if (compare_orig(entry->addr, info->packet.vis_orig)) + if (compare_orig(entry->addr, packet->vis_orig)) buff_pos += vis_data_read_prim_sec(buff + buff_pos, &vis_if_list); @@ -346,7 +355,9 @@ static struct vis_info *add_packet(struct vis_packet *vis_packet, int make_broadcast) { struct vis_info *info, *old_info; + struct vis_packet *search_packet, *old_packet; struct vis_info search_elem; + struct vis_packet *packet;
*is_new = 0; /* sanity check */ @@ -354,13 +365,21 @@ static struct vis_info *add_packet(struct vis_packet *vis_packet, return NULL;
/* see if the packet is already in vis_hash */ - memcpy(search_elem.packet.vis_orig, vis_packet->vis_orig, ETH_ALEN); + search_elem.skb_packet = dev_alloc_skb(sizeof(struct vis_packet)); + if (!search_elem.skb_packet) + return NULL; + search_packet = (struct vis_packet *)skb_put(search_elem.skb_packet, + sizeof(struct vis_packet)); + + memcpy(search_packet->vis_orig, vis_packet->vis_orig, ETH_ALEN); old_info = hash_find(vis_hash, &search_elem); + kfree_skb(search_elem.skb_packet);
if (old_info != NULL) { + old_packet = (struct vis_packet *)old_info->skb_packet->data; if (!seq_after(ntohl(vis_packet->seqno), - ntohl(old_info->packet.seqno))) { - if (old_info->packet.seqno == vis_packet->seqno) { + ntohl(old_packet->seqno))) { + if (old_packet->seqno == vis_packet->seqno) { recv_list_add(&old_info->recv_list, vis_packet->sender_orig); return old_info; @@ -375,30 +394,39 @@ static struct vis_info *add_packet(struct vis_packet *vis_packet, kref_put(&old_info->refcount, free_info); }
- info = kmalloc(sizeof(struct vis_info) + vis_info_len, GFP_ATOMIC); + info = kmalloc(sizeof(struct vis_info), GFP_ATOMIC); if (info == NULL) return NULL;
+ info->skb_packet = dev_alloc_skb(sizeof(struct vis_packet) + + vis_info_len + sizeof(struct ethhdr)); + if (!info->skb_packet) { + kfree(info); + return NULL; + } + skb_reserve(info->skb_packet, sizeof(struct ethhdr)); + packet = (struct vis_packet *)skb_put(info->skb_packet, + sizeof(struct vis_packet) + + vis_info_len); + kref_init(&info->refcount); INIT_LIST_HEAD(&info->send_list); INIT_LIST_HEAD(&info->recv_list); info->first_seen = jiffies; - memcpy(&info->packet, vis_packet, - sizeof(struct vis_packet) + vis_info_len); + memcpy(packet, vis_packet, sizeof(struct vis_packet) + vis_info_len);
/* initialize and add new packet. */ *is_new = 1;
/* Make it a broadcast packet, if required */ if (make_broadcast) - memcpy(info->packet.target_orig, broadcast_addr, ETH_ALEN); + memcpy(packet->target_orig, broadcast_addr, ETH_ALEN);
/* repair if entries is longer than packet. */ - if (info->packet.entries * sizeof(struct vis_info_entry) > vis_info_len) - info->packet.entries = vis_info_len / - sizeof(struct vis_info_entry); + if (packet->entries * sizeof(struct vis_info_entry) > vis_info_len) + packet->entries = vis_info_len / sizeof(struct vis_info_entry);
- recv_list_add(&info->recv_list, info->packet.sender_orig); + recv_list_add(&info->recv_list, packet->sender_orig);
/* try to add it */ if (hash_add(vis_hash, info) < 0) { @@ -441,6 +469,7 @@ void receive_client_update_packet(struct bat_priv *bat_priv, int vis_info_len) { struct vis_info *info; + struct vis_packet *packet; int is_new; unsigned long flags; int vis_server = atomic_read(&bat_priv->vis_mode); @@ -457,20 +486,23 @@ void receive_client_update_packet(struct bat_priv *bat_priv,
spin_lock_irqsave(&vis_hash_lock, flags); info = add_packet(vis_packet, vis_info_len, &is_new, are_target); + if (info == NULL) goto end; /* note that outdated packets will be dropped at this point. */
+ packet = (struct vis_packet *)info->skb_packet->data;
/* send only if we're the target server or ... */ if (are_target && is_new) { - info->packet.vis_type = VIS_TYPE_SERVER_SYNC; /* upgrade! */ + packet->vis_type = VIS_TYPE_SERVER_SYNC; /* upgrade! */ send_list_add(info);
/* ... we're not the recipient (and thus need to forward). */ - } else if (!is_my_mac(info->packet.target_orig)) { + } else if (!is_my_mac(packet->target_orig)) { send_list_add(info); } + end: spin_unlock_irqrestore(&vis_hash_lock, flags); } @@ -483,8 +515,11 @@ static int find_best_vis_server(struct vis_info *info) { HASHIT(hashit); struct orig_node *orig_node; + struct vis_packet *packet; int best_tq = -1;
+ packet = (struct vis_packet *)info->skb_packet->data; + while (hash_iterate(orig_hash, &hashit)) { orig_node = hashit.bucket->data; if ((orig_node != NULL) && @@ -492,8 +527,7 @@ static int find_best_vis_server(struct vis_info *info) (orig_node->flags & VIS_SERVER) && (orig_node->router->tq_avg > best_tq)) { best_tq = orig_node->router->tq_avg; - memcpy(info->packet.target_orig, orig_node->orig, - ETH_ALEN); + memcpy(packet->target_orig, orig_node->orig, ETH_ALEN); } } return best_tq; @@ -502,8 +536,11 @@ static int find_best_vis_server(struct vis_info *info) /* Return true if the vis packet is full. */ static bool vis_packet_full(struct vis_info *info) { - if (info->packet.entries + 1 > - (1000 - sizeof(struct vis_info)) / sizeof(struct vis_info_entry)) + struct vis_packet *packet; + packet = (struct vis_packet *)info->skb_packet->data; + + if (MAX_VIS_PACKET_SIZE / sizeof(struct vis_info_entry) + < packet->entries + 1) return true; return false; } @@ -516,21 +553,23 @@ static int generate_vis_packet(struct bat_priv *bat_priv) HASHIT(hashit_global); struct orig_node *orig_node; struct vis_info *info = (struct vis_info *)my_vis_info; - struct vis_info_entry *entry, *entry_array; + struct vis_packet *packet = (struct vis_packet *)info->skb_packet->data; + struct vis_info_entry *entry; struct hna_local_entry *hna_local_entry; int best_tq = -1; unsigned long flags;
info->first_seen = jiffies; - info->packet.vis_type = atomic_read(&bat_priv->vis_mode); + packet->vis_type = atomic_read(&bat_priv->vis_mode);
spin_lock_irqsave(&orig_hash_lock, flags); - memcpy(info->packet.target_orig, broadcast_addr, ETH_ALEN); - info->packet.ttl = TTL; - info->packet.seqno = htonl(ntohl(info->packet.seqno) + 1); - info->packet.entries = 0; + memcpy(packet->target_orig, broadcast_addr, ETH_ALEN); + packet->ttl = TTL; + packet->seqno = htonl(ntohl(packet->seqno) + 1); + packet->entries = 0; + skb_trim(info->skb_packet, sizeof(struct vis_packet));
- if (info->packet.vis_type == VIS_TYPE_CLIENT_UPDATE) { + if (packet->vis_type == VIS_TYPE_CLIENT_UPDATE) { best_tq = find_best_vis_server(info); if (best_tq < 0) { spin_unlock_irqrestore(&orig_hash_lock, flags); @@ -538,9 +577,6 @@ static int generate_vis_packet(struct bat_priv *bat_priv) } }
- entry_array = (struct vis_info_entry *) - ((char *)info + sizeof(struct vis_info)); - while (hash_iterate(orig_hash, &hashit_global)) { orig_node = hashit_global.bucket->data; if (orig_node->router != NULL @@ -551,13 +587,14 @@ static int generate_vis_packet(struct bat_priv *bat_priv) && orig_node->router->tq_avg > 0) {
/* fill one entry into buffer. */ - entry = &entry_array[info->packet.entries]; + entry = (struct vis_info_entry *) + skb_put(info->skb_packet, sizeof(*entry)); memcpy(entry->src, orig_node->router->if_incoming->net_dev->dev_addr, ETH_ALEN); memcpy(entry->dest, orig_node->orig, ETH_ALEN); entry->quality = orig_node->router->tq_avg; - info->packet.entries++; + packet->entries++;
if (vis_packet_full(info)) { spin_unlock_irqrestore(&orig_hash_lock, flags); @@ -571,11 +608,12 @@ static int generate_vis_packet(struct bat_priv *bat_priv) spin_lock_irqsave(&hna_local_hash_lock, flags); while (hash_iterate(hna_local_hash, &hashit_local)) { hna_local_entry = hashit_local.bucket->data; - entry = &entry_array[info->packet.entries]; + entry = (struct vis_info_entry *)skb_put(info->skb_packet, + sizeof(*entry)); memset(entry->src, 0, ETH_ALEN); memcpy(entry->dest, hna_local_entry->addr, ETH_ALEN); entry->quality = 0; /* 0 means HNA */ - info->packet.entries++; + packet->entries++;
if (vis_packet_full(info)) { spin_unlock_irqrestore(&hna_local_hash_lock, flags); @@ -606,15 +644,18 @@ static void purge_vis_packets(void) } }
-static void broadcast_vis_packet(struct vis_info *info, int packet_length) +static void broadcast_vis_packet(struct vis_info *info) { HASHIT(hashit); struct orig_node *orig_node; + struct vis_packet *packet; + struct sk_buff *skb; unsigned long flags; struct batman_if *batman_if; uint8_t dstaddr[ETH_ALEN];
spin_lock_irqsave(&orig_hash_lock, flags); + packet = (struct vis_packet *)info->skb_packet->data;
/* send to all routers in range. */ while (hash_iterate(orig_hash, &hashit)) { @@ -630,31 +671,35 @@ static void broadcast_vis_packet(struct vis_info *info, int packet_length) if (recv_list_is_in(&info->recv_list, orig_node->orig)) continue;
- memcpy(info->packet.target_orig, orig_node->orig, ETH_ALEN); + memcpy(packet->target_orig, orig_node->orig, ETH_ALEN); batman_if = orig_node->router->if_incoming; memcpy(dstaddr, orig_node->router->addr, ETH_ALEN); spin_unlock_irqrestore(&orig_hash_lock, flags);
- send_raw_packet((unsigned char *)&info->packet, - packet_length, batman_if, dstaddr); + skb = skb_clone(info->skb_packet, GFP_ATOMIC); + if (skb) + send_skb_packet(skb, batman_if, dstaddr);
spin_lock_irqsave(&orig_hash_lock, flags);
} spin_unlock_irqrestore(&orig_hash_lock, flags); - memcpy(info->packet.target_orig, broadcast_addr, ETH_ALEN); + memcpy(packet->target_orig, broadcast_addr, ETH_ALEN); }
-static void unicast_vis_packet(struct vis_info *info, int packet_length) +static void unicast_vis_packet(struct vis_info *info) { struct orig_node *orig_node; + struct sk_buff *skb; + struct vis_packet *packet; unsigned long flags; struct batman_if *batman_if; uint8_t dstaddr[ETH_ALEN];
spin_lock_irqsave(&orig_hash_lock, flags); - orig_node = ((struct orig_node *) - hash_find(orig_hash, info->packet.target_orig)); + packet = (struct vis_packet *)info->skb_packet->data; + orig_node = ((struct orig_node *)hash_find(orig_hash, + packet->target_orig));
if ((!orig_node) || (!orig_node->router)) goto out; @@ -665,8 +710,10 @@ static void unicast_vis_packet(struct vis_info *info, int packet_length) memcpy(dstaddr, orig_node->router->addr, ETH_ALEN); spin_unlock_irqrestore(&orig_hash_lock, flags);
- send_raw_packet((unsigned char *)&info->packet, - packet_length, batman_if, dstaddr); + skb = skb_clone(info->skb_packet, GFP_ATOMIC); + if (skb) + send_skb_packet(skb, batman_if, dstaddr); + return;
out: @@ -676,24 +723,22 @@ out: /* only send one vis packet. called from send_vis_packets() */ static void send_vis_packet(struct vis_info *info) { - int packet_length; + struct vis_packet *packet;
- if (info->packet.ttl < 2) { + packet = (struct vis_packet *)info->skb_packet->data; + if (packet->ttl < 2) { pr_warning("Error - can't send vis packet: ttl exceeded\n"); return; }
- memcpy(info->packet.sender_orig, main_if_addr, ETH_ALEN); - info->packet.ttl--; + memcpy(packet->sender_orig, main_if_addr, ETH_ALEN); + packet->ttl--;
- packet_length = sizeof(struct vis_packet) + - info->packet.entries * sizeof(struct vis_info_entry); - - if (is_bcast(info->packet.target_orig)) - broadcast_vis_packet(info, packet_length); + if (is_bcast(packet->target_orig)) + broadcast_vis_packet(info); else - unicast_vis_packet(info, packet_length); - info->packet.ttl++; /* restore TTL */ + unicast_vis_packet(info); + packet->ttl++; /* restore TTL */ }
/* called from timer; send (and maybe generate) vis packet. */ @@ -733,6 +778,7 @@ static DECLARE_DELAYED_WORK(vis_timer_wq, send_vis_packets); * initialized (e.g. bat0 is initialized, interfaces have been added) */ int vis_init(void) { + struct vis_packet *packet; unsigned long flags; if (vis_hash) return 1; @@ -745,27 +791,36 @@ int vis_init(void) goto err; }
- my_vis_info = kmalloc(1000, GFP_ATOMIC); + my_vis_info = kmalloc(MAX_VIS_PACKET_SIZE, GFP_ATOMIC); if (!my_vis_info) { pr_err("Can't initialize vis packet\n"); goto err; }
+ my_vis_info->skb_packet = dev_alloc_skb(sizeof(struct vis_packet) + + MAX_VIS_PACKET_SIZE + + sizeof(struct ethhdr)); + if (!my_vis_info->skb_packet) + goto free_info; + skb_reserve(my_vis_info->skb_packet, sizeof(struct ethhdr)); + packet = (struct vis_packet *)skb_put(my_vis_info->skb_packet, + sizeof(struct vis_packet)); + /* prefill the vis info */ my_vis_info->first_seen = jiffies - msecs_to_jiffies(VIS_INTERVAL); INIT_LIST_HEAD(&my_vis_info->recv_list); INIT_LIST_HEAD(&my_vis_info->send_list); kref_init(&my_vis_info->refcount); - my_vis_info->packet.version = COMPAT_VERSION; - my_vis_info->packet.packet_type = BAT_VIS; - my_vis_info->packet.ttl = TTL; - my_vis_info->packet.seqno = 0; - my_vis_info->packet.entries = 0; + packet->version = COMPAT_VERSION; + packet->packet_type = BAT_VIS; + packet->ttl = TTL; + packet->seqno = 0; + packet->entries = 0;
INIT_LIST_HEAD(&send_list);
- memcpy(my_vis_info->packet.vis_orig, main_if_addr, ETH_ALEN); - memcpy(my_vis_info->packet.sender_orig, main_if_addr, ETH_ALEN); + memcpy(packet->vis_orig, main_if_addr, ETH_ALEN); + memcpy(packet->sender_orig, main_if_addr, ETH_ALEN);
if (hash_add(vis_hash, my_vis_info) < 0) { pr_err("Can't add own vis packet into hash\n"); @@ -778,6 +833,9 @@ int vis_init(void) start_vis_timer(); return 1;
+free_info: + kfree(my_vis_info); + my_vis_info = NULL; err: spin_unlock_irqrestore(&vis_hash_lock, flags); vis_quit(); diff --git a/batman-adv/vis.h b/batman-adv/vis.h index bb13bf1..19dc325 100644 --- a/batman-adv/vis.h +++ b/batman-adv/vis.h @@ -32,7 +32,7 @@ struct vis_info { struct list_head send_list; struct kref refcount; /* this packet might be part of the vis send queue. */ - struct vis_packet packet; + struct sk_buff *skb_packet; /* vis_info may follow here*/ } __attribute__((packed));
We can use skb_cow instead of a handwritten function to test and create a writable skb buffer. This also allows us to pre-allocate headroom to be able to send the data without re-allocating the buffer again to add the ethernet header.
Signed-off-by: Sven Eckelmann sven.eckelmann@gmx.de --- batman-adv/routing.c | 77 +++++++++++++++---------------------------------- 1 files changed, 24 insertions(+), 53 deletions(-)
diff --git a/batman-adv/routing.c b/batman-adv/routing.c index 2764122..7667c55 100644 --- a/batman-adv/routing.c +++ b/batman-adv/routing.c @@ -754,7 +754,6 @@ int recv_bat_packet(struct sk_buff *skb, { struct ethhdr *ethhdr; unsigned long flags; - struct sk_buff *skb_old;
/* drop packet if it has not necessary minimum size */ if (skb_headlen(skb) < sizeof(struct batman_packet)) @@ -770,18 +769,11 @@ int recv_bat_packet(struct sk_buff *skb, if (is_bcast(ethhdr->h_source)) return NET_RX_DROP;
- /* TODO: we use headlen instead of "length", because - * only this data is paged in. */ - /* create a copy of the skb, if needed, to modify it. */ - if (!skb_clone_writable(skb, skb_headlen(skb))) { - skb_old = skb; - skb = skb_copy(skb, GFP_ATOMIC); - if (!skb) - return NET_RX_DROP; - ethhdr = (struct ethhdr *)skb_mac_header(skb); - kfree_skb(skb_old); - } + if (skb_cow(skb, 0) < 0) + return NET_RX_DROP; + + ethhdr = (struct ethhdr *)skb_mac_header(skb);
spin_lock_irqsave(&orig_hash_lock, flags); receive_aggr_bat_packet(ethhdr, @@ -801,7 +793,6 @@ static int recv_my_icmp_packet(struct sk_buff *skb, size_t icmp_len) struct orig_node *orig_node; struct icmp_packet_rr *icmp_packet; struct ethhdr *ethhdr; - struct sk_buff *skb_old; struct batman_if *batman_if; int ret; unsigned long flags; @@ -836,16 +827,11 @@ static int recv_my_icmp_packet(struct sk_buff *skb, size_t icmp_len) spin_unlock_irqrestore(&orig_hash_lock, flags);
/* create a copy of the skb, if needed, to modify it. */ - skb_old = NULL; - if (!skb_clone_writable(skb, icmp_len)) { - skb_old = skb; - skb = skb_copy(skb, GFP_ATOMIC); - if (!skb) - return NET_RX_DROP; - icmp_packet = (struct icmp_packet_rr *)skb->data; - ethhdr = (struct ethhdr *)skb_mac_header(skb); - kfree_skb(skb_old); - } + if (skb_cow(skb, sizeof(struct ethhdr)) < 0) + return NET_RX_DROP; + + icmp_packet = (struct icmp_packet_rr *)skb->data; + ethhdr = (struct ethhdr *)skb_mac_header(skb);
memcpy(icmp_packet->dst, icmp_packet->orig, ETH_ALEN); memcpy(icmp_packet->orig, @@ -869,7 +855,6 @@ static int recv_icmp_ttl_exceeded(struct sk_buff *skb, size_t icmp_len) struct orig_node *orig_node; struct icmp_packet *icmp_packet; struct ethhdr *ethhdr; - struct sk_buff *skb_old; struct batman_if *batman_if; int ret; unsigned long flags; @@ -905,15 +890,11 @@ static int recv_icmp_ttl_exceeded(struct sk_buff *skb, size_t icmp_len) spin_unlock_irqrestore(&orig_hash_lock, flags);
/* create a copy of the skb, if needed, to modify it. */ - if (!skb_clone_writable(skb, icmp_len)) { - skb_old = skb; - skb = skb_copy(skb, GFP_ATOMIC); - if (!skb) - return NET_RX_DROP; - icmp_packet = (struct icmp_packet *) skb->data; - ethhdr = (struct ethhdr *)skb_mac_header(skb); - kfree_skb(skb_old); - } + if (skb_cow(skb, sizeof(struct ethhdr)) < 0) + return NET_RX_DROP; + + icmp_packet = (struct icmp_packet *) skb->data; + ethhdr = (struct ethhdr *)skb_mac_header(skb);
memcpy(icmp_packet->dst, icmp_packet->orig, ETH_ALEN); memcpy(icmp_packet->orig, @@ -936,7 +917,6 @@ int recv_icmp_packet(struct sk_buff *skb) struct icmp_packet_rr *icmp_packet; struct ethhdr *ethhdr; struct orig_node *orig_node; - struct sk_buff *skb_old; struct batman_if *batman_if; int hdr_size = sizeof(struct icmp_packet); int ret; @@ -1002,15 +982,11 @@ int recv_icmp_packet(struct sk_buff *skb) spin_unlock_irqrestore(&orig_hash_lock, flags);
/* create a copy of the skb, if needed, to modify it. */ - if (!skb_clone_writable(skb, hdr_size)) { - skb_old = skb; - skb = skb_copy(skb, GFP_ATOMIC); - if (!skb) - return NET_RX_DROP; - icmp_packet = (struct icmp_packet_rr *)skb->data; - ethhdr = (struct ethhdr *)skb_mac_header(skb); - kfree_skb(skb_old); - } + if (skb_cow(skb, sizeof(struct ethhdr)) < 0) + return NET_RX_DROP; + + icmp_packet = (struct icmp_packet_rr *)skb->data; + ethhdr = (struct ethhdr *)skb_mac_header(skb);
/* decrement ttl */ icmp_packet->ttl--; @@ -1149,7 +1125,6 @@ static int route_unicast_packet(struct sk_buff *skb, struct batman_if *recv_if, struct orig_node *orig_node; struct neigh_node *router; struct batman_if *batman_if; - struct sk_buff *skb_old; uint8_t dstaddr[ETH_ALEN]; unsigned long flags; struct unicast_packet *unicast_packet = @@ -1185,15 +1160,11 @@ static int route_unicast_packet(struct sk_buff *skb, struct batman_if *recv_if, spin_unlock_irqrestore(&orig_hash_lock, flags);
/* create a copy of the skb, if needed, to modify it. */ - if (!skb_clone_writable(skb, hdr_size)) { - skb_old = skb; - skb = skb_copy(skb, GFP_ATOMIC); - if (!skb) - return NET_RX_DROP; - unicast_packet = (struct unicast_packet *) skb->data; - ethhdr = (struct ethhdr *)skb_mac_header(skb); - kfree_skb(skb_old); - } + if (skb_cow(skb, sizeof(struct ethhdr)) < 0) + return NET_RX_DROP; + + unicast_packet = (struct unicast_packet *) skb->data; + ethhdr = (struct ethhdr *)skb_mac_header(skb);
/* decrement ttl */ unicast_packet->ttl--;
We must ensure that all interesting data is linear and not paged out to access all information in a header or a full batman-adv related packet. Otherwise we may drop packets which have non-linear headers but which hold valid data.
This doesn't affect non-linear skbs which have all headers in a linear head unless we must process the whole packet like in ogms or vis packets.
Signed-off-by: Sven Eckelmann sven.eckelmann@gmx.de --- batman-adv/hard-interface.c | 2 +- batman-adv/routing.c | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/batman-adv/hard-interface.c b/batman-adv/hard-interface.c index fdabe4f..23a2595 100644 --- a/batman-adv/hard-interface.c +++ b/batman-adv/hard-interface.c @@ -490,7 +490,7 @@ int batman_skb_recv(struct sk_buff *skb, struct net_device *dev, goto err_out;
/* packet should hold at least type and version */ - if (unlikely(skb_headlen(skb) < 2)) + if (unlikely(!pskb_may_pull(skb, 2))) goto err_free;
/* expect a valid ethernet header here. */ diff --git a/batman-adv/routing.c b/batman-adv/routing.c index 7667c55..8f3b6fb 100644 --- a/batman-adv/routing.c +++ b/batman-adv/routing.c @@ -756,7 +756,7 @@ int recv_bat_packet(struct sk_buff *skb, unsigned long flags;
/* drop packet if it has not necessary minimum size */ - if (skb_headlen(skb) < sizeof(struct batman_packet)) + if (unlikely(!pskb_may_pull(skb, sizeof(struct batman_packet)))) return NET_RX_DROP;
ethhdr = (struct ethhdr *)skb_mac_header(skb); @@ -773,6 +773,10 @@ int recv_bat_packet(struct sk_buff *skb, if (skb_cow(skb, 0) < 0) return NET_RX_DROP;
+ /* keep skb linear */ + if (skb_linearize(skb) < 0) + return NET_RX_DROP; + ethhdr = (struct ethhdr *)skb_mac_header(skb);
spin_lock_irqsave(&orig_hash_lock, flags); @@ -926,11 +930,11 @@ int recv_icmp_packet(struct sk_buff *skb) /** * we truncate all incoming icmp packets if they don't match our size */ - if (skb_headlen(skb) >= sizeof(struct icmp_packet_rr)) + if (skb->len >= sizeof(struct icmp_packet_rr)) hdr_size = sizeof(struct icmp_packet_rr);
/* drop packet if it has not necessary minimum size */ - if (skb_headlen(skb) < hdr_size) + if (unlikely(!pskb_may_pull(skb, hdr_size))) return NET_RX_DROP;
ethhdr = (struct ethhdr *)skb_mac_header(skb); @@ -1099,7 +1103,7 @@ static int check_unicast_packet(struct sk_buff *skb, int hdr_size) struct ethhdr *ethhdr;
/* drop packet if it has not necessary minimum size */ - if (skb_headlen(skb) < hdr_size) + if (unlikely(!pskb_may_pull(skb, hdr_size))) return -1;
ethhdr = (struct ethhdr *) skb_mac_header(skb); @@ -1259,7 +1263,7 @@ int recv_bcast_packet(struct sk_buff *skb) unsigned long flags;
/* drop packet if it has not necessary minimum size */ - if (skb_headlen(skb) < hdr_size) + if (unlikely(!pskb_may_pull(skb, hdr_size))) return NET_RX_DROP;
ethhdr = (struct ethhdr *)skb_mac_header(skb); @@ -1332,7 +1336,11 @@ int recv_vis_packet(struct sk_buff *skb) struct bat_priv *bat_priv; int hdr_size = sizeof(struct vis_packet);
- if (skb_headlen(skb) < hdr_size) + /* keep skb linear */ + if (skb_linearize(skb) < 0) + return NET_RX_DROP; + + if (unlikely(!pskb_may_pull(skb, hdr_size))) return NET_RX_DROP;
vis_packet = (struct vis_packet *) skb->data; @@ -1354,13 +1362,11 @@ int recv_vis_packet(struct sk_buff *skb)
switch (vis_packet->vis_type) { case VIS_TYPE_SERVER_SYNC: - /* TODO: handle fragmented skbs properly */ receive_server_sync_packet(bat_priv, vis_packet, skb_headlen(skb)); break;
case VIS_TYPE_CLIENT_UPDATE: - /* TODO: handle fragmented skbs properly */ receive_client_update_packet(bat_priv, vis_packet, skb_headlen(skb)); break;
gw_is_target tries to access the data in a udp header without checking if there is enough data available inside the linear skb head.
Signed-off-by: Sven Eckelmann sven.eckelmann@gmx.de --- batman-adv/gateway_client.c | 26 ++++++++++++++++++++++---- 1 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/batman-adv/gateway_client.c b/batman-adv/gateway_client.c index f50bc41..2ab8b6b 100644 --- a/batman-adv/gateway_client.c +++ b/batman-adv/gateway_client.c @@ -399,6 +399,7 @@ bool gw_is_target(struct bat_priv *bat_priv, struct sk_buff *skb) struct ethhdr *ethhdr; struct iphdr *iphdr; struct udphdr *udphdr; + unsigned int header_len = 0;
if (atomic_read(&bat_priv->gw_mode) != GW_MODE_CLIENT) return false; @@ -406,22 +407,39 @@ bool gw_is_target(struct bat_priv *bat_priv, struct sk_buff *skb) if (!curr_gateway) return false;
+ /* check for ethernet header */ + if (!pskb_may_pull(skb, header_len + ETH_HLEN)) + return false; ethhdr = (struct ethhdr *)skb->data; + header_len += ETH_HLEN;
- if (ntohs(ethhdr->h_proto) == ETH_P_8021Q) + /* check for initial vlan header */ + if (ntohs(ethhdr->h_proto) == ETH_P_8021Q) { + if (!pskb_may_pull(skb, header_len + VLAN_HLEN)) + return false; ethhdr = (struct ethhdr *)(skb->data + VLAN_HLEN); + header_len += VLAN_HLEN; + }
+ /* check for ip header */ if (ntohs(ethhdr->h_proto) != ETH_P_IP) return false;
- iphdr = (struct iphdr *)(((unsigned char *)ethhdr) + ETH_HLEN); + if (!pskb_may_pull(skb, header_len + sizeof(struct iphdr))) + return false; + iphdr = (struct iphdr *)(skb->data + header_len); + header_len += iphdr->ihl * 4;
+ /* check for udp header */ if (iphdr->protocol != IPPROTO_UDP) return false;
- udphdr = (struct udphdr *)(((unsigned char *)iphdr) + - (iphdr->ihl * 4)); + if (!pskb_may_pull(skb, header_len + sizeof(struct udphdr))) + return false; + udphdr = (struct udphdr *)(skb->data + header_len); + header_len += sizeof(struct udphdr);
+ /* check for bootp port */ if (ntohs(udphdr->dest) != 67) return false;
Hi,
David S. Miller found some problems in our current codebase which must be fixed before it can be submitted for inclusion into the mainline kernel under net/.
This it the second version of those patches which adds some new ones and corrects some smaller things in the commit messages.
I committed all of them (revision 1757-1763).
Thanks, Marek
b.a.t.m.a.n@lists.open-mesh.org