diff options
Diffstat (limited to 'net/tipc/node.c')
-rw-r--r-- | net/tipc/node.c | 449 |
1 files changed, 444 insertions, 5 deletions
diff --git a/net/tipc/node.c b/net/tipc/node.c index 47d5f84c90c5..e110ba67422e 100644 --- a/net/tipc/node.c +++ b/net/tipc/node.c | |||
@@ -42,6 +42,87 @@ | |||
42 | #include "bcast.h" | 42 | #include "bcast.h" |
43 | #include "discover.h" | 43 | #include "discover.h" |
44 | 44 | ||
45 | /* Out-of-range value for node signature */ | ||
46 | #define INVALID_NODE_SIG 0x10000 | ||
47 | |||
48 | #define INVALID_BEARER_ID -1 | ||
49 | |||
50 | /* Flags used to take different actions according to flag type | ||
51 | * TIPC_NOTIFY_NODE_DOWN: notify node is down | ||
52 | * TIPC_NOTIFY_NODE_UP: notify node is up | ||
53 | * TIPC_DISTRIBUTE_NAME: publish or withdraw link state name type | ||
54 | */ | ||
55 | enum { | ||
56 | TIPC_NOTIFY_NODE_DOWN = (1 << 3), | ||
57 | TIPC_NOTIFY_NODE_UP = (1 << 4), | ||
58 | TIPC_NOTIFY_LINK_UP = (1 << 6), | ||
59 | TIPC_NOTIFY_LINK_DOWN = (1 << 7) | ||
60 | }; | ||
61 | |||
62 | struct tipc_link_entry { | ||
63 | struct tipc_link *link; | ||
64 | spinlock_t lock; /* per link */ | ||
65 | u32 mtu; | ||
66 | struct sk_buff_head inputq; | ||
67 | struct tipc_media_addr maddr; | ||
68 | }; | ||
69 | |||
70 | struct tipc_bclink_entry { | ||
71 | struct tipc_link *link; | ||
72 | struct sk_buff_head inputq1; | ||
73 | struct sk_buff_head arrvq; | ||
74 | struct sk_buff_head inputq2; | ||
75 | struct sk_buff_head namedq; | ||
76 | }; | ||
77 | |||
78 | /** | ||
79 | * struct tipc_node - TIPC node structure | ||
80 | * @addr: network address of node | ||
81 | * @ref: reference counter to node object | ||
82 | * @lock: rwlock governing access to structure | ||
83 | * @net: the applicable net namespace | ||
84 | * @hash: links to adjacent nodes in unsorted hash chain | ||
85 | * @inputq: pointer to input queue containing messages for msg event | ||
86 | * @namedq: pointer to name table input queue with name table messages | ||
87 | * @active_links: bearer ids of active links, used as index into links[] array | ||
88 | * @links: array containing references to all links to node | ||
89 | * @action_flags: bit mask of different types of node actions | ||
90 | * @state: connectivity state vs peer node | ||
91 | * @sync_point: sequence number where synch/failover is finished | ||
92 | * @list: links to adjacent nodes in sorted list of cluster's nodes | ||
93 | * @working_links: number of working links to node (both active and standby) | ||
94 | * @link_cnt: number of links to node | ||
95 | * @capabilities: bitmap, indicating peer node's functional capabilities | ||
96 | * @signature: node instance identifier | ||
97 | * @link_id: local and remote bearer ids of changing link, if any | ||
98 | * @publ_list: list of publications | ||
99 | * @rcu: rcu struct for tipc_node | ||
100 | */ | ||
101 | struct tipc_node { | ||
102 | u32 addr; | ||
103 | struct kref kref; | ||
104 | rwlock_t lock; | ||
105 | struct net *net; | ||
106 | struct hlist_node hash; | ||
107 | int active_links[2]; | ||
108 | struct tipc_link_entry links[MAX_BEARERS]; | ||
109 | struct tipc_bclink_entry bc_entry; | ||
110 | int action_flags; | ||
111 | struct list_head list; | ||
112 | int state; | ||
113 | u16 sync_point; | ||
114 | int link_cnt; | ||
115 | u16 working_links; | ||
116 | u16 capabilities; | ||
117 | u32 signature; | ||
118 | u32 link_id; | ||
119 | struct list_head publ_list; | ||
120 | struct list_head conn_sks; | ||
121 | unsigned long keepalive_intv; | ||
122 | struct timer_list timer; | ||
123 | struct rcu_head rcu; | ||
124 | }; | ||
125 | |||
45 | /* Node FSM states and events: | 126 | /* Node FSM states and events: |
46 | */ | 127 | */ |
47 | enum { | 128 | enum { |
@@ -75,6 +156,9 @@ static void node_lost_contact(struct tipc_node *n, struct sk_buff_head *inputq); | |||
75 | static void tipc_node_delete(struct tipc_node *node); | 156 | static void tipc_node_delete(struct tipc_node *node); |
76 | static void tipc_node_timeout(unsigned long data); | 157 | static void tipc_node_timeout(unsigned long data); |
77 | static void tipc_node_fsm_evt(struct tipc_node *n, int evt); | 158 | static void tipc_node_fsm_evt(struct tipc_node *n, int evt); |
159 | static struct tipc_node *tipc_node_find(struct net *net, u32 addr); | ||
160 | static void tipc_node_put(struct tipc_node *node); | ||
161 | static bool tipc_node_is_up(struct tipc_node *n); | ||
78 | 162 | ||
79 | struct tipc_sock_conn { | 163 | struct tipc_sock_conn { |
80 | u32 port; | 164 | u32 port; |
@@ -83,12 +167,54 @@ struct tipc_sock_conn { | |||
83 | struct list_head list; | 167 | struct list_head list; |
84 | }; | 168 | }; |
85 | 169 | ||
170 | static const struct nla_policy tipc_nl_link_policy[TIPC_NLA_LINK_MAX + 1] = { | ||
171 | [TIPC_NLA_LINK_UNSPEC] = { .type = NLA_UNSPEC }, | ||
172 | [TIPC_NLA_LINK_NAME] = { | ||
173 | .type = NLA_STRING, | ||
174 | .len = TIPC_MAX_LINK_NAME | ||
175 | }, | ||
176 | [TIPC_NLA_LINK_MTU] = { .type = NLA_U32 }, | ||
177 | [TIPC_NLA_LINK_BROADCAST] = { .type = NLA_FLAG }, | ||
178 | [TIPC_NLA_LINK_UP] = { .type = NLA_FLAG }, | ||
179 | [TIPC_NLA_LINK_ACTIVE] = { .type = NLA_FLAG }, | ||
180 | [TIPC_NLA_LINK_PROP] = { .type = NLA_NESTED }, | ||
181 | [TIPC_NLA_LINK_STATS] = { .type = NLA_NESTED }, | ||
182 | [TIPC_NLA_LINK_RX] = { .type = NLA_U32 }, | ||
183 | [TIPC_NLA_LINK_TX] = { .type = NLA_U32 } | ||
184 | }; | ||
185 | |||
86 | static const struct nla_policy tipc_nl_node_policy[TIPC_NLA_NODE_MAX + 1] = { | 186 | static const struct nla_policy tipc_nl_node_policy[TIPC_NLA_NODE_MAX + 1] = { |
87 | [TIPC_NLA_NODE_UNSPEC] = { .type = NLA_UNSPEC }, | 187 | [TIPC_NLA_NODE_UNSPEC] = { .type = NLA_UNSPEC }, |
88 | [TIPC_NLA_NODE_ADDR] = { .type = NLA_U32 }, | 188 | [TIPC_NLA_NODE_ADDR] = { .type = NLA_U32 }, |
89 | [TIPC_NLA_NODE_UP] = { .type = NLA_FLAG } | 189 | [TIPC_NLA_NODE_UP] = { .type = NLA_FLAG } |
90 | }; | 190 | }; |
91 | 191 | ||
192 | static struct tipc_link *node_active_link(struct tipc_node *n, int sel) | ||
193 | { | ||
194 | int bearer_id = n->active_links[sel & 1]; | ||
195 | |||
196 | if (unlikely(bearer_id == INVALID_BEARER_ID)) | ||
197 | return NULL; | ||
198 | |||
199 | return n->links[bearer_id].link; | ||
200 | } | ||
201 | |||
202 | int tipc_node_get_mtu(struct net *net, u32 addr, u32 sel) | ||
203 | { | ||
204 | struct tipc_node *n; | ||
205 | int bearer_id; | ||
206 | unsigned int mtu = MAX_MSG_SIZE; | ||
207 | |||
208 | n = tipc_node_find(net, addr); | ||
209 | if (unlikely(!n)) | ||
210 | return mtu; | ||
211 | |||
212 | bearer_id = n->active_links[sel & 1]; | ||
213 | if (likely(bearer_id != INVALID_BEARER_ID)) | ||
214 | mtu = n->links[bearer_id].mtu; | ||
215 | tipc_node_put(n); | ||
216 | return mtu; | ||
217 | } | ||
92 | /* | 218 | /* |
93 | * A trivial power-of-two bitmask technique is used for speed, since this | 219 | * A trivial power-of-two bitmask technique is used for speed, since this |
94 | * operation is done for every incoming TIPC packet. The number of hash table | 220 | * operation is done for every incoming TIPC packet. The number of hash table |
@@ -107,7 +233,7 @@ static void tipc_node_kref_release(struct kref *kref) | |||
107 | tipc_node_delete(node); | 233 | tipc_node_delete(node); |
108 | } | 234 | } |
109 | 235 | ||
110 | void tipc_node_put(struct tipc_node *node) | 236 | static void tipc_node_put(struct tipc_node *node) |
111 | { | 237 | { |
112 | kref_put(&node->kref, tipc_node_kref_release); | 238 | kref_put(&node->kref, tipc_node_kref_release); |
113 | } | 239 | } |
@@ -120,7 +246,7 @@ static void tipc_node_get(struct tipc_node *node) | |||
120 | /* | 246 | /* |
121 | * tipc_node_find - locate specified node object, if it exists | 247 | * tipc_node_find - locate specified node object, if it exists |
122 | */ | 248 | */ |
123 | struct tipc_node *tipc_node_find(struct net *net, u32 addr) | 249 | static struct tipc_node *tipc_node_find(struct net *net, u32 addr) |
124 | { | 250 | { |
125 | struct tipc_net *tn = net_generic(net, tipc_net_id); | 251 | struct tipc_net *tn = net_generic(net, tipc_net_id); |
126 | struct tipc_node *node; | 252 | struct tipc_node *node; |
@@ -141,12 +267,12 @@ struct tipc_node *tipc_node_find(struct net *net, u32 addr) | |||
141 | return NULL; | 267 | return NULL; |
142 | } | 268 | } |
143 | 269 | ||
144 | void tipc_node_read_lock(struct tipc_node *n) | 270 | static void tipc_node_read_lock(struct tipc_node *n) |
145 | { | 271 | { |
146 | read_lock_bh(&n->lock); | 272 | read_lock_bh(&n->lock); |
147 | } | 273 | } |
148 | 274 | ||
149 | void tipc_node_read_unlock(struct tipc_node *n) | 275 | static void tipc_node_read_unlock(struct tipc_node *n) |
150 | { | 276 | { |
151 | read_unlock_bh(&n->lock); | 277 | read_unlock_bh(&n->lock); |
152 | } | 278 | } |
@@ -588,7 +714,7 @@ static void tipc_node_link_down(struct tipc_node *n, int bearer_id, bool delete) | |||
588 | tipc_sk_rcv(n->net, &le->inputq); | 714 | tipc_sk_rcv(n->net, &le->inputq); |
589 | } | 715 | } |
590 | 716 | ||
591 | bool tipc_node_is_up(struct tipc_node *n) | 717 | static bool tipc_node_is_up(struct tipc_node *n) |
592 | { | 718 | { |
593 | return n->active_links[0] != INVALID_BEARER_ID; | 719 | return n->active_links[0] != INVALID_BEARER_ID; |
594 | } | 720 | } |
@@ -1465,3 +1591,316 @@ out: | |||
1465 | 1591 | ||
1466 | return skb->len; | 1592 | return skb->len; |
1467 | } | 1593 | } |
1594 | |||
1595 | /* tipc_link_find_owner - locate owner node of link by link's name | ||
1596 | * @net: the applicable net namespace | ||
1597 | * @name: pointer to link name string | ||
1598 | * @bearer_id: pointer to index in 'node->links' array where the link was found. | ||
1599 | * | ||
1600 | * Returns pointer to node owning the link, or 0 if no matching link is found. | ||
1601 | */ | ||
1602 | static struct tipc_node *tipc_link_find_owner(struct net *net, | ||
1603 | const char *link_name, | ||
1604 | unsigned int *bearer_id) | ||
1605 | { | ||
1606 | struct tipc_net *tn = net_generic(net, tipc_net_id); | ||
1607 | struct tipc_link *l_ptr; | ||
1608 | struct tipc_node *n_ptr; | ||
1609 | struct tipc_node *found_node = NULL; | ||
1610 | int i; | ||
1611 | |||
1612 | *bearer_id = 0; | ||
1613 | rcu_read_lock(); | ||
1614 | list_for_each_entry_rcu(n_ptr, &tn->node_list, list) { | ||
1615 | tipc_node_read_lock(n_ptr); | ||
1616 | for (i = 0; i < MAX_BEARERS; i++) { | ||
1617 | l_ptr = n_ptr->links[i].link; | ||
1618 | if (l_ptr && !strcmp(l_ptr->name, link_name)) { | ||
1619 | *bearer_id = i; | ||
1620 | found_node = n_ptr; | ||
1621 | break; | ||
1622 | } | ||
1623 | } | ||
1624 | tipc_node_read_unlock(n_ptr); | ||
1625 | if (found_node) | ||
1626 | break; | ||
1627 | } | ||
1628 | rcu_read_unlock(); | ||
1629 | |||
1630 | return found_node; | ||
1631 | } | ||
1632 | |||
1633 | int tipc_nl_node_set_link(struct sk_buff *skb, struct genl_info *info) | ||
1634 | { | ||
1635 | int err; | ||
1636 | int res = 0; | ||
1637 | int bearer_id; | ||
1638 | char *name; | ||
1639 | struct tipc_link *link; | ||
1640 | struct tipc_node *node; | ||
1641 | struct nlattr *attrs[TIPC_NLA_LINK_MAX + 1]; | ||
1642 | struct net *net = sock_net(skb->sk); | ||
1643 | |||
1644 | if (!info->attrs[TIPC_NLA_LINK]) | ||
1645 | return -EINVAL; | ||
1646 | |||
1647 | err = nla_parse_nested(attrs, TIPC_NLA_LINK_MAX, | ||
1648 | info->attrs[TIPC_NLA_LINK], | ||
1649 | tipc_nl_link_policy); | ||
1650 | if (err) | ||
1651 | return err; | ||
1652 | |||
1653 | if (!attrs[TIPC_NLA_LINK_NAME]) | ||
1654 | return -EINVAL; | ||
1655 | |||
1656 | name = nla_data(attrs[TIPC_NLA_LINK_NAME]); | ||
1657 | |||
1658 | if (strcmp(name, tipc_bclink_name) == 0) | ||
1659 | return tipc_nl_bc_link_set(net, attrs); | ||
1660 | |||
1661 | node = tipc_link_find_owner(net, name, &bearer_id); | ||
1662 | if (!node) | ||
1663 | return -EINVAL; | ||
1664 | |||
1665 | tipc_node_read_lock(node); | ||
1666 | |||
1667 | link = node->links[bearer_id].link; | ||
1668 | if (!link) { | ||
1669 | res = -EINVAL; | ||
1670 | goto out; | ||
1671 | } | ||
1672 | |||
1673 | if (attrs[TIPC_NLA_LINK_PROP]) { | ||
1674 | struct nlattr *props[TIPC_NLA_PROP_MAX + 1]; | ||
1675 | |||
1676 | err = tipc_nl_parse_link_prop(attrs[TIPC_NLA_LINK_PROP], | ||
1677 | props); | ||
1678 | if (err) { | ||
1679 | res = err; | ||
1680 | goto out; | ||
1681 | } | ||
1682 | |||
1683 | if (props[TIPC_NLA_PROP_TOL]) { | ||
1684 | u32 tol; | ||
1685 | |||
1686 | tol = nla_get_u32(props[TIPC_NLA_PROP_TOL]); | ||
1687 | link->tolerance = tol; | ||
1688 | tipc_link_proto_xmit(link, STATE_MSG, 0, 0, tol, 0); | ||
1689 | } | ||
1690 | if (props[TIPC_NLA_PROP_PRIO]) { | ||
1691 | u32 prio; | ||
1692 | |||
1693 | prio = nla_get_u32(props[TIPC_NLA_PROP_PRIO]); | ||
1694 | link->priority = prio; | ||
1695 | tipc_link_proto_xmit(link, STATE_MSG, 0, 0, 0, prio); | ||
1696 | } | ||
1697 | if (props[TIPC_NLA_PROP_WIN]) { | ||
1698 | u32 win; | ||
1699 | |||
1700 | win = nla_get_u32(props[TIPC_NLA_PROP_WIN]); | ||
1701 | tipc_link_set_queue_limits(link, win); | ||
1702 | } | ||
1703 | } | ||
1704 | |||
1705 | out: | ||
1706 | tipc_node_read_unlock(node); | ||
1707 | |||
1708 | return res; | ||
1709 | } | ||
1710 | |||
1711 | int tipc_nl_node_get_link(struct sk_buff *skb, struct genl_info *info) | ||
1712 | { | ||
1713 | struct net *net = genl_info_net(info); | ||
1714 | struct tipc_nl_msg msg; | ||
1715 | char *name; | ||
1716 | int err; | ||
1717 | |||
1718 | msg.portid = info->snd_portid; | ||
1719 | msg.seq = info->snd_seq; | ||
1720 | |||
1721 | if (!info->attrs[TIPC_NLA_LINK_NAME]) | ||
1722 | return -EINVAL; | ||
1723 | name = nla_data(info->attrs[TIPC_NLA_LINK_NAME]); | ||
1724 | |||
1725 | msg.skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | ||
1726 | if (!msg.skb) | ||
1727 | return -ENOMEM; | ||
1728 | |||
1729 | if (strcmp(name, tipc_bclink_name) == 0) { | ||
1730 | err = tipc_nl_add_bc_link(net, &msg); | ||
1731 | if (err) { | ||
1732 | nlmsg_free(msg.skb); | ||
1733 | return err; | ||
1734 | } | ||
1735 | } else { | ||
1736 | int bearer_id; | ||
1737 | struct tipc_node *node; | ||
1738 | struct tipc_link *link; | ||
1739 | |||
1740 | node = tipc_link_find_owner(net, name, &bearer_id); | ||
1741 | if (!node) | ||
1742 | return -EINVAL; | ||
1743 | |||
1744 | tipc_node_read_lock(node); | ||
1745 | link = node->links[bearer_id].link; | ||
1746 | if (!link) { | ||
1747 | tipc_node_read_unlock(node); | ||
1748 | nlmsg_free(msg.skb); | ||
1749 | return -EINVAL; | ||
1750 | } | ||
1751 | |||
1752 | err = __tipc_nl_add_link(net, &msg, link, 0); | ||
1753 | tipc_node_read_unlock(node); | ||
1754 | if (err) { | ||
1755 | nlmsg_free(msg.skb); | ||
1756 | return err; | ||
1757 | } | ||
1758 | } | ||
1759 | |||
1760 | return genlmsg_reply(msg.skb, info); | ||
1761 | } | ||
1762 | |||
1763 | int tipc_nl_node_reset_link_stats(struct sk_buff *skb, struct genl_info *info) | ||
1764 | { | ||
1765 | int err; | ||
1766 | char *link_name; | ||
1767 | unsigned int bearer_id; | ||
1768 | struct tipc_link *link; | ||
1769 | struct tipc_node *node; | ||
1770 | struct nlattr *attrs[TIPC_NLA_LINK_MAX + 1]; | ||
1771 | struct net *net = sock_net(skb->sk); | ||
1772 | struct tipc_link_entry *le; | ||
1773 | |||
1774 | if (!info->attrs[TIPC_NLA_LINK]) | ||
1775 | return -EINVAL; | ||
1776 | |||
1777 | err = nla_parse_nested(attrs, TIPC_NLA_LINK_MAX, | ||
1778 | info->attrs[TIPC_NLA_LINK], | ||
1779 | tipc_nl_link_policy); | ||
1780 | if (err) | ||
1781 | return err; | ||
1782 | |||
1783 | if (!attrs[TIPC_NLA_LINK_NAME]) | ||
1784 | return -EINVAL; | ||
1785 | |||
1786 | link_name = nla_data(attrs[TIPC_NLA_LINK_NAME]); | ||
1787 | |||
1788 | if (strcmp(link_name, tipc_bclink_name) == 0) { | ||
1789 | err = tipc_bclink_reset_stats(net); | ||
1790 | if (err) | ||
1791 | return err; | ||
1792 | return 0; | ||
1793 | } | ||
1794 | |||
1795 | node = tipc_link_find_owner(net, link_name, &bearer_id); | ||
1796 | if (!node) | ||
1797 | return -EINVAL; | ||
1798 | |||
1799 | le = &node->links[bearer_id]; | ||
1800 | tipc_node_read_lock(node); | ||
1801 | spin_lock_bh(&le->lock); | ||
1802 | link = node->links[bearer_id].link; | ||
1803 | if (!link) { | ||
1804 | spin_unlock_bh(&le->lock); | ||
1805 | tipc_node_read_unlock(node); | ||
1806 | return -EINVAL; | ||
1807 | } | ||
1808 | link_reset_statistics(link); | ||
1809 | spin_unlock_bh(&le->lock); | ||
1810 | tipc_node_read_unlock(node); | ||
1811 | return 0; | ||
1812 | } | ||
1813 | |||
1814 | /* Caller should hold node lock */ | ||
1815 | static int __tipc_nl_add_node_links(struct net *net, struct tipc_nl_msg *msg, | ||
1816 | struct tipc_node *node, u32 *prev_link) | ||
1817 | { | ||
1818 | u32 i; | ||
1819 | int err; | ||
1820 | |||
1821 | for (i = *prev_link; i < MAX_BEARERS; i++) { | ||
1822 | *prev_link = i; | ||
1823 | |||
1824 | if (!node->links[i].link) | ||
1825 | continue; | ||
1826 | |||
1827 | err = __tipc_nl_add_link(net, msg, | ||
1828 | node->links[i].link, NLM_F_MULTI); | ||
1829 | if (err) | ||
1830 | return err; | ||
1831 | } | ||
1832 | *prev_link = 0; | ||
1833 | |||
1834 | return 0; | ||
1835 | } | ||
1836 | |||
1837 | int tipc_nl_link_dump(struct sk_buff *skb, struct netlink_callback *cb) | ||
1838 | { | ||
1839 | struct net *net = sock_net(skb->sk); | ||
1840 | struct tipc_net *tn = net_generic(net, tipc_net_id); | ||
1841 | struct tipc_node *node; | ||
1842 | struct tipc_nl_msg msg; | ||
1843 | u32 prev_node = cb->args[0]; | ||
1844 | u32 prev_link = cb->args[1]; | ||
1845 | int done = cb->args[2]; | ||
1846 | int err; | ||
1847 | |||
1848 | if (done) | ||
1849 | return 0; | ||
1850 | |||
1851 | msg.skb = skb; | ||
1852 | msg.portid = NETLINK_CB(cb->skb).portid; | ||
1853 | msg.seq = cb->nlh->nlmsg_seq; | ||
1854 | |||
1855 | rcu_read_lock(); | ||
1856 | if (prev_node) { | ||
1857 | node = tipc_node_find(net, prev_node); | ||
1858 | if (!node) { | ||
1859 | /* We never set seq or call nl_dump_check_consistent() | ||
1860 | * this means that setting prev_seq here will cause the | ||
1861 | * consistence check to fail in the netlink callback | ||
1862 | * handler. Resulting in the last NLMSG_DONE message | ||
1863 | * having the NLM_F_DUMP_INTR flag set. | ||
1864 | */ | ||
1865 | cb->prev_seq = 1; | ||
1866 | goto out; | ||
1867 | } | ||
1868 | tipc_node_put(node); | ||
1869 | |||
1870 | list_for_each_entry_continue_rcu(node, &tn->node_list, | ||
1871 | list) { | ||
1872 | tipc_node_read_lock(node); | ||
1873 | err = __tipc_nl_add_node_links(net, &msg, node, | ||
1874 | &prev_link); | ||
1875 | tipc_node_read_unlock(node); | ||
1876 | if (err) | ||
1877 | goto out; | ||
1878 | |||
1879 | prev_node = node->addr; | ||
1880 | } | ||
1881 | } else { | ||
1882 | err = tipc_nl_add_bc_link(net, &msg); | ||
1883 | if (err) | ||
1884 | goto out; | ||
1885 | |||
1886 | list_for_each_entry_rcu(node, &tn->node_list, list) { | ||
1887 | tipc_node_read_lock(node); | ||
1888 | err = __tipc_nl_add_node_links(net, &msg, node, | ||
1889 | &prev_link); | ||
1890 | tipc_node_read_unlock(node); | ||
1891 | if (err) | ||
1892 | goto out; | ||
1893 | |||
1894 | prev_node = node->addr; | ||
1895 | } | ||
1896 | } | ||
1897 | done = 1; | ||
1898 | out: | ||
1899 | rcu_read_unlock(); | ||
1900 | |||
1901 | cb->args[0] = prev_node; | ||
1902 | cb->args[1] = prev_link; | ||
1903 | cb->args[2] = done; | ||
1904 | |||
1905 | return skb->len; | ||
1906 | } | ||