diff options
author | Tom Gundersen <teg@jklm.no> | 2014-06-19 19:55:23 +0200 |
---|---|---|
committer | Tom Gundersen <teg@jklm.no> | 2014-10-14 15:44:07 +0200 |
commit | 9baf4431b84ec371890ff755524f5e3c7b6c6cea (patch) | |
tree | 4042cee070f262bfdfab11dae93146f5f3db9686 | |
parent | 9ff5ff320ec71fec7f2c841223380665794afd07 (diff) |
PPP: add support for PPP and PPPoEppp
Authentication not currently supported.
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile.am | 31 | ||||
-rw-r--r-- | src/libsystemd-network/ppp-internal.h | 43 | ||||
-rw-r--r-- | src/libsystemd-network/ppp-machine.c | 1732 | ||||
-rw-r--r-- | src/libsystemd-network/ppp-machine.h | 46 | ||||
-rw-r--r-- | src/libsystemd-network/ppp-pap.c | 340 | ||||
-rw-r--r-- | src/libsystemd-network/ppp-pap.h | 44 | ||||
-rw-r--r-- | src/libsystemd-network/sd-ppp.c | 673 | ||||
-rw-r--r-- | src/libsystemd-network/sd-pppoe.c | 655 | ||||
-rw-r--r-- | src/libsystemd-network/test-ppp-machine.c | 112 | ||||
l--------- | src/pppd/Makefile | 1 | ||||
-rw-r--r-- | src/pppd/pppd.c | 161 | ||||
-rw-r--r-- | src/systemd/sd-ppp.h | 47 | ||||
-rw-r--r-- | src/systemd/sd-pppoe.h | 53 |
14 files changed, 3940 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore index f119b574c..45e46b87d 100644 --- a/.gitignore +++ b/.gitignore @@ -99,6 +99,7 @@ /systemd-notify /systemd-nspawn /systemd-path +/systemd-pppd /systemd-quotacheck /systemd-random-seed /systemd-rc-local-generator @@ -218,6 +219,7 @@ /test-replace-var /test-resolve /test-ring +/test-ppp-machine /test-rtnl /test-rtnl-manual /test-sched-prio diff --git a/Makefile.am b/Makefile.am index e52db1793..e167e4803 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2919,6 +2919,8 @@ libsystemd_network_la_SOURCES = \ src/systemd/sd-icmp6-nd.h \ src/systemd/sd-dhcp6-client.h \ src/systemd/sd-dhcp6-lease.h \ + src/systemd/sd-ppp.h \ + src/systemd/sd-pppoe.h \ src/libsystemd-network/sd-dhcp-client.c \ src/libsystemd-network/sd-dhcp-server.c \ src/libsystemd-network/dhcp-network.c \ @@ -2933,6 +2935,12 @@ libsystemd_network_la_SOURCES = \ src/libsystemd-network/ipv4ll-network.c \ src/libsystemd-network/ipv4ll-packet.c \ src/libsystemd-network/ipv4ll-internal.h \ + src/libsystemd-network/ppp-machine.h \ + src/libsystemd-network/ppp-machine.c \ + src/libsystemd-network/ppp-pap.h \ + src/libsystemd-network/ppp-pap.c \ + src/libsystemd-network/sd-pppoe.c \ + src/libsystemd-network/sd-ppp.c \ src/libsystemd-network/network-internal.c \ src/libsystemd-network/network-internal.h \ src/libsystemd-network/sd-icmp6-nd.c \ @@ -2992,6 +3000,14 @@ test_ipv4ll_LDADD = \ libsystemd-internal.la \ libsystemd-shared.la +test_ppp_machine_SOURCES = \ + src/libsystemd-network/ppp-machine.h \ + src/libsystemd-network/test-ppp-machine.c + +test_ppp_machine_LDADD = \ + libsystemd-network.la \ + libsystemd-shared.la + test_icmp6_rs_SOURCES = \ src/systemd/sd-dhcp6-client.h \ src/systemd/sd-icmp6-nd.h \ @@ -3018,6 +3034,7 @@ tests += \ test-dhcp-client \ test-dhcp-server \ test-ipv4ll \ + test-ppp-machine \ test-icmp6-rs \ test-dhcp6-client @@ -4892,6 +4909,20 @@ lib_LTLIBRARIES += \ endif # ------------------------------------------------------------------------------ +systemd_pppd_SOURCES = \ + src/pppd/pppd.c + +systemd_pppd_CFLAGS = \ + $(AM_CFLAGS) + +systemd_pppd_LDADD = \ + libsystemd-shared.la \ + libsystemd-network.la + +rootlibexec_PROGRAMS += \ + systemd-pppd + +# ------------------------------------------------------------------------------ if ENABLE_RESOLVED systemd_resolved_SOURCES = \ src/resolve/resolved.c \ diff --git a/src/libsystemd-network/ppp-internal.h b/src/libsystemd-network/ppp-internal.h new file mode 100644 index 000000000..b24b20950 --- /dev/null +++ b/src/libsystemd-network/ppp-internal.h @@ -0,0 +1,43 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + 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/>. +***/ + +#include "sparse-endian.h" +#include "util.h" + +typedef struct PPPPacket { + uint8_t code; + uint8_t identifier; + be16_t length; + uint8_t data[PPP_MRU]; +} _packed_ PPPPacket; + +typedef struct PPPEncapsulation { + be16_t protocol; + PPPPacket information; +} _packed_ PPPEncapsulation; + +#define PPP_PACKET_LENGTH(packet) \ + be16toh((packet)->length) + +#define PPP_PACKET_TAIL(packet) \ + (PPPOption*)(((uint8_t *)packet) + PPP_PACKET_LENGTH(packet)) diff --git a/src/libsystemd-network/ppp-machine.c b/src/libsystemd-network/ppp-machine.c new file mode 100644 index 000000000..937dd17ef --- /dev/null +++ b/src/libsystemd-network/ppp-machine.c @@ -0,0 +1,1732 @@ +/*-*- 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 "ppp-machine.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 4.2 */ + +typedef enum PPPMachineState { + PPP_STATE_STARTING, + PPP_STATE_CLOSED, + PPP_STATE_STOPPED, + PPP_STATE_CLOSING, + PPP_STATE_STOPPING, + PPP_STATE_REQ_SENT, + PPP_STATE_ACK_RCVD, + PPP_STATE_ACK_SENT, + PPP_STATE_OPENED, + _PPP_STATE_MAX, + _PPP_STATE_INVALID = -1, +} PPPMachineState; + +struct ppp_machine { + RefCount n_ref; + + PPPMachineState state; + int restart_count; + uint8_t identifier; + + sd_event *event; + int event_priority; + int fd; + sd_event_source *timeout; + + ppp_machine_cb_t cb; + void *userdata; + + uint16_t protocol; + + union { + struct { + struct in_addr remote_address; + struct in_addr address; + struct in_addr primary_dns; + struct in_addr secondary_dns; + } ipcp; + + struct { + uint16_t mru; + uint32_t magic_number; + } lcp; + }; + + uint64_t rejected; +}; + +typedef struct PPPOption { + uint8_t type; + uint8_t length; + uint8_t data[0]; +} _packed_ PPPOption; + +typedef enum LCPCounter { + LCP_COUNTER_TERMINATE, + LCP_COUNTER_CONFIGURE, + LCP_COUNTER_FAILURE, +} LCPCounter; + +enum { + LCP_OPTION_MRU = 1, + LCP_OPTION_AUTH_PROTOCOL = 3, + LCP_OPTION_MAGIC_NUMBER = 5, +}; + +enum { + LCP_OPTION_FLAG_MRU = 1ULL << 0, + LCP_OPTION_FLAG_MAGIC_NUMBER = 1ULL << 1, +}; + + +enum { + IPCP_OPTION_ADDRESS = 3, + IPCP_OPTION_PRIMARY_DNS = 129, + IPCP_OPTION_SECONDARY_DNS = 131, +}; + +enum { + IPCP_OPTION_FLAG_ADDRESS = 1ULL << 0, + IPCP_OPTION_FLAG_PRIMARY_DNS = 1ULL << 1, + IPCP_OPTION_FLAG_SECONDARY_DNS = 1ULL << 2, +}; + +/* RFC 1661 Section 5 */ +typedef enum PPPPacketType { + PPP_CONFIGURE_REQUEST = 1, + PPP_CONFIGURE_ACK = 2, + PPP_CONFIGURE_NAK = 3, + PPP_CONFIGURE_REJECT = 4, + PPP_TERMINATE_REQUEST = 5, + PPP_TERMINATE_ACK = 6, + PPP_CODE_REJECT = 7, + LCP_PROTOCOL_REJECT = 8, + LCP_ECHO_REQUEST = 9, + LCP_ECHO_REPLY = 10, + LCP_DISCARD_REQUEST = 11, + LCP_IDENTIFICATION = 12, + LCP_TIME_REMAINING = 13, + _PPP_PACKET_TYPE_MAX, + _PPP_PACKET_TYPE_INVALID = -1, +} PPPPacketType; + +#define PPP_OPTION_NEXT(option) \ + (PPPOption*)(((uint8_t *)option) + (option)->length) + +#define PPP_OPTION_FOREACH(option, packet) \ + for (option = (PPPOption*)(packet)->data; \ + ((uint8_t *)(option) < (uint8_t *)(packet) + be16toh((packet)->length)) && \ + ((uint8_t *) option >= (uint8_t *) packet); \ + option = PPP_OPTION_NEXT(option)) + +#define log_ppp_machine(ppp, fmt, ...) log_meta(LOG_DEBUG, __FILE__, __LINE__, __func__, "%s: " fmt, ppp->protocol == PPP_LCP ? "LCP" : "IPCP", ##__VA_ARGS__) + +/* managment */ + +void ppp_machine_free(ppp_machine *ppp) { + ppp_machine_stop(ppp); + sd_event_source_unref(ppp->timeout); + sd_event_unref(ppp->event); + free(ppp); +} + +int ppp_machine_new (ppp_machine **ret, uint16_t protocol, sd_event *event, int priority) { + ppp_machine *ppp; + + assert_return(ret, -EINVAL); + assert_return(IN_SET(protocol, PPP_LCP, PPP_IPCP), -EINVAL); + assert_return(event, -EINVAL); + + ppp = new0(ppp_machine, 1); + if (!ppp) + return -ENOMEM; + + ppp->n_ref = REFCNT_INIT; + ppp->state = PPP_STATE_STARTING; + ppp->event = sd_event_ref(event); + ppp->event_priority = priority; + ppp->fd = -1; + + ppp->protocol = protocol; + + if (protocol == PPP_LCP) + random_bytes(&ppp->lcp.magic_number, sizeof(ppp->lcp.magic_number)); + + *ret = ppp; + + return 0; +} + +int ppp_machine_set_callback(ppp_machine *ppp, ppp_machine_cb_t cb, void *userdata) { + assert_return(ppp, -EINVAL); + + ppp->cb = cb; + ppp->userdata = userdata; + + return 0; +} + +/* action helpers */ + +const char* ppp_machine_state_to_string(PPPMachineState i) _const_; +PPPMachineState ppp_machine_state_from_string(const char *s) _pure_; + +static const char* const ppp_machine_state_table[_PPP_STATE_MAX] = { + [PPP_STATE_STARTING] = "starting", + [PPP_STATE_CLOSED] = "closed", + [PPP_STATE_STOPPED] = "stopped", + [PPP_STATE_CLOSING] = "closing", + [PPP_STATE_STOPPING] = "stopping", + [PPP_STATE_REQ_SENT] = "request sent", + [PPP_STATE_ACK_RCVD] = "ack received", + [PPP_STATE_ACK_SENT] = "ack sent", + [PPP_STATE_OPENED] = "opened", +}; + +DEFINE_STRING_TABLE_LOOKUP(ppp_machine_state, PPPMachineState); + +const char* ppp_packet_type_to_string(PPPPacketType i) _const_; +PPPPacketType ppp_packet_type_from_string(const char *s) _pure_; + +static const char* const ppp_packet_type_table[_PPP_PACKET_TYPE_MAX] = { + [PPP_CONFIGURE_REQUEST] = "configure request", + [PPP_CONFIGURE_ACK] = "configure ack", + [PPP_CONFIGURE_NAK] = "configure nak", + [PPP_CONFIGURE_REJECT] = "configure reject", + [PPP_TERMINATE_REQUEST] = "terminate request", + [PPP_TERMINATE_ACK] = "terminate ack", + [PPP_CODE_REJECT] = "code reject", + [LCP_PROTOCOL_REJECT] = "protocol reject", + [LCP_ECHO_REQUEST] = "echo request", + [LCP_ECHO_REPLY] = "echo reply", + [LCP_DISCARD_REQUEST] = "discard request", + [LCP_IDENTIFICATION] = "identification", + [LCP_TIME_REMAINING] = "time remaining", +}; + +DEFINE_STRING_TABLE_LOOKUP(ppp_packet_type, PPPPacketType); + +static int ppp_initialize_packet(ppp_machine *ppp, PPPEncapsulation *packet) { + assert(packet); + + packet->protocol = htobe16(ppp->protocol); + +/* + The Identifier field MUST be changed whenever the contents of the + Options field changes, and whenever a valid reply has been + received for a previous request. For retransmissions, the + Identifier MAY remain unchanged. +*/ + random_bytes(&ppp->identifier, sizeof(ppp->identifier)); + packet->information.identifier = ppp->identifier; + packet->information.length = htobe16(offsetof(PPPPacket, data)); + + return 0; +} + +static int ppp_send_packet(ppp_machine *ppp, PPPEncapsulation *packet) { + int r; + + assert(ppp); + + r = write(ppp->fd, packet, offsetof(PPPEncapsulation, information) + + be16toh(packet->information.length)); + if (r < 0) + return -errno; + + log_ppp_machine(ppp, "sent %s", ppp_packet_type_to_string(packet->information.code)); + + return 0; +} + +static int ppp_timeout(sd_event_source *s, uint64_t usec, + void *userdata); + +static int ppp_arm_timeout(ppp_machine *ppp) { + _cleanup_event_source_unref_ sd_event_source *timeout = NULL; + usec_t next_timeout; + int r; + + assert(ppp); + + r = sd_event_now(ppp->event, CLOCK_MONOTONIC, &next_timeout); + if (r < 0) + return 0; + + /* TODO: reset to 500ms or so */ + next_timeout += 500 * USEC_PER_MSEC; + + r = sd_event_add_time(ppp->event, + &timeout, + CLOCK_MONOTONIC, + next_timeout, 10 * USEC_PER_MSEC, + ppp_timeout, ppp); + if (r < 0) + return r; + + r = sd_event_source_set_priority(timeout, ppp->event_priority); + if (r < 0) + return r; + + sd_event_source_unref(ppp->timeout); + ppp->timeout = timeout; + timeout = NULL; + + return 0; +} + +/* Actions: RFC 1661 Section 4.4 */ + +static int ppp_this_layer_up(ppp_machine *ppp) { + assert(ppp); + + log_ppp_machine(ppp, "this layer up"); + + if (ppp->cb) + ppp->cb(PPP_EVENT_UP, ppp->userdata); + + ppp->timeout = sd_event_source_unref(ppp->timeout); + + return 0; +} + +static int ppp_this_layer_down(ppp_machine *ppp) { + assert(ppp); + + log_ppp_machine(ppp, "this layer down"); + + if (ppp->cb) + ppp->cb(PPP_EVENT_DOWN, ppp->userdata); + + return 0; +} + +static int ppp_this_layer_started(ppp_machine *ppp) { + assert(ppp); + + log_ppp_machine(ppp, "this layer started"); + + /* we do not call back to the user, as the user initiated this event */ + + return 0; +} + +static int ppp_this_layer_finished(ppp_machine *ppp) { + assert(ppp); + + log_ppp_machine(ppp, "this layer finished"); + + if (ppp->cb) + ppp->cb(PPP_EVENT_FINISHED, ppp->userdata); + + return 0; +} + +static int ppp_initialize_restart_count(ppp_machine *ppp, LCPCounter counter) { + assert(ppp); + +/* + This action sets the Restart counter to the appropriate value + (Max-Terminate or Max-Configure). The counter is decremented for + each transmission, including the first. + + Implementation Note: + + In addition to setting the Restart counter, the implementation + MUST set the timeout period to the initial value when Restart + timer backoff is used. +*/ + + if (counter == LCP_COUNTER_TERMINATE) { +/* + Max-Terminate + + There is one required restart counter for Terminate-Requests. + Max-Terminate indicates the number of Terminate-Request packets + sent without receiving a Terminate-Ack before assuming that the + peer is unable to respond. Max-Terminate MUST be configurable, + but SHOULD default to two (2) transmissions. +*/ + ppp->restart_count = 2; + + } else if (counter == LCP_COUNTER_CONFIGURE) { +/* + Max-Configure + + A similar counter is recommended for Configure-Requests. Max- + Configure indicates the number of Configure-Request packets sent + without receiving a valid Configure-Ack, Configure-Nak or + Configure-Reject before assuming that the peer is unable to + respond. Max-Configure MUST be configurable, but SHOULD default + to ten (10) transmissions. +*/ + ppp->restart_count = 10; + } else if (counter == LCP_COUNTER_FAILURE) { +/* + Max-Failure + + A related counter is recommended for Configure-Nak. Max-Failure + indicates the number of Configure-Nak packets sent without sending + a Configure-Ack before assuming that configuration is not + converging. Any further Configure-Nak packets for peer requested + options are converted to Configure-Reject packets, and locally + desired options are no longer appended. Max-Failure MUST be + configurable, but SHOULD default to five (5) transmissions. +*/ + ppp->restart_count = 5; + } else + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + + return 0; +} + +static int ppp_zero_restart_count(ppp_machine *ppp) { + assert(ppp); + +/* + Zero-Restart-Count (zrc) + + This action sets the Restart counter to zero. + + Implementation Note: + + This action enables the FSA to pause before proceeding to the + desired final state, allowing traffic to be processed by the + peer. In addition to zeroing the Restart counter, the + implementation MUST set the timeout period to an appropriate + value. +*/ + + ppp->restart_count = 0; + + return 0; +} + +static int option_append(PPPPacket *packet, size_t packet_size, uint8_t option_type, void *option, size_t option_size) { + PPPOption *next_option; + + /* TODO: verify overflow */ + + assert(option_size <= 0xff); + + if (packet_size - PPP_PACKET_LENGTH(packet) < sizeof(PPPOption) + option_size) + return -ENOBUFS; + + next_option = PPP_PACKET_TAIL(packet); + + next_option->type = option_type; + next_option->length = sizeof(PPPOption) + option_size; + memcpy(&next_option->data, option, option_size); + packet->length = htobe16(PPP_PACKET_LENGTH(packet) + next_option->length); + + return 0; +} + +static int ppp_send_configure_request(ppp_machine *ppp) { + PPPEncapsulation packet = {}; + int r; + + assert(ppp); + + r = ppp_initialize_packet(ppp, &packet); + if (r < 0) + return r; + +/* + Send-Configure-Request (scr) + + A Configure-Request packet is transmitted. This indicates the + desire to open a connection with a specified set of Configuration + Options. The Restart timer is started when the Configure-Request + packet is transmitted, to guard against packet loss. The Restart + counter is decremented each time a Configure-Request is sent. +*/ + + packet.information.code = PPP_CONFIGURE_REQUEST; + + switch (ppp->protocol) { + case PPP_LCP: + if (!(ppp->rejected & LCP_OPTION_FLAG_MAGIC_NUMBER)) { + r = option_append(&packet.information, sizeof(packet.information), LCP_OPTION_MAGIC_NUMBER, + &ppp->lcp.magic_number, sizeof(ppp->lcp.magic_number)); + if (r < 0) + return r; + } + + break; + case PPP_IPCP: + if (!(ppp->rejected & IPCP_OPTION_FLAG_ADDRESS)) { + r = option_append(&packet.information, sizeof(packet.information), IPCP_OPTION_ADDRESS, + &ppp->ipcp.address, sizeof(ppp->ipcp.address)); + if (r < 0) + return r; + } + + if (!(ppp->rejected & IPCP_OPTION_FLAG_PRIMARY_DNS)) { + r = option_append(&packet.information, sizeof(packet.information), IPCP_OPTION_PRIMARY_DNS, + &ppp->ipcp.primary_dns, sizeof(ppp->ipcp.primary_dns)); + if (r < 0) + return r; + } + + if (!(ppp->rejected & IPCP_OPTION_FLAG_SECONDARY_DNS)) { + r = option_append(&packet.information, sizeof(packet.information), IPCP_OPTION_SECONDARY_DNS, + &ppp->ipcp.secondary_dns, sizeof(ppp->ipcp.secondary_dns)); + if (r < 0) + return r; + } + + break; + default: + assert_not_reached("invalid PPP protocol"); + } + + r = ppp_send_packet(ppp, &packet); + if (r < 0) + return r; + + r = ppp_arm_timeout(ppp); + if (r < 0) + return r; + + ppp->restart_count --; + + return 0; +} + +static int ppp_send_ack(ppp_machine *ppp, PPPEncapsulation *request) { + int r; + + assert(ppp); + assert(request); + + switch (request->information.code) { + case PPP_CONFIGURE_REQUEST: +/* + Send-Configure-Ack (sca) + + A Configure-Ack packet is transmitted. This acknowledges the + reception of a Configure-Request packet with an acceptable set of + Configuration Options. +*/ + + request->information.code = PPP_CONFIGURE_ACK; + + break; + case PPP_TERMINATE_REQUEST: +/* + Send-Terminate-Ack (sta) + + A Terminate-Ack packet is transmitted. This acknowledges the + reception of a Terminate-Request packet or otherwise serves to + synchronize the automatons. +*/ + request->information.code = PPP_TERMINATE_ACK; + + break; + case LCP_ECHO_REQUEST: +/* + Send-Echo-Reply (ser) + + An Echo-Reply packet is transmitted. This acknowledges the + reception of an Echo-Request packet. +*/ + request->information.code = LCP_ECHO_REPLY; + memcpy(&request->information.data, &ppp->lcp.magic_number, sizeof(ppp->lcp.magic_number)); + + break; + default: + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + } + + r = ppp_send_packet(ppp, request); + if (r < 0) + return r; + + return 0; +} + +static int ppp_send_unelicited_terminate_ack(ppp_machine *ppp) { + PPPEncapsulation packet = {}; + int r; + + assert(ppp); + + r = ppp_initialize_packet(ppp, &packet); + if (r < 0) + return r; +/* + + Reception of an unelicited Terminate-Ack indicates that the peer + is in the Closed or Stopped states, or is otherwise in need of + re-negotiation. +*/ + + packet.information.code = PPP_TERMINATE_ACK; + + r = ppp_send_packet(ppp, &packet); + if (r < 0) + return r; + + return 0; +} + +static int ppp_send_terminate_request(ppp_machine *ppp, const char *reason) { + PPPEncapsulation packet = {}; + size_t len; + int r; + + assert(ppp); + + r = ppp_initialize_packet(ppp, &packet); + if (r < 0) + return r; + +/* + Send-Terminate-Request (str) + + A Terminate-Request packet is transmitted. This indicates the + desire to close a connection. The Restart timer is started when + the Terminate-Request packet is transmitted, to guard against + packet loss. The Restart counter is decremented each time a + Terminate-Request is sent. +*/ + packet.information.code = PPP_TERMINATE_REQUEST; + + len = strlen(reason); + + if (len < sizeof(packet) - offsetof(PPPEncapsulation, information.data) - 1) { + strcpy((char *)packet.information.data, reason); + packet.information.length = htobe16(PPP_PACKET_LENGTH(&packet.information) + len); + } + + r = ppp_send_packet(ppp, &packet); + if (r < 0) + return r; + + r = ppp_arm_timeout(ppp); + if (r < 0) + return r; + + ppp->restart_count --; + + return 0; +} + +int ppp_machine_send_code_reject(ppp_machine *ppp, PPPPacket *request) { + PPPEncapsulation packet = {}; + int r; + + assert(ppp); + assert(request); + + r = ppp_initialize_packet(ppp, &packet); + if (r < 0) + return r; + + packet.information.code = PPP_CODE_REJECT; + + /* TODO: truncate */ + memcpy(packet.information.data, request, be16toh(request->length)); + packet.information.length = htobe16(be16toh(packet.information.length) + + be16toh(request->length)); + + r = ppp_send_packet(ppp, &packet); + if (r < 0) + return r; + + return 0; +} + +/* Events: RFC 1661 Section 4.3 */ + +static int ppp_receive_configure_request(ppp_machine *ppp, PPPEncapsulation *packet) { + _cleanup_free_ PPPEncapsulation *nak = NULL, *rej = NULL; + PPPOption *option; + bool do_nak = false, do_rej = false; + size_t packet_length, encap_length; + be16_t auth_protocol = htobe16(PPP_PAP); + int r; + + assert(ppp); + assert(packet); + + packet_length = PPP_PACKET_LENGTH(&packet->information); + encap_length = offsetof(PPPEncapsulation, information) + packet_length; + + nak = memdup(packet, encap_length); + if (!nak) + return -ENOMEM; + + nak->information.code = PPP_CONFIGURE_NAK; + nak->information.length = htobe16(offsetof(PPPPacket, data)); + + rej = memdup(packet, encap_length); + if (!rej) + return -ENOMEM; + + rej->information.code = PPP_CONFIGURE_REJECT; + rej->information.length = htobe16(offsetof(PPPPacket, data)); + + + PPP_OPTION_FOREACH(option, &packet->information) { + switch (ppp->protocol) { + case PPP_LCP: + switch (option->type) { + case LCP_OPTION_AUTH_PROTOCOL: + if (*(be16_t*)option->data != auth_protocol) { + + log_ppp_machine(ppp, "unsupported authentication protocol received, naking"); + + r = option_append(&nak->information, packet_length, option->type, + &auth_protocol, sizeof(auth_protocol)); + if (r < 0) + return r; + } + + break; + case LCP_OPTION_MAGIC_NUMBER: + /* + * When a Configure-Request is received with a + * Magic-Number Configuration Option, the + * received Magic-Number is compared with the + * Magic-Number of the last Configure-Request + * sent to the peer. If the two Magic-Numbers + * are different, then the link is not + * looped-back, and the Magic-Number SHOULD be + * acknowledged. If the two Magic-Numbers are + * equal, then it is possible, but not certain, + * that the link is looped-back and that this + * Configure-Request is actually the one last + * sent. To determine this, a Configure-Nak MUST + * be sent specifying a different Magic-Number + * value. A new Configure-Request SHOULD NOT be + * sent to the peer until normal processing + * would cause it to be sent (that is, until a + * Configure-Nak is received or the Restart + * timer runs out). + */ + if (ppp->lcp.magic_number == *(uint32_t*)option->data) { + random_bytes(&ppp->lcp.magic_number, sizeof(ppp->lcp.magic_number)); + + log_ppp_machine(ppp, "invalid magic number received, loop possible"); + + r = option_append(&nak->information, packet_length, option->type, + &ppp->lcp.magic_number, sizeof(ppp->lcp.magic_number)); + if (r < 0) + return r; + + do_nak = true; + } + break; + default: + r = option_append(&rej->information, packet_length, option->type, + option->data, option->length - offsetof(PPPOption, data)); + if (r < 0) + return r; + + log_ppp_machine(ppp, "rejecting unknown option: %"PRIu8, option->type); + + do_rej = true; + } + + break; + case PPP_IPCP: + switch (option->type) { + case IPCP_OPTION_ADDRESS: + if (option->length == sizeof(PPPOption) + sizeof(ppp->ipcp.remote_address)) + memcpy(&ppp->ipcp.remote_address, option->data, sizeof(ppp->ipcp.remote_address)); + else { + r = option_append(&nak->information, packet_length, option->type, + &ppp->ipcp.remote_address, sizeof(ppp->ipcp.remote_address)); + if (r < 0) + return 0; + + log_ppp_machine(ppp, "naking invalid IP address"); + + do_nak = true; + } + break; + default: + r = option_append(&rej->information, packet_length, option->type, + option->data, option->length - offsetof(PPPOption, data)); + if (r < 0) + return r; + + log_ppp_machine(ppp, "rejecting unknown option: %"PRIu8, option->type); + + do_rej = true; + } + + break; + default: + assert_not_reached("invalid PPP protocol"); + } + } + + /* + * When a Configure-Request is received with a Magic-Number + * Configuration Option, the received Magic-Number is compared with + * the Magic-Number of the last Configure-Request sent to the peer. + * If the two Magic-Numbers are different, then the link is not + * looped-back, and the Magic-Number SHOULD be acknowledged. If the + * two Magic-Numbers are equal, then it is possible, but not certain, + * that the link is looped-back and that this Configure-Request is + * actually the one last sent. To determine this, a Configure-Nak + * MUST be sent specifying a different Magic-Number value. A new + * Configure-Request SHOULD NOT be sent to the peer until normal + * processing would cause it to be sent (that is, until a Configure- + * Nak is received or the Restart timer runs out). + */ + + switch (ppp->state) { + case PPP_STATE_CLOSING: + case PPP_STATE_STOPPING: + + break; + case PPP_STATE_CLOSED: + r = ppp_send_unelicited_terminate_ack(ppp); + if (r < 0) + return r; + + break; + case PPP_STATE_ACK_RCVD: + r = ppp_send_ack(ppp, packet); + if (r < 0) + return r; + + r = ppp_this_layer_up(ppp); + if (r < 0) + return r; + + ppp->state = PPP_STATE_OPENED; + + break; + case PPP_STATE_STOPPED: + r = ppp_initialize_restart_count(ppp, LCP_COUNTER_CONFIGURE); + if (r < 0) + return r; + + r = ppp_send_configure_request(ppp); + if (r < 0) + return r; + + if (do_rej) { + r = ppp_send_packet(ppp, rej); + if (r < 0) + return r; + + ppp->state = PPP_STATE_REQ_SENT; + } else if (do_nak) { + r = ppp_send_packet(ppp, nak); + if (r < 0) + return r; + + ppp->state = PPP_STATE_ACK_SENT; + } else { + r = ppp_send_ack(ppp, packet); + if (r < 0) + return r; + + ppp->state = PPP_STATE_ACK_SENT; + } + + break; + case PPP_STATE_OPENED: + r = ppp_this_layer_down(ppp); + if (r < 0) + return r; + + r = ppp_send_configure_request(ppp); + if (r < 0) + return r; + + /* fallthrough */ + case PPP_STATE_REQ_SENT: + case PPP_STATE_ACK_SENT: + if (do_rej) { + r = ppp_send_packet(ppp, rej); + if (r < 0) + return r; + + ppp->state = PPP_STATE_REQ_SENT; + } else if (do_nak) { + r = ppp_send_packet(ppp, nak); + if (r < 0) + return r; + + ppp->state = PPP_STATE_ACK_SENT; + } else { + r = ppp_send_ack(ppp, packet); + if (r < 0) + return r; + + ppp->state = PPP_STATE_ACK_SENT; + } + + break; + default: + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + } + + return 0; +} + +static int ppp_receive_configure_ack(ppp_machine *ppp, PPPEncapsulation *packet) { + int r; + + assert(ppp); + assert(packet); + + if (packet->information.identifier != ppp->identifier) + return 0; + + switch (ppp->state) { + case PPP_STATE_CLOSING: + case PPP_STATE_STOPPING: + + break; + case PPP_STATE_CLOSED: + case PPP_STATE_STOPPED: + r = ppp_send_unelicited_terminate_ack(ppp); + if (r < 0) + return r; + + break; + case PPP_STATE_OPENED: + r = ppp_this_layer_down(ppp); + if (r < 0) + return r; + + /* fallthrough */ + case PPP_STATE_ACK_RCVD: + r = ppp_send_configure_request(ppp); + if (r < 0) + return r; + + ppp->state = PPP_STATE_REQ_SENT; + + break; + case PPP_STATE_REQ_SENT: + r = ppp_initialize_restart_count(ppp, LCP_COUNTER_CONFIGURE); + if (r < 0) + return r; + + ppp->state = PPP_STATE_ACK_RCVD; + + break; + case PPP_STATE_ACK_SENT: + r = ppp_initialize_restart_count(ppp, LCP_COUNTER_CONFIGURE); + if (r < 0) + return r; + + r = ppp_this_layer_up(ppp); + if (r < 0) + return r; + + ppp->state = PPP_STATE_OPENED; + + break; + default: + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + } + + return 0; +} + +static int ppp_receive_configure_nak(ppp_machine *ppp, PPPEncapsulation *packet) { + PPPOption *option; + int r; + + assert(ppp); + assert(packet); + + if (packet->information.identifier != ppp->identifier) + return 0; + + PPP_OPTION_FOREACH(option, &packet->information) { + if (packet->information.code == PPP_CONFIGURE_NAK) { + switch (ppp->protocol) { + case PPP_LCP: + switch (option->type) { + case LCP_OPTION_MAGIC_NUMBER: + /* + * Reception of a Configure-Nak with a Magic-Number + * different from that of the last Configure-Nak sent + * to the peer proves that a link is not looped-back, + * and indicates a unique Magic-Number. If the + * Magic-Number is equal to the one sent in the last + * Configure-Nak, the possibility of a looped-back + * link is increased, and a new Magic-Number MUST be + * chosen. In either case, a new Configure-Request + * SHOULD be sent with the new Magic-Number. + */ + if (ppp->lcp.magic_number == *(uint32_t*)option->data) { + log_ppp_machine(ppp, "received nak of magic number"); + random_bytes(&ppp->lcp.magic_number, sizeof(ppp->lcp.magic_number)); + } + + break; + default: + log_warning("ppp: received nak of unknown option type (%"PRIu8")", option->type); + } + + break; + case PPP_IPCP: + switch (option->type) { + case IPCP_OPTION_ADDRESS: + if (option->length == sizeof(PPPOption) + sizeof(ppp->ipcp.address)) { + log_ppp_machine(ppp, "received nak of IP address"); + memcpy(&ppp->ipcp.address, option->data, sizeof(ppp->ipcp.address)); + } + + break; + case IPCP_OPTION_PRIMARY_DNS: + if (option->length == sizeof(PPPOption) + sizeof(ppp->ipcp.primary_dns)) { + log_ppp_machine(ppp, "received nak of primary DNS"); + memcpy(&ppp->ipcp.primary_dns, option->data, sizeof(ppp->ipcp.primary_dns)); + } + + break; + case IPCP_OPTION_SECONDARY_DNS: + if (option->length == sizeof(PPPOption) + sizeof(ppp->ipcp.secondary_dns)) { + log_ppp_machine(ppp, "received nak of secondary DNS"); + memcpy(&ppp->ipcp.secondary_dns, option->data, sizeof(ppp->ipcp.secondary_dns)); + } + + break; + default: + log_warning("ppp: received nak of unknown option type (%"PRIu8")", option->type); + } + + break; + default: + assert_not_reached("invalid PPP protocol"); + } + } else if (packet->information.code == PPP_CONFIGURE_REJECT) { + switch (ppp->protocol) { + case PPP_LCP: + switch (option->type) { + case LCP_OPTION_MAGIC_NUMBER: + /* + * If an implementation does receive a + * Configure-Reject in response to a Configure- + * Request, it can only mean that the link is + * not looped-back, and that its peer will not + * be using Magic-Numbers. In this case, an + * implementation SHOULD act as if the + * negotiation had been successful (as if it had + * instead received a Configure-Ack). + */ + ppp->rejected |= LCP_OPTION_FLAG_MAGIC_NUMBER; + log_ppp_machine(ppp, "peer does not support magic number"); + + break; + default: + /* invalid configure reject are silently ignored */ + return 0; + } + + break; + case PPP_IPCP: + switch (option->type) { + case IPCP_OPTION_ADDRESS: + ppp->rejected |= IPCP_OPTION_FLAG_ADDRESS; + log_ppp_machine(ppp, "peer does not support IP address"); + + break; + case IPCP_OPTION_PRIMARY_DNS: + ppp->rejected |= IPCP_OPTION_FLAG_PRIMARY_DNS; + log_ppp_machine(ppp, "peer does not support primary DNS"); + + break; + case IPCP_OPTION_SECONDARY_DNS: + ppp->rejected |= IPCP_OPTION_FLAG_SECONDARY_DNS; + log_ppp_machine(ppp, "peer does not support secondary DNS"); + + break; + default: + /* invalid configure reject are silently ignored */ + return 0; + } + + break; + default: + assert_not_reached("invalid PPP protocol"); + } + } + } + + switch (ppp->state) { + case PPP_STATE_CLOSING: + case PPP_STATE_STOPPING: + + break; + case PPP_STATE_CLOSED: + case PPP_STATE_STOPPED: + r = ppp_send_unelicited_terminate_ack(ppp); + if (r < 0) + return r; + + break; + case PPP_STATE_REQ_SENT: + case PPP_STATE_ACK_SENT: + r = ppp_initialize_restart_count(ppp, LCP_COUNTER_CONFIGURE); + if (r < 0) + return r; + + r = ppp_send_configure_request(ppp); + if (r < 0) + return r; + + break; + case PPP_STATE_OPENED: + r = ppp_this_layer_down(ppp); + if (r < 0) + return r; + + /* fallthrough */ + case PPP_STATE_ACK_RCVD: + r = ppp_send_configure_request(ppp); + if (r < 0) + return r; + + ppp->state = PPP_STATE_REQ_SENT; + + break; + default: + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + } + + return 0; +} + +static int ppp_receive_terminate_request(ppp_machine *ppp, PPPEncapsulation *packet) { + const char *reason; + int len; + int r; + + assert(ppp); + assert(packet); + + len = be16toh(packet->information.length) - offsetof(PPPPacket, data); + assert(len >= 0); + + reason = (const char *)packet->information.data; + if (len > 0 && reason[len] == '\0' && strlen(reason) == (unsigned)len && utf8_is_valid(reason)) + log_info("ppp: peer terminated connection due to: '%s'", reason); + else + log_info("ppp: peer terminated connection for unknown reason"); + + switch (ppp->state) { + case PPP_STATE_ACK_RCVD: + case PPP_STATE_ACK_SENT: + r = ppp_send_ack(ppp, packet); + if (r < 0) + return r; + + ppp->state = PPP_STATE_REQ_SENT; + + break; + case PPP_STATE_CLOSED: + case PPP_STATE_STOPPED: + case PPP_STATE_CLOSING: + case PPP_STATE_STOPPING: + case PPP_STATE_REQ_SENT: + r = ppp_send_ack(ppp, packet); + if (r < 0) + return r; + + break; + case PPP_STATE_OPENED: + r = ppp_send_ack(ppp, packet); + if (r < 0) + return r; + + r = ppp_this_layer_down(ppp); + if (r < 0) + return r; + + r = ppp_zero_restart_count(ppp); + if (r < 0) + return r; + + ppp->state = PPP_STATE_STOPPING; + + break; + default: + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + } + + return 0; +} + +static int ppp_receive_terminate_ack(ppp_machine *ppp, PPPEncapsulation *packet) { + int r; + + assert(ppp); + assert(packet); + + switch (ppp->state) { + case PPP_STATE_CLOSED: + case PPP_STATE_STOPPED: + case PPP_STATE_REQ_SENT: + case PPP_STATE_ACK_SENT: + break; + case PPP_STATE_CLOSING: + r = ppp_this_layer_finished(ppp); + if (r < 0) + return r; + + ppp->state = PPP_STATE_CLOSED; + + break; + case PPP_STATE_STOPPING: + r = ppp_this_layer_finished(ppp); + if (r < 0) + return r; + + ppp->state = PPP_STATE_STOPPED; + + break; + + case PPP_STATE_ACK_RCVD: + ppp->state = PPP_STATE_REQ_SENT; + + break; + case PPP_STATE_OPENED: + r = ppp_this_layer_down(ppp); + if (r < 0) + return r; + + r = ppp_send_configure_request(ppp); + if (r < 0) + return r; + + ppp->state = PPP_STATE_REQ_SENT; + + break; + default: + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + } + + return 0; +} + +static int ppp_receive_code_reject(ppp_machine *ppp, PPPEncapsulation *packet) { + bool catastrophic = false; + int r; + + assert(ppp); + assert(packet); + + if (catastrophic) { + switch (ppp->state) { + case PPP_STATE_CLOSED: + case PPP_STATE_CLOSING: + r = ppp_this_layer_finished(ppp); + if (r < 0) + return r; + + ppp->state = PPP_STATE_CLOSED; + + break; + case PPP_STATE_STOPPED: + case PPP_STATE_STOPPING: + case PPP_STATE_REQ_SENT: + case PPP_STATE_ACK_RCVD: + case PPP_STATE_ACK_SENT: + r = ppp_this_layer_finished(ppp); + if (r < 0) + return r; + + ppp->state = PPP_STATE_STOPPED; + + break; + case PPP_STATE_OPENED: + r = ppp_this_layer_down(ppp); + if (r < 0) + return r; + + r = ppp_initialize_restart_count(ppp, LCP_COUNTER_TERMINATE); + if (r < 0) + return r; + + r = ppp_send_terminate_request(ppp, "receiving code reject in open state"); + if (r < 0) + return r; + + break; + default: + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + } + + } else { + switch (ppp->state) { + case PPP_STATE_CLOSED: + case PPP_STATE_STOPPED: + case PPP_STATE_CLOSING: + case PPP_STATE_STOPPING: + case PPP_STATE_REQ_SENT: + case PPP_STATE_ACK_SENT: + case PPP_STATE_OPENED: + break; + case PPP_STATE_ACK_RCVD: + ppp->state = PPP_STATE_REQ_SENT; + + break; + default: + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + } + } + + return 0; +} + +static int ppp_receive_echo_request(ppp_machine *ppp, PPPEncapsulation *packet) { + int r; + + assert(ppp); + assert(packet); + + switch (ppp->state) { + case PPP_STATE_CLOSED: + case PPP_STATE_STOPPED: + case PPP_STATE_CLOSING: + case PPP_STATE_STOPPING: + case PPP_STATE_REQ_SENT: + case PPP_STATE_ACK_RCVD: + case PPP_STATE_ACK_SENT: + break; + case PPP_STATE_OPENED: + r = ppp_send_ack(ppp, packet); + if (r < 0) + return r; + + break; + default: + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + } + + return 0; +} + +static int ppp_receive_unknown_code(ppp_machine *ppp, PPPEncapsulation *packet) { + int r; + + assert(ppp); + assert(packet); + + if (IN_SET(ppp->state, _PPP_STATE_INVALID, PPP_STATE_STARTING)) + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + + r = ppp_machine_send_code_reject(ppp, &packet->information); + if (r < 0) + return r; + + return 0; +} + +int ppp_machine_handle_message(ppp_machine *ppp, PPPEncapsulation *packet) { + const char *code; + int r; + + assert(ppp); + assert(packet); + assert(packet->protocol == htobe16(ppp->protocol)); + + code = ppp_packet_type_to_string(packet->information.code); + if (code) + log_ppp_machine(ppp, "received %s", code); + else + log_ppp_machine(ppp, "received unknown code (%"PRIu8")", packet->information.code); + + + switch (packet->information.code) { + case PPP_CONFIGURE_REQUEST: + r = ppp_receive_configure_request(ppp, packet); + if (r < 0) + return r; + + break; + case PPP_CONFIGURE_ACK: + r = ppp_receive_configure_ack(ppp, packet); + if (r < 0) + return r; + + break; + case PPP_CONFIGURE_NAK: + case PPP_CONFIGURE_REJECT: + r = ppp_receive_configure_nak(ppp, packet); + if (r < 0) + return r; + + break; + case PPP_TERMINATE_REQUEST: + r = ppp_receive_terminate_request(ppp, packet); + if (r < 0) + return r; + + break; + case PPP_TERMINATE_ACK: + r = ppp_receive_terminate_ack(ppp, packet); + if (r < 0) + return r; + + break; + case PPP_CODE_REJECT: + r = ppp_receive_code_reject(ppp, packet); + if (r < 0) + return r; + + break; + case LCP_PROTOCOL_REJECT: + if (ppp->protocol == PPP_LCP) { + r = ppp_receive_code_reject(ppp, packet); + if (r < 0) + return r; + } else { + r = ppp_receive_unknown_code(ppp, packet); + if (r < 0) + return r; + } + + break; + case LCP_ECHO_REQUEST: + case LCP_ECHO_REPLY: + case LCP_DISCARD_REQUEST: + if (ppp->protocol == PPP_LCP) { + r = ppp_receive_echo_request(ppp, packet); + if (r < 0) + return r; + } else { + r = ppp_receive_unknown_code(ppp, packet); + if (r < 0) + return r; + } + + break; + /* TODO: LCP_IDENTIFICATION and LCP_TIME_REMAINING (RFC 1570) */ + default: + r = ppp_receive_unknown_code(ppp, packet); + if (r < 0) + return r; + + break; + } + + return 0; +} + +int ppp_machine_lower_up(ppp_machine *ppp, int fd) { + int r; + + assert_return(ppp, -EINVAL); + assert_return(fd >= 0, -EINVAL); + assert_return(ppp->fd < 0, -EBUSY); + + log_ppp_machine(ppp, "lower layer up"); +/* + Up + + This event occurs when a lower layer indicates that it is ready to + carry packets. + + Typically, this event is used by a modem handling or calling + process, or by some other coupling of the LCP link to the physical + media, to signal LCP that the link is entering Link Establishment + phase. + + It also can be used by LCP to signal each NCP that the link is + entering Network-Layer Protocol phase. That is, the This-Layer-Up + action from LCP triggers the Up event in the NCP. +*/ + + ppp->fd = fd; + + switch (ppp->state) { + case _PPP_STATE_INVALID: + ppp->state = PPP_STATE_CLOSED; + break; + + case PPP_STATE_STARTING: + ppp->state = PPP_STATE_REQ_SENT; + + r = ppp_initialize_restart_count(ppp, LCP_COUNTER_CONFIGURE); + if (r < 0) + return r; + + r = ppp_send_configure_request(ppp); + if (r < 0) + return r; + + break; + default: + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + } + + return 0; +} + +int ppp_machine_lower_down(ppp_machine *ppp) { + int r; + + assert_return(ppp, -EINVAL); + assert_return(ppp->fd >= 0, -EUNATCH); + + log_ppp_machine(ppp, "lower layer down"); + + ppp->fd = -1; + + switch (ppp->state) { + case PPP_STATE_CLOSED: + case PPP_STATE_CLOSING: + ppp->state = _PPP_STATE_INVALID; + break; + + case PPP_STATE_STOPPED: + ppp->state = PPP_STATE_STARTING; + + r = ppp_this_layer_started(ppp); + if (r < 0) + return r; + break; + + case PPP_STATE_OPENED: + ppp->state = PPP_STATE_STARTING; + + r = ppp_this_layer_down(ppp); + if (r < 0) + return 0; + + break; + case PPP_STATE_STOPPING: + case PPP_STATE_REQ_SENT: + case PPP_STATE_ACK_RCVD: + case PPP_STATE_ACK_SENT: + ppp->state = PPP_STATE_STARTING; + + break; + default: + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + } + + return 0; +} + +int ppp_machine_start(ppp_machine *ppp) { + int r; + + assert_return(ppp, -EINVAL); + + log_ppp_machine(ppp, "start"); + + switch(ppp->state) { + case _PPP_STATE_INVALID: + ppp->state = PPP_STATE_STARTING; + + r = ppp_this_layer_started(ppp); + if (r < 0) + return r; + + break; + case PPP_STATE_CLOSED: + ppp->state = PPP_STATE_REQ_SENT; + + r = ppp_initialize_restart_count(ppp, LCP_COUNTER_CONFIGURE); + if (r < 0) + return 0; + + r = ppp_send_configure_request(ppp); + if (r < 0) + return 0; + + break; + case PPP_STATE_CLOSING: + ppp->state = PPP_STATE_STOPPING; + + break; + case PPP_STATE_STARTING: + case PPP_STATE_STOPPED: + case PPP_STATE_STOPPING: + case PPP_STATE_REQ_SENT: + case PPP_STATE_ACK_RCVD: + case PPP_STATE_ACK_SENT: + case PPP_STATE_OPENED: + break; + default: + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + } + + return 0; +} + +int ppp_machine_stop(ppp_machine *ppp) { + int r; + + assert_return(ppp, -EINVAL); + + log_ppp_machine(ppp, "stop"); + + switch (ppp->state) { + case PPP_STATE_STARTING: + ppp->state = _PPP_STATE_INVALID; + + r = ppp_this_layer_finished(ppp); + if (r < 0) + return 0; + + break; + case PPP_STATE_STOPPED: + ppp->state = PPP_STATE_CLOSED; + + break; + case PPP_STATE_STOPPING: + ppp->state = PPP_STATE_CLOSING; + + break; + case PPP_STATE_OPENED: + ppp->state = PPP_STATE_CLOSING; + + r = ppp_this_layer_down(ppp); + if (r < 0) + return 0; + + /* fallthrough */ + case PPP_STATE_REQ_SENT: + case PPP_STATE_ACK_RCVD: + case PPP_STATE_ACK_SENT: + ppp->state = PPP_STATE_CLOSING; + + r = ppp_initialize_restart_count(ppp, LCP_COUNTER_TERMINATE); + if (r < 0) + return r; + + r = ppp_send_terminate_request(ppp, "machine being stopped"); + if (r < 0) + return r; + + break; + case _PPP_STATE_INVALID: + case PPP_STATE_CLOSED: + case PPP_STATE_CLOSING: + break; + default: + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + } + + return 0; +} + +static int ppp_handle_timeout(ppp_machine *ppp) { + int r; + + assert(ppp); + + log_ppp_machine(ppp, "timeout"); + + switch (ppp->state) { + case PPP_STATE_CLOSING: + case PPP_STATE_STOPPING: + r = ppp_send_terminate_request(ppp, "timing out in closing or stopping state"); + if (r < 0) + return r; + + break; + case PPP_STATE_ACK_RCVD: + ppp->state = PPP_STATE_REQ_SENT; + + /* fallthrough */ + case PPP_STATE_REQ_SENT: + case PPP_STATE_ACK_SENT: + r = ppp_send_configure_request(ppp); + if (r < 0) + return r; + + break; + default: + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + } + + return 0; +} + +static int ppp_handle_timeout_expired(ppp_machine *ppp) { + int r; + + assert(ppp); + + log_ppp_machine(ppp, "timeout expired"); + + switch (ppp->state) { + case PPP_STATE_CLOSING: + ppp->state = PPP_STATE_CLOSED; + + r = ppp_this_layer_finished(ppp); + if (r < 0) + return r; + + break; + case PPP_STATE_STOPPING: + case PPP_STATE_REQ_SENT: + case PPP_STATE_ACK_RCVD: + case PPP_STATE_ACK_SENT: + ppp->state = PPP_STATE_STOPPED; + + r = ppp_this_layer_finished(ppp); + if (r < 0) + return r; + + break; + default: + assert_not_reached(ppp_machine_state_to_string(ppp->state)); + } + + return 0; +} + +static int ppp_timeout(sd_event_source *s, uint64_t usec, + void *userdata) { + ppp_machine *ppp = userdata; + + assert(ppp); + + if (ppp->restart_count <= 0) + return ppp_handle_timeout_expired(ppp); + + return ppp_handle_timeout(ppp); +} diff --git a/src/libsystemd-network/ppp-machine.h b/src/libsystemd-network/ppp-machine.h new file mode 100644 index 000000000..4193f665c --- /dev/null +++ b/src/libsystemd-network/ppp-machine.h @@ -0,0 +1,46 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosdpppmachinefoo +#define foosdpppmachinefoo + +/*** + 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/>. +***/ + +#include <stdbool.h> + +#include "sd-ppp.h" +#include "sd-event.h" + +#include "ppp-internal.h" + +typedef struct ppp_machine ppp_machine; +typedef void (*ppp_machine_cb_t)(PPPEvent event, void *userdata); + +int ppp_machine_set_callback(ppp_machine *ppp, ppp_machine_cb_t cb, void *userdata); +int ppp_machine_lower_up(ppp_machine *ppp, int fd); +int ppp_machine_lower_down(ppp_machine *ppp); +int ppp_machine_start(ppp_machine *ppp); +int ppp_machine_stop(ppp_machine *ppp); +int ppp_machine_new(ppp_machine **ret, uint16_t protocol, sd_event *event, int priority); +void ppp_machine_free(ppp_machine *ppp); + +int ppp_machine_handle_message(ppp_machine *ppp, PPPEncapsulation *packet); +int ppp_machine_send_code_reject(ppp_machine *ppp, PPPPacket *request); + +#endif diff --git a/src/libsystemd-network/ppp-pap.c b/src/libsystemd-network/ppp-pap.c new file mode 100644 index 000000000..43dd87d75 --- /dev/null +++ b/src/libsystemd-network/ppp-pap.c @@ -0,0 +1,340 @@ +/*-*- 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 1334 */ + +#include <linux/ppp-ioctl.h> +#include <net/if.h> +#include <netinet/in.h> +#include <linux/ppp_defs.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" + +typedef struct PAPAckPacket { + uint8_t code; + uint8_t identifier; + be16_t length; + uint8_t msg_length; + uint8_t message[0]; +} _packed_ PAPAckPacket; + +typedef struct PAPOption { + uint8_t length; + uint8_t data[0]; +} PAPOption; + +struct ppp_pap { + sd_event *event; + int event_priority; + int fd; + sd_event_source *timeout; + + ppp_pap_cb_t cb; + void *userdata; + + uint8_t identifier; + + char *name; + char *password; +}; + +typedef enum PAPPacketType { + PAP_AUTHENTICATE_REQUEST = 1, + PAP_AUTHENTICATE_ACK = 2, + PAP_AUTHENTICATE_NAK = 3, + _PAP_PACKET_TYPE_MAX, + _PAP_PACKET_TYPE_INVALID = -1 +} PAPPacketType; + +const char* pap_packet_type_to_string(PAPPacketType i) _const_; +PAPPacketType pap_packet_type_from_string(const char *s) _pure_; + +static const char* const pap_packet_type_table[_PAP_PACKET_TYPE_MAX] = { + [PAP_AUTHENTICATE_REQUEST] = "auth request", + [PAP_AUTHENTICATE_ACK] = "auth ack", + [PAP_AUTHENTICATE_NAK] = "autth nak", +}; + +DEFINE_STRING_TABLE_LOOKUP(pap_packet_type, PAPPacketType); + +static int pap_success(ppp_pap *pap, const char *message) { + assert(pap); + + if (message) + log_debug("PAP: success: %s", message); + else + log_debug("PAP: success"); + + if (pap->cb) + pap->cb(PPP_AUTH_EVENT_SUCCESS, pap->userdata); + + pap->timeout = sd_event_source_unref(pap->timeout); + + return 0; +} + +static int pap_failure(ppp_pap *pap, const char *message) { + assert(pap); + + if (message) + log_debug("PAP: failure: %s", message); + else + log_debug("PAP: failure"); + + if (pap->cb) + pap->cb(PPP_AUTH_EVENT_FAILURE, pap->userdata); + + pap->timeout = sd_event_source_unref(pap->timeout); + + return 0; +} + +static int pap_timeout(sd_event_source *s, uint64_t usec, void *userdata); + +static int pap_arm_timeout(ppp_pap *pap) { + _cleanup_event_source_unref_ sd_event_source *timeout = NULL; + usec_t next_timeout; + int r; + + assert(pap); + + r = sd_event_now(pap->event, CLOCK_MONOTONIC, &next_timeout); + if (r < 0) + return 0; + + /* TODO: reset to 500ms or so */ + next_timeout += 500 * USEC_PER_MSEC; + + r = sd_event_add_time(pap->event, + &timeout, + CLOCK_MONOTONIC, + next_timeout, 10 * USEC_PER_MSEC, + pap_timeout, pap); + if (r < 0) + return r; + + r = sd_event_source_set_priority(timeout, pap->event_priority); + if (r < 0) + return r; + + sd_event_source_unref(pap->timeout); + pap->timeout = timeout; + timeout = NULL; + + return 0; +} + +static int option_append(PPPPacket *packet, size_t packet_size, void *option, size_t option_size) { + PAPOption *next_option; + + assert(option_size <= 0xff); + + if (packet_size - PPP_PACKET_LENGTH(packet) < sizeof(PAPOption) + option_size) + return -ENOBUFS; + + next_option = (PAPOption*)((uint8_t*)packet + PPP_PACKET_LENGTH(packet)); + + next_option->length = option_size; + memcpy(&next_option->data, option, option_size); + packet->length = htobe16(PPP_PACKET_LENGTH(packet) + offsetof(PAPOption, data) + next_option->length); + + return 0; +} + +static int pap_send_authenticate_request(ppp_pap *pap) { + PPPEncapsulation packet = {}; + int r; + + assert(pap); + + packet.protocol = htobe16(PPP_PAP); + packet.information.code = PAP_AUTHENTICATE_REQUEST; + packet.information.identifier = pap->identifier; + packet.information.length = htobe16(offsetof(PPPPacket, data)); + + r = option_append(&packet.information, sizeof(packet.information), pap->name, strlen(pap->name)); + if (r < 0) + return r; + + r = option_append(&packet.information, sizeof(packet.information), pap->password, strlen(pap->password)); + if (r < 0) + return r; + + r = write(pap->fd, &packet, offsetof(PPPEncapsulation, information) + + PPP_PACKET_LENGTH(&packet.information)); + if (r < 0) + return -errno; + + log_debug("PAP: sent authentication request"); + + r = pap_arm_timeout(pap); + if (r < 0) + return r; + + return 0; +} + +static int pap_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + ppp_pap *pap = userdata; + int r; + + assert(pap); + + r = pap_send_authenticate_request(pap); + if (r < 0) + log_warning("PAP: could not send authenticate request: %s", strerror(-r)); + + return 0; +} + +int ppp_pap_handle_message(ppp_pap *pap, PPPEncapsulation *packet) { + _cleanup_free_ char *message = NULL; + PAPAckPacket *pap_packet; + int r; + + assert(pap); + assert(packet); + + if (packet->information.identifier != pap->identifier) { + log_warning("PAP: wrong identifier"); + return 0; + } + + if (PPP_PACKET_LENGTH(&packet->information) < sizeof(PAPAckPacket)) { + log_warning("PAP: packet (%u bytes) too small (%zu bytes)", PPP_PACKET_LENGTH(&packet->information), sizeof(PAPAckPacket)); + return 0; + } + + pap_packet = (PAPAckPacket*)&packet->information; + + if (PPP_PACKET_LENGTH(&packet->information) != sizeof(PAPAckPacket) + pap_packet->msg_length) { + log_warning("PAP: inconsistent package length"); + return 0; + } + + message = strndup((const char*)pap_packet->message, pap_packet->msg_length); + + switch(packet->information.code) { + case PAP_AUTHENTICATE_ACK: + r = pap_success(pap, message); + if (r < 0) + return r; + + break; + case PAP_AUTHENTICATE_NAK: + r = pap_failure(pap, message); + if (r < 0) + return r; + + break; + default: + assert_not_reached(pap_packet_type_to_string(packet->information.code)); + } + + return 0; +} + +/* managment */ + +void ppp_pap_free(ppp_pap *pap) { + sd_event_source_unref(pap->timeout); + sd_event_unref(pap->event); + free(pap->name); + free(pap->password); + free(pap); +} + +int ppp_pap_new(ppp_pap **ret, sd_event *event, int priority, const char *name, const char *password) { + ppp_pap *pap; + + assert(ret); + assert(event); + + /* TODO cleanup on error */ + + pap = new0(ppp_pap, 1); + if (!pap) + return -ENOMEM; + + pap->event = sd_event_ref(event); + pap->event_priority = priority; + pap->fd = -1; + + if (name) { + pap->name = strndup(name, 0xff); + if (!pap->name) + return -ENOMEM; + } + + if (password) { + pap->password = strndup(password, 0xff); + if (!pap->password) + return -ENOMEM; + } + + *ret = pap; + + return 0; +} + +int ppp_pap_set_callback(ppp_pap *pap, ppp_pap_cb_t cb, void *userdata) { + assert_return(pap, -EINVAL); + + pap->cb = cb; + pap->userdata = userdata; + + return 0; +} + +int ppp_pap_lower_up(ppp_pap *pap, int fd) { + assert(pap->fd == -1); + assert(!pap->timeout); + + log_debug("PAP: lower layer up"); + + pap->fd = fd; + pap_arm_timeout(pap); + + random_bytes(&pap->identifier, sizeof(pap->identifier)); + + return 0; +} + +int ppp_pap_lower_down(ppp_pap *pap) { + assert(pap->fd >= 0); + assert(pap->timeout); + + log_debug("PAP: lower layer down"); + + pap->fd = -1; + pap->timeout = sd_event_source_unref(pap->timeout); + + return 0; +} diff --git a/src/libsystemd-network/ppp-pap.h b/src/libsystemd-network/ppp-pap.h new file mode 100644 index 000000000..e8ce30296 --- /dev/null +++ b/src/libsystemd-network/ppp-pap.h @@ -0,0 +1,44 @@ +/*-*- 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/>. +***/ + +#pragma once + +#include <stdbool.h> + +#include "sd-ppp.h" +#include "sd-event.h" + +#include "ppp-internal.h" + +typedef enum PPPAuthEvent { + PPP_AUTH_EVENT_SUCCESS, + PPP_AUTH_EVENT_FAILURE, +} PPPAuthEvent; + +typedef struct ppp_pap ppp_pap; +typedef void (*ppp_pap_cb_t)(PPPAuthEvent event, void *userdata); + +int ppp_pap_set_callback(ppp_pap *pap, ppp_pap_cb_t cb, void *userdata); +int ppp_pap_lower_up(ppp_pap *pap, int fd); +int ppp_pap_lower_down(ppp_pap *pap); +int ppp_pap_new(ppp_pap **ret, sd_event *event, int priority, const char *name, const char *password); +void ppp_pap_free(ppp_pap *pap); +int ppp_pap_handle_message(ppp_pap *pap, PPPEncapsulation *packet); 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; +} diff --git a/src/libsystemd-network/sd-pppoe.c b/src/libsystemd-network/sd-pppoe.c new file mode 100644 index 000000000..75722d8f3 --- /dev/null +++ b/src/libsystemd-network/sd-pppoe.c @@ -0,0 +1,655 @@ +/*-*- 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 2516 */ + +#include <sys/ioctl.h> +#include <linux/ppp-ioctl.h> +#include <net/if.h> +#include <netinet/in.h> +#include <linux/if_pppox.h> + +#include "sd-pppoe.h" + +#include "event-util.h" + +#include "util.h" +#include "socket-util.h" +#include "async.h" +#include "refcnt.h" + +#define PPPOE_MAX_PACKET_SIZE 1484 + +/* TODO: resend packets to handle packet loss */ + +/* TODO: move this to socket-util.h without getting into + * a mess with the includes */ +union sockaddr_union_pppox { + struct sockaddr sa; + struct sockaddr_pppox pppox; +}; + +typedef enum PPPoEState { + PPPOE_STATE_INITIALIZING, + PPPOE_STATE_REQUESTING, + PPPOE_STATE_RUNNING, + PPPOE_STATE_STOPPED, + _PPPOE_STATE_MAX, + _PPPOE_STATE_INVALID = -1, +} PPPoEState; + +struct sd_pppoe { + RefCount n_ref; + + PPPoEState state; + uint64_t host_uniq; + + int ifindex; + char *ifname; + + sd_event *event; + int event_priority; + int fd; + sd_event_source *event_source; + + char *service_name; + struct ether_addr peer_mac; + be16_t session_id; + + int pppoe_fd; + int channel; + + sd_pppoe_cb_t cb; + void *userdata; +}; + +typedef struct PPPoERequest { + const char *host_uniq; + int host_uniq_len; + const char *cookie; + int cookie_len; + const char *service_name; + int service_name_len; + const char *ac_name; + int ac_name_len; +} PPPoERequest; + +int sd_pppoe_set_index(sd_pppoe *ppp, int ifindex) { + assert_return(ppp, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + + ppp->ifindex = ifindex; + + return 0; +} + +int sd_pppoe_set_ifname(sd_pppoe *ppp, const char *ifname) { + char *name; + + assert_return(ppp, -EINVAL); + assert_return(ifname, -EINVAL); + + if (strlen(ifname) > IFNAMSIZ) + return -EINVAL; + + name = strdup(ifname); + if (!name) + return -ENOMEM; + + free(ppp->ifname); + ppp->ifname = name; + + return 0; +} + +int sd_pppoe_set_service_name(sd_pppoe *ppp, const char *service_name) { + _cleanup_free_ char *name = NULL; + + assert_return(ppp, -EINVAL); + + if (service_name) { + name = strdup(service_name); + if (!name) + return -ENOMEM; + } + + free(ppp->service_name); + ppp->service_name = name; + name = NULL; + + return 0; +} + +int sd_pppoe_attach_event(sd_pppoe *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; +} + +int sd_pppoe_detach_event(sd_pppoe *ppp) { + assert_return(ppp, -EINVAL); + + ppp->event = sd_event_unref(ppp->event); + + return 0; +} + +sd_pppoe *sd_pppoe_ref(sd_pppoe *ppp) { + if (ppp) + assert_se(REFCNT_INC(ppp->n_ref) >= 2); + + return ppp; +} + +sd_pppoe *sd_pppoe_unref(sd_pppoe *ppp) { + if (ppp && REFCNT_DEC(ppp->n_ref) <= 0) { + sd_pppoe_stop(ppp); + sd_pppoe_detach_event(ppp); + + free(ppp); + } + + return NULL; +} + +int sd_pppoe_new (sd_pppoe **ret) { + sd_pppoe *ppp; + + assert_return(ret, -EINVAL); + + ppp = new0(sd_pppoe, 1); + if (!ppp) + return -ENOMEM; + + ppp->n_ref = REFCNT_INIT; + ppp->state = _PPPOE_STATE_INVALID; + ppp->ifindex = -1; + ppp->fd = -1; + ppp->pppoe_fd = -1; + + *ret = ppp; + + return 0; +} + +int sd_pppoe_get_channel(sd_pppoe *ppp, int *channel) { + assert_return(ppp, -EINVAL); + assert_return(channel, -EINVAL); + assert_return(ppp->pppoe_fd != -1, -EUNATCH); + assert_return(ppp->state == PPPOE_STATE_RUNNING, -EUNATCH); + + *channel = ppp->channel; + + return 0; +} + +int sd_pppoe_set_callback(sd_pppoe *ppp, sd_pppoe_cb_t cb, void *userdata) { + assert_return(ppp, -EINVAL); + + ppp->cb = cb; + ppp->userdata = userdata; + + return 0; +} + +static int pppoe_send(sd_pppoe *ppp, uint8_t code, PPPoERequest *req) { + union sockaddr_union link = { + .ll = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_PPP_DISC), + .sll_halen = ETH_ALEN, + }, + }; + _cleanup_free_ struct pppoe_hdr *packet = NULL; + struct pppoe_tag *tag; + uint16_t payload_length = 0; + int r; + + assert(ppp); + assert(ppp->fd != -1); + assert(IN_SET(code, PADI_CODE, PADR_CODE, PADT_CODE)); + assert(code != PADR_CODE || req); + + link.ll.sll_ifindex = ppp->ifindex; + if (code == PADI_CODE) + memset(&link.ll.sll_addr, 0xff, ETH_ALEN); + else + memcpy(&link.ll.sll_addr, &ppp->peer_mac, ETH_ALEN); + + packet = malloc0(PPPOE_MAX_PACKET_SIZE); + if (!packet) + return -ENOMEM; + + packet->ver = 0x1; + packet->type = 0x1; + packet->code = code; + if (code == PADT_CODE) + packet->sid = ppp->session_id; + + tag = packet->tag; + + /* Service-Name */ + tag->tag_type = PTT_SRV_NAME; + if (ppp->service_name) { + tag->tag_len = htobe16(strlen(ppp->service_name)); + memcpy(tag->tag_data, ppp->service_name, strlen(ppp->service_name)); + } + payload_length += sizeof(struct pppoe_tag) + be16toh(tag->tag_len); + tag = (struct pppoe_tag*)((uint8_t*)packet->tag + payload_length); + + /* AC-Cookie */ + if (code == PADR_CODE && req->cookie) { + tag->tag_type = PTT_AC_COOKIE; + tag->tag_len = htobe16(req->cookie_len); + memcpy(tag->tag_data, req->cookie, req->cookie_len); + + payload_length += sizeof(struct pppoe_tag) + req->cookie_len; + tag = (struct pppoe_tag*)((uint8_t*)packet->tag + payload_length); + } + + /* Host-Uniq */ + if (code != PADT_CODE) { + ppp->host_uniq = random_u64(); + + tag->tag_type = PTT_HOST_UNIQ; + tag->tag_len = htobe16(sizeof(ppp->host_uniq)); + memcpy(tag->tag_data, &ppp->host_uniq, sizeof(ppp->host_uniq)); + + payload_length += sizeof(struct pppoe_tag) + sizeof(ppp->host_uniq); + tag = (struct pppoe_tag*)((uint8_t*)packet->tag + payload_length); + } + + packet->length = htobe16(payload_length); + + r = sendto(ppp->fd, packet, sizeof(struct pppoe_hdr) + payload_length, + 0, &link.sa, sizeof(link.ll)); + if (r < 0) + return -errno; + + return 0; +} + +static int pppoe_send_initiation(sd_pppoe *ppp) { + int r; + + r = pppoe_send(ppp, PADI_CODE, NULL); + if (r < 0) + return r; + + log_debug("pppoe: sent DISCOVER (Service-Name: %s)", + ppp->service_name ? : ""); + + return r; +} + +static int pppoe_send_request(sd_pppoe *ppp, PPPoERequest *req) { + int r; + + r = pppoe_send(ppp, PADR_CODE, req); + if (r < 0) + return r; + + log_debug("pppoe: sent REQUEST"); + + return 0; +} + +static int pppoe_send_terminate(sd_pppoe *ppp) { + int r; + + r = pppoe_send(ppp, PADT_CODE, NULL); + if (r < 0) + return r; + + log_debug("pppoe: sent TERMINATE"); + + return 0; +} + +static int pppoe_payload_parse(PPPoERequest *req, uint8_t *tags, size_t length) { + size_t offset = 0; + + while (offset + sizeof(struct pppoe_tag) <= length) { + struct pppoe_tag *tag; + uint16_t tag_len; + + tag = (struct pppoe_tag*)&tags[offset]; + + tag_len = be16toh(tag->tag_len); + offset += sizeof(struct pppoe_tag) + tag_len; + if (offset > length) + return -EIO; + + switch (tag->tag_type) { + case PTT_EOL: + return 0; + case PTT_SRV_NAME: + req->service_name = tag->tag_data; + req->service_name_len = tag_len; + + break; + case PTT_AC_NAME: + req->ac_name = tag->tag_data; + req->ac_name_len = tag_len; + + break; + case PTT_HOST_UNIQ: + req->host_uniq = tag->tag_data; + req->host_uniq_len = tag_len; + + break; + case PTT_AC_COOKIE: + req->cookie = tag->tag_data; + req->cookie_len = tag_len; + + break; + case PTT_SRV_ERR: + case PTT_SYS_ERR: + case PTT_GEN_ERR: + /* TODO: do something more sensible with + the messages */ + return -EINVAL; + } + } + + return 0; +} + +static int pppoe_open_pppoe_socket(sd_pppoe *ppp) { + int s; + + assert(ppp); + assert(ppp->pppoe_fd == -1); + + s = socket(AF_PPPOX, SOCK_STREAM, 0); + if (s < 0) + return -errno; + + ppp->pppoe_fd = s; + + return 0; +} + +static int pppoe_connect_pppoe_socket(sd_pppoe *ppp) { + union sockaddr_union_pppox link = { + .pppox = { + .sa_family = AF_PPPOX, + .sa_protocol = PX_PROTO_OE, + }, + }; + int r, channel; + + assert(ppp); + assert(ppp->pppoe_fd != -1); + assert(ppp->session_id); + assert(ppp->ifname); + + link.pppox.sa_addr.pppoe.sid = ppp->session_id; + memcpy(link.pppox.sa_addr.pppoe.dev, ppp->ifname, strlen(ppp->ifname)); + memcpy(link.pppox.sa_addr.pppoe.remote, &ppp->peer_mac, ETH_ALEN); + + r = connect(ppp->pppoe_fd, &link.sa, sizeof(link.pppox)); + if (r < 0) + return r; + + r = ioctl(ppp->pppoe_fd, PPPIOCGCHAN, &channel); + if (r < 0) + return -errno; + + ppp->channel = channel; + + return 0; +} + +static int pppoe_handle_message(sd_pppoe *ppp, struct pppoe_hdr *packet, + size_t payload_length, struct ether_addr *mac) { + PPPoERequest req = {}; + int r; + + assert(packet); + + if (packet->ver != 0x1 || packet->type != 0x1) + return 0; + + if (be16toh(packet->length) != payload_length) + return 0; + + r = pppoe_payload_parse(&req, (uint8_t*)packet->tag, payload_length); + if (r < 0) + return 0; + + switch (ppp->state) { + case PPPOE_STATE_INITIALIZING: + if (packet->code != PADO_CODE) + return 0; + + if (req.host_uniq_len != sizeof(ppp->host_uniq) || + memcmp(req.host_uniq, &ppp->host_uniq, + sizeof(ppp->host_uniq)) != 0) + return 0; + + log_debug("pppoe: got OFFER (Peer: " + "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx; " + "Service-Name: '%.*s'; AC-Name: '%.*s')", + mac->ether_addr_octet[0], + mac->ether_addr_octet[1], + mac->ether_addr_octet[2], + mac->ether_addr_octet[3], + mac->ether_addr_octet[4], + mac->ether_addr_octet[5], + req.service_name_len, + req.service_name ? : "", + req.ac_name_len, + req.ac_name ? : ""); + + memcpy(&ppp->peer_mac, mac, ETH_ALEN); + + r = pppoe_open_pppoe_socket(ppp); + if (r < 0) { + log_warning("pppoe: could not open socket"); + return r; + } + + r = pppoe_send_request(ppp, &req); + if (r < 0) + return 0; + + ppp->state = PPPOE_STATE_REQUESTING; + + break; + case PPPOE_STATE_REQUESTING: + if (packet->code != PADS_CODE) + return 0; + + if (req.host_uniq_len != sizeof(ppp->host_uniq) || + memcmp(req.host_uniq, &ppp->host_uniq, + sizeof(ppp->host_uniq)) != 0) + return 0; + + if (memcmp(&ppp->peer_mac, mac, ETH_ALEN) != 0) + return 0; + + ppp->session_id = packet->sid; + + log_debug("pppoe: got CONFIRMATION (Session ID: %"PRIu16")", + be16toh(ppp->session_id)); + + r = pppoe_connect_pppoe_socket(ppp); + if (r < 0) { + log_warning("pppoe: could not connect socket"); + return r; + } + + ppp->state = PPPOE_STATE_RUNNING; + + assert(ppp->cb); + ppp->cb(ppp, PPPOE_EVENT_RUNNING, ppp->userdata); + + break; + case PPPOE_STATE_RUNNING: + if (packet->code != PADT_CODE) + return 0; + + if (memcmp(&ppp->peer_mac, mac, ETH_ALEN) != 0) + return 0; + + if (ppp->session_id != packet->sid) + return 0; + + log_debug("pppoe: got TERMINATE"); + + ppp->state = PPPOE_STATE_STOPPED; + + assert(ppp->cb); + ppp->cb(ppp, PPPOE_EVENT_STOPPED, ppp->userdata); + + break; + case PPPOE_STATE_STOPPED: + break; + default: + assert_not_reached("pppoe: invalid state when receiving message"); + } + + return 0; +} + +static int pppoe_receive_message(sd_event_source *s, int fd, + uint32_t revents, void *userdata) { + sd_pppoe *ppp = userdata; + _cleanup_free_ struct pppoe_hdr *packet = NULL; + union sockaddr_union link = {}; + socklen_t addrlen = sizeof(link); + int buflen = 0, len, r; + + assert(ppp); + assert(fd != -1); + + r = ioctl(fd, FIONREAD, &buflen); + if (r < 0) + return r; + + if (buflen < 0) + /* this can't be right */ + return -EIO; + + packet = malloc0(buflen); + if (!packet) + return -ENOMEM; + + len = recvfrom(fd, packet, buflen, 0, + &link.sa, &addrlen); + if (len < 0) { + log_warning("pppoe: colud not receive message from raw socket: %s", + strerror(-r)); + return 0; + } if ((size_t)len < sizeof(struct pppoe_hdr)) + return 0; + + if (link.ll.sll_halen != ETH_ALEN) + /* not ethernet? */ + return 0; + + r = pppoe_handle_message(ppp, packet, len - sizeof(struct pppoe_hdr), + (struct ether_addr*)&link.ll.sll_addr); + if (r < 0) + return r; + + return 1; +} + +int sd_pppoe_start(sd_pppoe *ppp) { + union sockaddr_union link = { + .ll = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_PPP_DISC), + }, + }; + _cleanup_close_ int s = -1; + _cleanup_event_source_unref_ sd_event_source *event_source = NULL; + int r; + + assert_return(ppp, -EINVAL); + assert_return(ppp->fd == -1, -EBUSY); + assert_return(!ppp->event_source, -EBUSY); + assert_return(ppp->ifindex > 0, -EUNATCH); + assert_return(ppp->ifname, -EUNATCH); + assert_return(ppp->event, -EUNATCH); + assert_return(ppp->cb, -EUNATCH); + + s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (s < 0) + return -errno; + + link.ll.sll_ifindex = ppp->ifindex; + + r = bind(s, &link.sa, sizeof(link.ll)); + if (r < 0) + return r; + + r = sd_event_add_io(ppp->event, &event_source, + s, EPOLLIN, pppoe_receive_message, + ppp); + if (r < 0) + return r; + + r = sd_event_source_set_priority(event_source, ppp->event_priority); + if (r < 0) + return r; + + ppp->fd = s; + s = -1; + ppp->event_source = event_source; + event_source = NULL; + + r = pppoe_send_initiation(ppp); + if (r < 0) + return r; + + ppp->state = PPPOE_STATE_INITIALIZING; + + return 0; +} + +int sd_pppoe_stop(sd_pppoe *ppp) { + assert_return(ppp, -EINVAL); + + if (ppp->state == PPPOE_STATE_RUNNING) + pppoe_send_terminate(ppp); + + ppp->event_source = sd_event_source_unref(ppp->event_source); + ppp->fd = asynchronous_close(ppp->fd); + ppp->pppoe_fd = asynchronous_close(ppp->pppoe_fd); + + return 0; +} diff --git a/src/libsystemd-network/test-ppp-machine.c b/src/libsystemd-network/test-ppp-machine.c new file mode 100644 index 000000000..3ef3ce65f --- /dev/null +++ b/src/libsystemd-network/test-ppp-machine.c @@ -0,0 +1,112 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 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/>. +***/ + +#include <linux/ppp_defs.h> +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> + +#include "util.h" +#include "socket-util.h" +#include "sd-event.h" +#include "event-util.h" + +#include "ppp-internal.h" +#include "ppp-machine.h" + +static int clients = 2; + +static void ppp_callback(PPPEvent event, void *userdata) { + ppp_machine *ppp = userdata; + + assert(ppp); + + switch(event) { + case PPP_EVENT_UP: + log_info("LCP is UP"); + + clients --; + + if (clients <= 0) + ppp_machine_stop(ppp); + + break; + case PPP_EVENT_DOWN: + log_info("LCP is DOWN"); + + break; + case PPP_EVENT_FINISHED: + log_info("LCP is FINISHED"); + } +} + + +static int ppp_receive_message(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + ppp_machine *ppp = userdata; + PPPEncapsulation packet = {}; + size_t len; + int r; + + assert_se(fd != -1); + assert_se(ppp); + + r = read(fd, &packet, sizeof(packet)); + + assert_se(r > 0); + + len = (unsigned) r; + assert_se(len >= offsetof(PPPEncapsulation, information.data)); + assert_se(len >= offsetof(PPPEncapsulation, information) + PPP_PACKET_LENGTH(&packet.information)); + + assert_se(ppp_machine_handle_message(ppp, &packet) >= 0); + + return 0; +} + +int main (void) { + _cleanup_close_pair_ int s[2] = { -1, -1 }; + _cleanup_event_unref_ sd_event *event = NULL; + ppp_machine *ppp1, *ppp2; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + assert_se(sd_event_new(&event) >= 0); + assert_se(ppp_machine_new(&ppp1, PPP_LCP, event, 0) >= 0); + assert_se(ppp_machine_new(&ppp2, PPP_LCP, event, 0) >= 0); + assert_se(ppp_machine_set_callback(ppp1, ppp_callback, ppp1) >= 0); + assert_se(ppp_machine_set_callback(ppp2, ppp_callback, ppp2) >= 0); + assert_se(ppp_machine_start(ppp1) >= 0); + assert_se(ppp_machine_start(ppp2) >= 0); + assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, s) >= 0); + assert_se(ppp_machine_lower_up(ppp1, s[0]) >= 0); + assert_se(ppp_machine_lower_up(ppp2, s[1]) >= 0); + assert_se(sd_event_add_io(event, NULL, s[0], EPOLLIN, ppp_receive_message, ppp1) >= 0); + assert_se(sd_event_add_io(event, NULL, s[1], EPOLLIN, ppp_receive_message, ppp2) >= 0); + assert_se(sd_event_loop(event) >= 0); + + return EXIT_SUCCESS; +} diff --git a/src/pppd/Makefile b/src/pppd/Makefile new file mode 120000 index 000000000..d0b0e8e00 --- /dev/null +++ b/src/pppd/Makefile @@ -0,0 +1 @@ +../Makefile
\ No newline at end of file diff --git a/src/pppd/pppd.c b/src/pppd/pppd.c new file mode 100644 index 000000000..20d65d0f1 --- /dev/null +++ b/src/pppd/pppd.c @@ -0,0 +1,161 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen <teg@jklm.no> + + 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/>. +***/ + +#include <sys/ioctl.h> +#include <linux/ppp-ioctl.h> +#include <net/if.h> + +#include "sd-ppp.h" +#include "sd-pppoe.h" +#include "sd-daemon.h" + +#include "event-util.h" + +#include "util.h" +#include "log.h" +#include "ppp-machine.h" + +typedef struct Manager { + sd_event *event; + sd_pppoe *pppoe; + sd_ppp *ppp; +} Manager; + +static void pppoe_handler(sd_pppoe *pppoe, int event, void *userdata) { + Manager *m = userdata; + int channel, r; + + assert(pppoe); + assert(m); + + switch (event) { + case PPPOE_EVENT_RUNNING: + r = sd_pppoe_get_channel(pppoe, &channel); + if (r < 0) { + log_warning("pppoe: could not get fd: %s", strerror(-r)); + return; + } + + log_info("pppoe: running"); + + r = sd_ppp_lower_up(m->ppp, channel); + if (r < 0) + log_warning("Could not start PPP: %s", strerror(-r)); + break; + + case PPPOE_EVENT_STOPPED: + sd_ppp_lower_down(m->ppp); + sd_pppoe_stop(m->pppoe); + sd_event_exit(m->event, 0); + log_info("pppoe: stopped"); + break; + + default: + assert_not_reached("pppoe: invalid event"); + } +} + +int main(int argc, char *argv[]) { + Manager m = {}; + const char *ifname; + int r, ifindex; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + if (argc != 2) { + log_error("This program takes one arguments."); + return EXIT_FAILURE; + } + + ifname = argv[1]; + ifindex = if_nametoindex(ifname); + + r = sd_pppoe_new(&m.pppoe); + if (r < 0) { + log_error("Could not create PPPoE: %s", + strerror(-r)); + return EXIT_FAILURE; + } + + r = sd_event_new(&m.event); + if (r < 0) { + log_error("Could not create event: %s", + strerror(-r)); + return EXIT_FAILURE; + } + + r = sd_ppp_new(&m.ppp, m.event, 0); + if (r < 0) { + log_error("Could not create PPP: %s", + strerror(-r)); + return EXIT_FAILURE; + } + + r = sd_pppoe_attach_event(m.pppoe, m.event, 0); + if (r < 0) { + log_error("Could not attach default event to PPPoE: %s", + strerror(-r)); + return EXIT_FAILURE; + } + + r = sd_pppoe_set_index(m.pppoe, ifindex); + if (r < 0) { + log_error("Could not set ifindex: %s", + strerror(-r)); + return EXIT_FAILURE; + } + + r = sd_pppoe_set_ifname(m.pppoe, ifname); + if (r < 0) { + log_error("Could not set ifname: %s", + strerror(-r)); + return EXIT_FAILURE; + } + + r = sd_pppoe_set_callback(m.pppoe, pppoe_handler, &m); + if (r < 0) { + log_error("Could not set callback: %s", + strerror(-r)); + return EXIT_FAILURE; + } + + r = sd_pppoe_start(m.pppoe); + if (r < 0) { + log_error("Could not start PPPoE: %s", + strerror(-r)); + return EXIT_FAILURE; + } + + r = sd_event_loop(m.event); + if (r < 0) { + log_error("Could not run mainloop: %s", + strerror(-r)); + return EXIT_FAILURE; + } + + sd_pppoe_unref(m.pppoe); + sd_ppp_unref(m.ppp); + sd_event_unref(m.event); + + return EXIT_SUCCESS; +} diff --git a/src/systemd/sd-ppp.h b/src/systemd/sd-ppp.h new file mode 100644 index 000000000..68b8c000b --- /dev/null +++ b/src/systemd/sd-ppp.h @@ -0,0 +1,47 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosdpppfoo +#define foosdpppfoo + +/*** + 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/>. +***/ + +#include <stdbool.h> + +#include "sd-event.h" + +typedef enum PPPEvent { + PPP_EVENT_UP = 0, + PPP_EVENT_DOWN = 1, + PPP_EVENT_FINISHED = 2, +} PPPEvent; + +typedef struct sd_ppp sd_ppp; +typedef void (*sd_ppp_cb_t)(sd_ppp *ppp, int event, void *userdata); + +int sd_ppp_detach_event(sd_ppp *ppp); +int sd_ppp_attach_event(sd_ppp *ppp, sd_event *event, int priority); +int sd_ppp_set_callback(sd_ppp *ppp, sd_ppp_cb_t cb, void *userdata); +int sd_ppp_lower_up(sd_ppp *ppp, int channel); +int sd_ppp_lower_down(sd_ppp *ppp); +sd_ppp *sd_ppp_ref(sd_ppp *ppp); +sd_ppp *sd_ppp_unref(sd_ppp *ppp); +int sd_ppp_new(sd_ppp **ret, sd_event *event, int priority); + +#endif diff --git a/src/systemd/sd-pppoe.h b/src/systemd/sd-pppoe.h new file mode 100644 index 000000000..9c62e1825 --- /dev/null +++ b/src/systemd/sd-pppoe.h @@ -0,0 +1,53 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosdpppoefoo +#define foosdpppoefoo + +/*** + 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/>. +***/ + +#include <stdbool.h> +#include <net/ethernet.h> + +#include "sd-event.h" + +#include "sparse-endian.h" + +enum { + PPPOE_EVENT_RUNNING = 0, + PPPOE_EVENT_STOPPED = 1, +}; + +typedef struct sd_pppoe sd_pppoe; +typedef void (*sd_pppoe_cb_t)(sd_pppoe *ppp, int event, void *userdata); + +int sd_pppoe_detach_event(sd_pppoe *ppp); +int sd_pppoe_attach_event(sd_pppoe *ppp, sd_event *event, int priority); +int sd_pppoe_get_channel(sd_pppoe *ppp, int *channel); +int sd_pppoe_set_callback(sd_pppoe *ppp, sd_pppoe_cb_t cb, void *userdata); +int sd_pppoe_set_index(sd_pppoe *ppp, int interface_index); +int sd_pppoe_set_ifname(sd_pppoe *ppp, const char *ifname); +int sd_pppoe_set_service_name(sd_pppoe *ppp, const char *service_name); +int sd_pppoe_start(sd_pppoe *ppp); +int sd_pppoe_stop(sd_pppoe *ppp); +sd_pppoe *sd_pppoe_ref(sd_pppoe *ppp); +sd_pppoe *sd_pppoe_unref(sd_pppoe *ppp); +int sd_pppoe_new (sd_pppoe **ret); + +#endif |