Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 1 | /* drivers/net/pppopns.c |
| 2 | * |
| 3 | * Driver for PPP on PPTP Network Server / PPPoPNS Socket (RFC 2637) |
| 4 | * |
| 5 | * Copyright (C) 2009 Google, Inc. |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 6 | * |
| 7 | * This software is licensed under the terms of the GNU General Public |
| 8 | * License version 2, as published by the Free Software Foundation, and |
| 9 | * may be copied, distributed, and modified under those terms. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | * GNU General Public License for more details. |
| 15 | */ |
| 16 | |
| 17 | /* This driver handles PPTP data packets between a RAW socket and a PPP channel. |
| 18 | * The socket is created in the kernel space and connected to the same address |
| 19 | * of the control socket. To keep things simple, packets are always sent with |
| 20 | * sequence but without acknowledgement. This driver should work on both IPv4 |
| 21 | * and IPv6. */ |
| 22 | |
| 23 | #include <linux/module.h> |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 24 | #include <linux/workqueue.h> |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 25 | #include <linux/skbuff.h> |
| 26 | #include <linux/file.h> |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 27 | #include <linux/netdevice.h> |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 28 | #include <linux/net.h> |
| 29 | #include <linux/ppp_defs.h> |
| 30 | #include <linux/if.h> |
| 31 | #include <linux/if_ppp.h> |
| 32 | #include <linux/if_pppox.h> |
| 33 | #include <linux/ppp_channel.h> |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 34 | #include <asm/uaccess.h> |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 35 | |
| 36 | #define GRE_HEADER_SIZE 8 |
| 37 | |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 38 | #define PPTP_GRE_BITS htons(0x2001) |
| 39 | #define PPTP_GRE_BITS_MASK htons(0xEF7F) |
| 40 | #define PPTP_GRE_SEQ_BIT htons(0x1000) |
| 41 | #define PPTP_GRE_ACK_BIT htons(0x0080) |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 42 | #define PPTP_GRE_TYPE htons(0x880B) |
| 43 | |
| 44 | #define PPP_ADDR 0xFF |
| 45 | #define PPP_CTRL 0x03 |
| 46 | |
| 47 | struct header { |
| 48 | __u16 bits; |
| 49 | __u16 type; |
| 50 | __u16 length; |
| 51 | __u16 call; |
| 52 | __u32 sequence; |
| 53 | } __attribute__((packed)); |
| 54 | |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 55 | static int pppopns_recv_core(struct sock *sk_raw, struct sk_buff *skb) |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 56 | { |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 57 | struct sock *sk = (struct sock *)sk_raw->sk_user_data; |
| 58 | struct pppopns_opt *opt = &pppox_sk(sk)->proto.pns; |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 59 | struct header *hdr; |
| 60 | |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 61 | /* Skip transport header */ |
| 62 | skb_pull(skb, skb_transport_header(skb) - skb->data); |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 63 | |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 64 | /* Drop the packet if it is too short. */ |
| 65 | if (skb->len < GRE_HEADER_SIZE) |
| 66 | goto drop; |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 67 | |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 68 | /* Check the header. */ |
| 69 | hdr = (struct header *)skb->data; |
| 70 | if (hdr->type != PPTP_GRE_TYPE || hdr->call != opt->local || |
| 71 | (hdr->bits & PPTP_GRE_BITS_MASK) != PPTP_GRE_BITS) |
| 72 | goto drop; |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 73 | |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 74 | /* Skip all fields including optional ones. */ |
| 75 | if (!skb_pull(skb, GRE_HEADER_SIZE + |
| 76 | (hdr->bits & PPTP_GRE_SEQ_BIT ? 4 : 0) + |
| 77 | (hdr->bits & PPTP_GRE_ACK_BIT ? 4 : 0))) |
| 78 | goto drop; |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 79 | |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 80 | /* Check the length. */ |
| 81 | if (skb->len != ntohs(hdr->length)) |
| 82 | goto drop; |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 83 | |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 84 | /* Skip PPP address and control if they are present. */ |
| 85 | if (skb->len >= 2 && skb->data[0] == PPP_ADDR && |
| 86 | skb->data[1] == PPP_CTRL) |
| 87 | skb_pull(skb, 2); |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 88 | |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 89 | /* Fix PPP protocol if it is compressed. */ |
| 90 | if (skb->len >= 1 && skb->data[0] & 1) |
| 91 | skb_push(skb, 1)[0] = 0; |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 92 | |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 93 | /* Finally, deliver the packet to PPP channel. */ |
| 94 | skb_orphan(skb); |
| 95 | ppp_input(&pppox_sk(sk)->chan, skb); |
| 96 | return NET_RX_SUCCESS; |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 97 | drop: |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 98 | kfree_skb(skb); |
| 99 | return NET_RX_DROP; |
| 100 | } |
| 101 | |
| 102 | static void pppopns_recv(struct sock *sk_raw, int length) |
| 103 | { |
| 104 | struct sk_buff *skb; |
| 105 | while ((skb = skb_dequeue(&sk_raw->sk_receive_queue))) { |
| 106 | sock_hold(sk_raw); |
| 107 | sk_receive_skb(sk_raw, skb, 0); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | static struct sk_buff_head delivery_queue; |
| 112 | |
| 113 | static void pppopns_xmit_core(struct work_struct *delivery_work) |
| 114 | { |
| 115 | mm_segment_t old_fs = get_fs(); |
| 116 | struct sk_buff *skb; |
| 117 | |
| 118 | set_fs(KERNEL_DS); |
| 119 | while ((skb = skb_dequeue(&delivery_queue))) { |
| 120 | struct sock *sk_raw = skb->sk; |
| 121 | struct kvec iov = {.iov_base = skb->data, .iov_len = skb->len}; |
| 122 | struct msghdr msg = { |
| 123 | .msg_iov = (struct iovec *)&iov, |
| 124 | .msg_iovlen = 1, |
| 125 | .msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT, |
| 126 | }; |
| 127 | sk_raw->sk_prot->sendmsg(NULL, sk_raw, &msg, skb->len); |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 128 | kfree_skb(skb); |
| 129 | } |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 130 | set_fs(old_fs); |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 131 | } |
| 132 | |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 133 | static DECLARE_WORK(delivery_work, pppopns_xmit_core); |
| 134 | |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 135 | static int pppopns_xmit(struct ppp_channel *chan, struct sk_buff *skb) |
| 136 | { |
| 137 | struct sock *sk_raw = (struct sock *)chan->private; |
| 138 | struct pppopns_opt *opt = &pppox_sk(sk_raw->sk_user_data)->proto.pns; |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 139 | struct header *hdr; |
| 140 | __u16 length; |
| 141 | |
| 142 | /* Install PPP address and control. */ |
| 143 | skb_push(skb, 2); |
| 144 | skb->data[0] = PPP_ADDR; |
| 145 | skb->data[1] = PPP_CTRL; |
| 146 | length = skb->len; |
| 147 | |
| 148 | /* Install PPTP GRE header. */ |
| 149 | hdr = (struct header *)skb_push(skb, 12); |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 150 | hdr->bits = PPTP_GRE_BITS | PPTP_GRE_SEQ_BIT; |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 151 | hdr->type = PPTP_GRE_TYPE; |
| 152 | hdr->length = htons(length); |
| 153 | hdr->call = opt->remote; |
| 154 | hdr->sequence = htonl(opt->sequence); |
| 155 | opt->sequence++; |
| 156 | |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 157 | /* Now send the packet via the delivery queue. */ |
| 158 | skb_set_owner_w(skb, sk_raw); |
| 159 | skb_queue_tail(&delivery_queue, skb); |
| 160 | schedule_work(&delivery_work); |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 161 | return 1; |
| 162 | } |
| 163 | |
| 164 | /******************************************************************************/ |
| 165 | |
| 166 | static struct ppp_channel_ops pppopns_channel_ops = { |
| 167 | .start_xmit = pppopns_xmit, |
| 168 | }; |
| 169 | |
| 170 | static int pppopns_connect(struct socket *sock, struct sockaddr *useraddr, |
| 171 | int addrlen, int flags) |
| 172 | { |
| 173 | struct sock *sk = sock->sk; |
| 174 | struct pppox_sock *po = pppox_sk(sk); |
| 175 | struct sockaddr_pppopns *addr = (struct sockaddr_pppopns *)useraddr; |
| 176 | struct sockaddr_storage ss; |
| 177 | struct socket *sock_tcp = NULL; |
| 178 | struct socket *sock_raw = NULL; |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 179 | struct sock *sk_tcp; |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 180 | struct sock *sk_raw; |
| 181 | int error; |
| 182 | |
| 183 | if (addrlen != sizeof(struct sockaddr_pppopns)) |
| 184 | return -EINVAL; |
| 185 | |
| 186 | lock_sock(sk); |
| 187 | error = -EALREADY; |
| 188 | if (sk->sk_state != PPPOX_NONE) |
| 189 | goto out; |
| 190 | |
| 191 | sock_tcp = sockfd_lookup(addr->tcp_socket, &error); |
| 192 | if (!sock_tcp) |
| 193 | goto out; |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 194 | sk_tcp = sock_tcp->sk; |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 195 | error = -EPROTONOSUPPORT; |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 196 | if (sk_tcp->sk_protocol != IPPROTO_TCP) |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 197 | goto out; |
| 198 | addrlen = sizeof(struct sockaddr_storage); |
| 199 | error = kernel_getpeername(sock_tcp, (struct sockaddr *)&ss, &addrlen); |
| 200 | if (error) |
| 201 | goto out; |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 202 | if (!sk_tcp->sk_bound_dev_if) { |
| 203 | struct dst_entry *dst = sk_dst_get(sk_tcp); |
| 204 | error = -ENODEV; |
| 205 | if (!dst) |
| 206 | goto out; |
| 207 | sk_tcp->sk_bound_dev_if = dst->dev->ifindex; |
| 208 | dst_release(dst); |
| 209 | } |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 210 | |
| 211 | error = sock_create(ss.ss_family, SOCK_RAW, IPPROTO_GRE, &sock_raw); |
| 212 | if (error) |
| 213 | goto out; |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 214 | sk_raw = sock_raw->sk; |
| 215 | sk_raw->sk_bound_dev_if = sk_tcp->sk_bound_dev_if; |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 216 | error = kernel_connect(sock_raw, (struct sockaddr *)&ss, addrlen, 0); |
| 217 | if (error) |
| 218 | goto out; |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 219 | |
| 220 | po->chan.hdrlen = 14; |
| 221 | po->chan.private = sk_raw; |
| 222 | po->chan.ops = &pppopns_channel_ops; |
| 223 | po->chan.mtu = PPP_MTU - 80; |
| 224 | po->proto.pns.local = addr->local; |
| 225 | po->proto.pns.remote = addr->remote; |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 226 | po->proto.pns.data_ready = sk_raw->sk_data_ready; |
| 227 | po->proto.pns.backlog_rcv = sk_raw->sk_backlog_rcv; |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 228 | |
| 229 | error = ppp_register_channel(&po->chan); |
| 230 | if (error) |
| 231 | goto out; |
| 232 | |
| 233 | sk->sk_state = PPPOX_CONNECTED; |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 234 | lock_sock(sk_raw); |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 235 | sk_raw->sk_data_ready = pppopns_recv; |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 236 | sk_raw->sk_backlog_rcv = pppopns_recv_core; |
| 237 | sk_raw->sk_user_data = sk; |
| 238 | release_sock(sk_raw); |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 239 | out: |
| 240 | if (sock_tcp) |
| 241 | sockfd_put(sock_tcp); |
| 242 | if (error && sock_raw) |
| 243 | sock_release(sock_raw); |
| 244 | release_sock(sk); |
| 245 | return error; |
| 246 | } |
| 247 | |
| 248 | static int pppopns_release(struct socket *sock) |
| 249 | { |
| 250 | struct sock *sk = sock->sk; |
| 251 | |
| 252 | if (!sk) |
| 253 | return 0; |
| 254 | |
| 255 | lock_sock(sk); |
| 256 | if (sock_flag(sk, SOCK_DEAD)) { |
| 257 | release_sock(sk); |
| 258 | return -EBADF; |
| 259 | } |
| 260 | |
| 261 | if (sk->sk_state != PPPOX_NONE) { |
| 262 | struct sock *sk_raw = (struct sock *)pppox_sk(sk)->chan.private; |
| 263 | lock_sock(sk_raw); |
| 264 | pppox_unbind_sock(sk); |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 265 | sk_raw->sk_data_ready = pppox_sk(sk)->proto.pns.data_ready; |
| 266 | sk_raw->sk_backlog_rcv = pppox_sk(sk)->proto.pns.backlog_rcv; |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 267 | sk_raw->sk_user_data = NULL; |
| 268 | release_sock(sk_raw); |
| 269 | sock_release(sk_raw->sk_socket); |
| 270 | } |
| 271 | |
| 272 | sock_orphan(sk); |
| 273 | sock->sk = NULL; |
| 274 | release_sock(sk); |
| 275 | sock_put(sk); |
| 276 | return 0; |
| 277 | } |
| 278 | |
| 279 | /******************************************************************************/ |
| 280 | |
| 281 | static struct proto pppopns_proto = { |
| 282 | .name = "PPPOPNS", |
| 283 | .owner = THIS_MODULE, |
| 284 | .obj_size = sizeof(struct pppox_sock), |
| 285 | }; |
| 286 | |
| 287 | static struct proto_ops pppopns_proto_ops = { |
| 288 | .family = PF_PPPOX, |
| 289 | .owner = THIS_MODULE, |
| 290 | .release = pppopns_release, |
| 291 | .bind = sock_no_bind, |
| 292 | .connect = pppopns_connect, |
| 293 | .socketpair = sock_no_socketpair, |
| 294 | .accept = sock_no_accept, |
| 295 | .getname = sock_no_getname, |
| 296 | .poll = sock_no_poll, |
| 297 | .ioctl = pppox_ioctl, |
| 298 | .listen = sock_no_listen, |
| 299 | .shutdown = sock_no_shutdown, |
| 300 | .setsockopt = sock_no_setsockopt, |
| 301 | .getsockopt = sock_no_getsockopt, |
| 302 | .sendmsg = sock_no_sendmsg, |
| 303 | .recvmsg = sock_no_recvmsg, |
| 304 | .mmap = sock_no_mmap, |
| 305 | }; |
| 306 | |
| 307 | static int pppopns_create(struct net *net, struct socket *sock) |
| 308 | { |
| 309 | struct sock *sk; |
| 310 | |
| 311 | sk = sk_alloc(net, PF_PPPOX, GFP_KERNEL, &pppopns_proto); |
| 312 | if (!sk) |
| 313 | return -ENOMEM; |
| 314 | |
| 315 | sock_init_data(sock, sk); |
| 316 | sock->state = SS_UNCONNECTED; |
| 317 | sock->ops = &pppopns_proto_ops; |
| 318 | sk->sk_protocol = PX_PROTO_OPNS; |
| 319 | sk->sk_state = PPPOX_NONE; |
| 320 | return 0; |
| 321 | } |
| 322 | |
| 323 | /******************************************************************************/ |
| 324 | |
| 325 | static struct pppox_proto pppopns_pppox_proto = { |
| 326 | .create = pppopns_create, |
| 327 | .owner = THIS_MODULE, |
| 328 | }; |
| 329 | |
| 330 | static int __init pppopns_init(void) |
| 331 | { |
| 332 | int error; |
| 333 | |
| 334 | error = proto_register(&pppopns_proto, 0); |
| 335 | if (error) |
| 336 | return error; |
| 337 | |
| 338 | error = register_pppox_proto(PX_PROTO_OPNS, &pppopns_pppox_proto); |
| 339 | if (error) |
| 340 | proto_unregister(&pppopns_proto); |
Chia-chi Yeh | c54f674 | 2009-06-13 02:29:04 +0800 | [diff] [blame^] | 341 | else |
| 342 | skb_queue_head_init(&delivery_queue); |
Chia-chi Yeh | 803341c | 2009-06-12 01:09:30 +0800 | [diff] [blame] | 343 | return error; |
| 344 | } |
| 345 | |
| 346 | static void __exit pppopns_exit(void) |
| 347 | { |
| 348 | unregister_pppox_proto(PX_PROTO_OPNS); |
| 349 | proto_unregister(&pppopns_proto); |
| 350 | } |
| 351 | |
| 352 | module_init(pppopns_init); |
| 353 | module_exit(pppopns_exit); |
| 354 | |
| 355 | MODULE_DESCRIPTION("PPP on PPTP Network Server (PPPoPNS)"); |
| 356 | MODULE_AUTHOR("Chia-chi Yeh <chiachi@android.com>"); |
| 357 | MODULE_LICENSE("GPL"); |