summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Gundersen <teg@jklm.no>2014-06-19 19:55:23 +0200
committerTom Gundersen <teg@jklm.no>2014-10-14 15:44:07 +0200
commit9baf4431b84ec371890ff755524f5e3c7b6c6cea (patch)
tree4042cee070f262bfdfab11dae93146f5f3db9686
parent9ff5ff320ec71fec7f2c841223380665794afd07 (diff)
PPP: add support for PPP and PPPoEppp
Authentication not currently supported.
-rw-r--r--.gitignore2
-rw-r--r--Makefile.am31
-rw-r--r--src/libsystemd-network/ppp-internal.h43
-rw-r--r--src/libsystemd-network/ppp-machine.c1732
-rw-r--r--src/libsystemd-network/ppp-machine.h46
-rw-r--r--src/libsystemd-network/ppp-pap.c340
-rw-r--r--src/libsystemd-network/ppp-pap.h44
-rw-r--r--src/libsystemd-network/sd-ppp.c673
-rw-r--r--src/libsystemd-network/sd-pppoe.c655
-rw-r--r--src/libsystemd-network/test-ppp-machine.c112
l---------src/pppd/Makefile1
-rw-r--r--src/pppd/pppd.c161
-rw-r--r--src/systemd/sd-ppp.h47
-rw-r--r--src/systemd/sd-pppoe.h53
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