Merge branch 'master' into net-next
diff --git a/bridge/vlan.c b/bridge/vlan.c
index d3505b5..0b6c690 100644
--- a/bridge/vlan.c
+++ b/bridge/vlan.c
@@ -15,14 +15,16 @@
 #include "utils.h"
 
 static unsigned int filter_index, filter_vlan;
+static int last_ifidx = -1;
 
 json_writer_t *jw_global = NULL;
 
 static void usage(void)
 {
-	fprintf(stderr, "Usage: bridge vlan { add | del } vid VLAN_ID dev DEV [ pvid] [ untagged ]\n");
+	fprintf(stderr, "Usage: bridge vlan { add | del } vid VLAN_ID dev DEV [ pvid ] [ untagged ]\n");
 	fprintf(stderr, "                                                     [ self ] [ master ]\n");
 	fprintf(stderr, "       bridge vlan { show } [ dev DEV ] [ vid VLAN_ID ]\n");
+	fprintf(stderr, "       bridge vlan { stats } [ dev DEV ] [ vid VLAN_ID ]\n");
 	exit(-1);
 }
 
@@ -298,6 +300,88 @@
 	return 0;
 }
 
+static void print_one_vlan_stats(FILE *fp,
+				 const struct bridge_vlan_xstats *vstats,
+				 int ifindex)
+{
+	const char *ifname = "";
+
+	if (filter_vlan && filter_vlan != vstats->vid)
+		return;
+	/* skip pure port entries, they'll be dumped via the slave stats call */
+	if ((vstats->flags & BRIDGE_VLAN_INFO_MASTER) &&
+	    !(vstats->flags & BRIDGE_VLAN_INFO_BRENTRY))
+		return;
+
+	if (last_ifidx != ifindex) {
+		ifname = ll_index_to_name(ifindex);
+		last_ifidx = ifindex;
+	}
+	fprintf(fp, "%-16s  %hu", ifname, vstats->vid);
+	if (vstats->flags & BRIDGE_VLAN_INFO_PVID)
+		fprintf(fp, " PVID");
+	if (vstats->flags & BRIDGE_VLAN_INFO_UNTAGGED)
+		fprintf(fp, " Egress Untagged");
+	fprintf(fp, "\n");
+	fprintf(fp, "%-16s    RX: %llu bytes %llu packets\n",
+		"", vstats->rx_bytes, vstats->rx_packets);
+	fprintf(fp, "%-16s    TX: %llu bytes %llu packets\n",
+		"", vstats->tx_bytes, vstats->tx_packets);
+}
+
+static void print_vlan_stats_attr(FILE *fp, struct rtattr *attr, int ifindex)
+{
+	struct rtattr *brtb[LINK_XSTATS_TYPE_MAX+1];
+	struct rtattr *i, *list;
+	int rem;
+
+	parse_rtattr(brtb, LINK_XSTATS_TYPE_MAX, RTA_DATA(attr),
+		     RTA_PAYLOAD(attr));
+	if (!brtb[LINK_XSTATS_TYPE_BRIDGE])
+		return;
+
+	list = brtb[LINK_XSTATS_TYPE_BRIDGE];
+	rem = RTA_PAYLOAD(list);
+	for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+		if (i->rta_type != BRIDGE_XSTATS_VLAN)
+			continue;
+		print_one_vlan_stats(fp, RTA_DATA(i), ifindex);
+	}
+}
+
+static int print_vlan_stats(const struct sockaddr_nl *who,
+			    struct nlmsghdr *n,
+			    void *arg)
+{
+	struct if_stats_msg *ifsm = NLMSG_DATA(n);
+	struct rtattr *tb[IFLA_STATS_MAX+1];
+	int len = n->nlmsg_len;
+	FILE *fp = arg;
+
+	len -= NLMSG_LENGTH(sizeof(*ifsm));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (filter_index && filter_index != ifsm->ifindex)
+		return 0;
+
+	parse_rtattr(tb, IFLA_STATS_MAX, IFLA_STATS_RTA(ifsm), len);
+
+	/* We have to check if any of the two attrs are usable */
+	if (tb[IFLA_STATS_LINK_XSTATS])
+		print_vlan_stats_attr(fp, tb[IFLA_STATS_LINK_XSTATS],
+				      ifsm->ifindex);
+
+	if (tb[IFLA_STATS_LINK_XSTATS_SLAVE])
+		print_vlan_stats_attr(fp, tb[IFLA_STATS_LINK_XSTATS_SLAVE],
+				      ifsm->ifindex);
+
+	fflush(fp);
+	return 0;
+}
+
 static int vlan_show(int argc, char **argv)
 {
 	char *filter_dev = NULL;
@@ -325,28 +409,58 @@
 		}
 	}
 
-	if (rtnl_wilddump_req_filter(&rth, PF_BRIDGE, RTM_GETLINK,
-				    (compress_vlans ?
-				    RTEXT_FILTER_BRVLAN_COMPRESSED :
-				    RTEXT_FILTER_BRVLAN)) < 0) {
-		perror("Cannont send dump request");
-		exit(1);
-	}
-
-	if (json_output) {
-		jw_global = jsonw_new(stdout);
-		if (!jw_global) {
-			fprintf(stderr, "Error allocation json object\n");
+	if (!show_stats) {
+		if (rtnl_wilddump_req_filter(&rth, PF_BRIDGE, RTM_GETLINK,
+					     (compress_vlans ?
+						RTEXT_FILTER_BRVLAN_COMPRESSED :
+						RTEXT_FILTER_BRVLAN)) < 0) {
+			perror("Cannont send dump request");
 			exit(1);
 		}
-		jsonw_start_object(jw_global);
-	} else {
-		printf("port\tvlan ids\n");
-	}
+		if (json_output) {
+			jw_global = jsonw_new(stdout);
+			if (!jw_global) {
+				fprintf(stderr, "Error allocation json object\n");
+				exit(1);
+			}
+			jsonw_start_object(jw_global);
+		} else {
+			printf("port\tvlan ids\n");
+		}
 
-	if (rtnl_dump_filter(&rth, print_vlan, stdout) < 0) {
-		fprintf(stderr, "Dump ternminated\n");
-		exit(1);
+		if (rtnl_dump_filter(&rth, print_vlan, stdout) < 0) {
+			fprintf(stderr, "Dump ternminated\n");
+			exit(1);
+		}
+	} else {
+		__u32 filt_mask;
+
+		filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS);
+		if (rtnl_wilddump_stats_req_filter(&rth, AF_UNSPEC,
+						   RTM_GETSTATS,
+						   filt_mask) < 0) {
+			perror("Cannont send dump request");
+			exit(1);
+		}
+
+		printf("%-16s vlan id\n", "port");
+		if (rtnl_dump_filter(&rth, print_vlan_stats, stdout) < 0) {
+			fprintf(stderr, "Dump terminated\n");
+			exit(1);
+		}
+
+		filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS_SLAVE);
+		if (rtnl_wilddump_stats_req_filter(&rth, AF_UNSPEC,
+						   RTM_GETSTATS,
+						   filt_mask) < 0) {
+			perror("Cannont send slave dump request");
+			exit(1);
+		}
+
+		if (rtnl_dump_filter(&rth, print_vlan_stats, stdout) < 0) {
+			fprintf(stderr, "Dump terminated\n");
+			exit(1);
+		}
 	}
 
 	if (jw_global) {
@@ -357,7 +471,6 @@
 	return 0;
 }
 
-
 int do_vlan(int argc, char **argv)
 {
 	ll_init_map(&rth);
@@ -373,8 +486,9 @@
 			return vlan_show(argc-1, argv+1);
 		if (matches(*argv, "help") == 0)
 			usage();
-	} else
+	} else {
 		return vlan_show(0, NULL);
+	}
 
 	fprintf(stderr, "Command \"%s\" is unknown, try \"bridge vlan help\".\n", *argv);
 	exit(-1);
diff --git a/include/libnetlink.h b/include/libnetlink.h
index f7b85dc..483509c 100644
--- a/include/libnetlink.h
+++ b/include/libnetlink.h
@@ -44,6 +44,9 @@
 int rtnl_wilddump_req_filter_fn(struct rtnl_handle *rth, int fam, int type,
 				req_filter_fn_t fn)
 	__attribute__((warn_unused_result));
+int rtnl_wilddump_stats_req_filter(struct rtnl_handle *rth, int fam, int type,
+				   __u32 filt_mask)
+	__attribute__((warn_unused_result));
 int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req,
 			     int len)
 	__attribute__((warn_unused_result));
@@ -202,6 +205,11 @@
 #define NETNS_PAYLOAD(n)	NLMSG_PAYLOAD(n, sizeof(struct rtgenmsg))
 #endif
 
+#ifndef IFLA_STATS_RTA
+#define IFLA_STATS_RTA(r) \
+	((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct if_stats_msg))))
+#endif
+
 /* User defined nlmsg_type which is used mostly for logging netlink
  * messages from dump file */
 #define NLMSG_TSTAMP	15
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 766db7f..d4a6e1f 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -95,6 +95,7 @@
 	BPF_PROG_TYPE_SCHED_ACT,
 	BPF_PROG_TYPE_TRACEPOINT,
 	BPF_PROG_TYPE_XDP,
+	BPF_PROG_TYPE_PERF_EVENT,
 };
 
 #define BPF_PSEUDO_MAP_FD	1
@@ -375,6 +376,56 @@
 	 */
 	BPF_FUNC_probe_write_user,
 
+	/**
+	 * bpf_current_task_under_cgroup(map, index) - Check cgroup2 membership of current task
+	 * @map: pointer to bpf_map in BPF_MAP_TYPE_CGROUP_ARRAY type
+	 * @index: index of the cgroup in the bpf_map
+	 * Return:
+	 *   == 0 current failed the cgroup2 descendant test
+	 *   == 1 current succeeded the cgroup2 descendant test
+	 *    < 0 error
+	 */
+	BPF_FUNC_current_task_under_cgroup,
+
+	/**
+	 * bpf_skb_change_tail(skb, len, flags)
+	 * The helper will resize the skb to the given new size,
+	 * to be used f.e. with control messages.
+	 * @skb: pointer to skb
+	 * @len: new skb length
+	 * @flags: reserved
+	 * Return: 0 on success or negative error
+	 */
+	BPF_FUNC_skb_change_tail,
+
+	/**
+	 * bpf_skb_pull_data(skb, len)
+	 * The helper will pull in non-linear data in case the
+	 * skb is non-linear and not all of len are part of the
+	 * linear section. Only needed for read/write with direct
+	 * packet access.
+	 * @skb: pointer to skb
+	 * @len: len to make read/writeable
+	 * Return: 0 on success or negative error
+	 */
+	BPF_FUNC_skb_pull_data,
+
+	/**
+	 * bpf_csum_update(skb, csum)
+	 * Adds csum into skb->csum in case of CHECKSUM_COMPLETE.
+	 * @skb: pointer to skb
+	 * @csum: csum to add
+	 * Return: csum on success or negative error
+	 */
+	BPF_FUNC_csum_update,
+
+	/**
+	 * bpf_set_hash_invalid(skb)
+	 * Invalidate current skb>hash.
+	 * @skb: pointer to skb
+	 */
+	BPF_FUNC_set_hash_invalid,
+
 	__BPF_FUNC_MAX_ID,
 };
 
diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h
index d9c76fe..b7393dd 100644
--- a/include/linux/if_bridge.h
+++ b/include/linux/if_bridge.h
@@ -140,7 +140,7 @@
 	__u64 tx_bytes;
 	__u64 tx_packets;
 	__u16 vid;
-	__u16 pad1;
+	__u16 flags;
 	__u32 pad2;
 };
 
diff --git a/include/linux/if_link.h b/include/linux/if_link.h
index 1feb708..20965c9 100644
--- a/include/linux/if_link.h
+++ b/include/linux/if_link.h
@@ -316,6 +316,7 @@
 	IFLA_BRPORT_FLUSH,
 	IFLA_BRPORT_MULTICAST_ROUTER,
 	IFLA_BRPORT_PAD,
+	IFLA_BRPORT_MCAST_FLOOD,
 	__IFLA_BRPORT_MAX
 };
 #define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1)
@@ -461,6 +462,7 @@
 enum ipvlan_mode {
 	IPVLAN_MODE_L2 = 0,
 	IPVLAN_MODE_L3,
+	IPVLAN_MODE_L3S,
 	IPVLAN_MODE_MAX
 };
 
@@ -615,7 +617,7 @@
 enum {
 	IFLA_VF_UNSPEC,
 	IFLA_VF_MAC,		/* Hardware queue specific attributes */
-	IFLA_VF_VLAN,
+	IFLA_VF_VLAN,		/* VLAN ID and QoS */
 	IFLA_VF_TX_RATE,	/* Max TX Bandwidth Allocation */
 	IFLA_VF_SPOOFCHK,	/* Spoof Checking on/off switch */
 	IFLA_VF_LINK_STATE,	/* link state enable/disable/auto switch */
@@ -627,6 +629,7 @@
 	IFLA_VF_TRUST,		/* Trust VF */
 	IFLA_VF_IB_NODE_GUID,	/* VF Infiniband node GUID */
 	IFLA_VF_IB_PORT_GUID,	/* VF Infiniband port GUID */
+	IFLA_VF_VLAN_LIST,	/* nested list of vlans, option for QinQ */
 	__IFLA_VF_MAX,
 };
 
@@ -643,6 +646,22 @@
 	__u32 qos;
 };
 
+enum {
+	IFLA_VF_VLAN_INFO_UNSPEC,
+	IFLA_VF_VLAN_INFO,	/* VLAN ID, QoS and VLAN protocol */
+	__IFLA_VF_VLAN_INFO_MAX,
+};
+
+#define IFLA_VF_VLAN_INFO_MAX (__IFLA_VF_VLAN_INFO_MAX - 1)
+#define MAX_VLAN_LIST_LEN 1
+
+struct ifla_vf_vlan_info {
+	__u32 vf;
+	__u32 vlan; /* 0 - 4095, 0 disables VLAN filter */
+	__u32 qos;
+	__be16 vlan_proto; /* VLAN protocol either 802.1Q or 802.1ad */
+};
+
 struct ifla_vf_tx_rate {
 	__u32 vf;
 	__u32 rate; /* Max TX bandwidth in Mbps, 0 disables throttling */
@@ -823,6 +842,7 @@
 	IFLA_STATS_LINK_64,
 	IFLA_STATS_LINK_XSTATS,
 	IFLA_STATS_LINK_XSTATS_SLAVE,
+	IFLA_STATS_LINK_OFFLOAD_XSTATS,
 	__IFLA_STATS_MAX,
 };
 
@@ -842,6 +862,14 @@
 };
 #define LINK_XSTATS_TYPE_MAX (__LINK_XSTATS_TYPE_MAX - 1)
 
