This implements ppp L2 component, LCP and IPCP modules. Fixes #14034 Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
1096 lines
23 KiB
C
1096 lines
23 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"
|
|
|
|
#define BUF_ALLOC_TIMEOUT K_MSEC(100)
|
|
|
|
/* This timeout is in milliseconds */
|
|
#define FSM_TIMEOUT CONFIG_NET_L2_PPP_TIMEOUT
|
|
|
|
#define MAX_NACK_LOOPS CONFIG_NET_L2_PPP_MAX_NACK_LOOPS
|
|
|
|
static void fsm_send_configure_req(struct ppp_fsm *fsm, bool retransmit)
|
|
{
|
|
struct net_buf *options = NULL;
|
|
|
|
if (fsm->state != PPP_ACK_RECEIVED &&
|
|
fsm->state != PPP_ACK_SENT &&
|
|
fsm->state != PPP_REQUEST_SENT) {
|
|
/* If we are not negotiating options, then reset them */
|
|
if (fsm->cb.config_info_reset) {
|
|
fsm->cb.config_info_reset(fsm);
|
|
}
|
|
|
|
fsm->recv_nack_loops = 0;
|
|
fsm->nack_loops = 0;
|
|
}
|
|
|
|
if (!retransmit) {
|
|
fsm->retransmits = MAX_CONFIGURE_REQ;
|
|
fsm->req_id = ++fsm->id;
|
|
}
|
|
|
|
fsm->ack_received = false;
|
|
|
|
if (fsm->cb.config_info_add) {
|
|
options = fsm->cb.config_info_add(fsm);
|
|
}
|
|
|
|
NET_DBG("[%s/%p] Sending %s (%d) id %d to peer while in %s (%d)",
|
|
fsm->name, fsm, ppp_pkt_type2str(PPP_CONFIGURE_REQ),
|
|
PPP_CONFIGURE_REQ, fsm->req_id, ppp_state_str(fsm->state),
|
|
fsm->state);
|
|
|
|
(void)ppp_send_pkt(fsm, NULL, PPP_CONFIGURE_REQ, fsm->req_id,
|
|
options, options ? net_buf_frags_len(options) : 0);
|
|
|
|
fsm->retransmits--;
|
|
|
|
(void)k_delayed_work_submit(&fsm->timer, FSM_TIMEOUT);
|
|
}
|
|
|
|
static void ppp_fsm_timeout(struct k_work *work)
|
|
{
|
|
struct ppp_fsm *fsm = CONTAINER_OF(work, struct ppp_fsm, timer);
|
|
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
switch (fsm->state) {
|
|
case PPP_ACK_RECEIVED:
|
|
case PPP_ACK_SENT:
|
|
case PPP_REQUEST_SENT:
|
|
if (fsm->retransmits <= 0) {
|
|
NET_DBG("[%s/%p] %s retransmit limit %d reached",
|
|
fsm->name, fsm,
|
|
ppp_pkt_type2str(PPP_CONFIGURE_REQ),
|
|
fsm->retransmits);
|
|
|
|
ppp_change_state(fsm, PPP_STOPPED);
|
|
|
|
if (fsm->cb.finished) {
|
|
fsm->cb.finished(fsm);
|
|
}
|
|
} else {
|
|
if (fsm->cb.retransmit) {
|
|
fsm->cb.retransmit(fsm);
|
|
}
|
|
|
|
fsm_send_configure_req(fsm, true);
|
|
|
|
if (fsm->state == PPP_ACK_RECEIVED) {
|
|
ppp_change_state(fsm, PPP_REQUEST_SENT);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case PPP_CLOSING:
|
|
case PPP_STOPPING:
|
|
if (fsm->retransmits <= 0) {
|
|
ppp_change_state(fsm,
|
|
fsm->state == PPP_CLOSING ?
|
|
PPP_CLOSED : PPP_STOPPED);
|
|
|
|
if (fsm->cb.finished) {
|
|
fsm->cb.finished(fsm);
|
|
}
|
|
} else {
|
|
fsm->req_id = ++fsm->id;
|
|
|
|
ppp_send_pkt(fsm, NULL, PPP_TERMINATE_REQ, fsm->req_id,
|
|
fsm->terminate_reason,
|
|
strlen(fsm->terminate_reason));
|
|
|
|
fsm->retransmits--;
|
|
|
|
(void)k_delayed_work_submit(&fsm->timer,
|
|
FSM_TIMEOUT);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid",
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void ppp_pkt_send(struct k_work *work)
|
|
{
|
|
struct ppp_fsm *fsm = CONTAINER_OF(work, struct ppp_fsm,
|
|
sender.work);
|
|
int ret;
|
|
|
|
ret = net_send_data(fsm->sender.pkt);
|
|
if (ret < 0) {
|
|
net_pkt_unref(fsm->sender.pkt);
|
|
}
|
|
}
|
|
|
|
|
|
void ppp_fsm_init(struct ppp_fsm *fsm, u16_t protocol)
|
|
{
|
|
fsm->protocol = protocol;
|
|
fsm->state = PPP_INITIAL;
|
|
fsm->flags = 0U;
|
|
|
|
k_delayed_work_init(&fsm->timer, ppp_fsm_timeout);
|
|
k_delayed_work_init(&fsm->sender.work, ppp_pkt_send);
|
|
}
|
|
|
|
static void terminate(struct ppp_fsm *fsm, enum ppp_state next_state)
|
|
{
|
|
if (fsm->state != PPP_OPENED) {
|
|
k_delayed_work_cancel(&fsm->timer);
|
|
} else if (fsm->cb.down) {
|
|
fsm->cb.down(fsm);
|
|
}
|
|
|
|
fsm->retransmits = MAX_CONFIGURE_REQ;
|
|
fsm->req_id = ++fsm->id;
|
|
|
|
(void)ppp_send_pkt(fsm, NULL, PPP_TERMINATE_REQ, fsm->req_id,
|
|
fsm->terminate_reason,
|
|
strlen(fsm->terminate_reason));
|
|
|
|
if (fsm->retransmits == 0) {
|
|
ppp_change_state(fsm, next_state);
|
|
|
|
if (fsm->cb.finished) {
|
|
fsm->cb.finished(fsm);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
(void)k_delayed_work_submit(&fsm->timer, FSM_TIMEOUT);
|
|
|
|
fsm->retransmits--;
|
|
|
|
ppp_change_state(fsm, next_state);
|
|
}
|
|
|
|
void ppp_fsm_close(struct ppp_fsm *fsm, const u8_t *reason)
|
|
{
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
switch (fsm->state) {
|
|
case PPP_ACK_RECEIVED:
|
|
case PPP_ACK_SENT:
|
|
case PPP_OPENED:
|
|
case PPP_REQUEST_SENT:
|
|
if (reason) {
|
|
int len = strlen(reason);
|
|
|
|
len = MIN(sizeof(fsm->terminate_reason) - 1, len);
|
|
strncpy(fsm->terminate_reason, reason, len);
|
|
}
|
|
|
|
terminate(fsm, PPP_CLOSING);
|
|
break;
|
|
|
|
case PPP_INITIAL:
|
|
case PPP_STARTING:
|
|
ppp_change_state(fsm, PPP_INITIAL);
|
|
break;
|
|
|
|
case PPP_STOPPED:
|
|
ppp_change_state(fsm, PPP_CLOSED);
|
|
break;
|
|
|
|
case PPP_STOPPING:
|
|
ppp_change_state(fsm, PPP_CLOSING);
|
|
break;
|
|
|
|
default:
|
|
NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid",
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ppp_fsm_lower_down(struct ppp_fsm *fsm)
|
|
{
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
switch (fsm->state) {
|
|
case PPP_ACK_RECEIVED:
|
|
case PPP_ACK_SENT:
|
|
case PPP_REQUEST_SENT:
|
|
case PPP_STOPPING:
|
|
ppp_change_state(fsm, PPP_STARTING);
|
|
k_delayed_work_cancel(&fsm->timer);
|
|
break;
|
|
|
|
case PPP_CLOSED:
|
|
ppp_change_state(fsm, PPP_INITIAL);
|
|
break;
|
|
|
|
case PPP_CLOSING:
|
|
ppp_change_state(fsm, PPP_INITIAL);
|
|
k_delayed_work_cancel(&fsm->timer);
|
|
break;
|
|
|
|
case PPP_OPENED:
|
|
ppp_change_state(fsm, PPP_STARTING);
|
|
if (fsm->cb.down) {
|
|
fsm->cb.down(fsm);
|
|
}
|
|
|
|
break;
|
|
|
|
case PPP_STOPPED:
|
|
ppp_change_state(fsm, PPP_STARTING);
|
|
if (fsm->cb.starting) {
|
|
fsm->cb.starting(fsm);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid",
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ppp_fsm_lower_up(struct ppp_fsm *fsm)
|
|
{
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
switch (fsm->state) {
|
|
case PPP_CLOSED:
|
|
break;
|
|
|
|
case PPP_INITIAL:
|
|
ppp_change_state(fsm, PPP_CLOSED);
|
|
break;
|
|
|
|
case PPP_STARTING:
|
|
fsm_send_configure_req(fsm, false);
|
|
ppp_change_state(fsm, PPP_REQUEST_SENT);
|
|
|
|
break;
|
|
|
|
default:
|
|
NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid",
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ppp_fsm_open(struct ppp_fsm *fsm)
|
|
{
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
switch (fsm->state) {
|
|
case PPP_CLOSED:
|
|
fsm_send_configure_req(fsm, false);
|
|
ppp_change_state(fsm, PPP_REQUEST_SENT);
|
|
break;
|
|
|
|
case PPP_CLOSING:
|
|
ppp_change_state(fsm, PPP_STOPPING);
|
|
if (fsm->flags & FSM_RESTART) {
|
|
ppp_fsm_lower_down(fsm);
|
|
ppp_fsm_lower_up(fsm);
|
|
}
|
|
|
|
break;
|
|
|
|
case PPP_INITIAL:
|
|
ppp_change_state(fsm, PPP_STARTING);
|
|
if (fsm->cb.starting) {
|
|
fsm->cb.starting(fsm);
|
|
}
|
|
|
|
break;
|
|
|
|
case PPP_OPENED:
|
|
case PPP_STOPPED:
|
|
if (fsm->flags & FSM_RESTART) {
|
|
ppp_fsm_lower_down(fsm);
|
|
ppp_fsm_lower_up(fsm);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid",
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int ppp_send_pkt(struct ppp_fsm *fsm, struct net_if *iface,
|
|
enum ppp_packet_type type, u8_t id,
|
|
void *data, u32_t data_len)
|
|
{
|
|
/* Note that the data parameter is the received PPP packet if
|
|
* we want to send PROTOCOL or CODE reject packet.
|
|
*/
|
|
struct net_pkt *req_pkt = data;
|
|
struct ppp_packet ppp;
|
|
struct net_pkt *pkt;
|
|
size_t len = 0;
|
|
int ret;
|
|
|
|
if (!iface) {
|
|
struct ppp_context *ctx;
|
|
|
|
if (fsm->protocol == PPP_LCP) {
|
|
ctx = CONTAINER_OF(fsm, struct ppp_context, lcp.fsm);
|
|
#if defined(CONFIG_NET_IPV4)
|
|
} else if (fsm->protocol == PPP_IPCP) {
|
|
ctx = CONTAINER_OF(fsm, struct ppp_context, ipcp.fsm);
|
|
#endif
|
|
} else {
|
|
return -ENOENT;
|
|
}
|
|
|
|
NET_ASSERT(ctx->iface);
|
|
|
|
iface = ctx->iface;
|
|
}
|
|
|
|
switch (type) {
|
|
case PPP_PROTOCOL_REJ:
|
|
case PPP_CODE_REJ:
|
|
len = net_pkt_get_len(req_pkt);
|
|
len = MIN(len, PPP_MRU);
|
|
break;
|
|
|
|
case PPP_CONFIGURE_ACK:
|
|
case PPP_CONFIGURE_NACK:
|
|
case PPP_CONFIGURE_REJ:
|
|
case PPP_CONFIGURE_REQ:
|
|
/* 2 + 1 + 1 (configure-[req|ack|nack|rej]) +
|
|
* data_len (options)
|
|
*/
|
|
len = sizeof(u16_t) + sizeof(u8_t) + sizeof(u8_t) + data_len;
|
|
break;
|
|
|
|
case PPP_DISCARD_REQ:
|
|
break;
|
|
|
|
case PPP_ECHO_REQ:
|
|
len = sizeof(u16_t) + sizeof(u8_t) + sizeof(u8_t) +
|
|
sizeof(u32_t) + data_len;
|
|
break;
|
|
|
|
case PPP_ECHO_REPLY:
|
|
break;
|
|
|
|
case PPP_TERMINATE_REQ:
|
|
case PPP_TERMINATE_ACK:
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (len == 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
ppp.code = type;
|
|
ppp.id = id;
|
|
ppp.length = htons(len);
|
|
|
|
pkt = net_pkt_alloc_with_buffer(iface,
|
|
sizeof(u16_t) + len,
|
|
AF_UNSPEC, 0, BUF_ALLOC_TIMEOUT);
|
|
if (!pkt) {
|
|
goto out_of_mem;
|
|
}
|
|
|
|
ret = net_pkt_write_be16(pkt, fsm->protocol);
|
|
if (ret < 0) {
|
|
goto out_of_mem;
|
|
}
|
|
|
|
ret = net_pkt_write(pkt, &ppp, sizeof(ppp));
|
|
if (ret < 0) {
|
|
goto out_of_mem;
|
|
}
|
|
|
|
if (type == PPP_CODE_REJ) {
|
|
if (!req_pkt) {
|
|
goto out_of_mem;
|
|
}
|
|
|
|
net_pkt_cursor_init(req_pkt);
|
|
net_pkt_copy(pkt, req_pkt, len);
|
|
|
|
} else if (type == PPP_ECHO_REQ) {
|
|
struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context,
|
|
lcp.fsm);
|
|
if (ctx->lcp.magic) {
|
|
ctx->lcp.magic = sys_rand32_get();
|
|
}
|
|
|
|
ret = net_pkt_write_be32(pkt, ctx->lcp.magic);
|
|
if (ret < 0) {
|
|
goto out_of_mem;
|
|
}
|
|
|
|
data_len = MIN(data_len, PPP_MRU);
|
|
if (data_len > 0) {
|
|
if (data_len == sizeof(u32_t)) {
|
|
ret = net_pkt_write_be32(pkt,
|
|
POINTER_TO_UINT(data));
|
|
} else {
|
|
ret = net_pkt_write(pkt, data, data_len);
|
|
}
|
|
|
|
if (ret < 0) {
|
|
goto out_of_mem;
|
|
}
|
|
}
|
|
|
|
} else if (type == PPP_CONFIGURE_ACK || type == PPP_CONFIGURE_REQ ||
|
|
type == PPP_CONFIGURE_REJ || type == PPP_CONFIGURE_NACK) {
|
|
/* add options */
|
|
if (data) {
|
|
net_buf_frag_add(pkt->buffer, data);
|
|
}
|
|
}
|
|
|
|
NET_DBG("[%s/%p] Sending %zd bytes pkt %p (options len %d)", fsm->name,
|
|
fsm, net_pkt_get_len(pkt), pkt, data_len);
|
|
|
|
net_pkt_set_ppp(pkt, true);
|
|
|
|
/* Do not call net_send_data() directly in order to make this thread
|
|
* run before the sending happens. If we call the net_send_data() from
|
|
* this thread, then in fast link (like when running inside QEMU) the
|
|
* reply might arrive before we have returned from this function. That
|
|
* is bad because the fsm would be in wrong state and the received pkt
|
|
* is dropped.
|
|
*/
|
|
fsm->sender.pkt = pkt;
|
|
|
|
/* FIXME: qemu_x86 crashes if timeout is 0 when running ppp
|
|
* driver unit test. As a workaround set the timeout to 1 msec
|
|
* in that case.
|
|
*/
|
|
(void)k_delayed_work_submit(&fsm->sender.work,
|
|
IS_ENABLED(CONFIG_NET_TEST) ? K_MSEC(1) : 0);
|
|
|
|
return 0;
|
|
|
|
out_of_mem:
|
|
if (pkt) {
|
|
net_pkt_unref(pkt);
|
|
}
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static enum net_verdict fsm_recv_configure_req(struct ppp_fsm *fsm,
|
|
u8_t id,
|
|
struct net_pkt *pkt,
|
|
u16_t remaining_len)
|
|
{
|
|
struct net_buf *buf = NULL;
|
|
int len = 0;
|
|
enum ppp_packet_type code;
|
|
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
switch (fsm->state) {
|
|
case PPP_ACK_SENT:
|
|
case PPP_ACK_RECEIVED:
|
|
break;
|
|
|
|
case PPP_CLOSED:
|
|
(void)ppp_send_pkt(fsm, net_pkt_iface(pkt), PPP_TERMINATE_ACK,
|
|
id, NULL, 0);
|
|
return NET_OK;
|
|
|
|
case PPP_CLOSING:
|
|
case PPP_STOPPING:
|
|
return NET_OK;
|
|
|
|
case PPP_OPENED:
|
|
if (fsm->cb.down) {
|
|
fsm->cb.down(fsm);
|
|
}
|
|
|
|
fsm_send_configure_req(fsm, false);
|
|
ppp_change_state(fsm, PPP_REQUEST_SENT);
|
|
break;
|
|
|
|
case PPP_REQUEST_SENT:
|
|
/* Received request while waiting ACK */
|
|
break;
|
|
|
|
case PPP_STOPPED:
|
|
fsm_send_configure_req(fsm, false);
|
|
ppp_change_state(fsm, PPP_REQUEST_SENT);
|
|
break;
|
|
|
|
default:
|
|
NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid",
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
return NET_DROP;
|
|
}
|
|
|
|
if (fsm->cb.config_info_req) {
|
|
int ret;
|
|
|
|
ret = fsm->cb.config_info_req(fsm, pkt, remaining_len, &buf);
|
|
if (ret < 0) {
|
|
return NET_DROP;
|
|
}
|
|
|
|
if (fsm->nack_loops >= MAX_NACK_LOOPS &&
|
|
ret == PPP_CONFIGURE_NACK) {
|
|
ret = PPP_CONFIGURE_REJ;
|
|
}
|
|
|
|
code = ret;
|
|
len = net_buf_frags_len(buf);
|
|
|
|
} else if (remaining_len) {
|
|
/* If there are any options at this point, then reject.
|
|
* TODO: construct the NACKed options buf
|
|
*/
|
|
code = PPP_CONFIGURE_REJ;
|
|
} else {
|
|
code = PPP_CONFIGURE_ACK;
|
|
}
|
|
|
|
NET_DBG("[%s/%p] Sending %s (%d) id %d to peer while in %s (%d)",
|
|
fsm->name, fsm, ppp_pkt_type2str(code), code, id,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
(void)ppp_send_pkt(fsm, NULL, code, id, buf, len);
|
|
|
|
if (code == PPP_CONFIGURE_ACK) {
|
|
if (fsm->state == PPP_ACK_RECEIVED) {
|
|
k_delayed_work_cancel(&fsm->timer);
|
|
|
|
ppp_change_state(fsm, PPP_OPENED);
|
|
|
|
if (fsm->cb.up) {
|
|
fsm->cb.up(fsm);
|
|
}
|
|
} else {
|
|
ppp_change_state(fsm, PPP_ACK_SENT);
|
|
}
|
|
|
|
fsm->nack_loops = 0;
|
|
} else {
|
|
if (fsm->state != PPP_ACK_RECEIVED) {
|
|
ppp_change_state(fsm, PPP_REQUEST_SENT);
|
|
}
|
|
|
|
if (code == PPP_CONFIGURE_NACK) {
|
|
fsm->nack_loops++;
|
|
}
|
|
}
|
|
|
|
return NET_OK;
|
|
}
|
|
|
|
static enum net_verdict fsm_recv_configure_ack(struct ppp_fsm *fsm, u8_t id,
|
|
struct net_pkt *pkt,
|
|
u16_t remaining_len)
|
|
{
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
if (id != fsm->req_id || fsm->ack_received) {
|
|
return NET_DROP;
|
|
}
|
|
|
|
if (fsm->cb.config_info_ack) {
|
|
if (fsm->cb.config_info_ack(fsm, pkt, remaining_len) < 0) {
|
|
NET_DBG("[%s/%p] %s %s received", fsm->name, fsm,
|
|
"Invalid",
|
|
ppp_pkt_type2str(PPP_CONFIGURE_ACK));
|
|
return NET_DROP;
|
|
}
|
|
}
|
|
|
|
fsm->ack_received = true;
|
|
fsm->recv_nack_loops = 0;
|
|
|
|
switch (fsm->state) {
|
|
case PPP_ACK_RECEIVED:
|
|
k_delayed_work_cancel(&fsm->timer);
|
|
fsm_send_configure_req(fsm, false);
|
|
ppp_change_state(fsm, PPP_REQUEST_SENT);
|
|
break;
|
|
|
|
case PPP_ACK_SENT:
|
|
k_delayed_work_cancel(&fsm->timer);
|
|
ppp_change_state(fsm, PPP_OPENED);
|
|
fsm->retransmits = MAX_CONFIGURE_REQ;
|
|
if (fsm->cb.up) {
|
|
fsm->cb.up(fsm);
|
|
}
|
|
|
|
break;
|
|
|
|
case PPP_CLOSED:
|
|
case PPP_STOPPED:
|
|
(void)ppp_send_pkt(fsm, net_pkt_iface(pkt), PPP_TERMINATE_ACK,
|
|
id, NULL, 0);
|
|
break;
|
|
|
|
case PPP_OPENED:
|
|
if (fsm->cb.down) {
|
|
fsm->cb.down(fsm);
|
|
}
|
|
|
|
fsm_send_configure_req(fsm, false);
|
|
ppp_change_state(fsm, PPP_REQUEST_SENT);
|
|
break;
|
|
|
|
case PPP_REQUEST_SENT:
|
|
ppp_change_state(fsm, PPP_ACK_RECEIVED);
|
|
fsm->retransmits = MAX_CONFIGURE_REQ;
|
|
break;
|
|
|
|
default:
|
|
NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid",
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
return NET_DROP;
|
|
}
|
|
|
|
return NET_OK;
|
|
}
|
|
|
|
static enum net_verdict fsm_recv_configure_nack_rej(struct ppp_fsm *fsm,
|
|
enum ppp_packet_type code,
|
|
u8_t id,
|
|
struct net_pkt *pkt,
|
|
u16_t length)
|
|
{
|
|
bool ret = false;
|
|
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
if (id != fsm->req_id || fsm->ack_received) {
|
|
return NET_DROP;
|
|
}
|
|
|
|
if (code == PPP_CONFIGURE_NACK) {
|
|
bool rejected = false;
|
|
|
|
fsm->recv_nack_loops++;
|
|
|
|
if (fsm->recv_nack_loops >= MAX_NACK_LOOPS) {
|
|
rejected = true;
|
|
}
|
|
|
|
if (fsm->cb.config_info_nack) {
|
|
int err;
|
|
|
|
err = fsm->cb.config_info_nack(fsm, pkt, length,
|
|
rejected);
|
|
if (err < 0) {
|
|
NET_DBG("[%s/%p] %s failed (%d)",
|
|
fsm->name, fsm, "Nack", err);
|
|
} else {
|
|
ret = true;
|
|
}
|
|
}
|
|
|
|
if (!ret) {
|
|
NET_DBG("[%s/%p] %s %s (id %d)", fsm->name, fsm,
|
|
"Invalid", ppp_pkt_type2str(code), id);
|
|
return NET_DROP;
|
|
}
|
|
} else {
|
|
fsm->recv_nack_loops = 0;
|
|
|
|
if (fsm->cb.config_info_rej) {
|
|
int err;
|
|
|
|
err = fsm->cb.config_info_rej(fsm, pkt, length);
|
|
if (err < 0) {
|
|
NET_DBG("[%s/%p] %s failed (%d)",
|
|
fsm->name, fsm, "Reject", err);
|
|
} else {
|
|
ret = true;
|
|
}
|
|
}
|
|
|
|
if (!ret) {
|
|
NET_DBG("[%s/%p] %s %s (id %d)", fsm->name, fsm,
|
|
"Invalid", ppp_pkt_type2str(code), id);
|
|
return NET_DROP;
|
|
}
|
|
}
|
|
|
|
fsm->ack_received = true;
|
|
|
|
switch (fsm->state) {
|
|
case PPP_ACK_RECEIVED:
|
|
k_delayed_work_cancel(&fsm->timer);
|
|
fsm_send_configure_req(fsm, false);
|
|
ppp_change_state(fsm, PPP_REQUEST_SENT);
|
|
break;
|
|
|
|
case PPP_ACK_SENT:
|
|
case PPP_REQUEST_SENT:
|
|
k_delayed_work_cancel(&fsm->timer);
|
|
fsm_send_configure_req(fsm, false);
|
|
break;
|
|
|
|
case PPP_CLOSED:
|
|
case PPP_STOPPED:
|
|
(void)ppp_send_pkt(fsm, net_pkt_iface(pkt), PPP_TERMINATE_ACK,
|
|
id, NULL, 0);
|
|
break;
|
|
|
|
case PPP_OPENED:
|
|
if (fsm->cb.down) {
|
|
fsm->cb.down(fsm);
|
|
}
|
|
|
|
fsm_send_configure_req(fsm, false);
|
|
ppp_change_state(fsm, PPP_REQUEST_SENT);
|
|
break;
|
|
|
|
default:
|
|
NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid",
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
return NET_DROP;
|
|
}
|
|
|
|
return NET_OK;
|
|
}
|
|
|
|
static enum net_verdict fsm_recv_terminate_req(struct ppp_fsm *fsm, u8_t id,
|
|
struct net_pkt *pkt,
|
|
u16_t length)
|
|
{
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
switch (fsm->state) {
|
|
case PPP_ACK_RECEIVED:
|
|
case PPP_ACK_SENT:
|
|
ppp_change_state(fsm, PPP_REQUEST_SENT);
|
|
break;
|
|
|
|
case PPP_OPENED:
|
|
if (length > 0) {
|
|
net_pkt_read(pkt, fsm->terminate_reason,
|
|
MIN(length,
|
|
sizeof(fsm->terminate_reason) - 1));
|
|
|
|
NET_DBG("[%s/%p] %s (%s)",
|
|
fsm->name, fsm, "Terminated by peer",
|
|
log_strdup(fsm->terminate_reason));
|
|
} else {
|
|
NET_DBG("[%s/%p] Terminated by peer",
|
|
fsm->name, fsm);
|
|
}
|
|
|
|
fsm->retransmits = 0;
|
|
ppp_change_state(fsm, PPP_STOPPING);
|
|
|
|
if (fsm->cb.down) {
|
|
fsm->cb.down(fsm);
|
|
}
|
|
|
|
(void)k_delayed_work_submit(&fsm->timer, FSM_TIMEOUT);
|
|
break;
|
|
|
|
default:
|
|
NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid",
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
return NET_DROP;
|
|
}
|
|
|
|
(void)ppp_send_pkt(fsm, net_pkt_iface(pkt), PPP_TERMINATE_ACK, id,
|
|
NULL, 0);
|
|
|
|
return NET_OK;
|
|
}
|
|
|
|
static enum net_verdict fsm_recv_terminate_ack(struct ppp_fsm *fsm, u8_t id,
|
|
struct net_pkt *pkt,
|
|
u16_t length)
|
|
{
|
|
enum ppp_state new_state;
|
|
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
switch (fsm->state) {
|
|
case PPP_CLOSING:
|
|
new_state = PPP_CLOSED;
|
|
goto stopped;
|
|
|
|
case PPP_OPENED:
|
|
if (fsm->cb.down) {
|
|
fsm->cb.down(fsm);
|
|
}
|
|
|
|
fsm_send_configure_req(fsm, false);
|
|
ppp_change_state(fsm, PPP_REQUEST_SENT);
|
|
break;
|
|
|
|
case PPP_STOPPING:
|
|
new_state = PPP_STOPPED;
|
|
goto stopped;
|
|
|
|
case PPP_ACK_RECEIVED:
|
|
ppp_change_state(fsm, PPP_REQUEST_SENT);
|
|
break;
|
|
|
|
default:
|
|
NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid",
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
return NET_DROP;
|
|
}
|
|
|
|
return NET_OK;
|
|
|
|
stopped:
|
|
k_delayed_work_cancel(&fsm->timer);
|
|
ppp_change_state(fsm, new_state);
|
|
|
|
if (fsm->cb.finished) {
|
|
fsm->cb.finished(fsm);
|
|
}
|
|
|
|
return NET_OK;
|
|
}
|
|
|
|
static enum net_verdict fsm_recv_code_rej(struct ppp_fsm *fsm,
|
|
struct net_pkt *pkt)
|
|
{
|
|
u8_t code, id;
|
|
int ret;
|
|
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
ret = net_pkt_read_u8(pkt, &code);
|
|
if (ret < 0) {
|
|
return NET_DROP;
|
|
}
|
|
|
|
ret = net_pkt_read_u8(pkt, &id);
|
|
if (ret < 0) {
|
|
return NET_DROP;
|
|
}
|
|
|
|
NET_DBG("[%s/%p] Received Code-Rej code %d id %d", fsm->name, fsm,
|
|
code, id);
|
|
|
|
if (fsm->state == PPP_ACK_RECEIVED) {
|
|
ppp_change_state(fsm, PPP_REQUEST_SENT);
|
|
}
|
|
|
|
return NET_OK;
|
|
}
|
|
|
|
void ppp_fsm_proto_reject(struct ppp_fsm *fsm)
|
|
{
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
switch (fsm->state) {
|
|
case PPP_ACK_RECEIVED:
|
|
case PPP_ACK_SENT:
|
|
case PPP_STOPPING:
|
|
case PPP_REQUEST_SENT:
|
|
k_delayed_work_cancel(&fsm->timer);
|
|
ppp_change_state(fsm, PPP_STOPPED);
|
|
if (fsm->cb.finished) {
|
|
fsm->cb.finished(fsm);
|
|
}
|
|
|
|
break;
|
|
|
|
case PPP_CLOSED:
|
|
ppp_change_state(fsm, PPP_CLOSED);
|
|
if (fsm->cb.finished) {
|
|
fsm->cb.finished(fsm);
|
|
}
|
|
|
|
break;
|
|
|
|
case PPP_CLOSING:
|
|
k_delayed_work_cancel(&fsm->timer);
|
|
ppp_change_state(fsm, PPP_CLOSED);
|
|
if (fsm->cb.finished) {
|
|
fsm->cb.finished(fsm);
|
|
}
|
|
|
|
break;
|
|
|
|
case PPP_OPENED:
|
|
terminate(fsm, PPP_STOPPING);
|
|
break;
|
|
|
|
case PPP_STOPPED:
|
|
ppp_change_state(fsm, PPP_STOPPED);
|
|
if (fsm->cb.finished) {
|
|
fsm->cb.finished(fsm);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
NET_DBG("[%s/%p] %s state %s (%d)", fsm->name, fsm, "Invalid",
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
enum net_verdict ppp_fsm_input(struct ppp_fsm *fsm, u16_t proto,
|
|
struct net_pkt *pkt)
|
|
{
|
|
u8_t code, id;
|
|
u16_t length;
|
|
int ret;
|
|
|
|
ret = net_pkt_read_u8(pkt, &code);
|
|
if (ret < 0) {
|
|
NET_DBG("[%s/%p] Cannot read %s (pkt len %zd)",
|
|
fsm->name, fsm, "code", net_pkt_get_len(pkt));
|
|
return NET_DROP;
|
|
}
|
|
|
|
ret = net_pkt_read_u8(pkt, &id);
|
|
if (ret < 0) {
|
|
NET_DBG("[%s/%p] Cannot read %s (pkt len %zd)",
|
|
fsm->name, fsm, "id", net_pkt_get_len(pkt));
|
|
return NET_DROP;
|
|
}
|
|
|
|
ret = net_pkt_read_be16(pkt, &length);
|
|
if (ret < 0) {
|
|
NET_DBG("[%s/%p] Cannot read %s (pkt len %zd)",
|
|
fsm->name, fsm, "length", net_pkt_get_len(pkt));
|
|
return NET_DROP;
|
|
}
|
|
|
|
if (length > PPP_MRU) {
|
|
NET_DBG("[%s/%p] Too long msg %d", fsm->name, fsm, length);
|
|
return NET_DROP;
|
|
}
|
|
|
|
if (fsm->state == PPP_INITIAL || fsm->state == PPP_STARTING) {
|
|
NET_DBG("[%s/%p] Received %s packet in wrong state %s (%d)",
|
|
fsm->name, fsm, ppp_proto2str(proto),
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
return NET_DROP;
|
|
}
|
|
|
|
/* Length will only contain payload/data length */
|
|
length -= sizeof(code) + sizeof(id) + sizeof(length);
|
|
|
|
NET_DBG("[%s/%p] %s %s (%d) id %d payload len %d", fsm->name, fsm,
|
|
ppp_proto2str(proto), ppp_pkt_type2str(code), code, id,
|
|
length);
|
|
|
|
switch (code) {
|
|
case PPP_CODE_REJ:
|
|
return fsm_recv_code_rej(fsm, pkt);
|
|
|
|
case PPP_CONFIGURE_ACK:
|
|
return fsm_recv_configure_ack(fsm, id, pkt, length);
|
|
|
|
case PPP_CONFIGURE_NACK:
|
|
return fsm_recv_configure_nack_rej(fsm, code, id, pkt, length);
|
|
|
|
case PPP_CONFIGURE_REQ:
|
|
return fsm_recv_configure_req(fsm, id, pkt, length);
|
|
|
|
case PPP_CONFIGURE_REJ:
|
|
return fsm_recv_configure_nack_rej(fsm, code, id, pkt, length);
|
|
|
|
case PPP_TERMINATE_ACK:
|
|
return fsm_recv_terminate_ack(fsm, id, pkt, length);
|
|
|
|
case PPP_TERMINATE_REQ:
|
|
return fsm_recv_terminate_req(fsm, id, pkt, length);
|
|
|
|
default:
|
|
if (fsm->cb.proto_extension) {
|
|
enum net_verdict verdict;
|
|
|
|
verdict = fsm->cb.proto_extension(fsm, code, id, pkt);
|
|
if (verdict != NET_DROP) {
|
|
return verdict;
|
|
}
|
|
}
|
|
|
|
(void)ppp_send_pkt(fsm, net_pkt_iface(pkt), PPP_CODE_REJ,
|
|
id, pkt, 0);
|
|
}
|
|
|
|
return NET_DROP;
|
|
}
|
|
|
|
enum net_verdict ppp_fsm_recv_protocol_rej(struct ppp_fsm *fsm,
|
|
u8_t id,
|
|
struct net_pkt *pkt)
|
|
{
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
return NET_DROP;
|
|
}
|
|
|
|
enum net_verdict ppp_fsm_recv_echo_req(struct ppp_fsm *fsm,
|
|
u8_t id,
|
|
struct net_pkt *pkt)
|
|
{
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
return NET_DROP;
|
|
}
|
|
|
|
enum net_verdict ppp_fsm_recv_echo_reply(struct ppp_fsm *fsm,
|
|
u8_t id,
|
|
struct net_pkt *pkt)
|
|
{
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
return NET_DROP;
|
|
}
|
|
|
|
enum net_verdict ppp_fsm_recv_discard_req(struct ppp_fsm *fsm,
|
|
u8_t id,
|
|
struct net_pkt *pkt)
|
|
{
|
|
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
|
|
ppp_state_str(fsm->state), fsm->state);
|
|
|
|
return NET_DROP;
|
|
}
|