csocketstcp

Handcrafted TCP SYN packet yields no response


I have this code where I'm manually crafting TCP SYN packets in C by setting all IP and TCP header fields myself, including the TCP flags and checksum. When I send these SYN packets to an open port on a remote host (or even localhost), I expect to receive a SYN-ACK response. However, I don't receive any response at all; all I see in my wireshark live capture is the handcrafted SYN packet I just sent out.

I have no idea what the problem is, but my best guess could be the checksum function? If the calculated checksum is wrong, then it would be standard behavior to drop the packet. That said, I've reviewed the checksum function a LOT of times, but I still can't see whats wrong.

Here's the code (I've replaced irrelevant parts with ..., and removed error checking for brevity):

struct pseudo_header {
  u_int32_t src_addr;
  u_int32_t dst_addr;
  u_int8_t placeholder;
  u_int8_t protocol;
  u_int16_t tcp_length;
};

unsigned short checksum(const char *buf, int size) {
  uint32_t sum = 0;
  uint16_t word16;
  int i = 0;

  for (i = 0; i < size - 1; i += 2) {
    word16 = ((uint16_t)buf[i] << 8) | buf[i + 1];
    sum += word16;
  }

  if (size & 1) {
    word16 = (uint16_t)buf[i] << 8;
    sum += word16;
  }

  while (sum >> 16) {
    sum = (sum & 0xFFFF) + (sum >> 16);
  }

  return htons((uint16_t)~sum);
}

int main(int argc, char *argv[]) {
  ...

      int syn = atoi(argv[1]);
  int ack = atoi(argv[2]);
  int rst = atoi(argv[3]);
  int fin = atoi(argv[4]);
  int push = atoi(argv[5]);
  int urg = atoi(argv[6]);

  const char *source_ip = "127.0.0.1";
  unsigned short source_port = 30000 + (rand() % (65536 - 30000));
  const char *dest_ip = "127.0.0.1";
  unsigned short dest_port = 1234;

  char packet[4096];
  struct iphdr *iph = (struct iphdr *)packet;
  struct tcphdr *tcph = (struct tcphdr *)(packet + sizeof(struct iphdr));
  struct sockaddr_in dest_addr;

  memset(packet, 0, 4096);

  char *data = packet + sizeof(struct iphdr) + sizeof(struct tcphdr);
  strcpy(data, "hiii\n");
  int data_len = strlen(data);

  memset(&dest_addr, 0, sizeof(struct sockaddr_in));
  dest_addr.sin_family = AF_INET;
  dest_addr.sin_port = htons(dest_port);

  inet_pton(AF_INET, dest_ip, &dest_addr.sin_addr)

      iph->version = 4;
  iph->ihl = 5;
  iph->tos = 0;
  iph->tot_len = htons(sizeof(struct iphdr) + sizeof(struct tcphdr) + data_len);
  iph->id = htons(12345);
  iph->frag_off = 0;
  iph->ttl = 64;
  iph->protocol = IPPROTO_TCP;
  iph->check = 0;
  if (inet_pton(AF_INET, source_ip, &iph->saddr) <= 0) {
    perror("inet_pton() source_ip");
    close(sock);
    exit(1);
  }
  iph->daddr = dest_addr.sin_addr.s_addr;

  tcph->source = htons(source_port);
  tcph->dest = htons(dest_port);
  tcph->seq = htonl(rand() % 10000);
  tcph->ack_seq = 1;
  tcph->doff = 5;

  tcph->fin = fin;
  tcph->syn = syn;
  tcph->rst = rst;
  tcph->psh = push;
  tcph->ack = ack;
  tcph->urg = urg;
  tcph->window = htons(5840);
  tcph->check = 0;
  tcph->urg_ptr = 0;

  struct pseudo_header psh;
  psh.src_addr = iph->saddr;
  psh.dst_addr = iph->daddr;
  psh.placeholder = 0;
  psh.protocol = IPPROTO_TCP;
  psh.tcp_length = htons(sizeof(struct tcphdr) + data_len);

  size_t checksum_buf_size =
      sizeof(struct pseudo_header) + sizeof(struct tcphdr) + data_len;
  char checksum_buf[checksum_buf_size];

  memset(checksum_buf, 0, checksum_buf_size);
  memcpy(checksum_buf, (char *)&psh, sizeof(struct pseudo_header));
  memcpy(checksum_buf + sizeof(struct pseudo_header), (char *)tcph,
         sizeof(struct tcphdr));
  memcpy(checksum_buf + sizeof(struct pseudo_header) + sizeof(struct tcphdr),
         data, data_len);

  tcph->check = checksum(checksum_buf, checksum_buf_size);

  iph->check = checksum(packet, iph->ihl * 4);

  ...
}

Solution

  • There may be other issues as well, but your checksum computation has a flaw that might be responsible for your system ignoring your hand-crafted SYN packets. Specifically, on a system where char is a signed type, this will not reliably do what you intend:

        word16 = ((uint16_t)buf[i] << 8) | buf[i + 1];
    

    The operands of | are subject to the usual arithmetic conversions, which in this case will result in both ((uint16_t)buf[i] << 8) and buf[i + 1] being converted to type int, in value-preserving manner, for the computation. That's ok for the former operand, but when the value of buf[i + 1] is negative, it will involve sign extension for that operand, with the ultimate effect that the top 8 bits of the resulting word16 value are all forced to 1, regardless of the value of buf[i].

    There are several ways you could solve that. The best and most natural one, IMO, would be simply to declare buf as an array of unsigned char instead of char, since that's in fact how you intend to interpret it.