+/* These are stats embedded into IFLA_STATS_LINK_OFFLOAD_XSTATS */
+enum {
+	IFLA_OFFLOAD_XSTATS_UNSPEC,
+	IFLA_OFFLOAD_XSTATS_CPU_HIT, /* struct rtnl_link_stats64 */
+	__IFLA_OFFLOAD_XSTATS_MAX
+};
+#define IFLA_OFFLOAD_XSTATS_MAX (__IFLA_OFFLOAD_XSTATS_MAX - 1)
+
 /* XDP section */
 
 enum {
diff --git a/include/linux/if_tunnel.h b/include/linux/if_tunnel.h
index 24aa175..4f975f5 100644
--- a/include/linux/if_tunnel.h
+++ b/include/linux/if_tunnel.h
@@ -27,9 +27,23 @@
 #define GRE_SEQ		__cpu_to_be16(0x1000)
 #define GRE_STRICT	__cpu_to_be16(0x0800)
 #define GRE_REC		__cpu_to_be16(0x0700)
-#define GRE_FLAGS	__cpu_to_be16(0x00F8)
+#define GRE_ACK		__cpu_to_be16(0x0080)
+#define GRE_FLAGS	__cpu_to_be16(0x0078)
 #define GRE_VERSION	__cpu_to_be16(0x0007)
 
+#define GRE_IS_CSUM(f)		((f) & GRE_CSUM)
+#define GRE_IS_ROUTING(f)	((f) & GRE_ROUTING)
+#define GRE_IS_KEY(f)		((f) & GRE_KEY)
+#define GRE_IS_SEQ(f)		((f) & GRE_SEQ)
+#define GRE_IS_STRICT(f)	((f) & GRE_STRICT)
+#define GRE_IS_REC(f)		((f) & GRE_REC)
+#define GRE_IS_ACK(f)		((f) & GRE_ACK)
+
+#define GRE_VERSION_0		__cpu_to_be16(0x0000)
+#define GRE_VERSION_1		__cpu_to_be16(0x0001)
+#define GRE_PROTO_PPP		__cpu_to_be16(0x880b)
+#define GRE_PPTP_KEY_MASK	__cpu_to_be32(0xffff)
+
 struct ip_tunnel_parm {
 	char			name[IFNAMSIZ];
 	int			link;
@@ -60,6 +74,7 @@
 	IFLA_IPTUN_ENCAP_FLAGS,
 	IFLA_IPTUN_ENCAP_SPORT,
 	IFLA_IPTUN_ENCAP_DPORT,
+	IFLA_IPTUN_COLLECT_METADATA,
 	__IFLA_IPTUN_MAX,
 };
 #define IFLA_IPTUN_MAX	(__IFLA_IPTUN_MAX - 1)
diff --git a/include/linux/inet_diag.h b/include/linux/inet_diag.h
index beb74ee..f5f5c1b 100644
--- a/include/linux/inet_diag.h
+++ b/include/linux/inet_diag.h
@@ -73,6 +73,7 @@
 	INET_DIAG_BC_S_COND,
 	INET_DIAG_BC_D_COND,
 	INET_DIAG_BC_DEV_COND,   /* u32 ifindex */
+	INET_DIAG_BC_MARK_COND,
 };
 
 struct inet_diag_hostcond {
@@ -82,6 +83,11 @@
 	__be32	addr[0];
 };
 
+struct inet_diag_markcond {
+	__u32 mark;
+	__u32 mask;
+};
+
 /* Base info structure. It contains socket identity (addrs/ports/cookie)
  * and, alas, the information shown by netstat. */
 struct inet_diag_msg {
@@ -117,6 +123,8 @@
 	INET_DIAG_LOCALS,
 	INET_DIAG_PEERS,
 	INET_DIAG_PAD,
+	INET_DIAG_MARK,
+	INET_DIAG_BBRINFO,
 	__INET_DIAG_MAX,
 };
 
@@ -150,8 +158,20 @@
 	__u32	dctcp_ab_tot;
 };
 
+/* INET_DIAG_BBRINFO */
+
+struct tcp_bbr_info {
+	/* u64 bw: max-filtered BW (app throughput) estimate in Byte per sec: */
+	__u32	bbr_bw_lo;		/* lower 32 bits of bw */
+	__u32	bbr_bw_hi;		/* upper 32 bits of bw */
+	__u32	bbr_min_rtt;		/* min-filtered RTT in uSec */
+	__u32	bbr_pacing_gain;	/* pacing gain shifted left 8 bits */
+	__u32	bbr_cwnd_gain;		/* cwnd gain shifted left 8 bits */
+};
+
 union tcp_cc_info {
 	struct tcpvegas_info	vegas;
 	struct tcp_dctcp_info	dctcp;
+	struct tcp_bbr_info	bbr;
 };
 #endif /* _INET_DIAG_H_ */
diff --git a/include/linux/pkt_cls.h b/include/linux/pkt_cls.h
index 5e6c61e..b47ed3a 100644
--- a/include/linux/pkt_cls.h
+++ b/include/linux/pkt_cls.h
@@ -342,6 +342,7 @@
 	TCA_BPF_FD,
 	TCA_BPF_NAME,
 	TCA_BPF_FLAGS,
+	TCA_BPF_FLAGS_GEN,
 	__TCA_BPF_MAX,
 };
 
@@ -374,6 +375,24 @@
 	TCA_FLOWER_KEY_UDP_DST,		/* be16 */
 
 	TCA_FLOWER_FLAGS,
+	TCA_FLOWER_KEY_VLAN_ID,		/* be16 */
+	TCA_FLOWER_KEY_VLAN_PRIO,	/* u8   */
+	TCA_FLOWER_KEY_VLAN_ETH_TYPE,	/* be16 */
+
+	TCA_FLOWER_KEY_ENC_KEY_ID,	/* be32 */
+	TCA_FLOWER_KEY_ENC_IPV4_SRC,	/* be32 */
+	TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK,/* be32 */
+	TCA_FLOWER_KEY_ENC_IPV4_DST,	/* be32 */
+	TCA_FLOWER_KEY_ENC_IPV4_DST_MASK,/* be32 */
+	TCA_FLOWER_KEY_ENC_IPV6_SRC,	/* struct in6_addr */
+	TCA_FLOWER_KEY_ENC_IPV6_SRC_MASK,/* struct in6_addr */
+	TCA_FLOWER_KEY_ENC_IPV6_DST,	/* struct in6_addr */
+	TCA_FLOWER_KEY_ENC_IPV6_DST_MASK,/* struct in6_addr */
+
+	TCA_FLOWER_KEY_TCP_SRC_MASK,	/* be16 */
+	TCA_FLOWER_KEY_TCP_DST_MASK,	/* be16 */
+	TCA_FLOWER_KEY_UDP_SRC_MASK,	/* be16 */
+	TCA_FLOWER_KEY_UDP_DST_MASK,	/* be16 */
 	__TCA_FLOWER_MAX,
 };
 
diff --git a/include/linux/pkt_sched.h b/include/linux/pkt_sched.h
index 2382eed..df7451d 100644
--- a/include/linux/pkt_sched.h
+++ b/include/linux/pkt_sched.h
@@ -792,6 +792,8 @@
 
 	TCA_FQ_ORPHAN_MASK,	/* mask applied to orphaned skb hashes */
 
+	TCA_FQ_LOW_RATE_THRESHOLD, /* per packet delay under this rate */
+
 	__TCA_FQ_MAX
 };
 
@@ -809,7 +811,7 @@
 	__u32	flows;
 	__u32	inactive_flows;
 	__u32	throttled_flows;
-	__u32	pad;
+	__u32	unthrottle_latency_ns;
 };
 
 /* Heavy-Hitter Filter */
diff --git a/include/linux/tc_act/tc_ife.h b/include/linux/tc_act/tc_ife.h
index 4ece02a..cd18360 100644
--- a/include/linux/tc_act/tc_ife.h
+++ b/include/linux/tc_act/tc_ife.h
@@ -32,8 +32,9 @@
 #define IFE_META_HASHID 2
 #define	IFE_META_PRIO 3
 #define	IFE_META_QMAP 4
+#define	IFE_META_TCINDEX 5
 /*Can be overridden at runtime by module option*/
-#define	__IFE_META_MAX 5
+#define	__IFE_META_MAX 6
 #define IFE_META_MAX (__IFE_META_MAX - 1)
 
 #endif
diff --git a/include/linux/tc_act/tc_vlan.h b/include/linux/tc_act/tc_vlan.h
index 31151ff..bddb272 100644
--- a/include/linux/tc_act/tc_vlan.h
+++ b/include/linux/tc_act/tc_vlan.h
@@ -16,6 +16,7 @@
 
 #define TCA_VLAN_ACT_POP	1
 #define TCA_VLAN_ACT_PUSH	2
+#define TCA_VLAN_ACT_MODIFY	3
 
 struct tc_vlan {
 	tc_gen;
@@ -29,6 +30,7 @@
 	TCA_VLAN_PUSH_VLAN_ID,
 	TCA_VLAN_PUSH_VLAN_PROTOCOL,
 	TCA_VLAN_PAD,
+	TCA_VLAN_PUSH_VLAN_PRIORITY,
 	__TCA_VLAN_MAX,
 };
 #define TCA_VLAN_MAX (__TCA_VLAN_MAX - 1)
diff --git a/include/linux/tcp.h b/include/linux/tcp.h
index f3dcdb7..cffa3bb 100644
--- a/include/linux/tcp.h
+++ b/include/linux/tcp.h
@@ -167,6 +167,7 @@
 	__u8	tcpi_backoff;
 	__u8	tcpi_options;
 	__u8	tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4;
+	__u8	tcpi_delivery_rate_app_limited:1;
 
 	__u32	tcpi_rto;
 	__u32	tcpi_ato;
@@ -211,6 +212,8 @@
 	__u32	tcpi_min_rtt;
 	__u32	tcpi_data_segs_in;	/* RFC4898 tcpEStatsDataSegsIn */
 	__u32	tcpi_data_segs_out;	/* RFC4898 tcpEStatsDataSegsOut */
+
+	__u64   tcpi_delivery_rate;
 };
 
 /* for TCP_MD5SIG socket option */
diff --git a/include/linux/tipc_netlink.h b/include/linux/tipc_netlink.h
index 5f3f6d0..f9edd20 100644
--- a/include/linux/tipc_netlink.h
+++ b/include/linux/tipc_netlink.h
@@ -59,6 +59,9 @@
 	TIPC_NL_MON_SET,
 	TIPC_NL_MON_GET,
 	TIPC_NL_MON_PEER_GET,
+	TIPC_NL_PEER_REMOVE,
+	TIPC_NL_BEARER_ADD,
+	TIPC_NL_UDP_GET_REMOTEIP,
 
 	__TIPC_NL_CMD_MAX,
 	TIPC_NL_CMD_MAX = __TIPC_NL_CMD_MAX - 1
@@ -98,6 +101,7 @@
 	TIPC_NLA_UDP_UNSPEC,
 	TIPC_NLA_UDP_LOCAL,		/* sockaddr_storage */
 	TIPC_NLA_UDP_REMOTE,		/* sockaddr_storage */
+	TIPC_NLA_UDP_MULTI_REMOTEIP,	/* flag */
 
 	__TIPC_NLA_UDP_MAX,
 	TIPC_NLA_UDP_MAX = __TIPC_NLA_UDP_MAX - 1
diff --git a/include/linux/xfrm.h b/include/linux/xfrm.h
index d09be24..d2dd1fd 100644
--- a/include/linux/xfrm.h
+++ b/include/linux/xfrm.h
@@ -298,7 +298,7 @@
 	XFRMA_ALG_AUTH_TRUNC,	/* struct xfrm_algo_auth */
 	XFRMA_MARK,		/* struct xfrm_mark */
 	XFRMA_TFCPAD,		/* __u32 */
-	XFRMA_REPLAY_ESN_VAL,	/* struct xfrm_replay_esn */
+	XFRMA_REPLAY_ESN_VAL,	/* struct xfrm_replay_state_esn */
 	XFRMA_SA_EXTRA_FLAGS,	/* __u32 */
 	XFRMA_PROTO,		/* __u8 */
 	XFRMA_ADDRESS_FILTER,	/* struct xfrm_address_filter */
diff --git a/ip/ip_common.h b/ip/ip_common.h
index 1c1cbdf..0147f45 100644
--- a/ip/ip_common.h
+++ b/ip/ip_common.h
@@ -31,6 +31,7 @@
 		  struct rtnl_ctrl_data *ctrl,
 		  struct nlmsghdr *n, void *arg);
 void netns_map_init(void);
+void netns_nsid_socket_init(void);
 int print_nsid(const struct sockaddr_nl *who,
 	       struct nlmsghdr *n, void *arg);
 int do_ipaddr(int argc, char **argv);
diff --git a/ip/iplink_ipvlan.c b/ip/iplink_ipvlan.c
index a6273be..f7735f3 100644
--- a/ip/iplink_ipvlan.c
+++ b/ip/iplink_ipvlan.c
@@ -20,18 +20,7 @@
 
 static void ipvlan_explain(FILE *f)
 {
-	fprintf(f, "Usage: ... ipvlan [ mode { l2 | l3 } ]\n");
-}
-
-static void explain(void)
-{
-	ipvlan_explain(stderr);
-}
-
-static int mode_arg(void)
-{
-	fprintf(stderr, "Error: argument of \"mode\" must be either \"l2\", or \"l3\"\n");
-	return -1;
+	fprintf(f, "Usage: ... ipvlan [ mode { l2 | l3  | l3s } ]\n");
 }
 
 static int ipvlan_parse_opt(struct link_util *lu, int argc, char **argv,
@@ -47,20 +36,24 @@
 				mode = IPVLAN_MODE_L2;
 			else if (strcmp(*argv, "l3") == 0)
 				mode = IPVLAN_MODE_L3;
-			else
-				return mode_arg();
-
+			else if (strcmp(*argv, "l3s") == 0)
+				mode = IPVLAN_MODE_L3S;
+			else {
+				fprintf(stderr, "Error: argument of \"mode\" must be either \"l2\", \"l3\" or \"l3s\"\n");
+				return -1;
+			}
 			addattr16(n, 1024, IFLA_IPVLAN_MODE, mode);
 		} else if (matches(*argv, "help") == 0) {
-			explain();
+			ipvlan_explain(stderr);
 			return -1;
 		} else {
 			fprintf(stderr, "ipvlan: unknown option \"%s\"?\n",
 				*argv);
-			explain();
+			ipvlan_explain(stderr);
 			return -1;
 		}
-		argc--, argv++;
+		argc--;
+		argv++;
 	}
 
 	return 0;
@@ -78,7 +71,8 @@
 
 			fprintf(f, " mode %s ",
 				mode == IPVLAN_MODE_L2 ? "l2" :
-				mode == IPVLAN_MODE_L3 ? "l3" : "unknown");
+				mode == IPVLAN_MODE_L3 ? "l3" :
+				mode == IPVLAN_MODE_L3S ? "l3s" : "unknown");
 		}
 	}
 }
diff --git a/ip/ipmonitor.c b/ip/ipmonitor.c
index 2090a45..c892b8f 100644
--- a/ip/ipmonitor.c
+++ b/ip/ipmonitor.c
@@ -301,6 +301,7 @@
 		exit(1);
 
 	ll_init_map(&rth);
+	netns_nsid_socket_init();
 	netns_map_init();
 
 	if (rtnl_listen(&rth, accept_msg, stdout) < 0)
diff --git a/ip/ipnetns.c b/ip/ipnetns.c
index ccc652c..bd1e901 100644
--- a/ip/ipnetns.c
+++ b/ip/ipnetns.c
@@ -194,6 +194,18 @@
 	free(c);
 }
 
