diff options
Diffstat (limited to 'src/libsystemd-network/sd-ppp.c')
-rw-r--r-- | src/libsystemd-network/sd-ppp.c | 673 |
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; +} |