mirror of git://git.sysmocom.de/ofono
504 lines
12 KiB
C
504 lines
12 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 <arpa/inet.h>
|
|
#include <glib.h>
|
|
|
|
#include "gatutil.h"
|
|
#include "gatppp.h"
|
|
#include "ppp.h"
|
|
|
|
#define IPCP_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))
|
|
|
|
enum ipcp_option_types {
|
|
IP_ADDRESSES = 1,
|
|
IP_COMPRESSION_PROTO = 2,
|
|
IP_ADDRESS = 3,
|
|
MOBILE_IPV4 = 4,
|
|
PRIMARY_DNS_SERVER = 129,
|
|
PRIMARY_NBNS_SERVER = 130,
|
|
SECONDARY_DNS_SERVER = 131,
|
|
SECONDARY_NBNS_SERVER = 132,
|
|
};
|
|
|
|
/* We request IP_ADDRESS, PRIMARY/SECONDARY DNS & NBNS */
|
|
#define MAX_CONFIG_OPTION_SIZE 5*6
|
|
|
|
#define REQ_OPTION_IPADDR 0x01
|
|
#define REQ_OPTION_DNS1 0x02
|
|
#define REQ_OPTION_DNS2 0x04
|
|
#define REQ_OPTION_NBNS1 0x08
|
|
#define REQ_OPTION_NBNS2 0x10
|
|
|
|
#define MAX_IPCP_FAILURE 100
|
|
|
|
struct ipcp_data {
|
|
guint8 options[MAX_CONFIG_OPTION_SIZE];
|
|
guint16 options_len;
|
|
guint8 req_options;
|
|
guint32 local_addr;
|
|
guint32 peer_addr;
|
|
guint32 dns1;
|
|
guint32 dns2;
|
|
guint32 nbns1;
|
|
guint32 nbns2;
|
|
gboolean is_server;
|
|
};
|
|
|
|
#define FILL_IP(options, req, type, var) \
|
|
if (req) { \
|
|
options[len] = type; \
|
|
options[len + 1] = 6; \
|
|
memcpy(options + len + 2, var, 4); \
|
|
\
|
|
len += 6; \
|
|
} \
|
|
|
|
static void ipcp_generate_config_options(struct ipcp_data *ipcp)
|
|
{
|
|
guint16 len = 0;
|
|
|
|
FILL_IP(ipcp->options, ipcp->req_options & REQ_OPTION_IPADDR,
|
|
IP_ADDRESS, &ipcp->local_addr);
|
|
FILL_IP(ipcp->options, ipcp->req_options & REQ_OPTION_DNS1,
|
|
PRIMARY_DNS_SERVER, &ipcp->dns1);
|
|
FILL_IP(ipcp->options, ipcp->req_options & REQ_OPTION_DNS2,
|
|
SECONDARY_DNS_SERVER, &ipcp->dns2);
|
|
FILL_IP(ipcp->options, ipcp->req_options & REQ_OPTION_NBNS1,
|
|
PRIMARY_NBNS_SERVER, &ipcp->nbns1);
|
|
FILL_IP(ipcp->options, ipcp->req_options & REQ_OPTION_NBNS2,
|
|
SECONDARY_NBNS_SERVER, &ipcp->nbns2);
|
|
|
|
ipcp->options_len = len;
|
|
}
|
|
|
|
static void ipcp_reset_client_config_options(struct ipcp_data *ipcp)
|
|
{
|
|
ipcp->req_options = REQ_OPTION_IPADDR | REQ_OPTION_DNS1 |
|
|
REQ_OPTION_DNS2 | REQ_OPTION_NBNS1 |
|
|
REQ_OPTION_NBNS2;
|
|
|
|
ipcp->local_addr = 0;
|
|
ipcp->peer_addr = 0;
|
|
ipcp->dns1 = 0;
|
|
ipcp->dns2 = 0;
|
|
ipcp->nbns1 = 0;
|
|
ipcp->nbns2 = 0;
|
|
|
|
ipcp_generate_config_options(ipcp);
|
|
}
|
|
|
|
static void ipcp_reset_server_config_options(struct ipcp_data *ipcp)
|
|
{
|
|
if (ipcp->local_addr != 0)
|
|
ipcp->req_options = REQ_OPTION_IPADDR;
|
|
else
|
|
ipcp->req_options = 0;
|
|
|
|
ipcp_generate_config_options(ipcp);
|
|
}
|
|
|
|
void ipcp_set_server_info(struct pppcp_data *pppcp, guint32 peer_addr,
|
|
guint32 dns1, guint32 dns2)
|
|
{
|
|
struct ipcp_data *ipcp = pppcp_get_data(pppcp);
|
|
|
|
ipcp->peer_addr = peer_addr;
|
|
ipcp->dns1 = dns1;
|
|
ipcp->dns2 = dns2;
|
|
}
|
|
|
|
static void ipcp_up(struct pppcp_data *pppcp)
|
|
{
|
|
struct ipcp_data *ipcp = pppcp_get_data(pppcp);
|
|
char local[INET_ADDRSTRLEN];
|
|
char peer[INET_ADDRSTRLEN];
|
|
char dns1[INET_ADDRSTRLEN];
|
|
char dns2[INET_ADDRSTRLEN];
|
|
struct in_addr addr;
|
|
|
|
memset(local, 0, sizeof(local));
|
|
addr.s_addr = ipcp->local_addr;
|
|
inet_ntop(AF_INET, &addr, local, INET_ADDRSTRLEN);
|
|
|
|
memset(peer, 0, sizeof(peer));
|
|
addr.s_addr = ipcp->peer_addr;
|
|
inet_ntop(AF_INET, &addr, peer, INET_ADDRSTRLEN);
|
|
|
|
memset(dns1, 0, sizeof(dns1));
|
|
addr.s_addr = ipcp->dns1;
|
|
inet_ntop(AF_INET, &addr, dns1, INET_ADDRSTRLEN);
|
|
|
|
memset(dns2, 0, sizeof(dns2));
|
|
addr.s_addr = ipcp->dns2;
|
|
inet_ntop(AF_INET, &addr, dns2, INET_ADDRSTRLEN);
|
|
|
|
ppp_ipcp_up_notify(pppcp_get_ppp(pppcp), local[0] ? local : NULL,
|
|
peer[0] ? peer : NULL,
|
|
dns1[0] ? dns1 : NULL,
|
|
dns2[0] ? dns2 : NULL);
|
|
}
|
|
|
|
static void ipcp_down(struct pppcp_data *pppcp)
|
|
{
|
|
struct ipcp_data *ipcp = pppcp_get_data(pppcp);
|
|
|
|
if (ipcp->is_server)
|
|
ipcp_reset_server_config_options(ipcp);
|
|
else
|
|
ipcp_reset_client_config_options(ipcp);
|
|
|
|
pppcp_set_local_options(pppcp, ipcp->options, ipcp->options_len);
|
|
ppp_ipcp_down_notify(pppcp_get_ppp(pppcp));
|
|
}
|
|
|
|
static void ipcp_finished(struct pppcp_data *pppcp)
|
|
{
|
|
ppp_ipcp_finished_notify(pppcp_get_ppp(pppcp));
|
|
}
|
|
|
|
static void ipcp_rca(struct pppcp_data *pppcp,
|
|
const struct pppcp_packet *packet)
|
|
{
|
|
struct ipcp_data *ipcp = pppcp_get_data(pppcp);
|
|
struct ppp_option_iter iter;
|
|
|
|
if (ipcp->is_server)
|
|
return;
|
|
|
|
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 IP_ADDRESS:
|
|
memcpy(&ipcp->local_addr, data, 4);
|
|
break;
|
|
case PRIMARY_DNS_SERVER:
|
|
memcpy(&ipcp->dns1, data, 4);
|
|
break;
|
|
case PRIMARY_NBNS_SERVER:
|
|
memcpy(&ipcp->nbns1, data, 4);
|
|
break;
|
|
case SECONDARY_DNS_SERVER:
|
|
memcpy(&ipcp->dns2, data, 4);
|
|
break;
|
|
case SECONDARY_NBNS_SERVER:
|
|
memcpy(&ipcp->nbns2, data, 4);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ipcp_rcn_nak(struct pppcp_data *pppcp,
|
|
const struct pppcp_packet *packet)
|
|
{
|
|
struct ipcp_data *ipcp = pppcp_get_data(pppcp);
|
|
struct ppp_option_iter iter;
|
|
|
|
if (ipcp->is_server)
|
|
return;
|
|
|
|
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 IP_ADDRESS:
|
|
ipcp->req_options |= REQ_OPTION_IPADDR;
|
|
memcpy(&ipcp->local_addr, data, 4);
|
|
break;
|
|
case PRIMARY_DNS_SERVER:
|
|
ipcp->req_options |= REQ_OPTION_DNS1;
|
|
memcpy(&ipcp->dns1, data, 4);
|
|
break;
|
|
case PRIMARY_NBNS_SERVER:
|
|
ipcp->req_options |= REQ_OPTION_NBNS1;
|
|
memcpy(&ipcp->nbns1, data, 4);
|
|
break;
|
|
case SECONDARY_DNS_SERVER:
|
|
ipcp->req_options |= REQ_OPTION_DNS2;
|
|
memcpy(&ipcp->dns2, data, 4);
|
|
break;
|
|
case SECONDARY_NBNS_SERVER:
|
|
ipcp->req_options |= REQ_OPTION_NBNS2;
|
|
memcpy(&ipcp->nbns2, data, 4);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
ipcp_generate_config_options(ipcp);
|
|
pppcp_set_local_options(pppcp, ipcp->options, ipcp->options_len);
|
|
}
|
|
|
|
static void ipcp_rcn_rej(struct pppcp_data *pppcp,
|
|
const struct pppcp_packet *packet)
|
|
{
|
|
struct ipcp_data *ipcp = pppcp_get_data(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 IP_ADDRESS:
|
|
ipcp->req_options &= ~REQ_OPTION_IPADDR;
|
|
break;
|
|
case PRIMARY_DNS_SERVER:
|
|
ipcp->req_options &= ~REQ_OPTION_DNS1;
|
|
break;
|
|
case PRIMARY_NBNS_SERVER:
|
|
ipcp->req_options &= ~REQ_OPTION_NBNS1;
|
|
break;
|
|
case SECONDARY_DNS_SERVER:
|
|
ipcp->req_options &= ~REQ_OPTION_DNS2;
|
|
break;
|
|
case SECONDARY_NBNS_SERVER:
|
|
ipcp->req_options &= ~REQ_OPTION_NBNS2;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
ipcp_generate_config_options(ipcp);
|
|
pppcp_set_local_options(pppcp, ipcp->options, ipcp->options_len);
|
|
}
|
|
|
|
static enum rcr_result ipcp_server_rcr(struct ipcp_data *ipcp,
|
|
const struct pppcp_packet *packet,
|
|
guint8 **new_options, guint16 *new_len)
|
|
{
|
|
struct ppp_option_iter iter;
|
|
guint8 nak_options[MAX_CONFIG_OPTION_SIZE];
|
|
guint16 len = 0;
|
|
guint8 *rej_options = NULL;
|
|
guint16 rej_len = 0;
|
|
guint32 addr;
|
|
|
|
ppp_option_iter_init(&iter, packet);
|
|
|
|
while (ppp_option_iter_next(&iter) == TRUE) {
|
|
const guint8 *data = ppp_option_iter_get_data(&iter);
|
|
guint8 type = ppp_option_iter_get_type(&iter);
|
|
|
|
switch (type) {
|
|
case IP_ADDRESS:
|
|
memcpy(&addr, data, 4);
|
|
|
|
FILL_IP(nak_options,
|
|
addr != ipcp->peer_addr || addr == 0,
|
|
type, &ipcp->peer_addr);
|
|
break;
|
|
case PRIMARY_DNS_SERVER:
|
|
memcpy(&addr, data, 4);
|
|
|
|
FILL_IP(nak_options, addr != ipcp->dns1 || addr == 0,
|
|
type, &ipcp->dns1);
|
|
break;
|
|
case SECONDARY_DNS_SERVER:
|
|
memcpy(&addr, data, 4);
|
|
|
|
FILL_IP(nak_options, addr != ipcp->dns2 || addr == 0,
|
|
type, &ipcp->dns2);
|
|
break;
|
|
default:
|
|
/* Reject */
|
|
if (rej_options == NULL) {
|
|
guint16 max_len = ntohs(packet->length) - 4;
|
|
rej_options = g_new0(guint8, max_len);
|
|
}
|
|
|
|
if (rej_options != NULL) {
|
|
guint8 opt_len =
|
|
ppp_option_iter_get_length(&iter);
|
|
|
|
rej_options[rej_len] = type;
|
|
rej_options[rej_len + 1] = opt_len + 2;
|
|
memcpy(rej_options + rej_len + 2,
|
|
data, opt_len);
|
|
rej_len += opt_len + 2;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (rej_len > 0) {
|
|
*new_len = rej_len;
|
|
*new_options = rej_options;
|
|
|
|
return RCR_REJECT;
|
|
}
|
|
|
|
if (len > 0) {
|
|
*new_len = len;
|
|
*new_options = g_memdup(nak_options, len);
|
|
|
|
return RCR_NAK;
|
|
}
|
|
|
|
return RCR_ACCEPT;
|
|
}
|
|
|
|
static enum rcr_result ipcp_client_rcr(struct ipcp_data *ipcp,
|
|
const struct pppcp_packet *packet,
|
|
guint8 **new_options, guint16 *new_len)
|
|
{
|
|
guint8 *options = NULL;
|
|
struct ppp_option_iter iter;
|
|
guint8 len = 0;
|
|
|
|
ppp_option_iter_init(&iter, packet);
|
|
|
|
while (ppp_option_iter_next(&iter) == TRUE) {
|
|
const guint8 *data = ppp_option_iter_get_data(&iter);
|
|
guint8 type = ppp_option_iter_get_type(&iter);
|
|
|
|
switch (type) {
|
|
case IP_ADDRESS:
|
|
memcpy(&ipcp->peer_addr, data, 4);
|
|
|
|
if (ipcp->peer_addr != 0)
|
|
break;
|
|
|
|
/*
|
|
* Fall through, reject IP_ADDRESS if peer sends
|
|
* us 0 (expecting us to provide its IP address)
|
|
*/
|
|
default:
|
|
if (options == NULL) {
|
|
guint16 max_len = ntohs(packet->length) - 4;
|
|
options = g_new0(guint8, max_len);
|
|
}
|
|
|
|
if (options != NULL) {
|
|
guint8 opt_len =
|
|
ppp_option_iter_get_length(&iter);
|
|
|
|
options[len] = type;
|
|
options[len + 1] = opt_len + 2;
|
|
memcpy(options + len + 2, data, opt_len);
|
|
len += opt_len + 2;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (len > 0) {
|
|
*new_len = len;
|
|
*new_options = options;
|
|
|
|
return RCR_REJECT;
|
|
}
|
|
|
|
return RCR_ACCEPT;
|
|
}
|
|
|
|
static enum rcr_result ipcp_rcr(struct pppcp_data *pppcp,
|
|
const struct pppcp_packet *packet,
|
|
guint8 **new_options, guint16 *new_len)
|
|
{
|
|
struct ipcp_data *ipcp = pppcp_get_data(pppcp);
|
|
|
|
if (ipcp->is_server)
|
|
return ipcp_server_rcr(ipcp, packet, new_options, new_len);
|
|
else
|
|
return ipcp_client_rcr(ipcp, packet, new_options, new_len);
|
|
}
|
|
|
|
struct pppcp_proto ipcp_proto = {
|
|
.proto = IPCP_PROTO,
|
|
.name = "ipcp",
|
|
.supported_codes = IPCP_SUPPORTED_CODES,
|
|
.this_layer_up = ipcp_up,
|
|
.this_layer_down = ipcp_down,
|
|
.this_layer_finished = ipcp_finished,
|
|
.rca = ipcp_rca,
|
|
.rcn_nak = ipcp_rcn_nak,
|
|
.rcn_rej = ipcp_rcn_rej,
|
|
.rcr = ipcp_rcr,
|
|
};
|
|
|
|
struct pppcp_data *ipcp_new(GAtPPP *ppp, gboolean is_server, guint32 ip)
|
|
{
|
|
struct ipcp_data *ipcp;
|
|
struct pppcp_data *pppcp;
|
|
|
|
ipcp = g_try_new0(struct ipcp_data, 1);
|
|
if (ipcp == NULL)
|
|
return NULL;
|
|
|
|
/*
|
|
* Some 3G modems use repeated IPCP NAKs as the way of stalling
|
|
* util sending us the client IP address. So we increase the
|
|
* default number of NAKs we accept before start treating them
|
|
* as rejects.
|
|
*/
|
|
pppcp = pppcp_new(ppp, &ipcp_proto, FALSE, MAX_IPCP_FAILURE);
|
|
if (pppcp == NULL) {
|
|
g_free(ipcp);
|
|
return NULL;
|
|
}
|
|
|
|
pppcp_set_data(pppcp, ipcp);
|
|
ipcp->is_server = is_server;
|
|
|
|
if (is_server) {
|
|
ipcp->local_addr = ip;
|
|
ipcp_reset_server_config_options(ipcp);
|
|
} else
|
|
ipcp_reset_client_config_options(ipcp);
|
|
|
|
pppcp_set_local_options(pppcp, ipcp->options, ipcp->options_len);
|
|
|
|
return pppcp;
|
|
}
|
|
|
|
void ipcp_free(struct pppcp_data *data)
|
|
{
|
|
struct ipcp_data *ipcp = pppcp_get_data(data);
|
|
|
|
g_free(ipcp);
|
|
pppcp_free(data);
|
|
}
|