blob: f570cef163f34e1d60a7bbbb123979f8739a3b61 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2023 Sean Anderson <seanga2@gmail.com>
*/
#include <common.h>
#include <dm.h>
#include <spl.h>
#include <test/spl.h>
#include <asm/eth.h>
#include <test/ut.h>
#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, FIT_INTERNAL, DM_FLAGS);
SPL_IMG_TEST(spl_test_net, FIT_EXTERNAL, DM_FLAGS);