When iterating though configuration options it is possible that we will fail to add data to nack_buf and hence unref it in error handling path. Just after that we will unref buf, which has nack_buf in its buffer chain. Drop code unrefing nack_buf and just go directly to unrefing buf. Signed-off-by: Marcin Niestroj <m.niestroj@grinn-global.com>
560 lines
13 KiB
C
560 lines
13 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 "ipv6.h"
|
|
|
|
#include "ppp_internal.h"
|
|
|
|
static enum net_verdict ipv6cp_handle(struct ppp_context *ctx,
|
|
struct net_if *iface,
|
|
struct net_pkt *pkt)
|
|
{
|
|
return ppp_fsm_input(&ctx->ipv6cp.fsm, PPP_IPV6CP, 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 (10): code + id + interface identifier length */
|
|
#define INTERFACE_IDENTIFIER_OPTION_LEN (1 + 1 + 8)
|
|
|
|
static struct net_buf *ipv6cp_config_info_add(struct ppp_fsm *fsm)
|
|
{
|
|
struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context,
|
|
ipv6cp.fsm);
|
|
|
|
/* Currently we support only one option (IP address) */
|
|
u8_t option[INTERFACE_IDENTIFIER_OPTION_LEN];
|
|
u8_t iid[PPP_INTERFACE_IDENTIFIER_LEN];
|
|
struct net_linkaddr *linkaddr;
|
|
struct net_buf *buf;
|
|
bool added;
|
|
|
|
linkaddr = net_if_get_link_addr(ctx->iface);
|
|
if (linkaddr->len == 8) {
|
|
memcpy(iid, linkaddr->addr, sizeof(iid));
|
|
} else {
|
|
memcpy(iid, linkaddr->addr, 3);
|
|
iid[3] = 0xff;
|
|
iid[4] = 0xfe;
|
|
memcpy(iid + 5, linkaddr->addr + 3, 3);
|
|
}
|
|
|
|
option[0] = IPV6CP_OPTION_INTERFACE_IDENTIFIER;
|
|
option[1] = INTERFACE_IDENTIFIER_OPTION_LEN;
|
|
memcpy(&option[2], iid, sizeof(iid));
|
|
|
|
buf = ppp_get_net_buf(NULL, sizeof(option));
|
|
if (!buf) {
|
|
goto out_of_mem;
|
|
}
|
|
|
|
added = append_to_buf(buf, option, sizeof(option));
|
|
if (!added) {
|
|
goto out_of_mem;
|
|
}
|
|
|
|
return buf;
|
|
|
|
out_of_mem:
|
|
if (buf) {
|
|
net_buf_unref(buf);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int ipv6cp_config_info_req(struct ppp_fsm *fsm,
|
|
struct net_pkt *pkt,
|
|
u16_t length,
|
|
struct net_buf **ret_buf)
|
|
{
|
|
int nack_idx = 0, iface_id_option_idx = -1;
|
|
struct net_buf *buf = NULL;
|
|
struct ppp_option_pkt options[MAX_IPV6CP_OPTIONS];
|
|
struct ppp_option_pkt nack_options[MAX_IPV6CP_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.ipv6cp != IPV6CP_OPTION_RESERVED) {
|
|
NET_DBG("[%s/%p] %s option %s (%d) len %d",
|
|
fsm->name, fsm, "Check",
|
|
ppp_option2str(PPP_IPV6CP,
|
|
options[i].type.ipv6cp),
|
|
options[i].type.ipv6cp, options[i].len);
|
|
}
|
|
|
|
switch (options[i].type.ipv6cp) {
|
|
case IPV6CP_OPTION_RESERVED:
|
|
continue;
|
|
|
|
case IPV6CP_OPTION_INTERFACE_IDENTIFIER:
|
|
/* Currently we only accept one option (iface id) */
|
|
iface_id_option_idx = i;
|
|
break;
|
|
|
|
default:
|
|
nack_options[nack_idx].type.ipv6cp =
|
|
options[i].type.ipv6cp;
|
|
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;
|
|
|
|
/* Once rejected count logic is in, it will be possible
|
|
* to set this code to PPP_CONFIGURE_REJ. */
|
|
code = PPP_CONFIGURE_NACK;
|
|
|
|
/* 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.ipv6cp, 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 {
|
|
u8_t iface_id[PPP_INTERFACE_IDENTIFIER_LEN];
|
|
struct ppp_context *ctx;
|
|
bool added;
|
|
u8_t val;
|
|
int ret;
|
|
|
|
ctx = CONTAINER_OF(fsm, struct ppp_context, ipv6cp.fsm);
|
|
|
|
if (iface_id_option_idx < 0) {
|
|
/* Interface id option was not present */
|
|
return -EINVAL;
|
|
}
|
|
|
|
code = PPP_CONFIGURE_ACK;
|
|
|
|
net_pkt_cursor_restore(pkt,
|
|
&options[iface_id_option_idx].value);
|
|
|
|
ret = net_pkt_read(pkt, iface_id, sizeof(iface_id));
|
|
if (ret < 0) {
|
|
/* Should not happen, is the pkt corrupt? */
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
memcpy(ctx->ipv6cp.peer_options.iid, iface_id,
|
|
sizeof(iface_id));
|
|
|
|
if (CONFIG_NET_L2_PPP_LOG_LEVEL >= LOG_LEVEL_DBG) {
|
|
u8_t iid_str[sizeof("xx:xx:xx:xx:xx:xx:xx:xx")];
|
|
|
|
net_sprint_ll_addr_buf(iface_id, sizeof(iface_id),
|
|
iid_str, sizeof(iid_str));
|
|
|
|
NET_DBG("[%s/%p] Received %siid %s",
|
|
fsm->name, fsm, "peer ", log_strdup(iid_str));
|
|
}
|
|
|
|
/* TODO: check whether iid is empty and create one if so */
|
|
|
|
buf = ppp_get_net_buf(NULL, INTERFACE_IDENTIFIER_OPTION_LEN);
|
|
if (!buf) {
|
|
goto bail_out;
|
|
}
|
|
|
|
val = IPV6CP_OPTION_INTERFACE_IDENTIFIER;
|
|
added = append_to_buf(buf, &val, sizeof(val));
|
|
if (!added) {
|
|
goto bail_out;
|
|
}
|
|
|
|
val = INTERFACE_IDENTIFIER_OPTION_LEN;
|
|
added = append_to_buf(buf, &val, sizeof(val));
|
|
if (!added) {
|
|
goto bail_out;
|
|
}
|
|
|
|
added = append_to_buf(buf, iface_id, sizeof(iface_id));
|
|
if (!added) {
|
|
goto bail_out;
|
|
}
|
|
}
|
|
|
|
if (buf) {
|
|
*ret_buf = buf;
|
|
}
|
|
|
|
return code;
|
|
|
|
bail_out:
|
|
if (buf) {
|
|
net_buf_unref(buf);
|
|
}
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static int config_info_ack_rej(struct ppp_context *ctx,
|
|
struct ppp_fsm *fsm,
|
|
struct net_pkt *pkt,
|
|
u16_t length,
|
|
u8_t code)
|
|
{
|
|
struct ppp_option_pkt nack_options[MAX_IPV6CP_OPTIONS];
|
|
u8_t iface_id[PPP_INTERFACE_IDENTIFIER_LEN];
|
|
int i, ret, iface_id_option_idx = -1;
|
|
enum net_verdict verdict;
|
|
|
|
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.ipv6cp != IPV6CP_OPTION_RESERVED) {
|
|
NET_DBG("[%s/%p] %s option %s (%d) len %d",
|
|
fsm->name, fsm, "Check",
|
|
ppp_option2str(PPP_IPV6CP,
|
|
nack_options[i].type.ipv6cp),
|
|
nack_options[i].type.ipv6cp,
|
|
nack_options[i].len);
|
|
}
|
|
|
|
switch (nack_options[i].type.ipv6cp) {
|
|
case IPV6CP_OPTION_RESERVED:
|
|
continue;
|
|
|
|
case IPV6CP_OPTION_INTERFACE_IDENTIFIER:
|
|
iface_id_option_idx = i;
|
|
break;
|
|
|
|
default:
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (iface_id_option_idx < 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
net_pkt_cursor_restore(pkt, &nack_options[iface_id_option_idx].value);
|
|
|
|
ret = net_pkt_read(pkt, iface_id, sizeof(iface_id));
|
|
if (ret < 0) {
|
|
/* Should not happen, is the pkt corrupt? */
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
memcpy(ctx->ipv6cp.peer_accepted.iid, iface_id, sizeof(iface_id));
|
|
|
|
if (CONFIG_NET_L2_PPP_LOG_LEVEL >= LOG_LEVEL_DBG) {
|
|
u8_t iid_str[sizeof("xx:xx:xx:xx:xx:xx:xx:xx")];
|
|
|
|
net_sprint_ll_addr_buf(iface_id, sizeof(iface_id),
|
|
iid_str, sizeof(iid_str));
|
|
|
|
NET_DBG("[%s/%p] Received %siid %s",
|
|
fsm->name, fsm, "", log_strdup(iid_str));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ipv6cp_config_info_rej(struct ppp_fsm *fsm,
|
|
struct net_pkt *pkt,
|
|
u16_t length)
|
|
{
|
|
struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context,
|
|
ipv6cp.fsm);
|
|
|
|
return config_info_ack_rej(ctx, fsm, pkt, length, PPP_CONFIGURE_REJ);
|
|
}
|
|
|
|
static int ipv6cp_config_info_ack(struct ppp_fsm *fsm,
|
|
struct net_pkt *pkt,
|
|
u16_t length)
|
|
{
|
|
struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context,
|
|
ipv6cp.fsm);
|
|
|
|
return config_info_ack_rej(ctx, fsm, pkt, length, PPP_CONFIGURE_ACK);
|
|
}
|
|
|
|
|
|
static void ipv6cp_lower_down(struct ppp_context *ctx)
|
|
{
|
|
ppp_fsm_lower_down(&ctx->ipv6cp.fsm);
|
|
}
|
|
|
|
static void ipv6cp_lower_up(struct ppp_context *ctx)
|
|
{
|
|
ppp_fsm_lower_up(&ctx->ipv6cp.fsm);
|
|
}
|
|
|
|
static void ipv6cp_open(struct ppp_context *ctx)
|
|
{
|
|
ppp_fsm_open(&ctx->ipv6cp.fsm);
|
|
}
|
|
|
|
static void ipv6cp_close(struct ppp_context *ctx, const u8_t *reason)
|
|
{
|
|
ppp_fsm_close(&ctx->ipv6cp.fsm, reason);
|
|
}
|
|
|
|
static void setup_iid_address(u8_t *iid, struct in6_addr *addr)
|
|
{
|
|
addr->s6_addr[0] = 0xfe;
|
|
addr->s6_addr[1] = 0x80;
|
|
UNALIGNED_PUT(0, &addr->s6_addr16[1]);
|
|
UNALIGNED_PUT(0, &addr->s6_addr32[1]);
|
|
memcpy(&addr->s6_addr[8], iid, PPP_INTERFACE_IDENTIFIER_LEN);
|
|
|
|
/* TODO: should we toggle local/global bit */
|
|
/* addr->s6_addr[8] ^= 0x02; */
|
|
}
|
|
|
|
static void add_iid_address(struct net_if *iface, u8_t *iid)
|
|
{
|
|
struct net_if_addr *ifaddr;
|
|
struct in6_addr addr;
|
|
|
|
setup_iid_address(iid, &addr);
|
|
|
|
ifaddr = net_if_ipv6_addr_add(iface, &addr, NET_ADDR_AUTOCONF, 0);
|
|
if (!ifaddr) {
|
|
NET_ERR("Cannot add %s address to interface %p",
|
|
log_strdup(net_sprint_ipv6_addr(&addr)), iface);
|
|
} else {
|
|
/* As DAD is disabled, we need to mark the address
|
|
* as a preferred one.
|
|
*/
|
|
ifaddr->addr_state = NET_ADDR_PREFERRED;
|
|
}
|
|
}
|
|
|
|
static void ipv6cp_up(struct ppp_fsm *fsm)
|
|
{
|
|
struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context,
|
|
ipv6cp.fsm);
|
|
struct net_nbr *nbr;
|
|
struct in6_addr peer_addr;
|
|
struct net_linkaddr peer_lladdr;
|
|
|
|
if (ctx->is_ipv6cp_up) {
|
|
return;
|
|
}
|
|
|
|
ppp_network_up(ctx, PPP_IPV6);
|
|
|
|
ctx->is_network_up = true;
|
|
ctx->is_ipv6cp_up = true;
|
|
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
add_iid_address(ctx->iface, ctx->ipv6cp.peer_accepted.iid);
|
|
|
|
/* Add peer to neighbor table */
|
|
setup_iid_address(ctx->ipv6cp.peer_options.iid, &peer_addr);
|
|
|
|
peer_lladdr.addr = ctx->ipv6cp.peer_options.iid;
|
|
peer_lladdr.len = sizeof(ctx->ipv6cp.peer_options.iid);
|
|
|
|
/* TODO: What should be the type? */
|
|
peer_lladdr.type = NET_LINK_DUMMY;
|
|
|
|
nbr = net_ipv6_nbr_add(ctx->iface, &peer_addr, &peer_lladdr,
|
|
false, NET_IPV6_NBR_STATE_STATIC);
|
|
if (!nbr) {
|
|
NET_ERR("[%s/%p] Cannot add peer %s to nbr table",
|
|
fsm->name, fsm,
|
|
log_strdup(net_sprint_addr(AF_INET6,
|
|
(const void *)&peer_addr)));
|
|
} else {
|
|
if (CONFIG_NET_L2_PPP_LOG_LEVEL >= LOG_LEVEL_DBG) {
|
|
u8_t iid_str[sizeof("xx:xx:xx:xx:xx:xx:xx:xx")];
|
|
char dst[INET6_ADDRSTRLEN];
|
|
char *addr_str;
|
|
|
|
net_sprint_ll_addr_buf(peer_lladdr.addr,
|
|
peer_lladdr.len,
|
|
iid_str, sizeof(iid_str));
|
|
|
|
addr_str = net_addr_ntop(AF_INET6, &peer_addr, dst,
|
|
sizeof(dst));
|
|
|
|
NET_DBG("[%s/%p] Peer %s [%s] %s nbr cache",
|
|
fsm->name, fsm, log_strdup(addr_str),
|
|
log_strdup(iid_str), "added to");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ipv6cp_down(struct ppp_fsm *fsm)
|
|
{
|
|
struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context,
|
|
ipv6cp.fsm);
|
|
struct net_linkaddr peer_lladdr;
|
|
struct in6_addr peer_addr;
|
|
int ret;
|
|
|
|
if (!ctx->is_network_up) {
|
|
return;
|
|
}
|
|
|
|
ctx->is_network_up = false;
|
|
ctx->is_ipv6cp_up = false;
|
|
|
|
ppp_network_down(ctx, PPP_IPV6);
|
|
|
|
/* Remove peer from neighbor table */
|
|
setup_iid_address(ctx->ipv6cp.peer_options.iid, &peer_addr);
|
|
|
|
peer_lladdr.addr = ctx->ipv6cp.peer_options.iid;
|
|
peer_lladdr.len = sizeof(ctx->ipv6cp.peer_options.iid);
|
|
|
|
/* TODO: What should be the type? */
|
|
peer_lladdr.type = NET_LINK_DUMMY;
|
|
|
|
ret = net_ipv6_nbr_rm(ctx->iface, &peer_addr);
|
|
if (!ret) {
|
|
NET_ERR("[%s/%p] Cannot rm peer %s from nbr table",
|
|
fsm->name, fsm,
|
|
log_strdup(net_sprint_addr(AF_INET6,
|
|
(const void *)&peer_addr)));
|
|
} else {
|
|
if (CONFIG_NET_L2_PPP_LOG_LEVEL >= LOG_LEVEL_DBG) {
|
|
u8_t iid_str[sizeof("xx:xx:xx:xx:xx:xx:xx:xx")];
|
|
char dst[INET6_ADDRSTRLEN];
|
|
char *addr_str;
|
|
|
|
net_sprint_ll_addr_buf(ctx->ipv6cp.peer_options.iid,
|
|
sizeof(ctx->ipv6cp.peer_options.iid),
|
|
iid_str, sizeof(iid_str));
|
|
|
|
addr_str = net_addr_ntop(AF_INET6, &peer_addr, dst,
|
|
sizeof(dst));
|
|
|
|
NET_DBG("[%s/%p] Peer %s [%s] %s nbr cache",
|
|
fsm->name, fsm, log_strdup(addr_str),
|
|
log_strdup(iid_str), "removed from");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ipv6cp_finished(struct ppp_fsm *fsm)
|
|
{
|
|
struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context,
|
|
ipv6cp.fsm);
|
|
|
|
if (!ctx->is_ipv6cp_open) {
|
|
return;
|
|
}
|
|
|
|
ctx->is_ipv6cp_open = false;
|
|
|
|
ppp_network_done(ctx, PPP_IPV6);
|
|
}
|
|
|
|
static void ipv6cp_proto_reject(struct ppp_fsm *fsm)
|
|
{
|
|
ppp_fsm_lower_down(fsm);
|
|
}
|
|
|
|
static void ipv6cp_init(struct ppp_context *ctx)
|
|
{
|
|
NET_DBG("proto %s (0x%04x) fsm %p", ppp_proto2str(PPP_IPV6CP),
|
|
PPP_IPV6CP, &ctx->ipv6cp.fsm);
|
|
|
|
memset(&ctx->ipv6cp.fsm, 0, sizeof(ctx->ipv6cp.fsm));
|
|
|
|
ppp_fsm_init(&ctx->ipv6cp.fsm, PPP_IPV6CP);
|
|
|
|
ppp_fsm_name_set(&ctx->ipv6cp.fsm, ppp_proto2str(PPP_IPV6CP));
|
|
|
|
ctx->ipv6cp.fsm.cb.up = ipv6cp_up;
|
|
ctx->ipv6cp.fsm.cb.down = ipv6cp_down;
|
|
ctx->ipv6cp.fsm.cb.finished = ipv6cp_finished;
|
|
ctx->ipv6cp.fsm.cb.proto_reject = ipv6cp_proto_reject;
|
|
ctx->ipv6cp.fsm.cb.config_info_ack = ipv6cp_config_info_ack;
|
|
ctx->ipv6cp.fsm.cb.config_info_add = ipv6cp_config_info_add;
|
|
ctx->ipv6cp.fsm.cb.config_info_req = ipv6cp_config_info_req;
|
|
ctx->ipv6cp.fsm.cb.config_info_rej = ipv6cp_config_info_rej;
|
|
}
|
|
|
|
PPP_PROTOCOL_REGISTER(IPV6CP, PPP_IPV6CP,
|
|
ipv6cp_init, ipv6cp_handle,
|
|
ipv6cp_lower_up, ipv6cp_lower_down,
|
|
ipv6cp_open, ipv6cp_close);
|