Compare commits

...

6 commits

8 changed files with 800 additions and 428 deletions

2
.gitignore vendored
View file

@ -2,5 +2,5 @@
*.a
*.pcap
build/*
test/unit/unit-core
test/unit/unit
tags

View file

@ -1,6 +1,6 @@
CC?=gcc
CFLAGS:=-Wall -Werror -Wextra -I.
OBJ=build/femtotcp.o build/test-linux.o
OBJ=build/femtotcp.o build/test-linux.o build/port/linux.o
all: build/test
@ -13,7 +13,7 @@ libtcpip.a: $(OBJ)
@ar rcs $@ $^
clean:
@rm -f build/*
@rm -f build/*.o build/port/*.o
@make -C test/unit clean
# Test

107
core.md Normal file
View file

@ -0,0 +1,107 @@
# FemtoTCP
## Stack architecture
- No dynamic allocation (pre-allocated sockets and buffers)
- Four-steps main loop function
- Callback-based socket interface (allows implementing blocking BSD calls)
## Data structures
* Two types of circular buffers (fixed size):
- "fifo" : contains entire frames, including a descriptor.
- "queue" : contains pure data, indexed by byte. Used for TCP receive buffer
only.
* One binary heap for timers
### FemtoTCP fifo
```
+---------------------------------------------------------------------------------------------------------------------------+
| +-----+---+----+-----+------------------+-----+---+----+-----+------------------+ |
| | De | E | IP | TCP | Payload | De | E | IP | TCP | Payload | |
| | sc | T | | | | sc | T | | | | |
|* FREE SPACE * | ri | H | | | | ri | H | | | | * FREE SPACE* |
| | pt | | | | | pt | | | | | |
| | or | | | | | or | | | | | |
| +-----+---+----+-----+------------------+-----+---+----+-----+------------------+ |
+---------------------------------------------------------------------------------------------------------------------------+
^ ^
| |
| |
| |
|Tail Head|
```
### FemtoTCP queue
```
+--------------+--------------------------------------------+---------------------------------------------------------------+
| |*------------------------------------------*| |
| || || |
| || || |
|* FREE SPACE *|| DATA PAYLOAD || * FREE SPACE * |
| || || |
| || || |
| |*------------------------------------------*| |
+--------------+--------------------------------------------+---------------------------------------------------------------+
^ ^
| |
| |
| |
|Tail Head|
```
## Sockets
### TCP socket
```
+-------------+
|Main loop TX |
+-------------+
^
+----------------------------------+ |
| | +------+
| TCP Socket | |
| | |
| | |
| | |
| +-----------------------+
| +---------------+ | |
>DATA OUT==>>|socket send() |-->| TX buffer (fifo) |
| +---------------+ | |
| +-----------------------+
| |
| |
| |
| +-----------------------+
| +-------------+ | |
<DATA IN<<====|socket recv()|<---| RX buffer (queue) |
| +-------------+ | |
| +-----------------------+
+----------------------------------+ ^
|
|
|
+--------------+
| tcp_recv() |
+--------------+
```

View file

@ -54,16 +54,16 @@ typedef uint32_t socklen_t;
#endif
#endif
int posix_socket(struct ipstack *s, int domain, int type, int protocol);
int posix_bind(struct ipstack *s, int sockfd, const struct ipstack_sockaddr *addr, socklen_t addrlen);
int posix_listen(struct ipstack *s, int sockfd, int backlog);
int posix_accept(struct ipstack *s, int sockfd, struct ipstack_sockaddr *addr, socklen_t *addrlen);
int posix_connect(struct ipstack *s, int sockfd, const struct ipstack_sockaddr *addr, socklen_t addrlen);
int posix_sendto(struct ipstack *s, int sockfd, const void *buf, size_t len, int flags, const struct ipstack_sockaddr *dest_addr, socklen_t addrlen);
int posix_recvfrom(struct ipstack *s, int sockfd, void *buf, size_t len, int flags, struct ipstack_sockaddr *src_addr, socklen_t *addrlen);
int posix_close(struct ipstack *s, int sockfd);
int posix_getpeername(struct ipstack *s, int sockfd, struct ipstack_sockaddr *addr, socklen_t *addrlen);
int posix_getsockname(struct ipstack *s, int sockfd, struct ipstack_sockaddr *addr, socklen_t *addrlen);
int ft_socket(struct ipstack *s, int domain, int type, int protocol);
int ft_bind(struct ipstack *s, int sockfd, const struct ipstack_sockaddr *addr, socklen_t addrlen);
int ft_listen(struct ipstack *s, int sockfd, int backlog);
int ft_accept(struct ipstack *s, int sockfd, struct ipstack_sockaddr *addr, socklen_t *addrlen);
int ft_connect(struct ipstack *s, int sockfd, const struct ipstack_sockaddr *addr, socklen_t addrlen);
int ft_sendto(struct ipstack *s, int sockfd, const void *buf, size_t len, int flags, const struct ipstack_sockaddr *dest_addr, socklen_t addrlen);
int ft_recvfrom(struct ipstack *s, int sockfd, void *buf, size_t len, int flags, struct ipstack_sockaddr *src_addr, socklen_t *addrlen);
int ft_close(struct ipstack *s, int sockfd);
int ft_getpeername(struct ipstack *s, int sockfd, struct ipstack_sockaddr *addr, socklen_t *addrlen);
int ft_getsockname(struct ipstack *s, int sockfd, struct ipstack_sockaddr *addr, socklen_t *addrlen);
int dhcp_client_init(struct ipstack *s);
int dhcp_bound(struct ipstack *s);
@ -78,6 +78,13 @@ void ipstack_ipconfig_get(struct ipstack *s, ip4 *ip, ip4 *mask, ip4 *gw);
struct ll *ipstack_getdev(struct ipstack *s);
ip4 atoip4(const char *ip);
/* Callback flags */
#define CB_EVENT_READABLE 0x01 /* Accepted connection or data available */
#define CB_EVENT_WRITABLE 0x02 /* Connected or space available to send */
#define CB_EVENT_CLOSED 0x04 /* Connection closed by peer */
#define CB_EVENT_TIMEOUT 0x08 /* Timeout */
void ipstack_register_callback(struct ipstack *s, int sock_fd, void (*cb)(int sock_fd, uint16_t events, void *arg), void *arg);

View file

