简体   繁体   中英

Why does not the server respond with syn-ack packets when I send syn-packets with raw sockets?

I am experimenting with raw sockets and I have just written a small program that sends TCP packets with the syn flag set. I can see the packets coming with Wireshark on the server side and they look good, but the server never responds with any syn-ack packets.

I have compared the syn packets that my program constructs (see code below) with the ones that hping3 sends (because the packets of hping3 always get a syn-ack ). The only that differs between my syn packets and hping3's syn packets are the ip identification number, tcp source port (which is randomized in hping3), tcp sequence number (which is also randomized in hping3) and the ip checksum field. All these four fields are based on some random numbers, that is why they differ. All other fields are equal! But my program does not get any syn-acks but hping3 does!

I am using Kali Linux for sending the packets (of course as root) and CentOS for the server.

Have I missed something essential or just missunderstood anything?

Removed code

EDIT
Here is the entire packet captured by Wireshark on the client side (divided into 4 images below). Note that the packets sent by hping3 are totally equal except the values for ip identification, source port, sequence number and checksum:

Images removed


Here is the hex dump of the packet.

Hexdump removed

EDIT 2

Ok, now I have created the pseudo header according to RFC793 . The pseudo header is just used for the tcp checksum calculation. Now the IP header seems to be correct, but Wireshark complains about that the packet does not contain a full TCP header and it really seems corrupted because some of the fields contains strange values that I have not set.

First I allocate a buffer (called tcp_header ) with space for the tcp header and the pseudo header. Second, I create a buffer for the ip header containing space for ip, tcp and pseudo headers.
First I fill the tcp_header with its data and then I copy it to the ip_header before sending it with the sendto function.

Does something go wrong when I copy the contents of tcp_packet to ip_packet or am I doing something else wrong?

#include <cstdlib>
#include <stdio.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#define __FAVOR_BSD 1
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <sys/ioctl.h>
#include <string.h>
#include <iostream>
#include <net/ethernet.h>
#include <time.h>

#define PCKT_LEN 1024

struct pseudohdr
{
    __u32 saddr;
    __u32 daddr;
    __u8  zero;
    __u8  protocol;
    __u16 lenght;
};

#define PSEUDOHDR_SIZE sizeof(struct pseudohdr)

unsigned short csum(unsigned short *buf, int len) {
    unsigned long sum;
    for(sum=0; len>0; len-=2)
        sum += *buf++;
    sum = (sum >> 16) + (sum &0xffff);
    sum += (sum >> 16);
    return (unsigned short)(~sum);
}


