ofono/gatchat/ppp_cp.c

1361 lines
32 KiB
C
Raw Normal View History

/*
*
* 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 <config.h>
#endif
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <termios.h>
#include <glib.h>
#include <arpa/inet.h>
#include "gatppp.h"
#include "ppp.h"
2010-04-04 05:25:09 +00:00
#define pppcp_trace(p) do { \
char *str = g_strdup_printf("%s: %s: current state %d:%s", \
p->driver->name, __FUNCTION__, \
2010-04-04 05:25:09 +00:00
p->state, pppcp_state_strings[p->state]); \
ppp_debug(p->ppp, str); \
g_free(str); \
} while (0);
2010-04-04 05:32:27 +00:00
#define PPP_HEADROOM 2
#define pppcp_to_ppp_packet(p) \
(((guint8 *) p) - PPP_HEADROOM)
#define INITIAL_RESTART_TIMEOUT 3 /* restart interval in seconds */
#define MAX_TERMINATE 2
#define MAX_CONFIGURE 10
#define MAX_FAILURE 5
#define CP_HEADER_SZ 4
enum pppcp_state {
INITIAL = 0,
STARTING = 1,
CLOSED = 2,
STOPPED = 3,
CLOSING = 4,
STOPPING = 5,
REQSENT = 6,
ACKRCVD = 7,
ACKSENT = 8,
OPENED = 9,
};
enum actions {
INV = 0x10,
IRC = 0x20,
ZRC = 0x40,
TLU = 0x100,
TLD = 0x200,
TLS = 0x400,
TLF = 0x800,
SCR = 0x1000,
SCA = 0x2000,
SCN = 0x4000,
STR = 0x8000,
STA = 0x10000,
SCJ = 0x20000,
SER = 0x40000,
};
static const char *pppcp_state_strings[] = {
"INITIAL", "STARTING", "CLOSED", "STOPPED", "CLOSING", "STOPPING",
"REQSENT", "ACKRCVD", "ACKSENT", "OPENED"
};
static const char *pppcp_event_strings[] = {
"Up", "Down", "Open", "Close", "TO+", "TO-", "RCR+", "RCR-",
"RCA", "RCN", "RTR", "RTA", "RUC", "RXJ+", "RXJ-", "RXR"
};
/*
* Transition table straight from RFC 1661 Section 4.1
* Y coordinate is the events, while X coordinate is the state
*
* Magic of bitwise operations allows the table to describe all state
* transitions defined in the specification
*/
static int cp_transitions[16][10] = {
/* Up */
{ 2, IRC|SCR|6, INV, INV, INV, INV, INV, INV, INV, INV },
/* Down */
{ INV, INV, 0, TLS|1, 0, 1, 1, 1, 1, TLD|1 },
/* Open */
{ TLS|1, 1, IRC|SCR|6, 3, 5, 5, 6, 7, 8, 9 },
/* Close */
{ 0, TLF|0, 2, 2, 4, 4, IRC|STR|4, IRC|STR|4, IRC|STR|4, TLD|IRC|STR|4 },
/* TO+ */
{ INV, INV, INV, INV, STR|4, STR|5, SCR|6, SCR|6, SCR|8, INV },
/* TO- */
{ INV, INV, INV, INV, TLF|2, TLF|3, TLF|3, TLF|3, TLF|3, INV },
/* RCR+ */
{ INV, INV, STA|2, IRC|SCR|SCA|8, 4, 5, SCA|8, SCA|TLU|9, SCA|8, TLD|SCR|SCA|8 },
/* RCR- */
{ INV, INV, STA|2, IRC|SCR|SCN|6, 4, 5, SCN|6, SCN|7, SCN|6, TLD|SCR|SCN|6 },
/* RCA */
{ INV, INV, STA|2, STA|3, 4, 5, IRC|7, SCR|6, IRC|TLU|9, TLD|SCR|6 },
/* RCN */
{ INV, INV, STA|2, STA|3, 4, 5, IRC|SCR|6, SCR|6, IRC|SCR|8, TLD|SCR|6 },
/* RTR */
{ INV, INV, STA|2, STA|3, STA|4, STA|5, STA|6, STA|6, STA|6, TLD|ZRC|STA|5 },
/* RTA */
{ INV, INV, 2, 3, TLF|2, TLF|3, 6, 6, 8, TLD|SCR|6 },
/* RUC */
{ INV, INV, SCJ|2, SCJ|3, SCJ|4, SCJ|5, SCJ|6, SCJ|7, SCJ|8, SCJ|9 },
/* RXJ+ */
{ INV, INV, 2, 3, 4, 5, 6, 6, 8, 9 },
/* RXJ- */
{ INV, INV, TLF|2, TLF|3, TLF|2, TLF|3, TLF|3, TLF|3, TLF|3, TLD|IRC|STR|5 },
/* RXR */
{ INV, INV, 2, 3, 4, 5, 6, 7, 8, SER|9 },
};
enum pppcp_event_type {
UP = 0,
DOWN = 1,
OPEN = 2,
CLOSE = 3,
TO_PLUS = 4,
TO_MINUS = 5,
RCR_PLUS = 6,
RCR_MINUS = 7,
RCA = 8,
RCN = 9,
RTR = 10,
RTA = 11,
RUC = 12,
RXJ_PLUS = 13,
RXJ_MINUS = 14,
RXR = 15,
};
2010-04-07 21:10:15 +00:00
struct pppcp_timer_data {
struct pppcp_data *data;
guint restart_counter;
guint restart_interval;
guint max_counter;
guint restart_timer;
};
struct pppcp_data {
unsigned char state;
struct pppcp_timer_data config_timer_data;
struct pppcp_timer_data terminate_timer_data;
guint max_failure;
guint failure_counter;
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;
const struct pppcp_proto *driver;
gpointer priv;
};
static void pppcp_generate_event(struct pppcp_data *data,
enum pppcp_event_type event_type,
guint8 *packet, guint len);
static void pppcp_packet_free(struct pppcp_packet *packet)
{
g_free(pppcp_to_ppp_packet(packet));
}
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->driver->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;
}
2010-04-07 20:40:02 +00:00
void ppp_option_iter_init(struct ppp_option_iter *iter,
const struct pppcp_packet *packet)
{
iter->max = ntohs(packet->length) - CP_HEADER_SZ;
iter->pdata = packet->data;
iter->pos = 0;
iter->type = 0;
iter->len = 0;
iter->option_data = NULL;
}
gboolean ppp_option_iter_next(struct ppp_option_iter *iter)
{
const guint8 *cur = iter->pdata + iter->pos;
const guint8 *end = iter->pdata + iter->max;
if (cur + 1 > end)
return FALSE;
if (cur + cur[1] > end)
return FALSE;
iter->type = cur[0];
iter->len = cur[1] - 2;
iter->option_data = cur + 2;
iter->pdata += cur[1];
return TRUE;
}
guint8 ppp_option_iter_get_type(struct ppp_option_iter *iter)
{
return iter->type;
}
guint8 ppp_option_iter_get_length(struct ppp_option_iter *iter)
{
return iter->len;
}
const guint8 *ppp_option_iter_get_data(struct ppp_option_iter *iter)
{
return iter->option_data;
}
static gboolean pppcp_timeout(gpointer user_data)
{
struct pppcp_timer_data *timer_data = user_data;
pppcp_trace(timer_data->data);
timer_data->restart_timer = 0;
if (timer_data->restart_counter)
pppcp_generate_event(timer_data->data, TO_PLUS, NULL, 0);
else
pppcp_generate_event(timer_data->data, TO_MINUS, NULL, 0);
return FALSE;
}
static void pppcp_start_timer(struct pppcp_timer_data *timer_data)
{
if (timer_data->restart_timer)
return;
timer_data->restart_timer =
g_timeout_add_seconds(timer_data->restart_interval,
pppcp_timeout, timer_data);
}
static void pppcp_stop_timer(struct pppcp_timer_data *timer_data)
{
if (timer_data->restart_timer) {
g_source_remove(timer_data->restart_timer);
timer_data->restart_timer = 0;
}
}
static gboolean is_first_request(struct pppcp_timer_data *timer_data)
{
return (timer_data->restart_counter == timer_data->max_counter);
}
/* 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)
{
if (data->driver->this_layer_up)
data->driver->this_layer_up(data);
}
static void pppcp_this_layer_down(struct pppcp_data *data)
{
if (data->driver->this_layer_down)
data->driver->this_layer_down(data);
}
static void pppcp_this_layer_started(struct pppcp_data *data)
{
if (data->driver->this_layer_started)
data->driver->this_layer_started(data);
}
static void pppcp_this_layer_finished(struct pppcp_data *data)
{
pppcp_trace(data);
if (data->driver->this_layer_finished)
data->driver->this_layer_finished(data);
}
static void pppcp_clear_options(struct pppcp_data *data)
{
g_list_foreach(data->acceptable_options, (GFunc) g_free, NULL);
g_list_foreach(data->unacceptable_options, (GFunc) g_free, NULL);
g_list_foreach(data->rejected_options, (GFunc) g_free, 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;
}
static void pppcp_free_options(struct pppcp_data *data)
{
/* remove all config options */
pppcp_clear_options(data);
/* remove default option list */
g_list_foreach(data->config_options, (GFunc) g_free, NULL);
g_list_free(data->config_options);
}
/*
* 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_timer_data *timer_data)
{
struct pppcp_data *data = timer_data->data;
pppcp_trace(data);
pppcp_clear_options(data);
timer_data->restart_counter = timer_data->max_counter;
}
/*
* set restart counter to zero
*/
static void pppcp_zero_restart_count(struct pppcp_timer_data *timer_data)
{
timer_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;
}
static void reject_option(gpointer data, gpointer user_data)
{
struct ppp_option *option = data;
struct pppcp_data *pppcp = user_data;
pppcp->rejected_options =
g_list_append(pppcp->rejected_options, option);
}
static void print_option(gpointer data, gpointer user_data)
{
struct ppp_option *option = data;
struct pppcp_data *pppcp = user_data;
g_print("%s: option %d len %d (%s)", pppcp->driver->name, option->type,
option->length,
pppcp->driver->option_strings[option->type]);
if (option->length > 2) {
int i;
for (i = 0; i < option->length - 2; i++)
g_print(" %02x", option->data[i]);
}
g_print("\n");
}
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;
struct pppcp_timer_data *timer_data = &data->config_timer_data;
pppcp_trace(data);
g_list_foreach(data->config_options, print_option, 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, PPPCP_CODE_TYPE_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 (is_first_request(timer_data))
data->config_identifier =
new_identity(data, data->config_identifier);
packet->identifier = data->config_identifier;
ppp_transmit(data->ppp, pppcp_to_ppp_packet(packet),
ntohs(packet->length));
pppcp_packet_free(packet);
/* start timer for retransmission */
timer_data->restart_counter--;
pppcp_start_timer(timer_data);
}
/*
* 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);
data->failure_counter = 0;
g_list_foreach(data->acceptable_options, print_option, data);
/* subtract for header. */
len = ntohs(pppcp_header->length) - sizeof(*packet);
packet = pppcp_packet_new(data, PPPCP_CODE_TYPE_CONFIGURE_ACK, len);
/* copy the applied options in. */
odata = packet->data;
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(packet),
ntohs(packet->length));
pppcp_packet_free(packet);
}
/*
* 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;
guint8 *odata;
/*
* if we have exceeded our Max-Failure counter, we need
* to convert all packets to Configure-Reject
*/
if (data->failure_counter >= data->max_failure) {
g_list_foreach(data->unacceptable_options, reject_option, data);
g_list_free(data->unacceptable_options);
data->unacceptable_options = NULL;
}
/* if we have any rejected options, send a config-reject */
if (g_list_length(data->rejected_options)) {
pppcp_trace(data);
g_list_foreach(data->rejected_options, print_option, data);
/* figure out how much space to allocate for options */
olength = 0;
g_list_foreach(data->rejected_options, get_option_length,
&olength);
packet = pppcp_packet_new(data,
PPPCP_CODE_TYPE_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(packet),
ntohs(packet->length));
pppcp_packet_free(packet);
}
/* if we have any unacceptable options, send a config-nak */
if (g_list_length(data->unacceptable_options)) {
pppcp_trace(data);
g_list_foreach(data->unacceptable_options, print_option, data);
/* figure out how much space to allocate for options */
olength = 0;
g_list_foreach(data->unacceptable_options, get_option_length,
&olength);
packet = pppcp_packet_new(data, PPPCP_CODE_TYPE_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(packet),
ntohs(packet->length));
pppcp_packet_free(packet);
data->failure_counter++;
}
}
/*
* 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;
struct pppcp_timer_data *timer_data = &data->terminate_timer_data;
pppcp_trace(data);
/*
* the data field can be used by the sender (us).
* leave this empty for now.
*/
packet = pppcp_packet_new(data, PPPCP_CODE_TYPE_TERMINATE_REQUEST, 0);
/*
* Is this a retransmission? If so, do not change
* the identifier. If not, we need a fresh identity.
*/
if (is_first_request(timer_data))
data->terminate_identifier =
new_identity(data, data->terminate_identifier);
packet->identifier = data->terminate_identifier;
ppp_transmit(data->ppp, pppcp_to_ppp_packet(packet),
ntohs(packet->length));
pppcp_packet_free(packet);
timer_data->restart_counter--;
pppcp_start_timer(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;
pppcp_trace(data);
packet = pppcp_packet_new(data, PPPCP_CODE_TYPE_TERMINATE_ACK, 0);
/* match identifier of the request */
packet->identifier = pppcp_header->identifier;
ppp_transmit(data->ppp, pppcp_to_ppp_packet(packet),
ntohs(pppcp_header->length));
pppcp_packet_free(packet);
}
/*
* 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;
struct pppcp_packet *old_packet =
(struct pppcp_packet *) rejected_packet;
pppcp_trace(data);
packet = pppcp_packet_new(data, PPPCP_CODE_TYPE_CODE_REJECT,
ntohs(old_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(packet),
ntohs(packet->length));
pppcp_packet_free(packet);
}
/*
* 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, PPPCP_CODE_TYPE_ECHO_REPLY, 4);
/*
* match identifier of request
*/
packet->identifier = header->identifier;
/* magic number? */
ppp_transmit(data->ppp, pppcp_to_ppp_packet(packet),
ntohs(packet->length));
pppcp_packet_free(packet);
}
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:
pppcp_stop_timer(&data->config_timer_data);
pppcp_stop_timer(&data->terminate_timer_data);
break;
case CLOSING:
case STOPPING:
case REQSENT:
case ACKRCVD:
case ACKSENT:
break;
}
data->state = new_state;
}
/*
* send the event handler a new event to process
*/
static void pppcp_generate_event(struct pppcp_data *data,
enum pppcp_event_type event_type,
guint8 *packet, guint len)
{
int actions;
unsigned char new_state;
if (event_type > RXR)
goto error;
pppcp_trace(data);
actions = cp_transitions[event_type][data->state];
new_state = actions & 0xf;
g_print("event: %d (%s), action: %x, new_state: %d (%s)\n",
event_type, pppcp_event_strings[event_type],
actions, new_state, pppcp_state_strings[new_state]);
if (actions & INV)
goto error;
if (actions & TLD)
pppcp_this_layer_down(data);
if (actions & TLF)
pppcp_this_layer_finished(data);
if (actions & IRC) {
struct pppcp_timer_data *timer_data;
if (new_state == CLOSING || new_state == STOPPING)
timer_data = &data->terminate_timer_data;
else
timer_data = &data->config_timer_data;
pppcp_initialize_restart_count(timer_data);
} else if (actions & ZRC)
pppcp_zero_restart_count(&data->terminate_timer_data);
if (actions & SCR)
pppcp_send_configure_request(data);
if (actions & SCA)
pppcp_send_configure_ack(data, packet);
else if (actions & SCN)
pppcp_send_configure_nak(data, packet);
if (actions & STR)
pppcp_send_terminate_request(data);
else if (actions & STA)
pppcp_send_terminate_ack(data, packet);
if (actions & SCJ)
pppcp_send_code_reject(data, packet);
if (actions & SER)
pppcp_send_echo_reply(data, packet);
if (actions & TLU)
pppcp_this_layer_up(data);
pppcp_transition_state(new_state, data);
/*
* The logic elsewhere generates the UP events when this is
* signaled. So we must call this last
*/
if (actions & TLS)
pppcp_this_layer_started(data);
return;
error:
pppcp_illegal_event(data->state, event_type);
}
void pppcp_signal_open(struct pppcp_data *data)
{
pppcp_generate_event(data, OPEN, NULL, 0);
}
void pppcp_signal_close(struct pppcp_data *data)
{
pppcp_generate_event(data, CLOSE, NULL, 0);
}
void pppcp_signal_up(struct pppcp_data *data)
{
pppcp_generate_event(data, UP, NULL, 0);
}
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;
guint type = config->type;
struct ppp_option *option;
GList *list;
/*
* determine whether this config option is in the
* acceptable options list
*/
list = g_list_find_custom(data->acceptable_options,
GUINT_TO_POINTER(type), is_option);
if (list)
return;
/*
* 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;
guint type = config->type;
GList *list;
/*
* determine whether this config option is in the
* applied options list
*/
list = g_list_find_custom(data->config_options,
GUINT_TO_POINTER(type), is_option);
if (!list)
return;
g_free(list->data);
data->config_options = g_list_delete_link(data->config_options, list);
}
static struct ppp_option *extract_ppp_option(struct pppcp_data *data,
guint8 *packet_data)
{
struct ppp_option *option;
guint8 otype = packet_data[0];
guint8 olen = packet_data[1];
option = g_try_malloc0(olen);
if (option == NULL)
return NULL;
option->type = otype;
option->length = olen;
memcpy(option->data, &packet_data[2], olen-2);
print_option(option, data);
return option;
}
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;
pppcp_trace(data);
len = ntohs(packet->length) - CP_HEADER_SZ;
/*
* check the options.
*/
while (i < len) {
option = extract_ppp_option(data, &packet->data[i]);
if (option == NULL)
break;
/* skip ahead to the next option */
i += option->length;
if (data->driver->option_scan)
rval = data->driver->option_scan(data, option);
else
rval = OPTION_REJECT;
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;
}
}
/* 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 (data->driver->option_process) {
GList *l;
for (l = data->acceptable_options; l; l = l->next)
data->driver->option_process(data, l->data);
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;
guint i;
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;
}
/*
* First we must sanity check that all config options acked are
* equal to the config options sent and are in the same order.
* If this is not the case, then silently drop the packet
*/
for (i = 0, list = data->config_options; list; list = list->next) {
struct ppp_option *sent_option = list->data;
if (sent_option->type != packet->data[i])
return 0;
if (sent_option->length != packet->data[i + 1])
return 0;
if (memcmp(sent_option->data, packet->data + i + 2,
sent_option->length) != 0)
return 0;
i += packet->data[i + 1];
}
/* Otherwise, apply local options */
if (data->driver->rca)
data->driver->rca(data, packet);
g_list_foreach(data->config_options, (GFunc)g_free, NULL);
g_list_free(data->config_options);
data->config_options = NULL;
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;
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) {
naked_option = extract_ppp_option(data, &packet->data[i]);
if (naked_option == NULL)
break;
/* skip ahead to the next option */
i += naked_option->length;
if (data->driver->option_scan)
rval = data->driver->option_scan(data, naked_option);
else
rval = OPTION_REJECT;
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((guint) naked_option->type),
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) &&
(naked_option - 2)) {
memcpy(config_option->data,
naked_option->data,
naked_option->length - 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);
}
}
return RCN;
}
static guint8 pppcp_process_configure_reject(struct pppcp_data *data,
struct pppcp_packet *packet)
{
guint len;
GList *list;
struct ppp_option *rejected_option;
guint i = 0;
len = ntohs(packet->length) - CP_HEADER_SZ;
/*
* make sure identifier matches that of last sent configure
* request
*/
if (packet->identifier != data->config_identifier)
return 0;
/*
* 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
*/
while (i < len) {
rejected_option = extract_ppp_option(data, &packet->data[i]);
if (rejected_option == NULL)
break;
/* skip ahead to the next option */
i += rejected_option->length;
/* find this option in our config options list */
list = g_list_find_custom(data->config_options,
GUINT_TO_POINTER((guint) rejected_option->type),
is_option);
if (list) {
/* delete this config option */
g_free(list->data);
data->config_options =
g_list_delete_link(data->config_options, list);
}
g_free(rejected_option);
}
return RCN;
}
static guint8 pppcp_process_terminate_request(struct pppcp_data *data,
struct pppcp_packet *packet)
{
pppcp_trace(data);
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
*/
pppcp_trace(data);
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.
*
* for now we always return RXJ_MINUS. Any code
* reject will be catastrophic, since we only support the
* bare minimum number of codes necessary to function.
*/
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.
*
* for now we always return RXJ_MINUS. Any protocol
* reject will be catastrophic, since we only support the
* bare minimum number of protocols necessary to function.
*/
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;
}
static guint8 (*packet_ops[11])(struct pppcp_data *data,
struct pppcp_packet *packet) = {
pppcp_process_configure_request,
pppcp_process_configure_ack,
pppcp_process_configure_nak,
pppcp_process_configure_reject,
pppcp_process_terminate_request,
pppcp_process_terminate_ack,
pppcp_process_code_reject,
pppcp_process_protocol_reject,
pppcp_process_echo_request,
pppcp_process_echo_reply,
pppcp_process_discard_request,
};
void pppcp_send_protocol_reject(struct pppcp_data *data,
guint8 *rejected_packet, gsize len)
{
struct pppcp_packet *packet;
struct ppp_header *ppp_packet = (struct ppp_header *) rejected_packet;
pppcp_trace(data);
/*
* Protocol-Reject can only be sent when we are in
* the OPENED state. If in any other state, silently discard.
*/
if (data->state != OPENED) {
g_free(ppp_packet);
return;
}
/*
* info should contain the old packet info, plus the 16bit
* protocol number we are rejecting.
*/
packet = pppcp_packet_new(data, PPPCP_CODE_TYPE_PROTOCOL_REJECT, len);
/*
* Identifier must be changed for each Protocol-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(packet),
ntohs(packet->length));
pppcp_packet_free(packet);
}
/*
* 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->driver->supported_codes & (1 << packet->code)))
event_type = RUC;
else
event_type = 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_free(struct pppcp_data *data)
{
if (data == NULL)
return;
/* remove all config options */
pppcp_free_options(data);
/* free self */
g_free(data);
}
void pppcp_set_data(struct pppcp_data *pppcp, gpointer data)
{
pppcp->priv = data;
}
gpointer pppcp_get_data(struct pppcp_data *pppcp)
{
return pppcp->priv;
}
2010-04-05 21:44:00 +00:00
GAtPPP *pppcp_get_ppp(struct pppcp_data *pppcp)
{
return pppcp->ppp;
}
struct pppcp_data *pppcp_new(GAtPPP *ppp, const struct pppcp_proto *proto)
{
struct pppcp_data *data;
data = g_try_malloc0(sizeof(struct pppcp_data));
if (!data)
return NULL;
data->state = INITIAL;
data->config_timer_data.restart_interval = INITIAL_RESTART_TIMEOUT;
data->terminate_timer_data.restart_interval = INITIAL_RESTART_TIMEOUT;
data->config_timer_data.max_counter = MAX_CONFIGURE;
data->terminate_timer_data.max_counter = MAX_TERMINATE;
data->config_timer_data.data = data;
data->terminate_timer_data.data = data;
data->max_failure = MAX_FAILURE;
data->identifier = 0;
data->ppp = ppp;
return data;
}