| /* |
| * Copyright (c) 2019 Intel Corporation. |
| * Copyright (c) 2020 Endian Technologies AB |
| * |
| * 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/ppp.h> |
| #include <zephyr/net/dns_resolve.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); |
| } |
| |
| /* Length is (6): code + id + IPv4 address length. RFC 1332 and also |
| * DNS in RFC 1877. |
| */ |
| #define IP_ADDRESS_OPTION_LEN (1 + 1 + 4) |
| |
| static int ipcp_add_address(struct ppp_context *ctx, struct net_pkt *pkt, |
| struct in_addr *addr) |
| { |
| net_pkt_write_u8(pkt, 1 + 1 + sizeof(addr->s_addr)); |
| return net_pkt_write(pkt, &addr->s_addr, sizeof(addr->s_addr)); |
| } |
| |
| static int ipcp_add_ip_address(struct ppp_context *ctx, struct net_pkt *pkt) |
| { |
| return ipcp_add_address(ctx, pkt, &ctx->ipcp.my_options.address); |
| } |
| |
| static int ipcp_add_dns1(struct ppp_context *ctx, struct net_pkt *pkt) |
| { |
| return ipcp_add_address(ctx, pkt, &ctx->ipcp.my_options.dns1_address); |
| } |
| |
| static int ipcp_add_dns2(struct ppp_context *ctx, struct net_pkt *pkt) |
| { |
| return ipcp_add_address(ctx, pkt, &ctx->ipcp.my_options.dns2_address); |
| } |
| |
| static int ipcp_ack_check_address(struct net_pkt *pkt, size_t oplen, |
| struct in_addr *addr) |
| { |
| struct in_addr ack_addr; |
| int ret; |
| |
| if (oplen != sizeof(ack_addr)) { |
| return -EINVAL; |
| } |
| |
| ret = net_pkt_read(pkt, &ack_addr, sizeof(ack_addr)); |
| if (ret) { |
| return ret; |
| } |
| |
| if (memcmp(&ack_addr, addr, sizeof(ack_addr)) != 0) { |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int ipcp_ack_ip_address(struct ppp_context *ctx, struct net_pkt *pkt, |
| uint8_t oplen) |
| { |
| return ipcp_ack_check_address(pkt, oplen, |
| &ctx->ipcp.my_options.address); |
| } |
| |
| static int ipcp_ack_dns1(struct ppp_context *ctx, struct net_pkt *pkt, |
| uint8_t oplen) |
| { |
| return ipcp_ack_check_address(pkt, oplen, |
| &ctx->ipcp.my_options.dns1_address); |
| } |
| |
| static int ipcp_ack_dns2(struct ppp_context *ctx, struct net_pkt *pkt, |
| uint8_t oplen) |
| { |
| return ipcp_ack_check_address(pkt, oplen, |
| &ctx->ipcp.my_options.dns2_address); |
| } |
| |
| static int ipcp_nak_override_address(struct net_pkt *pkt, size_t oplen, |
| struct in_addr *addr) |
| { |
| if (oplen != sizeof(*addr)) { |
| return -EINVAL; |
| } |
| |
| return net_pkt_read(pkt, addr, sizeof(*addr)); |
| } |
| |
| static int ipcp_nak_ip_address(struct ppp_context *ctx, struct net_pkt *pkt, |
| uint8_t oplen) |
| { |
| return ipcp_nak_override_address(pkt, oplen, |
| &ctx->ipcp.my_options.address); |
| } |
| |
| static int ipcp_nak_dns1(struct ppp_context *ctx, struct net_pkt *pkt, |
| uint8_t oplen) |
| { |
| return ipcp_nak_override_address(pkt, oplen, |
| &ctx->ipcp.my_options.dns1_address); |
| } |
| |
| static int ipcp_nak_dns2(struct ppp_context *ctx, struct net_pkt *pkt, |
| uint8_t oplen) |
| { |
| return ipcp_nak_override_address(pkt, oplen, |
| &ctx->ipcp.my_options.dns2_address); |
| } |
| |
| static const struct ppp_my_option_info ipcp_my_options[] = { |
| PPP_MY_OPTION(IPCP_OPTION_IP_ADDRESS, ipcp_add_ip_address, |
| ipcp_ack_ip_address, ipcp_nak_ip_address), |
| PPP_MY_OPTION(IPCP_OPTION_DNS1, ipcp_add_dns1, |
| ipcp_ack_dns1, ipcp_nak_dns1), |
| PPP_MY_OPTION(IPCP_OPTION_DNS2, ipcp_add_dns2, |
| ipcp_ack_dns2, ipcp_nak_dns2), |
| }; |
| |
| BUILD_ASSERT(ARRAY_SIZE(ipcp_my_options) == IPCP_NUM_MY_OPTIONS); |
| |
| static struct net_pkt *ipcp_config_info_add(struct ppp_fsm *fsm) |
| { |
| return ppp_my_options_add(fsm, 3 * IP_ADDRESS_OPTION_LEN); |
| } |
| |
| struct ipcp_peer_option_data { |
| bool addr_present; |
| struct in_addr addr; |
| }; |
| |
| #if defined(CONFIG_NET_L2_PPP_OPTION_SERVE_DNS) |
| static int ipcp_dns_address_parse(struct ppp_fsm *fsm, struct net_pkt *pkt, |
| void *user_data) |
| { |
| struct ipcp_peer_option_data *data = user_data; |
| int ret; |
| |
| ret = net_pkt_read(pkt, &data->addr, sizeof(data->addr)); |
| if (ret < 0) { |
| /* Should not happen, is the pkt corrupt? */ |
| return -EMSGSIZE; |
| } |
| |
| /* Request is zeros? Give our dns address in ConfNak */ |
| if (data->addr.s_addr == INADDR_ANY) { |
| NET_DBG("[IPCP] zeroes rcvd as %s addr, sending NAK with our %s addr", |
| "DNS", "DNS"); |
| return -EINVAL; |
| } |
| |
| data->addr_present = true; |
| |
| return 0; |
| } |
| #endif |
| |
| static int ipcp_ip_address_parse(struct ppp_fsm *fsm, struct net_pkt *pkt, |
| void *user_data) |
| { |
| struct ipcp_peer_option_data *data = user_data; |
| int ret; |
| |
| ret = net_pkt_read(pkt, &data->addr, sizeof(data->addr)); |
| if (ret < 0) { |
| /* Should not happen, is the pkt corrupt? */ |
| return -EMSGSIZE; |
| } |
| |
| #if defined(CONFIG_NET_L2_PPP_OPTION_SERVE_IP) |
| /* Request is zeros? Give our IP address in ConfNak */ |
| if (data->addr.s_addr == INADDR_ANY) { |
| NET_DBG("[IPCP] zeroes rcvd as %s addr, sending NAK with our %s addr", |
| "IP", "IP"); |
| return -EINVAL; |
| } |
| #endif |
| if (CONFIG_NET_L2_PPP_LOG_LEVEL >= LOG_LEVEL_DBG) { |
| char dst[INET_ADDRSTRLEN]; |
| char *addr_str; |
| |
| addr_str = net_addr_ntop(AF_INET, &data->addr, dst, |
| sizeof(dst)); |
| |
| NET_DBG("[IPCP] Received peer address %s", |
| addr_str); |
| } |
| |
| data->addr_present = true; |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_NET_L2_PPP_OPTION_SERVE_IP) |
| static int ipcp_server_nak_ip_address(struct ppp_fsm *fsm, |
| struct net_pkt *ret_pkt, void *user_data) |
| { |
| struct ppp_context *ctx = |
| CONTAINER_OF(fsm, struct ppp_context, ipcp.fsm); |
| |
| (void)net_pkt_write_u8(ret_pkt, IPCP_OPTION_IP_ADDRESS); |
| ipcp_add_ip_address(ctx, ret_pkt); |
| |
| return 0; |
| } |
| #endif |
| |
| #if defined(CONFIG_NET_L2_PPP_OPTION_SERVE_DNS) |
| static int ipcp_server_nak_dns1_address(struct ppp_fsm *fsm, |
| struct net_pkt *ret_pkt, |
| void *user_data) |
| { |
| struct ppp_context *ctx = |
| CONTAINER_OF(fsm, struct ppp_context, ipcp.fsm); |
| |
| (void)net_pkt_write_u8(ret_pkt, IPCP_OPTION_DNS1); |
| ipcp_add_dns1(ctx, ret_pkt); |
| |
| return 0; |
| } |
| |
| static int ipcp_server_nak_dns2_address(struct ppp_fsm *fsm, |
| struct net_pkt *ret_pkt, |
| void *user_data) |
| { |
| struct ppp_context *ctx = |
| CONTAINER_OF(fsm, struct ppp_context, ipcp.fsm); |
| |
| (void)net_pkt_write_u8(ret_pkt, IPCP_OPTION_DNS2); |
| ipcp_add_dns2(ctx, ret_pkt); |
| |
| return 0; |
| } |
| #endif |
| |
| static const struct ppp_peer_option_info ipcp_peer_options[] = { |
| #if defined(CONFIG_NET_L2_PPP_OPTION_SERVE_IP) |
| PPP_PEER_OPTION(IPCP_OPTION_IP_ADDRESS, ipcp_ip_address_parse, |
| ipcp_server_nak_ip_address), |
| #else |
| PPP_PEER_OPTION(IPCP_OPTION_IP_ADDRESS, ipcp_ip_address_parse, NULL), |
| #endif |
| #if defined(CONFIG_NET_L2_PPP_OPTION_SERVE_DNS) |
| PPP_PEER_OPTION(IPCP_OPTION_DNS1, ipcp_dns_address_parse, |
| ipcp_server_nak_dns1_address), |
| PPP_PEER_OPTION(IPCP_OPTION_DNS2, ipcp_dns_address_parse, |
| ipcp_server_nak_dns2_address), |
| #endif |
| }; |
| |
| static int ipcp_config_info_req(struct ppp_fsm *fsm, |
| struct net_pkt *pkt, |
| uint16_t length, |
| struct net_pkt *ret_pkt) |
| { |
| struct ppp_context *ctx = |
| CONTAINER_OF(fsm, struct ppp_context, ipcp.fsm); |
| struct ipcp_peer_option_data data = { |
| .addr_present = false, |
| }; |
| int ret; |
| |
| ret = ppp_config_info_req(fsm, pkt, length, ret_pkt, PPP_IPCP, |
| ipcp_peer_options, |
| ARRAY_SIZE(ipcp_peer_options), |
| &data); |
| if (ret != PPP_CONFIGURE_ACK) { |
| /* There are some issues with configuration still */ |
| return ret; |
| } |
| |
| if (!data.addr_present) { |
| NET_DBG("[%s/%p] No %saddress provided", |
| fsm->name, fsm, "peer "); |
| return PPP_CONFIGURE_ACK; |
| } |
| |
| /* 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 |
| */ |
| |
| memcpy(&ctx->ipcp.peer_options.address, &data.addr, sizeof(data.addr)); |
| |
| return PPP_CONFIGURE_ACK; |
| } |
| |
| static void ipcp_set_dns_servers(struct ppp_fsm *fsm) |
| { |
| #if defined(CONFIG_NET_L2_PPP_OPTION_DNS_USE) |
| struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context, |
| ipcp.fsm); |
| |
| struct dns_resolve_context *dnsctx; |
| struct sockaddr_in dns1 = { |
| .sin_family = AF_INET, |
| .sin_port = htons(53), |
| .sin_addr = ctx->ipcp.my_options.dns1_address |
| }; |
| struct sockaddr_in dns2 = { |
| .sin_family = AF_INET, |
| .sin_port = htons(53), |
| .sin_addr = ctx->ipcp.my_options.dns2_address |
| }; |
| const struct sockaddr *dns_servers[] = { |
| (struct sockaddr *) &dns1, |
| (struct sockaddr *) &dns2, |
| NULL |
| }; |
| int ret; |
| |
| if (!dns1.sin_addr.s_addr) { |
| return; |
| } |
| |
| if (!dns2.sin_addr.s_addr) { |
| dns_servers[1] = NULL; |
| } |
| |
| dnsctx = dns_resolve_get_default(); |
| ret = dns_resolve_reconfigure(dnsctx, NULL, dns_servers); |
| if (ret < 0) { |
| NET_ERR("Could not set DNS servers"); |
| return; |
| } |
| #endif |
| } |
| |
| static int ipcp_config_info_nack(struct ppp_fsm *fsm, |
| struct net_pkt *pkt, |
| uint16_t length, |
| bool rejected) |
| { |
| struct ppp_context *ctx = CONTAINER_OF(fsm, struct ppp_context, |
| ipcp.fsm); |
| int ret; |
| |
| ret = ppp_my_options_parse_conf_nak(fsm, pkt, length); |
| if (ret) { |
| return ret; |
| } |
| |
| if (!ctx->ipcp.my_options.address.s_addr) { |
| return -EINVAL; |
| } |
| |
| ipcp_set_dns_servers(fsm); |
| |
| 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 uint8_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", addr_str); |
| return; |
| } |
| |
| NET_DBG("PPP up with address %s", addr_str); |
| ppp_network_up(ctx, PPP_IP); |
| |
| 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); |
| |
| /* Ensure address is always removed if it exists */ |
| if (ctx->ipcp.my_options.address.s_addr) { |
| (void)net_if_ipv4_addr_rm( |
| ctx->iface, &ctx->ipcp.my_options.address); |
| } |
| memset(&ctx->ipcp.my_options.address, 0, |
| sizeof(ctx->ipcp.my_options.address)); |
| memset(&ctx->ipcp.my_options.dns1_address, 0, |
| sizeof(ctx->ipcp.my_options.dns1_address)); |
| memset(&ctx->ipcp.my_options.dns2_address, 0, |
| sizeof(ctx->ipcp.my_options.dns2_address)); |
| |
| if (!ctx->is_ipcp_up) { |
| return; |
| } |
| |
| 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.my_options.info = ipcp_my_options; |
| ctx->ipcp.fsm.my_options.data = ctx->ipcp.my_options_data; |
| ctx->ipcp.fsm.my_options.count = ARRAY_SIZE(ipcp_my_options); |
| |
| 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; |
| ctx->ipcp.fsm.cb.config_info_rej = ppp_my_options_parse_conf_rej; |
| } |
| |
| PPP_PROTOCOL_REGISTER(IPCP, PPP_IPCP, |
| ipcp_init, ipcp_handle, |
| ipcp_lower_up, ipcp_lower_down, |
| ipcp_open, ipcp_close); |