@ -4,7 +4,7 @@
* Copyright: 2024 Danielinux
*
*/
#include <stdint.h>
#include <string.h>
#include "femtotcp.h"
@ -21,7 +21,7 @@
#define MAX_NEIGHBORS 16
/* Constants */
#define MARK_TCP_SOCKET 0x8000 /* Mark a socket as TCP */
#define MARK_TCP_SOCKET 0x1000 /* Mark a socket as TCP */
#define MARK_UDP_SOCKET 0x4000 /* Mark a socket as UDP */
#define IPPROTO_ICMP 0x01
@ -70,11 +70,6 @@
#define PKT_FLAG_ACKED 0x02
#define PKT_FLAG_FIN 0x04
/* Callback flags */
#define CB_EVENT_READABLE 0x01 /* Accepted connection or data available */
#define CB_EVENT_WRITABLE 0x02 /* Connected or space available to send */
#define CB_EVENT_CLOSED 0x04 /* Connection closed by peer */
#define CB_EVENT_TIMEOUT 0x08 /* Timeout */
/* Random number generator, provided by the user */
uint32_t ipstack_getrandom(void);
@ -92,7 +87,7 @@ struct fifo {
/* TCP TX is a circular buffer and contains an array of full packets */
/* TCP RX only contains application data */
/* FIFO functions
/* FIFO functions
* head: next empty slot
* tail: oldest populated slot
*
@ -148,7 +143,7 @@ static struct pkt_desc *fifo_peek(struct fifo *f)
f->tail = 0;
f->h_wrap = 0;
}
if (f->tail == f->head)
if (f->tail == f->head)
return NULL;
while (f->tail % 4)
f->tail++;
@ -532,7 +527,7 @@ struct timers_binheap {
uint32_t size;
};
struct ipstack
struct ipstack
{
struct ll ll_dev;
struct ipconf ipconf;
@ -566,7 +561,7 @@ void ipstack_register_callback(struct ipstack *s, int sock_fd, void (*cb)(int so
if (sock_fd < 0)
return;
if (sock_fd & MARK_TCP_SOCKET) {
if (sock_fd >= MAX_TCPSOCKETS)
if ((sock_fd & (~MARK_TCP_SOCKET)) >= MAX_TCPSOCKETS)
return;
t = &s->tcpsockets[sock_fd & ~MARK_TCP_SOCKET];
t->callback = cb;
@ -584,23 +579,26 @@ void ipstack_register_callback(struct ipstack *s, int sock_fd, void (*cb)(int so
static struct ipstack_timer timers_binheap_pop(struct timers_binheap *heap)
{
uint32_t i = 0;
struct ipstack_timer tmr = heap->timers[0];
heap->size--;
heap->timers[0] = heap->timers[heap->size];
while (2*i+1 < heap->size) {
struct ipstack_timer tmp;
uint32_t j = 2*i+1;
if (j+1 < heap->size && heap->timers[j+1].expires < heap->timers[j].expires) {
j++;
struct ipstack_timer tmr = {0};
do {
tmr = heap->timers[0];
heap->size--;
heap->timers[0] = heap->timers[heap->size];
while (2*i+1 < heap->size) {
struct ipstack_timer tmp;
uint32_t j = 2*i+1;
if (j+1 < heap->size && heap->timers[j+1].expires < heap->timers[j].expires) {
j++;
}
if (heap->timers[i].expires <= heap->timers[j].expires) {
break;
}
tmp = heap->timers[i];
heap->timers[i] = heap->timers[j];
heap->timers[j] = tmp;
i = j;
}
if (heap->timers[i].expires <= heap->timers[j].expires) {
break;
}
tmp = heap->timers[i];
heap->timers[i] = heap->timers[j];
heap->timers[j] = tmp;
i = j;
}
} while ((tmr.expires == 0) && (heap->size > 0));
return tmr;
}
@ -670,7 +668,7 @@ static void udp_try_recv(struct ipstack *s, struct ipstack_udp_datagram *udp, ui
{
for (int i = 0; i < MAX_UDPSOCKETS; i++) {
struct tsocket *t = &s->udpsockets[i];
if (t->src_port == ee16(udp->dst_port) && t->dst_port == ee16(udp->src_port) &&
if (t->src_port == ee16(udp->dst_port) && t->dst_port == ee16(udp->src_port) &&
(((t->local_ip == 0) && DHCP_IS_RUNNING(s)) ||
(t->local_ip == ee32(udp->ip.dst) && t->remote_ip != s->ipconf.ip)) ) {
@ -801,6 +799,7 @@ static void tcp_recv(struct tsocket *t, struct ipstack_tcp_seg *seg)
t->sock.tcp.ack = seq + seg_len;
timer_binheap_cancel(&t->S->timers, t->sock.tcp.tmr_rto);
t->sock.tcp.tmr_rto = NO_TIMER;
t->events |= CB_EVENT_READABLE;
}
tcp_send_ack(t);
}
@ -993,6 +992,8 @@ static void tcp_ack(struct tsocket *t, struct ipstack_tcp_seg *tcp)
t->sock.tcp.cwnd += TCP_MSS;
}
}
if (fifo_space(&t->sock.tcp.txbuf) > 0)
t->events |= CB_EVENT_WRITABLE;
} else {
struct pkt_desc *desc;
/* Duplicate ack */
@ -1019,7 +1020,7 @@ static void tcp_ack(struct tsocket *t, struct ipstack_tcp_seg *tcp)
desc = fifo_next(&t->sock.tcp.txbuf, desc);
}
}
}
}
/* Preselect socket, parse options, manage handshakes, pass to application */
static void tcp_input(struct ipstack *S, struct ipstack_tcp_seg *tcp, uint32_t frame_len)
@ -1050,13 +1051,15 @@ static void tcp_input(struct ipstack *S, struct ipstack_tcp_seg *tcp, uint32_t f
t->sock.tcp.state = TCP_CLOSE_WAIT;
t->sock.tcp.ack = ee32(tcp->seq) + 1;
tcp_send_ack(t);
t->events |= CB_EVENT_CLOSED;
}
else if (t->sock.tcp.state == TCP_FIN_WAIT_1) {
t->sock.tcp.state = TCP_CLOSING;
t->sock.tcp.ack = ee32(tcp->seq) + 1;
tcp_send_ack(t);
t->events |= CB_EVENT_CLOSED;
}
}
}
/* Check if SYN */
if (tcp->flags & 0x02) {
@ -1101,12 +1104,12 @@ static void tcp_input(struct ipstack *S, struct ipstack_tcp_seg *tcp, uint32_t f
/* FIN */
if (t->sock.tcp.state == TCP_ESTABLISHED) {
t->sock.tcp.state = TCP_CLOSE_WAIT;
t->events |= CB_EVENT_CLOSED;
t->events &= ~CB_EVENT_READABLE;
} else if (t->sock.tcp.state == TCP_FIN_WAIT_1) {
t->sock.tcp.state = TCP_CLOSING;
}
t->sock.tcp.ack = ee32(tcp->seq) + 1;
t->events |= CB_EVENT_CLOSED;
tcp_send_ack(t);
}
if (tcp->flags & 0x10) {
@ -1170,7 +1173,7 @@ static void close_socket(struct tsocket *ts)
}
int posix_socket(struct ipstack *s, int domain, int type, int protocol)
int ft_socket(struct ipstack *s, int domain, int type, int protocol)
{
struct tsocket *ts;
if (domain != AF_INET)
@ -1190,7 +1193,7 @@ int posix_socket(struct ipstack *s, int domain, int type, int protocol)
return -1;
}
int posix_connect(struct ipstack *s, int sockfd, const struct ipstack_sockaddr *addr, socklen_t addrlen)
int ft_connect(struct ipstack *s, int sockfd, const struct ipstack_sockaddr *addr, socklen_t addrlen)
{
struct tsocket *ts;
struct ipstack_sockaddr_in *sin = (struct ipstack_sockaddr_in *)addr;
@ -1228,7 +1231,7 @@ int posix_connect(struct ipstack *s, int sockfd, const struct ipstack_sockaddr *
return -2;
}
int posix_accept(struct ipstack *s, int sockfd, struct ipstack_sockaddr *addr, socklen_t *addrlen)
int ft_accept(struct ipstack *s, int sockfd, struct ipstack_sockaddr *addr, socklen_t *addrlen)
{
struct tsocket *ts;
struct ipstack_sockaddr_in *sin = (struct ipstack_sockaddr_in *)addr;
@ -1251,6 +1254,9 @@ int posix_accept(struct ipstack *s, int sockfd, struct ipstack_sockaddr *addr, s
if (!newts)
return -1;
ts->events &= ~CB_EVENT_READABLE;
newts->events |= CB_EVENT_WRITABLE;
newts->callback = ts->callback;
newts->callback_arg = ts->callback_arg;
newts->local_ip = ts->local_ip;
newts->remote_ip = ts->remote_ip;
newts->src_port = ts->src_port;
@ -1273,10 +1279,10 @@ int posix_accept(struct ipstack *s, int sockfd, struct ipstack_sockaddr *addr, s
return -1;;
}
int posix_sendto(struct ipstack *s, int sockfd, const void *buf, size_t len, int flags,
int ft_sendto(struct ipstack *s, int sockfd, const void *buf, size_t len, int flags,
const struct ipstack_sockaddr *dest_addr, socklen_t addrlen)
{
uint8_t frame[LINK_MTU];
uint8_t frame[LINK_MTU];
struct tsocket *ts;
struct ipstack_tcp_seg *tcp = (struct ipstack_tcp_seg *)frame;
struct ipstack_udp_datagram *udp = (struct ipstack_udp_datagram *)frame;
@ -1356,7 +1362,7 @@ int posix_sendto(struct ipstack *s, int sockfd, const void *buf, size_t len, int
} else return -1;
}
int posix_recvfrom(struct ipstack *s, int sockfd, void *buf, size_t len, int flags,
int ft_recvfrom(struct ipstack *s, int sockfd, void *buf, size_t len, int flags,
struct ipstack_sockaddr *src_addr, socklen_t *addrlen)
{
uint32_t seg_len;
@ -1402,12 +1408,13 @@ int posix_recvfrom(struct ipstack *s, int sockfd, void *buf, size_t len, int fla
} else return -1;
}
int posix_close(struct ipstack *s, int sockfd)
int ft_close(struct ipstack *s, int sockfd)
{
if (sockfd & MARK_TCP_SOCKET) {
struct tsocket *ts = &s->tcpsockets[sockfd & ~MARK_TCP_SOCKET];
if (ts->sock.tcp.state == TCP_ESTABLISHED) {
ts->sock.tcp.state = TCP_FIN_WAIT_1;
LOG("close(): Sending FIN\n");
tcp_send_finack(ts);
return -11;
} else if (ts->sock.tcp.state == TCP_CLOSE_WAIT) {
@ -1436,7 +1443,7 @@ int posix_close(struct ipstack *s, int sockfd)
return 0;
}
int posix_getsockname(struct ipstack *s, int sockfd, struct ipstack_sockaddr *addr, socklen_t *addrlen)
int ft_getsockname(struct ipstack *s, int sockfd, struct ipstack_sockaddr *addr, socklen_t *addrlen)
{
struct tsocket *ts = &s->tcpsockets[sockfd];
struct ipstack_sockaddr_in *sin = (struct ipstack_sockaddr_in *)addr;
@ -1448,7 +1455,7 @@ int posix_getsockname(struct ipstack *s, int sockfd, struct ipstack_sockaddr *ad
return 0;
}
int posix_bind(struct ipstack *s, int sockfd, const struct ipstack_sockaddr *addr, socklen_t addrlen)
int ft_bind(struct ipstack *s, int sockfd, const struct ipstack_sockaddr *addr, socklen_t addrlen)
{
struct tsocket *ts;
struct ipstack_sockaddr_in *sin = (struct ipstack_sockaddr_in *)addr;
@ -1476,7 +1483,7 @@ int posix_bind(struct ipstack *s, int sockfd, const struct ipstack_sockaddr *add
}
int posix_listen(struct ipstack *s, int sockfd, int backlog)
int ft_listen(struct ipstack *s, int sockfd, int backlog)
{
struct tsocket *ts;
(void)backlog;
@ -1489,7 +1496,7 @@ int posix_listen(struct ipstack *s, int sockfd, int backlog)
return 0;
}
int posix_getpeername(struct ipstack *s, int sockfd, struct ipstack_sockaddr *addr, socklen_t *addrlen)
int ft_getpeername(struct ipstack *s, int sockfd, struct ipstack_sockaddr *addr, socklen_t *addrlen)
{
struct tsocket *ts = &s->tcpsockets[sockfd];
struct ipstack_sockaddr_in *sin = (struct ipstack_sockaddr_in *)addr;
@ -1643,7 +1650,7 @@ static int dhcp_poll(struct ipstack *s)
struct dhcp_msg msg;
int len;
memset(&msg, 0xBB, sizeof(msg));
len = posix_recvfrom(s, s->dhcp_udp_sd, &msg, sizeof(struct dhcp_msg), 0, (struct ipstack_sockaddr *)&sin, &sl);
len = ft_recvfrom(s, s->dhcp_udp_sd, &msg, sizeof(struct dhcp_msg), 0, (struct ipstack_sockaddr *)&sin, &sl);
if (len < 0)
return -1;
if ((s->dhcp_state == DHCP_DISCOVER_SENT) && (dhcp_parse_offer(s, &msg) == 0))
@ -1705,7 +1712,7 @@ static int dhcp_send_request(struct ipstack *s)
sin.sin_port = ee16(DHCP_SERVER_PORT);
sin.sin_addr.s_addr = ee32(0xFFFFFFFF); /* Broadcast */
sin.sin_family = AF_INET;
posix_sendto(s, s->dhcp_udp_sd, &req, DHCP_HEADER_LEN + opt_sz, 0,
ft_sendto(s, s->dhcp_udp_sd, &req, DHCP_HEADER_LEN + opt_sz, 0,
(struct ipstack_sockaddr *)&sin, sizeof(struct ipstack_sockaddr_in));
tmr.expires = s->last_tick + DHCP_REQUEST_TIMEOUT + (ipstack_getrandom() % 200);
tmr.arg = s;
@ -1749,7 +1756,7 @@ static int dhcp_send_discover(struct ipstack *s)
sin.sin_port = ee16(DHCP_SERVER_PORT);
sin.sin_addr.s_addr = ee32(0xFFFFFFFF); /* Broadcast */
sin.sin_family = AF_INET;
posix_sendto(s, s->dhcp_udp_sd, &disc, DHCP_HEADER_LEN + opt_sz, 0,
ft_sendto(s, s->dhcp_udp_sd, &disc, DHCP_HEADER_LEN + opt_sz, 0,
(struct ipstack_sockaddr *)&sin, sizeof(struct ipstack_sockaddr_in));
tmr.expires = s->last_tick + DHCP_DISCOVER_TIMEOUT + (ipstack_getrandom() % 200);
tmr.arg = s;
@ -1773,10 +1780,10 @@ int dhcp_client_init(struct ipstack *s)
s->dhcp_xid = ipstack_getrandom();
if (s->dhcp_udp_sd > 0) {
posix_close(s, s->dhcp_udp_sd);
ft_close(s, s->dhcp_udp_sd);
}
s->dhcp_udp_sd = posix_socket(s, AF_INET, IPSTACK_SOCK_DGRAM, IPPROTO_UDP);
s->dhcp_udp_sd = ft_socket(s, AF_INET, IPSTACK_SOCK_DGRAM, IPPROTO_UDP);
if (s->dhcp_udp_sd < 0) {
s->dhcp_state = DHCP_OFF;
return -1;
@ -1784,7 +1791,7 @@ int dhcp_client_init(struct ipstack *s)
memset(&sin, 0, sizeof(struct ipstack_sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_port = ee16(DHCP_CLIENT_PORT);
if (posix_bind(s, s->dhcp_udp_sd, (struct ipstack_sockaddr *)&sin, sizeof(struct ipstack_sockaddr_in)) < 0) {
if (ft_bind(s, s->dhcp_udp_sd, (struct ipstack_sockaddr *)&sin, sizeof(struct ipstack_sockaddr_in)) < 0) {
s->dhcp_state = DHCP_OFF;
return -1;
}
@ -1893,7 +1900,7 @@ void ipstack_init_static(struct ipstack **s)
/* ipstack_poll: poll the network stack for incoming packets
* This function should be called in a loop to process incoming packets.
* It will call the poll function of the device driver and process the
* received packets.
* received packets.
*
* This function also handles timers for all supported protocols.
*
@ -1966,6 +1973,7 @@ int ipstack_poll(struct ipstack *s, uint64_t now)
ts->callback(i | MARK_TCP_SOCKET, ts->events, ts->callback_arg);
ts->events = 0;
}
}
for (i = 0; i < MAX_UDPSOCKETS; i++) {
struct tsocket *ts = &s->udpsockets[i];
@ -2006,7 +2014,7 @@ int ipstack_poll(struct ipstack *s, uint64_t now)
struct ipstack_timer tmr = {};
len = desc->len - ETH_HEADER_LEN;
tcp = (struct ipstack_tcp_seg *)(ts->txmem + desc->pos + sizeof(*desc));
if ((ts->sock.tcp.ack == ts->sock.tcp.last_ack) &&
if ((ts->sock.tcp.ack == ts->sock.tcp.last_ack) &&
(len == IP_HEADER_LEN + (uint32_t)(tcp->hlen >> 2)) &&
(tcp->flags == 0x10)) {
desc->flags |= PKT_FLAG_SENT;
@ -2054,7 +2062,7 @@ int ipstack_poll(struct ipstack *s, uint64_t now)
/* Send ARP request */
arp_request(s, nexthop);
break;
}
}
if (IS_IP_BCAST(nexthop)) memset(t->nexthop_mac, 0xFF, 6);
#endif
len = desc->len - ETH_HEADER_LEN;

140
src/port/linux.c Normal file
View file

@ -0,0 +1,140 @@
#include <net/if.h>
#include <stdint.h>
#include <stdio.h>
/* tap device */
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/if_tun.h>
#include <unistd.h>
#include <sys/poll.h>
#include <string.h>
#include "femtotcp.h"
#include <netinet/in.h>
#include <arpa/inet.h>
static int tap_fd;
void print_buffer(uint8_t *buf, int len)
{
int i;
for (i = 0; i < len; i++) {
if (i % 16 == 0)
printf("\n");
printf("%02x ", buf[i]);
}
printf("\n");
}
static int tap_poll(struct ll *ll, void *buf, int len)
{
struct pollfd pfd;
(void)ll;
int ret;
pfd.fd = tap_fd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 2);
if (ret < 0) {
perror("poll");
return -1;
}
if (ret == 0) {
return 0;
}
return read(tap_fd, buf, len);
}
static int tap_send(struct ll *ll, void *buf, int len)
{
(void)ll;
//print_buffer(buf, len);
return write(tap_fd, buf, len);
}
int tap_init(struct ll *ll, const char *ifname, uint32_t host_ip)
{
struct ifreq ifr;
struct sockaddr_in *addr;
int sock_fd;
if ((tap_fd = open("/dev/net/tun", O_RDWR)) < 0) {
perror("accessing /dev/net/tun");
return -1;
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
if (ioctl(tap_fd, TUNSETIFF, (void *)&ifr) < 0) {
perror("ioctl TUNSETIFF");
close(tap_fd);
return -1;
}
/* Get mac address */
if (ioctl(tap_fd, SIOCGIFHWADDR, &ifr) < 0) {
perror("ioctl SIOCGIFHWADDR");
close(tap_fd);
return -1;
}
strncpy(ll->ifname, ifname, sizeof(ll->ifname) - 1);
memcpy(ll->mac, ifr.ifr_hwaddr.sa_data, 6);
ll->mac[5] ^= 1;
ll->poll = tap_poll;
ll->send = tap_send;
/* Set up network side */
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (sock_fd < 0) {
perror("socket");
close(tap_fd);
return -1;
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
if (ioctl(sock_fd, SIOCGIFFLAGS, &ifr) < 0) {
perror("ioctl SIOCGIFFLAGS");
close(sock_fd);
return -1;
}
ifr.ifr_flags |= IFF_UP;
if (ioctl(sock_fd, SIOCSIFFLAGS, &ifr) < 0) {
perror("ioctl SIOCSIFFLAGS");
close(sock_fd);
return -1;
}
addr = (struct sockaddr_in *)&ifr.ifr_addr;
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = host_ip;
if (ioctl(sock_fd, SIOCSIFADDR, &ifr) < 0) {
perror("ioctl SIOCSIFADDR");
close(sock_fd);
return -1;
}
inet_pton(AF_INET, "255.255.255.0", &addr->sin_addr);
if (ioctl(sock_fd, SIOCSIFNETMASK, &ifr) < 0) {
perror("ioctl SIOCSIFNETMASK");
close(sock_fd);
return -1;
}
if (ioctl(sock_fd, SIOCGIFFLAGS, &ifr) < 0) {
perror("ioctl SIOCGIFFLAGS");
close(sock_fd);
return -1;
}
ifr.ifr_flags = IFF_UP | IFF_RUNNING;
if (ioctl(sock_fd, SIOCSIFFLAGS, &ifr) < 0) {
perror("ioctl SIOCSIFFLAGS");
close(sock_fd);
return -1;
}
printf("Successfully initialized tap device %s\n", ifname);
return 0;
}
#include <sys/random.h>
uint32_t ipstack_getrandom(void)
{
uint32_t ret;
getrandom(&ret, sizeof(ret), 0);
return ret;
}

View file

@ -1,171 +1,104 @@
#include <net/if.h>
#include <stdio.h>
/* tap device */
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/if_tun.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/poll.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdlib.h>
#include "femtotcp.h"
static int tap_fd;
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
//#define DHCP
#define FEMTOTCP_IP "10.10.10.2"
#define LINUX_IP "10.10.10.1"
#define TEST_SIZE (8 * 1024)
void print_buffer(uint8_t *buf, int len)
{
int i;
for (i = 0; i < len; i++) {
if (i % 16 == 0)
printf("\n");
printf("%02x ", buf[i]);
}
printf("\n");
}
int tap_poll(struct ll *ll, void *buf, int len)
{
struct pollfd pfd;
(void)ll;
int ret;
pfd.fd = tap_fd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 2);
if (ret < 0) {
perror("poll");
return -1;
}
if (ret == 0) {
return 0;
}
return read(tap_fd, buf, len);
}
int tap_send(struct ll *ll, void *buf, int len)
{
(void)ll;
//print_buffer(buf, len);
return write(tap_fd, buf, len);
}
int tap_init(struct ll *ll, const char *ifname)
{
struct ifreq ifr;
struct sockaddr_in *addr;
int sock_fd;
if ((tap_fd = open("/dev/net/tun", O_RDWR)) < 0) {
perror("accessing /dev/net/tun");
return -1;
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
if (ioctl(tap_fd, TUNSETIFF, (void *)&ifr) < 0) {
perror("ioctl TUNSETIFF");
close(tap_fd);
return -1;
}
/* Get mac address */
if (ioctl(tap_fd, SIOCGIFHWADDR, &ifr) < 0) {
perror("ioctl SIOCGIFHWADDR");
close(tap_fd);
return -1;
}
strncpy(ll->ifname, ifname, sizeof(ll->ifname) - 1);
memcpy(ll->mac, ifr.ifr_hwaddr.sa_data, 6);
ll->mac[5] ^= 1;
ll->poll = tap_poll;
ll->send = tap_send;
/* Set up network side */
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (sock_fd < 0) {
perror("socket");
close(tap_fd);
return -1;
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
if (ioctl(sock_fd, SIOCGIFFLAGS, &ifr) < 0) {
perror("ioctl SIOCGIFFLAGS");
close(sock_fd);
return -1;
}
ifr.ifr_flags |= IFF_UP;
if (ioctl(sock_fd, SIOCSIFFLAGS, &ifr) < 0) {
perror("ioctl SIOCSIFFLAGS");
close(sock_fd);
return -1;
}
addr = (struct sockaddr_in *)&ifr.ifr_addr;
addr->sin_family = AF_INET;
inet_pton(AF_INET, LINUX_IP, &addr->sin_addr);
if (ioctl(sock_fd, SIOCSIFADDR, &ifr) < 0) {
perror("ioctl SIOCSIFADDR");
close(sock_fd);
return -1;
}
inet_pton(AF_INET, "255.255.255.0", &addr->sin_addr);
if (ioctl(sock_fd, SIOCSIFNETMASK, &ifr) < 0) {
perror("ioctl SIOCSIFNETMASK");
close(sock_fd);
return -1;
}
if (ioctl(sock_fd, SIOCGIFFLAGS, &ifr) < 0) {
perror("ioctl SIOCGIFFLAGS");
close(sock_fd);
return -1;
}
ifr.ifr_flags = IFF_UP | IFF_RUNNING;
if (ioctl(sock_fd, SIOCSIFFLAGS, &ifr) < 0) {
perror("ioctl SIOCSIFFLAGS");
close(sock_fd);
return -1;
}
printf("Successfully initialized tap device %s\n", ifname);
return 0;
}
#include <sys/random.h>
uint32_t ipstack_getrandom(void)
{
uint32_t ret;
getrandom(&ret, sizeof(ret), 0);
return ret;
}
#define BUFFER_SIZE TEST_SIZE
static int test_echoserver_closewait(struct ipstack *s)
static int listen_fd = -1, client_fd = -1;
static int exit_ok = 0, exit_count = 0;
static uint8_t buf[TEST_SIZE];
static int tot_sent = 0;
static int tot_recv = 0;
static int server_closing = 0;
static int closed = 0;
static void socket_cb(int fd, uint16_t event, void *arg)
{
int fd, ret;
int client_fd = -1;
int tot_sent = 0;
int exit_ok = 0, exit_count = 0;
struct ipstack_sockaddr_in local_sock = {
.sin_family = AF_INET,
.sin_port = ee16(8), /* Echo */
.sin_addr.s_addr = 0
};
fd = posix_socket(s, AF_INET, IPSTACK_SOCK_STREAM, 0);
printf("socket: %04x\n", fd);
ret = posix_bind(s, fd, (struct ipstack_sockaddr *)&local_sock, sizeof(local_sock));
printf("bind: %d\n", ret);
ret = posix_listen(s, fd, 1);
printf("listen: %d\n", ret);
int ret = 0;
printf("Called socket_cb, events: %04x fd %d\n", event, fd & (~0x1000));
if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) {
client_fd = ft_accept((struct ipstack *)arg, listen_fd, NULL, NULL);
if (client_fd > 0) {
printf("accept: %04x\n", client_fd);
}
} else if ((fd == client_fd) && (event & CB_EVENT_READABLE )) {
ret = ft_recvfrom((struct ipstack *)arg, client_fd, buf, sizeof(buf), 0, NULL, NULL);
if (ret != -11) {
if (ret < 0) {
printf("Recv error: %d\n", ret);
ft_close((struct ipstack *)arg, client_fd);
} else if (ret == 0) {
printf("Client side closed the connection.\n");
ft_close((struct ipstack *)arg, client_fd);
printf("Server: Exiting.\n");
exit_ok = 1;
} else if (ret > 0) {
printf("recv: %d, echoing back\n", ret);
tot_recv += ret;
}
}
}
if ((event & CB_EVENT_WRITABLE) || ((ret > 0) && !closed)) {
int snd_ret;
if ((tot_sent >= 4096) && server_closing) {
ft_close((struct ipstack *)arg, client_fd);
printf("Server: I closed the connection.\n");
closed = 1;
exit_ok = 1;
}
if ((!closed) && (tot_sent < tot_recv)) {
snd_ret = ft_sendto((struct ipstack *)arg, client_fd, buf + tot_sent, tot_recv - tot_sent, 0, NULL, 0);
if (snd_ret != -11) {
if (snd_ret < 0) {
printf("Send error: %d\n", snd_ret);
ft_close((struct ipstack *)arg, client_fd);
} else {
tot_sent += snd_ret;
printf("sent %d bytes\n", snd_ret);
if (tot_recv == tot_sent) {
tot_sent = 0;
tot_recv = 0;
}
}
}
}
}
if (event & CB_EVENT_CLOSED) {
printf("Closing %d, client fd: %d\n", fd, client_fd);
}
if ((fd == client_fd) && (event & CB_EVENT_CLOSED)) {
printf("Client side closed the connection (EVENT_CLOSED)\n");
ft_close((struct ipstack *)arg, client_fd);
client_fd = -1;
printf("Server: Exiting.\n");
exit_ok = 1;
}
(void)arg;
}
static int test_echoserver(struct ipstack *s, int active_close)
{
exit_ok = 0;
exit_count = 0;
tot_sent = 0;
server_closing = active_close;
closed = 0;
while(1) {
uint32_t ms_next;
uint8_t buf[BUFFER_SIZE];
struct timeval tv;
gettimeofday(&tv, NULL);
ms_next = ipstack_poll(s, tv.tv_sec * 1000 + tv.tv_usec / 1000);
@ -175,55 +108,11 @@ static int test_echoserver_closewait(struct ipstack *s)
continue;
else break;
}
if (client_fd < 0) {
client_fd = posix_accept(s, fd, NULL, NULL);
if (client_fd > 0) {
printf("accept: %04x\n", client_fd);
}
} else {
if (ret <= 0) {
ret = posix_recvfrom(s, client_fd, buf, sizeof(buf), 0, NULL, NULL);
if (ret == -11)
continue; /* Call again */
if (ret < 0) {
printf("Recv error: %d\n", ret);
posix_close(s, client_fd);
return -1;
} else if (ret == 0) {
printf("Client side closed the connection.\n");
posix_close(s, client_fd);
client_fd = -1;
printf("Server: Exiting.\n");
exit_ok = 1;
continue;
} else if (ret > 0) {
printf("recv: %d, echoing back\n", ret);
}
}
if (ret > 0) {
int snd_ret;
snd_ret = posix_sendto(s, client_fd, buf + tot_sent, ret - tot_sent, 0, NULL, 0);
if (snd_ret == -11)
continue; /* Call again */
if (snd_ret < 0) {
printf("Send error: %d\n", snd_ret);
posix_close(s, client_fd);
return 0;
}
tot_sent += snd_ret;
printf("sent %d bytes\n", snd_ret);
if (ret == tot_sent) {
tot_sent = 0;
ret = 0;
}
}
}
}
return 0;
}
static void *pt_echoclient_closing(void *arg)
void *pt_echoclient(void *arg)
{
int fd, ret;
unsigned total_r = 0;
@ -265,7 +154,10 @@ static void *pt_echoclient_closing(void *arg)
}
if (ret == 0) {
printf("test client read: server has closed the connection.\n");
return (void *)-1;
if (server_closing)
return (void *)0;
else
return (void *)-1;
}
total_r += ret;
}
@ -283,35 +175,43 @@ static void *pt_echoclient_closing(void *arg)
return (void *)0;
}
extern int tap_init(struct ll *dev, const char *name, uint32_t host_ip);
int main(int argc, char **argv)
{
struct ipstack *s;
struct ll *tapdev;
pthread_t pt;
struct timeval tv;
struct in_addr linux_ip;
ip4 ip = 0, nm = 0, gw = 0;
uint32_t srv_ip;
struct ipstack_sockaddr_in local_sock = {
.sin_family = AF_INET,
.sin_port = ee16(8), /* Echo */
.sin_addr.s_addr = 0
};
int ret, test_ret;
int ret, test_ret = 0;
(void)argc;
(void)argv;
(void)ip;
(void)nm;
(void)gw;
(void)tv;
(void)pt;
(void)srv_ip;
ipstack_init_static(&s);
tapdev = ipstack_getdev(s);
if (!tapdev)
return 1;
if (tap_init(tapdev, "femt0") < 0) {
inet_aton(LINUX_IP, &linux_ip);
if (tap_init(tapdev, "femt0", linux_ip.s_addr) < 0) {
perror("tap init");
return 2;
}
system("tcpdump -i femt0 -w test.pcap &");
sleep(1);
#ifdef DHCP
gettimeofday(&tv, NULL);
@ -333,14 +233,32 @@ int main(int argc, char **argv)
inet_pton(AF_INET, FEMTOTCP_IP, &srv_ip);
#endif
listen_fd = ft_socket(s, AF_INET, IPSTACK_SOCK_STREAM, 0);
printf("socket: %04x\n", listen_fd);
ipstack_register_callback(s, listen_fd, socket_cb, s);
pthread_create(&pt, NULL, pt_echoclient_closing, &srv_ip);
pthread_create(&pt, NULL, pt_echoclient, &srv_ip);
printf("Starting test: echo server close-wait\n");
ret = test_echoserver_closewait(s);
ret = ft_bind(s, listen_fd, (struct ipstack_sockaddr *)&local_sock, sizeof(local_sock));
printf("bind: %d\n", ret);
ret = ft_listen(s, listen_fd, 1);
printf("listen: %d\n", ret);
ret = test_echoserver(s, 0);
pthread_join(pt, (void **)&test_ret);
printf("Test echo server close-wait: %d\n", ret);
printf("Test linux client: %d\n", test_ret);
sleep(1);
pthread_create(&pt, NULL, pt_echoclient, &srv_ip);
printf("Starting test: echo server active close\n");
ret = test_echoserver(s, 1);
printf("Test echo server close-wait: %d\n", ret);
pthread_join(pt, (void **)&test_ret);
printf("Test linux client: %d\n", test_ret);
sleep(1);
system("killall tcpdump");
return 0;
}

View file

@ -45,162 +45,10 @@ void mock_link_init(struct ipstack *s)
ll->send = mock_send;
}
START_TEST(test_arp_request_basic)
{
struct ipstack s;
struct arp_packet *arp;
ipstack_init(&s);
uint32_t target_ip = 0xC0A80002; /* 192.168.0.2 */
mock_link_init(&s);
s.last_tick = 1000;
arp_request(&s, target_ip);
ck_assert_int_eq(last_frame_sent_size, sizeof(struct arp_packet));
arp = (struct arp_packet *)last_frame_sent;
ck_assert_mem_eq(arp->eth.dst, "\xff\xff\xff\xff\xff\xff", 6);
ck_assert_mem_eq(arp->eth.src, s.ll_dev.mac, 6);
ck_assert_int_eq(arp->eth.type, ee16(0x0806));
ck_assert_int_eq(arp->htype, ee16(1));
ck_assert_int_eq(arp->ptype, ee16(0x0800));
ck_assert_int_eq(arp->hlen, 6);
ck_assert_int_eq(arp->plen, 4);
ck_assert_int_eq(arp->opcode, ee16(ARP_REQUEST));
ck_assert_mem_eq(arp->sma, s.ll_dev.mac, 6);
ck_assert_int_eq(arp->sip, ee32(s.ipconf.ip));
ck_assert_mem_eq(arp->tma, "\x00\x00\x00\x00\x00\x00", 6);
ck_assert_int_eq(arp->tip, ee32(target_ip));
static struct timers_binheap heap;
static void reset_heap(void) {
heap.size = 0;
}
END_TEST
START_TEST(test_arp_request_throttle)
{
struct ipstack s;
ipstack_init(&s);
uint32_t target_ip = 0xC0A80002; /*192.168.0.2*/
mock_link_init(&s);
s.last_tick = 1000;
s.arp.last_arp = 880;
last_frame_sent_size = 0;
arp_request(&s, target_ip);
ck_assert_int_eq(last_frame_sent_size, 0);
}
END_TEST
START_TEST(test_arp_request_target_ip) {
uint32_t target_ip = 0xC0A80002;
struct ipstack s;
ipstack_init(&s);
mock_link_init(&s);
s.last_tick = 1000;
arp_request(&s, target_ip);
ck_assert_int_eq(((struct arp_packet *)(last_frame_sent))->tip, ee32(target_ip));
}
END_TEST
START_TEST(test_arp_request_handling) {
struct arp_packet arp_req;
struct arp_packet *arp_reply;
memset(&arp_req, 0, sizeof(arp_req));
uint32_t req_ip = 0xC0A80002; // 192.168.0.2
uint32_t device_ip = 0xC0A80001; // 192.168.0.1
uint8_t req_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
//uint8_t mac[6] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
struct ipstack s;
ipstack_init(&s);
mock_link_init(&s);
s.ipconf.ip = device_ip;
/* Prepare ARP request */
arp_req.opcode = ee16(ARP_REQUEST);
arp_req.sip = ee32(req_ip);
memcpy(arp_req.sma, req_mac, 6);
arp_req.tip = ee32(device_ip);
/* Call arp_recv with the ARP request */
arp_recv(&s, &arp_req, sizeof(arp_req));
ipstack_poll(&s, 1000);
ipstack_poll(&s, 1001);
ipstack_poll(&s, 1002);
/* Check if ARP table updated with requester's MAC and IP */
/* TODO */
//ck_assert_int_eq(arp_lookup(&s, req_ip, mac), 0);
//ck_assert_mem_eq(mac, req_mac, 6);
/* Check if an ARP reply was generated */
arp_reply = (struct arp_packet *)last_frame_sent;
ck_assert_int_eq(last_frame_sent_size, sizeof(struct arp_packet));
ck_assert_int_eq(arp_reply->opcode, ee16(ARP_REPLY));
ck_assert_mem_eq(arp_reply->sma, s.ll_dev.mac, 6); // source MAC
ck_assert_int_eq(arp_reply->sip, ee32(device_ip)); // source IP
ck_assert_mem_eq(arp_reply->tma, req_mac, 6); // target MAC
ck_assert_int_eq(arp_reply->tip, ee32(req_ip)); // target IP
}
END_TEST
START_TEST(test_arp_reply_handling) {
struct arp_packet arp_reply;
memset(&arp_reply, 0, sizeof(arp_reply));
uint32_t reply_ip = 0xC0A80003; // 192.168.0.3
uint8_t reply_mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01};
struct ipstack s;
ipstack_init(&s);
mock_link_init(&s);
/* Prepare ARP reply */
arp_reply.opcode = ee16(ARP_REPLY);
arp_reply.sip = ee32(reply_ip);
memcpy(arp_reply.sma, reply_mac, 6);
/* Call arp_recv with the ARP reply */
arp_recv(&s, &arp_reply, sizeof(arp_reply));
/* Check if ARP table updated with reply IP and MAC */
ck_assert_int_eq(s.arp.neighbors[0].ip, reply_ip);
ck_assert_mem_eq(s.arp.neighbors[0].mac, reply_mac, 6);
/* Update same IP with a different MAC address */
uint8_t new_mac[6] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
memcpy(arp_reply.sma, new_mac, 6);
arp_recv(&s, &arp_reply, sizeof(arp_reply));
/* Check if ARP table updates with new MAC */
ck_assert_mem_eq(s.arp.neighbors[0].mac, new_mac, 6);
}
END_TEST
START_TEST(test_arp_lookup_success) {
uint8_t found_mac[6];
uint32_t ip = 0xC0A80002;
const uint8_t mock_mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01};
struct ipstack s;
ipstack_init(&s);
mock_link_init(&s);
/* Add a known IP-MAC pair */
s.arp.neighbors[0].ip = ip;
memcpy(s.arp.neighbors[0].mac, mock_mac, 6);
/* Test arp_lookup */
int result = arp_lookup(&s, ip, found_mac);
ck_assert_int_eq(result, 0);
ck_assert_mem_eq(found_mac, mock_mac, 6);
}
END_TEST
START_TEST(test_arp_lookup_failure) {
uint8_t found_mac[6];
uint32_t ip = 0xC0A80004;
struct ipstack s;
ipstack_init(&s);
mock_link_init(&s);
/* Ensure arp_lookup fails for unknown IP */
int result = arp_lookup(&s, ip, found_mac);
ck_assert_int_eq(result, -1);
uint8_t zero_mac[6] = {0, 0, 0, 0, 0, 0};
ck_assert_mem_eq(found_mac, zero_mac, 6);
}
END_TEST
START_TEST(test_fifo_init)
@ -505,19 +353,348 @@ START_TEST(test_queue_pop_wraparound) {
END_TEST
/* Utils */
START_TEST(test_insert_timer) {
reset_heap();
struct ipstack_timer tmr1 = { .expires = 100 };
struct ipstack_timer tmr2 = { .expires = 50 };
struct ipstack_timer tmr3 = { .expires = 200 };
int id1 = timers_binheap_insert(&heap, tmr1);
int id2 = timers_binheap_insert(&heap, tmr2);
int id3 = timers_binheap_insert(&heap, tmr3);
ck_assert_int_eq(heap.size, 3);
ck_assert_int_lt(heap.timers[0].expires, heap.timers[1].expires);
ck_assert_int_lt(heap.timers[0].expires, heap.timers[2].expires);
ck_assert_int_ne(id1, id2);
ck_assert_int_ne(id2, id3);
}
END_TEST
START_TEST(test_pop_timer) {
reset_heap();
struct ipstack_timer tmr1 = { .expires = 300 };
struct ipstack_timer tmr2 = { .expires = 100 };
struct ipstack_timer tmr3 = { .expires = 200 };
timers_binheap_insert(&heap, tmr1);
timers_binheap_insert(&heap, tmr2);
timers_binheap_insert(&heap, tmr3);
struct ipstack_timer popped = timers_binheap_pop(&heap);
ck_assert_int_eq(popped.expires, 100);
ck_assert_int_eq(heap.size, 2);
ck_assert_int_lt(heap.timers[0].expires, heap.timers[1].expires);
}
END_TEST
START_TEST(test_is_timer_expired) {
reset_heap();
struct ipstack_timer tmr = { .expires = 150 };
timers_binheap_insert(&heap, tmr);
ck_assert_int_eq(is_timer_expired(&heap, 100), 0);
ck_assert_int_eq(is_timer_expired(&heap, 150), 1);
ck_assert_int_eq(is_timer_expired(&heap, 200), 1);
}
END_TEST
START_TEST(test_cancel_timer) {
reset_heap();
struct ipstack_timer tmr1 = { .expires = 100 };
struct ipstack_timer tmr2 = { .expires = 200 };
int id1 = timers_binheap_insert(&heap, tmr1);
int id2 = timers_binheap_insert(&heap, tmr2);
(void)id2;
timer_binheap_cancel(&heap, id1);
ck_assert_int_eq(heap.timers[0].expires, 0); // tmr1 canceled
struct ipstack_timer popped = timers_binheap_pop(&heap);
ck_assert_int_eq(popped.expires, 200); // Only tmr2 should remain
ck_assert_int_eq(heap.size, 0);
}
END_TEST
/* Arp suite */
START_TEST(test_arp_request_basic)
{
struct ipstack s;
struct arp_packet *arp;
ipstack_init(&s);
uint32_t target_ip = 0xC0A80002; /* 192.168.0.2 */
mock_link_init(&s);
s.last_tick = 1000;
arp_request(&s, target_ip);
ck_assert_int_eq(last_frame_sent_size, sizeof(struct arp_packet));
arp = (struct arp_packet *)last_frame_sent;
ck_assert_mem_eq(arp->eth.dst, "\xff\xff\xff\xff\xff\xff", 6);
ck_assert_mem_eq(arp->eth.src, s.ll_dev.mac, 6);
ck_assert_int_eq(arp->eth.type, ee16(0x0806));
ck_assert_int_eq(arp->htype, ee16(1));
ck_assert_int_eq(arp->ptype, ee16(0x0800));
ck_assert_int_eq(arp->hlen, 6);
ck_assert_int_eq(arp->plen, 4);
ck_assert_int_eq(arp->opcode, ee16(ARP_REQUEST));
ck_assert_mem_eq(arp->sma, s.ll_dev.mac, 6);
ck_assert_int_eq(arp->sip, ee32(s.ipconf.ip));
ck_assert_mem_eq(arp->tma, "\x00\x00\x00\x00\x00\x00", 6);
ck_assert_int_eq(arp->tip, ee32(target_ip));
}
END_TEST
START_TEST(test_arp_request_throttle)
{
struct ipstack s;
ipstack_init(&s);
uint32_t target_ip = 0xC0A80002; /*192.168.0.2*/
mock_link_init(&s);
s.last_tick = 1000;
s.arp.last_arp = 880;
last_frame_sent_size = 0;
arp_request(&s, target_ip);
ck_assert_int_eq(last_frame_sent_size, 0);
}
END_TEST
START_TEST(test_arp_request_target_ip) {
uint32_t target_ip = 0xC0A80002;
struct ipstack s;
ipstack_init(&s);
mock_link_init(&s);
s.last_tick = 1000;
arp_request(&s, target_ip);
ck_assert_int_eq(((struct arp_packet *)(last_frame_sent))->tip, ee32(target_ip));
}
END_TEST
START_TEST(test_arp_request_handling) {
struct arp_packet arp_req;
struct arp_packet *arp_reply;
memset(&arp_req, 0, sizeof(arp_req));
uint32_t req_ip = 0xC0A80002; // 192.168.0.2
uint32_t device_ip = 0xC0A80001; // 192.168.0.1
uint8_t req_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
//uint8_t mac[6] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
struct ipstack s;
ipstack_init(&s);
mock_link_init(&s);
s.ipconf.ip = device_ip;
/* Prepare ARP request */
arp_req.opcode = ee16(ARP_REQUEST);
arp_req.sip = ee32(req_ip);
memcpy(arp_req.sma, req_mac, 6);
arp_req.tip = ee32(device_ip);
/* Call arp_recv with the ARP request */
arp_recv(&s, &arp_req, sizeof(arp_req));
ipstack_poll(&s, 1000);
ipstack_poll(&s, 1001);
ipstack_poll(&s, 1002);
/* Check if ARP table updated with requester's MAC and IP */
/* TODO */
//ck_assert_int_eq(arp_lookup(&s, req_ip, mac), 0);
//ck_assert_mem_eq(mac, req_mac, 6);
/* Check if an ARP reply was generated */
arp_reply = (struct arp_packet *)last_frame_sent;
ck_assert_int_eq(last_frame_sent_size, sizeof(struct arp_packet));
ck_assert_int_eq(arp_reply->opcode, ee16(ARP_REPLY));
ck_assert_mem_eq(arp_reply->sma, s.ll_dev.mac, 6); // source MAC
ck_assert_int_eq(arp_reply->sip, ee32(device_ip)); // source IP
ck_assert_mem_eq(arp_reply->tma, req_mac, 6); // target MAC
ck_assert_int_eq(arp_reply->tip, ee32(req_ip)); // target IP
}
END_TEST
START_TEST(test_arp_reply_handling) {
struct arp_packet arp_reply;
memset(&arp_reply, 0, sizeof(arp_reply));
uint32_t reply_ip = 0xC0A80003; // 192.168.0.3
uint8_t reply_mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01};
struct ipstack s;
ipstack_init(&s);
mock_link_init(&s);
/* Prepare ARP reply */
arp_reply.opcode = ee16(ARP_REPLY);
arp_reply.sip = ee32(reply_ip);
memcpy(arp_reply.sma, reply_mac, 6);
/* Call arp_recv with the ARP reply */
arp_recv(&s, &arp_reply, sizeof(arp_reply));
/* Check if ARP table updated with reply IP and MAC */
ck_assert_int_eq(s.arp.neighbors[0].ip, reply_ip);
ck_assert_mem_eq(s.arp.neighbors[0].mac, reply_mac, 6);
/* Update same IP with a different MAC address */
uint8_t new_mac[6] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
memcpy(arp_reply.sma, new_mac, 6);
arp_recv(&s, &arp_reply, sizeof(arp_reply));
/* Check if ARP table updates with new MAC */
ck_assert_mem_eq(s.arp.neighbors[0].mac, new_mac, 6);
}
END_TEST
START_TEST(test_arp_lookup_success) {
uint8_t found_mac[6];
uint32_t ip = 0xC0A80002;
const uint8_t mock_mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01};
struct ipstack s;
ipstack_init(&s);
mock_link_init(&s);
/* Add a known IP-MAC pair */
s.arp.neighbors[0].ip = ip;
memcpy(s.arp.neighbors[0].mac, mock_mac, 6);
/* Test arp_lookup */
int result = arp_lookup(&s, ip, found_mac);
ck_assert_int_eq(result, 0);
ck_assert_mem_eq(found_mac, mock_mac, 6);
}
END_TEST
START_TEST(test_arp_lookup_failure) {
uint8_t found_mac[6];
uint32_t ip = 0xC0A80004;
struct ipstack s;
ipstack_init(&s);
mock_link_init(&s);
/* Ensure arp_lookup fails for unknown IP */
int result = arp_lookup(&s, ip, found_mac);
ck_assert_int_eq(result, -1);
uint8_t zero_mac[6] = {0, 0, 0, 0, 0, 0};
ck_assert_mem_eq(found_mac, zero_mac, 6);
}
END_TEST
// Test for `transport_checksum` calculation
START_TEST(test_transport_checksum) {
union transport_pseudo_header ph;
struct ipstack_tcp_seg tcp_data;
memset(&ph, 0, sizeof(ph));
memset(&tcp_data, 0, sizeof(tcp_data));
// Set up pseudo-header values for test
ph.ph.src = 0xc0a80101; // 192.168.1.1
ph.ph.dst = 0xc0a80102; // 192.168.1.2
ph.ph.proto = IPPROTO_TCP;
ph.ph.len = ee16(20); // TCP header length (without options)
// Test with a simple TCP header with src/dst ports and no data
tcp_data.src_port = ee16(12345);
tcp_data.dst_port = ee16(80);
tcp_data.seq = ee32(1);
tcp_data.ack = ee32(0);
tcp_data.hlen = 5; // offset=5 (20 bytes)
tcp_data.flags = 0x02; // SYN
tcp_data.win = ee16(65535);
uint16_t checksum = transport_checksum(&ph, &tcp_data.src_port);
ck_assert_msg(checksum != 0, "Transport checksum should not be zero");
}
END_TEST
// Test for `iphdr_set_checksum` calculation
START_TEST(test_iphdr_set_checksum) {
struct ipstack_ip_packet ip;
memset(&ip, 0, sizeof(ip));
ip.ver_ihl = 0x45;
ip.tos = 0;
ip.len = ee16(20);
ip.id = ee16(1);
ip.flags_fo = 0;
ip.ttl = 64;
ip.proto = IPPROTO_TCP;
ip.src = ee32(0xc0a80101); // 192.168.1.1
ip.dst = ee32(0xc0a80102); // 192.168.1.2
iphdr_set_checksum(&ip);
ck_assert_msg(ip.csum != 0, "IP header checksum should not be zero");
}
END_TEST
// Test for `eth_output_add_header` to add Ethernet headers
START_TEST(test_eth_output_add_header) {
struct ipstack_eth_frame eth_frame;
struct ipstack S;
memset(&S, 0, sizeof(S));
memset(&eth_frame, 0, sizeof(eth_frame));
uint8_t test_mac[6] = {0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
memcpy(S.ll_dev.mac, test_mac, 6);
eth_output_add_header(&S, NULL, &eth_frame, ETH_TYPE_IP);
ck_assert_mem_eq(eth_frame.dst, "\xff\xff\xff\xff\xff\xff", 6); // Broadcast
ck_assert_mem_eq(eth_frame.src, test_mac, 6);
ck_assert_uint_eq(eth_frame.type, ee16(ETH_TYPE_IP));
}
END_TEST
// Test for `ip_output_add_header` to set up IP headers and calculate checksums
START_TEST(test_ip_output_add_header) {
struct tsocket t;
struct ipstack_ip_packet ip;
struct ipstack S;
memset(&t, 0, sizeof(t));
memset(&ip, 0, sizeof(ip));
memset(&S, 0, sizeof(S));
// Setup socket and IP stack parameters
t.local_ip = 0xc0a80101; // 192.168.1.1
t.remote_ip = 0xc0a80102; // 192.168.1.2
t.S = &S;
// Run the function for a TCP packet
int result = ip_output_add_header(&t, &ip, IPPROTO_TCP, 40);
ck_assert_int_eq(result, 0);
// Validate IP header fields
ck_assert_uint_eq(ip.ver_ihl, 0x45);
ck_assert_uint_eq(ip.ttl, 64);
ck_assert_uint_eq(ip.proto, IPPROTO_TCP);
ck_assert_uint_eq(ip.src, ee32(t.local_ip));
ck_assert_uint_eq(ip.dst, ee32(t.remote_ip));
ck_assert_msg(ip.csum != 0, "IP header checksum should not be zero");
// Check the pseudo-header checksum calculation for TCP segment
struct ipstack_tcp_seg *tcp = (struct ipstack_tcp_seg *)&ip;
ck_assert_msg(tcp->csum != 0, "TCP checksum should not be zero");
}
END_TEST
Suite *femto_suite(void)
{
Suite *s;
TCase *tc_core, *tc_proto;
TCase *tc_core, *tc_proto, *tc_utils;
s = suite_create("FemtoTCP");
tc_core = tcase_create("Core");
tc_utils = tcase_create("Utils");
tc_proto = tcase_create("Protocols");
tcase_add_test(tc_core, test_fifo_init);
suite_add_tcase(s, tc_core);
suite_add_tcase(s, tc_utils);
suite_add_tcase(s, tc_proto);
tcase_add_test(tc_core, test_fifo_push_and_pop);
@ -560,6 +737,15 @@ Suite *femto_suite(void)
tcase_add_test(tc_core, test_queue_pop_wraparound);
suite_add_tcase(s, tc_core);
tcase_add_test(tc_utils, test_insert_timer);
suite_add_tcase(s, tc_utils);
tcase_add_test(tc_utils, test_pop_timer);
suite_add_tcase(s, tc_utils);
tcase_add_test(tc_utils, test_is_timer_expired);
suite_add_tcase(s, tc_utils);
tcase_add_test(tc_utils, test_cancel_timer);
suite_add_tcase(s, tc_utils);
tcase_add_test(tc_proto, test_arp_request_basic);
suite_add_tcase(s, tc_proto);
tcase_add_test(tc_proto, test_arp_request_throttle);
@ -574,9 +760,15 @@ Suite *femto_suite(void)
suite_add_tcase(s, tc_proto);
tcase_add_test(tc_proto, test_arp_lookup_failure);
suite_add_tcase(s, tc_proto);
tcase_add_test(tc_utils, test_transport_checksum);
suite_add_tcase(s, tc_proto);
tcase_add_test(tc_utils, test_iphdr_set_checksum);
suite_add_tcase(s, tc_proto);
tcase_add_test(tc_utils, test_eth_output_add_header);
suite_add_tcase(s, tc_proto);
tcase_add_test(tc_utils, test_ip_output_add_header);
suite_add_tcase(s, tc_proto);
return s;
}