blob: 1f64de14f13c369f1442fde866fac503899c648e [file] [log] [blame]
/*
* Copyright (c) 2019 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(net_l2_ppp, CONFIG_NET_L2_PPP_LOG_LEVEL);
#include <zephyr/net/net_core.h>
#include <zephyr/net/net_pkt.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/ppp.h>
#include <zephyr/random/random.h>
#include "net_private.h"
#include "ppp_internal.h"
/* This timeout is in milliseconds */
#define FSM_TIMEOUT K_MSEC(CONFIG_NET_L2_PPP_TIMEOUT)
#define MAX_NACK_LOOPS CONFIG_NET_L2_PPP_MAX_NACK_LOOPS
struct ppp_context *ppp_fsm_ctx(struct ppp_fsm *fsm)
{
if (fsm->protocol == PPP_LCP) {
return CONTAINER_OF(fsm, struct ppp_context, lcp.fsm);
#if defined(CONFIG_NET_IPV4)
} else if (fsm->protocol == PPP_IPCP) {
return CONTAINER_OF(fsm, struct ppp_context, ipcp.fsm);
#endif
#if defined(CONFIG_NET_IPV6)
} else if (fsm->protocol == PPP_IPV6CP) {
return CONTAINER_OF(fsm, struct ppp_context, ipv6cp.fsm);
#endif
#if defined(CONFIG_NET_L2_PPP_PAP)
} else if (fsm->protocol == PPP_PAP) {
return CONTAINER_OF(fsm, struct ppp_context, pap.fsm);
#endif
}
return NULL;
}
struct net_if *ppp_fsm_iface(struct ppp_fsm *fsm)
{
struct ppp_context *ctx = ppp_fsm_ctx(fsm);
NET_ASSERT(ctx->iface);
return ctx->iface;
}
static void fsm_send_configure_req(struct ppp_fsm *fsm, bool retransmit)
{
struct net_pkt *pkt = 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) {
pkt = 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,
pkt, pkt ? net_pkt_get_len(pkt) : 0);
fsm->retransmits--;
(void)k_work_reschedule(&fsm->timer, FSM_TIMEOUT);
}
static void ppp_fsm_timeout(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct ppp_fsm *fsm = CONTAINER_OF(dwork, 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_work_reschedule(&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;
}
}
void ppp_fsm_init(struct ppp_fsm *fsm, uint16_t protocol)
{
fsm->protocol = protocol;
fsm->state = PPP_INITIAL;
fsm->flags = 0U;
k_work_init_delayable(&fsm->timer, ppp_fsm_timeout);
}
static void fsm_down(struct ppp_fsm *fsm)
{
size_t i;
for (i = 0; i < fsm->my_options.count; i++) {
fsm->my_options.data[i].flags = 0;
}
if (fsm->cb.down) {
fsm->cb.down(fsm);
}
}
static void terminate(struct ppp_fsm *fsm, enum ppp_state next_state)
{
if (fsm->state != PPP_OPENED) {
k_work_cancel_delayable(&fsm->timer);
} else {
fsm_down(fsm);
}
fsm->retransmits = MAX_TERMINATE_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_work_reschedule(&fsm->timer, FSM_TIMEOUT);
fsm->retransmits--;
ppp_change_state(fsm, next_state);
}
void ppp_fsm_close(struct ppp_fsm *fsm, const uint8_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_work_cancel_delayable(&fsm->timer);
break;
case PPP_CLOSED:
ppp_change_state(fsm, PPP_INITIAL);
break;
case PPP_CLOSING:
ppp_change_state(fsm, PPP_INITIAL);
k_work_cancel_delayable(&fsm->timer);
break;
case PPP_OPENED:
ppp_change_state(fsm, PPP_STARTING);
fsm_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:
ppp_change_state(fsm, PPP_REQUEST_SENT);
fsm_send_configure_req(fsm, false);
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, uint8_t id,
void *data, uint32_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;
uint16_t protocol = 0;
size_t len = 0;
struct ppp_packet ppp;
struct net_pkt *pkt = NULL;
int ret;
if (!iface) {
if (!fsm) {
return -ENOENT;
}
iface = ppp_fsm_iface(fsm);
}
if (!net_if_is_carrier_ok(iface)) {
return -ENETDOWN;
}
if (fsm) {
protocol = fsm->protocol;
}
switch (type) {
case PPP_CODE_REJ: {
struct ppp_context *ctx = ppp_fsm_ctx(fsm);
len = net_pkt_get_len(req_pkt);
len = MIN(len, ctx->lcp.my_options.mru);
break;
}
case PPP_CONFIGURE_ACK:
case PPP_CONFIGURE_NACK:
case PPP_CONFIGURE_REJ:
case PPP_CONFIGURE_REQ:
pkt = data;
/* 2 + 1 + 1 (configure-[req|ack|nack|rej]) +
* data_len (options)
*/
len = sizeof(ppp) + data_len;
break;
case PPP_DISCARD_REQ:
break;
case PPP_ECHO_REQ:
len = sizeof(ppp) + sizeof(uint32_t) + data_len;
break;
case PPP_ECHO_REPLY:
len = sizeof(ppp) + net_pkt_remaining_data(req_pkt);
break;
case PPP_PROTOCOL_REJ:
len = sizeof(ppp) + sizeof(uint16_t) +
net_pkt_remaining_data(req_pkt);
protocol = PPP_LCP;
break;
case PPP_TERMINATE_REQ:
case PPP_TERMINATE_ACK:
len = sizeof(ppp);
break;
default:
break;
}
if (len < sizeof(ppp)) {
return -EINVAL;
}
ppp.code = type;
ppp.id = id;
ppp.length = htons(len);
if (!pkt) {
pkt = net_pkt_alloc_with_buffer(iface,
sizeof(uint16_t) + len,
AF_UNSPEC, 0,
PPP_BUF_ALLOC_TIMEOUT);
if (!pkt) {
goto out_of_mem;
}
} else {
struct net_buf *buf;
buf = net_pkt_get_reserve_tx_data(sizeof(uint16_t) + len,
PPP_BUF_ALLOC_TIMEOUT);
if (!buf) {
LOG_ERR("failed to allocate buffer");
goto out_of_mem;
}
net_pkt_frag_insert(pkt, buf);
net_pkt_cursor_init(pkt);
}
ret = net_pkt_write_be16(pkt, 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, ctx->lcp.my_options.mru);
if (data_len > 0) {
if (data_len == sizeof(uint32_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_ECHO_REPLY) {
net_pkt_copy(pkt, req_pkt, len);
} else if (type == PPP_PROTOCOL_REJ) {
net_pkt_cursor_init(req_pkt);
net_pkt_copy(pkt, req_pkt, len);
}
NET_DBG("[%s/%p] Sending %zd bytes pkt %p (options len %d)",
fsm ? fsm->name : "?", fsm, net_pkt_get_len(pkt), pkt,
data_len);
net_pkt_set_ppp(pkt, true);
if (fsm) {
/* 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.
*/
ppp_queue_pkt(pkt);
} else {
ret = net_send_data(pkt);
if (ret < 0) {
net_pkt_unref(pkt);
}
}
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,
uint8_t id,
struct net_pkt *pkt,
uint16_t remaining_len)
{
struct net_pkt *out = 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:
fsm_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;
}
out = net_pkt_alloc_with_buffer(net_pkt_iface(pkt),
sizeof(uint16_t) + sizeof(uint16_t) +
sizeof(uint8_t) + sizeof(uint8_t) +
remaining_len,
AF_UNSPEC, 0, PPP_BUF_ALLOC_TIMEOUT);
if (!out) {
return NET_DROP;
}
net_pkt_cursor_init(out);
if (fsm->cb.config_info_req) {
int ret;
ret = fsm->cb.config_info_req(fsm, pkt, remaining_len, out);
if (ret < 0) {
goto unref_out_pkt;
}
if (fsm->nack_loops >= MAX_NACK_LOOPS &&
ret == PPP_CONFIGURE_NACK) {
ret = PPP_CONFIGURE_REJ;
}
code = ret;
len = net_pkt_get_len(out);
} else if (remaining_len) {
code = PPP_CONFIGURE_REJ;
net_pkt_copy(out, pkt, remaining_len);
len = remaining_len;
} 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, out, len);
if (code == PPP_CONFIGURE_ACK) {
if (fsm->state == PPP_ACK_RECEIVED) {
k_work_cancel_delayable(&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;
unref_out_pkt:
net_pkt_unref(out);
return NET_DROP;
}
static enum net_verdict fsm_recv_configure_ack(struct ppp_fsm *fsm, uint8_t id,
struct net_pkt *pkt,
uint16_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_work_cancel_delayable(&fsm->timer);
fsm_send_configure_req(fsm, false);
ppp_change_state(fsm, PPP_REQUEST_SENT);
break;
case PPP_ACK_SENT:
k_work_cancel_delayable(&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:
fsm_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,
uint8_t id,
struct net_pkt *pkt,
uint16_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_work_cancel_delayable(&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_work_cancel_delayable(&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:
fsm_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, uint8_t id,
struct net_pkt *pkt,
uint16_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",
fsm->terminate_reason);
} else {
NET_DBG("[%s/%p] Terminated by peer",
fsm->name, fsm);
}
fsm->retransmits = 0;
ppp_change_state(fsm, PPP_STOPPING);
fsm_down(fsm);
(void)k_work_reschedule(&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, uint8_t id,
struct net_pkt *pkt,
uint16_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:
fsm_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_work_cancel_delayable(&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)
{
uint8_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_work_cancel_delayable(&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_work_cancel_delayable(&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, uint16_t proto,
struct net_pkt *pkt)
{
uint8_t code, id;
uint16_t length;
int ret;
struct ppp_context *ctx = ppp_fsm_ctx(fsm);
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 > ctx->lcp.my_options.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,
uint8_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,
uint8_t id,
struct net_pkt *pkt)
{
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
ppp_state_str(fsm->state), fsm->state);
(void)ppp_send_pkt(fsm, net_pkt_iface(pkt), PPP_ECHO_REPLY,
id, pkt, 0);
return NET_OK;
}
enum net_verdict ppp_fsm_recv_echo_reply(struct ppp_fsm *fsm,
uint8_t id,
struct net_pkt *pkt)
{
NET_DBG("[%s/%p] Current state %s (%d)", fsm->name, fsm,
ppp_state_str(fsm->state), fsm->state);
#if defined(CONFIG_NET_SHELL)
struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context,
lcp.fsm);
if (ctx->shell.echo_reply.cb) {
ctx->shell.echo_reply.cb(ctx->shell.echo_reply.user_data,
ctx->shell.echo_reply.user_data_len);
}
#endif /* CONFIG_NET_SHELL */
return NET_OK;
}
enum net_verdict ppp_fsm_recv_discard_req(struct ppp_fsm *fsm,
uint8_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_OK;
}
void ppp_send_proto_rej(struct net_if *iface, struct net_pkt *pkt,
uint16_t protocol)
{
uint8_t code, id;
int ret;
ret = net_pkt_read_u8(pkt, &code);
if (ret < 0) {
goto quit;
}
ret = net_pkt_read_u8(pkt, &id);
if (ret < 0) {
goto quit;
}
(void)ppp_send_pkt(NULL, iface, PPP_PROTOCOL_REJ, id, pkt, 0);
quit:
return;
}