+void netns_nsid_socket_init(void)
+{
+	if (rtnsh.fd > -1 || !ipnetns_have_nsid())
+		return;
+
+	if (rtnl_open(&rtnsh, 0) < 0) {
+		fprintf(stderr, "Cannot open rtnetlink\n");
+		exit(1);
+	}
+
+}
+
 void netns_map_init(void)
 {
 	static int initialized;
@@ -204,11 +216,6 @@
 	if (initialized || !ipnetns_have_nsid())
 		return;
 
-	if (rtnl_open(&rtnsh, 0) < 0) {
-		fprintf(stderr, "Cannot open rtnetlink\n");
-		exit(1);
-	}
-
 	dir = opendir(NETNS_RUN_DIR);
 	if (!dir)
 		return;
@@ -775,17 +782,23 @@
 
 int do_netns(int argc, char **argv)
 {
-	netns_map_init();
+	netns_nsid_socket_init();
 
-	if (argc < 1)
+	if (argc < 1) {
+		netns_map_init();
 		return netns_list(0, NULL);
+	}
 
 	if ((matches(*argv, "list") == 0) || (matches(*argv, "show") == 0) ||
-	    (matches(*argv, "lst") == 0))
+	    (matches(*argv, "lst") == 0)) {
+		netns_map_init();
 		return netns_list(argc-1, argv+1);
+	}
 
-	if ((matches(*argv, "list-id") == 0))
+	if ((matches(*argv, "list-id") == 0)) {
+		netns_map_init();
 		return netns_list_id(argc-1, argv+1);
+	}
 
 	if (matches(*argv, "help") == 0)
 		return usage();
diff --git a/ip/link_ip6tnl.c b/ip/link_ip6tnl.c
index 59162a3..051c89f 100644
--- a/ip/link_ip6tnl.c
+++ b/ip/link_ip6tnl.c
@@ -40,6 +40,7 @@
 	fprintf(f, "          [ noencap ] [ encap { fou | gue | none } ]\n");
 	fprintf(f, "          [ encap-sport PORT ] [ encap-dport PORT ]\n");
 	fprintf(f, "          [ [no]encap-csum ] [ [no]encap-csum6 ] [ [no]encap-remcsum ]\n");
+	fprintf(f, "          [ external ]\n");
 	fprintf(f, "\n");
 	fprintf(f, "Where: NAME      := STRING\n");
 	fprintf(f, "       ADDR      := IPV6_ADDRESS\n");
@@ -89,6 +90,7 @@
 	__u16 encapflags = TUNNEL_ENCAP_FLAG_CSUM6;
 	__u16 encapsport = 0;
 	__u16 encapdport = 0;
+	__u8 metadata = 0;
 
 	if (!(n->nlmsg_flags & NLM_F_CREATE)) {
 		if (rtnl_talk(&rth, &req.n, &req.n, sizeof(req)) < 0) {
@@ -141,6 +143,8 @@
 
 		if (iptuninfo[IFLA_IPTUN_PROTO])
 			proto = rta_getattr_u8(iptuninfo[IFLA_IPTUN_PROTO]);
+		if (iptuninfo[IFLA_IPTUN_COLLECT_METADATA])
+			metadata = 1;
 	}
 
 	while (argc > 0) {
@@ -277,12 +281,18 @@
 			encapflags |= TUNNEL_ENCAP_FLAG_REMCSUM;
 		} else if (strcmp(*argv, "noencap-remcsum") == 0) {
 			encapflags |= ~TUNNEL_ENCAP_FLAG_REMCSUM;
+		} else if (strcmp(*argv, "external") == 0) {
+			metadata = 1;
 		} else
 			usage();
 		argc--, argv++;
 	}
 
 	addattr8(n, 1024, IFLA_IPTUN_PROTO, proto);
+	if (metadata) {
+		addattr_l(n, 1024, IFLA_IPTUN_COLLECT_METADATA, NULL, 0);
+		return 0;
+	}
 	addattr_l(n, 1024, IFLA_IPTUN_LOCAL, &laddr, sizeof(laddr));
 	addattr_l(n, 1024, IFLA_IPTUN_REMOTE, &raddr, sizeof(raddr));
 	addattr8(n, 1024, IFLA_IPTUN_TTL, hop_limit);
diff --git a/ip/link_iptnl.c b/ip/link_iptnl.c
index 7ec3777..1875348 100644
--- a/ip/link_iptnl.c
+++ b/ip/link_iptnl.c
@@ -36,6 +36,7 @@
 		fprintf(f, "          [ mode { ip6ip | ipip | any } ]\n");
 		fprintf(f, "          [ isatap ]\n");
 	}
+	fprintf(f, "          [ external ]\n");
 	fprintf(f, "\n");
 	fprintf(f, "Where: NAME := STRING\n");
 	fprintf(f, "       ADDR := { IP_ADDRESS | any }\n");
@@ -85,6 +86,7 @@
 	__u16 encapflags = 0;
 	__u16 encapsport = 0;
 	__u16 encapdport = 0;
+	__u8 metadata = 0;
 
 	if (!(n->nlmsg_flags & NLM_F_CREATE)) {
 		if (rtnl_talk(&rth, &req.n, &req.n, sizeof(req)) < 0) {
@@ -161,6 +163,8 @@
 		if (iptuninfo[IFLA_IPTUN_6RD_RELAY_PREFIXLEN])
 			ip6rdrelayprefixlen =
 				rta_getattr_u16(iptuninfo[IFLA_IPTUN_6RD_RELAY_PREFIXLEN]);
+		if (iptuninfo[IFLA_IPTUN_COLLECT_METADATA])
+			metadata = 1;
 	}
 
 	while (argc > 0) {
@@ -257,6 +261,8 @@
 			encapflags |= TUNNEL_ENCAP_FLAG_REMCSUM;
 		} else if (strcmp(*argv, "noencap-remcsum") == 0) {
 			encapflags &= ~TUNNEL_ENCAP_FLAG_REMCSUM;
+		} else if (strcmp(*argv, "external") == 0) {
+			metadata = 1;
 		} else if (strcmp(*argv, "6rd-prefix") == 0) {
 			inet_prefix prefix;
 
@@ -291,6 +297,11 @@
 		exit(-1);
 	}
 
+	if (metadata) {
+		addattr_l(n, 1024, IFLA_IPTUN_COLLECT_METADATA, NULL, 0);
+		return 0;
+	}
+
 	addattr32(n, 1024, IFLA_IPTUN_LINK, link);
 	addattr32(n, 1024, IFLA_IPTUN_LOCAL, laddr);
 	addattr32(n, 1024, IFLA_IPTUN_REMOTE, raddr);
diff --git a/lib/libnetlink.c b/lib/libnetlink.c
index a02cf9f..2279935 100644
--- a/lib/libnetlink.c
+++ b/lib/libnetlink.c
@@ -152,6 +152,26 @@
 	return send(rth->fd, (void*)&req, sizeof(req), 0);
 }
 
+int rtnl_wilddump_stats_req_filter(struct rtnl_handle *rth, int fam, int type,
+				   __u32 filt_mask)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct if_stats_msg ifsm;
+	} req;
+
+	memset(&req, 0, sizeof(req));
+	req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct if_stats_msg));
+	req.nlh.nlmsg_type = type;
+	req.nlh.nlmsg_flags = NLM_F_DUMP|NLM_F_REQUEST;
+	req.nlh.nlmsg_pid = 0;
+	req.nlh.nlmsg_seq = rth->dump = ++rth->seq;
+	req.ifsm.family = fam;
+	req.ifsm.filter_mask = filt_mask;
+
+	return send(rth->fd, (void *)&req, sizeof(req), 0);
+}
+
 int rtnl_send(struct rtnl_handle *rth, const void *buf, int len)
 {
 	return send(rth->fd, buf, len, 0);
diff --git a/man/man8/bridge.8 b/man/man8/bridge.8
index bb70442..7bfb068 100644
--- a/man/man8/bridge.8
+++ b/man/man8/bridge.8
@@ -570,6 +570,11 @@
 
 This command displays the current VLAN filter table.
 
+.PP
+With the
+.B -statistics
+option, the command displays per-vlan traffic statistics.
+
 .SH bridge monitor - state monitoring
 
 The
diff --git a/man/man8/tc-flower.8 b/man/man8/tc-flower.8
index 9ae10e6..74f7664 100644
--- a/man/man8/tc-flower.8
+++ b/man/man8/tc-flower.8
@@ -23,7 +23,13 @@
 .R " | { "
 .BR dst_mac " | " src_mac " } "
 .IR mac_address " | "
-.BR eth_type " { " ipv4 " | " ipv6 " | "
+.BR eth_type " { " ipv4 " | " ipv6 " | " 802.1Q " | "
+.IR ETH_TYPE " } | "
+.B vlan_id
+.IR VID " | "
+.B vlan_prio
+.IR PRIORITY " | "
+.BR vlan_eth_type " { " ipv4 " | " ipv6 " | "
 .IR ETH_TYPE " } | "
 .BR ip_proto " { " tcp " | " udp " | "
 .IR IP_PROTO " } | { "
@@ -70,6 +76,23 @@
 Match on source or destination MAC address.
 .TP
 .BI eth_type " ETH_TYPE"
+Match on the next protocol.
+.I ETH_TYPE
+may be either
+.BR ipv4 , ipv6 , 802.1Q ,
+or an unsigned 16bit value in hexadecimal format.
+.TP
+.BI vlan_id " VID"
+Match on vlan tag id.
+.I VID
+is an unsigned 12bit value in decimal format.
+.TP
+.BI vlan_prio " priority"
+Match on vlan tag priority.
+.I PRIORITY
+is an unsigned 3bit value in decimal format.
+.TP
+.BI vlan_eth_type " VLAN_ETH_TYPE"
 Match on layer three protocol.
 .I ETH_TYPE
 may be either
diff --git a/man/man8/tc-vlan.8 b/man/man8/tc-vlan.8
index 4bfd72b..4d0c5c8 100644
--- a/man/man8/tc-vlan.8
+++ b/man/man8/tc-vlan.8
@@ -12,6 +12,8 @@
 .IR PUSH " := "
 .BR push " [ " protocol
 .IR VLANPROTO " ]"
+.BR " [ " priority
+.IR VLANPRIO " ] "
 .BI id " VLANID"
 
 .ti -8
@@ -55,6 +57,9 @@
 Choose the VLAN protocol to use. At the time of writing, the kernel accepts only
 .BR 802.1Q " or " 802.1ad .
 .TP
+.BI priority " VLANPRIO"
+Choose the VLAN priority to use. Decimal number in range of 0-7.
+.TP
 .I CONTROL
 How to continue after executing this action.
 .RS
diff --git a/man/man8/tipc-bearer.8 b/man/man8/tipc-bearer.8
index 846f1db..d95b1e1 100644
--- a/man/man8/tipc-bearer.8
+++ b/man/man8/tipc-bearer.8
@@ -11,6 +11,11 @@
 .in +8
 
 .ti -8
+.B tipc bearer add media udp name
+.IB "NAME " "remoteip " REMOTEIP
+.br
+
+.ti -8
 .B tipc bearer enable
 .RB "[ " domain
 .IR DOMAIN " ]"
@@ -68,7 +73,7 @@
 
 .ti -8
 .B tipc bearer get
-.RB "{ " "priority" " | " tolerance " | " window " } " media
+.RB "[ " "priority" " | " tolerance " | " window " ] " media
 .br
 .RB "{ { " eth " | " ib " } " device
 .IR "DEVICE" " }"
@@ -76,7 +81,8 @@
 .br
 .RB "{ " udp
 .B name
-.IR NAME " }"
+.IR NAME
+.RB "[ " "localip " "| " "localport " "| " "remoteip " "| " "remoteport " "] }"
 .br
 
 .ti -8
@@ -196,6 +202,25 @@
 .B udp
 bearer runs in point-to-point mode.
 
+Multiple
+.B remoteip
+addresses can be added via the
+.B bearer add
+command. Adding one or more unicast
+.B remoteip
+addresses to an existing
+.B udp
+bearer puts the bearer in replicast mode where IP
+multicast is emulated by sending multiple unicast messages to each configured
+.B remoteip.
+When a peer sees a TIPC discovery message from an unknown peer the peer address
+is automatically added to the
+.B remoteip
+(replicast) list, thus only one side of
+a link needs to be manually configured. A
+.B remoteip
+address cannot be added to a multicast bearer.
+
 .TP
 .BI "remoteport " REMOTEPORT
 .br
@@ -212,6 +237,7 @@
 .BR tipc-media (8),
 .BR tipc-nametable (8),
 .BR tipc-node (8),
+.BR tipc-peer (8),
 .BR tipc-socket (8)
 .br
 .SH REPORTING BUGS
diff --git a/man/man8/tipc-link.8 b/man/man8/tipc-link.8
index 3be8c9a..fee283e 100644
--- a/man/man8/tipc-link.8
+++ b/man/man8/tipc-link.8
@@ -39,6 +39,29 @@
 .B tipc link list
 .br
 
+.ti -8
+.B tipc link monitor set
+.RB "{ " "threshold" " } "
+
+.ti -8
+.B tipc link monitor get
+.RB "{ " "threshold" " } "
+
+.ti -8
+.B tipc link monitor summary
+.br
+
+.ti -8
+.B tipc link monitor list
+.br
+.RB "[ " "media " " { " eth " | " ib " } " device
+.IR "DEVICE" " ]"
+.RB "|"
+.br
+.RB "[ " "media udp name"
+.IR NAME " ]"
+.br
+
 .SH OPTIONS
 Options (flags) that can be passed anywhere in the command chain.
 .TP
@@ -204,6 +227,87 @@
 have in its transmit queue before TIPC's congestion control mechanism is
 activated.
 
+.SS Monitor properties
+
+.TP
+.B threshold
+.br
+The threshold specifies the cluster size exceeding which the link monitoring
+algorithm will switch from "full-mesh" to "overlapping-ring".
+If set of 0 the overlapping-ring monitoring is always on and if set to a
+value larger than anticipated cluster size the overlapping-ring is disabled.
+The default value is 32.
+
+.SS Monitor information
+
+.TP
+.B table_generation
+.br
+Represents the event count in a node's local monitoring list. It steps every
+time something changes in the local monitor list, including changes in the
+local domain.
+
+.TP
+.B cluster_size
+.br
+Represents the current count of cluster members.
+
+.TP
+.B algorithm
+.br
+The current supervision algorithm used for neighbour monitoring for the bearer.
+Possible values are full-mesh or overlapping-ring.
+
+.TP
+.B status
+.br
+The node status derived by the local node.
+Possible status are up or down.
+
+.TP
+.B monitored
+.br
+Represent the type of monitoring chosen by the local node.
+Possible values are direct or indirect.
+
+.TP
+.B generation
+.br
+Represents the domain generation which is the event count in a node's local
+domain. Every time something changes (peer add/remove/up/down) the domain
+generation is stepped and a new version of node record is sent to inform
+the neighbors about this change. The domain generation helps the receiver
+of a domain record to know if it should ignore or process the record.
+
+.TP
+.B applied_node_status
+.br
+The node status reported by the peer node for the succeeding peers in
+the node list. The Node list is a circular list of ascending addresses
+starting with the local node.
+Possible status are: U or D. The status U implies up and D down.
+
+.TP
+.B [non_applied_node:status]
+.br
+Represents the nodes and their status as reported by the peer node.
+These nodes were not applied to the monitoring list for this peer node.
+They are usually transient and occur during the cluster startup phase
+or network reconfiguration.
+Possible status are: U or D. The status U implies up and D down.
+
+.SH EXAMPLES
+.PP
+tipc link monitor list
+.RS 4
+Shows the link monitoring information for cluster members on device data0.
+.RE
+.PP
+tipc link monitor summary
+.RS 4
+The monitor summary command prints the basic attributes.
+.RE
+
 .SH EXIT STATUS
 Exit status is 0 if command was successful or a positive integer upon failure.
 
@@ -213,6 +317,7 @@
 .BR tipc-bearer (8),
 .BR tipc-nametable (8),
 .BR tipc-node (8),
+.BR tipc-peer (8),
 .BR tipc-socket (8)
 .br
 .SH REPORTING BUGS
diff --git a/man/man8/tipc-media.8 b/man/man8/tipc-media.8
index 6c6e2b1..4689cb3 100644
--- a/man/man8/tipc-media.8
+++ b/man/man8/tipc-media.8
@@ -74,6 +74,7 @@
 .BR tipc-link (8),
 .BR tipc-nametable (8),
 .BR tipc-node (8),
+.BR tipc-peer (8),
 .BR tipc-socket (8)
 .br
 .SH REPORTING BUGS
diff --git a/man/man8/tipc-nametable.8 b/man/man8/tipc-nametable.8
index d3397f9..4bcefe4 100644
--- a/man/man8/tipc-nametable.8
+++ b/man/man8/tipc-nametable.8
@@ -87,6 +87,7 @@
 .BR tipc-link (8),
 .BR tipc-media (8),
 .BR tipc-node (8),
+.BR tipc-peer (8),
 .BR tipc-socket (8)
 .br
 .SH REPORTING BUGS
diff --git a/man/man8/tipc-node.8 b/man/man8/tipc-node.8
index ef32ec7..a72a409 100644
--- a/man/man8/tipc-node.8
+++ b/man/man8/tipc-node.8
@@ -59,6 +59,7 @@
 .BR tipc-link (8),
 .BR tipc-media (8),
 .BR tipc-nametable (8),
+.BR tipc-peer (8),
 .BR tipc-socket (8)
 .br
 .SH REPORTING BUGS
diff --git a/man/man8/tipc-peer.8 b/man/man8/tipc-peer.8
new file mode 100644
index 0000000..430651f
--- /dev/null
+++ b/man/man8/tipc-peer.8
@@ -0,0 +1,52 @@
+.TH TIPC-PEER 8 "04 Dec 2015" "iproute2" "Linux"
+
+.\" For consistency, please keep padding right aligned.
+.\" For example '.B "foo " bar' and not '.B foo " bar"'
+
+.SH NAME
+tipc-peer \- modify peer information
+
+.SH SYNOPSIS
+.ad l
+.in +8
+
+.ti -8
+.B tipc peer remove address
+.IR ADDRESS
+
+.SH OPTIONS
+Options (flags) that can be passed anywhere in the command chain.
+.TP
+.BR "\-h" , " --help"
+Show help about last valid command. For example
+.B tipc peer --help
+will show peer help and
+.B tipc --help
+will show general help. The position of the option in the string is irrelevant.
+.SH DESCRIPTION
+
+.SS Peer remove
+Remove an offline peer node from the local data structures. The peer is
+identified by its
+.B address
+
+.SH EXIT STATUS
+Exit status is 0 if command was successful or a positive integer upon failure.
+
+.SH SEE ALSO
+.BR tipc (8),
+.BR tipc-bearer (8),
+.BR tipc-link (8),
+.BR tipc-media (8),
+.BR tipc-nametable (8),
+.BR tipc-node (8),
+.BR tipc-socket (8)
+.br
+.SH REPORTING BUGS
+Report any bugs to the Network Developers mailing list
+.B <netdev@vger.kernel.org>
+where the development and maintenance is primarily done.
+You do not have to be subscribed to the list to send a message there.
+
+.SH AUTHOR
+Richard Alpe <richard.alpe@ericsson.com>
diff --git a/man/man8/tipc.8 b/man/man8/tipc.8
index c116552..32943fa 100644
--- a/man/man8/tipc.8
+++ b/man/man8/tipc.8
@@ -87,6 +87,7 @@
 .BR tipc-media (8),
 .BR tipc-nametable (8),
 .BR tipc-node (8),
+.BR tipc-peer (8),
 .BR tipc-socket (8)
 .br
 .SH REPORTING BUGS
diff --git a/misc/ss.c b/misc/ss.c
index f67557a..dd77b81 100644
--- a/misc/ss.c
+++ b/misc/ss.c
@@ -737,6 +737,7 @@
 	unsigned long long  sk;
 	char *name;
 	char *peer_name;
+	__u32		    mark;
 };
 
 struct dctcpstat {
@@ -789,6 +790,7 @@
 	bool		    has_fastopen_opt;
 	bool		    has_wscale_opt;
 	struct dctcpstat    *dctcp;
+	struct tcp_bbr_info *bbr_info;
 };
 
 static void sock_state_print(struct sockstat *s, const char *sock_name)
@@ -808,6 +810,9 @@
 
 	printf(" ino:%u", s->ino);
 	printf(" sk:%llx", s->sk);
+
+	if (s->mark)
+		printf(" fwmark:0x%x", s->mark);
 }
 
 static void sock_addr_print_width(int addr_len, const char *addr, char *delim,
@@ -1047,6 +1052,8 @@
 	inet_prefix	addr;
 	int		port;
 	unsigned int	iface;
+	__u32		mark;
+	__u32		mask;
 	struct aafilter *next;
 };
 
@@ -1167,6 +1174,12 @@
 
 		return s->iface == a->iface;
 	}
+		case SSF_MARKMASK:
+	{
+		struct aafilter *a = (void *)f->pred;
+
+		return (s->mark & a->mask) == a->mark;
+	}
 		/* Yup. It is recursion. Sorry. */
 		case SSF_AND:
 		return run_ssfilter(f->pred, s) && run_ssfilter(f->post, s);
@@ -1342,6 +1355,23 @@
 		/* bytecompile for SSF_DEVCOND not supported yet */
 		return 0;
 	}
+		case SSF_MARKMASK:
+	{
+		struct aafilter *a = (void *)f->pred;
+		struct instr {
+			struct inet_diag_bc_op op;
+			struct inet_diag_markcond cond;
+		};
+		int inslen = sizeof(struct instr);
+
+		if (!(*bytecode = malloc(inslen))) abort();
+		((struct instr *)*bytecode)[0] = (struct instr) {
+			{ INET_DIAG_BC_MARK_COND, inslen, inslen + 4 },
+			{ a->mark, a->mask},
+		};
+
+		return inslen;
+	}
 		default:
 		abort();
 	}
@@ -1621,6 +1651,25 @@
 	return res;
 }
 
+void *parse_markmask(const char *markmask)
+{
+	struct aafilter a, *res;
+
+	if (strchr(markmask, '/')) {
+		if (sscanf(markmask, "%i/%i", &a.mark, &a.mask) != 2)
+			return NULL;
+	} else {
+		a.mask = 0xffffffff;
+		if (sscanf(markmask, "%i", &a.mark) != 1)
+			return NULL;
+	}
+
+	res = malloc(sizeof(*res));
+	if (res)
+		memcpy(res, &a, sizeof(a));
+	return res;
+}
+
 static char *proto_name(int protocol)
 {
 	switch (protocol) {
@@ -1784,6 +1833,25 @@
 		printf(" dctcp:fallback_mode");
 	}
 
+	if (s->bbr_info) {
+		__u64 bw;
+
+		bw = s->bbr_info->bbr_bw_hi;
+		bw <<= 32;
+		bw |= s->bbr_info->bbr_bw_lo;
+
+		printf(" bbr:(bw:%sbps,mrtt:%g",
+		       sprint_bw(b1, bw * 8.0),
+		       (double)s->bbr_info->bbr_min_rtt / 1000.0);
+		if (s->bbr_info->bbr_pacing_gain)
+			printf(",pacing_gain:%g",
+			       (double)s->bbr_info->bbr_pacing_gain / 256.0);
+		if (s->bbr_info->bbr_cwnd_gain)
+			printf(",cwnd_gain:%g",
+			       (double)s->bbr_info->bbr_cwnd_gain / 256.0);
+		printf(")");
+	}
+
 	if (s->send_bps)
 		printf(" send %sbps", sprint_bw(b1, s->send_bps));
 	if (s->lastsnd)
@@ -2062,6 +2130,16 @@
 			s.dctcp		= dctcp;
 		}
 
+		if (tb[INET_DIAG_BBRINFO]) {
+			const void *bbr_info = RTA_DATA(tb[INET_DIAG_BBRINFO]);
+			int len = min(RTA_PAYLOAD(tb[INET_DIAG_BBRINFO]),
+				      sizeof(*s.bbr_info));
+
+			s.bbr_info = calloc(1, sizeof(*s.bbr_info));
+			if (s.bbr_info && bbr_info)
+				memcpy(s.bbr_info, bbr_info, len);
+		}
+
 		if (rtt > 0 && info->tcpi_snd_mss && info->tcpi_snd_cwnd) {
 			s.send_bps = (double) info->tcpi_snd_cwnd *
 				(double)info->tcpi_snd_mss * 8000000. / rtt;
@@ -2086,6 +2164,7 @@
 			s.min_rtt = (double) info->tcpi_min_rtt / 1000;
 		tcp_stats_print(&s);
 		free(s.dctcp);
+		free(s.bbr_info);
 	}
 }
 
@@ -2108,6 +2187,10 @@
 	s->iface	= r->id.idiag_if;
 	s->sk		= cookie_sk_get(&r->id.idiag_cookie[0]);
 
+	s->mark = 0;
+	if (tb[INET_DIAG_MARK])
+		s->mark = *(__u32 *) RTA_DATA(tb[INET_DIAG_MARK]);
+
 	if (s->local.family == AF_INET)
 		s->local.bytelen = s->remote.bytelen = 4;
 	else
diff --git a/misc/ssfilter.h b/misc/ssfilter.h
index c7db8ee..dfc5b93 100644
--- a/misc/ssfilter.h
+++ b/misc/ssfilter.h
@@ -9,6 +9,7 @@
 #define SSF_S_LE  8
 #define SSF_S_AUTO  9
 #define SSF_DEVCOND 10
+#define SSF_MARKMASK 11
 
 #include <stdbool.h>
 
@@ -22,3 +23,4 @@
 int ssfilter_parse(struct ssfilter **f, int argc, char **argv, FILE *fp);
 void *parse_hostcond(char *addr, bool is_port);
 void *parse_devcond(char *name);
+void *parse_markmask(const char *markmask);
diff --git a/misc/ssfilter.y b/misc/ssfilter.y
index 14bf981..ba82b65 100644
--- a/misc/ssfilter.y
+++ b/misc/ssfilter.y
@@ -36,7 +36,7 @@
 
 %}
 
-%token HOSTCOND DCOND SCOND DPORT SPORT LEQ GEQ NEQ AUTOBOUND DEVCOND DEVNAME
+%token HOSTCOND DCOND SCOND DPORT SPORT LEQ GEQ NEQ AUTOBOUND DEVCOND DEVNAME MARKMASK FWMARK
 %left '|'
 %left '&'
 %nonassoc '!'
@@ -116,7 +116,14 @@
         {
 		$$ = alloc_node(SSF_NOT, alloc_node(SSF_DEVCOND, $3));
         }
-
+        | FWMARK '=' MARKMASK
+        {
+                $$ = alloc_node(SSF_MARKMASK, $3);
+        }
+        | FWMARK NEQ MARKMASK
+        {
+                $$ = alloc_node(SSF_NOT, alloc_node(SSF_MARKMASK, $3));
+        }
         | AUTOBOUND
         {
                 $$ = alloc_node(SSF_S_AUTO, NULL);
@@ -249,6 +256,10 @@
 		tok_type = DEVNAME;
 		return DEVNAME;
 	}
+	if (strcmp(curtok, "fwmark") == 0) {
+		tok_type = FWMARK;
+		return FWMARK;
+	}
 	if (strcmp(curtok, ">=") == 0 ||
 	    strcmp(curtok, "ge") == 0 ||
 	    strcmp(curtok, "geq") == 0)
@@ -283,6 +294,14 @@
 		}
 		return DEVCOND;
 	}
+	if (tok_type == FWMARK) {
+		yylval = (void*)parse_markmask(curtok);
+		if (yylval == NULL) {
+			fprintf(stderr, "Cannot parse mark %s.\n", curtok);
+			exit(1);
+		}
+		return MARKMASK;
+	}
 	yylval = (void*)parse_hostcond(curtok, tok_type == SPORT || tok_type == DPORT);
 	if (yylval == NULL) {
 		fprintf(stderr, "Cannot parse dst/src address.\n");
diff --git a/tc/f_flower.c b/tc/f_flower.c
index 791ade7..2d31d1a 100644
--- a/tc/f_flower.c
+++ b/tc/f_flower.c
@@ -17,6 +17,7 @@
 #include <net/if.h>
 #include <linux/if_ether.h>
 #include <linux/ip.h>
+#include <linux/tc_act/tc_vlan.h>
 
 #include "utils.h"
 #include "tc_util.h"
@@ -30,6 +31,9 @@
 	fprintf(stderr, "\n");
 	fprintf(stderr, "Where: MATCH-LIST := [ MATCH-LIST ] MATCH\n");
 	fprintf(stderr, "       MATCH      := { indev DEV-NAME |\n");
+	fprintf(stderr, "                       vlan_id VID |\n");
+	fprintf(stderr, "                       vlan_prio PRIORITY |\n");
+	fprintf(stderr, "                       vlan_ethtype [ ipv4 | ipv6 | ETH-TYPE ] |\n");
 	fprintf(stderr, "                       dst_mac MAC-ADDR |\n");
 	fprintf(stderr, "                       src_mac MAC-ADDR |\n");
 	fprintf(stderr, "                       [ipv4 | ipv6 ] |\n");
@@ -61,6 +65,23 @@
 	return 0;
 }
 
