/* * Copyright (c) 2000 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* ----------------------------------------------------------------------------- * * Theory of operation : * * plugin to add PPTP client support to pppd. * ----------------------------------------------------------------------------- */ /* ----------------------------------------------------------------------------- Includes ----------------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define APPLE 1 #include "../../../Controller/ppp_msg.h" #include "../../../Family/ppp_defs.h" #include "../../../Family/if_ppp.h" #include "../../../Family/ppp_domain.h" #include "../PPTP-extension/PPTP.h" #include "../../../Helpers/pppd/pppd.h" #include "../../../Helpers/pppd/fsm.h" #include "../../../Helpers/pppd/lcp.h" #include "pptp.h" /* ----------------------------------------------------------------------------- Definitions ----------------------------------------------------------------------------- */ #define MODE_CONNECT "connect" #define MODE_LISTEN "listen" #define MODE_ANSWER "answer" #define PPTP_MIN_HDR_SIZE 44 /* IPHdr(20) + GRE(16) + PPP/MPPE(8) */ #define PPTP_RETRY_CONNECT_CODE 1 #define PPTP_DEFAULT_WAIT_IF_TIMEOUT 10 /* seconds */ /* ----------------------------------------------------------------------------- Forward declarations ----------------------------------------------------------------------------- */ void pptp_process_extra_options(); void pptp_check_options(); int pptp_connect(int *errorcode); void pptp_disconnect(); void pptp_close(); void pptp_cleanup(); int pptp_establish_ppp(int); void pptp_wait_input(); void pptp_disestablish_ppp(int); void pptp_link_down(void *arg, int p); static void pptp_echo_check(); static void pptp_stop_echo_check(); static void pptp_echo_timeout(void *arg); static void pptp_send_echo_request(); static void pptp_link_failure(); static void closeall(); static u_long load_kext(char *kext); static boolean_t host_gateway(int cmd, struct in_addr host, struct in_addr gateway, char *ifname, int isnet); static int pptp_set_peer_route(); static int pptp_clean_peer_route(); static void pptp_ip_up(void *arg, int p); u_int32_t pptp_get_if_baudrate(char *if_name); u_int32_t pptp_get_if_mtu(char *if_name); static void pptp_start_wait_interface (); static void pptp_stop_wait_interface (); static void pptp_wait_interface_timeout (void *arg); /* ----------------------------------------------------------------------------- PPP globals ----------------------------------------------------------------------------- */ static int ctrlsockfd = -1; /* control socket (TCP) file descriptor */ static int datasockfd = -1; /* data socket (GRE) file descriptor */ static int eventsockfd = -1; /* event socket to detect interface change */ static CFBundleRef bundle = 0; /* our bundle ref */ /* option variables */ static char *mode = MODE_CONNECT; /* connect mode by default */ static bool noload = 0; /* don't load the kernel extension */ static u_int16_t our_call_id = 0; /* pptp call id */ static u_int16_t peer_call_id = 0; /* pptp peer's calld id */ static u_int16_t our_window = PPTP_RECEIVE_WINDOW;/* our receive window size */ static u_int16_t peer_ppd = 0; /* peer packet processing delay */ static u_int16_t our_ppd = 0; /* our packet processing delay */ static u_int16_t peer_window = 0; /* peer's receive window size */ static u_int16_t maxtimeout = 64; /* Maximum adaptative timeout (in seconds) */ static struct in_addr peeraddress; /* the other side IP address */ static struct in_addr ip_zeros = { 0 }; static u_int8_t routeraddress[16]; static u_int8_t interface[17]; static pthread_t resolverthread = 0; static int resolverfds[2]; static bool linkdown = 0; /* flag set when we receive link down event */ static int peer_route_set = 0; /* has a route to the peer been set ? */ static int transport_up = 1; static int wait_interface_timer_running = 0; /* Fast echo request procedure is run when a networking change is detected Echos are sent every 5 seconds for 30 seconds, or until a reply is received If the network is detected dead, the the tunnel is disconnected. Echos are not sent during normal operation. */ static int echo_interval = 5; /* Interval between echo-requests */ static int echo_fails = 6; /* Tolerance to unanswered echo-requests */ static int echo_timer_running = 0; static int echos_pending = 0; static int echo_identifier = 0; static int echo_active = 0; static int tcp_keepalive = 0; static int wait_if_timeout = PPTP_DEFAULT_WAIT_IF_TIMEOUT; extern int kill_link; extern CFStringRef serviceidRef; /* from pppd/sys_MacOSX.c */ extern SCDynamicStoreRef cfgCache; /* from pppd/sys_MacOSX.c */ /* option descriptors */ option_t pptp_options[] = { { "nopptpload", o_bool, &noload, "Don't try to load the PPTP kernel extension", 1 }, { "pptpmode", o_string, &mode, "Configure configuration mode [connect, listen, answer]" }, { "pptpmaxtimeout", o_int, &maxtimeout, "Maximum adaptative timeout" }, { "pptp-fast-echo-interval", o_int, &echo_interval, "Fast echo interval to reassert link" }, { "pptp-fast-echo-failure", o_int, &echo_fails, "Fast echo failure to reassert link" }, { "pptp-tcp-keepalive", o_int, &tcp_keepalive, "TCP keepalive interval" }, { "pptpwaitiftimeout", o_int, &wait_if_timeout, "How long do we wait for our transport interface to come back after interface events" }, { NULL } }; /* ----------------------------------------------------------------------------- plugin entry point, called by pppd ----------------------------------------------------------------------------- */ int start(CFBundleRef ref) { bundle = ref; CFRetain(bundle); // hookup our socket handlers bzero(the_channel, sizeof(struct channel)); the_channel->options = pptp_options; the_channel->process_extra_options = pptp_process_extra_options; the_channel->wait_input = pptp_wait_input; the_channel->check_options = pptp_check_options; the_channel->connect = pptp_connect; the_channel->disconnect = pptp_disconnect; the_channel->cleanup = pptp_cleanup; the_channel->close = pptp_close; the_channel->establish_ppp = pptp_establish_ppp; the_channel->disestablish_ppp = pptp_disestablish_ppp; // use the default config functions the_channel->send_config = generic_send_config; the_channel->recv_config = generic_recv_config; add_notifier(&link_down_notifier, pptp_link_down, 0); add_notifier(&ip_up_notify, pptp_ip_up, 0); return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ void pptp_process_extra_options() { if (!strcmp(mode, MODE_ANSWER)) { // make sure we get a file descriptor > 2 so that pppd can detach and close 0,1,2 ctrlsockfd = dup(0); } } /* ----------------------------------------------------------------------------- do consistency checks on the options we were given ----------------------------------------------------------------------------- */ void pptp_check_options() { if (strcmp(mode, MODE_CONNECT) && strcmp(mode, MODE_LISTEN) && strcmp(mode, MODE_ANSWER)) { error("PPTP incorrect mode : '%s'", mode ? mode : ""); mode = MODE_CONNECT; } /* reuse pppd redial functionality to retry connection retry every 3 seconds for the duration of the extra time do not report busy state */ if (extraconnecttime) { busycode = PPTP_RETRY_CONNECT_CODE; redialtimer = 3 ; redialcount = extraconnecttime / redialtimer; hasbusystate = 0; } } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ void pptp_link_down(void *arg, int p) { linkdown = 1; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void pptp_start_wait_interface () { if (wait_interface_timer_running != 0) return; TIMEOUT (pptp_wait_interface_timeout, 0, wait_if_timeout); wait_interface_timer_running = 1; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void pptp_stop_wait_interface () { if (wait_interface_timer_running) { UNTIMEOUT (pptp_wait_interface_timeout, 0); wait_interface_timer_running = 0; } } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void pptp_wait_interface_timeout (void *arg) { if (wait_interface_timer_running != 0) { wait_interface_timer_running = 0; // our transport interface didn't come back, take down the connection pptp_link_failure(); } } /* ----------------------------------------------------------------------------- called back everytime we go out of select, and data needs to be read the hook is called and has a chance to get data out of its file descriptor in the case of PPTP, we get control data on the socket or get awaken when connection is closed ----------------------------------------------------------------------------- */ void pptp_wait_input() { int err; if (eventsockfd != -1 && is_ready_fd(eventsockfd)) { char buf[256], ev_if[32]; struct kern_event_msg *ev_msg; struct kev_in_data *inetdata; if (recv(eventsockfd, &buf, sizeof(buf), 0) != -1) { ev_msg = (struct kern_event_msg *) &buf; inetdata = (struct kev_in_data *) &ev_msg->event_data[0]; switch (ev_msg->event_code) { case KEV_INET_NEW_ADDR: case KEV_INET_CHANGED_ADDR: case KEV_INET_ADDR_DELETED: sprintf(ev_if, "%s%ld", inetdata->link_data.if_name, inetdata->link_data.if_unit); // check if changes occured on the interface we are using if (!strncmp(ev_if, interface, sizeof(interface))) { if (inetdata->link_data.if_family == APPLE_IF_FAM_PPP || echo_interval == 0 || echo_fails == 0) { // disconnect immediatly pptp_link_failure(); } else { // don't disconnect if an address has been removed if (ev_msg->event_code == KEV_INET_ADDR_DELETED) { /* stop any running echo */ pptp_stop_echo_check(); transport_up = 0; break; } /* Our transport interface comes back with the same address. Stop waiting for interface. A smarter algorithm should be implemented here. */ transport_up = 1; pptp_stop_wait_interface(); // give time to check for connectivity /* Clear the parameters for generating echo frames */ echos_pending = 0; echo_active = 1; /* If a timeout interval is specified then start the timer */ if (echo_interval != 0) pptp_echo_check(); } } else { if (ev_msg->event_code == KEV_INET_NEW_ADDR || ev_msg->event_code == KEV_INET_CHANGED_ADDR) { if (transport_up == 0) { /* Another interface got an address while our underlying transport interface was still down. Take is as a clue to disconnect our link, but give some time for our interface to come back. A smarter algorithm should be implemented here. */ pptp_start_wait_interface(); } } } break; } } } if (ctrlsockfd != -1 && is_ready_fd(ctrlsockfd)) { err = pptp_data_in(ctrlsockfd); if (err < 0) { // looks like we have been disconnected... // the status is updated only if link is not already down if (linkdown == 0) { notice("PPTP hangup"); status = EXIT_HANGUP; } remove_fd(ctrlsockfd); remove_fd(eventsockfd); hungup = 1; lcp_lowerdown(0); /* PPTP link is no longer available */ link_terminated(0); } } } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ void *pptp_resolver_thread(void *arg) { struct hostent *host; char result = -1; int count, fd; u_int8_t rd8; if (pthread_detach(pthread_self()) == 0) { // try to resolve the name if (host = gethostbyname(remoteaddress)) { for (count = 0; host->h_addr_list[count]; count++); rd8 = 0; fd = open("/dev/random", O_RDONLY); if (fd) { read(fd, &rd8, sizeof(rd8)); close(fd); } peeraddress = *(struct in_addr*)host->h_addr_list[rd8 % count]; result = 0; } } write(resolverfds[1], &result, 1); return 0; } /* ----------------------------------------------------------------------------- get the socket ready to start doing PPP. That is, open the socket and start the PPTP dialog ----------------------------------------------------------------------------- */ int pptp_connect(int *errorcode) { char dev[32], name[MAXPATHLEN], c; int err = 0, len, fd, val; CFURLRef url; CFDictionaryRef dict; CFStringRef string, key; struct sockaddr_in addr; struct kev_request kev_req; u_int32_t baudrate; *errorcode = 0; if (cfgCache == NULL || serviceidRef == NULL) { goto fail; } sprintf(dev, "socket[%d:%d]", PF_PPP, PPPPROTO_PPTP); strlcpy(ppp_devnam, dev, sizeof(ppp_devnam)); hungup = 0; kill_link = 0; linkdown = 0; our_call_id = getpid(); routeraddress[0] = 0; interface[0] = 0; key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4); if (key) { dict = SCDynamicStoreCopyValue(cfgCache, key); CFRelease(key); if (dict) { if (string = CFDictionaryGetValue(dict, kSCPropNetIPv4Router)) CFStringGetCString(string, routeraddress, sizeof(routeraddress), kCFStringEncodingUTF8); if (string = CFDictionaryGetValue(dict, kSCDynamicStorePropNetPrimaryInterface)) CFStringGetCString(string, interface, sizeof(interface), kCFStringEncodingUTF8); CFRelease(dict); } } /* now that we know our interface, adjust the MTU if necessary */ if (interface[0]) { int min_mtu = pptp_get_if_mtu(interface) - PPTP_MIN_HDR_SIZE; if (lcp_allowoptions[0].mru > min_mtu) /* defines out mtu */ lcp_allowoptions[0].mru = min_mtu; /* Don't adjust MRU, radar 3974763 */ #if 0 if (lcp_wantoptions[0].mru > min_mtu) /* defines out mru */ lcp_wantoptions[0].mru = min_mtu; if (lcp_wantoptions[0].neg_mru > min_mtu) /* defines our mru */ lcp_wantoptions[0].neg_mru = min_mtu; #endif } /* let's say our underlying transport is up */ transport_up = 1; wait_interface_timer_running = 0; eventsockfd = socket(PF_SYSTEM, SOCK_RAW, SYSPROTO_EVENT); if (eventsockfd != -1) { // PPTP can survive without event socket anyway kev_req.vendor_code = KEV_VENDOR_APPLE; kev_req.kev_class = KEV_NETWORK_CLASS; kev_req.kev_subclass = KEV_INET_SUBCLASS; ioctl(eventsockfd, SIOCSKEVFILT, &kev_req); } /* -------------------------------------------------------------*/ /* connect mode : we need a valid remote address or name */ if (!strcmp(mode, MODE_CONNECT)) { if (remoteaddress == 0) { error("PPTP: No remote address supplied...\n"); devstatus = EXIT_PPTP_NOSERVER; goto fail; } if (inet_aton(remoteaddress, &peeraddress) == 0) { if (pipe(resolverfds) < 0) { error("PPTP: failed to create pipe for gethostbyname...\n"); goto fail; } if (pthread_create(&resolverthread, NULL, pptp_resolver_thread, NULL)) { error("PPTP: failed to create thread for gethostbyname...\n"); close(resolverfds[0]); close(resolverfds[1]); goto fail; } while (read(resolverfds[0], &c, 1) != 1) { if (kill_link) { pthread_cancel(resolverthread); break; } } close(resolverfds[0]); close(resolverfds[1]); if (kill_link) goto fail1; if (c) { error("PPTP: Host '%s' not found...\n", remoteaddress); *errorcode = PPTP_RETRY_CONNECT_CODE; /* wait and retry if necessary */ devstatus = EXIT_PPTP_NOSERVER; goto fail; } } notice("PPTP connecting to server '%s' (%s)...\n", remoteaddress, inet_ntoa(peeraddress)); if ((ctrlsockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { error("PPTP can't create control socket...\n"); goto fail; } /* connect to the pptp server */ bzero(&addr, sizeof(addr)); addr.sin_len = sizeof(addr); addr.sin_family = AF_INET; addr.sin_port = PPTP_TCP_PORT; addr.sin_addr = peeraddress; while (connect(ctrlsockfd, (struct sockaddr *)&addr, sizeof(addr))) { if (errno != EINTR) { error("PPTP connect errno = %d %m\n", errno); *errorcode = PPTP_RETRY_CONNECT_CODE; /* wait and retry if necessary */ devstatus = EXIT_PPTP_NOANSWER; goto fail; } if (kill_link) goto fail1; } err = pptp_outgoing_call(ctrlsockfd, our_call_id, our_window, our_ppd, &peer_call_id, &peer_window, &peer_ppd); /* setup the specific route */ pptp_set_peer_route(); } /* -------------------------------------------------------------*/ /* answer mode : we need a valid remote address or name */ else if (!strcmp(mode, MODE_ANSWER)) { len = sizeof(addr); if (getpeername(ctrlsockfd, (struct sockaddr *)&addr, &len) < 0) { error("PPTP: cannot get client address... %m\n"); //devstatus = EXIT_PPTP_NOSERVER; goto fail; } peeraddress = addr.sin_addr; remoteaddress = inet_ntoa(peeraddress); notice("PPTP incoming call in progress from '%s'...", remoteaddress); err = pptp_incoming_call(ctrlsockfd, our_call_id, our_window, our_ppd, &peer_call_id, &peer_window, &peer_ppd); } /* -------------------------------------------------------------*/ else if (!strcmp(mode, MODE_LISTEN)) { notice("PPTP listening...\n"); if ((fd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { error("PPTP can't create listening socket...\n"); goto fail; } val = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); bzero(&addr, sizeof(addr)); addr.sin_len = sizeof(addr); addr.sin_family = AF_INET; addr.sin_port = PPTP_TCP_PORT; addr.sin_addr.s_addr = INADDR_ANY; if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { error("PPTP bind failed, %m"); goto fail; } if (listen(fd, 10) < 0) { error("PPTP listen failed, %m"); return errno; } len = sizeof(addr); ctrlsockfd = accept(fd, (struct sockaddr *)&addr, &len); close(fd); // close the socket used for listening if (ctrlsockfd < 0) { error("PPTP accept failed, %m"); goto fail; } peeraddress = addr.sin_addr; remoteaddress = inet_ntoa(peeraddress); notice("PPTP incoming call in progress from '%s'...", remoteaddress); err = pptp_incoming_call(ctrlsockfd, our_call_id, our_window, our_ppd, &peer_call_id, &peer_window, &peer_ppd); } if (err) { if (err != -2) { if (err != -1) devstatus = err; goto fail; } goto fail1; } notice("PPTP connection established."); /* enable keepalive on control connection */ if (tcp_keepalive) { val = 1; setsockopt(ctrlsockfd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); setsockopt(ctrlsockfd, IPPROTO_TCP, TCP_KEEPALIVE, &tcp_keepalive, sizeof(tcp_keepalive)); } /* get reachability flags of peer */ bzero(&addr, sizeof(addr)); addr.sin_len = sizeof(addr); addr.sin_family = AF_INET; addr.sin_port = PPTP_TCP_PORT; addr.sin_addr = peeraddress; /* open the data socket */ datasockfd = socket(PF_PPP, SOCK_DGRAM, PPPPROTO_PPTP); if (datasockfd < 0) { if (!noload) { if (url = CFBundleCopyBundleURL(bundle)) { name[0] = 0; CFURLGetFileSystemRepresentation(url, 0, name, MAXPATHLEN - 1); CFRelease(url); strcat(name, "/"); if (url = CFBundleCopyBuiltInPlugInsURL(bundle)) { CFURLGetFileSystemRepresentation(url, 0, name + strlen(name), MAXPATHLEN - strlen(name) - strlen(PPTP_NKE) - 1); CFRelease(url); strcat(name, "/"); strcat(name, PPTP_NKE); if (!load_kext(name)) datasockfd = socket(PF_PPP, SOCK_DGRAM, PPPPROTO_PPTP); } } } if (datasockfd < 0) { error("Failed to open PPTP socket: %m"); goto fail; } } if (kdebugflag & 1) { u_int32_t flags; flags = debug ? PPTP_FLAG_DEBUG : 0; if (setsockopt(datasockfd, PPPPROTO_PPTP, PPTP_OPT_FLAGS, &flags, 4)) { error("PPTP can't set PPTP flags...\n"); goto fail; } } len = sizeof(addr); if (getsockname(ctrlsockfd, (struct sockaddr *)&addr, &len) < 0) { error("PPTP: cannot get our address... %m\n"); goto fail; } if (setsockopt(datasockfd, PPPPROTO_PPTP, PPTP_OPT_OURADDRESS, &addr.sin_addr.s_addr, 4)) { error("PPTP can't set our PPTP address...\n"); goto fail; } if (setsockopt(datasockfd, PPPPROTO_PPTP, PPTP_OPT_PEERADDRESS, &peeraddress.s_addr, 4)) { error("PPTP can't set PPTP server address...\n"); goto fail; } baudrate = pptp_get_if_baudrate(interface); if (setsockopt(datasockfd, PPPPROTO_PPTP, PPTP_OPT_BAUDRATE, &baudrate, 4)) { error("PPTP can't set our baudrate...\n"); goto fail; } if (setsockopt(datasockfd, PPPPROTO_PPTP, PPTP_OPT_CALL_ID, &our_call_id, 2)) { error("PPTP can't set our call id...\n"); goto fail; } if (setsockopt(datasockfd, PPPPROTO_PPTP, PPTP_OPT_PEER_CALL_ID, &peer_call_id, 2)) { error("PPTP can't set peer call id...\n"); goto fail; } if (setsockopt(datasockfd, PPPPROTO_PPTP, PPTP_OPT_WINDOW, &our_window, 2)) { error("PPTP can't set our receive window size...\n"); goto fail; } if (setsockopt(datasockfd, PPPPROTO_PPTP, PPTP_OPT_PEER_WINDOW, &peer_window, 2)) { error("PPTP can't set peer receive window size...\n"); goto fail; } if (setsockopt(datasockfd, PPPPROTO_PPTP, PPTP_OPT_PEER_PPD, &peer_ppd, 2)) { error("PPTP can't set peer packet processing delay ...\n"); goto fail; } if (setsockopt(datasockfd, PPPPROTO_PPTP, PPTP_OPT_MAXTIMEOUT, &maxtimeout, 2)) { error("PPTP can't set adaptative maximum timeout ...\n"); goto fail; } return datasockfd; fail: status = EXIT_CONNECT_FAILED; fail1: if (eventsockfd != -1) { close(eventsockfd); eventsockfd = -1; } if (ctrlsockfd != -1) { close(ctrlsockfd); ctrlsockfd = -1; } return -1; } /* ----------------------------------------------------------------------------- run the disconnector ----------------------------------------------------------------------------- */ void pptp_disconnect() { notice("PPTP disconnecting...\n"); if (eventsockfd != -1) { close(eventsockfd); eventsockfd = -1; } if (ctrlsockfd >= 0) { close(ctrlsockfd); ctrlsockfd = -1; } notice("PPTP disconnected\n"); } /* ----------------------------------------------------------------------------- close the socket descriptors ----------------------------------------------------------------------------- */ void pptp_close() { pptp_stop_echo_check(); if (eventsockfd != -1) { close(eventsockfd); eventsockfd = -1; } if (datasockfd >= 0) { close(datasockfd); datasockfd = -1; } if (ctrlsockfd >= 0) { close(ctrlsockfd); ctrlsockfd = -1; } } /* ----------------------------------------------------------------------------- clean up before quitting ----------------------------------------------------------------------------- */ void pptp_cleanup() { pptp_close(); pptp_clean_peer_route(); } /* ----------------------------------------------------------------------------- establish the socket as a ppp link ----------------------------------------------------------------------------- */ int pptp_establish_ppp(int fd) { int x, new_fd; if (ioctl(fd, PPPIOCATTACH, &x) < 0) { error("Couldn't attach socket to the link layer: %m"); return -1; } new_fd = generic_establish_ppp(fd); if (new_fd == -1) return -1; // add just the control socket to the select // the data socket is just for moving data in the kernel add_fd(ctrlsockfd); add_fd(eventsockfd); return new_fd; } /* ----------------------------------------------------------------------------- disestablish the socket as a ppp link ----------------------------------------------------------------------------- */ void pptp_disestablish_ppp(int fd) { int x; remove_fd(ctrlsockfd); remove_fd(eventsockfd); if (ioctl(fd, PPPIOCDETACH, &x) < 0) error("Couldn't detach socket from link layer: %m"); generic_disestablish_ppp(fd); } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ void closeall() { int i; for (i = getdtablesize() - 1; i >= 0; i--) close(i); open("/dev/null", O_RDWR, 0); dup(0); dup(0); return; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ u_long load_kext(char *kext) { int pid; if ((pid = fork()) < 0) return 1; if (pid == 0) { closeall(); // PPP kernel extension not loaded, try load it... execl("/sbin/kextload", "kextload", kext, (char *)0); exit(1); } while (waitpid(pid, 0, 0) < 0) { if (errno == EINTR) continue; return 1; } return 0; } /* ----------------------------------------------------------------------------- get the MTU on the network interface ----------------------------------------------------------------------------- */ u_int32_t pptp_get_if_mtu(char *if_name) { struct ifreq ifr; int s, err; ifr.ifr_mtu = 1500; s = socket(AF_INET, SOCK_DGRAM, 0); if (s >= 0) { strlcpy(ifr.ifr_name, if_name, sizeof (ifr.ifr_name)); if (err = ioctl(s, SIOCGIFMTU, (caddr_t) &ifr) < 0) error("PPTP: can't get interface '%s' mtu, err %d (%m)", if_name, err); close(s); } return ifr.ifr_mtu; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ u_int32_t pptp_get_if_baudrate(char *if_name) { char * buf = NULL; size_t buf_len = 0; struct if_msghdr * ifm; unsigned int if_index; u_int32_t baudrate = 0; int mib[6]; /* get the interface index */ if_index = if_nametoindex(if_name); if (if_index == 0) { goto done; // if unknown interface } /* get information for the specified device */ mib[0] = CTL_NET; mib[1] = PF_ROUTE; mib[2] = 0; mib[3] = AF_LINK; mib[4] = NET_RT_IFLIST; mib[5] = if_index; /* ask for exactly one interface */ if (sysctl(mib, 6, NULL, &buf_len, NULL, 0) < 0) { goto done; } buf = malloc(buf_len); if (sysctl(mib, 6, buf, &buf_len, NULL, 0) < 0) { goto done; } /* get the baudrate for the interface */ ifm = (struct if_msghdr *)buf; switch (ifm->ifm_type) { case RTM_IFINFO : { baudrate = ifm->ifm_data.ifi_baudrate; break; } } done : if (buf != NULL) free(buf); return baudrate; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ int pptp_set_peer_route() { SCNetworkReachabilityRef ref; SCNetworkConnectionFlags flags; bool is_peer_local; struct in_addr gateway; struct sockaddr_in addr; if (peeraddress.s_addr == 0) return -1; /* check if is peer on our local subnet */ bzero(&addr, sizeof(addr)); addr.sin_len = sizeof(addr); addr.sin_family = AF_INET; addr.sin_port = PPTP_TCP_PORT; addr.sin_addr = peeraddress; ref = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&addr); is_peer_local = SCNetworkReachabilityGetFlags(ref, &flags) && (flags & kSCNetworkFlagsIsDirect); CFRelease(ref); host_gateway(RTM_DELETE, peeraddress, ip_zeros, 0, 0); if (is_peer_local || routeraddress[0] == 0 || inet_aton(routeraddress, &gateway) != 1) { if (interface[0]) { bzero(&gateway, sizeof(gateway)); /* subnet route */ host_gateway(RTM_ADD, peeraddress, gateway, interface, 1); peer_route_set = 2; } } else { /* host route */ host_gateway(RTM_ADD, peeraddress, gateway, 0, 0); peer_route_set = 1; } return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ int pptp_clean_peer_route() { if (peeraddress.s_addr == 0) return -1; if (peer_route_set) { host_gateway(RTM_DELETE, peeraddress, ip_zeros, 0, peer_route_set == 1 ? 0 : 1); peer_route_set = 0; } return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ void pptp_ip_up(void *arg, int p) { if (peer_route_set == 2) { /* in the link local case, delete the route to the server, in case it conflicts with the one from the ppp interface */ host_gateway(RTM_DELETE, peeraddress, ip_zeros, 0, 0); } } /* ----------------------------------------------------------------------------- add/remove a host route ----------------------------------------------------------------------------- */ static boolean_t host_gateway(int cmd, struct in_addr host, struct in_addr gateway, char *ifname, int isnet) { int len; int rtm_seq = 0; struct { struct rt_msghdr hdr; struct sockaddr_in dst; struct sockaddr_in gway; struct sockaddr_in mask; struct sockaddr_dl link; } rtmsg; int sockfd = -1; if ((sockfd = socket(PF_ROUTE, SOCK_RAW, AF_INET)) < 0) { syslog(LOG_INFO, "host_gateway: open routing socket failed, %s", strerror(errno)); return (FALSE); } memset(&rtmsg, 0, sizeof(rtmsg)); rtmsg.hdr.rtm_type = cmd; rtmsg.hdr.rtm_flags = RTF_UP | RTF_STATIC; if (isnet) rtmsg.hdr.rtm_flags |= RTF_CLONING; else rtmsg.hdr.rtm_flags |= RTF_HOST; if (gateway.s_addr) rtmsg.hdr.rtm_flags |= RTF_GATEWAY; rtmsg.hdr.rtm_version = RTM_VERSION; rtmsg.hdr.rtm_seq = ++rtm_seq; rtmsg.hdr.rtm_addrs = RTA_DST | RTA_NETMASK; rtmsg.dst.sin_len = sizeof(rtmsg.dst); rtmsg.dst.sin_family = AF_INET; rtmsg.dst.sin_addr = host; rtmsg.hdr.rtm_addrs |= RTA_GATEWAY; rtmsg.gway.sin_len = sizeof(rtmsg.gway); rtmsg.gway.sin_family = AF_INET; rtmsg.gway.sin_addr = gateway; rtmsg.mask.sin_len = sizeof(rtmsg.mask); rtmsg.mask.sin_family = AF_INET; rtmsg.mask.sin_addr.s_addr = 0xFFFFFFFF; len = sizeof(rtmsg); if (ifname) { rtmsg.link.sdl_len = sizeof(rtmsg.link); rtmsg.link.sdl_family = AF_LINK; rtmsg.link.sdl_nlen = strlen(ifname); rtmsg.hdr.rtm_addrs |= RTA_IFP; bcopy(ifname, rtmsg.link.sdl_data, rtmsg.link.sdl_nlen); } else { /* no link information */ len -= sizeof(rtmsg.link); } rtmsg.hdr.rtm_msglen = len; if (write(sockfd, &rtmsg, len) < 0) { syslog(LOG_DEBUG, "host_gateway: write routing socket failed, %s", strerror(errno)); close(sockfd); return (FALSE); } close(sockfd); return (TRUE); } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void pptp_echo_check () { if (echo_active == 0 || echo_timer_running != 0) return; pptp_send_echo_request (); TIMEOUT (pptp_echo_timeout, 0, echo_interval); echo_timer_running = 1; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void pptp_stop_echo_check () { echo_active = 0; if (echo_timer_running) { UNTIMEOUT (pptp_echo_timeout, 0); echo_timer_running = 0; } } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void pptp_echo_timeout (void *arg) { if (echo_timer_running != 0) { echo_timer_running = 0; pptp_echo_check (); } } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ void pptp_received_echo_reply(u_int32_t identifier, u_int8_t result, u_int8_t error) { // not really interested in the content // we just know our link is still alive dbglog("PPTP received Echo Reply, id = %d", identifier); /* Reset the number of outstanding echo frames */ echos_pending = 0; echo_active = 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void pptp_link_failure () { // major change happen on the interface we are using. // disconnect PPTP // Enhancement : should check if link is still usable notice("PPTP has detected change in the network and lost connection with the server."); devstatus = EXIT_PPTP_NETWORKCHANGED; status = EXIT_HANGUP; remove_fd(ctrlsockfd); remove_fd(eventsockfd); hungup = 1; lcp_lowerdown(0); /* PPTP link is no longer available */ link_terminated(0); } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void pptp_send_echo_request () { /* * Detect the failure of the peer at this point. */ if (echo_fails != 0) { if (echos_pending >= echo_fails) { pptp_link_failure(); echos_pending = 0; } } /* * Make and send the echo request frame. */ dbglog("PPTP sent Echo Request, id = %d", echo_identifier); if (pptp_echo(ctrlsockfd, echo_identifier++) == -1) pptp_link_failure(); echos_pending++; }