From 31e99d9d786a37935f5bc677d04469264dbb8d71 Mon Sep 17 00:00:00 2001 From: Kristen Carlson Accardi Date: Mon, 22 Mar 2010 17:05:57 -0700 Subject: [PATCH] Generic PPP control protocol support Implement a generic protocol that can be shared by both the LCP and the NCP implementation. --- Makefile.am | 3 +- gatchat/ppp.h | 2 + gatchat/ppp_cp.c | 1503 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ gatchat/ppp_cp.h | 139 +++++ 4 files changed, 1646 insertions(+), 1 deletion(-) create mode 100644 gatchat/ppp_cp.c create mode 100644 gatchat/ppp_cp.h diff --git a/Makefile.am b/Makefile.am index 6891d16..a58f10e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -57,7 +57,8 @@ gatchat_sources = gatchat/gatchat.h gatchat/gatchat.c \ gatchat/gat.h \ gatchat/gatserver.h gatchat/gatserver.c \ gatchat/gatppp.c gatchat/gatppp.h \ - gatchat/ppp.c gatchat/ppp.h + gatchat/ppp.c gatchat/ppp.h gatchat/ppp_cp.h \ + gatchat/ppp_cp.c udev_files = plugins/ofono.rules diff --git a/gatchat/ppp.h b/gatchat/ppp.h index 573c967..0f39440 100644 --- a/gatchat/ppp.h +++ b/gatchat/ppp.h @@ -18,6 +18,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ +#include "ppp_cp.h" + #define DEFAULT_MRU 1500 #define BUFFERSZ DEFAULT_MRU*2 #define DEFAULT_ACCM 0x00000000 diff --git a/gatchat/ppp_cp.c b/gatchat/ppp_cp.c new file mode 100644 index 0000000..6967f9d --- /dev/null +++ b/gatchat/ppp_cp.c @@ -0,0 +1,1503 @@ +/* + * + * PPP library with GLib integration + * + * Copyright (C) 2009-2010 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "gatppp.h" +#include "ppp.h" + +#ifdef DEBUG +const char *pppcp_state_strings[] = + {"INITIAL", "STARTING", "CLOSED", "STOPPED", "CLOSING", "STOPPING", + "REQSENT", "ACKRCVD", "ACKSENT", "OPENED" }; + +#define pppcp_trace(p) \ + (g_print("%s: current state %d:%s\n", __FUNCTION__, \ + p->state, pppcp_state_strings[p->state])) +#else +#define pppcp_trace(p) +#endif + +#define pppcp_to_ppp_packet(p) \ + (p-PPP_HEADROOM) + +struct pppcp_event { + enum pppcp_event_type type; + gint len; + guint8 data[0]; +}; + +#define INITIAL_RESTART_TIMEOUT 3000 +#define MAX_TERMINATE 2 +#define MAX_CONFIGURE 10 +#define MAX_FAILURE 5 +#define CP_HEADER_SZ 4 + +static struct pppcp_packet *pppcp_packet_new(struct pppcp_data *data, + guint type, guint bufferlen) +{ + struct pppcp_packet *packet; + struct ppp_header *ppp_packet; + guint16 packet_length = bufferlen + sizeof(*packet); + + ppp_packet = g_try_malloc0(packet_length + 2); + if (!ppp_packet) + return NULL; + + /* add our protocol information */ + ppp_packet->proto = htons(data->proto); + + /* advance past protocol to add CP header information */ + packet = (struct pppcp_packet *) (ppp_packet->info); + + packet->length = htons(packet_length); + packet->code = type; + return packet; +} + +static gboolean pppcp_timeout(gpointer user_data) +{ + struct pppcp_data *data = user_data; + + pppcp_trace(data); + + data->restart_timer = 0; + + if (data->restart_counter) + pppcp_generate_event(data, TO_PLUS, NULL, 0); + else + pppcp_generate_event(data, TO_MINUS, NULL, 0); + return FALSE; +} + +static void pppcp_start_timer(struct pppcp_data *data) +{ + data->restart_timer = g_timeout_add(data->restart_interval, + pppcp_timeout, data); +} + +static void pppcp_stop_timer(struct pppcp_data *data) +{ + if (data->restart_timer) { + g_source_remove(data->restart_timer); + data->restart_timer = 0; + } +} + +static gboolean pppcp_timer_is_running(struct pppcp_data *data) +{ + /* determine if the restart timer is running */ + if (data->restart_timer) + return TRUE; + return FALSE; +} + +static struct pppcp_event *pppcp_event_new(enum pppcp_event_type type, + gpointer event_data, guint len) +{ + struct pppcp_event *event; + guint8 *data = event_data; + + event = g_try_malloc0(sizeof(struct pppcp_event) + len); + if (!event) + return NULL; + + event->type = type; + memcpy(event->data, data, len); + event->len = len; + return event; +} + +static struct pppcp_event *pppcp_get_event(struct pppcp_data *data) +{ + return g_queue_pop_head(data->event_queue); +} + +/* actions */ +/* log an illegal event, but otherwise do nothing */ +static void pppcp_illegal_event(guint8 state, guint8 type) +{ + g_printerr("Illegal event %d while in state %d\n", type, state); +} + +static void pppcp_this_layer_up(struct pppcp_data *data) +{ + struct pppcp_action *action = data->action; + + if (action->this_layer_up) + action->this_layer_up(data); +} + +static void pppcp_this_layer_down(struct pppcp_data *data) +{ + struct pppcp_action *action = data->action; + + if (action->this_layer_up) + action->this_layer_down(data); +} + +static void pppcp_this_layer_started(struct pppcp_data *data) +{ + struct pppcp_action *action = data->action; + + if (action->this_layer_up) + action->this_layer_started(data); +} + +static void pppcp_this_layer_finished(struct pppcp_data *data) +{ + struct pppcp_action *action = data->action; + + if (action->this_layer_up) + action->this_layer_finished(data); +} + +static void pppcp_free_option(gpointer data, gpointer user_data) +{ + struct ppp_option *option = data; + g_free(option); +} + +static void pppcp_clear_options(struct pppcp_data *data) +{ + g_list_foreach(data->acceptable_options, pppcp_free_option, NULL); + g_list_foreach(data->unacceptable_options, pppcp_free_option, NULL); + g_list_foreach(data->rejected_options, pppcp_free_option, NULL); + g_list_free(data->acceptable_options); + g_list_free(data->unacceptable_options); + g_list_free(data->rejected_options); + data->acceptable_options = NULL; + data->unacceptable_options = NULL; + data->rejected_options = NULL; +} + +/* + * set the restart counter to either max-terminate + * or max-configure. The counter is decremented for + * each transmission, including the first. + */ +static void pppcp_initialize_restart_count(struct pppcp_data *data, guint value) +{ + pppcp_trace(data); + pppcp_clear_options(data); + data->restart_counter = value; +} + +/* + * set restart counter to zero + */ +static void pppcp_zero_restart_count(struct pppcp_data *data) +{ + data->restart_counter = 0; +} + +/* + * TBD - generate new identifier for packet + */ +static guint8 new_identity(struct pppcp_data *data, guint prev_identifier) +{ + return prev_identifier+1; +} + +static void get_option_length(gpointer data, gpointer user_data) +{ + struct ppp_option *option = data; + guint8 *length = user_data; + + *length += option->length; +} + +static void copy_option(gpointer data, gpointer user_data) +{ + struct ppp_option *option = data; + guint8 **location = user_data; + memcpy(*location, (guint8 *) option, option->length); + *location += option->length; +} + +void pppcp_add_config_option(struct pppcp_data *data, struct ppp_option *option) +{ + data->config_options = g_list_append(data->config_options, option); +} + +/* + * transmit a Configure-Request packet + * start the restart timer + * decrement the restart counter + */ +static void pppcp_send_configure_request(struct pppcp_data *data) +{ + struct pppcp_packet *packet; + guint8 olength = 0; + guint8 *odata; + + pppcp_trace(data); + + /* figure out how much space to allocate for options */ + g_list_foreach(data->config_options, get_option_length, &olength); + + packet = pppcp_packet_new(data, CONFIGURE_REQUEST, olength); + + /* copy config options into packet data */ + odata = packet->data; + g_list_foreach(data->config_options, copy_option, &odata); + + /* + * if this is the first request, we need a new identifier. + * if this is a retransmission, leave the identifier alone. + */ + if (data->restart_counter == data->max_configure) + data->config_identifier = + new_identity(data, data->config_identifier); + packet->identifier = data->config_identifier; + + ppp_transmit(data->ppp, pppcp_to_ppp_packet((guint8 *) packet), + ntohs(packet->length)); + + /* XXX don't retransmit right now */ +#if 0 + data->restart_counter--; + pppcp_start_timer(data); +#endif +} + +/* + * transmit a Configure-Ack packet + */ +static void pppcp_send_configure_ack(struct pppcp_data *data, + guint8 *request) +{ + struct pppcp_packet *packet; + struct pppcp_packet *pppcp_header = (struct pppcp_packet *) request; + guint len; + guint8 *odata; + + pppcp_trace(data); + + /* subtract for header. */ + len = ntohs(pppcp_header->length) - sizeof(*packet); + + packet = pppcp_packet_new(data, CONFIGURE_ACK, len); + + /* copy the applied options in. */ + odata = packet->data; + + if (g_list_length(data->acceptable_options)) + g_list_foreach(data->acceptable_options, copy_option, &odata); + + /* match identifier of the request */ + packet->identifier = pppcp_header->identifier; + + ppp_transmit(data->ppp, pppcp_to_ppp_packet((guint8 *) packet), + ntohs(packet->length)); +} + +/* + * transmit a Configure-Nak or Configure-Reject packet + */ +static void pppcp_send_configure_nak(struct pppcp_data *data, + guint8 *configure_packet) +{ + struct pppcp_packet *packet; + struct pppcp_packet *pppcp_header = + (struct pppcp_packet *) configure_packet; + guint8 olength = 0; + guint8 *odata; + + /* if we have any rejected options, send a config-reject */ + if (g_list_length(data->rejected_options)) { + /* figure out how much space to allocate for options */ + g_list_foreach(data->rejected_options, get_option_length, + &olength); + + packet = pppcp_packet_new(data, CONFIGURE_REJECT, olength); + + /* copy the rejected options in. */ + odata = packet->data; + g_list_foreach(data->rejected_options, copy_option, + &odata); + + packet->identifier = pppcp_header->identifier; + ppp_transmit(data->ppp, pppcp_to_ppp_packet((guint8 *) packet), + ntohs(packet->length)); + + } + /* if we have any unacceptable options, send a config-nak */ + if (g_list_length(data->unacceptable_options)) { + olength = 0; + + /* figure out how much space to allocate for options */ + g_list_foreach(data->unacceptable_options, get_option_length, + &olength); + + packet = pppcp_packet_new(data, CONFIGURE_NAK, olength); + + /* copy the unacceptable options in. */ + odata = packet->data; + g_list_foreach(data->unacceptable_options, copy_option, + &odata); + + packet->identifier = pppcp_header->identifier; + ppp_transmit(data->ppp, pppcp_to_ppp_packet((guint8 *) packet), + ntohs(packet->length)); + } +} + +/* + * transmit a Terminate-Request packet. + * start the restart timer. + * decrement the restart counter + */ +static void pppcp_send_terminate_request(struct pppcp_data *data) +{ + struct pppcp_packet *packet; + + /* + * the data field can be used by the sender (us). + * leave this empty for now. + */ + packet = pppcp_packet_new(data, TERMINATE_REQUEST, 0); + + /* + * Is this a retransmission? If so, do not change + * the identifier. If not, we need a fresh identity. + */ + if (data->restart_counter == data->max_terminate) + data->terminate_identifier = + new_identity(data, data->terminate_identifier); + packet->identifier = data->terminate_identifier; + ppp_transmit(data->ppp, pppcp_to_ppp_packet((guint8 *) packet), + ntohs(packet->length)); + data->restart_counter--; + pppcp_start_timer(data); +} + +/* + * transmit a Terminate-Ack packet + */ +static void pppcp_send_terminate_ack(struct pppcp_data *data, + guint8 *request) +{ + struct pppcp_packet *packet; + struct pppcp_packet *pppcp_header = (struct pppcp_packet *) request; + + packet = pppcp_packet_new(data, TERMINATE_ACK, 0); + + /* match identifier of the request */ + packet->identifier = pppcp_header->identifier; + + ppp_transmit(data->ppp, pppcp_to_ppp_packet((guint8 *) packet), + ntohs(pppcp_header->length)); +} + +/* + * transmit a Code-Reject packet + * + * XXX this seg faults. + */ +static void pppcp_send_code_reject(struct pppcp_data *data, + guint8 *rejected_packet) +{ + struct pppcp_packet *packet; + + packet = pppcp_packet_new(data, CODE_REJECT, + ntohs(((struct pppcp_packet *) rejected_packet)->length)); + + /* + * Identifier must be changed for each Code-Reject sent + */ + packet->identifier = new_identity(data, data->reject_identifier); + + /* + * rejected packet should be copied in, but it should be + * truncated if it needs to be to comply with mtu requirement + */ + memcpy(packet->data, rejected_packet, + ntohs(packet->length - CP_HEADER_SZ)); + + ppp_transmit(data->ppp, pppcp_to_ppp_packet((guint8 *) packet), + ntohs(packet->length)); +} + +/* + * transmit an Echo-Reply packet + */ +static void pppcp_send_echo_reply(struct pppcp_data *data, + guint8 *request) +{ + struct pppcp_packet *packet; + struct pppcp_packet *header = (struct pppcp_packet *) request; + + /* + * 0 bytes for data, 4 bytes for magic number + */ + packet = pppcp_packet_new(data, ECHO_REPLY, 4); + + /* + * match identifier of request + */ + packet->identifier = header->identifier; + + /* magic number? */ + ppp_transmit(data->ppp, pppcp_to_ppp_packet((guint8 *) packet), + ntohs(packet->length)); + +} + +static void pppcp_transition_state(enum pppcp_state new_state, + struct pppcp_data *data) +{ + /* + * if switching from a state where + * TO events occur, to one where they + * may not, shut off the timer + */ + switch (new_state) { + case INITIAL: + case STARTING: + case CLOSED: + case STOPPED: + case OPENED: + /* if timer is running, stop it */ + if (pppcp_timer_is_running(data)) + pppcp_stop_timer(data); + break; + case CLOSING: + case STOPPING: + case REQSENT: + case ACKRCVD: + case ACKSENT: + break; + } + data->state = new_state; +} + +static void pppcp_up_event(struct pppcp_data *data, guint8 *packet, guint len) +{ + pppcp_trace(data); + switch (data->state) { + case INITIAL: + /* switch state to CLOSED */ + pppcp_transition_state(CLOSED, data); + break; + case STARTING: + /* irc, scr/6 */ + pppcp_initialize_restart_count(data, data->max_configure); + pppcp_send_configure_request(data); + pppcp_transition_state(REQSENT, data); + break; + case CLOSED: + case STOPPED: + case OPENED: + case CLOSING: + case STOPPING: + case REQSENT: + case ACKRCVD: + case ACKSENT: + pppcp_illegal_event(data->state, UP); + } +} + +static void pppcp_down_event(struct pppcp_data *data, guint8 *packet, guint len) +{ + switch (data->state) { + case CLOSED: + pppcp_transition_state(INITIAL, data); + break; + case STOPPED: + /* tls/1 */ + pppcp_transition_state(STARTING, data); + pppcp_this_layer_started(data); + break; + case CLOSING: + pppcp_transition_state(INITIAL, data); + break; + case STOPPING: + case REQSENT: + case ACKRCVD: + case ACKSENT: + pppcp_transition_state(STARTING, data); + break; + case OPENED: + pppcp_transition_state(STARTING, data); + pppcp_this_layer_down(data); + case INITIAL: + case STARTING: + pppcp_illegal_event(data->state, DOWN); + /* illegal */ + } +} + +static void pppcp_open_event(struct pppcp_data *data, guint8 *packet, guint len) +{ + pppcp_trace(data); + switch (data->state) { + case INITIAL: + /* tls/1 */ + pppcp_transition_state(STARTING, data); + pppcp_this_layer_started(data); + break; + case STARTING: + pppcp_transition_state(STARTING, data); + break; + case CLOSED: + pppcp_initialize_restart_count(data, data->max_configure); + pppcp_send_configure_request(data); + pppcp_transition_state(REQSENT, data); + break; + case STOPPED: + /* 3r */ + pppcp_transition_state(STOPPED, data); + break; + case CLOSING: + case STOPPING: + /* 5r */ + pppcp_transition_state(STOPPING, data); + break; + case REQSENT: + case ACKRCVD: + case ACKSENT: + pppcp_transition_state(data->state, data); + break; + case OPENED: + /* 9r */ + pppcp_transition_state(data->state, data); + break; + } +} + +static void pppcp_close_event(struct pppcp_data *data, guint8* packet, guint len) +{ + switch (data->state) { + case INITIAL: + pppcp_transition_state(INITIAL, data); + break; + case STARTING: + pppcp_this_layer_finished(data); + pppcp_transition_state(INITIAL, data); + break; + case CLOSED: + case STOPPED: + pppcp_transition_state(CLOSED, data); + break; + case CLOSING: + case STOPPING: + pppcp_transition_state(CLOSING, data); + break; + case OPENED: + pppcp_this_layer_down(data); + case REQSENT: + case ACKRCVD: + case ACKSENT: + pppcp_initialize_restart_count(data, data->max_terminate); + pppcp_send_terminate_request(data); + pppcp_transition_state(CLOSING, data); + break; + } +} + +static void pppcp_to_plus_event(struct pppcp_data *data, guint8 *packet, guint len) +{ + pppcp_trace(data); + + switch (data->state) { + case CLOSING: + pppcp_send_terminate_request(data); + pppcp_transition_state(CLOSING, data); + break; + case STOPPING: + pppcp_send_terminate_request(data); + pppcp_transition_state(STOPPING, data); + break; + case REQSENT: + case ACKRCVD: + pppcp_send_configure_request(data); + pppcp_transition_state(REQSENT, data); + break; + case ACKSENT: + pppcp_send_configure_request(data); + pppcp_transition_state(ACKSENT, data); + break; + case INITIAL: + case STARTING: + case CLOSED: + case STOPPED: + case OPENED: + pppcp_illegal_event(data->state, TO_PLUS); + } +} + +static void pppcp_to_minus_event(struct pppcp_data *data, guint8 *packet, guint len) +{ + pppcp_trace(data); + switch (data->state) { + case CLOSING: + pppcp_transition_state(CLOSED, data); + pppcp_this_layer_finished(data); + break; + case STOPPING: + pppcp_transition_state(STOPPED, data); + pppcp_this_layer_finished(data); + break; + case REQSENT: + case ACKRCVD: + case ACKSENT: + /* tlf/3p */ + pppcp_transition_state(STOPPED, data); + pppcp_this_layer_finished(data); + break; + case INITIAL: + case STARTING: + case CLOSED: + case STOPPED: + case OPENED: + pppcp_illegal_event(data->state, TO_MINUS); + } +} + +static void pppcp_rcr_plus_event(struct pppcp_data *data, + guint8 *packet, guint len) +{ + pppcp_trace(data); + switch (data->state) { + case CLOSED: + pppcp_send_terminate_ack(data, packet); + pppcp_transition_state(CLOSED, data); + break; + case STOPPED: + pppcp_initialize_restart_count(data, data->max_configure); + pppcp_send_configure_request(data); + pppcp_send_configure_ack(data, packet); + pppcp_transition_state(ACKSENT, data); + break; + case CLOSING: + case STOPPING: + pppcp_transition_state(data->state, data); + break; + case REQSENT: + pppcp_send_configure_ack(data, packet); + pppcp_transition_state(ACKSENT, data); + break; + case ACKRCVD: + pppcp_send_configure_ack(data, packet); + pppcp_this_layer_up(data); + pppcp_transition_state(OPENED, data); + break; + case ACKSENT: + pppcp_send_configure_ack(data, packet); + pppcp_transition_state(ACKSENT, data); + break; + case OPENED: + pppcp_this_layer_down(data); + pppcp_send_configure_request(data); + pppcp_send_configure_ack(data, packet); + pppcp_transition_state(ACKSENT, data); + break; + case INITIAL: + case STARTING: + pppcp_illegal_event(data->state, RCR_PLUS); + } +} + +static void pppcp_rcr_minus_event(struct pppcp_data *data, + guint8 *packet, guint len) +{ + pppcp_trace(data); + + switch (data->state) { + case CLOSED: + pppcp_send_terminate_ack(data, packet); + pppcp_transition_state(CLOSED, data); + break; + case STOPPED: + pppcp_initialize_restart_count(data, data->max_configure); + pppcp_send_configure_request(data); + pppcp_send_configure_nak(data, packet); + pppcp_transition_state(REQSENT, data); + break; + case CLOSING: + case STOPPING: + pppcp_transition_state(data->state, data); + break; + case REQSENT: + case ACKRCVD: + pppcp_send_configure_nak(data, packet); + pppcp_transition_state(data->state, data); + break; + case ACKSENT: + pppcp_send_configure_nak(data, packet); + pppcp_transition_state(REQSENT, data); + break; + case OPENED: + pppcp_this_layer_down(data); + pppcp_send_configure_request(data); + pppcp_send_configure_nak(data, packet); + pppcp_transition_state(REQSENT, data); + break; + case INITIAL: + case STARTING: + pppcp_illegal_event(data->state, RCR_MINUS); + } +} + +static void pppcp_rca_event(struct pppcp_data *data, guint8 *packet, guint len) +{ + pppcp_trace(data); + + switch (data->state) { + case CLOSED: + case STOPPED: + pppcp_send_terminate_ack(data, packet); + case CLOSING: + case STOPPING: + pppcp_transition_state(data->state, data); + break; + case REQSENT: + pppcp_initialize_restart_count(data, data->max_configure); + pppcp_transition_state(ACKRCVD, data); + break; + case ACKRCVD: + /* scr/6x */ + pppcp_send_configure_request(data); + pppcp_transition_state(REQSENT, data); + case ACKSENT: + pppcp_initialize_restart_count(data, data->max_configure); + pppcp_this_layer_up(data); + pppcp_transition_state(OPENED, data); + break; + case OPENED: + pppcp_this_layer_down(data); + pppcp_send_configure_request(data); + pppcp_transition_state(REQSENT, data); + break; + case INITIAL: + case STARTING: + pppcp_illegal_event(data->state, RCA); + } +} + +static void pppcp_rcn_event(struct pppcp_data *data, guint8 *packet, guint len) +{ + pppcp_trace(data); + + switch (data->state) { + case CLOSED: + case STOPPED: + pppcp_send_terminate_ack(data, packet); + case CLOSING: + case STOPPING: + pppcp_transition_state(data->state, data); + case REQSENT: + pppcp_initialize_restart_count(data, data->max_configure); + pppcp_send_configure_request(data); + pppcp_transition_state(REQSENT, data); + break; + case ACKRCVD: + /* scr/6x */ + pppcp_send_configure_request(data); + pppcp_transition_state(REQSENT, data); + break; + case ACKSENT: + pppcp_initialize_restart_count(data, data->max_configure); + pppcp_send_configure_request(data); + pppcp_transition_state(ACKSENT, data); + break; + case OPENED: + pppcp_this_layer_down(data); + pppcp_send_configure_request(data); + pppcp_transition_state(REQSENT, data); + break; + case INITIAL: + case STARTING: + pppcp_illegal_event(data->state, RCN); + } +} + +static void pppcp_rtr_event(struct pppcp_data *data, guint8 *packet, guint len) +{ + pppcp_trace(data); + + switch (data->state) { + case CLOSED: + case STOPPED: + pppcp_send_terminate_ack(data, packet); + case CLOSING: + case STOPPING: + break; + case REQSENT: + case ACKRCVD: + case ACKSENT: + pppcp_send_terminate_ack(data, packet); + pppcp_transition_state(REQSENT, data); + break; + case OPENED: + pppcp_this_layer_down(data); + pppcp_zero_restart_count(data); + pppcp_send_terminate_ack(data, packet); + pppcp_transition_state(STOPPING, data); + break; + case INITIAL: + case STARTING: + pppcp_illegal_event(data->state, RTR); + } +} + +static void pppcp_rta_event(struct pppcp_data *data, guint8 *packet, guint len) +{ + pppcp_trace(data); + + switch (data->state) { + case CLOSED: + case STOPPED: + pppcp_transition_state(data->state, data); + break; + case CLOSING: + pppcp_this_layer_finished(data); + pppcp_transition_state(CLOSED, data); + break; + case STOPPING: + pppcp_this_layer_finished(data); + pppcp_transition_state(STOPPED, data); + break; + case REQSENT: + case ACKRCVD: + pppcp_transition_state(REQSENT, data); + break; + case ACKSENT: + pppcp_transition_state(ACKSENT, data); + break; + case OPENED: + pppcp_this_layer_down(data); + pppcp_send_configure_request(data); + pppcp_transition_state(REQSENT, data); + break; + case INITIAL: + case STARTING: + pppcp_illegal_event(data->state, RTA); + } +} + +static void pppcp_ruc_event(struct pppcp_data *data, guint8 *packet, guint len) +{ + pppcp_trace(data); + + switch (data->state) { + case CLOSED: + case STOPPED: + case CLOSING: + case STOPPING: + case REQSENT: + case ACKRCVD: + case ACKSENT: + case OPENED: + pppcp_send_code_reject(data, packet); + pppcp_transition_state(data->state, data); + break; + case INITIAL: + case STARTING: + pppcp_illegal_event(data->state, RUC); + } +} + +static void pppcp_rxj_plus_event(struct pppcp_data *data, guint8 *packet, guint len) +{ + pppcp_trace(data); + + switch (data->state) { + case CLOSED: + case STOPPED: + case CLOSING: + case STOPPING: + pppcp_transition_state(data->state, data); + break; + case REQSENT: + case ACKRCVD: + pppcp_transition_state(REQSENT, data); + break; + case ACKSENT: + case OPENED: + pppcp_transition_state(data->state, data); + break; + case INITIAL: + case STARTING: + pppcp_illegal_event(data->state, RXJ_PLUS); + } +} + +static void pppcp_rxj_minus_event(struct pppcp_data *data, + guint8 *packet, guint len) +{ + pppcp_trace(data); + + switch (data->state) { + case CLOSED: + case STOPPED: + pppcp_this_layer_finished(data); + pppcp_transition_state(data->state, data); + break; + case CLOSING: + pppcp_this_layer_finished(data); + pppcp_transition_state(CLOSED, data); + break; + case STOPPING: + pppcp_this_layer_finished(data); + pppcp_transition_state(STOPPED, data); + break; + case REQSENT: + case ACKRCVD: + case ACKSENT: + pppcp_this_layer_finished(data); + pppcp_transition_state(STOPPED, data); + break; + case OPENED: + pppcp_this_layer_down(data); + pppcp_initialize_restart_count(data, data->max_terminate); + pppcp_send_terminate_request(data); + pppcp_transition_state(STOPPING, data); + break; + case INITIAL: + case STARTING: + pppcp_illegal_event(data->state, RXJ_MINUS); + } +} + +static void pppcp_rxr_event(struct pppcp_data *data, guint8 *packet, guint len) +{ + pppcp_trace(data); + + switch (data->state) { + case CLOSED: + case STOPPED: + case CLOSING: + case STOPPING: + case REQSENT: + case ACKRCVD: + case ACKSENT: + pppcp_transition_state(data->state, data); + break; + case OPENED: + pppcp_send_echo_reply(data, packet); + pppcp_transition_state(OPENED, data); + break; + case INITIAL: + case STARTING: + pppcp_illegal_event(data->state, RXR); + } +} + +static void pppcp_handle_event(gpointer user_data) +{ + struct pppcp_event *event; + struct pppcp_data *data = user_data; + + while ((event = pppcp_get_event(data))) { + if (event->type > RXR) + pppcp_illegal_event(data->state, event->type); + else + data->event_ops[event->type](data, event->data, + event->len); + g_free(event); + } +} + +/* + * send the event handler a new event to process + */ +void pppcp_generate_event(struct pppcp_data *data, + enum pppcp_event_type event_type, + gpointer event_data, guint data_len) +{ + struct pppcp_event *event; + + event = pppcp_event_new(event_type, event_data, data_len); + if (event) + g_queue_push_tail(data->event_queue, event); + pppcp_handle_event(data); +} + +static gint is_option(gconstpointer a, gconstpointer b) +{ + const struct ppp_option *o = a; + guint8 otype = (guint8) GPOINTER_TO_UINT(b); + + if (o->type == otype) + return 0; + else + return -1; +} + +static void verify_config_option(gpointer elem, gpointer user_data) +{ + struct ppp_option *config = elem; + struct pppcp_data *data = user_data; + GList *list = NULL; + struct ppp_option *option; + + /* + * determine whether this config option is in the + * acceptable options list + */ + if (g_list_length(data->acceptable_options)) + list = g_list_find_custom(data->acceptable_options, + GUINT_TO_POINTER(config->type), + is_option); + if (!list) { + /* + * if the option did not exist, we need to store a copy + * of the option in the unacceptable_options list so it + * can be nak'ed. + */ + option = g_try_malloc0(config->length); + if (option == NULL) + return; + option->type = config->type; + option->length = config->length; + data->unacceptable_options = + g_list_append(data->unacceptable_options, option); + } + +} + +static void remove_config_option(gpointer elem, gpointer user_data) +{ + struct ppp_option *config = elem; + struct pppcp_data *data = user_data; + GList *list; + + /* + * determine whether this config option is in the + * applied options list + */ + if (g_list_length(data->config_options)) { + list = g_list_find_custom(data->config_options, + GUINT_TO_POINTER(config->type), + is_option); + if (list) + data->config_options = + g_list_delete_link(data->config_options, list); + } +} + +static guint8 pppcp_process_configure_request(struct pppcp_data *data, + struct pppcp_packet *packet) +{ + gint len; + int i = 0; + struct ppp_option *option; + enum option_rval rval = OPTION_ERR; + struct pppcp_action *action = data->action; + + pppcp_trace(data); + + len = ntohs(packet->length) - CP_HEADER_SZ; + + /* + * check the options. + */ + while (i < len) { + guint8 otype = packet->data[i]; + guint8 olen = packet->data[i+1]; + option = g_try_malloc0(olen); + if (option == NULL) + break; + option->type = otype; + option->length = olen; + memcpy(option->data, &packet->data[i+2], olen-2); + if (action->option_scan) + rval = action->option_scan(option, data); + switch (rval) { + case OPTION_ACCEPT: + data->acceptable_options = + g_list_append(data->acceptable_options, option); + break; + case OPTION_REJECT: + data->rejected_options = + g_list_append(data->rejected_options, option); + break; + case OPTION_NAK: + data->unacceptable_options = + g_list_append(data->unacceptable_options, + option); + break; + case OPTION_ERR: + g_printerr("unhandled option type %d\n", otype); + } + /* skip ahead to the next option */ + i += olen; + } + + /* make sure all required config options were included */ + g_list_foreach(data->config_options, verify_config_option, data); + + if (g_list_length(data->unacceptable_options) || + g_list_length(data->rejected_options)) + return RCR_MINUS; + + /* + * all options were acceptable, so we should apply them before + * sending a configure-ack + * + * Remove all applied options from the config_option list. The + * protocol will have to re-add them if they want them renegotiated + * when the ppp goes down. + */ + if (action->option_process) { + g_list_foreach(data->acceptable_options, + action->option_process, data->priv); + g_list_foreach(data->acceptable_options, remove_config_option, + data); + } + + return RCR_PLUS; +} + +static guint8 pppcp_process_configure_ack(struct pppcp_data *data, + struct pppcp_packet *packet) +{ + guint len; + GList *list; + struct ppp_option *acked_option; + guint i = 0; + struct pppcp_action *action = data->action; + + pppcp_trace(data); + + len = ntohs(packet->length) - CP_HEADER_SZ; + + /* if identifiers don't match, we should silently discard */ + if (packet->identifier != data->config_identifier) { + g_printerr("received an ack id %d, but config id is %d\n", + packet->identifier, data->config_identifier); + return 0; + } + + /* + * check each acked option. If it is what we requested, + * then we can apply these option values. + * + * XXX what if it isn't? Do this correctly -- for now + * we are just going to assume that all options matched + * and apply them. + */ + while (i < len) { + guint8 otype = packet->data[i]; + guint8 olen = packet->data[i + 1]; + acked_option = g_try_malloc0(olen); + if (acked_option == NULL) + break; + acked_option->type = otype; + acked_option->length = olen; + memcpy(acked_option->data, &packet->data[i + 2], olen - 2); + list = g_list_find_custom(data->config_options, + GUINT_TO_POINTER(acked_option->type), + is_option); + if (list) { + /* + * once we've applied the option, delete it from + * the config_options list. + */ + if (action->option_process) + action->option_process(acked_option, + data->priv); + data->config_options = + g_list_delete_link(data->config_options, list); + } else + g_printerr("oops -- found acked option %d we didn't request\n", acked_option->type); + g_free(acked_option); + + /* skip ahead to the next option */ + i += olen; + } + return RCA; +} + +static guint8 pppcp_process_configure_nak(struct pppcp_data *data, + struct pppcp_packet *packet) +{ + guint len; + GList *list; + struct ppp_option *naked_option; + struct ppp_option *config_option; + guint i = 0; + enum option_rval rval = OPTION_ERR; + struct pppcp_action *action = data->action; + + pppcp_trace(data); + + len = ntohs(packet->length) - CP_HEADER_SZ; + + /* if identifiers don't match, we should silently discard */ + if (packet->identifier != data->config_identifier) + return 0; + + /* + * check each unacceptable option. If it is acceptable, then + * we can resend the configure request with this value. we need + * to check the current config options to see if we need to + * modify a value there, or add a new option. + */ + while (i < len) { + guint8 otype = packet->data[i]; + guint8 olen = packet->data[i+1]; + naked_option = g_try_malloc0(olen); + if (naked_option == NULL) + break; + naked_option->type = otype; + naked_option->length = olen; + memcpy(naked_option->data, &packet->data[i + 2], olen - 2); + if (action->option_scan) + rval = action->option_scan(naked_option, data); + if (rval == OPTION_ACCEPT) { + /* + * check the current config options to see if they + * match. + */ + list = g_list_find_custom(data->config_options, + GUINT_TO_POINTER(otype), + is_option); + if (list) { + /* modify current option value to match */ + config_option = list->data; + + /* + * option values should match, otherwise + * we need to reallocate + */ + if ((config_option->length == + naked_option->length) && (olen - 2)) { + memcpy(config_option->data, + naked_option->data, + olen - 2); + } else { + /* XXX implement this */ + g_printerr("uh oh, option value doesn't match\n"); + } + g_free(naked_option); + } else { + /* add to list of config options */ + pppcp_add_config_option(data, naked_option); + } + } else { + /* XXX handle this correctly */ + g_printerr("oops, option wasn't acceptable\n"); + g_free(naked_option); + } + + /* skip ahead to the next option */ + i += olen; + } + return RCN; +} + +static guint8 pppcp_process_configure_reject(struct pppcp_data *data, + struct pppcp_packet *packet) +{ + /* + * make sure identifier matches that of last sent configure + * request + */ + if (packet->identifier == data->config_identifier) { + /* + * check to see which options were rejected + * Rejected options must be a subset of requested + * options. + * + * when a new configure-request is sent, we may + * not request any of these options be negotiated + */ + return RCN; + } + return 0; +} + +static guint8 pppcp_process_terminate_request(struct pppcp_data *data, + struct pppcp_packet *packet) +{ + return RTR; +} + +static guint8 pppcp_process_terminate_ack(struct pppcp_data *data, + struct pppcp_packet *packet) +{ + /* + * if we wind up using the data field for anything, then + * we'd want to check the identifier. + * even if the identifiers don't match, we still handle + * a terminate ack, as it is allowed to be unelicited + */ + return RTA; +} + +static guint8 pppcp_process_code_reject(struct pppcp_data *data, + struct pppcp_packet *packet) +{ + /* + * determine if the code reject is catastrophic or not. + * return RXJ_PLUS if this reject is acceptable, RXJ_MINUS if + * it is catastrophic. + */ + return RXJ_MINUS; +} + +static guint8 pppcp_process_protocol_reject(struct pppcp_data *data, + struct pppcp_packet *packet) +{ + /* + * determine if the protocol reject is catastrophic or not. + * return RXJ_PLUS if this reject is acceptable, RXJ_MINUS if + * it is catastrophic. + */ + return RXJ_MINUS; +} + +static guint8 pppcp_process_echo_request(struct pppcp_data *data, + struct pppcp_packet *packet) +{ + return RXR; +} + +static guint8 pppcp_process_echo_reply(struct pppcp_data *data, + struct pppcp_packet *packet) +{ + return 0; +} + +static guint8 pppcp_process_discard_request(struct pppcp_data *data, + struct pppcp_packet *packet) +{ + return 0; +} + +/* + * parse the packet and determine which event this packet caused + */ +void pppcp_process_packet(gpointer priv, guint8 *new_packet) +{ + struct pppcp_data *data = priv; + struct pppcp_packet *packet = (struct pppcp_packet *) new_packet; + guint8 event_type; + gpointer event_data = NULL; + guint data_len = 0; + + if (data == NULL) + return; + + /* check flags to see if we support this code */ + if (!(data->valid_codes & (1 << packet->code))) { + event_type = RUC; + } else + event_type = data->packet_ops[packet->code-1](data, packet); + if (event_type) { + data_len = ntohs(packet->length); + event_data = packet; + pppcp_generate_event(data, event_type, event_data, data_len); + } +} + +void pppcp_set_valid_codes(struct pppcp_data *data, guint16 codes) +{ + if (data == NULL) + return; + + data->valid_codes = codes; +} + +void pppcp_free(struct pppcp_data *data) +{ + if (data == NULL) + return; + + /* free event queue */ + if (!g_queue_is_empty(data->event_queue)) + g_queue_foreach(data->event_queue, (GFunc) g_free, NULL); + g_queue_free(data->event_queue); + + /* remove all config options */ + pppcp_clear_options(data); + + /* free self */ + g_free(data); +} + +struct pppcp_data *pppcp_new(GAtPPP *ppp, guint16 proto, + gpointer priv) +{ + struct pppcp_data *data; + + data = g_try_malloc0(sizeof(struct pppcp_data)); + if (!data) + return NULL; + + data->state = INITIAL; + data->restart_interval = INITIAL_RESTART_TIMEOUT; + data->max_terminate = MAX_TERMINATE; + data->max_configure = MAX_CONFIGURE; + data->max_failure = MAX_FAILURE; + data->event_queue = g_queue_new(); + data->identifier = 0; + data->ppp = ppp; + data->proto = proto; + data->priv = priv; + + /* setup func ptrs for processing packet by pppcp code */ + data->packet_ops[CONFIGURE_REQUEST - 1] = + pppcp_process_configure_request; + data->packet_ops[CONFIGURE_ACK - 1] = pppcp_process_configure_ack; + data->packet_ops[CONFIGURE_NAK - 1] = pppcp_process_configure_nak; + data->packet_ops[CONFIGURE_REJECT - 1] = pppcp_process_configure_reject; + data->packet_ops[TERMINATE_REQUEST - 1] = + pppcp_process_terminate_request; + data->packet_ops[TERMINATE_ACK - 1] = pppcp_process_terminate_ack; + data->packet_ops[CODE_REJECT - 1] = pppcp_process_code_reject; + data->packet_ops[PROTOCOL_REJECT - 1] = pppcp_process_protocol_reject; + data->packet_ops[ECHO_REQUEST - 1] = pppcp_process_echo_request; + data->packet_ops[ECHO_REPLY - 1] = pppcp_process_echo_reply; + data->packet_ops[DISCARD_REQUEST - 1] = pppcp_process_discard_request; + + /* setup func ptrs for handling events by event type */ + data->event_ops[UP] = pppcp_up_event; + data->event_ops[DOWN] = pppcp_down_event; + data->event_ops[OPEN] = pppcp_open_event; + data->event_ops[CLOSE] = pppcp_close_event; + data->event_ops[TO_PLUS] = pppcp_to_plus_event; + data->event_ops[TO_MINUS] = pppcp_to_minus_event; + data->event_ops[RCR_PLUS] = pppcp_rcr_plus_event; + data->event_ops[RCR_MINUS] = pppcp_rcr_minus_event; + data->event_ops[RCA] = pppcp_rca_event; + data->event_ops[RCN] = pppcp_rcn_event; + data->event_ops[RTR] = pppcp_rtr_event; + data->event_ops[RTA] = pppcp_rta_event; + data->event_ops[RUC] = pppcp_ruc_event; + data->event_ops[RXJ_PLUS] = pppcp_rxj_plus_event; + data->event_ops[RXJ_MINUS] = pppcp_rxj_minus_event; + data->event_ops[RXR] = pppcp_rxr_event; + + return data; +} diff --git a/gatchat/ppp_cp.h b/gatchat/ppp_cp.h new file mode 100644 index 0000000..875d02f --- /dev/null +++ b/gatchat/ppp_cp.h @@ -0,0 +1,139 @@ +/* + * + * PPP library with GLib integration + * + * Copyright (C) 2009-2010 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct pppcp_data; + +enum pppcp_code { + CONFIGURE_REQUEST = 1, + CONFIGURE_ACK, + CONFIGURE_NAK, + CONFIGURE_REJECT, + TERMINATE_REQUEST, + TERMINATE_ACK, + CODE_REJECT, + PROTOCOL_REJECT, + ECHO_REQUEST, + ECHO_REPLY, + DISCARD_REQUEST +}; + +enum pppcp_event_type { + UP, + DOWN, + OPEN, + CLOSE, + TO_PLUS, + TO_MINUS, + RCR_PLUS, + RCR_MINUS, + RCA, + RCN, + RTR, + RTA, + RUC, + RXJ_PLUS, + RXJ_MINUS, + RXR, +}; + +enum pppcp_state { + INITIAL, + STARTING, + CLOSED, + STOPPED, + CLOSING, + STOPPING, + REQSENT, + ACKRCVD, + ACKSENT, + OPENED, +}; + +/* option format */ +struct ppp_option { + guint8 type; + guint8 length; + guint8 data[0]; +}; + +enum option_rval { + OPTION_ACCEPT, + OPTION_REJECT, + OPTION_NAK, + OPTION_ERR, +}; + +struct pppcp_action { + void (*this_layer_up)(struct pppcp_data *data); + void (*this_layer_down)(struct pppcp_data *data); + void (*this_layer_started)(struct pppcp_data *data); + void (*this_layer_finished)(struct pppcp_data *data); + enum option_rval (*option_scan)(struct ppp_option *option, + gpointer user_data); + void (*option_process)(gpointer option, gpointer user_data); +}; + +struct pppcp_packet { + guint8 code; + guint8 identifier; + guint16 length; + guint8 data[0]; +} __attribute__((packed)); + +struct pppcp_data { + enum pppcp_state state; + guint restart_timer; + guint restart_counter; + guint restart_interval; + guint max_terminate; + guint max_configure; + guint max_failure; + guint32 magic_number; + GQueue *event_queue; + GList *config_options; + GList *acceptable_options; + GList *unacceptable_options; + GList *rejected_options; + GList *applied_options; + GAtPPP *ppp; + guint8 identifier; /* don't think I need this now */ + guint8 config_identifier; + guint8 terminate_identifier; + guint8 reject_identifier; + struct pppcp_action *action; + guint16 valid_codes; + guint8 (*packet_ops[11])(struct pppcp_data *data, + struct pppcp_packet *packet); + void (*event_ops[16])(struct pppcp_data *data, guint8 *packet, + guint length); + gpointer priv; + guint16 proto; +}; + +struct pppcp_data *pppcp_new(GAtPPP *ppp, guint16 proto, gpointer priv); +void pppcp_free(struct pppcp_data *data); +void pppcp_add_config_option(struct pppcp_data *data, + struct ppp_option *option); +void pppcp_set_valid_codes(struct pppcp_data *data, guint16 codes); +void pppcp_generate_event(struct pppcp_data *data, + enum pppcp_event_type event_type, + gpointer event_data, guint data_len); +void pppcp_process_packet(gpointer priv, guint8 *new_packet); -- 2.7.4