int main(int argc, char** argv) {

    srand(time(NULL));

    char *ip_packet = new char[sizeof(struct iphdr) + 
                               sizeof(struct tcphdr)]();

    char *tcp_packet = new char[sizeof(struct pseudohdr) + 
                                sizeof(struct tcphdr)]();

    struct pseudohdr *pseudoheader = (struct pseudohdr*) tcp_packet;
    class tcphdr *tcp = (struct tcphdr *) (tcp_packet + sizeof(struct pseudohdr));
    class iphdr *ip = (struct iphdr *) ip_packet;


    class sockaddr_in sin, din;

    int sd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if(sd < 0) {
       perror("socket() error");
       exit(-1);
    } else {
        printf("socket()-SOCK_RAW and tcp protocol is OK.\n");
    }

    // Randomize src port
    int srcport = rand()%100+25000;

    sin.sin_family = AF_INET;           // Address family
    sin.sin_addr.s_addr = inet_addr("192.168.2.80");
    sin.sin_port = htons(srcport); // Source port

    din.sin_family = AF_INET;
    din.sin_addr.s_addr = inet_addr("192.168.2.6");
    din.sin_port = htons(80); // Destination port

    /* tcp pseudo header */
    memcpy(&pseudoheader->saddr, &sin.sin_addr.s_addr, 4);
    memcpy(&pseudoheader->daddr, &din.sin_addr.s_addr, 4);
    pseudoheader->protocol      = 6; /* tcp */
    pseudoheader->lenght        = htons(sizeof(struct pseudohdr) + sizeof(struct tcphdr));


    ip->ihl = 5;
    ip->version = 4;
    ip->tos = 0;
    ip->tot_len = sizeof(class iphdr) + sizeof(class tcphdr);
    ip->id = htons((getpid() & 255) + rand()%100+30000);
    ip->frag_off = 0;
    ip->ttl = 32;
    ip->protocol = 6; // TCP
    ip->check = 0; // Done by kernel
    memcpy(&ip->saddr, (char*)&sin.sin_addr, sizeof(ip->saddr));
    memcpy(&ip->daddr, (char*)&din.sin_addr, sizeof(ip->daddr));


    // The TCP structure
    tcp->th_sport = htons(srcport);
    tcp->th_dport = htons(80);      // Destination port
    tcp->th_seq = htonl(rand()%100+1000);
    tcp->th_ack = htonl(rand()%30);
    tcp->th_off = 5;
    tcp->th_flags = TH_SYN;
    tcp->th_win = htons(1024);
    tcp->th_urp = 0;

    // Now calculate tcp checksum
    tcp->th_sum = csum((unsigned short *) tcp_packet, sizeof(struct pseudohdr) + sizeof(struct tcphdr));

    // Copy tcp_packet to ip_packet
    memcpy(ip_packet + sizeof(struct iphdr), tcp_packet+sizeof(struct pseudohdr), sizeof(struct tcphdr));

    // Bind socket to interface
    int one = 1;
    const int *val = &one;
    const char opt[] = "eth0";

    if(setsockopt(sd, IPPROTO_IP, IP_HDRINCL, (char *)&one, sizeof(one)) < 0) {
        perror("setsockopt() error");
        exit(-1);
    }
    else
        printf("setsockopt() is OK\n");

    if(sendto(sd, ip_packet, ip->tot_len, 0, (sockaddr*)&din, sizeof(din)) < 0) {
       perror("sendto() error");
       exit(-1);
    }
    else
        printf("Send OK!");

    close(sd);
    return 0;
}

The tcp contents of the packet:

Images removed

Edit 3

Now I have found something interesting. Study the cheksums on this picture: 在此输入图像描述

The checksum is in network order and shall thus be read in reversed order, as 0x06c0 (and not is as it is stated above as 0xc006). That is equal to the decimal value of 1728 . Wireshark says the correct cheksum should be 0x12c0 which gives a decimal value of 4800 .
4800-1728=3072 . That is the difference between the actual checksum and the correct checksum calculated by Wireshark in all packets that is sent by my program.

So, if I simply add that value to the cheksum result:

tcp->th_sum = csum((unsigned short *) tcp_packet, sizeof(struct pseudohdr) + sizeof(struct tcphdr)) + 3072;

...then all packets get the correct checksum and receives a corresponding SYN-ACK .

Why the magic number 3072???

I am not content with the check sum algorithm you are using. The one suggested by Stevens:

uint16_t
in_cksum(uint16_t *addr, int len)
{
        int                     nleft = len;
        uint32_t                sum = 0;
        uint16_t                *w = addr;
        uint16_t                answer = 0;

        /*
         * Our algorithm is simple, using a 32 bit accumulator (sum), we add
         * sequential 16 bit words to it, and at the end, fold back all the
         * carry bits from the top 16 bits into the lower 16 bits.
         */
        while (nleft > 1)  {
                sum += *w++;
                nleft -= 2;
        }

                /* 4mop up an odd byte, if necessary */
        if (nleft == 1) {
                *(unsigned char *)(&answer) = *(unsigned char *)w ;
                sum += answer;
        }

                /* 4add back carry outs from top 16 bits to low 16 bits */
        sum = (sum >> 16) + (sum & 0xffff);     /* add hi 16 to low 16 */
        sum += (sum >> 16);                     /* add carry */
        answer = ~sum;                          /* truncate to 16 bits */
        return(answer);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM