From patchwork Thu Mar 18 18:14:26 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Chapman X-Patchwork-Id: 48080 X-Patchwork-Delegate: shemminger@vyatta.com Return-Path: X-Original-To: patchwork-incoming@ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3015EB7CFC for ; Fri, 19 Mar 2010 05:14:41 +1100 (EST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752702Ab0CRSOe (ORCPT ); Thu, 18 Mar 2010 14:14:34 -0400 Received: from katalix.com ([82.103.140.233]:48185 "EHLO mail.katalix.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752548Ab0CRSOe (ORCPT ); Thu, 18 Mar 2010 14:14:34 -0400 Received: from localhost (localhost.localdomain [127.0.0.1]) by mail.katalix.com (Postfix) with ESMTP id 8B678A620B1; Thu, 18 Mar 2010 18:14:33 +0000 (GMT) X-Virus-Scanned: amavisd-new at katalix.com Received: from mail.katalix.com ([127.0.0.1]) by localhost (mail.katalix.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id gx69PwpvuVo0; Thu, 18 Mar 2010 18:14:26 +0000 (GMT) Received: from bert.katalix.com (localhost.localdomain [127.0.0.1]) by mail.katalix.com (Postfix) with ESMTP id CB5E8A62087; Thu, 18 Mar 2010 18:14:26 +0000 (GMT) Subject: [RFC PATCH 2/2] iproute2: Add support for static L2TPv3 tunnels. To: shemminger@vyatta.com From: James Chapman Cc: netdev@vger.kernel.org Date: Thu, 18 Mar 2010 18:14:26 +0000 Message-ID: <20100318181426.6862.7278.stgit@bert.katalix.com> In-Reply-To: <20100318181416.6862.60894.stgit@bert.katalix.com> References: <20100318181416.6862.60894.stgit@bert.katalix.com> User-Agent: StGit/0.15 MIME-Version: 1.0 Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Requires kernel with L2TPv3 ethernet support. This patch adds a new series of commands under "ip l2tp", to configure and show L2TPv3 static tunnels. Only L2TPv3 ethernet pseudowires are supported at the moment. Usage: ip l2tp add tunnel remote ADDR local ADDR tunnel_id ID peer_tunnel_id ID [ encap { ip | udp } ] [ udp_sport PORT ] [ udp_dport PORT ] ip l2tp add session tunnel_id ID session_id ID peer_session_id ID [ cookie HEXSTR ] [ peer_cookie HEXSTR ] [ offset OFFSET ] [ peer_offset OFFSET ] ip l2tp del tunnel tunnel_id ID ip l2tp del session tunnel_id ID session_id ID ip l2tp show tunnel [ tunnel_id ID ] ip l2tp show session [ tunnel_id ID ] [ session_id ID ] Where: NAME := STRING ADDR := { IP_ADDRESS | any } PORT := { 0..65535 } ID := { 1..4294967295 } HEXSTR := { 8 or 16 hex digits (4 / 8 bytes) } --- include/linux/l2tp.h | 164 ++++++++++ ip/Makefile | 2 ip/ip.c | 3 ip/ip_common.h | 1 ip/ipl2tp.c | 815 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 983 insertions(+), 2 deletions(-) create mode 100644 include/linux/l2tp.h create mode 100644 ip/ipl2tp.c -- To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/include/linux/l2tp.h b/include/linux/l2tp.h new file mode 100644 index 0000000..c22a119 --- /dev/null +++ b/include/linux/l2tp.h @@ -0,0 +1,164 @@ +/* + * L2TP public kernel interfaces. + * + * Author: James Chapman + */ + +#ifndef _LINUX_L2TP_H_ +#define _LINUX_L2TP_H_ + +#include +#ifdef __KERNEL__ +#include +#include +#else +#include +#endif + +#define IPPROTO_L2TP 115 + +/** + * struct sockaddr_l2tpip - the sockaddr structure for L2TP-over-IP sockets + * @l2tp_family: address family number AF_L2TPIP. + * @l2tp_addr: protocol specific address information + * @l2tp_conn_id: connection id of tunnel + */ +#define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */ +struct sockaddr_l2tpip { + /* The first fields must match struct sockaddr_in */ + sa_family_t l2tp_family; /* AF_INET */ + __be16 l2tp_unused; /* INET port number (unused) */ + struct in_addr l2tp_addr; /* Internet address */ + + __u32 l2tp_conn_id; /* Connection ID of tunnel */ + + /* Pad to size of `struct sockaddr'. */ + unsigned char __pad[sizeof(struct sockaddr) - sizeof(sa_family_t) - + sizeof(__be16) - sizeof(struct in_addr) - + sizeof(__u32)]; +}; + +/***************************************************************************** + * NETLINK_GENERIC netlink family. + *****************************************************************************/ + +/* + * Commands. + * Valid TLVs of each command are:- + * TUNNEL_CREATE - CONN_ID, pw_type, netns, ifname, ipinfo, udpinfo, udpcsum, vlanid + * TUNNEL_DELETE - CONN_ID + * TUNNEL_MODIFY - CONN_ID, udpcsum + * TUNNEL_GETSTATS - CONN_ID, (stats) + * TUNNEL_GET - CONN_ID, (...) + * SESSION_CREATE - SESSION_ID, PW_TYPE, offset, data_seq, cookie, peer_cookie, offset, l2spec + * SESSION_DELETE - SESSION_ID + * SESSION_MODIFY - SESSION_ID, data_seq + * SESSION_GET - SESSION_ID, (...) + * SESSION_GETSTATS - SESSION_ID, (stats) + * + */ +enum { + L2TP_CMD_NOOP, + L2TP_CMD_TUNNEL_CREATE, + L2TP_CMD_TUNNEL_DELETE, + L2TP_CMD_TUNNEL_MODIFY, + L2TP_CMD_TUNNEL_GET, + L2TP_CMD_SESSION_CREATE, + L2TP_CMD_SESSION_DELETE, + L2TP_CMD_SESSION_MODIFY, + L2TP_CMD_SESSION_GET, + __L2TP_CMD_MAX, +}; + +#define L2TP_CMD_MAX (__L2TP_CMD_MAX - 1) + +/* + * ATTR types defined for L2TP + */ +enum { + L2TP_ATTR_NONE, /* no data */ + L2TP_ATTR_PW_TYPE, /* u16, enum l2tp_pwtype */ + L2TP_ATTR_ENCAP_TYPE, /* u16, enum l2tp_encap_type */ + L2TP_ATTR_OFFSET, /* u16 */ + L2TP_ATTR_DATA_SEQ, /* u16 */ + L2TP_ATTR_L2SPEC_TYPE, /* u8, enum l2tp_l2spec_type */ + L2TP_ATTR_L2SPEC_LEN, /* u8, enum l2tp_l2spec_type */ + L2TP_ATTR_PROTO_VERSION, /* u8 */ + L2TP_ATTR_IFNAME, /* string */ + L2TP_ATTR_CONN_ID, /* u32 */ + L2TP_ATTR_PEER_CONN_ID, /* u32 */ + L2TP_ATTR_SESSION_ID, /* u32 */ + L2TP_ATTR_PEER_SESSION_ID, /* u32 */ + L2TP_ATTR_UDP_CSUM, /* flag */ + L2TP_ATTR_VLAN_ID, /* u16 */ + L2TP_ATTR_COOKIE, /* 0, 4 or 8 bytes */ + L2TP_ATTR_PEER_COOKIE, /* 0, 4 or 8 bytes */ + L2TP_ATTR_DEBUG, /* u32 */ + L2TP_ATTR_RECV_SEQ, /* flag */ + L2TP_ATTR_SEND_SEQ, /* flag */ + L2TP_ATTR_LNS_MODE, /* flag */ + L2TP_ATTR_USING_IPSEC, /* flag */ + L2TP_ATTR_RECV_TIMEOUT, /* msec */ + L2TP_ATTR_FD, /* int */ + L2TP_ATTR_IP_SADDR, /* u32 */ + L2TP_ATTR_IP_DADDR, /* u32 */ + L2TP_ATTR_UDP_SPORT, /* u16 */ + L2TP_ATTR_UDP_DPORT, /* u16 */ + L2TP_ATTR_MTU, /* u16 */ + L2TP_ATTR_MRU, /* u16 */ + L2TP_ATTR_STATS, /* nested */ + __L2TP_ATTR_MAX, +}; + +#define L2TP_ATTR_MAX (__L2TP_ATTR_MAX - 1) + +/* Nested in L2TP_ATTR_STATS */ +enum { + L2TP_ATTR_STATS_NONE, /* no data */ + L2TP_ATTR_TX_PACKETS, /* u64 */ + L2TP_ATTR_TX_BYTES, /* u64 */ + L2TP_ATTR_TX_ERRORS, /* u64 */ + L2TP_ATTR_RX_PACKETS, /* u64 */ + L2TP_ATTR_RX_BYTES, /* u64 */ + L2TP_ATTR_RX_SEQ_DISCARDS, /* u64 */ + L2TP_ATTR_RX_OOS_PACKETS, /* u64 */ + L2TP_ATTR_RX_ERRORS, /* u64 */ + __L2TP_ATTR_STATS_MAX, +}; + +#define L2TP_ATTR_STATS_MAX (__L2TP_ATTR_STATS_MAX - 1) + +enum l2tp_pwtype { + L2TP_PWTYPE_NONE = 0x0000, + L2TP_PWTYPE_ETH_VLAN = 0x0004, + L2TP_PWTYPE_ETH = 0x0005, + L2TP_PWTYPE_PPP = 0x0007, + L2TP_PWTYPE_PPP_AC = 0x0008, + L2TP_PWTYPE_IP = 0x000b, + __L2TP_PWTYPE_MAX +}; + +enum l2tp_l2spec_type { + L2TP_L2SPECTYPE_NONE, + L2TP_L2SPECTYPE_DEFAULT, +}; + +enum l2tp_encap_type { + L2TP_ENCAPTYPE_UDP, + L2TP_ENCAPTYPE_IP, +}; + + +enum l2tp_seqmode { + L2TP_SEQ_NONE = 0, + L2TP_SEQ_IP = 1, + L2TP_SEQ_ALL = 2, +}; + +/* + * NETLINK_GENERIC related info + */ +#define L2TP_GENL_NAME "l2tp" +#define L2TP_GENL_VERSION 0x1 + +#endif diff --git a/ip/Makefile b/ip/Makefile index 2f223ca..b167770 100644 --- a/ip/Makefile +++ b/ip/Makefile @@ -3,7 +3,7 @@ IPOBJ=ip.o ipaddress.o ipaddrlabel.o iproute.o iprule.o \ ipmaddr.o ipmonitor.o ipmroute.o ipprefix.o iptuntap.o \ ipxfrm.o xfrm_state.o xfrm_policy.o xfrm_monitor.o \ iplink_vlan.o link_veth.o link_gre.o iplink_can.o \ - iplink_macvlan.o + iplink_macvlan.o ipl2tp.o RTMONOBJ=rtmon.o diff --git a/ip/ip.c b/ip/ip.c index e0cf175..b1b5fb9 100644 --- a/ip/ip.c +++ b/ip/ip.c @@ -42,7 +42,7 @@ static void usage(void) "Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }\n" " ip [ -force ] -batch filename\n" "where OBJECT := { link | addr | addrlabel | route | rule | neigh | ntable |\n" -" tunnel | tuntap | maddr | mroute | monitor | xfrm }\n" +" tunnel | tuntap | maddr | mroute | monitor | xfrm | l2tp }\n" " OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |\n" " -f[amily] { inet | inet6 | ipx | dnet | link } |\n" " -o[neline] | -t[imestamp] | -b[atch] [filename] |\n" @@ -73,6 +73,7 @@ static const struct cmd { { "tunl", do_iptunnel }, { "tuntap", do_iptuntap }, { "tap", do_iptuntap }, + { "l2tp", do_ipl2tp }, { "monitor", do_ipmonitor }, { "xfrm", do_xfrm }, { "mroute", do_multiroute }, diff --git a/ip/ip_common.h b/ip/ip_common.h index c857667..e01be73 100644 --- a/ip/ip_common.h +++ b/ip/ip_common.h @@ -38,6 +38,7 @@ extern int do_ipmonitor(int argc, char **argv); extern int do_multiaddr(int argc, char **argv); extern int do_multiroute(int argc, char **argv); extern int do_xfrm(int argc, char **argv); +extern int do_ipl2tp(int argc, char **argv); static inline int rtm_get_table(struct rtmsg *r, struct rtattr **tb) { diff --git a/ip/ipl2tp.c b/ip/ipl2tp.c new file mode 100644 index 0000000..520a07d --- /dev/null +++ b/ip/ipl2tp.c @@ -0,0 +1,815 @@ +/* + * ipl2tp.c "ip l2tp" + * + * 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: James Chapman + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "utils.h" +#include "ip_common.h" + +enum { + L2TP_ADD, + L2TP_CHG, + L2TP_DEL, + L2TP_GET +}; + +struct l2tp_parm { + uint32_t tunnel_id; + uint32_t peer_tunnel_id; + uint32_t session_id; + uint32_t peer_session_id; + uint32_t offset; + uint32_t peer_offset; + enum l2tp_encap_type encap; + uint16_t local_udp_port; + uint16_t peer_udp_port; + int cookie_len; + uint8_t cookie[8]; + int peer_cookie_len; + uint8_t peer_cookie[8]; + struct in_addr local_ip; + struct in_addr peer_ip; + + uint16_t pw_type; + uint16_t mtu; + int udp_csum:1; + int recv_seq:1; + int send_seq:1; + int lns_mode:1; + int data_seq:2; + int tunnel:1; + int session:1; + int reorder_timeout; + char *ifname; +}; + +struct l2tp_stats { + uint64_t data_rx_packets; + uint64_t data_rx_bytes; + uint64_t data_rx_errors; + uint64_t data_rx_oos_packets; + uint64_t data_rx_oos_discards; + uint64_t data_tx_packets; + uint64_t data_tx_bytes; + uint64_t data_tx_errors; +}; + +struct l2tp_data { + struct l2tp_parm config; + struct l2tp_stats stats; +}; + +/* netlink socket */ +static struct nl_handle *nl_sock; +static int nl_family; + +/***************************************************************************** + * Netlink actions + *****************************************************************************/ + +static int create_tunnel(struct l2tp_parm *p) +{ + struct nl_msg *msg; + int result = 0; + + msg = nlmsg_alloc(); + + genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, nl_family, 0, NLM_F_REQUEST, + L2TP_CMD_TUNNEL_CREATE, L2TP_GENL_VERSION); + + nla_put_u32(msg, L2TP_ATTR_CONN_ID, p->tunnel_id); + nla_put_u32(msg, L2TP_ATTR_PEER_CONN_ID, p->peer_tunnel_id); + nla_put_u8(msg, L2TP_ATTR_PROTO_VERSION, 3); + nla_put_u16(msg, L2TP_ATTR_ENCAP_TYPE, p->encap); + + nla_put_u32(msg, L2TP_ATTR_IP_SADDR, p->local_ip.s_addr); + nla_put_u32(msg, L2TP_ATTR_IP_DADDR, p->peer_ip.s_addr); + if (p->encap == L2TP_ENCAPTYPE_UDP) { + nla_put_u16(msg, L2TP_ATTR_UDP_SPORT, p->local_udp_port); + nla_put_u16(msg, L2TP_ATTR_UDP_DPORT, p->peer_udp_port); + } + + nl_send_auto_complete(nl_sock, msg); + + nlmsg_free(msg); + + result = nl_wait_for_ack(nl_sock); + if (result > 0) { + result = 0; + } + + return result; +} + +static int delete_tunnel(struct l2tp_parm *p) +{ + struct nl_msg *msg; + int result; + + msg = nlmsg_alloc(); + + genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, nl_family, 0, NLM_F_REQUEST, + L2TP_CMD_TUNNEL_DELETE, L2TP_GENL_VERSION); + + nla_put_u32(msg, L2TP_ATTR_CONN_ID, p->tunnel_id); + + nl_send_auto_complete(nl_sock, msg); + + nlmsg_free(msg); + + result = nl_wait_for_ack(nl_sock); + if (result > 0) { + result = 0; + } + + return result; +} + +static int create_session(struct l2tp_parm *p) +{ + struct nl_msg *msg; + int result = 0; + + msg = nlmsg_alloc(); + + genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, nl_family, 0, NLM_F_REQUEST, + L2TP_CMD_SESSION_CREATE, L2TP_GENL_VERSION); + + nla_put_u32(msg, L2TP_ATTR_CONN_ID, p->tunnel_id); + nla_put_u32(msg, L2TP_ATTR_PEER_CONN_ID, p->peer_tunnel_id); + nla_put_u32(msg, L2TP_ATTR_SESSION_ID, p->session_id); + nla_put_u32(msg, L2TP_ATTR_PEER_SESSION_ID, p->peer_session_id); + nla_put_u16(msg, L2TP_ATTR_PW_TYPE, p->pw_type); + if (p->mtu) { + nla_put_u16(msg, L2TP_ATTR_MTU, p->mtu); + } + if (p->recv_seq) { + nla_put_flag(msg, L2TP_ATTR_RECV_SEQ); + } + if (p->send_seq) { + nla_put_flag(msg, L2TP_ATTR_SEND_SEQ); + } + if (p->lns_mode) { + nla_put_flag(msg, L2TP_ATTR_LNS_MODE); + } + if (p->data_seq) { + nla_put_u8(msg, L2TP_ATTR_DATA_SEQ, p->data_seq); + } + if (p->reorder_timeout) { + nla_put_msecs(msg, L2TP_ATTR_RECV_TIMEOUT, p->reorder_timeout); + } + if (p->offset) { + nla_put_u16(msg, L2TP_ATTR_OFFSET, p->offset); + } + if (p->cookie_len) { + nla_put(msg, L2TP_ATTR_COOKIE, p->cookie_len, p->cookie); + } + if (p->peer_cookie_len) { + nla_put(msg, L2TP_ATTR_PEER_COOKIE, p->peer_cookie_len, p->peer_cookie); + } + if (p->ifname && p->ifname[0]) { + nla_put_string(msg, L2TP_ATTR_IFNAME, p->ifname); + } + + nl_send_auto_complete(nl_sock, msg); + + nlmsg_free(msg); + + result = nl_wait_for_ack(nl_sock); + if (result > 0) { + result = 0; + } + + return result; +} + +static int delete_session(struct l2tp_parm *p) +{ + struct nl_msg *msg; + int result = 0; + + msg = nlmsg_alloc(); + + genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, nl_family, 0, NLM_F_REQUEST, + L2TP_CMD_SESSION_DELETE, L2TP_GENL_VERSION); + + nla_put_u32(msg, L2TP_ATTR_CONN_ID, p->tunnel_id); + nla_put_u32(msg, L2TP_ATTR_SESSION_ID, p->session_id); + + nl_send_auto_complete(nl_sock, msg); + + nlmsg_free(msg); + + result = nl_wait_for_ack(nl_sock); + if (result > 0) { + result = 0; + } + + return result; +} + +static void print_cookie(char *name, uint8_t *cookie, int len) +{ + printf(" %s %02x%02x%02x%02x", name, + cookie[0], cookie[1], + cookie[2], cookie[3]); + if (len == 8) + printf("%02x%02x%02x%02x", + cookie[4], cookie[5], + cookie[6], cookie[7]); +} + +static void print_tunnel(struct l2tp_data *data) +{ + struct l2tp_parm *p = &data->config; + + printf("Tunnel %u, encap %s\n", + p->tunnel_id, + p->encap == L2TP_ENCAPTYPE_UDP ? "UDP" : + p->encap == L2TP_ENCAPTYPE_IP ? "IP" : "??"); + printf(" From %s ", inet_ntoa(p->local_ip)); + printf("to %s\n", inet_ntoa(p->peer_ip)); + printf(" Peer tunnel %u\n", + p->peer_tunnel_id); + + if (p->encap == L2TP_ENCAPTYPE_UDP) + printf(" UDP source / dest ports: %hu/%hu\n", + p->local_udp_port, p->peer_udp_port); +} + +static void print_session(struct l2tp_data *data) +{ + struct l2tp_parm *p = &data->config; + + printf("Session %u in tunnel %u\n", + p->session_id, p->tunnel_id); + printf(" Peer session %u, tunnel %u\n", + p->peer_session_id, p->peer_tunnel_id); + + if (p->ifname != NULL) { + printf(" interface name: %s\n", p->ifname); + } + printf(" offset %u, peer offset %u\n", + p->offset, p->peer_offset); + if (p->cookie_len > 0) + print_cookie("cookie", p->cookie, p->cookie_len); + if (p->peer_cookie_len > 0) + print_cookie("peer cookie", p->peer_cookie, p->peer_cookie_len); + + if (p->reorder_timeout != 0) { + printf(" reorder timeout: %u\n", p->reorder_timeout); + } +} + +static int nl_get_response(struct nl_msg *msg, void *arg) +{ + struct l2tp_data *data = arg; + struct l2tp_parm *p = &data->config; + struct nlmsghdr *nlh = nlmsg_hdr(msg); + struct nlattr *attrs[L2TP_ATTR_MAX + 1]; + struct nlattr *nla_stats; + int result = 0; + + /* Validate message and parse attributes */ + genlmsg_parse(nlh, 0, attrs, L2TP_ATTR_MAX, NULL); + if (nlh->nlmsg_type == NLMSG_ERROR) { + result = -EBADMSG; + goto out; + } + + if (attrs[L2TP_ATTR_PW_TYPE]) + p->pw_type = nla_get_u16(attrs[L2TP_ATTR_PW_TYPE]); + if (attrs[L2TP_ATTR_ENCAP_TYPE]) + p->encap = nla_get_u16(attrs[L2TP_ATTR_ENCAP_TYPE]); + if (attrs[L2TP_ATTR_OFFSET]) + p->offset = nla_get_u16(attrs[L2TP_ATTR_OFFSET]); + if (attrs[L2TP_ATTR_DATA_SEQ]) + p->data_seq = nla_get_u16(attrs[L2TP_ATTR_DATA_SEQ]); + if (attrs[L2TP_ATTR_CONN_ID]) + p->tunnel_id = nla_get_u32(attrs[L2TP_ATTR_CONN_ID]); + if (attrs[L2TP_ATTR_PEER_CONN_ID]) + p->peer_tunnel_id = nla_get_u32(attrs[L2TP_ATTR_PEER_CONN_ID]); + if (attrs[L2TP_ATTR_SESSION_ID]) + p->session_id = nla_get_u32(attrs[L2TP_ATTR_SESSION_ID]); + if (attrs[L2TP_ATTR_PEER_SESSION_ID]) + p->peer_session_id = nla_get_u32(attrs[L2TP_ATTR_PEER_SESSION_ID]); + if (attrs[L2TP_ATTR_UDP_CSUM]) + p->udp_csum = nla_get_flag(attrs[L2TP_ATTR_UDP_CSUM]); + if (attrs[L2TP_ATTR_COOKIE]) { + nla_memcpy(&p->cookie[0], attrs[L2TP_ATTR_COOKIE], sizeof(p->cookie)); + p->cookie_len = nla_len(attrs[L2TP_ATTR_COOKIE]); + } + if (attrs[L2TP_ATTR_PEER_COOKIE]) { + nla_memcpy(&p->peer_cookie[0], attrs[L2TP_ATTR_PEER_COOKIE], sizeof(p->peer_cookie)); + p->peer_cookie_len = nla_len(attrs[L2TP_ATTR_PEER_COOKIE]); + } + if (attrs[L2TP_ATTR_RECV_SEQ]) + p->recv_seq = nla_get_flag(attrs[L2TP_ATTR_RECV_SEQ]); + if (attrs[L2TP_ATTR_SEND_SEQ]) + p->send_seq = nla_get_flag(attrs[L2TP_ATTR_SEND_SEQ]); + if (attrs[L2TP_ATTR_RECV_TIMEOUT]) + p->reorder_timeout = nla_get_msecs(attrs[L2TP_ATTR_RECV_TIMEOUT]); + if (attrs[L2TP_ATTR_IP_SADDR]) + p->local_ip.s_addr = nla_get_u32(attrs[L2TP_ATTR_IP_SADDR]); + if (attrs[L2TP_ATTR_IP_DADDR]) + p->peer_ip.s_addr = nla_get_u32(attrs[L2TP_ATTR_IP_DADDR]); + if (attrs[L2TP_ATTR_UDP_SPORT]) + p->local_udp_port = nla_get_u16(attrs[L2TP_ATTR_UDP_SPORT]); + if (attrs[L2TP_ATTR_UDP_DPORT]) + p->peer_udp_port = nla_get_u16(attrs[L2TP_ATTR_UDP_DPORT]); + if (attrs[L2TP_ATTR_MTU]) + p->mtu = nla_get_u16(attrs[L2TP_ATTR_MTU]); + if (attrs[L2TP_ATTR_IFNAME]) + p->ifname = nla_get_string(attrs[L2TP_ATTR_IFNAME]); + + nla_stats = attrs[L2TP_ATTR_STATS]; + if (nla_stats) { + struct nlattr *tb[L2TP_ATTR_STATS_MAX + 1]; + + result = nla_parse_nested(tb, L2TP_ATTR_STATS_MAX, nla_stats, NULL); + if (result < 0) + goto out; + + if (tb[L2TP_ATTR_TX_PACKETS]) + data->stats.data_tx_packets = nla_get_u64(tb[L2TP_ATTR_TX_PACKETS]); + if (tb[L2TP_ATTR_TX_BYTES]) + data->stats.data_tx_bytes = nla_get_u64(tb[L2TP_ATTR_TX_BYTES]); + if (tb[L2TP_ATTR_TX_ERRORS]) + data->stats.data_tx_errors = nla_get_u64(tb[L2TP_ATTR_TX_ERRORS]); + if (tb[L2TP_ATTR_RX_PACKETS]) + data->stats.data_rx_packets = nla_get_u64(tb[L2TP_ATTR_RX_PACKETS]); + if (tb[L2TP_ATTR_RX_BYTES]) + data->stats.data_rx_bytes = nla_get_u64(tb[L2TP_ATTR_RX_BYTES]); + if (tb[L2TP_ATTR_RX_ERRORS]) + data->stats.data_rx_errors = nla_get_u64(tb[L2TP_ATTR_RX_ERRORS]); + if (tb[L2TP_ATTR_RX_SEQ_DISCARDS]) + data->stats.data_rx_oos_discards = nla_get_u64(tb[L2TP_ATTR_RX_SEQ_DISCARDS]); + if (tb[L2TP_ATTR_RX_OOS_PACKETS]) + data->stats.data_rx_oos_packets = nla_get_u64(tb[L2TP_ATTR_RX_OOS_PACKETS]); + } + + result = 0; + +out: + return result; +} + +static int nl_session_get_response(struct nl_msg *msg, void *arg) +{ + int ret = nl_get_response(msg, arg); + + if (ret == 0) + print_session(arg); + + return ret; +} + +static int nl_tunnel_get_response(struct nl_msg *msg, void *arg) +{ + int ret = nl_get_response(msg, arg); + + if (ret == 0) + print_tunnel(arg); + + return ret; +} + +static int get_session(struct l2tp_data *p) +{ + struct nl_msg *msg; + struct nl_cb *cb; + int result = -EPROTONOSUPPORT; + enum nl_cb_kind cb_kind = NL_CB_DEFAULT; + int flags = NLM_F_DUMP; + + if (nl_family <= 0) { + goto out; + } + + cb = nl_cb_alloc(cb_kind); + if (!cb) { + goto out; + } + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, nl_session_get_response, p); + + msg = nlmsg_alloc(); + + if (p->config.tunnel_id && p->config.session_id) + flags = NLM_F_REQUEST; + + genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, nl_family, 0, flags, + L2TP_CMD_SESSION_GET, L2TP_GENL_VERSION); + + if (p->config.tunnel_id && p->config.session_id) { + nla_put_u32(msg, L2TP_ATTR_CONN_ID, p->config.tunnel_id); + nla_put_u32(msg, L2TP_ATTR_SESSION_ID, p->config.session_id); + } + + nl_send_auto_complete(nl_sock, msg); + + nlmsg_free(msg); + + result = nl_recvmsgs(nl_sock, cb); + if (result > 0) { + result = nl_wait_for_ack(nl_sock); + } + + if (result > 0) { + result = 0; + } + + nl_cb_put(cb); + +out: + return result; +} + +static int get_tunnel(struct l2tp_data *p) +{ + struct nl_msg *msg; + struct nl_cb *cb; + int result = -EPROTONOSUPPORT; + enum nl_cb_kind cb_kind = NL_CB_DEFAULT; + int flags = NLM_F_DUMP; + + if (nl_family <= 0) { + goto out; + } + + cb = nl_cb_alloc(cb_kind); + if (!cb) { + goto out; + } + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, nl_tunnel_get_response, p); + + msg = nlmsg_alloc(); + + if (p->config.tunnel_id) + flags = NLM_F_REQUEST; + + genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, nl_family, 0, flags, + L2TP_CMD_TUNNEL_GET, L2TP_GENL_VERSION); + + if (p->config.tunnel_id) + nla_put_u32(msg, L2TP_ATTR_CONN_ID, p->config.tunnel_id); + + nl_send_auto_complete(nl_sock, msg); + + nlmsg_free(msg); + + result = nl_recvmsgs(nl_sock, cb); + if (result > 0) { + result = nl_wait_for_ack(nl_sock); + } + + if (result > 0) { + result = 0; + } + + nl_cb_put(cb); + +out: + return result; +} + +/***************************************************************************** + * Command parser + *****************************************************************************/ + +static int hex(char ch) +{ + if ((ch >= 'a') && (ch <= 'f')) + return ch - 'a' + 10; + if ((ch >= '0') && (ch <= '9')) + return ch - '0'; + if ((ch >= 'A') && (ch <= 'F')) + return ch - 'A' + 10; + return -1; +} + +static int hex2mem(char *buf, uint8_t *mem, int count) +{ + int i, j; + int c; + + for (i = 0, j = 0; i < count; i++, j += 2) { + c = hex(buf[j]); + if (c < 0) + goto err; + + mem[i] = c << 4; + + c = hex(buf[j + 1]); + if (c < 0) + goto err; + + mem[i] |= c; + } + + return 0; + +err: + return -1; +} + +static void usage(void) __attribute__((noreturn)); + +static void usage(void) +{ + fprintf(stderr, "Usage: ip l2tp add tunnel\n"); + fprintf(stderr, " remote ADDR local ADDR\n"); + fprintf(stderr, " tunnel_id ID peer_tunnel_id ID\n"); + fprintf(stderr, " [ encap { ip | udp } ]\n"); + fprintf(stderr, " [ udp_sport PORT ] [ udp_dport PORT ]\n"); + fprintf(stderr, "Usage: ip l2tp add session\n"); + fprintf(stderr, " tunnel_id ID\n"); + fprintf(stderr, " session_id ID peer_session_id ID\n"); + fprintf(stderr, " [ cookie HEXSTR ] [ peer_cookie HEXSTR ]\n"); + fprintf(stderr, " [ offset OFFSET ] [ peer_offset OFFSET ]\n"); + fprintf(stderr, " ip l2tp del tunnel tunnel_id ID\n"); + fprintf(stderr, " ip l2tp del session tunnel_id ID session_id ID\n"); + fprintf(stderr, " ip l2tp show tunnel [ tunnel_id ID ]\n"); + fprintf(stderr, " ip l2tp show session [ tunnel_id ID ] [ session_id ID ]\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Where: NAME := STRING\n"); + fprintf(stderr, " ADDR := { IP_ADDRESS | any }\n"); + fprintf(stderr, " PORT := { 0..65535 }\n"); + fprintf(stderr, " ID := { 1..4294967295 }\n"); + fprintf(stderr, " HEXSTR := { 8 or 16 hex digits (4 / 8 bytes) }\n"); + exit(-1); +} + +static int parse_args(int argc, char **argv, int cmd, struct l2tp_parm *p) +{ + memset(p, 0, sizeof(*p)); + + if (argc == 0) + usage(); + + while (argc > 0) { + if (strcmp(*argv, "encap") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "ip") == 0) { + p->encap = L2TP_ENCAPTYPE_IP; + } else if (strcmp(*argv, "udp") == 0) { + p->encap = L2TP_ENCAPTYPE_UDP; + } else { + fprintf(stderr, "Unknown tunnel encapsulation.\n"); + exit(-1); + } + } else if (strcmp(*argv, "remote") == 0) { + NEXT_ARG(); + p->peer_ip.s_addr = get_addr32(*argv); + } else if (strcmp(*argv, "local") == 0) { + NEXT_ARG(); + p->local_ip.s_addr = get_addr32(*argv); + } else if ((strcmp(*argv, "tunnel_id") == 0) || + (strcmp(*argv, "tid") == 0)) { + __u32 uval; + NEXT_ARG(); + if (get_u32(&uval, *argv, 0)) + invarg("invalid ID\n", *argv); + p->tunnel_id = uval; + } else if ((strcmp(*argv, "peer_tunnel_id") == 0) || + (strcmp(*argv, "ptid") == 0)) { + __u32 uval; + NEXT_ARG(); + if (get_u32(&uval, *argv, 0)) + invarg("invalid ID\n", *argv); + p->peer_tunnel_id = uval; + } else if ((strcmp(*argv, "session_id") == 0) || + (strcmp(*argv, "sid") == 0)) { + __u32 uval; + NEXT_ARG(); + if (get_u32(&uval, *argv, 0)) + invarg("invalid ID\n", *argv); + p->session_id = uval; + } else if ((strcmp(*argv, "peer_session_id") == 0) || + (strcmp(*argv, "psid") == 0)) { + __u32 uval; + NEXT_ARG(); + if (get_u32(&uval, *argv, 0)) + invarg("invalid ID\n", *argv); + p->peer_session_id = uval; + } else if (strcmp(*argv, "udp_sport") == 0) { + __u16 uval; + NEXT_ARG(); + if (get_u16(&uval, *argv, 0)) + invarg("invalid port\n", *argv); + p->local_udp_port = uval; + } else if (strcmp(*argv, "udp_dport") == 0) { + __u16 uval; + NEXT_ARG(); + if (get_u16(&uval, *argv, 0)) + invarg("invalid port\n", *argv); + p->peer_udp_port = uval; + } else if (strcmp(*argv, "offset") == 0) { + __u8 uval; + NEXT_ARG(); + if (get_u8(&uval, *argv, 0)) + invarg("invalid offset\n", *argv); + p->offset = uval; + } else if (strcmp(*argv, "peer_offset") == 0) { + __u8 uval; + NEXT_ARG(); + if (get_u8(&uval, *argv, 0)) + invarg("invalid offset\n", *argv); + p->peer_offset = uval; + } else if (strcmp(*argv, "cookie") == 0) { + int slen; + NEXT_ARG(); + slen = strlen(*argv); + if ((slen != 8) && (slen != 16)) + invarg("cookie must be either 8 or 16 hex digits\n", *argv); + + p->cookie_len = slen / 2; + if (hex2mem(*argv, p->cookie, p->cookie_len) < 0) + invarg("cookie must be a hex string\n", *argv); + } else if (strcmp(*argv, "peer_cookie") == 0) { + int slen; + NEXT_ARG(); + slen = strlen(*argv); + if ((slen != 8) && (slen != 16)) + invarg("cookie must be either 8 or 16 hex digits\n", *argv); + + p->peer_cookie_len = slen / 2; + if (hex2mem(*argv, p->peer_cookie, p->peer_cookie_len) < 0) + invarg("cookie must be a hex string\n", *argv); + } else if (strcmp(*argv, "tunnel") == 0) { + p->tunnel = 1; + } else if (strcmp(*argv, "session") == 0) { + p->session = 1; + } else if (matches(*argv, "help") == 0) { + usage(); + } else { + fprintf(stderr, "Unknown command: %s\n", *argv); + usage(); + } + + argc--; argv++; + } + + return 0; +} + + +static int do_add(int argc, char **argv) +{ + struct l2tp_parm p; + int ret = 0; + + if (parse_args(argc, argv, L2TP_ADD, &p) < 0) + return -1; + + if (!p.tunnel && !p.session) + missarg("tunnel or session"); + + if (p.tunnel_id == 0) + missarg("tunnel_id"); + + /* session_id and peer_session_id must be provided for sessions */ + if ((p.session) && (p.peer_session_id == 0)) + missarg("peer_session_id"); + if ((p.session) && (p.session_id == 0)) + missarg("session_id"); + + /* peer_tunnel_id is needed for tunnels */ + if ((p.tunnel) && (p.peer_tunnel_id == 0)) + missarg("peer_tunnel_id"); + + if (p.tunnel) { + if (p.local_ip.s_addr == 0) + missarg("local"); + + if (p.peer_ip.s_addr == 0) + missarg("remote"); + + if (p.encap == L2TP_ENCAPTYPE_UDP) { + if (p.local_udp_port == 0) + missarg("udp_sport"); + if (p.peer_udp_port == 0) + missarg("udp_dport"); + } + + ret = create_tunnel(&p); + } + + if (p.session) { + /* Only ethernet pseudowires supported */ + p.pw_type = L2TP_PWTYPE_ETH; + + ret = create_session(&p); + } + + return ret; +} + +static int do_del(int argc, char **argv) +{ + struct l2tp_parm p; + + if (parse_args(argc, argv, L2TP_DEL, &p) < 0) + return -1; + + if (!p.tunnel && !p.session) + missarg("tunnel or session"); + + if ((p.tunnel) && (p.tunnel_id == 0)) + missarg("tunnel_id"); + if ((p.session) && (p.session_id == 0)) + missarg("session_id"); + + if (p.session_id) + return delete_session(&p); + else + return delete_tunnel(&p); + + return -1; +} + +static int do_show(int argc, char **argv) +{ + struct l2tp_data data; + struct l2tp_parm *p = &data.config; + + if (parse_args(argc, argv, L2TP_GET, p) < 0) + return -1; + + if (!p->tunnel && !p->session) + missarg("tunnel or session"); + + if (p->session) + get_session(&data); + else + get_tunnel(&data); + + return 0; +} + +int do_ipl2tp(int argc, char **argv) +{ + nl_sock = nl_handle_alloc(); + if (!nl_sock) { + perror("nl_handle_alloc"); + return 1; + } + + if (nl_connect(nl_sock, NETLINK_GENERIC) < 0) { + perror("nl_connect"); + return 1; + } + + nl_family = genl_ctrl_resolve(nl_sock, L2TP_GENL_NAME); + if (nl_family < 0) { + fprintf(stderr, "L2TP netlink support unavailable.\n"); + return 1; + } + + if (argc > 0) { + if (matches(*argv, "add") == 0) + return do_add(argc-1, argv+1); + if (matches(*argv, "del") == 0) + return do_del(argc-1, argv+1); + if (matches(*argv, "show") == 0 || + matches(*argv, "lst") == 0 || + matches(*argv, "list") == 0) + return do_show(argc-1, argv+1); + if (matches(*argv, "help") == 0) + usage(); + + fprintf(stderr, "Command \"%s\" is unknown, try \"ip l2tp help\".\n", *argv); + exit(-1); + } + + usage(); +}