diff --git a/network/Makefile b/network/Makefile new file mode 100644 index 00000000..b894cab7 --- /dev/null +++ b/network/Makefile @@ -0,0 +1,5 @@ +NAME := test-tcp +LOCAL_SRCS := test-tcp.c common.c +DEP_LIBS := unity + +include $(binary.mk) diff --git a/network/common.c b/network/common.c new file mode 100644 index 00000000..3db2c251 --- /dev/null +++ b/network/common.c @@ -0,0 +1,94 @@ +/* + * Phoenix-RTOS + * + * network tests common routines + * + * Copyright 2024 Phoenix Systems + * Author: Adam Debek + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + + +int create_con(const char *daddr, uint16_t dport) +{ + int try = 10; + int sockfd; + struct sockaddr_in dest_addr; + + if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { + return -1; + } + + memset(&dest_addr, 0, sizeof dest_addr); + dest_addr.sin_family = AF_INET; + dest_addr.sin_addr.s_addr = inet_addr(daddr); + dest_addr.sin_port = htons(dport); + + while (connect(sockfd, (struct sockaddr *)&dest_addr, sizeof dest_addr) < 0) { + if (try-- > 0) { + close(sockfd); + if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { + perror("socket"); + return -1; + } + sleep(1); + } + else { + close(sockfd); + return -1; + } + } + + return sockfd; +} + + +int wait_if_running(void) +{ + struct ifreq ioctlInterface; + short interfaceFlags; + char *ifname = "en1"; + int try = 300; + int ret; + + strcpy(ioctlInterface.ifr_name, ifname); + + int sd = socket(AF_INET, SOCK_STREAM, 0); + if (sd < 0) { + perror("socket"); + return -1; + } + + while (try-- > 0) { + ret = ioctl(sd, SIOCGIFFLAGS, &ioctlInterface); + if (ret < 0) { + /* Unable to obtain flags */ + perror("ioctl(SIOCGIFFLAGS)"); + return ret; + } + + interfaceFlags = ioctlInterface.ifr_flags; + if (interfaceFlags & IFF_RUNNING) { + return 0; + } + + usleep(10000); + } + + /* Interface not running */ + return -1; +} diff --git a/network/common.h b/network/common.h new file mode 100644 index 00000000..a13229d3 --- /dev/null +++ b/network/common.h @@ -0,0 +1,111 @@ +/* + * Phoenix-RTOS + * + * network tests common header + * + * Copyright 2024 Phoenix Systems + * Author: Adam Debek + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#include + +#ifndef _TEST_NETWORK_COMMON_H +#define _TEST_NETWORK_COMMON_H + +#define send_asserted(_sockfd, _buffer, _length, _flags) \ + do { \ + ssize_t n = 0; \ + size_t total = 0; \ + size_t left = _length; \ + size_t send_len = _length; \ + struct pollfd fds[1]; \ + fds[0].fd = _sockfd; \ + fds[0].events = POLLOUT; \ + fds[0].revents = 0; \ + while (n < _length && poll(fds, 1, 500)) { \ + if (fds[0].revents & POLLOUT) { \ + n = send(_sockfd, _buffer + total, send_len, _flags); \ + if (n < 0 && errno == EPIPE) { \ + TEST_FAIL_MESSAGE("Host closed connection"); \ + } \ + else if (n < 0 && errno == EMSGSIZE) { \ + send_len /= 2; \ + continue; \ + } \ + else if (n < 0) { \ + TEST_FAIL_MESSAGE(strerror(errno)); \ + } \ + } \ + total += n; \ + left -= n; \ + if (left < send_len) { \ + send_len = left; \ + } \ + } \ + TEST_ASSERT(n == _length); \ + } while (0) + + +#define recv_asserted(_sockfd, _buffer, _length, _flags) \ + do { \ + ssize_t r = 0; \ + struct pollfd fds[1]; \ + fds[0].fd = _sockfd; \ + fds[0].events = POLLIN; \ + fds[0].revents = 0; \ + while (r < _length && poll(fds, 1, 500)) { \ + if (fds[0].revents & POLLIN) { \ + r = recv(_sockfd, _buffer, _length, _flags); \ + if (r == 0) { \ + TEST_FAIL_MESSAGE("Host closed connection"); \ + } \ + else if (r < 0) { \ + TEST_FAIL_MESSAGE(strerror(errno)); \ + } \ + } \ + else { \ + TEST_FAIL_MESSAGE("Poll didn't receive remaining data"); \ + } \ + } \ + TEST_ASSERT(r == _length); \ + } while (0) + + +#define get_host_response(_syncfd, _buffer) \ + do { \ + ssize_t r; \ + r = recv(_syncfd, _buffer, sizeof _buffer, MSG_WAITALL); \ + if (r == 0) { \ + fprintf(stderr, "Sync socket: host closed connection\n"); \ + close(_syncfd); \ + exit(1); \ + } \ + else if (r < 0) { \ + perror("Sync socket"); \ + close(_syncfd); \ + exit(1); \ + } \ + else if (target_failed_flag == 0) { \ + host_response_flag = 1; \ + if (_buffer[0] != 0) { \ + TEST_FAIL_MESSAGE(_buffer); \ + } \ + } \ + else if (target_failed_flag == 1) { \ + if (_buffer[0] != 0) { \ + fprintf(stderr, "%s\n", _buffer); \ + } \ + } \ + } while (0) + +/* If sport is 0, source port will be assigned by kernel */ +int create_con(const char *daddr, uint16_t dport); + +/* Wait for running interface */ +int wait_if_running(void); + +#endif /* TEST_NETWORK_COMMON_H */ diff --git a/network/tcp-harness.py b/network/tcp-harness.py new file mode 100644 index 00000000..c79113b4 --- /dev/null +++ b/network/tcp-harness.py @@ -0,0 +1,562 @@ +import random +import select +import re +import socket +import subprocess +import os +import threading +import struct +import time +from dataclasses import dataclass + +import psh.tools.psh as psh +from trunner.ctx import TestContext +from trunner.dut import Dut +from trunner.types import TestResult +from trunner.harness.unity import unity_harness + + +def get_config(): + """Return config depending if trunner is being run inside github-runner container or not""" + out = subprocess.run( + ["ifconfig", "eth0"], + encoding="ascii", + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=True, + ) + + host_ip = os.environ.get("HOST_IP") + netmask = os.environ.get("HOST_MASK") + doc_ip = None + + if host_ip is None or netmask is None: + # Host interface + host_ip = re.search("inet ([0-9.]+)", out.stdout).group(1) + netmask = re.search("netmask ([0-9.]+)", out.stdout).group(1) + else: + # Docker interface + doc_ip = re.search("inet ([0-9.]+)", out.stdout).group(1) + + octets = host_ip.split(".") + fourth_octet = int(octets[3]) + 100 + target_ip = ".".join(octets[:3] + [str(fourth_octet)]) + + return doc_ip, host_ip, target_ip, netmask + + +def target_setup(p, iface, ip, netmask): + ifconfig_setup_cmd = f"ifconfig {iface} {ip} netmask {netmask} up" + psh.assert_prompt_after_cmd(p, ifconfig_setup_cmd, "success") + + +def con_setup(doc_ip, host_ip, port): + try: + host_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + host_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + if doc_ip is None: + host_socket.bind((host_ip, port)) + else: + host_socket.bind((doc_ip, port)) + + host_socket.listen(1) + ready_to_read, _, _ = select.select([host_socket], [], [], 20) + + if ready_to_read: + peer_socket, _ = host_socket.accept() + else: + peer_socket = None + + host_socket.close() + + except socket.error as e: + print(f"Host error - con_setup: {e}") + return None + + return peer_socket + + +def create_IP_header(src_ip, dest_ip): + # Convert IP addresses to integer + src_ip_int = struct.unpack("!I", socket.inet_aton(src_ip))[0] + dest_ip_int = struct.unpack("!I", socket.inet_aton(dest_ip))[0] + + ip_header = struct.pack("!BBHHHBBHII", + 69, # Version and IHL + 0, # Type of Service + 0, # Total length (filled by kernel) + 0, # Packet id + 0x4000, # Flags and Fragment Offset + 64, # Time to Live + 6, # TCP protocol + 0, # Checksum (filled by kernel) + src_ip_int, # Source Address + dest_ip_int) # Destination Address + + return ip_header + + +def create_pseudo_header(src_ip, dest_ip, payload): + # Convert IP addresses to integer + src_ip_int = struct.unpack("!I", socket.inet_aton(src_ip))[0] + dest_ip_int = struct.unpack("!I", socket.inet_aton(dest_ip))[0] + + if payload is True: + payload_len = 4 + else: + payload_len = 0 + + tcp_len = struct.calcsize("!HHIIBBHHH") + payload_len + + pseudo_header = struct.pack("!IIBBH", + src_ip_int, # Source IP address + dest_ip_int, # Destination IP address + 0, # Reserved + 6, # TCP protocol + tcp_len) # TCP segment length + + return pseudo_header + + +@dataclass +class TCP_Data: + src_port: int + dest_port: int + seq: int + ack: int + flags: int + window: int + checksum: int + wrong_checksum: int + urg: int + payload: bool + + +def create_tcp_data(seq, ack, window, src_port, target_port, **kwargs) -> TCP_Data: + src_port = src_port + dest_port = target_port + checksum = 0 + wrong_checksum = None + urg = 0 + payload = True + flags = 24 # ACK + PUSH + + key, value = next(iter(kwargs.items())) + if key == "bad_src_port": + src_port = value + elif key == "bad_dest_port": + dest_port = value + elif key == "bad_seq": + seq = value + elif key == "bad_ack": + ack = value + elif key == "bad_window": + window = value + elif key == "bad_checksum": + wrong_checksum = value + elif urg == "bad_urg": + urg = value + elif key == "flags": + flags = value + if flags == 4: # RST + ack = 0 + window = 0 + payload = False + + tcp_data = TCP_Data(src_port, dest_port, seq, ack, flags, + window, checksum, wrong_checksum, urg, payload) + + return tcp_data + + +def create_tcp_header(tcp_data: TCP_Data): + tcp_header = struct.pack("!HHIIBBHHH", + tcp_data.src_port, # Source port + tcp_data.dest_port, # Destination port + tcp_data.seq, # Sequence number + tcp_data.ack, # Acknowledgment number + 5 << 4, # Data offset + tcp_data.flags, # Flags + tcp_data.window, # Window size + tcp_data.checksum, # Checksum + tcp_data.urg) # Urgent pointer + + return tcp_header + + +def calc_tcp_checksum(data): + sum = 0 + data_len = len(data) + + for i in range(0, data_len - data_len % 2, 2): + sum += (data[i] << 8) + data[i + 1] + + if data_len % 2: + sum += data[-1] << 8 + + while sum >> 16: + sum = (sum & 0xFFFF) + (sum >> 16) + + return ~sum & 0xFFFF + + +def create_packet(host_ip, target_ip, tcp_data: TCP_Data): + ip_header = create_IP_header(host_ip, target_ip) + tcp_header = create_tcp_header(tcp_data) + pseudo_header = create_pseudo_header(host_ip, target_ip, tcp_data.payload) + payload = bytes([1, 2, 3, 4]) + + if tcp_data.payload is True: + data = pseudo_header + tcp_header + payload + else: + data = pseudo_header + tcp_header + + checksum = calc_tcp_checksum(data) + tcp_data.checksum = checksum + + if tcp_data.wrong_checksum is not None: + tcp_data.checksum = tcp_data.wrong_checksum + + tcp_header = create_tcp_header(tcp_data) + + if tcp_data.payload is True: + packet = ip_header + tcp_header + payload + else: + packet = ip_header + tcp_header + + return packet + + +def sniff_packet(target_ip, result): + # Sniff last packet of 3 way handshake to get connection details + raw_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) + timeout = struct.pack('ll', 15, 0) + raw_socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, timeout) + prev_seq = None + + while True: + try: + packet, addr = raw_socket.recvfrom(128) + except socket.error as e: + raw_socket.close() + print(f"Host error - sniff thread: {e}") + return + + ack = (packet[28] << 24) + (packet[29] << 16) + (packet[30] << 8) + packet[31] + seq = (packet[24] << 24) + (packet[25] << 16) + (packet[26] << 8) + packet[27] + + if ack == 0: + prev_seq = (packet[24] << 24) + (packet[25] << 16) + (packet[26] << 8) + packet[27] + + if addr[0] == target_ip and ack != 0 and prev_seq is not None and prev_seq + 1 == seq: + seq = (packet[24] << 24) + (packet[25] << 16) + (packet[26] << 8) + packet[27] + window = (packet[34] << 8) + packet[35] + target_port = (packet[20] << 8) + packet[21] + + result.append(ack) + result.append(seq) + result.append(window) + result.append(target_port) + raw_socket.close() + break + + +def end_tc(sync_socket, err_msg, target_socket): + if len(err_msg) != 0: + # Send error message indicating fail + sync_socket.sendall(err_msg.encode("utf-8") + b"\0") + else: + # Send 1 byte with 0 value indicating success + sync_socket.send(bytes([0])) + + if target_socket is not None: + target_socket.close() + + +def tc1(target_socket, sync_socket): + err_msg = "" + + if target_socket is None: + err_msg = "Host error: connection failed" + end_tc(sync_socket, err_msg, target_socket) + return + + try: + send_data = bytes([random.randint(0, 128) for _ in range(128)]) + target_socket.sendall(send_data) + + recv_data = target_socket.recv(128, socket.MSG_WAITALL) + recv_len = len(recv_data) + if recv_len != 128: + err_msg = f"Host error: received just {recv_len} bytes out of 128" + end_tc(sync_socket, err_msg, target_socket) + return + + send_data = bytes([recv_data[i] - send_data[i] for i in range(128)]) + target_socket.sendall(send_data) + except socket.error as e: + err_msg = f"Host error1: {e}" + end_tc(sync_socket, err_msg, target_socket) + return + + end_tc(sync_socket, err_msg, target_socket) + + +def tc2(target_socket, sync_socket): + err_msg = "" + data_size = 24 * 1024 + + if target_socket is None: + err_msg = "Host error: connection failed" + end_tc(sync_socket, err_msg, target_socket) + return + + try: + send_data = bytes([random.randint(0, 128) for _ in range(data_size)]) + target_socket.sendall(send_data) + + timeout = struct.pack('ll', 5, 0) + target_socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, timeout) + + recv_data = target_socket.recv(data_size, socket.MSG_WAITALL) + recv_len = len(recv_data) + if recv_len != data_size: + err_msg = f"Host error: received just {recv_len} bytes out of {data_size}" + end_tc(sync_socket, err_msg, target_socket) + return + + send_data = bytes([recv_data[i] - send_data[i] for i in range(data_size)]) + target_socket.sendall(send_data) + except socket.error as e: + err_msg = f"Host error2: {e}" + end_tc(sync_socket, err_msg, target_socket) + return + + end_tc(sync_socket, err_msg, target_socket) + + +def tc3(target_socket, sync_socket): + # Just close host side of connection + err_msg = "" + end_tc(sync_socket, err_msg, target_socket) + + +def tc4(target_socket, sync_socket): + err_msg = "" + + if target_socket is None: + err_msg = "Host error: connection failed" + end_tc(sync_socket, err_msg, target_socket) + return + + try: + # Wait until target start listening + time.sleep(2) + sockets = [] + for _ in range(100): + con_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sockets.append(con_sock) + + for i in range(0, 100): + sockets[i].connect(('192.168.72.121', 2000)) + print(con_sock) + + for i in range(0, 100): + sockets[i].close() + + except socket.error as e: + err_msg = f"Host error3: {e}" + end_tc(sync_socket, err_msg, target_socket) + return + + print("essa") + end_tc(sync_socket, err_msg, target_socket) + + +def tc5(target_socket, sync_socket): + err_msg = "" + data_size = 24 * 1024 + + if target_socket is None: + err_msg = "Host error: connection failed" + end_tc(sync_socket, err_msg, target_socket) + return + + try: + send_data = bytes([random.randint(0, 128) for _ in range(data_size)]) + target_socket.sendall(send_data) + + target_socket.close() + + msg = "ready" + sync_socket.sendall(msg.encode("utf-8") + b"\0") + + except socket.error as e: + err_msg = f"Host error4: {e}" + end_tc(sync_socket, err_msg, target_socket) + return + + end_tc(sync_socket, err_msg, target_socket=None) + + +def tc6(target_socket, sync_socket, packet): + err_msg = "" + + if packet is None: + err_msg = "Host error: testcase setup failed" + end_tc(sync_socket, err_msg, target_socket) + return + + if target_socket is None: + err_msg = "Host error: connection failed" + end_tc(sync_socket, err_msg, target_socket) + return + + raw_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) + raw_socket.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) + + target_ip = target_socket.getpeername()[0] + ret = raw_socket.sendto(packet, (target_ip, 0)) + if ret <= 0: + err_msg = "Host error: Failed to send raw packet" + end_tc(sync_socket, err_msg, target_socket) + return + + try: + target_socket.sendall(bytes([1, 2, 3, 4])) + except socket.error as e: + err_msg = f"Host error5: {e}" + end_tc(sync_socket, err_msg, target_socket) + return + + end_tc(sync_socket, err_msg, target_socket) + + +def raw_tc_setup(doc_ip, host_ip, host_port, target_ip, **kwargs): + result = [] + sniff_thread = threading.Thread(target=sniff_packet, args=(target_ip, result)) + sniff_thread.start() + target_socket = con_setup(doc_ip, host_ip, host_port) + sniff_thread.join() + + if not result: + return None, None + + # Get values set by sniff_thread + seq = result[0] + ack = result[1] + window = result[2] + target_port = result[3] + + tcp_data = create_tcp_data(seq, ack, window, host_port, target_port, **kwargs) + packet = create_packet(host_ip, target_ip, tcp_data) + + return target_socket, packet + + +def raw_tc(target_socket, sync_socket, packet): + err_msg = "" + + if packet is None: + err_msg = "Host error: testcase setup failed" + end_tc(sync_socket, err_msg, target_socket) + return + + if target_socket is None: + err_msg = "Host error: connection failed" + end_tc(sync_socket, err_msg, target_socket) + return + + raw_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) + raw_socket.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) + + target_ip = target_socket.getpeername()[0] + ret = raw_socket.sendto(packet, (target_ip, 0)) + if ret <= 0: + err_msg = "Host error: Failed to send raw packet" + end_tc(sync_socket, err_msg, target_socket) + return + + ready_to_read, _, _ = select.select([target_socket], [], [], 5) + + if not ready_to_read: + err_msg = "Host error: Packet with error was accepted" + end_tc(sync_socket, err_msg, target_socket) + return + + end_tc(sync_socket, err_msg, target_socket) + + +def harness(dut: Dut, ctx: TestContext, result: TestResult): + doc_ip, host_ip, target_ip, netmask = get_config() + target_port = 1900 + if doc_ip is not None: + # Tests run using docker (packet redirection) + host_port = 1700 + else: + # Tests run locally + host_port = 1800 + + target_setup(dut, "en1", target_ip, netmask) + + dut.send(f"/bin/test-tcp {host_ip}:{host_port} {target_ip}:{target_port}\n") + dut.expect(f"/bin/test-tcp {host_ip}:{host_port} {target_ip}:{target_port}" + psh.EOL) + + # Start unity harness + target_harness = threading.Thread(target=unity_harness, args=(dut, ctx, result)) + target_harness.start() + + # Setup sync connection + sync_socket = con_setup(doc_ip, host_ip, host_port) + if sync_socket is None: + print("Host error: sync socket set up failed") + return 1 + + # Testcase + target_socket = con_setup(doc_ip, host_ip, host_port) + tc1(target_socket, sync_socket) + + # Testcase + target_socket = con_setup(doc_ip, host_ip, host_port) + tc2(target_socket, sync_socket) + + # Testcase + target_socket = con_setup(doc_ip, host_ip, host_port) + tc3(target_socket, sync_socket) + + # Testcase + # target_socket = con_setup(doc_ip, host_ip, host_port) + # tc4(target_socket, sync_socket) + + # Testcase + target_socket = con_setup(doc_ip, host_ip, host_port) + tc5(target_socket, sync_socket) + + # Testcase + target_socket, packet = raw_tc_setup(doc_ip, host_ip, host_port, target_ip, flags=4) + tc6(target_socket, sync_socket, packet) + + # Testcase + target_socket, packet = raw_tc_setup(doc_ip, host_ip, host_port, target_ip, bad_src_port=1) + raw_tc(target_socket, sync_socket, packet) + + # Testcase + target_socket, packet = raw_tc_setup(doc_ip, host_ip, host_port, target_ip, bad_dest_port=12345) + raw_tc(target_socket, sync_socket, packet) + + # Testcase + target_socket, packet = raw_tc_setup(doc_ip, host_ip, host_port, target_ip, bad_seq=12345) + raw_tc(target_socket, sync_socket, packet) + + # Testcase + target_socket, packet = raw_tc_setup(doc_ip, host_ip, host_port, target_ip, bad_checksum=12345) + raw_tc(target_socket, sync_socket, packet) + + # Testcase + # Packets with wrong ack are accepted ISSUE?? + # target_socket, packet = raw_tc_setup(doc_ip, host_ip, host_port, target_ip, bad_ack=1) + # raw_tc(target_socket, sync_socket, packet) + + sync_socket.close() + target_harness.join() diff --git a/network/test-tcp.c b/network/test-tcp.c new file mode 100644 index 00000000..bd32d81f --- /dev/null +++ b/network/test-tcp.c @@ -0,0 +1,402 @@ +/* + * Phoenix-RTOS + * + * test-tcp (target side part) + * + * network tests + * + * Copyright 2024 Phoenix Systems + * Author: Adam Debek + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + + +uint8_t send_data[128]; +uint8_t recv_data[128]; +uint8_t rand_data[128]; +char host_response[128]; +int host_response_flag; +int target_failed_flag; +char *host_ip; +char *target_ip; +uint16_t host_port, target_port; +int sockfd, syncfd; + + +TEST_GROUP(test_tcp); + + +TEST_SETUP(test_tcp) +{ + target_failed_flag = 0; + host_response_flag = 0; + memset(host_response, 1, sizeof host_response); + sockfd = create_con(host_ip, host_port); + if (sockfd < 0) { + TEST_FAIL_MESSAGE("Testcase connection creation failed"); + } +} + + +TEST_TEAR_DOWN(test_tcp) +{ + /* If response hadn't been got yet, test failed due to target fail */ + if (host_response_flag == 0) { + /* Close testcase socket first to make sure host will not get blocked */ + close(sockfd); + target_failed_flag = 1; + get_host_response(syncfd, host_response); + } +} + + +TEST(test_tcp, basic) +{ + uint8_t recv_buf[128]; + uint8_t expected[128]; + + recv_asserted(sockfd, recv_data, sizeof recv_data, MSG_WAITALL); + memcpy(recv_buf, recv_data, sizeof recv_buf); + + srand(time(NULL)); + for (unsigned int i = 0; i < sizeof(send_data); i++) { + rand_data[i] = rand() % 128; + send_data[i] = recv_data[i] + rand_data[i]; + } + + send_asserted(sockfd, send_data, sizeof send_data, MSG_NOSIGNAL); + + recv_asserted(sockfd, recv_data, sizeof recv_data, MSG_WAITALL); + + for (unsigned int i = 0; i < sizeof(recv_data); i++) { + expected[i] = send_data[i] - recv_data[i]; + } + + TEST_ASSERT_EQUAL_MEMORY(expected, recv_buf, sizeof rand_data); + + close(sockfd); + get_host_response(syncfd, host_response); +} + + +TEST(test_tcp, big_data) +{ + size_t data_size = 24 * 1024; + uint8_t *send_data_big = (uint8_t *)malloc(data_size); + uint8_t *recv_data_big = (uint8_t *)malloc(data_size); + uint8_t *rand_data_big = (uint8_t *)malloc(data_size); + uint8_t *recv_buf_big = (uint8_t *)malloc(data_size); + uint8_t *expected_big = (uint8_t *)malloc(data_size); + + if (send_data_big == NULL || recv_data_big == NULL || rand_data_big == NULL || + recv_buf_big == NULL || expected_big == NULL) { + TEST_FAIL_MESSAGE("Not enough memory"); + } + + recv_asserted(sockfd, recv_data_big, data_size, MSG_WAITALL); + memcpy(recv_buf_big, recv_data_big, data_size); + + srand(time(NULL)); + for (size_t i = 0; i < data_size; i++) { + rand_data_big[i] = rand() % 128; + send_data_big[i] = recv_data_big[i] + rand_data_big[i]; + } + + send_asserted(sockfd, send_data_big, data_size, MSG_NOSIGNAL); + + recv_asserted(sockfd, recv_data_big, data_size, MSG_WAITALL); + + for (size_t i = 0; i < data_size; i++) { + expected_big[i] = send_data_big[i] - recv_data_big[i]; + } + + TEST_ASSERT_EQUAL_MEMORY(expected_big, recv_buf_big, data_size); + + free(send_data_big); + free(recv_data_big); + free(rand_data_big); + free(recv_buf_big); + free(expected_big); + close(sockfd); + get_host_response(syncfd, host_response); +} + + +TEST(test_tcp, send_after_close) +{ + ssize_t n; + int ret; + + ret = close(sockfd); + TEST_ASSERT(ret == 0); + + n = send(sockfd, send_data, sizeof send_data, MSG_NOSIGNAL); + TEST_ASSERT(n < 0); + + get_host_response(syncfd, host_response); +} + + +TEST(test_tcp, recv_remaining_data) +{ + /* Wait until host close connection and read remaining data */ + ssize_t r; + size_t data_size = 24 * 1024; + uint8_t *recv_data_big = (uint8_t *)malloc(data_size); + char *msg = "ready"; + char errmsg[64]; + char msg_buf[6]; + + if (recv_data_big == NULL) { + TEST_FAIL_MESSAGE("Not enough memory"); + } + + r = recv(syncfd, msg_buf, sizeof msg_buf, 0); + /* Wait for sync sygnal from host indicating closure of testcase connection */ + TEST_ASSERT(r == sizeof(msg_buf)); + if (strcmp(msg_buf, msg) != 0) { + sprintf(errmsg, "Wrong message, got: %s", msg_buf); + TEST_FAIL_MESSAGE(errmsg); + } + + recv_asserted(sockfd, recv_data_big, data_size, MSG_WAITALL); + + free(recv_data_big); + close(sockfd); + get_host_response(syncfd, host_response); +} + + +TEST(test_tcp, multiple_connections) +{ +} + + +TEST(test_tcp, accept_connections) +{ + /* Issue with poll */ + int listenfd, ret; + int confd[100]; + struct pollfd fds[1]; + struct sockaddr_in addr; + socklen_t len = sizeof addr; + int reuse = 1; + unsigned int i = 0; + char errmsg[64]; + + listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + TEST_ASSERT(listenfd > 0); + + ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof reuse); + TEST_ASSERT(ret == 0); + + memset(&addr, 0, sizeof addr); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("192.168.72.121"); + addr.sin_port = htons(2000); + + ret = bind(listenfd, (struct sockaddr *)&addr, sizeof addr); + TEST_ASSERT(ret == 0); + + ret = listen(listenfd, 1000); + TEST_ASSERT(ret == 0); + + fds[0].fd = listenfd; + fds[0].events = POLLIN; + fds[0].revents = 0; + + for (i = 0; i < 100; i++) { + ret = poll(fds, 1, 3000); + + if (ret != 1 && !(fds[0].revents & POLLIN)) { + sprintf(errmsg, "polling %dth connection failed", i + 1); + TEST_FAIL_MESSAGE(errmsg); + } + + confd[i] = accept(listenfd, (struct sockaddr *)&addr, &len); + if (confd[i] < 0) { + sprintf(errmsg, "accepting %dth connection failed", i + 1); + TEST_FAIL_MESSAGE(errmsg); + } + } + + close(listenfd); + for (i = 0; i < 100; i++) { + close(confd[i]); + } + + // close(sockfd); + get_host_response(syncfd, host_response); +} + + +/* Function used in testcases which only check if wrong packets are dropped */ +void assert_packet_dropped(void) +{ + int ret; + struct pollfd fds[1]; + + fds[0].fd = sockfd; + fds[0].events = POLLIN; + fds[0].revents = 0; + ret = poll(fds, 1, 3000); + + /* Assert poll timed out */ + TEST_ASSERT(ret == 0); + TEST_ASSERT(fds[0].revents == 0); + + /* Send anything to check if session is not desynchronized */ + send_asserted(sockfd, send_data, sizeof send_data, MSG_NOSIGNAL); +} + + +TEST(test_tcp, receive_rst) +{ + /* Receive packet with RST flag on, after that packets should be dropped */ + int ret; + struct pollfd fds[1]; + ssize_t r; + uint8_t dummy_buf[16]; + + fds[0].fd = sockfd; + fds[0].events = POLLIN | POLLHUP; + fds[0].revents = 0; + ret = poll(fds, 1, 3000); + + /* Assert poll timed out */ + TEST_ASSERT(ret == 1); + /* Only POLLIN is received, I think POLLHUP should also */ + TEST_ASSERT(fds[0].revents == POLLIN); + + errno = 0; + r = recv(sockfd, dummy_buf, sizeof dummy_buf, 0); + printf("r: %zd\n", r); + printf("dummy_buf: %hhu\n", dummy_buf[0]); + printf("dummy_buf: %hhu\n", dummy_buf[1]); + printf("dummy_buf: %hhu\n", dummy_buf[2]); + printf("dummy_buf: %hhu\n", dummy_buf[3]); + + printf("errno: %d\n", errno); + TEST_ASSERT(r == -1); + TEST_ASSERT(errno == ECONNRESET); + + close(sockfd); + get_host_response(syncfd, host_response); +} + + +TEST(test_tcp, wrong_src_port) +{ + /* Host sends segment with source port, packet should be dropped */ + assert_packet_dropped(); + close(sockfd); + get_host_response(syncfd, host_response); +} + + +TEST(test_tcp, wrong_dest_port) +{ + /* Host sends segment with wrong destination port, packet should be dropped */ + assert_packet_dropped(); + close(sockfd); + get_host_response(syncfd, host_response); +} + + +TEST(test_tcp, wrong_seq) +{ + /* Host sends segment with wrong sequential number, packet should be dropped */ + assert_packet_dropped(); + close(sockfd); + get_host_response(syncfd, host_response); +} + + +TEST(test_tcp, wrong_ack) +{ + /* Packets with ack out of window are accepted ISSUE?? */ + assert_packet_dropped(); + close(sockfd); + get_host_response(syncfd, host_response); +} + + +TEST(test_tcp, wrong_chk_sum) +{ + /* Host sends segment with wrong checksum, packet should be dropped */ + assert_packet_dropped(); + close(sockfd); + get_host_response(syncfd, host_response); +} + + +TEST_GROUP_RUNNER(test_tcp) +{ + /* Wait until interface will be in running state */ + if (wait_if_running() < 0) { + fprintf(stderr, "Interface en1 is not running"); + exit(1); + } + /* Create sync socket */ + syncfd = create_con(host_ip, host_port); + if (syncfd < 0) { + fprintf(stderr, "Setting sync connection failed\n"); + exit(1); + } + + RUN_TEST_CASE(test_tcp, basic); + RUN_TEST_CASE(test_tcp, big_data); + // RUN_TEST_CASE(test_tcp, accept_connections); + RUN_TEST_CASE(test_tcp, send_after_close); + RUN_TEST_CASE(test_tcp, recv_remaining_data); + RUN_TEST_CASE(test_tcp, receive_rst); + RUN_TEST_CASE(test_tcp, wrong_src_port); + RUN_TEST_CASE(test_tcp, wrong_dest_port); + RUN_TEST_CASE(test_tcp, wrong_seq); + RUN_TEST_CASE(test_tcp, wrong_chk_sum); + // RUN_TEST_CASE(test_tcp, wrong_ack); + + /* Close sync socket */ + close(syncfd); +} + + +void runner(void) +{ + RUN_TEST_GROUP(test_tcp); +} + + +int main(int argc, char **argv) +{ + if (argc == 3 && argv[1] != NULL && argv[2] != NULL) { + host_ip = strtok(argv[1], ":"); + host_port = (uint16_t)atoi(strtok(NULL, "")); + target_ip = strtok(argv[2], ":"); + target_port = (uint16_t)atoi(strtok(NULL, "")); + } + else { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(EXIT_FAILURE); + } + + return (UnityMain(argc, (const char **)argv, runner) == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/network/test.yaml b/network/test.yaml new file mode 100644 index 00000000..f2cf7c4f --- /dev/null +++ b/network/test.yaml @@ -0,0 +1,9 @@ +test: + type: harness + harness: tcp-harness.py + targets: + value: [armv7a7-imx6ull-evk] + tests: + - name: tcp + + # TODO: stress tcp test