I am able to get the DUT to respond to the fabricated packet.
It appears the checksums were computed badly. I updated the check sum for the TCP computation, since I learned that the TCP needs to have a "pseudo IP" header to make the computation. It's explained here: Calculation of TCP Checksum
I also restructured the code to build it from the inside out (TCP-> IP-> Ethernet) and the DUT responds to the SYN.
I also disabled "Checksum offload" on the Linux PC to be sure, and to allow me to see and verify the checksums.
So the result: it puts me back to my first reported challenge trying to fabricate a test for RFC5961:
The problem is that after the ACK to the SYN, Linux is sending a RST on it's own. I learned that is because the socket has nothing listening or connected. I don't know how to get around that, since it's closing before my test even has a chance to issue a "revcfrom()"
For anyone who is interested, here is the updated code for "main.cpp". It's not meant to be robust or defensive, just to test fabricating a packet from the Ethernet level.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h> // struct ip and IP_MAXPACKET (which is 65535)
#include <netinet/in.h> // IPPROTO_RAW, IPPROTO_IP, IPPROTO_TCP, INET_ADDRSTRLEN
#define __FAVOR_BSD // Use BSD format of tcp header
#include <netinet/tcp.h> // struct tcphdr
#include <arpa/inet.h> // inet_pton() and inet_ntop()
#include <errno.h>
#include "Packet.h"
int BuildEthernetHdr(unsigned char **buffer, uint8_t *src_ip, uint8_t *dst_ip);
int BuildIPHdr(unsigned char **buffer, const char *src_ip, const char *dst_ip);
int BuildTCPHdr(unsigned char **buffer, const char *, const char *);
uint16_t checksum (uint8_t *addr, int len);
unsigned char buffer[2048];
int main() {
int tcplen, iplen, maclen, len;
unsigned char *eth, *tcp, *ip, *pkt;
CPacket packet;
uint8_t srcMac[6];
uint8_t dstMac[6];
tcplen = BuildTCPHdr(&tcp, "192.168.1.211","192.168.1.94");
iplen = BuildIPHdr(&ip, "192.168.1.211","192.168.1.94");
// Know your MAC addresses...
memcpy(srcMac, "\x60\xa4\x4c\x63\x4d\x9e", 6);
memcpy(dstMac, "\xa4\x9b\x13\x00\xfe\x0e", 6);
maclen = BuildEthernetHdr(ð,srcMac, dstMac);
packet.Initialize();
pkt = buffer;
memcpy(pkt,eth, maclen);
pkt += maclen;
memcpy(pkt,ip, iplen);
pkt += iplen;
memcpy(pkt,tcp, tcplen);
pkt += tcplen;
len = pkt - buffer;
packet.SendMessage(buffer, len);
free(tcp);
free(ip);
free(eth);
packet.Cleanup();
return EXIT_SUCCESS;
}
#define IP4_HDRLEN 20
#define TCP_HDRLEN 20
#define ETH_HDRLEN 14
int BuildEthernetHdr(uint8_t **buffer, uint8_t *src_mac, uint8_t *dst_mac) {
ETHERHDR * ethhdr;
ethhdr = (ETHERHDR * )malloc(sizeof(ETHERHDR));
memcpy(ethhdr->srcMac, src_mac,6);
memcpy(ethhdr->dstMac, dst_mac,6);
ethhdr->etherType = htons(0x0800);
*buffer = (uint8_t*) ethhdr;
return sizeof(ETHERHDR);
}
int BuildIPHdr(uint8_t **buffer, const char *src_ip, const char *dst_ip) {
struct ip *iphdr;
int status;
unsigned int ip_flags[4];
iphdr = (struct ip*) malloc(sizeof(struct ip));
memset(iphdr,0,sizeof(struct ip));
iphdr->ip_hl = IP4_HDRLEN / sizeof (uint32_t);
// Internet Protocol version (4 bits): IPv4
iphdr->ip_v = 4;
// Type of service (8 bits)
iphdr->ip_tos = 0;
// Total length of datagram (16 bits): IP header + TCP header
iphdr->ip_len = htons (IP4_HDRLEN + TCP_HDRLEN);
// ID sequence number (16 bits): unused, since single datagram
iphdr->ip_id = htons (0);
// Flags, and Fragmentation offset (3, 13 bits): 0 since single datagram
// Zero (1 bit)
ip_flags[0] = 0;
// Do not fragment flag (1 bit)
ip_flags[1] = 1;
// More fragments following flag (1 bit)
ip_flags[2] = 0;
// Fragmentation offset (13 bits)
ip_flags[3] = 0;
iphdr->ip_off = htons ((ip_flags[0] << 15)
+ (ip_flags[1] << 14)
+ (ip_flags[2] << 13)
+ ip_flags[3]);
// Time-to-Live (8 bits): default to maximum value
iphdr->ip_ttl = 64;
// Transport layer protocol (8 bits): 6 for TCP
iphdr->ip_p = IPPROTO_TCP;
// Source IPv4 address (32 bits)
if ((status = inet_pton (AF_INET, src_ip, &(iphdr->ip_src))) != 1) {
fprintf (stderr, "inet_pton() failed for source address.\nError message: %s", strerror (status));
exit (EXIT_FAILURE);
}
// Destination IPv4 address (32 bits)
if ((status = inet_pton (AF_INET, dst_ip, &(iphdr->ip_dst))) != 1) {
fprintf (stderr, "inet_pton() failed for destination address.\nError message: %s", strerror (status));
exit (EXIT_FAILURE);
}
// IPv4 header checksum (16 bits): set to 0 when calculating checksum
iphdr->ip_sum = 0;
iphdr->ip_sum = checksum ((uint8_t*) iphdr, IP4_HDRLEN);
printf("IP Chk %x\n", iphdr->ip_sum);
*buffer = (uint8_t *)iphdr;
return sizeof(struct ip);
}
typedef struct {
uint32_t srcIP[1];
uint32_t dstIP[1];
uint8_t res[1];
uint8_t proto[1];
uint16_t len[1];
} IP_PSEUDO;
uint8_t * PseudoHeader(uint8_t * packet, uint16_t len, uint32_t dst, uint32_t src) {
IP_PSEUDO * iphdr;
memmove(&packet[12], packet, len);
iphdr = (IP_PSEUDO*)packet;
iphdr->dstIP[0] = dst; // 5e = 94
iphdr->srcIP[0] = src; // d3 = 211
iphdr->res[0] = 0;
iphdr->proto[0] = 6;
iphdr->len[0] = htons(len);
return &packet[20];
}
int BuildTCPHdr(uint8_t **buffer, const char * src, const char *dest) {
struct tcphdr *tcphdr;
int optsize = 0;
unsigned int tcp_flags[8];
unsigned char optbuffer[20];
tcphdr = (struct tcphdr *) malloc(sizeof(struct tcphdr));
memset(tcphdr,0,sizeof(struct tcphdr));
if (false) {
// Option length (with itself) value
optbuffer[0] = 2; optbuffer[1] = 4; optbuffer[2] = 5; optbuffer [3] = 0xb4; //Max Seg Size
optbuffer[4] = 4; optbuffer[5] = 2; // SACK permitted
uint32_t time1 = 0x12345678; uint32_t time2 = 0x87654321;
optbuffer[6] = 8; optbuffer[7] = 10; memcpy(&optbuffer[8], &time1, 4); memcpy(&optbuffer[12], &time2, 4);
optbuffer[16] = 1; // NoOp
optbuffer[17] = 3; optbuffer[18] = 3; optbuffer[19] = 7; // Shift Multiplier
optsize = 20;
}
// Source port number (16 bits)
tcphdr->th_sport = htons (32500);
// Destination port number (16 bits)
tcphdr->th_dport = htons (80);
// Sequence number (32 bits)
tcphdr->th_seq = htonl (5);
// Acknowledgement number (32 bits): 0 in first packet of SYN/ACK process
tcphdr->th_ack = htonl (0);
// Reserved (4 bits): should be 0
tcphdr->th_x2 = 0;
// Data offset (4 bits): size of TCP header in 32-bit words
tcphdr->th_off = (TCP_HDRLEN + optsize) / 4;
// Flags (8 bits)
// FIN flag (1 bit)
tcp_flags[0] = 0;
// SYN flag (1 bit): set to 1
tcp_flags[1] = 1;
// RST flag (1 bit)
tcp_flags[2] = 0;
// PSH flag (1 bit)
tcp_flags[3] = 0;
// ACK flag (1 bit)
tcp_flags[4] = 0;
// URG flag (1 bit)
tcp_flags[5] = 0;
// ECE flag (1 bit)
tcp_flags[6] = 0;
// CWR flag (1 bit)
tcp_flags[7] = 0;
tcphdr->th_flags = 0;
for (int i=0; i<8; i++) {
tcphdr->th_flags += (tcp_flags[i] << i);
}
// Window size (16 bits)
tcphdr->th_win = htons (8192);
// Urgent pointer (16 bits): 0 (only valid if URG flag is set)
tcphdr->th_urp = htons (0);
// TCP checksum (16 bits)
uint8_t temp[64];
memset(temp,0,64);
uint32_t ip_src, ip_dest;
inet_pton (AF_INET, src, &ip_src);
inet_pton (AF_INET, dest, &ip_dest);
memcpy(temp,tcphdr,sizeof(struct tcphdr));
PseudoHeader(temp,20, ip_src,ip_dest);
tcphdr->th_sum = checksum(temp, sizeof(struct tcphdr) + 12);
printf("TCP Chk %x\n", tcphdr->th_sum);
*buffer = (uint8_t*) tcphdr;
return sizeof(struct tcphdr);
}
// Computing the internet checksum (RFC 1071).
// Note that the internet checksum is not guaranteed to preclude collisions.
uint16_t checksum(uint8_t *addr, int len) {
int count = len;
register uint32_t sum = 0;
uint16_t answer = 0;
// Sum up 2-byte values until none or only one byte left.
while (count > 1) {
printf(" Adding %04x \n", *((unsigned short *)(addr)));
sum += htons(*((unsigned short *)(addr)));
addr += 2;
count -= 2;
}
// Add left-over byte, if any.
if (count > 0) {
sum += *(uint8_t *)addr;
}
// Fold 32-bit sum into 16 bits; we lose information by doing this,
// increasing the chances of a collision.
// sum = (lower 16 bits) + (upper 16 bits shifted right 16 bits)
sum = htonl(sum);
while (sum >> 16) {
sum = (sum & 0xffff) + (sum >> 16);
}
// Checksum is one's compliment of sum.
answer = ~sum;
return (answer);
}