/** * BATADV_TP_DEF_TEST_LENGTH - Default test length if not specified by the user * in milliseconds
*/ #define BATADV_TP_DEF_TEST_LENGTH 10000
/** * BATADV_TP_AWND - Advertised window by the receiver (in bytes)
*/ #define BATADV_TP_AWND 0x20000000
/** * BATADV_TP_RECV_TIMEOUT - Receiver activity timeout. If the receiver does not * get anything for such amount of milliseconds, the connection is killed
*/ #define BATADV_TP_RECV_TIMEOUT 1000
/** * BATADV_TP_MAX_RTO - Maximum sender timeout. If the sender RTO gets beyond * such amount of milliseconds, the receiver is considered unreachable and the * connection is killed
*/ #define BATADV_TP_MAX_RTO 30000
/** * BATADV_TP_FIRST_SEQ - First seqno of each session. The number is rather high * in order to immediately trigger a wrap around (test purposes)
*/ #define BATADV_TP_FIRST_SEQ ((u32)-1 - 2000)
/** * BATADV_TP_PLEN - length of the payload (data after the batadv_unicast header) * to simulate
*/ #define BATADV_TP_PLEN (BATADV_TP_PACKET_LEN - ETH_HLEN - \ sizeof(struct batadv_unicast_packet))
/** * batadv_tp_cwnd() - compute the new cwnd size * @base: base cwnd size value * @increment: the value to add to base to get the new size * @min: minimum cwnd value (usually MSS) * * Return the new cwnd size and ensure it does not exceed the Advertised * Receiver Window size. It is wrapped around safely. * For details refer to Section 3.1 of RFC5681 * * Return: new congestion window size in bytes
*/ static u32 batadv_tp_cwnd(u32 base, u32 increment, u32 min)
{
u32 new_size = base + increment;
/* check for wrap-around */ if (new_size < base)
new_size = (u32)ULONG_MAX;
new_size = min_t(u32, new_size, BATADV_TP_AWND);
return max_t(u32, new_size, min);
}
/** * batadv_tp_update_cwnd() - update the Congestion Windows * @tp_vars: the private data of the current TP meter session * @mss: maximum segment size of transmission * * 1) if the session is in Slow Start, the CWND has to be increased by 1 * MSS every unique received ACK * 2) if the session is in Congestion Avoidance, the CWND has to be * increased by MSS * MSS / CWND for every unique received ACK
*/ staticvoid batadv_tp_update_cwnd(struct batadv_tp_vars *tp_vars, u32 mss)
{
spin_lock_bh(&tp_vars->cwnd_lock);
/** * batadv_tp_update_rto() - calculate new retransmission timeout * @tp_vars: the private data of the current TP meter session * @new_rtt: new roundtrip time in msec
*/ staticvoid batadv_tp_update_rto(struct batadv_tp_vars *tp_vars,
u32 new_rtt)
{ long m = new_rtt;
/* RTT update * Details in Section 2.2 and 2.3 of RFC6298 * * It's tricky to understand. Don't lose hair please. * Inspired by tcp_rtt_estimator() tcp_input.c
*/ if (tp_vars->srtt != 0) {
m -= (tp_vars->srtt >> 3); /* m is now error in rtt est */
tp_vars->srtt += m; /* rtt = 7/8 srtt + 1/8 new */ if (m < 0)
m = -m;
m -= (tp_vars->rttvar >> 2);
tp_vars->rttvar += m; /* mdev ~= 3/4 rttvar + 1/4 new */
} else { /* first measure getting in */
tp_vars->srtt = m << 3; /* take the measured time to be srtt */
tp_vars->rttvar = m << 1; /* new_rtt / 2 */
}
/* rto = srtt + 4 * rttvar. * rttvar is scaled by 4, therefore doesn't need to be multiplied
*/
tp_vars->rto = (tp_vars->srtt >> 3) + tp_vars->rttvar;
}
/** * batadv_tp_batctl_notify() - send client status result to client * @reason: reason for tp meter session stop * @dst: destination of tp_meter session * @bat_priv: the bat priv with all the mesh interface information * @start_time: start of transmission in jiffies * @total_sent: bytes acked to the receiver * @cookie: cookie of tp_meter session
*/ staticvoid batadv_tp_batctl_notify(enum batadv_tp_meter_reason reason, const u8 *dst, struct batadv_priv *bat_priv, unsignedlong start_time, u64 total_sent,
u32 cookie)
{
u32 test_time;
u8 result;
u32 total_bytes;
if (!batadv_tp_is_error(reason)) {
result = BATADV_TP_REASON_COMPLETE;
test_time = jiffies_to_msecs(jiffies - start_time);
total_bytes = total_sent;
} else {
result = reason;
test_time = 0;
total_bytes = 0;
}
/** * batadv_tp_batctl_error_notify() - send client error result to client * @reason: reason for tp meter session stop * @dst: destination of tp_meter session * @bat_priv: the bat priv with all the mesh interface information * @cookie: cookie of tp_meter session
*/ staticvoid batadv_tp_batctl_error_notify(enum batadv_tp_meter_reason reason, const u8 *dst, struct batadv_priv *bat_priv,
u32 cookie)
{
batadv_tp_batctl_notify(reason, dst, bat_priv, 0, 0, cookie);
}
/** * batadv_tp_list_find() - find a tp_vars object in the global list * @bat_priv: the bat priv with all the mesh interface information * @dst: the other endpoint MAC address to look for * * Look for a tp_vars object matching dst as end_point and return it after * having increment the refcounter. Return NULL is not found * * Return: matching tp_vars or NULL when no tp_vars with @dst was found
*/ staticstruct batadv_tp_vars *batadv_tp_list_find(struct batadv_priv *bat_priv, const u8 *dst)
{ struct batadv_tp_vars *pos, *tp_vars = NULL;
rcu_read_lock();
hlist_for_each_entry_rcu(pos, &bat_priv->tp_list, list) { if (!batadv_compare_eth(pos->other_end, dst)) continue;
/* most of the time this function is invoked during the normal * process..it makes sens to pay more when the session is * finished and to speed the process up during the measurement
*/ if (unlikely(!kref_get_unless_zero(&pos->refcount))) continue;
tp_vars = pos; break;
}
rcu_read_unlock();
return tp_vars;
}
/** * batadv_tp_list_find_session() - find tp_vars session object in the global * list * @bat_priv: the bat priv with all the mesh interface information * @dst: the other endpoint MAC address to look for * @session: session identifier * * Look for a tp_vars object matching dst as end_point, session as tp meter * session and return it after having increment the refcounter. Return NULL * is not found * * Return: matching tp_vars or NULL when no tp_vars was found
*/ staticstruct batadv_tp_vars *
batadv_tp_list_find_session(struct batadv_priv *bat_priv, const u8 *dst, const u8 *session)
{ struct batadv_tp_vars *pos, *tp_vars = NULL;
rcu_read_lock();
hlist_for_each_entry_rcu(pos, &bat_priv->tp_list, list) { if (!batadv_compare_eth(pos->other_end, dst)) continue;
if (memcmp(pos->session, session, sizeof(pos->session)) != 0) continue;
/* most of the time this function is invoked during the normal * process..it makes sense to pay more when the session is * finished and to speed the process up during the measurement
*/ if (unlikely(!kref_get_unless_zero(&pos->refcount))) continue;
tp_vars = pos; break;
}
rcu_read_unlock();
return tp_vars;
}
/** * batadv_tp_vars_release() - release batadv_tp_vars from lists and queue for * free after rcu grace period * @ref: kref pointer of the batadv_tp_vars
*/ staticvoid batadv_tp_vars_release(struct kref *ref)
{ struct batadv_tp_vars *tp_vars; struct batadv_tp_unacked *un, *safe;
/* lock should not be needed because this object is now out of any * context!
*/
spin_lock_bh(&tp_vars->unacked_lock);
list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) {
list_del(&un->list);
kfree(un);
}
spin_unlock_bh(&tp_vars->unacked_lock);
kfree_rcu(tp_vars, rcu);
}
/** * batadv_tp_vars_put() - decrement the batadv_tp_vars refcounter and possibly * release it * @tp_vars: the private data of the current TP meter session to be free'd
*/ staticvoid batadv_tp_vars_put(struct batadv_tp_vars *tp_vars)
{ if (!tp_vars) return;
/** * batadv_tp_sender_cleanup() - cleanup sender data and drop and timer * @bat_priv: the bat priv with all the mesh interface information * @tp_vars: the private data of the current TP meter session to cleanup
*/ staticvoid batadv_tp_sender_cleanup(struct batadv_priv *bat_priv, struct batadv_tp_vars *tp_vars)
{
cancel_delayed_work(&tp_vars->finish_work);
/* drop list reference */
batadv_tp_vars_put(tp_vars);
atomic_dec(&tp_vars->bat_priv->tp_num);
/* kill the timer and remove its reference */
timer_delete_sync(&tp_vars->timer); /* the worker might have rearmed itself therefore we kill it again. Note * that if the worker should run again before invoking the following * timer_delete(), it would not re-arm itself once again because the status * is OFF now
*/
timer_delete(&tp_vars->timer);
batadv_tp_vars_put(tp_vars);
}
/** * batadv_tp_sender_end() - print info about ended session and inform client * @bat_priv: the bat priv with all the mesh interface information * @tp_vars: the private data of the current TP meter session
*/ staticvoid batadv_tp_sender_end(struct batadv_priv *bat_priv, struct batadv_tp_vars *tp_vars)
{
u32 session_cookie;
batadv_dbg(BATADV_DBG_TP_METER, bat_priv, "Test towards %pM finished..shutting down (reason=%d)\n",
tp_vars->other_end, tp_vars->reason);
/** * batadv_tp_sender_shutdown() - let sender thread/timer stop gracefully * @tp_vars: the private data of the current TP meter session * @reason: reason for tp meter session stop
*/ staticvoid batadv_tp_sender_shutdown(struct batadv_tp_vars *tp_vars, enum batadv_tp_meter_reason reason)
{ if (!atomic_dec_and_test(&tp_vars->sending)) return;
tp_vars->reason = reason;
}
/** * batadv_tp_sender_finish() - stop sender session after test_length was reached * @work: delayed work reference of the related tp_vars
*/ staticvoid batadv_tp_sender_finish(struct work_struct *work)
{ struct delayed_work *delayed_work; struct batadv_tp_vars *tp_vars;
/** * batadv_tp_reset_sender_timer() - reschedule the sender timer * @tp_vars: the private TP meter data for this session * * Reschedule the timer using tp_vars->rto as delay
*/ staticvoid batadv_tp_reset_sender_timer(struct batadv_tp_vars *tp_vars)
{ /* most of the time this function is invoked while normal packet * reception...
*/ if (unlikely(atomic_read(&tp_vars->sending) == 0)) /* timer ref will be dropped in batadv_tp_sender_cleanup */ return;
/** * batadv_tp_sender_timeout() - timer that fires in case of packet loss * @t: address to timer_list inside tp_vars * * If fired it means that there was packet loss. * Switch to Slow Start, set the ss_threshold to half of the current cwnd and * reset the cwnd to 3*MSS
*/ staticvoid batadv_tp_sender_timeout(struct timer_list *t)
{ struct batadv_tp_vars *tp_vars = timer_container_of(tp_vars, t, timer); struct batadv_priv *bat_priv = tp_vars->bat_priv;
if (atomic_read(&tp_vars->sending) == 0) return;
/* if the user waited long enough...shutdown the test */ if (unlikely(tp_vars->rto >= BATADV_TP_MAX_RTO)) {
batadv_tp_sender_shutdown(tp_vars,
BATADV_TP_REASON_DST_UNREACHABLE); return;
}
/* RTO exponential backoff * Details in Section 5.5 of RFC6298
*/
tp_vars->rto <<= 1;
batadv_dbg(BATADV_DBG_TP_METER, bat_priv, "Meter: RTO fired during test towards %pM! cwnd=%u new ss_thr=%u, resetting last_sent to %u\n",
tp_vars->other_end, tp_vars->cwnd, tp_vars->ss_threshold,
atomic_read(&tp_vars->last_acked));
tp_vars->cwnd = BATADV_TP_PLEN * 3;
spin_unlock_bh(&tp_vars->cwnd_lock);
/* resend the non-ACKed packets.. */
tp_vars->last_sent = atomic_read(&tp_vars->last_acked);
wake_up(&tp_vars->more_bytes);
batadv_tp_reset_sender_timer(tp_vars);
}
/** * batadv_tp_fill_prerandom() - Fill buffer with prefetched random bytes * @tp_vars: the private TP meter data for this session * @buf: Buffer to fill with bytes * @nbytes: amount of pseudorandom bytes
*/ staticvoid batadv_tp_fill_prerandom(struct batadv_tp_vars *tp_vars,
u8 *buf, size_t nbytes)
{
u32 local_offset;
size_t bytes_inbuf;
size_t to_copy;
size_t pos = 0;
/** * batadv_tp_send_msg() - send a single message * @tp_vars: the private TP meter data for this session * @src: source mac address * @orig_node: the originator of the destination * @seqno: sequence number of this packet * @len: length of the entire packet * @session: session identifier * @uid: local ICMP "socket" index * @timestamp: timestamp in jiffies which is replied in ack * * Create and send a single TP Meter message. * * Return: 0 on success, BATADV_TP_REASON_DST_UNREACHABLE if the destination is * not reachable, BATADV_TP_REASON_MEMORY_ERROR if the packet couldn't be * allocated
*/ staticint batadv_tp_send_msg(struct batadv_tp_vars *tp_vars, const u8 *src, struct batadv_orig_node *orig_node,
u32 seqno, size_t len, const u8 *session, int uid, u32 timestamp)
{ struct batadv_icmp_tp_packet *icmp; struct sk_buff *skb; int r;
u8 *data;
size_t data_len;
skb = netdev_alloc_skb_ip_align(NULL, len + ETH_HLEN); if (unlikely(!skb)) return BATADV_TP_REASON_MEMORY_ERROR;
/* find the tp_vars */
tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
icmp->session); if (unlikely(!tp_vars)) return;
if (unlikely(atomic_read(&tp_vars->sending) == 0)) goto out;
/* old ACK? silently drop it.. */ if (batadv_seq_before(ntohl(icmp->seqno),
(u32)atomic_read(&tp_vars->last_acked))) goto out;
primary_if = batadv_primary_if_get_selected(bat_priv); if (unlikely(!primary_if)) goto out;
orig_node = batadv_orig_hash_find(bat_priv, icmp->orig); if (unlikely(!orig_node)) goto out;
/* update RTO with the new sampled RTT, if any */
rtt = jiffies_to_msecs(jiffies) - ntohl(icmp->timestamp); if (icmp->timestamp && rtt)
batadv_tp_update_rto(tp_vars, rtt);
/* ACK for new data... reset the timer */
batadv_tp_reset_sender_timer(tp_vars);
recv_ack = ntohl(icmp->seqno);
/* check if this ACK is a duplicate */ if (atomic_read(&tp_vars->last_acked) == recv_ack) {
atomic_inc(&tp_vars->dup_acks); if (atomic_read(&tp_vars->dup_acks) != 3) goto out;
if (recv_ack >= tp_vars->recover) goto out;
/* if this is the third duplicate ACK do Fast Retransmit */
batadv_tp_send_msg(tp_vars, primary_if->net_dev->dev_addr,
orig_node, recv_ack, packet_len,
icmp->session, icmp->uid,
jiffies_to_msecs(jiffies));
spin_lock_bh(&tp_vars->cwnd_lock);
/* Fast Recovery */
tp_vars->fast_recovery = true; /* Set recover to the last outstanding seqno when Fast Recovery * is entered. RFC6582, Section 3.2, step 1
*/
tp_vars->recover = tp_vars->last_sent;
tp_vars->ss_threshold = tp_vars->cwnd >> 1;
batadv_dbg(BATADV_DBG_TP_METER, bat_priv, "Meter: Fast Recovery, (cur cwnd=%u) ss_thr=%u last_sent=%u recv_ack=%u\n",
tp_vars->cwnd, tp_vars->ss_threshold,
tp_vars->last_sent, recv_ack);
tp_vars->cwnd = batadv_tp_cwnd(tp_vars->ss_threshold, 3 * mss,
mss);
tp_vars->dec_cwnd = 0;
tp_vars->last_sent = recv_ack;
spin_unlock_bh(&tp_vars->cwnd_lock);
} else { /* count the acked data */
atomic64_add(recv_ack - atomic_read(&tp_vars->last_acked),
&tp_vars->tot_sent); /* reset the duplicate ACKs counter */
atomic_set(&tp_vars->dup_acks, 0);
if (tp_vars->fast_recovery) { /* partial ACK */ if (batadv_seq_before(recv_ack, tp_vars->recover)) { /* this is another hole in the window. React * immediately as specified by NewReno (see * Section 3.2 of RFC6582 for details)
*/
dev_addr = primary_if->net_dev->dev_addr;
batadv_tp_send_msg(tp_vars, dev_addr,
orig_node, recv_ack,
packet_len, icmp->session,
icmp->uid,
jiffies_to_msecs(jiffies));
tp_vars->cwnd = batadv_tp_cwnd(tp_vars->cwnd,
mss, mss);
} else {
tp_vars->fast_recovery = false; /* set cwnd to the value of ss_threshold at the * moment that Fast Recovery was entered. * RFC6582, Section 3.2, step 3
*/
cwnd = batadv_tp_cwnd(tp_vars->ss_threshold, 0,
mss);
tp_vars->cwnd = cwnd;
} goto move_twnd;
}
if (recv_ack - atomic_read(&tp_vars->last_acked) >= mss)
batadv_tp_update_cwnd(tp_vars, mss);
move_twnd: /* move the Transmit Window */
atomic_set(&tp_vars->last_acked, recv_ack);
}
/** * batadv_tp_avail() - check if congestion window is not full * @tp_vars: the private data of the current TP meter session * @payload_len: size of the payload of a single message * * Return: true when congestion window is not full, false otherwise
*/ staticbool batadv_tp_avail(struct batadv_tp_vars *tp_vars,
size_t payload_len)
{
u32 win_left, win_limit;
/** * batadv_tp_wait_available() - wait until congestion window becomes free or * timeout is reached * @tp_vars: the private data of the current TP meter session * @plen: size of the payload of a single message * * Return: 0 if the condition evaluated to false after the timeout elapsed, * 1 if the condition evaluated to true after the timeout elapsed, the * remaining jiffies (at least 1) if the condition evaluated to true before * the timeout elapsed, or -ERESTARTSYS if it was interrupted by a signal.
*/ staticint batadv_tp_wait_available(struct batadv_tp_vars *tp_vars, size_t plen)
{ int ret;
ret = wait_event_interruptible_timeout(tp_vars->more_bytes,
batadv_tp_avail(tp_vars, plen),
HZ / 10);
return ret;
}
/** * batadv_tp_send() - main sending thread of a tp meter session * @arg: address of the related tp_vars * * Return: nothing, this function never returns
*/ staticint batadv_tp_send(void *arg)
{ struct batadv_tp_vars *tp_vars = arg; struct batadv_priv *bat_priv = tp_vars->bat_priv; struct batadv_hard_iface *primary_if = NULL; struct batadv_orig_node *orig_node = NULL;
size_t payload_len, packet_len; int err = 0;
/* assume that all the hard_interfaces have a correctly * configured MTU, so use the mesh_iface MTU as MSS. * This might not be true and in that case the fragmentation * should be used. * Now, try to send the packet as it is
*/
payload_len = BATADV_TP_PLEN;
BUILD_BUG_ON(sizeof(struct batadv_icmp_tp_packet) > BATADV_TP_PLEN);
batadv_tp_reset_sender_timer(tp_vars);
/* queue the worker in charge of terminating the test */
queue_delayed_work(batadv_event_workqueue, &tp_vars->finish_work,
msecs_to_jiffies(tp_vars->test_length));
while (atomic_read(&tp_vars->sending) != 0) { if (unlikely(!batadv_tp_avail(tp_vars, payload_len))) {
batadv_tp_wait_available(tp_vars, payload_len); continue;
}
/* to emulate normal unicast traffic, add to the payload len * the size of the unicast header
*/
packet_len = payload_len + sizeof(struct batadv_unicast_packet);
/** * batadv_tp_start_kthread() - start new thread which manages the tp meter * sender * @tp_vars: the private data of the current TP meter session
*/ staticvoid batadv_tp_start_kthread(struct batadv_tp_vars *tp_vars)
{ struct task_struct *kthread; struct batadv_priv *bat_priv = tp_vars->bat_priv;
u32 session_cookie;
/* drop reserved reference for kthread */
batadv_tp_vars_put(tp_vars);
/* cleanup of failed tp meter variables */
batadv_tp_sender_cleanup(bat_priv, tp_vars); return;
}
wake_up_process(kthread);
}
/** * batadv_tp_start() - start a new tp meter session * @bat_priv: the bat priv with all the mesh interface information * @dst: the receiver MAC address * @test_length: test length in milliseconds * @cookie: session cookie
*/ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
u32 test_length, u32 *cookie)
{ struct batadv_tp_vars *tp_vars;
u8 session_id[2];
u8 icmp_uid;
u32 session_cookie;
/* look for an already existing test towards this node */
spin_lock_bh(&bat_priv->tp_list_lock);
tp_vars = batadv_tp_list_find(bat_priv, dst); if (tp_vars) {
spin_unlock_bh(&bat_priv->tp_list_lock);
batadv_tp_vars_put(tp_vars);
batadv_dbg(BATADV_DBG_TP_METER, bat_priv, "Meter: test to or from the same node already ongoing, aborting\n");
batadv_tp_batctl_error_notify(BATADV_TP_REASON_ALREADY_ONGOING,
dst, bat_priv, session_cookie); return;
}
if (!atomic_add_unless(&bat_priv->tp_num, 1, BATADV_TP_MAX_NUM)) {
spin_unlock_bh(&bat_priv->tp_list_lock);
batadv_dbg(BATADV_DBG_TP_METER, bat_priv, "Meter: too many ongoing sessions, aborting (SEND)\n");
batadv_tp_batctl_error_notify(BATADV_TP_REASON_TOO_MANY, dst,
bat_priv, session_cookie); return;
}
/* initialise the CWND to 3*MSS (Section 3.1 in RFC5681). * For batman-adv the MSS is the size of the payload received by the * mesh_interface, hence its MTU
*/
tp_vars->cwnd = BATADV_TP_PLEN * 3; /* at the beginning initialise the SS threshold to the biggest possible * window size, hence the AWND size
*/
tp_vars->ss_threshold = BATADV_TP_AWND;
/* RTO initial value is 3 seconds. * Details in Section 2.1 of RFC6298
*/
tp_vars->rto = 1000;
tp_vars->srtt = 0;
tp_vars->rttvar = 0;
tp_vars->test_length = test_length; if (!tp_vars->test_length)
tp_vars->test_length = BATADV_TP_DEF_TEST_LENGTH;
batadv_dbg(BATADV_DBG_TP_METER, bat_priv, "Meter: starting throughput meter towards %pM (length=%ums)\n",
dst, test_length);
/* init work item for finished tp tests */
INIT_DELAYED_WORK(&tp_vars->finish_work, batadv_tp_sender_finish);
/* start tp kthread. This way the write() call issued from userspace can * happily return and avoid to block
*/
batadv_tp_start_kthread(tp_vars);
/* don't return reference to new tp_vars */
batadv_tp_vars_put(tp_vars);
}
/** * batadv_tp_stop() - stop currently running tp meter session * @bat_priv: the bat priv with all the mesh interface information * @dst: the receiver MAC address * @return_value: reason for tp meter session stop
*/ void batadv_tp_stop(struct batadv_priv *bat_priv, const u8 *dst,
u8 return_value)
{ struct batadv_orig_node *orig_node; struct batadv_tp_vars *tp_vars;
batadv_dbg(BATADV_DBG_TP_METER, bat_priv, "Meter: stopping test towards %pM\n", dst);
orig_node = batadv_orig_hash_find(bat_priv, dst); if (!orig_node) return;
tp_vars = batadv_tp_list_find(bat_priv, orig_node->orig); if (!tp_vars) {
batadv_dbg(BATADV_DBG_TP_METER, bat_priv, "Meter: trying to interrupt an already over connection\n"); goto out;
}
/** * batadv_tp_reset_receiver_timer() - reset the receiver shutdown timer * @tp_vars: the private data of the current TP meter session * * start the receiver shutdown timer or reset it if already started
*/ staticvoid batadv_tp_reset_receiver_timer(struct batadv_tp_vars *tp_vars)
{
mod_timer(&tp_vars->timer,
jiffies + msecs_to_jiffies(BATADV_TP_RECV_TIMEOUT));
}
/** * batadv_tp_receiver_shutdown() - stop a tp meter receiver when timeout is * reached without received ack * @t: address to timer_list inside tp_vars
*/ staticvoid batadv_tp_receiver_shutdown(struct timer_list *t)
{ struct batadv_tp_vars *tp_vars = timer_container_of(tp_vars, t, timer); struct batadv_tp_unacked *un, *safe; struct batadv_priv *bat_priv;
bat_priv = tp_vars->bat_priv;
/* if there is recent activity rearm the timer */ if (!batadv_has_timed_out(tp_vars->last_recv_time,
BATADV_TP_RECV_TIMEOUT)) { /* reset the receiver shutdown timer */
batadv_tp_reset_receiver_timer(tp_vars); return;
}
batadv_dbg(BATADV_DBG_TP_METER, bat_priv, "Shutting down for inactivity (more than %dms) from %pM\n",
BATADV_TP_RECV_TIMEOUT, tp_vars->other_end);
/* drop reference of timer */
batadv_tp_vars_put(tp_vars);
}
/** * batadv_tp_send_ack() - send an ACK packet * @bat_priv: the bat priv with all the mesh interface information * @dst: the mac address of the destination originator * @seq: the sequence number to ACK * @timestamp: the timestamp to echo back in the ACK * @session: session identifier * @socket_index: local ICMP socket identifier * * Return: 0 on success, a positive integer representing the reason of the * failure otherwise
*/ staticint batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
u32 seq, __be32 timestamp, const u8 *session, int socket_index)
{ struct batadv_hard_iface *primary_if = NULL; struct batadv_orig_node *orig_node; struct batadv_icmp_tp_packet *icmp; struct sk_buff *skb; int r, ret;
orig_node = batadv_orig_hash_find(bat_priv, dst); if (unlikely(!orig_node)) {
ret = BATADV_TP_REASON_DST_UNREACHABLE; goto out;
}
primary_if = batadv_primary_if_get_selected(bat_priv); if (unlikely(!primary_if)) {
ret = BATADV_TP_REASON_DST_UNREACHABLE; goto out;
}
skb = netdev_alloc_skb_ip_align(NULL, sizeof(*icmp) + ETH_HLEN); if (unlikely(!skb)) {
ret = BATADV_TP_REASON_MEMORY_ERROR; goto out;
}
/* send the ack */
r = batadv_send_skb_to_orig(skb, orig_node, NULL); if (unlikely(r < 0) || r == NET_XMIT_DROP) {
ret = BATADV_TP_REASON_DST_UNREACHABLE; goto out;
}
ret = 0;
/** * batadv_tp_handle_out_of_order() - store an out of order packet * @tp_vars: the private data of the current TP meter session * @skb: the buffer containing the received packet * * Store the out of order packet in the unacked list for late processing. This * packets are kept in this list so that they can be ACKed at once as soon as * all the previous packets have been received * * Return: true if the packed has been successfully processed, false otherwise
*/ staticbool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars, conststruct sk_buff *skb)
{ conststruct batadv_icmp_tp_packet *icmp; struct batadv_tp_unacked *un, *new;
u32 payload_len; bool added = false;
new = kmalloc(sizeof(*new), GFP_ATOMIC); if (unlikely(!new)) returnfalse;
spin_lock_bh(&tp_vars->unacked_lock); /* if the list is empty immediately attach this new object */ if (list_empty(&tp_vars->unacked_list)) {
list_add(&new->list, &tp_vars->unacked_list); goto out;
}
/* otherwise loop over the list and either drop the packet because this * is a duplicate or store it at the right position. * * The iteration is done in the reverse way because it is likely that * the last received packet (the one being processed now) has a bigger * seqno than all the others already stored.
*/
list_for_each_entry_reverse(un, &tp_vars->unacked_list, list) { /* check for duplicates */ if (new->seqno == un->seqno) { if (new->len > un->len)
un->len = new->len;
kfree(new);
added = true; break;
}
/* look for the right position */ if (batadv_seq_before(new->seqno, un->seqno)) continue;
/* as soon as an entry having a bigger seqno is found, the new * one is attached _after_ it. In this way the list is kept in * ascending order
*/
list_add_tail(&new->list, &un->list);
added = true; break;
}
/* received packet with smallest seqno out of order; add it to front */ if (!added)
list_add(&new->list, &tp_vars->unacked_list);
out:
spin_unlock_bh(&tp_vars->unacked_lock);
returntrue;
}
/** * batadv_tp_ack_unordered() - update number received bytes in current stream * without gaps * @tp_vars: the private data of the current TP meter session
*/ staticvoid batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
{ struct batadv_tp_unacked *un, *safe;
u32 to_ack;
/* go through the unacked packet list and possibly ACK them as * well
*/
spin_lock_bh(&tp_vars->unacked_lock);
list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { /* the list is ordered, therefore it is possible to stop as soon * there is a gap between the last acked seqno and the seqno of * the packet under inspection
*/ if (batadv_seq_before(tp_vars->last_recv, un->seqno)) break;
/** * batadv_tp_init_recv() - return matching or create new receiver tp_vars * @bat_priv: the bat priv with all the mesh interface information * @icmp: received icmp tp msg * * Return: corresponding tp_vars or NULL on errors
*/ staticstruct batadv_tp_vars *
batadv_tp_init_recv(struct batadv_priv *bat_priv, conststruct batadv_icmp_tp_packet *icmp)
{ struct batadv_tp_vars *tp_vars;
spin_lock_bh(&bat_priv->tp_list_lock);
tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
icmp->session); if (tp_vars) goto out_unlock;
if (!atomic_add_unless(&bat_priv->tp_num, 1, BATADV_TP_MAX_NUM)) {
batadv_dbg(BATADV_DBG_TP_METER, bat_priv, "Meter: too many ongoing sessions, aborting (RECV)\n"); goto out_unlock;
}
tp_vars = kmalloc(sizeof(*tp_vars), GFP_ATOMIC); if (!tp_vars) goto out_unlock;
/** * batadv_tp_recv_msg() - process a single data message * @bat_priv: the bat priv with all the mesh interface information * @skb: the buffer containing the received packet * * Process a received TP MSG packet
*/ staticvoid batadv_tp_recv_msg(struct batadv_priv *bat_priv, conststruct sk_buff *skb)
{ conststruct batadv_icmp_tp_packet *icmp; struct batadv_tp_vars *tp_vars;
size_t packet_size;
u32 seqno;
icmp = (struct batadv_icmp_tp_packet *)skb->data;
seqno = ntohl(icmp->seqno); /* check if this is the first seqno. This means that if the * first packet is lost, the tp meter does not work anymore!
*/ if (seqno == BATADV_TP_FIRST_SEQ) {
tp_vars = batadv_tp_init_recv(bat_priv, icmp); if (!tp_vars) {
batadv_dbg(BATADV_DBG_TP_METER, bat_priv, "Meter: seqno != BATADV_TP_FIRST_SEQ cannot initiate connection\n"); goto out;
}
} else {
tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
icmp->session); if (!tp_vars) {
batadv_dbg(BATADV_DBG_TP_METER, bat_priv, "Unexpected packet from %pM!\n",
icmp->orig); goto out;
}
}
if (unlikely(tp_vars->role != BATADV_TP_RECEIVER)) {
batadv_dbg(BATADV_DBG_TP_METER, bat_priv, "Meter: dropping packet: not expected (role=%u)\n",
tp_vars->role); goto out;
}
tp_vars->last_recv_time = jiffies;
/* if the packet is a duplicate, it may be the case that an ACK has been * lost. Resend the ACK
*/ if (batadv_seq_before(seqno, tp_vars->last_recv)) goto send_ack;
/* if the packet is out of order enqueue it */ if (ntohl(icmp->seqno) != tp_vars->last_recv) { /* exit immediately (and do not send any ACK) if the packet has * not been enqueued correctly
*/ if (!batadv_tp_handle_out_of_order(tp_vars, skb)) goto out;
/* send a duplicate ACK */ goto send_ack;
}
/* if everything was fine count the ACKed bytes */
packet_size = skb->len - sizeof(struct batadv_unicast_packet);
tp_vars->last_recv += packet_size;
/* check if this ordered message filled a gap.... */
batadv_tp_ack_unordered(tp_vars);
send_ack: /* send the ACK. If the received packet was out of order, the ACK that * is going to be sent is a duplicate (the sender will count them and * possibly enter Fast Retransmit as soon as it has reached 3)
*/
batadv_tp_send_ack(bat_priv, icmp->orig, tp_vars->last_recv,
icmp->timestamp, icmp->session, icmp->uid);
out:
batadv_tp_vars_put(tp_vars);
}
/** * batadv_tp_meter_recv() - main TP Meter receiving function * @bat_priv: the bat priv with all the mesh interface information * @skb: the buffer containing the received packet
*/ void batadv_tp_meter_recv(struct batadv_priv *bat_priv, struct sk_buff *skb)
{ struct batadv_icmp_tp_packet *icmp;
icmp = (struct batadv_icmp_tp_packet *)skb->data;
switch (icmp->subtype) { case BATADV_TP_MSG:
batadv_tp_recv_msg(bat_priv, skb); break; case BATADV_TP_ACK:
batadv_tp_recv_ack(bat_priv, skb); break; default:
batadv_dbg(BATADV_DBG_TP_METER, bat_priv, "Received unknown TP Metric packet type %u\n",
icmp->subtype);
}
consume_skb(skb);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.