+static int flower_parse_vlan_eth_type(char *str, __be16 eth_type, int type,
+				      __be16 *p_vlan_eth_type, struct nlmsghdr *n)
+{
+	__be16 vlan_eth_type;
+
+	if (eth_type != htons(ETH_P_8021Q)) {
+		fprintf(stderr, "Can't set \"vlan_ethtype\" if ethertype isn't 802.1Q\n");
+		return -1;
+	}
+
+	if (ll_proto_a2n(&vlan_eth_type, str))
+		invarg("invalid vlan_ethtype", str);
+	addattr16(n, MAX_MSG, type, vlan_eth_type);
+	*p_vlan_eth_type = vlan_eth_type;
+	return 0;
+}
+
 static int flower_parse_ip_proto(char *str, __be16 eth_type, int type,
 				 __u8 *p_ip_proto, struct nlmsghdr *n)
 {
@@ -167,6 +188,7 @@
 	struct tcmsg *t = NLMSG_DATA(n);
 	struct rtattr *tail;
 	__be16 eth_type = TC_H_MIN(t->tcm_info);
+	__be16 vlan_ethtype = 0;
 	__u8 ip_proto = 0xff;
 	__u32 flags = 0;
 
@@ -208,6 +230,41 @@
 			NEXT_ARG();
 			strncpy(ifname, *argv, sizeof(ifname) - 1);
 			addattrstrz(n, MAX_MSG, TCA_FLOWER_INDEV, ifname);
+		} else if (matches(*argv, "vlan_id") == 0) {
+			__u16 vid;
+
+			NEXT_ARG();
+			if (eth_type != htons(ETH_P_8021Q)) {
+				fprintf(stderr, "Can't set \"vlan_id\" if ethertype isn't 802.1Q\n");
+				return -1;
+			}
+			ret = get_u16(&vid, *argv, 10);
+			if (ret < 0 || vid & ~0xfff) {
+				fprintf(stderr, "Illegal \"vlan_id\"\n");
+				return -1;
+			}
+			addattr16(n, MAX_MSG, TCA_FLOWER_KEY_VLAN_ID, vid);
+		} else if (matches(*argv, "vlan_prio") == 0) {
+			__u8 vlan_prio;
+
+			NEXT_ARG();
+			if (eth_type != htons(ETH_P_8021Q)) {
+				fprintf(stderr, "Can't set \"vlan_prio\" if ethertype isn't 802.1Q\n");
+				return -1;
+			}
+			ret = get_u8(&vlan_prio, *argv, 10);
+			if (ret < 0 || vlan_prio & ~0x7) {
+				fprintf(stderr, "Illegal \"vlan_prio\"\n");
+				return -1;
+			}
+			addattr8(n, MAX_MSG, TCA_FLOWER_KEY_VLAN_PRIO, vlan_prio);
+		} else if (matches(*argv, "vlan_ethtype") == 0) {
+			NEXT_ARG();
+			ret = flower_parse_vlan_eth_type(*argv, eth_type,
+							 TCA_FLOWER_KEY_VLAN_ETH_TYPE,
+							 &vlan_ethtype, n);
+			if (ret < 0)
+				return -1;
 		} else if (matches(*argv, "dst_mac") == 0) {
 			NEXT_ARG();
 			ret = flower_parse_eth_addr(*argv,
@@ -230,7 +287,8 @@
 			}
 		} else if (matches(*argv, "ip_proto") == 0) {
 			NEXT_ARG();
-			ret = flower_parse_ip_proto(*argv, eth_type,
+			ret = flower_parse_ip_proto(*argv, vlan_ethtype ?
+						    vlan_ethtype : eth_type,
 						    TCA_FLOWER_KEY_IP_PROTO,
 						    &ip_proto, n);
 			if (ret < 0) {
@@ -239,7 +297,8 @@
 			}
 		} else if (matches(*argv, "dst_ip") == 0) {
 			NEXT_ARG();
-			ret = flower_parse_ip_addr(*argv, eth_type,
+			ret = flower_parse_ip_addr(*argv, vlan_ethtype ?
+						   vlan_ethtype : eth_type,
 						   TCA_FLOWER_KEY_IPV4_DST,
 						   TCA_FLOWER_KEY_IPV4_DST_MASK,
 						   TCA_FLOWER_KEY_IPV6_DST,
@@ -251,7 +310,8 @@
 			}
 		} else if (matches(*argv, "src_ip") == 0) {
 			NEXT_ARG();
-			ret = flower_parse_ip_addr(*argv, eth_type,
+			ret = flower_parse_ip_addr(*argv, vlan_ethtype ?
+						   vlan_ethtype : eth_type,
 						   TCA_FLOWER_KEY_IPV4_SRC,
 						   TCA_FLOWER_KEY_IPV4_SRC_MASK,
 						   TCA_FLOWER_KEY_IPV6_SRC,
@@ -477,6 +537,18 @@
 		fprintf(f, "\n  indev %s", rta_getattr_str(attr));
 	}
 
+	if (tb[TCA_FLOWER_KEY_VLAN_ID]) {
+		struct rtattr *attr = tb[TCA_FLOWER_KEY_VLAN_ID];
+
+		fprintf(f, "\n  vlan_id %d", rta_getattr_u16(attr));
+	}
+
+	if (tb[TCA_FLOWER_KEY_VLAN_PRIO]) {
+		struct rtattr *attr = tb[TCA_FLOWER_KEY_VLAN_PRIO];
+
+		fprintf(f, "\n  vlan_prio %d", rta_getattr_u8(attr));
+	}
+
 	flower_print_eth_addr(f, "dst_mac", tb[TCA_FLOWER_KEY_ETH_DST],
 			      tb[TCA_FLOWER_KEY_ETH_DST_MASK]);
 	flower_print_eth_addr(f, "src_mac", tb[TCA_FLOWER_KEY_ETH_SRC],
diff --git a/tc/m_vlan.c b/tc/m_vlan.c
index ac63d9e..05a63b4 100644
--- a/tc/m_vlan.c
+++ b/tc/m_vlan.c
@@ -22,7 +22,7 @@
 static void explain(void)
 {
 	fprintf(stderr, "Usage: vlan pop\n");
-	fprintf(stderr, "       vlan push [ protocol VLANPROTO ] id VLANID [CONTROL]\n");
+	fprintf(stderr, "       vlan push [ protocol VLANPROTO ] id VLANID [ priority VLANPRIO ] [CONTROL]\n");
 	fprintf(stderr, "       VLANPROTO is one of 802.1Q or 802.1AD\n");
 	fprintf(stderr, "            with default: 802.1Q\n");
 	fprintf(stderr, "       CONTROL := reclassify | pipe | drop | continue | pass\n");
@@ -45,6 +45,8 @@
 	int id_set = 0;
 	__u16 proto;
 	int proto_set = 0;
+	__u8 prio;
+	int prio_set = 0;
 	struct tc_vlan parm = { 0 };
 
 	if (matches(*argv, "vlan") != 0)
@@ -91,6 +93,17 @@
 			if (ll_proto_a2n(&proto, *argv))
 				invarg("protocol is invalid", *argv);
 			proto_set = 1;
+		} else if (matches(*argv, "priority") == 0) {
+			if (action != TCA_VLAN_ACT_PUSH) {
+				fprintf(stderr, "\"%s\" is only valid for push\n",
+					*argv);
+				explain();
+				return -1;
+			}
+			NEXT_ARG();
+			if (get_u8(&prio, *argv, 0) || (prio & ~0x7))
+				invarg("prio is invalid", *argv);
+			prio_set = 1;
 		} else if (matches(*argv, "help") == 0) {
 			usage();
 		} else {
@@ -138,6 +151,9 @@
 
 		addattr_l(n, MAX_MSG, TCA_VLAN_PUSH_VLAN_PROTOCOL, &proto, 2);
 	}
+	if (prio_set)
+		addattr8(n, MAX_MSG, TCA_VLAN_PUSH_VLAN_PRIORITY, prio);
+
 	tail->rta_len = (char *)NLMSG_TAIL(n) - (char *)tail;
 
 	*argc_p = argc;
@@ -180,6 +196,10 @@
 				ll_proto_n2a(rta_getattr_u16(tb[TCA_VLAN_PUSH_VLAN_PROTOCOL]),
 					     b1, sizeof(b1)));
 		}
+		if (tb[TCA_VLAN_PUSH_VLAN_PRIORITY]) {
+			val = rta_getattr_u8(tb[TCA_VLAN_PUSH_VLAN_PRIORITY]);
+			fprintf(f, " priority %u", val);
+		}
 		break;
 	}
 	fprintf(f, " %s", action_n2a(parm->action));
diff --git a/testsuite/tests/ip/netns/set_nsid.t b/testsuite/tests/ip/netns/set_nsid.t
new file mode 100755
index 0000000..606d45a
--- /dev/null
+++ b/testsuite/tests/ip/netns/set_nsid.t
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+source lib/generic.sh
+
+ts_log "[Testing netns nsid]"
+
+NS=testnsid
+NSID=99
+
+ts_ip "$0" "Add new netns $NS" netns add $NS
+ts_ip "$0" "Set $NS nsid to $NSID" netns set $NS $NSID
+
+ts_ip "$0" "List netns" netns list
+test_on "$NS \(id: $NSID\)"
+
+ts_ip "$0" "List netns without explicit list or show" netns
+test_on "$NS \(id: $NSID\)"
+
+ts_ip "$0" "List nsid" netns list-id
+test_on "$NSID \(iproute2 netns name: $NS\)"
+
+ts_ip "$0" "Delete netns $NS" netns del $NS
diff --git a/testsuite/tests/ip/netns/set_nsid_batch.t b/testsuite/tests/ip/netns/set_nsid_batch.t
new file mode 100755
index 0000000..abb3f1b
--- /dev/null
+++ b/testsuite/tests/ip/netns/set_nsid_batch.t
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+source lib/generic.sh
+
+ts_log "[Testing netns nsid in batch mode]"
+
+NS=testnsid
+NSID=99
+BATCHFILE=`mktemp`
+
+echo "netns add $NS" >> $BATCHFILE
+echo "netns set $NS $NSID" >> $BATCHFILE
+echo "netns list-id" >> $BATCHFILE
+ts_ip "$0" "Add ns, set nsid and list in batch mode" -b $BATCHFILE
+test_on "nsid $NSID \(iproute2 netns name: $NS\)"
+rm -f $BATCHFILE
+
+ts_ip "$0" "Delete netns $NS" netns del $NS
diff --git a/tipc/Makefile b/tipc/Makefile
index 868d13a..87e3cac 100644
--- a/tipc/Makefile
+++ b/tipc/Makefile
@@ -6,7 +6,7 @@
     media.o misc.o \
     msg.o nametable.o \
     node.o socket.o \
-    tipc.o
+    peer.o tipc.o
 
 include ../Config
 
diff --git a/tipc/bearer.c b/tipc/bearer.c
index 05dabe6..810344f 100644
--- a/tipc/bearer.c
+++ b/tipc/bearer.c
@@ -14,6 +14,7 @@
 #include <string.h>
 #include <netdb.h>
 #include <errno.h>
+#include <arpa/inet.h>
 
 #include <linux/tipc_netlink.h>
 #include <linux/tipc.h>
@@ -26,6 +27,15 @@
 #include "msg.h"
 #include "bearer.h"
 
+#define UDP_PROP_IP 1
+#define UDP_PROP_PORT 2
+
+struct cb_data {
+	int attr;
+	int prop;
+	struct nlmsghdr *nlh;
+};
+
 static void _print_bearer_opts(void)
 {
 	fprintf(stderr,
@@ -35,7 +45,7 @@
 		" window                - Bearer link window\n");
 }
 
-static void _print_bearer_media(void)
+void print_bearer_media(void)
 {
 	fprintf(stderr,
 		"\nMEDIA\n"
@@ -57,14 +67,17 @@
 static void cmd_bearer_enable_udp_help(struct cmdl *cmdl, char *media)
 {
 	fprintf(stderr,
-		"Usage: %s bearer enable media %s name NAME localip IP [OPTIONS]\n"
-		"\nOPTIONS\n"
+		"Usage: %s bearer enable [OPTIONS] media %s name NAME localip IP [UDP OPTIONS]\n\n",
+		cmdl->argv[0], media);
+	fprintf(stderr,
+		"OPTIONS\n"
 		" domain DOMAIN         - Discovery domain\n"
-		" priority PRIORITY     - Bearer priority\n"
+		" priority PRIORITY     - Bearer priority\n\n");
+	fprintf(stderr,
+		"UDP OPTIONS\n"
 		" localport PORT        - Local UDP port (default 6118)\n"
 		" remoteip IP           - Remote IP address\n"
-		" remoteport IP         - Remote UDP port (default 6118)\n",
-		cmdl->argv[0], media);
+		" remoteport PORT       - Remote UDP port (default 6118)\n");
 }
 
 static int get_netid_cb(const struct nlmsghdr *nlh, void *data)
@@ -179,14 +192,28 @@
 }
 
 static int nl_add_bearer_name(struct nlmsghdr *nlh, const struct cmd *cmd,
-			   struct cmdl *cmdl, struct opt *opts,
-			   struct tipc_sup_media sup_media[])
+			      struct cmdl *cmdl, struct opt *opts,
+			      const struct tipc_sup_media *sup_media)
 {
-	char id[TIPC_MAX_BEARER_NAME];
+	char bname[TIPC_MAX_BEARER_NAME];
+	int err;
+
+	if ((err = cmd_get_unique_bearer_name(cmd, cmdl, opts, bname, sup_media)))
+		return err;
+
+	mnl_attr_put_strz(nlh, TIPC_NLA_BEARER_NAME, bname);
+	return 0;
+}
+
+int cmd_get_unique_bearer_name(const struct cmd *cmd, struct cmdl *cmdl,
+			       struct opt *opts, char *bname,
+			       const struct tipc_sup_media *sup_media)
+{
 	char *media;
 	char *identifier;
 	struct opt *opt;
-	struct tipc_sup_media *entry;
+	const struct tipc_sup_media *entry;
+
 
 	if (!(opt = get_opt(opts, "media"))) {
 		if (help_flag)
@@ -206,13 +233,12 @@
 				(entry->help)(cmdl, media);
 			else
 				fprintf(stderr, "error, missing bearer %s\n",
-						entry->identifier);
+					entry->identifier);
 			return -EINVAL;
 		}
 
 		identifier = opt->val;
-		snprintf(id, sizeof(id), "%s:%s", media, identifier);
-		mnl_attr_put_strz(nlh, TIPC_NLA_BEARER_NAME, id);
+		snprintf(bname, TIPC_MAX_BEARER_NAME, "%s:%s", media, identifier);
 
 		return 0;
 	}
@@ -222,6 +248,129 @@
 	return -EINVAL;
 }
 
