使用 nat 后面从服务器到客户端的数据获取 ECHO REPLY

Posted

技术标签:

【中文标题】使用 nat 后面从服务器到客户端的数据获取 ECHO REPLY【英文标题】:get ECHO REPLY with data from server to client behind nat 【发布时间】:2016-10-13 00:02:49 【问题描述】:

我必须编写 C 程序,将 ICMP ECHO REQUEST 从电话(它由 Mobile isp 连接,它在 NAT 后面)发送到 IP PUBLIC 的服务器。我编写了一个简单的程序来发送回显请求并接收回显回复,但现在我想将 ECHO REQUEST 从客户端发送到服务器,并从服务器到客户端接收带有一些数据(IP PUBLIC 和 ICMP ID)的 ECHO REPLY。我该怎么做呢?

这是我的代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

typedef unsigned char u8;
typedef unsigned short int u16;

struct icmp_header
   unsigned char type;
   unsigned char code;
   unsigned short checksum;
   unsigned short id;
   unsigned short seq;
;

unsigned short in_cksum(unsigned short *ptr, int nbytes);    

int main(int argc, char **argv)
int c=100;
int ls;//lunghezza struct sockaddr_in serveraddr
int rf;//receive from    

unsigned long daddr;
unsigned long saddr;
int payload_size = 0, sent = 0, sent_size;

saddr = inet_addr("IP PRIVATE");
daddr = inet_addr("IP PUBLIC");

//Raw socket - if you use IPPROTO_ICMP, then kernel will fill in the correct ICMP header checksum, if IPPROTO_RAW, then it wont
int sockfd = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);

if (sockfd < 0)
    perror("could not create socket");
    return (0);


int on = 1;

// We shall provide IP headers
if (setsockopt (sockfd, IPPROTO_IP, IP_HDRINCL, (const char*)&on, sizeof (on)) == -1)
    perror("setsockopt");
    return (0);


//allow socket to send datagrams to broadcast addresses
if (setsockopt (sockfd, SOL_SOCKET, SO_BROADCAST, (const char*)&on, sizeof (on)) == -1)
    perror("setsockopt");
    return (0);
   

//Calculate total packet size
int packet_size = sizeof (struct iphdr) + sizeof (struct icmp_header) + payload_size;

char *buffer = (char *) malloc (packet_size);   
char *packet = (char *) malloc (packet_size);    

if (!packet)
    perror("out of memory");
    close(sockfd);
    return (0);


//ip header
struct iphdr *ip = (struct iphdr *) packet;
//struct icmphdr *icmp = (struct icmphdr *) (packet + sizeof (struct iphdr));
struct icmp_header *icmphdr = (struct icmp_header *) (packet + sizeof(struct iphdr)); 
//zero out the packet buffer
memset (packet, 0, packet_size);
memset (buffer, 0, packet_size);

ip->version = 4;
ip->ihl = 5;
ip->tos = 0;
ip->tot_len = htons (packet_size);
ip->id = rand ();
ip->frag_off = 0;
ip->ttl = 255;
ip->protocol = IPPROTO_ICMP;
ip->saddr = saddr;
ip->daddr = daddr;
//ip->check = in_cksum ((u16 *) ip, sizeof (struct iphdr));

//icmp->type = ICMP_ECHO significa ECHO REQUEST
//icmp->code = 0 è il codice dei ECHO REQUEST
icmphdr->type = ICMP_ECHO;
icmphdr->code = 0;
icmphdr->id = 5;
icmphdr->seq = 66;
//checksum
icmphdr->checksum = 0;

struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = daddr;
memset(&servaddr.sin_zero, 0, sizeof (servaddr.sin_zero));

puts("flooding...");

//while (1)
while(c>0)
    memset(packet + sizeof(struct iphdr) + sizeof(struct icmp_header), rand() % 255, payload_size);
    //memset(buffer + sizeof(struct iphdr) + sizeof(struct icmphdr), rand() % 255, payload_size);

    //recalculate the icmp header checksum since we are filling the payload with random characters everytime
    icmphdr->checksum = 0;
    icmphdr->checksum = in_cksum((unsigned short *)icmphdr, sizeof(struct icmp_header) + payload_size);

    if ( (sent_size = sendto(sockfd, packet, packet_size, 0, (struct sockaddr*) &servaddr, sizeof (servaddr))) < 1)
        perror("send failed\n");
        break;
    
    ++sent;
    printf("%d packets sent\r", sent);
    fflush(stdout);

    ls = sizeof(servaddr);
    //rf = recvfrom(sockfd, buffer, 42, 0, (struct sockaddr *)&servaddr, &ls);
