// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2023 Sean Anderson */ #include #include #include #include #include #include #include "../../net/bootp.h" /* * sandbox_eth_bootp_req_to_reply() * * Check if a BOOTP request was sent. If so, inject a reply * * returns 0 if injected, -EAGAIN if not */ static int sandbox_eth_bootp_req_to_reply(struct udevice *dev, void *packet, unsigned int len) { struct eth_sandbox_priv *priv = dev_get_priv(dev); struct ethernet_hdr *eth = packet; struct ip_udp_hdr *ip; struct bootp_hdr *bp; struct ethernet_hdr *eth_recv; struct ip_udp_hdr *ipr; struct bootp_hdr *bpr; if (ntohs(eth->et_protlen) != PROT_IP) return -EAGAIN; ip = packet + ETHER_HDR_SIZE; if (ip->ip_p != IPPROTO_UDP) return -EAGAIN; if (ntohs(ip->udp_dst) != PORT_BOOTPS) return -EAGAIN; bp = (void *)ip + IP_UDP_HDR_SIZE; if (bp->bp_op != OP_BOOTREQUEST) return -EAGAIN; /* Don't allow the buffer to overrun */ if (priv->recv_packets >= PKTBUFSRX) return 0; /* reply to the request */ eth_recv = (void *)priv->recv_packet_buffer[priv->recv_packets]; memcpy(eth_recv, packet, len); ipr = (void *)eth_recv + ETHER_HDR_SIZE; bpr = (void *)ipr + IP_UDP_HDR_SIZE; memcpy(eth_recv->et_dest, eth->et_src, ARP_HLEN); memcpy(eth_recv->et_src, priv->fake_host_hwaddr, ARP_HLEN); ipr->ip_sum = 0; ipr->ip_off = 0; net_write_ip(&ipr->ip_dst, net_ip); net_write_ip(&ipr->ip_src, priv->fake_host_ipaddr); ipr->ip_sum = compute_ip_checksum(ipr, IP_HDR_SIZE); ipr->udp_src = ip->udp_dst; ipr->udp_dst = ip->udp_src; bpr->bp_op = OP_BOOTREPLY; net_write_ip(&bpr->bp_yiaddr, net_ip); net_write_ip(&bpr->bp_siaddr, priv->fake_host_ipaddr); copy_filename(bpr->bp_file, CONFIG_BOOTFILE, sizeof(CONFIG_BOOTFILE)); memset(&bpr->bp_vend, 0, sizeof(bpr->bp_vend)); priv->recv_packet_length[priv->recv_packets] = len; ++priv->recv_packets; return 0; } struct spl_test_net_priv { struct unit_test_state *uts; void *img; size_t img_size; u16 port; }; /* Well known TFTP port # */ #define TFTP_PORT 69 /* Transaction ID, chosen at random */ #define TFTP_TID 21313 /* * TFTP operations. */ #define TFTP_RRQ 1 #define TFTP_DATA 3 #define TFTP_ACK 4 /* default TFTP block size */ #define TFTP_BLOCK_SIZE 512 struct tftp_hdr { u16 opcode; u16 block; }; #define TFTP_HDR_SIZE sizeof(struct tftp_hdr) /* * sandbox_eth_tftp_req_to_reply() * * Check if a TFTP request was sent. If so, inject a reply. We don't support * options, and we don't check for rollover, so we are limited files of less * than 32M. * * returns 0 if injected, -EAGAIN if not */ static int sandbox_eth_tftp_req_to_reply(struct udevice *dev, void *packet, unsigned int len) { struct eth_sandbox_priv *priv = dev_get_priv(dev); struct spl_test_net_priv *test_priv = priv->priv; struct ethernet_hdr *eth = packet; struct ip_udp_hdr *ip; struct tftp_hdr *tftp; struct ethernet_hdr *eth_recv; struct ip_udp_hdr *ipr; struct tftp_hdr *tftpr; size_t size; u16 block; if (ntohs(eth->et_protlen) != PROT_IP) return -EAGAIN; ip = packet + ETHER_HDR_SIZE; if (ip->ip_p != IPPROTO_UDP) return -EAGAIN; if (ntohs(ip->udp_dst) == TFTP_PORT) { tftp = (void *)ip + IP_UDP_HDR_SIZE; if (htons(tftp->opcode) != TFTP_RRQ) return -EAGAIN; block = 0; } else if (ntohs(ip->udp_dst) == TFTP_TID) { tftp = (void *)ip + IP_UDP_HDR_SIZE; if (htons(tftp->opcode) != TFTP_ACK) return -EAGAIN; block = htons(tftp->block); } else { return -EAGAIN; } if (block * TFTP_BLOCK_SIZE > test_priv->img_size) return 0; size = min(test_priv->img_size - block * TFTP_BLOCK_SIZE, (size_t)TFTP_BLOCK_SIZE); /* Don't allow the buffer to overrun */ if (priv->recv_packets >= PKTBUFSRX) return 0; /* reply to the request */ eth_recv = (void *)priv->recv_packet_buffer[priv->recv_packets]; memcpy(eth_recv->et_dest, eth->et_src, ARP_HLEN); memcpy(eth_recv->et_src, priv->fake_host_hwaddr, ARP_HLEN); eth_recv->et_protlen = htons(PROT_IP); ipr = (void *)eth_recv + ETHER_HDR_SIZE; ipr->ip_hl_v = 0x45; ipr->ip_len = htons(IP_UDP_HDR_SIZE + TFTP_HDR_SIZE + size); ipr->ip_off = htons(IP_FLAGS_DFRAG); ipr->ip_ttl = 255; ipr->ip_p = IPPROTO_UDP; ipr->ip_sum = 0; net_copy_ip(&ipr->ip_dst, &ip->ip_src); net_copy_ip(&ipr->ip_src, &ip->ip_dst); ipr->ip_sum = compute_ip_checksum(ipr, IP_HDR_SIZE); ipr->udp_src = htons(TFTP_TID); ipr->udp_dst = ip->udp_src; ipr->udp_len = htons(UDP_HDR_SIZE + TFTP_HDR_SIZE + size); ipr->udp_xsum = 0; tftpr = (void *)ipr + IP_UDP_HDR_SIZE; tftpr->opcode = htons(TFTP_DATA); tftpr->block = htons(block + 1); memcpy((void *)tftpr + TFTP_HDR_SIZE, test_priv->img + block * TFTP_BLOCK_SIZE, size); priv->recv_packet_length[priv->recv_packets] = ETHER_HDR_SIZE + IP_UDP_HDR_SIZE + TFTP_HDR_SIZE + size; ++priv->recv_packets; return 0; } static int spl_net_handler(struct udevice *dev, void *packet, unsigned int len) { struct eth_sandbox_priv *priv = dev_get_priv(dev); int old_packets = priv->recv_packets; priv->fake_host_ipaddr = string_to_ip("1.1.2.4"); net_ip = string_to_ip("1.1.2.2"); sandbox_eth_arp_req_to_reply(dev, packet, len); sandbox_eth_bootp_req_to_reply(dev, packet, len); sandbox_eth_tftp_req_to_reply(dev, packet, len); if (old_packets == priv->recv_packets) return 0; return 0; } static int spl_test_net_write_image(struct unit_test_state *uts, void *img, size_t img_size) { struct spl_test_net_priv *test_priv = malloc(sizeof(*test_priv)); ut_assertnonnull(test_priv); test_priv->uts = uts; test_priv->img = img; test_priv->img_size = img_size; sandbox_eth_set_tx_handler(0, spl_net_handler); sandbox_eth_set_priv(0, test_priv); return 0; } static int spl_test_net(struct unit_test_state *uts, const char *test_name, enum spl_test_image type) { struct eth_sandbox_priv *priv; struct udevice *dev; int ret; net_server_ip = string_to_ip("1.1.2.4"); ret = do_spl_test_load(uts, test_name, type, SPL_LOAD_IMAGE_GET(0, BOOT_DEVICE_CPGMAC, spl_net_load_image_cpgmac), spl_test_net_write_image); sandbox_eth_set_tx_handler(0, NULL); ut_assertok(uclass_get_device(UCLASS_ETH, 0, &dev)); priv = dev_get_priv(dev); free(priv->priv); return ret; } SPL_IMG_TEST(spl_test_net, LEGACY, DM_FLAGS); SPL_IMG_TEST(spl_test_net, LEGACY_LZMA, DM_FLAGS); SPL_IMG_TEST(spl_test_net, IMX8, DM_FLAGS); SPL_IMG_TEST(spl_test_net, FIT_INTERNAL, DM_FLAGS); SPL_IMG_TEST(spl_test_net, FIT_EXTERNAL, DM_FLAGS);