+static void cmd_bearer_add_udp_help(struct cmdl *cmdl, char *media)
+{
+	fprintf(stderr, "Usage: %s bearer add media %s name NAME remoteip REMOTEIP\n\n",
+		cmdl->argv[0], media);
+}
+
+static void cmd_bearer_add_help(struct cmdl *cmdl)
+{
+	fprintf(stderr, "Usage: %s bearer add media udp name NAME remoteip REMOTEIP\n",
+		cmdl->argv[0]);
+}
+
+static int udp_bearer_add(struct nlmsghdr *nlh, struct opt *opts,
+			  struct cmdl *cmdl)
+{
+	int err;
+	struct opt *opt;
+	struct nlattr *opts_nest;
+	char *remport = "6118";
+
+	opts_nest = mnl_attr_nest_start(nlh, TIPC_NLA_BEARER_UDP_OPTS);
+
+	if ((opt = get_opt(opts, "remoteport")))
+		remport = opt->val;
+
+	if ((opt = get_opt(opts, "remoteip"))) {
+		char *ip = opt->val;
+		struct addrinfo *addr = NULL;
+		struct addrinfo hints = {
+			.ai_family = AF_UNSPEC,
+			.ai_socktype = SOCK_DGRAM
+		};
+
+		if ((err = getaddrinfo(ip, remport, &hints, &addr))) {
+			fprintf(stderr, "UDP address error: %s\n",
+				gai_strerror(err));
+			freeaddrinfo(addr);
+			return err;
+		}
+
+		mnl_attr_put(nlh, TIPC_NLA_UDP_REMOTE, addr->ai_addrlen,
+			     addr->ai_addr);
+		freeaddrinfo(addr);
+	} else {
+		fprintf(stderr, "error, missing remoteip\n");
+		return -EINVAL;
+	}
+	mnl_attr_nest_end(nlh, opts_nest);
+
+	return 0;
+}
+
+static int cmd_bearer_add_media(struct nlmsghdr *nlh, const struct cmd *cmd,
+				struct cmdl *cmdl, void *data)
+{
+	int err;
+	char *media;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct opt *opt;
+	struct nlattr *attrs;
+	struct opt opts[] = {
+		{ "remoteip",		OPT_KEYVAL,	NULL },
+		{ "remoteport",		OPT_KEYVAL,	NULL },
+		{ "name",		OPT_KEYVAL,	NULL },
+		{ "media",		OPT_KEYVAL,	NULL },
+		{ NULL }
+	};
+	const struct tipc_sup_media sup_media[] = {
+		{ "udp",	"name",		cmd_bearer_add_udp_help},
+		{ NULL, },
+	};
+
+	/* Rewind optind to include media in the option list */
+	cmdl->optind--;
+	if (parse_opts(opts, cmdl) < 0)
+		return -EINVAL;
+
+	if (!(opt = get_opt(opts, "media"))) {
+		fprintf(stderr, "error, missing media value\n");
+		return -EINVAL;
+	}
+	media = opt->val;
+
+	if (strcmp(media, "udp") != 0) {
+		fprintf(stderr, "error, no \"%s\" media specific options available\n",
+			media);
+		return -EINVAL;
+	}
+	if (!(opt = get_opt(opts, "name"))) {
+		fprintf(stderr, "error, missing media name\n");
+		return -EINVAL;
+	}
+
+	if (!(nlh = msg_init(buf, TIPC_NL_BEARER_ADD))) {
+		fprintf(stderr, "error, message initialisation failed\n");
+		return -1;
+	}
+
+	attrs = mnl_attr_nest_start(nlh, TIPC_NLA_BEARER);
+	err = nl_add_bearer_name(nlh, cmd, cmdl, opts, sup_media);
+	if (err)
+		return err;
+
+	err = udp_bearer_add(nlh, opts, cmdl);
+	if (err)
+		return err;
+
+	mnl_attr_nest_end(nlh, attrs);
+
+	return msg_doit(nlh, NULL, NULL);
+}
+
+static int cmd_bearer_add(struct nlmsghdr *nlh, const struct cmd *cmd,
+			  struct cmdl *cmdl, void *data)
+{
+	const struct cmd cmds[] = {
+		{ "media",	cmd_bearer_add_media,	cmd_bearer_add_help },
+		{ NULL }
+	};
+
+	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
 static void cmd_bearer_enable_help(struct cmdl *cmdl)
 {
 	fprintf(stderr,
@@ -230,7 +379,7 @@
 		" domain DOMAIN         - Discovery domain\n"
 		" priority PRIORITY     - Bearer priority\n",
 		cmdl->argv[0]);
-	_print_bearer_media();
+	print_bearer_media();
 }
 
 static int cmd_bearer_enable(struct nlmsghdr *nlh, const struct cmd *cmd,
@@ -241,21 +390,21 @@
 	struct nlattr *nest;
 	char buf[MNL_SOCKET_BUFFER_SIZE];
 	struct opt opts[] = {
-		{ "device",		NULL },
-		{ "domain",		NULL },
-		{ "localip",		NULL },
-		{ "localport",		NULL },
-		{ "media",		NULL },
-		{ "name",		NULL },
-		{ "priority",		NULL },
-		{ "remoteip",		NULL },
-		{ "remoteport",		NULL },
+		{ "device",		OPT_KEYVAL,	NULL },
+		{ "domain",		OPT_KEYVAL,	NULL },
+		{ "localip",		OPT_KEYVAL,	NULL },
+		{ "localport",		OPT_KEYVAL,	NULL },
+		{ "media",		OPT_KEYVAL,	NULL },
+		{ "name",		OPT_KEYVAL,	NULL },
+		{ "priority",		OPT_KEYVAL,	NULL },
+		{ "remoteip",		OPT_KEYVAL,	NULL },
+		{ "remoteport",		OPT_KEYVAL,	NULL },
 		{ NULL }
 	};
 	struct tipc_sup_media sup_media[] = {
-		{ "udp",	"name",		cmd_bearer_enable_udp_help},
-		{ "eth",	"device",	cmd_bearer_enable_l2_help },
-		{ "ib",		"device",	cmd_bearer_enable_l2_help },
+		{ "udp",        "name",         cmd_bearer_enable_udp_help},
+		{ "eth",        "device",       cmd_bearer_enable_l2_help },
+		{ "ib",         "device",       cmd_bearer_enable_l2_help },
 		{ NULL, },
 	};
 
@@ -313,7 +462,7 @@
 {
 	fprintf(stderr, "Usage: %s bearer disable media MEDIA ARGS...\n",
 		cmdl->argv[0]);
-	_print_bearer_media();
+	print_bearer_media();
 }
 
 static int cmd_bearer_disable(struct nlmsghdr *nlh, const struct cmd *cmd,
@@ -323,15 +472,15 @@
 	char buf[MNL_SOCKET_BUFFER_SIZE];
 	struct nlattr *nest;
 	struct opt opts[] = {
-		{ "device",		NULL },
-		{ "name",		NULL },
-		{ "media",		NULL },
+		{ "device",		OPT_KEYVAL,	NULL },
+		{ "name",		OPT_KEYVAL,	NULL },
+		{ "media",		OPT_KEYVAL,	NULL },
 		{ NULL }
 	};
 	struct tipc_sup_media sup_media[] = {
-		{ "udp",	"name",		cmd_bearer_disable_udp_help},
-		{ "eth",	"device",	cmd_bearer_disable_l2_help },
-		{ "ib",		"device",	cmd_bearer_disable_l2_help },
+		{ "udp",        "name",         cmd_bearer_disable_udp_help},
+		{ "eth",        "device",       cmd_bearer_disable_l2_help },
+		{ "ib",         "device",       cmd_bearer_disable_l2_help },
 		{ NULL, },
 	};
 
@@ -361,7 +510,7 @@
 	fprintf(stderr, "Usage: %s bearer set OPTION media MEDIA ARGS...\n",
 		cmdl->argv[0]);
 	_print_bearer_opts();
-	_print_bearer_media();
+	print_bearer_media();
 }
 
 static void cmd_bearer_set_udp_help(struct cmdl *cmdl, char *media)
@@ -380,7 +529,7 @@
 }
 
 static int cmd_bearer_set_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
-			 struct cmdl *cmdl, void *data)
+			       struct cmdl *cmdl, void *data)
 {
 	int err;
 	int val;
@@ -389,15 +538,15 @@
 	struct nlattr *props;
 	struct nlattr *attrs;
 	struct opt opts[] = {
-		{ "device",		NULL },
-		{ "media",		NULL },
-		{ "name",		NULL },
+		{ "device",		OPT_KEYVAL,	NULL },
+		{ "media",		OPT_KEYVAL,	NULL },
+		{ "name",		OPT_KEYVAL,	NULL },
 		{ NULL }
 	};
 	struct tipc_sup_media sup_media[] = {
-		{ "udp",	"name",		cmd_bearer_set_udp_help},
-		{ "eth",	"device",	cmd_bearer_set_l2_help },
-		{ "ib",		"device",	cmd_bearer_set_l2_help },
+		{ "udp",        "name",         cmd_bearer_set_udp_help},
+		{ "eth",        "device",       cmd_bearer_set_l2_help },
+		{ "ib",         "device",       cmd_bearer_set_l2_help },
 		{ NULL, },
 	};
 
@@ -453,16 +602,22 @@
 
 static void cmd_bearer_get_help(struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s bearer get OPTION media MEDIA ARGS...\n",
+	fprintf(stderr, "Usage: %s bearer get [OPTION] media MEDIA ARGS...\n",
 		cmdl->argv[0]);
 	_print_bearer_opts();
-	_print_bearer_media();
+	print_bearer_media();
 }
 
 static void cmd_bearer_get_udp_help(struct cmdl *cmdl, char *media)
 {
-	fprintf(stderr, "Usage: %s bearer get OPTION media %s name NAME\n\n",
+	fprintf(stderr, "Usage: %s bearer get [OPTION] media %s name NAME [UDP OPTIONS]\n\n",
 		cmdl->argv[0], media);
+	fprintf(stderr,
+		"UDP OPTIONS\n"
+		" remoteip              - Remote ip address\n"
+		" remoteport            - Remote port\n"
+		" localip               - Local ip address\n"
+		" localport             - Local port\n\n");
 	_print_bearer_opts();
 }
 
@@ -474,6 +629,115 @@
 	_print_bearer_opts();
 }
 
+
+static int bearer_dump_udp_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct sockaddr_storage *addr;
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct nlattr *info[TIPC_NLA_UDP_MAX + 1] = {};
+
+	mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+
+	if (!info[TIPC_NLA_UDP_REMOTE])
+		return MNL_CB_ERROR;
+
+	addr = mnl_attr_get_payload(info[TIPC_NLA_UDP_REMOTE]);
+
+	if (addr->ss_family == AF_INET) {
+		struct sockaddr_in *ipv4 = (struct sockaddr_in *) addr;
+
+		printf("%s\n", inet_ntoa(ipv4->sin_addr));
+	} else if (addr->ss_family == AF_INET6) {
+		char straddr[INET6_ADDRSTRLEN];
+		struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *) addr;
+
+		if (!inet_ntop(AF_INET6, &ipv6->sin6_addr, straddr,
+			       sizeof(straddr))) {
+			fprintf(stderr, "error, parsing IPv6 addr\n");
+			return MNL_CB_ERROR;
+		}
+		printf("%s\n", straddr);
+
+	} else {
+		return MNL_CB_ERROR;
+	}
+
+	return MNL_CB_OK;
+}
+
+static int bearer_get_udp_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct cb_data *cb_data = (struct cb_data *) data;
+	struct sockaddr_storage *addr;
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+	struct nlattr *attrs[TIPC_NLA_BEARER_MAX + 1] = {};
+	struct nlattr *opts[TIPC_NLA_UDP_MAX + 1] = {};
+
+	mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+	if (!info[TIPC_NLA_BEARER])
+		return MNL_CB_ERROR;
+
+	mnl_attr_parse_nested(info[TIPC_NLA_BEARER], parse_attrs, attrs);
+	if (!attrs[TIPC_NLA_BEARER_UDP_OPTS])
+		return MNL_CB_ERROR;
+
+	mnl_attr_parse_nested(attrs[TIPC_NLA_BEARER_UDP_OPTS], parse_attrs, opts);
+	if (!opts[TIPC_NLA_UDP_LOCAL])
+		return MNL_CB_ERROR;
+
+	if ((cb_data->attr == TIPC_NLA_UDP_REMOTE) &&
+	    (cb_data->prop == UDP_PROP_IP) &&
+	    opts[TIPC_NLA_UDP_MULTI_REMOTEIP]) {
+		struct genlmsghdr *genl = mnl_nlmsg_get_payload(cb_data->nlh);
+
+		genl->cmd = TIPC_NL_UDP_GET_REMOTEIP;
+		return msg_dumpit(cb_data->nlh, bearer_dump_udp_cb, NULL);
+	}
+
+	addr = mnl_attr_get_payload(opts[cb_data->attr]);
+
+	if (addr->ss_family == AF_INET) {
+		struct sockaddr_in *ipv4 = (struct sockaddr_in *) addr;
+
+		switch (cb_data->prop) {
+		case UDP_PROP_IP:
+			printf("%s\n", inet_ntoa(ipv4->sin_addr));
+			break;
+		case UDP_PROP_PORT:
+			printf("%u\n", ntohs(ipv4->sin_port));
+			break;
+		default:
+			return MNL_CB_ERROR;
+		}
+
+	} else if (addr->ss_family == AF_INET6) {
+		char straddr[INET6_ADDRSTRLEN];
+		struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *) addr;
+
+		switch (cb_data->prop) {
+		case UDP_PROP_IP:
+			if (!inet_ntop(AF_INET6, &ipv6->sin6_addr, straddr,
+				       sizeof(straddr))) {
+				fprintf(stderr, "error, parsing IPv6 addr\n");
+				return MNL_CB_ERROR;
+			}
+			printf("%s\n", straddr);
+			break;
+		case UDP_PROP_PORT:
+			printf("%u\n", ntohs(ipv6->sin6_port));
+			break;
+		default:
+			return MNL_CB_ERROR;
+		}
+
+	} else {
+		return MNL_CB_ERROR;
+	}
+
+	return MNL_CB_OK;
+}
+
 static int bearer_get_cb(const struct nlmsghdr *nlh, void *data)
 {
 	int *prop = data;
@@ -499,6 +763,86 @@
 	return MNL_CB_OK;
 }
 
+static int cmd_bearer_get_media(struct nlmsghdr *nlh, const struct cmd *cmd,
+				struct cmdl *cmdl, void *data)
+{
+	int err;
+	char *media;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct opt *opt;
+	struct cb_data cb_data = {0};
+	struct nlattr *attrs;
+	struct opt opts[] = {
+		{ "localip",		OPT_KEY,	NULL },
+		{ "localport",		OPT_KEY,	NULL },
+		{ "remoteip",		OPT_KEY,	NULL },
+		{ "remoteport",		OPT_KEY,	NULL },
+		{ "name",		OPT_KEYVAL,	NULL },
+		{ "media",		OPT_KEYVAL,	NULL },
+		{ NULL }
+	};
+	struct tipc_sup_media sup_media[] = {
+		{ "udp",        "name",         cmd_bearer_get_udp_help},
+		{ NULL, },
+	};
+
+	/* Rewind optind to include media in the option list */
+	cmdl->optind--;
+	if (parse_opts(opts, cmdl) < 0)
+		return -EINVAL;
+
+	if (!(opt = get_opt(opts, "media"))) {
+		fprintf(stderr, "error, missing media value\n");
+		return -EINVAL;
+	}
+	media = opt->val;
+
+	if (help_flag) {
+		cmd_bearer_get_udp_help(cmdl, media);
+		return -EINVAL;
+	}
+	if (strcmp(media, "udp") != 0) {
+		fprintf(stderr, "error, no \"%s\" media specific options\n", media);
+		return -EINVAL;
+	}
+	if (!(opt = get_opt(opts, "name"))) {
+		fprintf(stderr, "error, missing media name\n");
+		return -EINVAL;
+	}
+
+	if (!(nlh = msg_init(buf, TIPC_NL_BEARER_GET))) {
+		fprintf(stderr, "error, message initialisation failed\n");
+		return -1;
+	}
+
+	attrs = mnl_attr_nest_start(nlh, TIPC_NLA_BEARER);
+	err = nl_add_bearer_name(nlh, cmd, cmdl, opts, sup_media);
+	if (err)
+		return err;
+	mnl_attr_nest_end(nlh, attrs);
+	cb_data.nlh = nlh;
+
+	if (has_opt(opts, "localip")) {
+		cb_data.attr = TIPC_NLA_UDP_LOCAL;
+		cb_data.prop = UDP_PROP_IP;
+		return msg_doit(nlh, bearer_get_udp_cb, &cb_data);
+	} else if (has_opt(opts, "localport")) {
+		cb_data.attr = TIPC_NLA_UDP_LOCAL;
+		cb_data.prop = UDP_PROP_PORT;
+		return msg_doit(nlh, bearer_get_udp_cb, &cb_data);
+	} else if (has_opt(opts, "remoteip")) {
+		cb_data.attr = TIPC_NLA_UDP_REMOTE;
+		cb_data.prop = UDP_PROP_IP;
+		return msg_doit(nlh, bearer_get_udp_cb, &cb_data);
+	} else if (has_opt(opts, "remoteport")) {
+		cb_data.attr = TIPC_NLA_UDP_REMOTE;
+		cb_data.prop = UDP_PROP_PORT;
+		return msg_doit(nlh, bearer_get_udp_cb, &cb_data);
+	}
+	fprintf(stderr, "error, missing UDP option\n");
+	return -EINVAL;
+}
+
 static int cmd_bearer_get_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
 			       struct cmdl *cmdl, void *data)
 {
@@ -507,18 +851,23 @@
 	char buf[MNL_SOCKET_BUFFER_SIZE];
 	struct nlattr *attrs;
 	struct opt opts[] = {
-		{ "device",		NULL },
-		{ "media",		NULL },
-		{ "name",		NULL },
+		{ "device",		OPT_KEYVAL,	NULL },
+		{ "media",		OPT_KEYVAL,	NULL },
+		{ "name",		OPT_KEYVAL,	NULL },
 		{ NULL }
 	};
 	struct tipc_sup_media sup_media[] = {
-		{ "udp",	"name",		cmd_bearer_get_udp_help},
-		{ "eth",	"device",	cmd_bearer_get_l2_help },
-		{ "ib",		"device",	cmd_bearer_get_l2_help },
+		{ "udp",        "name",         cmd_bearer_get_udp_help},
+		{ "eth",        "device",       cmd_bearer_get_l2_help },
+		{ "ib",         "device",       cmd_bearer_get_l2_help },
 		{ NULL, },
 	};
 
+	if (help_flag) {
+		(cmd->help)(cmdl);
+		return -EINVAL;
+	}
+
 	if (strcmp(cmd->cmd, "priority") == 0)
 		prop = TIPC_NLA_PROP_PRIO;
 	else if ((strcmp(cmd->cmd, "tolerance") == 0))
@@ -552,6 +901,7 @@
 		{ "priority",	cmd_bearer_get_prop,	cmd_bearer_get_help },
 		{ "tolerance",	cmd_bearer_get_prop,	cmd_bearer_get_help },
 		{ "window",	cmd_bearer_get_prop,	cmd_bearer_get_help },
+		{ "media",	cmd_bearer_get_media,	cmd_bearer_get_help },
 		{ NULL }
 	};
 
