summaryrefslogtreecommitdiff
path: root/src/libsystemd-network/sd-ppp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libsystemd-network/sd-ppp.c')
-rw-r--r--src/libsystemd-network/sd-ppp.c673
1 files changed, 673 insertions, 0 deletions
diff --git a/src/libsystemd-network/sd-ppp.c b/src/libsystemd-network/sd-ppp.c
new file mode 100644
index 000000000..33a649df0
--- /dev/null
+++ b/src/libsystemd-network/sd-ppp.c
@@ -0,0 +1,673 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Tom Gundersen
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+/* See RFC 1661 */
+
+#include <linux/ppp-ioctl.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/ppp_defs.h>
+
+#include "sd-ppp.h"
+#include "ppp-machine.h"
+#include "ppp-pap.h"
+
+#include "event-util.h"
+
+#include "sparse-endian.h"
+#include "util.h"
+#include "utf8.h"
+#include "socket-util.h"
+#include "async.h"
+#include "refcnt.h"
+
+#define LCP_MAX_PACKET_SIZE 1484
+
+/* see RFC 1661 Section 3 */
+typedef enum PPPState {
+ PPP_DEAD,
+ PPP_ESTABLISH,
+ PPP_AUTHENTICATE,
+ PPP_NETWORK,
+ PPP_TERMINATE,
+ _PPP_MAX,
+ _PPP_INVALID = -1,
+} PPPState;
+
+struct sd_ppp {
+ RefCount n_ref;
+
+ PPPState state;
+ int restart_count;
+ uint8_t identifier;
+
+ int unit;
+
+ sd_event *event;
+ int event_priority;
+ int fd_unit;
+ int fd_channel;
+ sd_event_source *io_unit;
+ sd_event_source *io_channel;
+
+ sd_ppp_cb_t cb;
+ void *userdata;
+
+ ppp_machine *lcp;
+ ppp_pap *pap;
+ ppp_machine *ipcp;
+};
+
+/* managment */
+
+static int ppp_attach_event(sd_ppp *ppp, sd_event *event, int priority) {
+ int r;
+
+ assert_return(ppp, -EINVAL);
+ assert_return(!ppp->event, -EBUSY);
+
+ if (event)
+ ppp->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&ppp->event);
+ if (r < 0)
+ return r;
+ }
+
+ ppp->event_priority = priority;
+
+ return 0;
+}
+
+sd_ppp *sd_ppp_ref(sd_ppp *ppp) {
+ if (ppp)
+ assert_se(REFCNT_INC(ppp->n_ref) >= 2);
+
+ return ppp;
+}
+
+sd_ppp *sd_ppp_unref(sd_ppp *ppp) {
+ if (ppp && REFCNT_DEC(ppp->n_ref) <= 0) {
+ ppp_machine_free(ppp->ipcp);
+ ppp_pap_free(ppp->pap);
+ ppp_machine_free(ppp->lcp);
+ sd_event_source_unref(ppp->io_channel);
+ sd_event_source_unref(ppp->io_unit);
+ sd_event_unref(ppp->event);
+ safe_close(ppp->fd_channel);
+ safe_close(ppp->fd_unit);
+ free(ppp);
+ }
+
+ return NULL;
+}
+
+void ppp_handle_lcp(PPPEvent event, void *userdata);
+void ppp_handle_auth(PPPAuthEvent event, void *userdata);
+void ppp_handle_ipcp(PPPEvent event, void *userdata);
+int ppp_receive_message(sd_event_source *s, int fd, uint32_t revents, void *userdata);
+
+int sd_ppp_new (sd_ppp **ret, sd_event *event, int priority) {
+/* TODO: _cleanup_ppp_unref_ sd_ppp *ppp = NULL; */
+ sd_ppp *ppp = NULL;
+ int r;
+
+ assert_return(ret, -EINVAL);
+
+ ppp = new0(sd_ppp, 1);
+ if (!ppp)
+ return -ENOMEM;
+
+ ppp->n_ref = REFCNT_INIT;
+ ppp->state = PPP_DEAD;
+ ppp->fd_channel = -1;
+
+ r = ppp_machine_new(&ppp->ipcp, PPP_IPCP, event, priority);
+ if (r < 0)
+ return r;
+
+ r = ppp_machine_set_callback(ppp->ipcp, ppp_handle_ipcp, ppp);
+ if (r < 0)
+ return r;
+
+ r = ppp_pap_new(&ppp->pap, event, priority, "tom", "password");
+ if (r < 0)
+ return r;
+
+ r = ppp_pap_set_callback(ppp->pap, ppp_handle_auth, ppp);
+ if (r < 0)
+ return r;
+
+ r = ppp_machine_new(&ppp->lcp, PPP_LCP, event, priority);
+ if (r < 0)
+ return r;
+
+ r = ppp_machine_set_callback(ppp->lcp, ppp_handle_lcp, ppp);
+ if (r < 0)
+ return r;
+
+ r = ppp_attach_event(ppp, event, priority);
+ if (r < 0)
+ return r;
+
+/*
+ ppp->fd_unit = open("/dev/ppp", O_RDWR | O_CLOEXEC | O_NONBLOCK);
+ if (ppp->fd_unit < 0)
+ return -errno;
+
+ ppp->unit = -1;
+
+ r = ioctl(ppp->fd_unit, PPPIOCNEWUNIT, &ppp->unit);
+ if (r < 0) {
+ log_warning("PPP: could not create new PPP unit: %m");
+ return -errno;
+ }
+
+ r = sd_event_add_io(ppp->event, &ppp->io_unit, ppp->fd_unit, EPOLLIN, ppp_receive_message, ppp);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(ppp->io_unit, ppp->event_priority);
+ if (r < 0)
+ return r;
+*/
+
+ *ret = ppp;
+
+ return 0;
+}
+
+int sd_ppp_set_callback(sd_ppp *ppp, sd_ppp_cb_t cb, void *userdata) {
+ assert_return(ppp, -EINVAL);
+
+ ppp->cb = cb;
+ ppp->userdata = userdata;
+
+ return 0;
+}
+
+/* action helpers */
+
+const char* ppp_state_to_string(PPPState i) _const_;
+PPPState ppp_state_from_string(const char *s) _pure_;
+
+static const char* const ppp_state_table[_PPP_MAX] = {
+ [PPP_DEAD] = "dead",
+ [PPP_ESTABLISH] = "establish",
+ [PPP_AUTHENTICATE] = "authenticate",
+ [PPP_NETWORK] = "network",
+ [PPP_TERMINATE] = "terminate",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(ppp_state, PPPState);
+
+/* Actions: RFC 1661 Section 4.4 */
+
+static int ppp_this_layer_up(sd_ppp *ppp) {
+/* int r; */
+
+ assert(ppp);
+
+ log_debug("PPP: this layer up");
+/*
+ r = ioctl(ppp->fd_channel, PPPIOCCONNECT, &ppp->unit);
+ if (r < 0) {
+ log_warning("PPP: could not connect channel to PPP unit %d: %m", ppp->unit);
+ return -errno;
+ }
+*/
+ if (ppp->cb)
+ ppp->cb(ppp, PPP_EVENT_UP, ppp->userdata);
+
+ return 0;
+}
+
+/*
+static int ppp_this_layer_down(sd_ppp *ppp) {
+ assert(ppp);
+
+ log_debug("PPP: this layer down");
+
+ if (ppp->cb)
+ ppp->cb(ppp, PPP_EVENT_DOWN, ppp->userdata);
+
+ return 0;
+}
+*/
+
+static int ppp_this_layer_finished(sd_ppp *ppp) {
+ assert(ppp);
+
+ log_debug("PPP: this layer finished");
+
+ if (ppp->cb)
+ ppp->cb(ppp, PPP_EVENT_FINISHED, ppp->userdata);
+
+ return 0;
+}
+
+static int ppp_receive_lcp_up(sd_ppp *ppp) {
+ int r;
+
+ assert(ppp);
+
+ switch (ppp->state) {
+ case PPP_ESTABLISH:
+ ppp->state = PPP_NETWORK;
+
+ r = ppp_pap_lower_up(ppp->pap, ppp->fd_channel);
+ if (r < 0)
+ return r;
+
+ ppp->state = PPP_AUTHENTICATE;
+
+ break;
+ default:
+ assert_not_reached(ppp_state_to_string(ppp->state));
+ }
+
+ return 0;
+}
+
+static int ppp_receive_lcp_down(sd_ppp *ppp) {
+ int r;
+
+ assert(ppp);
+
+ switch (ppp->state) {
+ case PPP_ESTABLISH:
+
+ break;
+ case PPP_NETWORK:
+ r = ppp_machine_lower_down(ppp->ipcp);
+ if (r < 0)
+ return r;
+
+ /* fall through */
+ case PPP_AUTHENTICATE:
+ r = ppp_pap_lower_down(ppp->pap);
+
+ ppp->state = PPP_ESTABLISH;
+
+ break;
+ case PPP_TERMINATE:
+ ppp->state = PPP_DEAD;
+
+ break;
+ default:
+ assert_not_reached(ppp_state_to_string(ppp->state));
+ }
+
+ return 0;
+}
+
+static int ppp_receive_lcp_finished(sd_ppp *ppp) {
+ int r;
+
+ assert(ppp);
+
+ switch (ppp->state) {
+ case PPP_NETWORK:
+ r = ppp_machine_lower_down(ppp->ipcp);
+ if (r < 0)
+ return r;
+
+ /* fall through */
+ case PPP_AUTHENTICATE:
+ r = ppp_pap_lower_down(ppp->pap);
+ if (r < 0)
+ return r;
+
+ /* fall through */
+ case PPP_TERMINATE:
+ case PPP_ESTABLISH:
+ r = ppp_this_layer_finished(ppp);
+ if (r < 0)
+ return r;
+
+ ppp->state = PPP_DEAD;
+
+ break;
+ default:
+ assert_not_reached(ppp_state_to_string(ppp->state));
+ }
+
+ return 0;
+}
+
+static int ppp_receive_auth_success(sd_ppp *ppp) {
+ int r;
+
+ assert(ppp);
+
+ switch (ppp->state) {
+ case PPP_AUTHENTICATE:
+ /* TODO: should be unit */
+ r = ppp_machine_lower_up(ppp->ipcp, ppp->fd_channel);
+ if (r < 0)
+ return r;
+
+ ppp->state = PPP_NETWORK;
+
+ break;
+ default:
+ assert_not_reached(ppp_state_to_string(ppp->state));
+ }
+
+ return 0;
+}
+
+static int ppp_receive_auth_failure(sd_ppp *ppp) {
+ int r;
+
+ assert(ppp);
+
+ switch (ppp->state) {
+ case PPP_NETWORK:
+ r = ppp_machine_lower_down(ppp->ipcp);
+ if (r < 0)
+ return r;
+
+ /* fall through */
+ case PPP_AUTHENTICATE:
+ r = ppp_machine_stop(ppp->lcp);
+ if (r < 0)
+ return r;
+
+ ppp->state = PPP_TERMINATE;
+
+ break;
+ default:
+ assert_not_reached(ppp_state_to_string(ppp->state));
+ }
+
+ return 0;
+}
+
+static int ppp_receive_ipcp_up(sd_ppp *ppp) {
+ int r;
+
+ assert(ppp);
+
+ switch (ppp->state) {
+ case PPP_NETWORK:
+ r = ppp_this_layer_up(ppp);
+ if (r < 0)
+ return r;
+
+ break;
+ default:
+ assert_not_reached(ppp_state_to_string(ppp->state));
+ }
+
+ return 0;
+}
+
+static int ppp_receive_ipcp_down(sd_ppp *ppp) {
+ assert(ppp);
+
+ switch (ppp->state) {
+ case PPP_NETWORK:
+ break;
+ default:
+ assert_not_reached(ppp_state_to_string(ppp->state));
+ }
+
+ return 0;
+}
+
+static int ppp_receive_ipcp_finished(sd_ppp *ppp) {
+ int r;
+
+ assert(ppp);
+
+ switch (ppp->state) {
+ case PPP_NETWORK:
+/*
+ r = ppp_pap_stop(ppp->pap);
+ if (r < 0)
+ return r;
+*/
+ r = ppp_machine_stop(ppp->lcp);
+ if (r < 0)
+ return r;
+
+ ppp->state = PPP_TERMINATE;
+
+ break;
+ default:
+ assert_not_reached(ppp_state_to_string(ppp->state));
+ }
+
+ return 0;
+}
+
+void ppp_handle_lcp(PPPEvent event, void *userdata) {
+ sd_ppp *ppp = userdata;
+ int r;
+
+ assert(ppp);
+
+ switch (event) {
+ case PPP_EVENT_UP:
+ r = ppp_receive_lcp_up(ppp);
+ if (r < 0)
+ log_warning("PPP: could not handle UP event from LCP: %s", strerror(-r));
+
+ break;
+ case PPP_EVENT_DOWN:
+ r = ppp_receive_lcp_down(ppp);
+ if (r < 0)
+ log_warning("PPP: could not handle DOWN event from LCP: %s", strerror(-r));
+
+ break;
+ case PPP_EVENT_FINISHED:
+ r = ppp_receive_lcp_finished(ppp);
+ if (r < 0)
+ log_warning("PPP: could not handle FINISHED event from LCP: %s", strerror(-r));
+
+ break;
+ default:
+ assert_not_reached("invalid LCP event");
+ }
+}
+
+void ppp_handle_auth(PPPAuthEvent event, void *userdata) {
+ sd_ppp *ppp = userdata;
+ int r;
+
+ assert(ppp);
+
+ switch (event) {
+ case PPP_AUTH_EVENT_SUCCESS:
+ r = ppp_receive_auth_success(ppp);
+ if (r < 0)
+ log_warning("PPP: could not handle AUTH SUCCESS event: %s", strerror(-r));
+
+ break;
+ case PPP_AUTH_EVENT_FAILURE:
+ r = ppp_receive_auth_failure(ppp);
+ if (r < 0)
+ log_warning("PPP: could not handle AUTH SUCCESS event: %s", strerror(-r));
+
+ break;
+ default:
+ assert_not_reached("invalid AUTH event");
+ }
+}
+
+void ppp_handle_ipcp(PPPEvent event, void *userdata) {
+ sd_ppp *ppp = userdata;
+ int r;
+
+ assert(ppp);
+
+ switch (event) {
+ case PPP_EVENT_UP:
+ r = ppp_receive_ipcp_up(ppp);
+ if (r < 0)
+ log_warning("PPP: could not handle UP event from IPCP: %s", strerror(-r));
+
+ break;
+ case PPP_EVENT_DOWN:
+ r = ppp_receive_ipcp_down(ppp);
+ if (r < 0)
+ log_warning("PPP: could not handle DOWN event from IPCP: %s", strerror(-r));
+
+ break;
+ case PPP_EVENT_FINISHED:
+ r = ppp_receive_ipcp_finished(ppp);
+ if (r < 0)
+ log_warning("PPP: could not handle FINISHED event from IPCP: %s", strerror(-r));
+
+ break;
+ default:
+ assert_not_reached("invalid IPCP event");
+ }
+}
+
+int ppp_receive_message(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ sd_ppp *ppp = userdata;
+ PPPEncapsulation packet = {};
+ int len, r;
+
+ assert(ppp);
+ assert(fd != -1);
+
+ len = read(fd, &packet, sizeof(packet));
+ if (len < 0) {
+ log_warning("PPP: could not receive PPP packet: %m");
+ return 0;
+ } else if ((size_t)len < offsetof(PPPEncapsulation, information.data)) {
+ log_warning("PPP: message too small for header (%d bytes) when reading from %s", len,
+ fd == ppp->fd_channel ? "channel" : "unit");
+ return 0;
+ } else if ((size_t)len < offsetof(PPPEncapsulation, information) + PPP_PACKET_LENGTH(&packet.information)) {
+ log_warning("PPP: received (%d bytes) less than expected (%zu bytes)",
+ len, offsetof(PPPEncapsulation, information) + PPP_PACKET_LENGTH(&packet.information));
+ return 0;
+ }
+
+ switch (be16toh(packet.protocol)) {
+ case PPP_LCP:
+ r = ppp_machine_handle_message(ppp->lcp, &packet);
+ if (r < 0)
+ return r;
+
+ break;
+ case PPP_IPCP:
+ if (ppp->state == PPP_NETWORK) {
+ r = ppp_machine_handle_message(ppp->ipcp, &packet);
+ if (r < 0)
+ return r;
+ } else
+ log_debug("PPP: dropped IPCP packet received in '%s' state", ppp_state_to_string(ppp->state));
+
+ break;
+ case PPP_PAP:
+ if (IN_SET(ppp->state, PPP_AUTHENTICATE, PPP_NETWORK)) {
+ r = ppp_pap_handle_message(ppp->pap, &packet);
+ if (r < 0)
+ return r;
+ } else
+ log_debug("PPP: dropped PAP packet received in '%s' state", ppp_state_to_string(ppp->state));
+
+ break;
+ default:
+ ppp_machine_send_code_reject(ppp->lcp, &packet.information);
+ }
+
+ return 1;
+}
+
+int sd_ppp_lower_up(sd_ppp *ppp, int channel) {
+ _cleanup_event_source_unref_ sd_event_source *io = NULL;
+ _cleanup_close_ int fd = -1;
+ int r;
+
+ assert_return(ppp, -EINVAL);
+ assert_return(channel >= 0, -EINVAL);
+ assert_return(ppp->fd_channel < 0, -EBUSY);
+
+ log_debug("PPP: lower layer up");
+
+ switch (ppp->state) {
+ case PPP_DEAD:
+ ppp->fd_channel = open("/dev/ppp", O_RDWR | O_CLOEXEC | O_NONBLOCK);
+ if (ppp->fd_channel < 0)
+ return -errno;
+
+ r = ioctl(ppp->fd_channel, PPPIOCATTCHAN, &channel);
+ if (r < 0) {
+ log_warning("PPP: could not attach ppp instance to channel %d: %m", channel);
+ return -errno;
+ }
+
+ r = sd_event_add_io(ppp->event, &ppp->io_channel, ppp->fd_channel, EPOLLIN, ppp_receive_message, ppp);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(ppp->io_channel, ppp->event_priority);
+ if (r < 0)
+ return r;
+
+ r = ppp_machine_lower_up(ppp->lcp, ppp->fd_channel);
+ if (r < 0)
+ return r;
+
+ ppp->state = PPP_ESTABLISH;
+ default:
+ ; /*TODO*/
+ }
+
+ return 0;
+}
+
+int sd_ppp_lower_down(sd_ppp *ppp) {
+ int r;
+
+ assert_return(ppp, -EINVAL);
+ assert_return(ppp->fd_channel >= 0, -EUNATCH);
+
+ log_debug("PPP: lower layer down");
+
+ ppp->fd_channel = safe_close(ppp->fd_channel);
+
+ switch (ppp->state) {
+ case PPP_NETWORK:
+ case PPP_ESTABLISH:
+ r = ppp_machine_lower_down(ppp->lcp);
+ if (r < 0)
+ return r;
+
+ ppp->io_channel = sd_event_source_unref(ppp->io_channel);
+ ppp->fd_channel = safe_close(ppp->fd_channel);
+
+ ppp->state = PPP_DEAD;
+
+ break;
+ default:
+ /*TODO*/
+ assert_not_reached(ppp_state_to_string(ppp->state));
+ }
+
+ return 0;
+}