rf = recvfrom(sockfd, buffer, packet_size, 0, (struct sockaddr *)&servaddr, &ls);
    if(rf < 0)
       perror("Errore recvfrom\n");
       break;
    
    else
       char *cp;
       struct iphdr *ip_reply = (struct iphdr *)buffer;
       cp = (char *)&ip_reply->saddr;
   printf("Received %d byte reply from %u.%u.%u.%u:\n", ntohs(ip_reply->tot_len), cp[0]&0xff,cp[1]&0xff,cp[2]&0xff,cp[3]&0xff);
       printf("ID: %d\n", ntohs(ip_reply->id));
       printf("TTL: %d\n", ip_reply->ttl);
             
    usleep(10000);  //microseconds
    c--;
    
free (buffer); 
free(packet);
close(sockfd);     
return (0);


/*
Function calculate checksum
*/
unsigned short in_cksum(unsigned short *ptr, int nbytes)
register long sum;
u_short oddbyte;
register u_short answer;

sum = 0;
while (nbytes > 1) 
    sum += *ptr++;
    nbytes -= 2;


if (nbytes == 1) 
    oddbyte = 0;
    *((u_char *) & oddbyte) = *(u_char *) ptr;
    sum += oddbyte;


sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;

return (answer);

【问题讨论】:

我不确定我是否理解您的问题?您共享的代码是否会产生某种错误? 我的代码很好,没有错误。在这段代码中,我将 ECHO REQUEST 发送到服务器并返回 ECHO REPLY,没问题。我想知道如何将 ECHO REQUEST 发送到服务器并取回 ECHO REPLY(来自服务器),其中包含 IP PUBLIC ADDRESS 和两个数字等数据 您应该首先在服务器上使用 sysctl 禁用 ping 响应。那么这是一个简单的任务。格式化你的代码并提供服务端的代码。 【参考方案1】:

回答。 好的,我解决问题。我使用unsigned char 数组作为有效负载创建自定义icmp 标头(在此示例中支付)。我在服务器端使用此数组以这种方式存储数据memcpy(pay, (unsigned char *)&amp;icmp-&gt;id, 2); //here i store incoming icmp echo request id

然后我准备发送缓冲区

struct eth_frame *eths = (struct eth_frame *) buffer;
crea_eth(eths,0x0800,mactarget);//create eth frame
struct ip_datagram *ips = (struct ip_datagram*) eths->payload;
struct icmp_packet *icmps = (struct icmp_packet*) ips->payload;

然后我构建自定义 icmp echo 回复和自定义 ip 数据包,我必须从服务器发送到客户端

memcpy(icmps->payload, &pay, 10);
icmps->type = 0;
icmps->code = 0;
icmps->checksum = 0;
icmps->id = (icmp->id);//i use same receiving icmp id
icmps->seq = htons(1);                          
icmps->checksum = //calculate checksum

ips->ver_ihl = 0x45;
ips->tos = 0x00;
ips->totlen = htons(20 + 8 + 8);
ips->id = 0xABCD;
ips->flags_offs = 0;
ips->ttl = 255;
ips->proto = IPPROTO_ICMP;
ips->checksum = 0;
ips->src = *(unsigned int *)serverip;
ips->dst = *(unsigned int *)clientip;//fill with IP PUBLIC ADDRESS OF CLIENT!!
ips->checksum = htons(calcola_checksum((unsigned char*)ips,20));

然后将icmp数据包从服务器发送到客户端

unsigned char *p = (unsigned char *)&addr;
for(i = 0;i < sizeof(struct sockaddr_ll);i++)
   p[i] = 0;
addr.sll_family = AF_PACKET;
addr.sll_ifindex = 3;
/*send to*/
n=sendto(s, buffer, 14 + 20 + 8 + 8 , 0 , (struct sockaddr*) &addr , sizeof(addr));

【讨论】:

以上是关于使用 nat 后面从服务器到客户端的数据获取 ECHO REPLY的主要内容,如果未能解决你的问题,请参考以下文章

如何从负载均衡器后面获取客户端的 IP 地址?

NAT 后面到 NAT 连接后面

如何在没有 NAT 的情况下连接路由器或防火墙后面的客户端 tcp 端口

LVS-NAT模式实验

UDP、NAT 和设置“连接”

UDP打孔:无法从服务器发送到客户端