| /* |
| * 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 out_of_mem; |
| } |
| |
| if (!buf) { |
| buf = nack_buf; |
| } |
| |
| added = append_to_buf(nack_buf, |
| &nack_options[i].type.ipv6cp, 1); |
| if (!added) { |
| goto out_of_mem; |
| } |
| |
| added = append_to_buf(nack_buf, &nack_options[i].len, |
| 1); |
| if (!added) { |
| goto out_of_mem; |
| } |
| |
| /* 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 out_of_mem; |
| } |
| } |
| |
| continue; |
| |
| out_of_mem: |
| if (nack_buf) { |
| net_buf_unref(nack_buf); |
| } |
| |
| 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; |
| |
| 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); |