ofono/gatchat/ppp_lcp.c

447 lines
9.6 KiB
C

/*
*
* PPP library with GLib integration
*
* Copyright (C) 2009-2011 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"
#define LCP_SUPPORTED_CODES ((1 << PPPCP_CODE_TYPE_CONFIGURE_REQUEST) | \
(1 << PPPCP_CODE_TYPE_CONFIGURE_ACK) | \
(1 << PPPCP_CODE_TYPE_CONFIGURE_NAK) | \
(1 << PPPCP_CODE_TYPE_CONFIGURE_REJECT) | \
(1 << PPPCP_CODE_TYPE_TERMINATE_REQUEST) | \
(1 << PPPCP_CODE_TYPE_TERMINATE_ACK) | \
(1 << PPPCP_CODE_TYPE_CODE_REJECT) | \
(1 << PPPCP_CODE_TYPE_PROTOCOL_REJECT) | \
(1 << PPPCP_CODE_TYPE_ECHO_REQUEST) | \
(1 << PPPCP_CODE_TYPE_ECHO_REPLY) | \
(1 << PPPCP_CODE_TYPE_DISCARD_REQUEST))
enum lcp_options {
RESERVED = 0,
MRU = 1,
ACCM = 2,
AUTH_PROTO = 3,
QUAL_PROTO = 4,
MAGIC_NUMBER = 5,
DEPRECATED_QUAL_PROTO = 6,
PFC = 7,
ACFC = 8,
};
/* Maximum size of all options, we only ever request ACCM, MRU, ACFC and PFC */
#define MAX_CONFIG_OPTION_SIZE 14
#define REQ_OPTION_ACCM 0x1
#define REQ_OPTION_MRU 0x2
#define REQ_OPTION_ACFC 0x4
#define REQ_OPTION_PFC 0x8
struct lcp_data {
guint8 options[MAX_CONFIG_OPTION_SIZE];
guint16 options_len;
guint8 req_options;
guint32 accm; /* ACCM value */
guint16 mru;
};
static void lcp_generate_config_options(struct lcp_data *lcp)
{
guint16 len = 0;
if (lcp->req_options & REQ_OPTION_ACCM) {
guint32 accm;
accm = htonl(lcp->accm);
lcp->options[len] = ACCM;
lcp->options[len + 1] = 6;
memcpy(lcp->options + len + 2, &accm, sizeof(accm));
len += 6;
}
if (lcp->req_options & REQ_OPTION_MRU) {
guint16 mru;
mru = htons(lcp->mru);
lcp->options[len] = MRU;
lcp->options[len + 1] = 4;
memcpy(lcp->options + len + 2, &mru, sizeof(mru));
len += 4;
}
if (lcp->req_options & REQ_OPTION_ACFC) {
lcp->options[len] = ACFC;
lcp->options[len + 1] = 2;
len += 2;
}
if (lcp->req_options & REQ_OPTION_PFC) {
lcp->options[len] = PFC;
lcp->options[len + 1] = 2;
len += 2;
}
lcp->options_len = len;
}
static void lcp_reset_config_options(struct lcp_data *lcp)
{
/* Using RX ACCM = 0 instead of the default ACCM */
lcp->accm = 0;
lcp->req_options |= REQ_OPTION_ACCM;
lcp_generate_config_options(lcp);
}
/*
* signal the Up event to the NCP
*/
static void lcp_up(struct pppcp_data *pppcp)
{
ppp_lcp_up_notify(pppcp_get_ppp(pppcp));
}
/*
* signal the Down event to the NCP
*/
static void lcp_down(struct pppcp_data *pppcp)
{
struct lcp_data *lcp = pppcp_get_data(pppcp);
lcp_reset_config_options(lcp);
pppcp_set_local_options(pppcp, lcp->options, lcp->options_len);
ppp_lcp_down_notify(pppcp_get_ppp(pppcp));
}
/*
* Indicate that the lower layer is not needed
* Should trigger Down event
*/
static void lcp_finished(struct pppcp_data *pppcp)
{
ppp_lcp_finished_notify(pppcp_get_ppp(pppcp));
}
static void lcp_rca(struct pppcp_data *pppcp, const struct pppcp_packet *packet)
{
struct ppp_option_iter iter;
ppp_option_iter_init(&iter, packet);
while (ppp_option_iter_next(&iter) == TRUE) {
const guint8 *data = ppp_option_iter_get_data(&iter);
switch (ppp_option_iter_get_type(&iter)) {
case ACCM:
/*
* RFC1662 Section 7.1
* The Configuration Option is used to inform the peer
* which control characters MUST remain mapped when
* the peer sends them.
*/
ppp_set_recv_accm(pppcp_get_ppp(pppcp),
get_host_long(data));
break;
default:
break;
}
}
}
static void lcp_rcn_nak(struct pppcp_data *pppcp,
const struct pppcp_packet *packet)
{
struct lcp_data *lcp = pppcp_get_data(pppcp);
struct ppp_option_iter iter;
ppp_option_iter_init(&iter, packet);
while (ppp_option_iter_next(&iter) == TRUE) {
const guint8 *data = ppp_option_iter_get_data(&iter);
switch (ppp_option_iter_get_type(&iter)) {
case MRU:
{
guint16 mru = get_host_short(data);
if (mru < 2048) {
lcp->mru = get_host_short(data);
lcp->req_options |= REQ_OPTION_MRU;
}
break;
}
default:
break;
}
}
lcp_generate_config_options(lcp);
pppcp_set_local_options(pppcp, lcp->options, lcp->options_len);
}
static void lcp_rcn_rej(struct pppcp_data *pppcp,
const struct pppcp_packet *packet)
{
}
static enum rcr_result lcp_rcr(struct pppcp_data *pppcp,
const struct pppcp_packet *packet,
guint8 **new_options, guint16 *new_len)
{
GAtPPP *ppp = pppcp_get_ppp(pppcp);
struct ppp_option_iter iter;
ppp_option_iter_init(&iter, packet);
while (ppp_option_iter_next(&iter) == TRUE) {
switch (ppp_option_iter_get_type(&iter)) {
case AUTH_PROTO:
{
const guint8 *option_data =
ppp_option_iter_get_data(&iter);
guint16 proto = get_host_short(option_data);
guint8 method = option_data[2];
guint8 *option;
switch (g_at_ppp_get_auth_method(ppp)) {
case G_AT_PPP_AUTH_METHOD_CHAP:
if (proto == CHAP_PROTOCOL && method == MD5)
break;
/*
* Try to suggest CHAP/MD5.
* Just reject if we run out of memory.
*/
option = g_try_malloc0(5);
if (option == NULL)
return RCR_REJECT;
option[0] = AUTH_PROTO;
option[1] = 5;
put_network_short(&option[2], CHAP_PROTOCOL);
option[4] = MD5;
*new_options = option;
*new_len = 5;
return RCR_NAK;
case G_AT_PPP_AUTH_METHOD_PAP:
if (proto == PAP_PROTOCOL)
break;
/*
* Try to suggest PAP.
* Just reject if we run out of memory.
*/
option = g_try_malloc0(4);
if (option == NULL)
return RCR_REJECT;
option[0] = AUTH_PROTO;
option[1] = 4;
put_network_short(&option[2], PAP_PROTOCOL);
*new_options = option;
*new_len = 4;
return RCR_NAK;
case G_AT_PPP_AUTH_METHOD_NONE:
return RCR_REJECT;
}
break;
}
case ACCM:
case PFC:
case ACFC:
case MRU:
break;
case MAGIC_NUMBER:
{
guint32 magic =
get_host_long(ppp_option_iter_get_data(&iter));
if (magic == 0)
return RCR_REJECT;
break;
}
default:
return RCR_REJECT;
}
}
/* All options were found acceptable, apply them here and return */
ppp_option_iter_init(&iter, packet);
while (ppp_option_iter_next(&iter) == TRUE) {
switch (ppp_option_iter_get_type(&iter)) {
case ACCM:
/*
* RFC1662 Section 7.1
* The Configuration Option is used to inform the peer
* which control characters MUST remain mapped when
* the peer sends them.
*/
ppp_set_xmit_accm(ppp,
get_host_long(ppp_option_iter_get_data(&iter)));
break;
case AUTH_PROTO:
ppp_set_auth(ppp, ppp_option_iter_get_data(&iter));
break;
case MRU:
ppp_set_mtu(ppp, ppp_option_iter_get_data(&iter));
break;
case MAGIC_NUMBER:
/* don't care */
break;
case PFC:
{
struct lcp_data *lcp = pppcp_get_data(pppcp);
if (lcp->req_options & REQ_OPTION_PFC)
ppp_set_xmit_pfc(ppp, TRUE);
break;
}
case ACFC:
{
struct lcp_data *lcp = pppcp_get_data(pppcp);
if (lcp->req_options & REQ_OPTION_ACFC)
ppp_set_xmit_acfc(ppp, TRUE);
break;
}
}
}
return RCR_ACCEPT;
}
struct pppcp_proto lcp_proto = {
.proto = LCP_PROTOCOL,
.name = "lcp",
.supported_codes = LCP_SUPPORTED_CODES,
.this_layer_up = lcp_up,
.this_layer_down = lcp_down,
.this_layer_finished = lcp_finished,
.rca = lcp_rca,
.rcn_nak = lcp_rcn_nak,
.rcn_rej = lcp_rcn_rej,
.rcr = lcp_rcr,
};
void lcp_free(struct pppcp_data *pppcp)
{
struct lcp_data *lcp = pppcp_get_data(pppcp);
g_free(lcp);
pppcp_free(pppcp);
}
struct pppcp_data *lcp_new(GAtPPP *ppp, gboolean is_server)
{
struct pppcp_data *pppcp;
struct lcp_data *lcp;
lcp = g_try_new0(struct lcp_data, 1);
if (lcp == NULL)
return NULL;
pppcp = pppcp_new(ppp, &lcp_proto, is_server, 0);
if (pppcp == NULL) {
g_free(lcp);
return NULL;
}
pppcp_set_data(pppcp, lcp);
lcp_reset_config_options(lcp);
pppcp_set_local_options(pppcp, lcp->options, lcp->options_len);
return pppcp;
}
void lcp_set_accm(struct pppcp_data *pppcp, guint32 accm)
{
struct lcp_data *lcp = pppcp_get_data(pppcp);
lcp->accm = accm;
lcp->req_options |= REQ_OPTION_ACCM;
lcp_generate_config_options(lcp);
pppcp_set_local_options(pppcp, lcp->options, lcp->options_len);
}
void lcp_set_acfc_enabled(struct pppcp_data *pppcp, gboolean enabled)
{
struct lcp_data *lcp = pppcp_get_data(pppcp);
guint8 old = lcp->req_options;
if (enabled == TRUE)
lcp->req_options |= REQ_OPTION_ACFC;
else
lcp->req_options &= ~REQ_OPTION_ACFC;
if (lcp->req_options == old)
return;
lcp_generate_config_options(lcp);
pppcp_set_local_options(pppcp, lcp->options, lcp->options_len);
}
void lcp_set_pfc_enabled(struct pppcp_data *pppcp, gboolean enabled)
{
struct lcp_data *lcp = pppcp_get_data(pppcp);
guint8 old = lcp->req_options;
if (enabled == TRUE)
lcp->req_options |= REQ_OPTION_PFC;
else
lcp->req_options &= ~REQ_OPTION_PFC;
if (lcp->req_options == old)
return;
lcp_generate_config_options(lcp);
pppcp_set_local_options(pppcp, lcp->options, lcp->options_len);
}