mirror of git://git.sysmocom.de/ofono
434 lines
9.2 KiB
C
434 lines
9.2 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 the default 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_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);
|
|
}
|