It is not unusual that the peer does not provide an IP address in the ipcp negotiation. But because ppp is a peer-to-peer protocol, we do not actually need to know the peer's address to use the network. Signed-off-by: Göran Weinholt <goran.weinholt@endian.se>
455 lines
9.8 KiB
C
455 lines
9.8 KiB
C
/*
|
|
* Copyright (c) 2019 Intel Corporation.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <logging/log.h>
|
|
LOG_MODULE_DECLARE(net_l2_ppp, CONFIG_NET_L2_PPP_LOG_LEVEL);
|
|
|
|
#include <net/net_core.h>
|
|
#include <net/net_pkt.h>
|
|
|
|
#include <net/ppp.h>
|
|
|
|
#include "net_private.h"
|
|
|
|
#include "ppp_internal.h"
|
|
|
|
static enum net_verdict ipcp_handle(struct ppp_context *ctx,
|
|
struct net_if *iface,
|
|
struct net_pkt *pkt)
|
|
{
|
|
return ppp_fsm_input(&ctx->ipcp.fsm, PPP_IPCP, pkt);
|
|
}
|
|
|
|
static bool append_to_buf(struct net_buf *buf, u8_t *data, u8_t data_len)
|
|
{
|
|
if (data_len > net_buf_tailroom(buf)) {
|
|
return false;
|
|
}
|
|
|
|
/* FIXME: use net_pkt api so that we can handle a case where data might
|
|
* split to two net_buf's
|
|
*/
|
|
net_buf_add_mem(buf, data, data_len);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Length is (6): code + id + IPv4 address length */
|
|
#define IP_ADDRESS_OPTION_LEN (1 + 1 + 4)
|
|
|
|
static struct net_buf *ipcp_config_info_add(struct ppp_fsm *fsm)
|
|
{
|
|
struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context,
|
|
ipcp.fsm);
|
|
|
|
/* Currently we support only one option (IP address) */
|
|
u8_t option[IP_ADDRESS_OPTION_LEN];
|
|
const struct in_addr *my_addr;
|
|
struct net_buf *buf;
|
|
bool added;
|
|
|
|
my_addr = &ctx->ipcp.my_options.address;
|
|
|
|
option[0] = IPCP_OPTION_IP_ADDRESS;
|
|
option[1] = IP_ADDRESS_OPTION_LEN;
|
|
memcpy(&option[2], &my_addr->s_addr, sizeof(my_addr->s_addr));
|
|
|
|
buf = ppp_get_net_buf(NULL, 0);
|
|
if (!buf) {
|
|
goto out_of_mem;
|
|
}
|
|
|
|
added = append_to_buf(buf, option, sizeof(option));
|
|
if (!added) {
|
|
goto out_of_mem;
|
|
}
|
|
|
|
NET_DBG("Added IPCP IP Address option %d.%d.%d.%d",
|
|
option[2], option[3], option[4], option[5]);
|
|
|
|
return buf;
|
|
|
|
out_of_mem:
|
|
if (buf) {
|
|
net_buf_unref(buf);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int ipcp_config_info_req(struct ppp_fsm *fsm,
|
|
struct net_pkt *pkt,
|
|
u16_t length,
|
|
struct net_buf **ret_buf)
|
|
{
|
|
int nack_idx = 0, address_option_idx = -1;
|
|
struct net_buf *buf = NULL;
|
|
struct ppp_option_pkt options[MAX_IPCP_OPTIONS];
|
|
struct ppp_option_pkt nack_options[MAX_IPCP_OPTIONS];
|
|
enum ppp_packet_type code;
|
|
enum net_verdict verdict;
|
|
int i;
|
|
|
|
memset(options, 0, sizeof(options));
|
|
memset(nack_options, 0, sizeof(nack_options));
|
|
|
|
verdict = ppp_parse_options(fsm, pkt, length, options,
|
|
ARRAY_SIZE(options));
|
|
if (verdict != NET_OK) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(options); i++) {
|
|
if (options[i].type.ipcp != IPCP_OPTION_RESERVED) {
|
|
NET_DBG("[%s/%p] %s option %s (%d) len %d",
|
|
fsm->name, fsm, "Check",
|
|
ppp_option2str(PPP_IPCP, options[i].type.ipcp),
|
|
options[i].type.ipcp, options[i].len);
|
|
}
|
|
|
|
switch (options[i].type.ipcp) {
|
|
case IPCP_OPTION_RESERVED:
|
|
continue;
|
|
|
|
case IPCP_OPTION_IP_ADDRESS:
|
|
/* Currently we only accept one option (IP address) */
|
|
address_option_idx = i;
|
|
break;
|
|
|
|
default:
|
|
nack_options[nack_idx].type.ipcp =
|
|
options[i].type.ipcp;
|
|
nack_options[nack_idx].len = options[i].len;
|
|
|
|
if (options[i].len > 2) {
|
|
memcpy(&nack_options[nack_idx].value,
|
|
&options[i].value,
|
|
sizeof(nack_options[nack_idx].value));
|
|
}
|
|
|
|
nack_idx++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nack_idx > 0) {
|
|
struct net_buf *nack_buf;
|
|
|
|
code = PPP_CONFIGURE_REJ;
|
|
|
|
/* Create net_buf containing options that are not accepted */
|
|
for (i = 0; i < MIN(nack_idx, ARRAY_SIZE(nack_options)); i++) {
|
|
bool added;
|
|
|
|
nack_buf = ppp_get_net_buf(buf, nack_options[i].len);
|
|
if (!nack_buf) {
|
|
goto bail_out;
|
|
}
|
|
|
|
if (!buf) {
|
|
buf = nack_buf;
|
|
}
|
|
|
|
added = append_to_buf(nack_buf,
|
|
&nack_options[i].type.ipcp, 1);
|
|
if (!added) {
|
|
goto bail_out;
|
|
}
|
|
|
|
added = append_to_buf(nack_buf, &nack_options[i].len,
|
|
1);
|
|
if (!added) {
|
|
goto bail_out;
|
|
}
|
|
|
|
/* If there is some data, copy it to result buf */
|
|
if (nack_options[i].value.pos) {
|
|
added = append_to_buf(nack_buf,
|
|
nack_options[i].value.pos,
|
|
nack_options[i].len - 1 - 1);
|
|
if (!added) {
|
|
goto bail_out;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
struct ppp_context *ctx;
|
|
struct in_addr addr;
|
|
int ret;
|
|
|
|
ctx = CONTAINER_OF(fsm, struct ppp_context, ipcp.fsm);
|
|
|
|
if (address_option_idx < 0) {
|
|
/* The address option was not present, but we
|
|
* can continue without it. */
|
|
NET_DBG("[%s/%p] No %saddress provided",
|
|
fsm->name, fsm, "peer ");
|
|
return PPP_CONFIGURE_ACK;
|
|
}
|
|
|
|
code = PPP_CONFIGURE_ACK;
|
|
|
|
net_pkt_cursor_restore(pkt,
|
|
&options[address_option_idx].value);
|
|
|
|
ret = net_pkt_read(pkt, (u32_t *)&addr, sizeof(addr));
|
|
if (ret < 0) {
|
|
/* Should not happen, is the pkt corrupt? */
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
memcpy(&ctx->ipcp.peer_options.address, &addr, sizeof(addr));
|
|
|
|
if (CONFIG_NET_L2_PPP_LOG_LEVEL >= LOG_LEVEL_DBG) {
|
|
char dst[INET_ADDRSTRLEN];
|
|
char *addr_str;
|
|
|
|
addr_str = net_addr_ntop(AF_INET, &addr, dst,
|
|
sizeof(dst));
|
|
|
|
NET_DBG("[%s/%p] Received %saddress %s",
|
|
fsm->name, fsm, "peer ", log_strdup(addr_str));
|
|
}
|
|
|
|
if (addr.s_addr) {
|
|
bool added;
|
|
u8_t val;
|
|
|
|
/* The address is the remote address, we then need
|
|
* to figure out what our address should be.
|
|
*
|
|
* TODO:
|
|
* - check that the IP address can be accepted
|
|
*/
|
|
|
|
buf = ppp_get_net_buf(NULL, IP_ADDRESS_OPTION_LEN);
|
|
if (!buf) {
|
|
goto bail_out;
|
|
}
|
|
|
|
val = IPCP_OPTION_IP_ADDRESS;
|
|
added = append_to_buf(buf, &val, sizeof(val));
|
|
if (!added) {
|
|
goto bail_out;
|
|
}
|
|
|
|
val = IP_ADDRESS_OPTION_LEN;
|
|
added = append_to_buf(buf, &val, sizeof(val));
|
|
if (!added) {
|
|
goto bail_out;
|
|
}
|
|
|
|
added = append_to_buf(buf, (u8_t *)&addr.s_addr,
|
|
sizeof(addr.s_addr));
|
|
if (!added) {
|
|
goto bail_out;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (buf) {
|
|
*ret_buf = buf;
|
|
}
|
|
|
|
return code;
|
|
|
|
bail_out:
|
|
if (buf) {
|
|
net_buf_unref(buf);
|
|
}
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static int ipcp_config_info_nack(struct ppp_fsm *fsm,
|
|
struct net_pkt *pkt,
|
|
u16_t length,
|
|
bool rejected)
|
|
{
|
|
struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context,
|
|
ipcp.fsm);
|
|
struct ppp_option_pkt nack_options[MAX_IPCP_OPTIONS];
|
|
enum net_verdict verdict;
|
|
int i, ret, address_option_idx = -1;
|
|
struct in_addr addr;
|
|
|
|
memset(nack_options, 0, sizeof(nack_options));
|
|
|
|
verdict = ppp_parse_options(fsm, pkt, length, nack_options,
|
|
ARRAY_SIZE(nack_options));
|
|
if (verdict != NET_OK) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(nack_options); i++) {
|
|
if (nack_options[i].type.ipcp != IPCP_OPTION_RESERVED) {
|
|
NET_DBG("[%s/%p] %s option %s (%d) len %d",
|
|
fsm->name, fsm, "Check",
|
|
ppp_option2str(PPP_IPCP,
|
|
nack_options[i].type.ipcp),
|
|
nack_options[i].type.ipcp,
|
|
nack_options[i].len);
|
|
}
|
|
|
|
switch (nack_options[i].type.ipcp) {
|
|
case IPCP_OPTION_RESERVED:
|
|
continue;
|
|
|
|
case IPCP_OPTION_IP_ADDRESSES:
|
|
continue;
|
|
|
|
case IPCP_OPTION_IP_COMP_PROTO:
|
|
continue;
|
|
|
|
case IPCP_OPTION_IP_ADDRESS:
|
|
address_option_idx = i;
|
|
break;
|
|
|
|
default:
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (address_option_idx < 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
net_pkt_cursor_restore(pkt, &nack_options[address_option_idx].value);
|
|
|
|
ret = net_pkt_read(pkt, (u32_t *)&addr, sizeof(addr));
|
|
if (ret < 0) {
|
|
/* Should not happen, is the pkt corrupt? */
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
memcpy(&ctx->ipcp.my_options.address, &addr, sizeof(addr));
|
|
|
|
if (CONFIG_NET_L2_PPP_LOG_LEVEL >= LOG_LEVEL_DBG) {
|
|
char dst[INET_ADDRSTRLEN];
|
|
char *addr_str;
|
|
|
|
addr_str = net_addr_ntop(AF_INET, &addr, dst,
|
|
sizeof(dst));
|
|
|
|
NET_DBG("[%s/%p] Received %saddress %s",
|
|
fsm->name, fsm, "", log_strdup(addr_str));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ipcp_lower_down(struct ppp_context *ctx)
|
|
{
|
|
ppp_fsm_lower_down(&ctx->ipcp.fsm);
|
|
}
|
|
|
|
static void ipcp_lower_up(struct ppp_context *ctx)
|
|
{
|
|
ppp_fsm_lower_up(&ctx->ipcp.fsm);
|
|
}
|
|
|
|
static void ipcp_open(struct ppp_context *ctx)
|
|
{
|
|
ppp_fsm_open(&ctx->ipcp.fsm);
|
|
}
|
|
|
|
static void ipcp_close(struct ppp_context *ctx, const u8_t *reason)
|
|
{
|
|
ppp_fsm_close(&ctx->ipcp.fsm, reason);
|
|
}
|
|
|
|
static void ipcp_up(struct ppp_fsm *fsm)
|
|
{
|
|
struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context,
|
|
ipcp.fsm);
|
|
struct net_if_addr *addr;
|
|
char dst[INET_ADDRSTRLEN];
|
|
char *addr_str;
|
|
|
|
if (ctx->is_ipcp_up) {
|
|
return;
|
|
}
|
|
|
|
addr_str = net_addr_ntop(AF_INET, &ctx->ipcp.my_options.address,
|
|
dst, sizeof(dst));
|
|
|
|
addr = net_if_ipv4_addr_add(ctx->iface,
|
|
&ctx->ipcp.my_options.address,
|
|
NET_ADDR_MANUAL,
|
|
0);
|
|
if (addr == NULL) {
|
|
NET_ERR("Could not set IP address %s", log_strdup(addr_str));
|
|
return;
|
|
}
|
|
|
|
NET_DBG("PPP up with address %s", log_strdup(addr_str));
|
|
ppp_network_up(ctx, PPP_IP);
|
|
|
|
ctx->is_network_up = true;
|
|
ctx->is_ipcp_up = true;
|
|
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
}
|
|
|
|
static void ipcp_down(struct ppp_fsm *fsm)
|
|
{
|
|
struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context,
|
|
ipcp.fsm);
|
|
|
|
if (!ctx->is_network_up) {
|
|
return;
|
|
}
|
|
|
|
ctx->is_network_up = false;
|
|
ctx->is_ipcp_up = false;
|
|
|
|
ppp_network_down(ctx, PPP_IP);
|
|
}
|
|
|
|
static void ipcp_finished(struct ppp_fsm *fsm)
|
|
{
|
|
struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context,
|
|
ipcp.fsm);
|
|
|
|
if (!ctx->is_ipcp_open) {
|
|
return;
|
|
}
|
|
|
|
ctx->is_ipcp_open = false;
|
|
|
|
ppp_network_done(ctx, PPP_IP);
|
|
}
|
|
|
|
static void ipcp_proto_reject(struct ppp_fsm *fsm)
|
|
{
|
|
ppp_fsm_lower_down(fsm);
|
|
}
|
|
|
|
static void ipcp_init(struct ppp_context *ctx)
|
|
{
|
|
NET_DBG("proto %s (0x%04x) fsm %p", ppp_proto2str(PPP_IPCP), PPP_IPCP,
|
|
&ctx->ipcp.fsm);
|
|
|
|
memset(&ctx->ipcp.fsm, 0, sizeof(ctx->ipcp.fsm));
|
|
|
|
ppp_fsm_init(&ctx->ipcp.fsm, PPP_IPCP);
|
|
|
|
ppp_fsm_name_set(&ctx->ipcp.fsm, ppp_proto2str(PPP_IPCP));
|
|
|
|
ctx->ipcp.fsm.cb.up = ipcp_up;
|
|
ctx->ipcp.fsm.cb.down = ipcp_down;
|
|
ctx->ipcp.fsm.cb.finished = ipcp_finished;
|
|
ctx->ipcp.fsm.cb.proto_reject = ipcp_proto_reject;
|
|
ctx->ipcp.fsm.cb.config_info_add = ipcp_config_info_add;
|
|
ctx->ipcp.fsm.cb.config_info_req = ipcp_config_info_req;
|
|
ctx->ipcp.fsm.cb.config_info_nack = ipcp_config_info_nack;
|
|
}
|
|
|
|
PPP_PROTOCOL_REGISTER(IPCP, PPP_IPCP,
|
|
ipcp_init, ipcp_handle,
|
|
ipcp_lower_up, ipcp_lower_down,
|
|
ipcp_open, ipcp_close);
|