I believe this one is ready for posting now. Please ack. ///jon
> -----Original Message----- > From: Jon Maloy [mailto:[email protected]] > Sent: Wednesday, 08 June, 2016 14:54 > To: [email protected]; Parthasarathy Bhuvaragan; Ying Xue; > Richard Alpe; Jon Maloy > Cc: [email protected] > Subject: [PATCH net-next v8 1/1] tipc: add neighbor monitoring framework > > TIPC based clusters are by default set up with full-mesh link > connectivity between all nodes. Those links are expected to provide > a short failure detection time, by default set to 1500 ms. Because > of this, the background load for neighbor monitoring in an N-node > cluster increases with a factor N on each node, while the overall > monitoring traffic through the network infrastructure increases at > a ~(N * (N - 1)) rate. Experience has shown that such clusters don't > scale well beyond ~100 nodes unless we significantly increase failure > discovery tolerance. > > This commit introduces a framework and an algorithm that drastically > reduces this background load, while basically maintaining the original > failure detection times across the whole cluster. Using this algorithm, > background load will now grow at a rate of ~(2 * sqrt(N)) per node, and > at ~(2 * N * sqrt(N)) in traffic overhead. As an example, each node will > now have to actively monitor 38 neighbors in a 400-node cluster, instead > of as before 399. > > This "Overlapping Ring Supervision Algorithm" is completely distributed > and employs no centralized or coordinated state. It goes as follows: > > - Each node makes up a linearly ascending, circular list of all its N > known neighbors, based on their TIPC node identity. This algorithm > must be the same on all nodes. > > - The node then selects the next M = sqrt(N) - 1 nodes downstream from > itself in the list, and chooses to actively monitor those. This is > called its "local monitoring domain". > > - It creates a domain record describing the monitoring domain, and > piggy-backs this in the data area of all neighbor monitoring messages > (LINK_PROTOCOL/STATE) leaving that node. This means that all nodes in > the cluster eventually (default within 400 ms) will learn about > its monitoring domain. > > - Whenever a node discovers a change in its local domain, e.g., a node > has been added or has gone down, it creates and sends out a new > version of its node record to inform all neighbors about the change. > > - A node receiving a domain record from anybody outside its local domain > matches this against its own list (which may not look the same), and > chooses to not actively monitor those members of the received domain > record that are also present in its own list. Instead, it relies on > indications from the direct monitoring nodes if an indirectly > monitored node has gone up or down. If a node is indicated lost, the > receiving node temporarily activates its own direct monitoring towards > that node in order to confirm, or not, that it is actually gone. > > - Since each node is actively monitoring sqrt(N) downstream neighbors, > each node is also actively monitored by the same number of upstream > neighbors. This means that all non-direct monitoring nodes normally > will receive sqrt(N) indications that a node is gone. > > - A major drawback with ring monitoring is how it handles failures that > cause massive network partitionings. If both a lost node and all its > direct monitoring neighbors are inside the lost partition, the nodes in > the remaining partition will never receive indications about the loss. > To overcome this, each node also chooses to actively monitor some > nodes outside its local domain. Those nodes are called remote domain > "heads", and are selected in such a way that no node in the cluster > will be more than two direct monitoring hops away. Because of this, > each node, apart from monitoring the member of its local domain, will > also typically monitor sqrt(N) remote head nodes. > > - As an optimization, local list status, domain status and domain > records are marked with a generation number. This saves senders from > unnecessarily conveying unaltered domain records, and receivers from > performing unneeded re-adaptations of their node monitoring list, such > as re-assigning domain heads. > > v2: - Updated according to comments from Richard Alpe. His proposal > for the peer_xxx() functions didn't work out well, but for the > rest it was ok. > - Added a breakpoint (cluster size) where monitoring algo should > switch from "full mesh" to "overlapping ring". Default value > 32, but I even tested to set it to 100 without any problems. > This should be the variable Partha should make configurable, > instead of just toggling as I suggested originally. By setting > the breakpoint to 0 ring monitoring is always on, by setting it > to some high number it is always off. > > v3: - Renamed "mon_breakpoint" to "mon_threshold" > - Added code to revert to full-mesh monitoring when a node is > removed and we reach the monitor threshold from above. > > v4: - Fixed typos in commit log and a couple of minor issues, > as per feedback from Ying. > - Removed unused function and flag for disabling the monitor. > This functionality was later superseded by commit 5b7066c3dd24 > ("tipc: stricter filtering of packets in bearer layer"), I just > forgot to remove it. > > v5: - Eliminated the 'head_map" field from struct tipc_peer. Instead, > we now identify lost domain members by comparing the peer's > old domain record with the newly arrived one. This is much simpler > and eliminates a source of potential table inconsistency. > - Replaced the 'confirm' bit in struct tipc_peer with a counter, so > that we can keep track of how many "peer down" event we have > received for a given peer since we last heard from it. When the > counter is >= 4, we reset the link without waiting for the own > confirmation probing to finish. > - Apply matching domains of remote peer's domains completely, even > when the latter comprise the own node. This has no functional > significance, but gives a more symmetric algorithm, and looks > better in the table printouts. > - Added a new condition for sending STATE messages on non-monitored > links. Those must be sent even when there are received/ > unacknowledged broadcast messages, in order to acknowledge those. > - Added a slow (2 min) background timer which will check if the > current local domain size is optimal, and otherwise update the > local domain. This way, we smooth out the domain sizes, so that > all nodes eventually (in a full-mesh cluster) will have the same > size. > - Added a new check in update_local_domain(), so that we step > generation number only when something really has changed. > - Added an skb_trim() in build_proto_msg(), so that we send only > the actual size of the domain record, and nothing more. > - Moved skb_linearize() in link_proto_rcv() so that it now covers > all received messages. I found out the hard way that this is > necessary. > > v6: - Updated some finction and varaible names, both according to > comments from Partha and own improvements. > - Introduced new flag 'check_gen' in struct mon_state to fixe a > problem with reception of domain records: when a peer has just > come up, it must accept any domain generation, without checking > if it is in a valid range. > - Tried to make the domain generation handling simpler and more > comprehensible. > > v7: - Cosmetic refactoring of tipc_mon_rcv(). > - Updated according to latest comments from Partha, including > the tipc_mon_is_active() function. > - After some pondering I decided to remove the one-bit boolean > fields after all. Such a design has some caveats, which is more > than we need in this rather complex code. > - Added correct data length and skb_trim() for all messages > created in link_build_proto_msg(), not only STATE messages. > - Added a random distribution to the monitor timer interval. The > interval will now be spread across the interval 2-3 minutes. > - Only consider tic_peer->down_cnt's value when the peer is not in > the local domain. I.e., loss of a local peer must always be > confirmed by direct probing. This eliminates the risk of avalanche > link losses because of false positives. > > v8: - Added missing "peer->applied = 0" in tipc_mon_remove_peer() when > monitor moves back to full-mesh monitoring. This fixes a crash > reported by Partha. > > Signed-off-by: Jon Maloy <[email protected]> > --- > net/tipc/Makefile | 2 +- > net/tipc/addr.h | 1 + > net/tipc/bearer.c | 8 +- > net/tipc/bearer.h | 2 +- > net/tipc/core.c | 1 + > net/tipc/core.h | 15 +- > net/tipc/link.c | 49 +++- > net/tipc/monitor.c | 651 > +++++++++++++++++++++++++++++++++++++++++++++++++++++ > net/tipc/monitor.h | 73 ++++++ > net/tipc/node.c | 26 +-- > 10 files changed, 797 insertions(+), 31 deletions(-) > create mode 100644 net/tipc/monitor.c > create mode 100644 net/tipc/monitor.h > > diff --git a/net/tipc/Makefile b/net/tipc/Makefile > index 57e460b..31b9f9c 100644 > --- a/net/tipc/Makefile > +++ b/net/tipc/Makefile > @@ -6,7 +6,7 @@ obj-$(CONFIG_TIPC) := tipc.o > > tipc-y += addr.o bcast.o bearer.o \ > core.o link.o discover.o msg.o \ > - name_distr.o subscr.o name_table.o net.o \ > + name_distr.o subscr.o monitor.o name_table.o net.o \ > netlink.o netlink_compat.o node.o socket.o eth_media.o \ > server.o socket.o > > diff --git a/net/tipc/addr.h b/net/tipc/addr.h > index 93f7c98..64f4004 100644 > --- a/net/tipc/addr.h > +++ b/net/tipc/addr.h > @@ -73,4 +73,5 @@ int tipc_addr_node_valid(u32 addr); > int tipc_in_scope(u32 domain, u32 addr); > int tipc_addr_scope(u32 domain); > char *tipc_addr_string_fill(char *string, u32 addr); > + > #endif > diff --git a/net/tipc/bearer.c b/net/tipc/bearer.c > index 6f11c62..9a70e1d 100644 > --- a/net/tipc/bearer.c > +++ b/net/tipc/bearer.c > @@ -1,7 +1,7 @@ > /* > * net/tipc/bearer.c: TIPC bearer code > * > - * Copyright (c) 1996-2006, 2013-2014, Ericsson AB > + * Copyright (c) 1996-2006, 2013-2016, Ericsson AB > * Copyright (c) 2004-2006, 2010-2013, Wind River Systems > * All rights reserved. > * > @@ -39,6 +39,7 @@ > #include "bearer.h" > #include "link.h" > #include "discover.h" > +#include "monitor.h" > #include "bcast.h" > #include "netlink.h" > > @@ -313,6 +314,10 @@ restart: > rcu_assign_pointer(tn->bearer_list[bearer_id], b); > if (skb) > tipc_bearer_xmit_skb(net, bearer_id, skb, &b->bcast_addr); > + > + if (tipc_mon_create(net, bearer_id)) > + return -ENOMEM; > + > pr_info("Enabled bearer <%s>, discovery domain %s, priority %u\n", > name, > tipc_addr_string_fill(addr_string, disc_domain), priority); > @@ -348,6 +353,7 @@ static void bearer_disable(struct net *net, struct > tipc_bearer *b) > tipc_disc_delete(b->link_req); > RCU_INIT_POINTER(tn->bearer_list[bearer_id], NULL); > kfree_rcu(b, rcu); > + tipc_mon_delete(net, bearer_id); > } > > int tipc_enable_l2_media(struct net *net, struct tipc_bearer *b, > diff --git a/net/tipc/bearer.h b/net/tipc/bearer.h > index f686e41..0d337c7 100644 > --- a/net/tipc/bearer.h > +++ b/net/tipc/bearer.h > @@ -1,7 +1,7 @@ > /* > * net/tipc/bearer.h: Include file for TIPC bearer code > * > - * Copyright (c) 1996-2006, 2013-2014, Ericsson AB > + * Copyright (c) 1996-2006, 2013-2016, Ericsson AB > * Copyright (c) 2005, 2010-2011, Wind River Systems > * All rights reserved. > * > diff --git a/net/tipc/core.c b/net/tipc/core.c > index fe1b062..236b043 100644 > --- a/net/tipc/core.c > +++ b/net/tipc/core.c > @@ -57,6 +57,7 @@ static int __net_init tipc_init_net(struct net *net) > > tn->net_id = 4711; > tn->own_addr = 0; > + tn->mon_threshold = TIPC_DEF_MON_THRESHOLD; > get_random_bytes(&tn->random, sizeof(int)); > INIT_LIST_HEAD(&tn->node_list); > spin_lock_init(&tn->node_list_lock); > diff --git a/net/tipc/core.h b/net/tipc/core.h > index eff58dc..a1845fb 100644 > --- a/net/tipc/core.h > +++ b/net/tipc/core.h > @@ -66,11 +66,13 @@ struct tipc_bc_base; > struct tipc_link; > struct tipc_name_table; > struct tipc_server; > +struct tipc_monitor; > > #define TIPC_MOD_VER "2.0.0" > > -#define NODE_HTABLE_SIZE 512 > -#define MAX_BEARERS 3 > +#define NODE_HTABLE_SIZE 512 > +#define MAX_BEARERS 3 > +#define TIPC_DEF_MON_THRESHOLD 32 > > extern int tipc_net_id __read_mostly; > extern int sysctl_tipc_rmem[3] __read_mostly; > @@ -88,6 +90,10 @@ struct tipc_net { > u32 num_nodes; > u32 num_links; > > + /* Neighbor monitoring list */ > + struct tipc_monitor *monitors[MAX_BEARERS]; > + int mon_threshold; > + > /* Bearer list */ > struct tipc_bearer __rcu *bearer_list[MAX_BEARERS + 1]; > > @@ -126,6 +132,11 @@ static inline struct list_head *tipc_nodes(struct net > *net) > return &tipc_net(net)->node_list; > } > > +static inline unsigned int tipc_hashfn(u32 addr) > +{ > + return addr & (NODE_HTABLE_SIZE - 1); > +} > + > static inline u16 mod(u16 x) > { > return x & 0xffffu; > diff --git a/net/tipc/link.c b/net/tipc/link.c > index a904ccd..03f8bdf 100644 > --- a/net/tipc/link.c > +++ b/net/tipc/link.c > @@ -42,6 +42,7 @@ > #include "name_distr.h" > #include "discover.h" > #include "netlink.h" > +#include "monitor.h" > > #include <linux/pkt_sched.h> > > @@ -95,6 +96,7 @@ struct tipc_stats { > * @pmsg: convenience pointer to "proto_msg" field > * @priority: current link priority > * @net_plane: current link network plane ('A' through 'H') > + * @mon_state: cookie with information needed by link monitor > * @backlog_limit: backlog queue congestion thresholds (indexed by > importance) > * @exp_msg_count: # of tunnelled messages expected during link changeover > * @reset_rcv_checkpt: seq # of last acknowledged message at time of link > reset > @@ -138,6 +140,7 @@ struct tipc_link { > char if_name[TIPC_MAX_IF_NAME]; > u32 priority; > char net_plane; > + struct tipc_mon_state mon_state; > u16 rst_cnt; > > /* Failover/synch */ > @@ -708,18 +711,25 @@ int tipc_link_timeout(struct tipc_link *l, struct > sk_buff_head *xmitq) > bool setup = false; > u16 bc_snt = l->bc_sndlink->snd_nxt - 1; > u16 bc_acked = l->bc_rcvlink->acked; > - > - link_profile_stats(l); > + struct tipc_mon_state *mstate = &l->mon_state; > > switch (l->state) { > case LINK_ESTABLISHED: > case LINK_SYNCHING: > - if (l->silent_intv_cnt > l->abort_limit) > - return tipc_link_fsm_evt(l, LINK_FAILURE_EVT); > mtyp = STATE_MSG; > + link_profile_stats(l); > + tipc_mon_get_state(l->net, l->addr, mstate, l->bearer_id); > + if (mstate->reset || (l->silent_intv_cnt > l->abort_limit)) > + return tipc_link_fsm_evt(l, LINK_FAILURE_EVT); > state = bc_acked != bc_snt; > - probe = l->silent_intv_cnt; > - l->silent_intv_cnt++; > + state |= l->bc_rcvlink->rcv_unacked; > + state |= l->rcv_unacked; > + state |= !skb_queue_empty(&l->transmq); > + state |= !skb_queue_empty(&l->deferdq); > + probe = mstate->probing; > + probe |= l->silent_intv_cnt; > + if (probe || mstate->monitoring) > + l->silent_intv_cnt++; > break; > case LINK_RESET: > setup = l->rst_cnt++ <= 4; > @@ -830,6 +840,7 @@ void tipc_link_reset(struct tipc_link *l) > l->stats.recv_info = 0; > l->stale_count = 0; > l->bc_peer_is_up = false; > + memset(&l->mon_state, 0, sizeof(l->mon_state)); > tipc_link_reset_stats(l); > } > > @@ -1238,6 +1249,9 @@ static void tipc_link_build_proto_msg(struct tipc_link > *l, > int mtyp, bool probe, > struct tipc_msg *hdr; > struct sk_buff_head *dfq = &l->deferdq; > bool node_up = link_is_up(l->bc_rcvlink); > + struct tipc_mon_state *mstate = &l->mon_state; > + int dlen = 0; > + void *data; > > /* Don't send protocol message during reset or link failover */ > if (tipc_link_is_blocked(l)) > @@ -1250,12 +1264,13 @@ static void tipc_link_build_proto_msg(struct tipc_link > *l, int mtyp, bool probe, > rcvgap = buf_seqno(skb_peek(dfq)) - l->rcv_nxt; > > skb = tipc_msg_create(LINK_PROTOCOL, mtyp, INT_H_SIZE, > - TIPC_MAX_IF_NAME, l->addr, > + tipc_max_domain_size, l->addr, > tipc_own_addr(l->net), 0, 0, 0); > if (!skb) > return; > > hdr = buf_msg(skb); > + data = msg_data(hdr); > msg_set_session(hdr, l->session); > msg_set_bearer_id(hdr, l->bearer_id); > msg_set_net_plane(hdr, l->net_plane); > @@ -1271,14 +1286,18 @@ static void tipc_link_build_proto_msg(struct tipc_link > *l, int mtyp, bool probe, > > if (mtyp == STATE_MSG) { > msg_set_seq_gap(hdr, rcvgap); > - msg_set_size(hdr, INT_H_SIZE); > msg_set_probe(hdr, probe); > + tipc_mon_prep(l->net, data, &dlen, mstate, l->bearer_id); > + msg_set_size(hdr, INT_H_SIZE + dlen); > + skb_trim(skb, INT_H_SIZE + dlen); > l->stats.sent_states++; > l->rcv_unacked = 0; > } else { > /* RESET_MSG or ACTIVATE_MSG */ > msg_set_max_pkt(hdr, l->advertised_mtu); > - strcpy(msg_data(hdr), l->if_name); > + strcpy(data, l->if_name); > + msg_set_size(hdr, INT_H_SIZE + TIPC_MAX_IF_NAME); > + skb_trim(skb, INT_H_SIZE + TIPC_MAX_IF_NAME); > } > if (probe) > l->stats.sent_probes++; > @@ -1371,7 +1390,9 @@ static int tipc_link_proto_rcv(struct tipc_link *l, > struct > sk_buff *skb, > u16 peers_tol = msg_link_tolerance(hdr); > u16 peers_prio = msg_linkprio(hdr); > u16 rcv_nxt = l->rcv_nxt; > + u16 dlen = msg_data_sz(hdr); > int mtyp = msg_type(hdr); > + void *data; > char *if_name; > int rc = 0; > > @@ -1381,6 +1402,10 @@ static int tipc_link_proto_rcv(struct tipc_link *l, > struct > sk_buff *skb, > if (tipc_own_addr(l->net) > msg_prevnode(hdr)) > l->net_plane = msg_net_plane(hdr); > > + skb_linearize(skb); > + hdr = buf_msg(skb); > + data = msg_data(hdr); > + > switch (mtyp) { > case RESET_MSG: > > @@ -1391,8 +1416,6 @@ static int tipc_link_proto_rcv(struct tipc_link *l, > struct > sk_buff *skb, > /* fall thru' */ > > case ACTIVATE_MSG: > - skb_linearize(skb); > - hdr = buf_msg(skb); > > /* Complete own link name with peer's interface name */ > if_name = strrchr(l->name, ':') + 1; > @@ -1400,7 +1423,7 @@ static int tipc_link_proto_rcv(struct tipc_link *l, > struct > sk_buff *skb, > break; > if (msg_data_sz(hdr) < TIPC_MAX_IF_NAME) > break; > - strncpy(if_name, msg_data(hdr), TIPC_MAX_IF_NAME); > + strncpy(if_name, data, TIPC_MAX_IF_NAME); > > /* Update own tolerance if peer indicates a non-zero value */ > if (in_range(peers_tol, TIPC_MIN_LINK_TOL, > TIPC_MAX_LINK_TOL)) > @@ -1448,6 +1471,8 @@ static int tipc_link_proto_rcv(struct tipc_link *l, > struct > sk_buff *skb, > rc = TIPC_LINK_UP_EVT; > break; > } > + tipc_mon_rcv(l->net, data, dlen, l->addr, > + &l->mon_state, l->bearer_id); > > /* Send NACK if peer has sent pkts we haven't received yet */ > if (more(peers_snd_nxt, rcv_nxt) && !tipc_link_is_synching(l)) > diff --git a/net/tipc/monitor.c b/net/tipc/monitor.c > new file mode 100644 > index 0000000..14d3021 > --- /dev/null > +++ b/net/tipc/monitor.c > @@ -0,0 +1,651 @@ > +/* > + * net/tipc/monitor.c > + * > + * Copyright (c) 2016, Ericsson AB > + * All rights reserved. > + * > + * Redistribution and use in source and binary forms, with or without > + * modification, are permitted provided that the following conditions are > met: > + * > + * 1. Redistributions of source code must retain the above copyright > + * notice, this list of conditions and the following disclaimer. > + * 2. Redistributions in binary form must reproduce the above copyright > + * notice, this list of conditions and the following disclaimer in the > + * documentation and/or other materials provided with the distribution. > + * 3. Neither the names of the copyright holders nor the names of its > + * contributors may be used to endorse or promote products derived from > + * this software without specific prior written permission. > + * > + * Alternatively, this software may be distributed under the terms of the > + * GNU General Public License ("GPL") version 2 as published by the Free > + * Software Foundation. > + * > + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND > CONTRIBUTORS "AS IS" > + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED > TO, THE > + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A > PARTICULAR PURPOSE > + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR > CONTRIBUTORS BE > + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR > + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, > PROCUREMENT OF > + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR > BUSINESS > + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, > WHETHER IN > + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR > OTHERWISE) > + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED > OF THE > + * POSSIBILITY OF SUCH DAMAGE. > + */ > + > +#include "core.h" > +#include "addr.h" > +#include "monitor.h" > + > +#define MAX_MON_DOMAIN 64 > +#define MON_TIMEOUT 120000 > +#define MAX_PEER_DOWN_EVENTS 4 > + > +/* struct tipc_mon_domain: domain record to be transferred between peers > + * @len: actual size of domain record > + * @gen: current generation of sender's domain > + * @ack_gen: most recent generation of self's domain acked by peer > + * @member_cnt: number of domain member nodes described in this record > + * @up_map: bit map indicating which of the members the sender considers up > + * @members: identity of the domain members > + */ > +struct tipc_mon_domain { > + u16 len; > + u16 gen; > + u16 ack_gen; > + u16 member_cnt; > + u64 up_map; > + u32 members[MAX_MON_DOMAIN]; > +}; > + > +/* struct tipc_peer: state of a peer node and its domain > + * @addr: tipc node identity of peer > + * @head_map: shows which other nodes currently consider peer 'up' > + * @domain: most recent domain record from peer > + * @hash: position in hashed lookup list > + * @list: position in linked list, in circular ascending order by 'addr' > + * @applied: number of reported domain members applied on this monitor list > + * @is_up: peer is up as seen from this node > + * @is_head: peer is assigned domain head as seen from this node > + * @is_local: peer is in local domain and should be continuously monitored > + * @down_cnt: - numbers of other peers which have reported this on lost > + */ > +struct tipc_peer { > + u32 addr; > + struct tipc_mon_domain *domain; > + struct hlist_node hash; > + struct list_head list; > + u8 applied; > + u8 down_cnt; > + bool is_up; > + bool is_head; > + bool is_local; > +}; > + > +struct tipc_monitor { > + struct hlist_head peers[NODE_HTABLE_SIZE]; > + int peer_cnt; > + struct tipc_peer *self; > + rwlock_t lock; > + struct tipc_mon_domain cache; > + u16 list_gen; > + u16 dom_gen; > + struct net *net; > + struct timer_list timer; > + unsigned long timer_intv; > +}; > + > +static struct tipc_monitor *tipc_monitor(struct net *net, int bearer_id) > +{ > + return tipc_net(net)->monitors[bearer_id]; > +} > + > +const int tipc_max_domain_size = sizeof(struct tipc_mon_domain); > + > +/* dom_rec_len(): actual length of domain record for transport > + */ > +static int dom_rec_len(struct tipc_mon_domain *dom, u16 mcnt) > +{ > + return ((void *)&dom->members - (void *)dom) + (mcnt * sizeof(u32)); > +} > + > +/* dom_size() : calculate size of own domain based on number of peers > + */ > +static int dom_size(int peers) > +{ > + int i = 0; > + > + while ((i * i) < peers) > + i++; > + return i < MAX_MON_DOMAIN ? i : MAX_MON_DOMAIN; > +} > + > +static void map_set(u64 *up_map, int i, unsigned int v) > +{ > + *up_map &= ~(1 << i); > + *up_map |= (v << i); > +} > + > +static int map_get(u64 up_map, int i) > +{ > + return (up_map & (1 << i)) >> i; > +} > + > +static struct tipc_peer *peer_prev(struct tipc_peer *peer) > +{ > + return list_last_entry(&peer->list, struct tipc_peer, list); > +} > + > +static struct tipc_peer *peer_nxt(struct tipc_peer *peer) > +{ > + return list_first_entry(&peer->list, struct tipc_peer, list); > +} > + > +static struct tipc_peer *peer_head(struct tipc_peer *peer) > +{ > + while (!peer->is_head) > + peer = peer_prev(peer); > + return peer; > +} > + > +static struct tipc_peer *get_peer(struct tipc_monitor *mon, u32 addr) > +{ > + struct tipc_peer *peer; > + unsigned int thash = tipc_hashfn(addr); > + > + hlist_for_each_entry(peer, &mon->peers[thash], hash) { > + if (peer->addr == addr) > + return peer; > + } > + return NULL; > +} > + > +static struct tipc_peer *get_self(struct net *net, int bearer_id) > +{ > + struct tipc_monitor *mon = tipc_monitor(net, bearer_id); > + > + return mon->self; > +} > + > +static inline bool tipc_mon_is_active(struct net *net, struct tipc_monitor > *mon) > +{ > + struct tipc_net *tn = tipc_net(net); > + > + return mon->peer_cnt > tn->mon_threshold; > +} > + > +/* mon_identify_lost_members() : - identify amd mark potentially lost > members > + */ > +static void mon_identify_lost_members(struct tipc_peer *peer, > + struct tipc_mon_domain *dom_bef, > + int applied_bef) > +{ > + struct tipc_peer *member = peer; > + struct tipc_mon_domain *dom_aft = peer->domain; > + int applied_aft = peer->applied; > + int i; > + > + for (i = 0; i < applied_bef; i++) { > + member = peer_nxt(member); > + > + /* Do nothing if self or peer already see member as down */ > + if (!member->is_up || !map_get(dom_bef->up_map, i)) > + continue; > + > + /* Loss of local node must be detected by active probing */ > + if (member->is_local) > + continue; > + > + /* Start probing if member was removed from applied domain */ > + if (!applied_aft || (applied_aft < i)) { > + member->down_cnt = 1; > + continue; > + } > + > + /* Member loss is confirmed if it is still in applied domain */ > + if (!map_get(dom_aft->up_map, i)) > + member->down_cnt++; > + } > +} > + > +/* mon_apply_domain() : match a peer's domain record against monitor list > + */ > +static void mon_apply_domain(struct tipc_monitor *mon, > + struct tipc_peer *peer) > +{ > + struct tipc_mon_domain *dom = peer->domain; > + struct tipc_peer *member; > + u32 addr; > + int i; > + > + if (!dom || !peer->is_up) > + return; > + > + /* Scan across domain members and match against monitor list */ > + peer->applied = 0; > + member = peer_nxt(peer); > + for (i = 0; i < dom->member_cnt; i++) { > + addr = dom->members[i]; > + if (addr != member->addr) > + return; > + peer->applied++; > + member = peer_nxt(member); > + } > +} > + > +/* mon_update_local_domain() : update after peer addition/removal/up/down > + */ > +static void mon_update_local_domain(struct tipc_monitor *mon) > +{ > + struct tipc_peer *self = mon->self; > + struct tipc_mon_domain *cache = &mon->cache; > + struct tipc_mon_domain *dom = self->domain; > + struct tipc_peer *peer = self; > + u64 prev_up_map = dom->up_map; > + u16 member_cnt, i; > + bool diff; > + > + /* Update local domain size based on current size of cluster */ > + member_cnt = dom_size(mon->peer_cnt) - 1; > + self->applied = member_cnt; > + > + /* Update native and cached outgoing local domain records */ > + dom->len = dom_rec_len(dom, member_cnt); > + diff = dom->member_cnt != member_cnt; > + dom->member_cnt = member_cnt; > + for (i = 0; i < member_cnt; i++) { > + peer = peer_nxt(peer); > + diff |= dom->members[i] != peer->addr; > + dom->members[i] = peer->addr; > + map_set(&dom->up_map, i, peer->is_up); > + cache->members[i] = htonl(peer->addr); > + } > + diff |= dom->up_map != prev_up_map; > + if (!diff) > + return; > + dom->gen = ++mon->dom_gen; > + cache->len = htons(dom->len); > + cache->gen = htons(dom->gen); > + cache->member_cnt = htons(member_cnt); > + cache->up_map = cpu_to_be64(dom->up_map); > + mon_apply_domain(mon, self); > +} > + > +/* mon_update_neighbors() : update preceding neighbors of added/removed > peer > + */ > +static void mon_update_neighbors(struct tipc_monitor *mon, > + struct tipc_peer *peer) > +{ > + int dz, i; > + > + dz = dom_size(mon->peer_cnt); > + for (i = 0; i < dz; i++) { > + mon_apply_domain(mon, peer); > + peer = peer_prev(peer); > + } > +} > + > +/* mon_assign_roles() : reassign peer roles after a network change > + * The monitor list is consistent at this stage; i.e., each peer is > monitoring > + * a set of domain members as matched between domain record and the > monitor list > + */ > +static void mon_assign_roles(struct tipc_monitor *mon, struct tipc_peer > *head) > +{ > + struct tipc_peer *peer = peer_nxt(head); > + struct tipc_peer *self = mon->self; > + int i = 0; > + > + for (; peer != self; peer = peer_nxt(peer)) { > + peer->is_local = false; > + > + /* Update domain member */ > + if (i++ < head->applied) { > + peer->is_head = false; > + if (head == self) > + peer->is_local = true; > + continue; > + } > + /* Assign next domain head */ > + if (!peer->is_up) > + continue; > + if (peer->is_head) > + break; > + head = peer; > + head->is_head = true; > + i = 0; > + } > + mon->list_gen++; > +} > + > +void tipc_mon_remove_peer(struct net *net, u32 addr, int bearer_id) > +{ > + struct tipc_monitor *mon = tipc_monitor(net, bearer_id); > + struct tipc_peer *self = get_self(net, bearer_id); > + struct tipc_peer *peer, *prev, *head; > + > + write_lock_bh(&mon->lock); > + peer = get_peer(mon, addr); > + if (!peer) > + goto exit; > + prev = peer_prev(peer); > + list_del(&peer->list); > + hlist_del(&peer->hash); > + kfree(peer->domain); > + kfree(peer); > + mon->peer_cnt--; > + head = peer_head(prev); > + if (head == self) > + mon_update_local_domain(mon); > + mon_update_neighbors(mon, prev); > + > + /* Revert to full-mesh monitoring if we reach threshold */ > + if (!tipc_mon_is_active(net, mon)) { > + list_for_each_entry(peer, &self->list, list) { > + kfree(peer->domain); > + peer->domain = NULL; > + peer->applied = 0; > + } > + } > + mon_assign_roles(mon, head); > +exit: > + write_unlock_bh(&mon->lock); > +} > + > +static bool tipc_mon_add_peer(struct tipc_monitor *mon, u32 addr, > + struct tipc_peer **peer) > +{ > + struct tipc_peer *self = mon->self; > + struct tipc_peer *cur, *prev, *p; > + > + p = kzalloc(sizeof(*p), GFP_ATOMIC); > + *peer = p; > + if (!p) > + return false; > + p->addr = addr; > + > + /* Add new peer to lookup list */ > + INIT_LIST_HEAD(&p->list); > + hlist_add_head(&p->hash, &mon->peers[tipc_hashfn(addr)]); > + > + /* Sort new peer into iterator list, in ascending circular order */ > + prev = self; > + list_for_each_entry(cur, &self->list, list) { > + if ((addr > prev->addr) && (addr < cur->addr)) > + break; > + if (((addr < cur->addr) || (addr > prev->addr)) && > + (prev->addr > cur->addr)) > + break; > + prev = cur; > + } > + list_add_tail(&p->list, &cur->list); > + mon->peer_cnt++; > + mon_update_neighbors(mon, p); > + return true; > +} > + > +void tipc_mon_peer_up(struct net *net, u32 addr, int bearer_id) > +{ > + struct tipc_monitor *mon = tipc_monitor(net, bearer_id); > + struct tipc_peer *self = get_self(net, bearer_id); > + struct tipc_peer *peer, *head; > + > + write_lock_bh(&mon->lock); > + peer = get_peer(mon, addr); > + if (!peer && !tipc_mon_add_peer(mon, addr, &peer)) > + goto exit; > + peer->is_up = true; > + head = peer_head(peer); > + if (head == self) > + mon_update_local_domain(mon); > + mon_assign_roles(mon, head); > +exit: > + write_unlock_bh(&mon->lock); > +} > + > +void tipc_mon_peer_down(struct net *net, u32 addr, int bearer_id) > +{ > + struct tipc_monitor *mon = tipc_monitor(net, bearer_id); > + struct tipc_peer *self = get_self(net, bearer_id); > + struct tipc_peer *peer, *head; > + struct tipc_mon_domain *dom; > + int applied; > + > + write_lock_bh(&mon->lock); > + peer = get_peer(mon, addr); > + if (!peer) { > + pr_warn("Mon: unknown link %x/%u DOWN\n", addr, > bearer_id); > + goto exit; > + } > + applied = peer->applied; > + peer->applied = 0; > + dom = peer->domain; > + peer->domain = NULL; > + if (peer->is_head) > + mon_identify_lost_members(peer, dom, applied); > + kfree(dom); > + peer->is_up = false; > + peer->is_head = false; > + peer->is_local = false; > + peer->down_cnt = 0; > + head = peer_head(peer); > + if (head == self) > + mon_update_local_domain(mon); > + mon_assign_roles(mon, head); > +exit: > + write_unlock_bh(&mon->lock); > +} > + > +/* tipc_mon_rcv - process monitor domain event message > + */ > +void tipc_mon_rcv(struct net *net, void *data, u16 dlen, u32 addr, > + struct tipc_mon_state *state, int bearer_id) > +{ > + struct tipc_monitor *mon = tipc_monitor(net, bearer_id); > + struct tipc_mon_domain *arrv_dom = data; > + struct tipc_mon_domain dom_bef; > + struct tipc_mon_domain *dom; > + struct tipc_peer *peer; > + u16 new_member_cnt = ntohs(arrv_dom->member_cnt); > + int new_dlen = dom_rec_len(arrv_dom, new_member_cnt); > + u16 new_gen = ntohs(arrv_dom->gen); > + u16 acked_gen = ntohs(arrv_dom->ack_gen); > + bool probing = state->probing; > + int i, applied_bef; > + > + state->probing = false; > + if (!dlen) > + return; > + > + /* Sanity check received domain record */ > + if ((dlen != ntohs(arrv_dom->len)) || (dlen != new_dlen)) { > + pr_warn_ratelimited("Received illegal domain record\n"); > + return; > + } > + > + /* Synch generation numbers with peer if link just came up */ > + if (!state->synched) { > + state->peer_gen = new_gen - 1; > + state->acked_gen = acked_gen; > + state->synched = true; > + } > + > + if (more(acked_gen, state->acked_gen)) > + state->acked_gen = acked_gen; > + > + /* Drop duplicate unless we are waiting for a probe response */ > + if (!more(new_gen, state->peer_gen) && !probing) > + return; > + > + write_lock_bh(&mon->lock); > + peer = get_peer(mon, addr); > + if (!peer || !peer->is_up) > + goto exit; > + > + /* Peer is confirmed, stop any ongoing probing */ > + peer->down_cnt = 0; > + > + /* Task is done for duplicate record */ > + if (!more(new_gen, state->peer_gen)) > + goto exit; > + > + state->peer_gen = new_gen; > + > + /* Cache current domain record for later use */ > + dom_bef.member_cnt = 0; > + dom = peer->domain; > + if (dom) > + memcpy(&dom_bef, dom, dom->len); > + > + /* Transform and store received domain record */ > + if (!dom || (dom->len < new_dlen)) { > + kfree(dom); > + dom = kmalloc(new_dlen, GFP_ATOMIC); > + peer->domain = dom; > + if (!dom) > + goto exit; > + } > + dom->len = new_dlen; > + dom->gen = new_gen; > + dom->member_cnt = new_member_cnt; > + dom->up_map = be64_to_cpu(arrv_dom->up_map); > + for (i = 0; i < new_member_cnt; i++) > + dom->members[i] = ntohl(arrv_dom->members[i]); > + > + /* Update peers affected by this domain record */ > + applied_bef = peer->applied; > + mon_apply_domain(mon, peer); > + mon_identify_lost_members(peer, &dom_bef, applied_bef); > + mon_assign_roles(mon, peer_head(peer)); > +exit: > + write_unlock_bh(&mon->lock); > +} > + > +void tipc_mon_prep(struct net *net, void *data, int *dlen, > + struct tipc_mon_state *state, int bearer_id) > +{ > + struct tipc_monitor *mon = tipc_monitor(net, bearer_id); > + struct tipc_mon_domain *dom = data; > + u16 gen = mon->dom_gen; > + u16 len; > + > + if (!tipc_mon_is_active(net, mon)) > + return; > + > + /* Send only a dummy record with ack if peer has acked our last sent */ > + if (likely(state->acked_gen == gen)) { > + len = dom_rec_len(dom, 0); > + *dlen = len; > + dom->len = htons(len); > + dom->gen = htons(gen); > + dom->ack_gen = htons(state->peer_gen); > + dom->member_cnt = 0; > + return; > + } > + /* Send the full record */ > + read_lock_bh(&mon->lock); > + len = ntohs(mon->cache.len); > + *dlen = len; > + memcpy(data, &mon->cache, len); > + read_unlock_bh(&mon->lock); > + dom->ack_gen = htons(state->peer_gen); > +} > + > +void tipc_mon_get_state(struct net *net, u32 addr, > + struct tipc_mon_state *state, > + int bearer_id) > +{ > + struct tipc_monitor *mon = tipc_monitor(net, bearer_id); > + struct tipc_peer *peer; > + > + /* Used cached state if table has not changed */ > + if (!state->probing && > + (state->list_gen == mon->list_gen) && > + (state->acked_gen == mon->dom_gen)) > + return; > + > + read_lock_bh(&mon->lock); > + peer = get_peer(mon, addr); > + if (peer) { > + state->probing = state->acked_gen != mon->dom_gen; > + state->probing |= peer->down_cnt; > + state->reset |= peer->down_cnt >= MAX_PEER_DOWN_EVENTS; > + state->monitoring = peer->is_local; > + state->monitoring |= peer->is_head; > + state->list_gen = mon->list_gen; > + } > + read_unlock_bh(&mon->lock); > +} > + > +static void mon_timeout(unsigned long m) > +{ > + struct tipc_monitor *mon = (void *)m; > + struct tipc_peer *self; > + int best_member_cnt = dom_size(mon->peer_cnt) - 1; > + > + write_lock_bh(&mon->lock); > + self = mon->self; > + if (self && (best_member_cnt != self->applied)) { > + mon_update_local_domain(mon); > + mon_assign_roles(mon, self); > + } > + write_unlock_bh(&mon->lock); > + mod_timer(&mon->timer, jiffies + mon->timer_intv); > +} > + > +int tipc_mon_create(struct net *net, int bearer_id) > +{ > + struct tipc_net *tn = tipc_net(net); > + struct tipc_monitor *mon; > + struct tipc_peer *self; > + struct tipc_mon_domain *dom; > + > + if (tn->monitors[bearer_id]) > + return 0; > + > + mon = kzalloc(sizeof(*mon), GFP_ATOMIC); > + self = kzalloc(sizeof(*self), GFP_ATOMIC); > + dom = kzalloc(sizeof(*dom), GFP_ATOMIC); > + if (!mon || !self || !dom) { > + kfree(mon); > + kfree(self); > + kfree(dom); > + return -ENOMEM; > + } > + tn->monitors[bearer_id] = mon; > + rwlock_init(&mon->lock); > + mon->net = net; > + mon->peer_cnt = 1; > + mon->self = self; > + self->domain = dom; > + self->addr = tipc_own_addr(net); > + self->is_up = true; > + self->is_head = true; > + INIT_LIST_HEAD(&self->list); > + setup_timer(&mon->timer, mon_timeout, (unsigned long)mon); > + mon->timer_intv = msecs_to_jiffies(MON_TIMEOUT + (tn->random & > 0xffff)); > + mod_timer(&mon->timer, jiffies + mon->timer_intv); > + return 0; > +} > + > +void tipc_mon_delete(struct net *net, int bearer_id) > +{ > + struct tipc_net *tn = tipc_net(net); > + struct tipc_monitor *mon = tipc_monitor(net, bearer_id); > + struct tipc_peer *self = get_self(net, bearer_id); > + struct tipc_peer *peer, *tmp; > + > + write_lock_bh(&mon->lock); > + tn->monitors[bearer_id] = NULL; > + list_for_each_entry_safe(peer, tmp, &self->list, list) { > + list_del(&peer->list); > + hlist_del(&peer->hash); > + kfree(peer->domain); > + kfree(peer); > + } > + mon->self = NULL; > + write_unlock_bh(&mon->lock); > + del_timer_sync(&mon->timer); > + kfree(self->domain); > + kfree(self); > + kfree(mon); > +} > diff --git a/net/tipc/monitor.h b/net/tipc/monitor.h > new file mode 100644 > index 0000000..598459c > --- /dev/null > +++ b/net/tipc/monitor.h > @@ -0,0 +1,73 @@ > +/* > + * net/tipc/monitor.h > + * > + * Copyright (c) 2015, Ericsson AB > + * All rights reserved. > + * > + * Redistribution and use in source and binary forms, with or without > + * modification, are permitted provided that the following conditions are > met: > + * > + * 1. Redistributions of source code must retain the above copyright > + * notice, this list of conditions and the following disclaimer. > + * 2. Redistributions in binary form must reproduce the above copyright > + * notice, this list of conditions and the following disclaimer in the > + * documentation and/or other materials provided with the distribution. > + * 3. Neither the names of the copyright holders nor the names of its > + * contributors may be used to endorse or promote products derived from > + * this software without specific prior written permission. > + * > + * Alternatively, this software may be distributed under the terms of the > + * GNU General Public License ("GPL") version 2 as published by the Free > + * Software Foundation. > + * > + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND > CONTRIBUTORS "AS IS" > + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED > TO, THE > + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A > PARTICULAR PURPOSE > + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR > CONTRIBUTORS BE > + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR > + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, > PROCUREMENT OF > + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR > BUSINESS > + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, > WHETHER IN > + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR > OTHERWISE) > + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED > OF THE > + * POSSIBILITY OF SUCH DAMAGE. > + */ > + > +#ifndef _TIPC_MONITOR_H > +#define _TIPC_MONITOR_H > + > +/* struct tipc_mon_state: link instance's cache of monitor list and domain > state > + * @list_gen: current generation of this node's monitor list > + * @gen: current generation of this node's local domain > + * @peer_gen: most recent domain generation received from peer > + * @acked_gen: most recent generation of self's domain acked by peer > + * @monitoring: this peer endpoint should continuously monitored > + * @probing: peer endpoint should be temporarily probed for potential loss > + * @synched: domain record's generation has been synched with peer after > reset > + */ > +struct tipc_mon_state { > + u16 list_gen; > + u16 peer_gen; > + u16 acked_gen; > + bool monitoring :1; > + bool probing :1; > + bool reset :1; > + bool synched :1; > +}; > + > +int tipc_mon_create(struct net *net, int bearer_id); > +void tipc_mon_delete(struct net *net, int bearer_id); > + > +void tipc_mon_peer_up(struct net *net, u32 addr, int bearer_id); > +void tipc_mon_peer_down(struct net *net, u32 addr, int bearer_id); > +void tipc_mon_prep(struct net *net, void *data, int *dlen, > + struct tipc_mon_state *state, int bearer_id); > +void tipc_mon_rcv(struct net *net, void *data, u16 dlen, u32 addr, > + struct tipc_mon_state *state, int bearer_id); > +void tipc_mon_get_state(struct net *net, u32 addr, > + struct tipc_mon_state *state, > + int bearer_id); > +void tipc_mon_remove_peer(struct net *net, u32 addr, int bearer_id); > + > +extern const int tipc_max_domain_size; > +#endif > diff --git a/net/tipc/node.c b/net/tipc/node.c > index d6a490f..a3fc0a3 100644 > --- a/net/tipc/node.c > +++ b/net/tipc/node.c > @@ -40,6 +40,7 @@ > #include "name_distr.h" > #include "socket.h" > #include "bcast.h" > +#include "monitor.h" > #include "discover.h" > #include "netlink.h" > > @@ -205,17 +206,6 @@ u16 tipc_node_get_capabilities(struct net *net, u32 > addr) > return caps; > } > > -/* > - * A trivial power-of-two bitmask technique is used for speed, since this > - * operation is done for every incoming TIPC packet. The number of hash table > - * entries has been chosen so that no hash chain exceeds 8 nodes and will > - * usually be much smaller (typically only a single node). > - */ > -static unsigned int tipc_hashfn(u32 addr) > -{ > - return addr & (NODE_HTABLE_SIZE - 1); > -} > - > static void tipc_node_kref_release(struct kref *kref) > { > struct tipc_node *n = container_of(kref, struct tipc_node, kref); > @@ -279,6 +269,7 @@ static void tipc_node_write_unlock(struct tipc_node *n) > u32 addr = 0; > u32 flags = n->action_flags; > u32 link_id = 0; > + u32 bearer_id; > struct list_head *publ_list; > > if (likely(!flags)) { > @@ -288,6 +279,7 @@ static void tipc_node_write_unlock(struct tipc_node *n) > > addr = n->addr; > link_id = n->link_id; > + bearer_id = link_id & 0xffff; > publ_list = &n->publ_list; > > n->action_flags &= ~(TIPC_NOTIFY_NODE_DOWN | > TIPC_NOTIFY_NODE_UP | > @@ -301,13 +293,16 @@ static void tipc_node_write_unlock(struct tipc_node > *n) > if (flags & TIPC_NOTIFY_NODE_UP) > tipc_named_node_up(net, addr); > > - if (flags & TIPC_NOTIFY_LINK_UP) > + if (flags & TIPC_NOTIFY_LINK_UP) { > + tipc_mon_peer_up(net, addr, bearer_id); > tipc_nametbl_publish(net, TIPC_LINK_STATE, addr, addr, > TIPC_NODE_SCOPE, link_id, addr); > - > - if (flags & TIPC_NOTIFY_LINK_DOWN) > + } > + if (flags & TIPC_NOTIFY_LINK_DOWN) { > + tipc_mon_peer_down(net, addr, bearer_id); > tipc_nametbl_withdraw(net, TIPC_LINK_STATE, addr, > link_id, addr); > + } > } > > struct tipc_node *tipc_node_create(struct net *net, u32 addr, u16 > capabilities) > @@ -691,6 +686,7 @@ static void tipc_node_link_down(struct tipc_node *n, int > bearer_id, bool delete) > struct tipc_link *l = le->link; > struct tipc_media_addr *maddr; > struct sk_buff_head xmitq; > + int old_bearer_id = bearer_id; > > if (!l) > return; > @@ -710,6 +706,8 @@ static void tipc_node_link_down(struct tipc_node *n, int > bearer_id, bool delete) > tipc_link_fsm_evt(l, LINK_RESET_EVT); > } > tipc_node_write_unlock(n); > + if (delete) > + tipc_mon_remove_peer(n->net, n->addr, old_bearer_id); > tipc_bearer_xmit(n->net, bearer_id, &xmitq, maddr); > tipc_sk_rcv(n->net, &le->inputq); > } > -- > 1.9.1 ------------------------------------------------------------------------------ What NetFlow Analyzer can do for you? Monitors network bandwidth and traffic patterns at an interface-level. Reveals which users, apps, and protocols are consuming the most bandwidth. Provides multi-vendor support for NetFlow, J-Flow, sFlow and other flows. Make informed decisions using capacity planning reports. https://ad.doubleclick.net/ddm/clk/305295220;132659582;e _______________________________________________ tipc-discussion mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/tipc-discussion