@@ -605,6 +955,7 @@
 		"Usage: %s bearer COMMAND [ARGS] ...\n"
 		"\n"
 		"COMMANDS\n"
+		" add			- Add data to existing bearer\n"
 		" enable                - Enable a bearer\n"
 		" disable               - Disable a bearer\n"
 		" set                   - Set various bearer properties\n"
@@ -616,6 +967,7 @@
 	       void *data)
 {
 	const struct cmd cmds[] = {
+		{ "add",	cmd_bearer_add,		cmd_bearer_add_help },
 		{ "disable",	cmd_bearer_disable,	cmd_bearer_disable_help },
 		{ "enable",	cmd_bearer_enable,	cmd_bearer_enable_help },
 		{ "get",	cmd_bearer_get,		cmd_bearer_get_help },
diff --git a/tipc/bearer.h b/tipc/bearer.h
index 9459d65..c0d0996 100644
--- a/tipc/bearer.h
+++ b/tipc/bearer.h
@@ -19,4 +19,8 @@
 int cmd_bearer(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl, void *data);
 void cmd_bearer_help(struct cmdl *cmdl);
 
+void print_bearer_media(void);
+int cmd_get_unique_bearer_name(const struct cmd *cmd, struct cmdl *cmdl,
+			       struct opt *opts, char *bname,
+			       const struct tipc_sup_media *sup_media);
 #endif
diff --git a/tipc/cmdl.c b/tipc/cmdl.c
index b816f7d..4a2f4fd 100644
--- a/tipc/cmdl.c
+++ b/tipc/cmdl.c
@@ -62,6 +62,11 @@
 	return NULL;
 }
 
+bool has_opt(struct opt *opts, char *key)
+{
+	return get_opt(opts, key) ? true : false;
+}
+
 char *shift_cmdl(struct cmdl *cmdl)
 {
 	int next;
@@ -80,7 +85,7 @@
 	int i;
 	int cnt = 0;
 
-	for (i = cmdl->optind; i < cmdl->argc; i += 2) {
+	for (i = cmdl->optind; i < cmdl->argc; i++) {
 		struct opt *o;
 
 		o = find_opt(opts, cmdl->argv[i]);
@@ -89,9 +94,13 @@
 					cmdl->argv[i]);
 			return -EINVAL;
 		}
+		if (o->flag & OPT_KEYVAL) {
+			cmdl->optind++;
+			i++;
+		}
 		cnt++;
-		o->val = cmdl->argv[i + 1];
-		cmdl->optind += 2;
+		o->val = cmdl->argv[i];
+		cmdl->optind++;
 	}
 
 	return cnt;
diff --git a/tipc/cmdl.h b/tipc/cmdl.h
index d4795cf..d37239f 100644
--- a/tipc/cmdl.h
+++ b/tipc/cmdl.h
@@ -16,6 +16,11 @@
 
 extern int help_flag;
 
+enum {
+	OPT_KEY			= (1 << 0),
+	OPT_KEYVAL		= (1 << 1),
+};
+
 struct cmdl {
 	int optind;
 	int argc;
@@ -37,10 +42,12 @@
 
 struct opt {
 	const char *key;
+	uint16_t flag;
 	char *val;
 };
 
 struct opt *get_opt(struct opt *opts, char *key);
+bool has_opt(struct opt *opts, char *key);
 int parse_opts(struct opt *opts, struct cmdl *cmdl);
 char *shift_cmdl(struct cmdl *cmdl);
 
diff --git a/tipc/link.c b/tipc/link.c
index 89fb4ff..4ae1c91 100644
--- a/tipc/link.c
+++ b/tipc/link.c
@@ -22,6 +22,7 @@
 #include "cmdl.h"
 #include "msg.h"
 #include "link.h"
+#include "bearer.h"
 
 static int link_list_cb(const struct nlmsghdr *nlh, void *data)
 {
@@ -57,7 +58,8 @@
 		return -EINVAL;
 	}
 
-	if (!(nlh = msg_init(buf, TIPC_NL_LINK_GET))) {
+	nlh = msg_init(buf, TIPC_NL_LINK_GET);
+	if (!nlh) {
 		fprintf(stderr, "error, message initialisation failed\n");
 		return -1;
 	}
@@ -90,7 +92,6 @@
 	return MNL_CB_OK;
 }
 
-
 static int cmd_link_get_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
 			     struct cmdl *cmdl, void *data)
 {
@@ -98,7 +99,7 @@
 	char buf[MNL_SOCKET_BUFFER_SIZE];
 	struct opt *opt;
 	struct opt opts[] = {
-		{ "link",		NULL },
+		{ "link",		OPT_KEYVAL,	NULL },
 		{ NULL }
 	};
 
@@ -119,12 +120,14 @@
 	if (parse_opts(opts, cmdl) < 0)
 		return -EINVAL;
 
-	if (!(nlh = msg_init(buf, TIPC_NL_LINK_GET))) {
+	nlh = msg_init(buf, TIPC_NL_LINK_GET);
+	if (!nlh) {
 		fprintf(stderr, "error, message initialisation failed\n");
 		return -1;
 	}
 
-	if (!(opt = get_opt(opts, "link"))) {
+	opt = get_opt(opts, "link");
+	if (!opt) {
 		fprintf(stderr, "error, missing link\n");
 		return -EINVAL;
 	}
@@ -169,7 +172,7 @@
 	struct opt *opt;
 	struct nlattr *nest;
 	struct opt opts[] = {
-		{ "link",		NULL },
+		{ "link",		OPT_KEYVAL,	NULL },
 		{ NULL }
 	};
 
@@ -183,12 +186,14 @@
 		return -EINVAL;
 	}
 
-	if (!(nlh = msg_init(buf, TIPC_NL_LINK_RESET_STATS))) {
+	nlh = msg_init(buf, TIPC_NL_LINK_RESET_STATS);
+	if (!nlh) {
 		fprintf(stderr, "error, message initialisation failed\n");
 		return -1;
 	}
 
-	if (!(opt = get_opt(opts, "link"))) {
+	opt = get_opt(opts, "link");
+	if (!opt) {
 		fprintf(stderr, "error, missing link\n");
 		return -EINVAL;
 	}
@@ -245,8 +250,7 @@
 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_CNT]),
 	       mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_TOT]) / proft);
 
-	printf("  0-64:%u%% -256:%u%% -1024:%u%% -4096:%u%% "
-	       "-16384:%u%% -32768:%u%% -66000:%u%%\n",
+	printf("  0-64:%u%% -256:%u%% -1024:%u%% -4096:%u%% -16384:%u%% -32768:%u%% -66000:%u%%\n",
 	       perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P0]), proft),
 	       perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P1]), proft),
 	       perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P2]), proft),
@@ -365,7 +369,7 @@
 	char buf[MNL_SOCKET_BUFFER_SIZE];
 	struct opt *opt;
 	struct opt opts[] = {
-		{ "link",		NULL },
+		{ "link",		OPT_KEYVAL,	NULL },
 		{ NULL }
 	};
 
@@ -374,7 +378,8 @@
 		return -EINVAL;
 	}
 
-	if (!(nlh = msg_init(buf, TIPC_NL_LINK_GET))) {
+	nlh = msg_init(buf, TIPC_NL_LINK_GET);
+	if (!nlh) {
 		fprintf(stderr, "error, message initialisation failed\n");
 		return -1;
 	}
@@ -382,7 +387,8 @@
 	if (parse_opts(opts, cmdl) < 0)
 		return -EINVAL;
 
-	if ((opt = get_opt(opts, "link")))
+	opt = get_opt(opts, "link");
+	if (opt)
 		link = opt->val;
 
 	return msg_dumpit(nlh, link_stat_show_cb, link);
@@ -429,7 +435,7 @@
 	struct nlattr *attrs;
 	struct opt *opt;
 	struct opt opts[] = {
-		{ "link",	NULL },
+		{ "link",		OPT_KEYVAL,	NULL },
 		{ NULL }
 	};
 
@@ -456,13 +462,15 @@
 	if (parse_opts(opts, cmdl) < 0)
 		return -EINVAL;
 
-	if (!(nlh = msg_init(buf, TIPC_NL_LINK_SET))) {
+	nlh = msg_init(buf, TIPC_NL_LINK_SET);
+	if (!nlh) {
 		fprintf(stderr, "error, message initialisation failed\n");
 		return -1;
 	}
 	attrs = mnl_attr_nest_start(nlh, TIPC_NLA_LINK);
 
-	if (!(opt = get_opt(opts, "link"))) {
+	opt = get_opt(opts, "link");
+	if (!opt) {
 		fprintf(stderr, "error, missing link\n");
 		return -EINVAL;
 	}
@@ -475,8 +483,6 @@
 	mnl_attr_nest_end(nlh, attrs);
 
 	return msg_doit(nlh, link_get_cb, &prop);
-
-	return 0;
 }
 
 static int cmd_link_set(struct nlmsghdr *nlh, const struct cmd *cmd,
@@ -492,6 +498,414 @@
 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
 }
 
+static int cmd_link_mon_set_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
+				 struct cmdl *cmdl, void *data)
+{
+	int size;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlattr *attrs;
+
+	if (cmdl->argc != cmdl->optind + 1) {
+		fprintf(stderr, "error, missing value\n");
+		return -EINVAL;
+	}
+	size = atoi(shift_cmdl(cmdl));
+
+	nlh = msg_init(buf, TIPC_NL_MON_SET);
+	if (!nlh) {
+		fprintf(stderr, "error, message initialisation failed\n");
+		return -1;
+	}
+	attrs = mnl_attr_nest_start(nlh, TIPC_NLA_MON);
+
+	mnl_attr_put_u32(nlh, TIPC_NLA_MON_ACTIVATION_THRESHOLD, size);
+
+	mnl_attr_nest_end(nlh, attrs);
+
+	return msg_doit(nlh, NULL, NULL);
+}
+
+static int link_mon_summary_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+	struct nlattr *attrs[TIPC_NLA_MON_MAX + 1] = {};
+
+	mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+	if (!info[TIPC_NLA_MON])
+		return MNL_CB_ERROR;
+
+	mnl_attr_parse_nested(info[TIPC_NLA_MON], parse_attrs, attrs);
+
+	printf("\nbearer %s\n",
+		mnl_attr_get_str(attrs[TIPC_NLA_MON_BEARER_NAME]));
+
+	printf("    table_generation %u\n",
+	       mnl_attr_get_u32(attrs[TIPC_NLA_MON_LISTGEN]));
+	printf("    cluster_size %u\n",
+		mnl_attr_get_u32(attrs[TIPC_NLA_MON_PEERCNT]));
+	printf("    algorithm %s\n",
+		attrs[TIPC_NLA_MON_ACTIVE] ? "overlapping-ring" : "full-mesh");
+
+	return MNL_CB_OK;
+}
+
+static int cmd_link_mon_summary(struct nlmsghdr *nlh, const struct cmd *cmd,
+				struct cmdl *cmdl, void *data)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+
+	if (help_flag) {
+		fprintf(stderr,	"Usage: %s monitor summary\n", cmdl->argv[0]);
+		return -EINVAL;
+	}
+
+	nlh = msg_init(buf, TIPC_NL_MON_GET);
+	if (!nlh) {
+		fprintf(stderr, "error, message initialisation failed\n");
+		return -1;
+	}
+
+	return msg_dumpit(nlh, link_mon_summary_cb, NULL);
+}
+
+#define STATUS_WIDTH 7
+#define MAX_NODE_WIDTH 14 /* 255.4095.4095 */
+#define MAX_DOM_GEN_WIDTH 11 /* 65535 */
+#define DIRECTLY_MON_WIDTH 10
+
+#define APPL_NODE_STATUS_WIDTH 5
+
+static int map_get(uint64_t up_map, int i)
+{
+	return (up_map & (1 << i)) >> i;
+}
+
+/* print the applied members, since we know the the members
+ * are listed in ascending order, we print only the state
+ */
+static void link_mon_print_applied(uint16_t applied, uint64_t up_map)
+{
+	int i;
+	char state;
+
+	for (i = 0; i < applied; i++) {
+		/* print the delimiter for every -n- entry */
+		if (i && !(i % APPL_NODE_STATUS_WIDTH))
+			printf(",");
+
+		state = map_get(up_map, i) ? 'U' : 'D';
+		printf("%c", state);
+	}
+}
+
+/* print the non applied members, since we dont know
+ * the members, we print them along with the state
+ */
+static void link_mon_print_non_applied(uint16_t applied, uint16_t member_cnt,
+				       uint64_t up_map,  uint32_t *members)
+{
+	int i;
+	char state;
+
+	printf(" [");
+	for (i = applied; i < member_cnt; i++) {
+		char addr_str[16];
+
+		/* print the delimiter for every entry */
+		if (i != applied)
+			printf(",");
+
+		sprintf(addr_str, "%u.%u.%u:", tipc_zone(members[i]),
+			tipc_cluster(members[i]), tipc_node(members[i]));
+		state = map_get(up_map, i) ? 'U' : 'D';
+		printf("%s%c", addr_str, state);
+	}
+	printf("]");
+}
+
+static void link_mon_print_peer_state(const uint32_t addr, const char *status,
+				      const char *monitored,
+				      const uint32_t dom_gen)
+{
+	char addr_str[16];
+
+	sprintf(addr_str, "%u.%u.%u", tipc_zone(addr), tipc_cluster(addr),
+		tipc_node(addr));
+
+	printf("%-*s", MAX_NODE_WIDTH, addr_str);
+	printf("%-*s", STATUS_WIDTH, status);
+	printf("%-*s", DIRECTLY_MON_WIDTH, monitored);
+	printf("%-*u", MAX_DOM_GEN_WIDTH, dom_gen);
+}
+
+static int link_mon_peer_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct nlattr *attrs[TIPC_NLA_MON_PEER_MAX + 1] = {};
+	struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+	uint16_t member_cnt;
+	uint32_t applied;
+	uint32_t dom_gen;
+	uint64_t up_map;
+	char status[16];
+	char monitored[16];
+
+	mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+	if (!info[TIPC_NLA_MON_PEER])
+		return MNL_CB_ERROR;
+
+	mnl_attr_parse_nested(info[TIPC_NLA_MON_PEER], parse_attrs, attrs);
+
+	(attrs[TIPC_NLA_MON_PEER_LOCAL] || attrs[TIPC_NLA_MON_PEER_HEAD]) ?
+		strcpy(monitored, "direct") :
+		strcpy(monitored, "indirect");
+
+	attrs[TIPC_NLA_MON_PEER_UP] ?
+		strcpy(status, "up") :
+		strcpy(status, "down");
+
+	dom_gen = attrs[TIPC_NLA_MON_PEER_DOMGEN] ?
+		mnl_attr_get_u32(attrs[TIPC_NLA_MON_PEER_DOMGEN]) : 0;
+
+	link_mon_print_peer_state(mnl_attr_get_u32(attrs[TIPC_NLA_MON_PEER_ADDR]),
+				  status, monitored, dom_gen);
+
+	applied = mnl_attr_get_u32(attrs[TIPC_NLA_MON_PEER_APPLIED]);
+
+	if (!applied)
+		goto exit;
+
+	up_map = mnl_attr_get_u64(attrs[TIPC_NLA_MON_PEER_UPMAP]);
+
+	member_cnt = mnl_attr_get_payload_len(attrs[TIPC_NLA_MON_PEER_MEMBERS]);
+
+	/* each tipc address occupies 4 bytes of payload, hence compensate it */
+	member_cnt /= sizeof(uint32_t);
+
+	link_mon_print_applied(applied, up_map);
+
+	link_mon_print_non_applied(applied, member_cnt, up_map,
+				   mnl_attr_get_payload(attrs[TIPC_NLA_MON_PEER_MEMBERS]));
+
+exit:
+	printf("\n");
+
+	return MNL_CB_OK;
+}
+
+static int link_mon_peer_list(uint32_t mon_ref)
+{
+	struct nlmsghdr *nlh;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlattr *nest;
+
+	nlh = msg_init(buf, TIPC_NL_MON_PEER_GET);
+	if (!nlh) {
+		fprintf(stderr, "error, message initialisation failed\n");
+		return -1;
+	}
+
+	nest = mnl_attr_nest_start(nlh, TIPC_NLA_MON);
+	mnl_attr_put_u32(nlh, TIPC_NLA_MON_REF, mon_ref);
+	mnl_attr_nest_end(nlh, nest);
+
+	return msg_dumpit(nlh, link_mon_peer_list_cb, NULL);
+}
+
+static int link_mon_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+	struct nlattr *attrs[TIPC_NLA_MON_MAX + 1] = {};
+	char *req_bearer = data;
+	const char *bname;
+	const char title[] =
+	  "node          status monitored generation applied_node_status [non_applied_node:status]";
+
+	mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+	if (!info[TIPC_NLA_MON])
+		return MNL_CB_ERROR;
+
+	mnl_attr_parse_nested(info[TIPC_NLA_MON], parse_attrs, attrs);
+
+	bname = mnl_attr_get_str(attrs[TIPC_NLA_MON_BEARER_NAME]);
+
+	if (*req_bearer && (strcmp(req_bearer, bname) != 0))
+		return MNL_CB_OK;
+
+	printf("\nbearer %s\n", bname);
+	printf("%s\n", title);
+
+	if (mnl_attr_get_u32(attrs[TIPC_NLA_MON_PEERCNT]))
+		link_mon_peer_list(mnl_attr_get_u32(attrs[TIPC_NLA_MON_REF]));
+
+	return MNL_CB_OK;
+}
+
+static void cmd_link_mon_list_help(struct cmdl *cmdl)
+{
+	fprintf(stderr, "Usage: %s monitor list [ media MEDIA ARGS...]\n\n",
+		cmdl->argv[0]);
+	print_bearer_media();
+}
+
+static void cmd_link_mon_list_l2_help(struct cmdl *cmdl, char *media)
+{
+	fprintf(stderr,
+		"Usage: %s monitor list media %s device DEVICE [OPTIONS]\n",
+		cmdl->argv[0], media);
+}
+
+static void cmd_link_mon_list_udp_help(struct cmdl *cmdl, char *media)
+{
+	fprintf(stderr,
+		"Usage: %s monitor list media udp name NAME\n\n",
+		cmdl->argv[0]);
+}
+
+static int cmd_link_mon_list(struct nlmsghdr *nlh, const struct cmd *cmd,
+			     struct cmdl *cmdl, void *data)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	char bname[TIPC_MAX_BEARER_NAME] = {0};
+	struct opt opts[] = {
+		{ "media",	OPT_KEYVAL,	NULL },
+		{ "device",	OPT_KEYVAL,	NULL },
+		{ "name",	OPT_KEYVAL,	NULL },
+		{ NULL }
+	};
+	struct tipc_sup_media sup_media[] = {
+		{ "udp",        "name",         cmd_link_mon_list_udp_help},
+		{ "eth",        "device",       cmd_link_mon_list_l2_help },
+		{ "ib",         "device",       cmd_link_mon_list_l2_help },
+		{ NULL, },
+	};
+
+	int err;
+
+	if (parse_opts(opts, cmdl) < 0)
+		return -EINVAL;
+
+	if (get_opt(opts, "media")) {
+		err = cmd_get_unique_bearer_name(cmd, cmdl, opts, bname,
+						 sup_media);
+		if (err)
+			return err;
+	}
+
+	if (help_flag) {
+		cmd->help(cmdl);
+		return -EINVAL;
+	}
+
+	nlh = msg_init(buf, TIPC_NL_MON_GET);
+	if (!nlh) {
+		fprintf(stderr, "error, message initialisation failed\n");
+		return -1;
+	}
+
+	return msg_dumpit(nlh, link_mon_list_cb, bname);
+}
+
+static void cmd_link_mon_set_help(struct cmdl *cmdl)
+{
+	fprintf(stderr, "Usage: %s monitor set PPROPERTY\n\n"
+		"PROPERTIES\n"
+		" threshold SIZE	- Set monitor activation threshold\n",
+		cmdl->argv[0]);
+}
+
+static int cmd_link_mon_set(struct nlmsghdr *nlh, const struct cmd *cmd,
+			    struct cmdl *cmdl, void *data)
+{
+	const struct cmd cmds[] = {
+		{ "threshold",	cmd_link_mon_set_prop,	NULL },
+		{ NULL }
+	};
+
+	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+static void cmd_link_mon_get_help(struct cmdl *cmdl)
+{
+	fprintf(stderr, "Usage: %s monitor get PPROPERTY\n\n"
+		"PROPERTIES\n"
+		" threshold	- Get monitor activation threshold\n",
+		cmdl->argv[0]);
+}
+
+static int link_mon_get_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+	struct nlattr *attrs[TIPC_NLA_MON_MAX + 1] = {};
+
+	mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+	if (!info[TIPC_NLA_MON])
+		return MNL_CB_ERROR;
+
+	mnl_attr_parse_nested(info[TIPC_NLA_MON], parse_attrs, attrs);
+	if (!attrs[TIPC_NLA_MON_ACTIVATION_THRESHOLD])
+		return MNL_CB_ERROR;
+
+	printf("%u\n",
+	       mnl_attr_get_u32(attrs[TIPC_NLA_MON_ACTIVATION_THRESHOLD]));
+
+	return MNL_CB_OK;
+}
+
+static int cmd_link_mon_get_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
+				 struct cmdl *cmdl, void *data)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+
+	nlh = msg_init(buf, TIPC_NL_MON_GET);
+	if (!nlh) {
+		fprintf(stderr, "error, message initialisation failed\n");
+		return -1;
+	}
+
+	return msg_doit(nlh,	link_mon_get_cb,	NULL);
+}
+
+static int cmd_link_mon_get(struct nlmsghdr *nlh, const struct cmd *cmd,
+			    struct cmdl *cmdl, void *data)
+{
+	const struct cmd cmds[] = {
+		{ "threshold",	cmd_link_mon_get_prop,	NULL},
+		{ NULL }
+	};
+
+	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+static void cmd_link_mon_help(struct cmdl *cmdl)
+{
+	fprintf(stderr,
+		"Usage: %s montior COMMAND [ARGS] ...\n\n"
+		"COMMANDS\n"
+		" set			- Set monitor properties\n"
+		" get			- Get monitor properties\n"
+		" list			- List all cluster members\n"
+		" summary		- Show local node monitor summary\n",
+		cmdl->argv[0]);
+}
+
+static int cmd_link_mon(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+			void *data)
+{
+	const struct cmd cmds[] = {
+		{ "set",	cmd_link_mon_set,	cmd_link_mon_set_help },
+		{ "get",	cmd_link_mon_get,	cmd_link_mon_get_help },
+		{ "list",	cmd_link_mon_list,	cmd_link_mon_list_help },
+		{ "summary",	cmd_link_mon_summary,	NULL },
+		{ NULL }
+	};
+
+	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
 void cmd_link_help(struct cmdl *cmdl)
 {
 	fprintf(stderr,
@@ -501,7 +915,8 @@
 		" list                  - List links\n"
 		" get                   - Get various link properties\n"
 		" set                   - Set various link properties\n"
-		" statistics            - Show or reset statistics\n",
+		" statistics            - Show or reset statistics\n"
+		" monitor               - Show or set link supervision\n",
 		cmdl->argv[0]);
 }
 
@@ -513,6 +928,7 @@
 		{ "list",	cmd_link_list,	NULL },
 		{ "set",	cmd_link_set,	cmd_link_set_help },
 		{ "statistics", cmd_link_stat,	cmd_link_stat_help },
+		{ "monitor",	cmd_link_mon,	cmd_link_mon_help },
 		{ NULL }
 	};
 
diff --git a/tipc/media.c b/tipc/media.c
index a902ab7..6e10c7e 100644
--- a/tipc/media.c
+++ b/tipc/media.c
@@ -93,7 +93,7 @@
 	struct nlattr *nest;
 	struct opt *opt;
 	struct opt opts[] = {
-		{ "media",		NULL },
+		{ "media",		OPT_KEYVAL,	NULL },
 		{ NULL }
 	};
 
@@ -173,7 +173,7 @@
 	struct nlattr *attrs;
 	struct opt *opt;
 	struct opt opts[] = {
-		{ "media",		NULL },
+		{ "media",		OPT_KEYVAL,	NULL },
 		{ NULL }
 	};
 
diff --git a/tipc/peer.c b/tipc/peer.c
new file mode 100644
index 0000000..de0c73c
--- /dev/null
+++ b/tipc/peer.c
@@ -0,0 +1,93 @@
+/*
+ * peer.c	TIPC peer functionality.
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ *
+ * Authors:	Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <linux/tipc_netlink.h>
+#include <linux/tipc.h>
+#include <linux/genetlink.h>
+#include <libmnl/libmnl.h>
+
+#include "cmdl.h"
+#include "msg.h"
+#include "misc.h"
+#include "peer.h"
+
+static int cmd_peer_rm_addr(struct nlmsghdr *nlh, const struct cmd *cmd,
+			    struct cmdl *cmdl, void *data)
+{
+	char *str;
+	uint32_t addr;
+	struct nlattr *nest;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+
+	if ((cmdl->argc != cmdl->optind + 1) || help_flag) {
+		fprintf(stderr, "Usage: %s peer remove address ADDRESS\n",
+			cmdl->argv[0]);
+		return -EINVAL;
+	}
+
+	str = shift_cmdl(cmdl);
+	addr = str2addr(str);
+	if (!addr)
+		return -1;
+
+	if (!(nlh = msg_init(buf, TIPC_NL_PEER_REMOVE))) {
+		fprintf(stderr, "error, message initialisation failed\n");
+		return -1;
+	}
+
+	nest = mnl_attr_nest_start(nlh, TIPC_NLA_NET);
+	mnl_attr_put_u32(nlh, TIPC_NLA_NET_ADDR, addr);
+	mnl_attr_nest_end(nlh, nest);
+
+	return msg_doit(nlh, NULL, NULL);
+}
+
+static void cmd_peer_rm_help(struct cmdl *cmdl)
+{
+	fprintf(stderr, "Usage: %s peer remove address ADDRESS\n",
+		cmdl->argv[0]);
+}
+
+static int cmd_peer_rm(struct nlmsghdr *nlh, const struct cmd *cmd,
+			struct cmdl *cmdl, void *data)
+{
+	const struct cmd cmds[] = {
+		{ "address",	cmd_peer_rm_addr,	cmd_peer_rm_help },
+		{ NULL }
+	};
+
+	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+void cmd_peer_help(struct cmdl *cmdl)
+{
+	fprintf(stderr,
+		"Usage: %s peer COMMAND [ARGS] ...\n\n"
+		"COMMANDS\n"
+		" remove                - Remove an offline peer node\n",
+		cmdl->argv[0]);
+}
+
+int cmd_peer(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+	     void *data)
+{
+	const struct cmd cmds[] = {
+		{ "remove",	cmd_peer_rm,	cmd_peer_rm_help },
+		{ NULL }
+	};
+
+	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
diff --git a/tipc/peer.h b/tipc/peer.h
new file mode 100644
index 0000000..8972261
--- /dev/null
+++ b/tipc/peer.h
@@ -0,0 +1,21 @@
+/*
+ * peer.h	TIPC peer functionality.
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ *
+ * Authors:	Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#ifndef _TIPC_PEER_H
+#define _TIPC_PEER_H
+
+extern int help_flag;
+
+int cmd_peer(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+	     void *data);
+void cmd_peer_help(struct cmdl *cmdl);
+
+#endif
diff --git a/tipc/tipc.c b/tipc/tipc.c
index 4439805..600d5e2 100644
--- a/tipc/tipc.c
+++ b/tipc/tipc.c
@@ -20,6 +20,7 @@
 #include "socket.h"
 #include "media.h"
 #include "node.h"
+#include "peer.h"
 #include "cmdl.h"
 
 int help_flag;
@@ -39,6 +40,7 @@
 		" media                 - Show or modify media\n"
 		" nametable             - Show nametable\n"
 		" node                  - Show or modify node related parameters\n"
+		" peer                  - Peer related operations\n"
 		" socket                - Show sockets\n",
 		cmdl->argv[0]);
 }
@@ -59,6 +61,7 @@
 		{ "media",	cmd_media,	cmd_media_help},
 		{ "nametable",	cmd_nametable,	cmd_nametable_help},
 		{ "node",	cmd_node,	cmd_node_help},
+		{ "peer",	cmd_peer,	cmd_peer_help},
 		{ "socket",	cmd_socket,	cmd_socket_help},
 		{ NULL }
 	};