网上关于使用RAW SOCKET构造原始帧的资料很少,在外国的一个网站上找到了一份源码,经过改造后可以再局域网内广播UDP了。 在此之前遇到了很多问题,在此总结:
(1)使用wireshark可以抓取到UDP数据帧,但应用层的QT程序就是不能读到这个UDP中的数据。经测试,这可能是因为UDP段的校验和错误,wireshark并不会报错,但该UDP无法被应用层的程序接收(经过本人测试)。
(2)以为identification(ID)的值对于发送只有一个数据帧的UDP会产生影响。经测试,如果UDP只有一个数据帧,则identification可以取任意值,不会影响UDP消息的正确接收。
(3)构造UDP原始帧的Linux C程序和接收UDP消息的QT程序若放在同一台计算机下测试,则无法接收到广播的UDP消息;若将两个程序分别放在两台不同的计算机下运行,则UDP广播消息可被正确地接收。(本人分析的原因:本机上发送的UDP广播可能不会广播给自身,从而导致了在同一台计算机上不能正确接收)
(4)为何wireshark抓到的UDP原始帧中最后几位为"00",wireshark显示其属于trailer。在以太网中,规定最小的数据包为64字节(和碰撞检测有关),如果数据包不足64字节,则会由网卡填充,所以显示到后面几位就是"00"。
(5)以太网帧的最小长度到底是多少?不是64字节么?为什么看到了42字节的arp包? 是64字节,你用wireshark抓到的包是把最后4个字节的FCS丢掉的结果,在没有达到64字节时,网卡驱动会自动填充到64字节。楼主看到的42字节,可能是wireshark做了处理,去掉了填充部分。
在这个过程中其实遇到了很多问题,就不一一列举了,再好的文字都不如代码来的实在,下面是一些源码:
【1】接收UDP消息的QT程序
- /**
- *这个程序功能很简单:监听6666端口的UDP消息,如果接收到消息则显示。
- */
- #include "mainwindow.h"
- #include "ui_mainwindow.h"
- MainWindow::MainWindow(QWidget *parent) :
- QMainWindow(parent),
- ui(new Ui::MainWindow)
- {
- ui->setupUi(this);
- socket=new QUdpSocket(this);
- connect(ui->startButton,SIGNAL(clicked()),this,SLOT(startDo()));
- connect(ui->clearButton,SIGNAL(clicked()),ui->historyTextEdit,SLOT(clear()));
- connect(ui->quitButton,SIGNAL(clicked()),this,SLOT(quitDo()));
- }
- MainWindow::~MainWindow()
- {
- delete ui;
- }
- bool MainWindow::validatePort(QString port)
- {
- bool ok=false;
- int tmp_port=port.toInt(&ok,10);
- if(ok){
- if((tmp_port>1024)&&(tmp_port<65536)){
- return true;
- }else{
- return false;
- }
- }else{
- return false;
- }
- }
- //初始化程序UDP相关的部分
- void MainWindow::startDo()
- {
- QString port=ui->portEdit->text();
- bool bo=validatePort(port);
- if(bo && (port != "")){
- bool ok=socket->bind((quint16)port.toInt());
- if(ok){
- ui->historyTextEdit->append("bind indicated port!");
- }else{
- ui->historyTextEdit->append("bind error!");
- }
- }else{
- bool ok=socket->bind(DEFAULT_PORT);
- if(ok){
- ui->historyTextEdit->append("bind default port!");
- }else{
- ui->historyTextEdit->append("bind error!");
- }
- }
- connect(socket,SIGNAL(readyRead()),this,SLOT(readData()));
- ui->startButton->setEnabled(false);
- }
- //读取UDP数据报中的信息
- void MainWindow::readData()
- {
- while(socket->hasPendingDatagrams()){
- QByteArray arr;
- arr.resize(socket->pendingDatagramSize());
- socket->readDatagram(arr.data(),arr.size());
- ui->historyTextEdit->append(arr);
- }
- }
- void MainWindow::quitDo()
- {
- exit(0);
- }
通过了测试的Linux C UDP原始帧构造程序有两个,便于参考。
【2】Linux C UDP构造参考程序一
- /**
- *功能:构造UDP 原始帧,并使用Linux原始套接字发送到局域网中。
- *UDP数据帧中包含了一个测试用的字符串“123456789”
- **/
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <netinet/if_ether.h>
- #include <netinet/ip.h>
- #include <linux/if_packet.h>
- #include <linux/if.h>
- #include <linux/sockios.h>
- #include <stdio.h>
- #include <unistd.h>
- int main(void)
- {
- int fd=0;
- char buf[256]={0};
- struct sockaddr_ll dest;
- int destlen=0;
- int ret=0;
- struct ifreq ifstruct;
- fd=socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));
- if(fd<0){
- printf("ERR:socket was failed.\n");
- return -1;
- }
- memset((char *)&dest, 0x00,sizeof(dest));
- destlen=sizeof(dest);
- strcpy(ifstruct.ifr_name, "eth0");
- ioctl(fd, SIOCGIFINDEX, &ifstruct);
- dest.sll_ifindex=ifstruct.ifr_ifindex;
- dest.sll_family=AF_PACKET;
- unsigned char my_mac[]={0x08,0x00,0x27,0x93,0x68,0xe5};
- memcpy(dest.sll_addr,my_mac,6);
- dest.sll_halen=ETH_ALEN;
- memset(buf,0x00,sizeof(buf));
- int j=0;
- buf[0]=0xff;
- buf[1]=0xff;
- buf[2]=0xff;
- buf[3]=0xff;
- buf[4]=0xff;
- buf[5]=0xff;
- buf[6]=0x00;
- buf[7]=0x11;
- buf[8]=0x22;
- buf[9]=0x33;
- buf[10]=0x00;
- buf[11]=0x00;
- buf[12]=0x08;
- buf[13]=0x00; //protocal field
- buf[14]=0x45;
- buf[15]=0x00;
- buf[16]=0x00;
- buf[17]=0x25;
- buf[18]=0x00;
- buf[19]=0x00;
- buf[20]=0x40;
- buf[21]=0x00;
- buf[22]=0x40;
- buf[23]=0x11;
- buf[24]=0x76;
- buf[25]=0x4f;
- buf[26]=0xc0;
- buf[27]=0xa8;
- buf[28]=0x03;
- buf[29]=0xd1;
- buf[30]=0xff;
- buf[31]=0xff;
- buf[32]=0xff;
- buf[33]=0xff;
- buf[34]=0x9e;
- buf[35]=0xae;
- buf[36]=0x1a;
- buf[37]=0x0a;
- buf[38]=0x00;
- buf[39]=0x11;
- buf[40]=0x78;
- buf[41]=0xc5;
- buf[42]=0x31;
- buf[43]=0x32;
- buf[44]=0x33;
- buf[45]=0x34;
- buf[46]=0x35;
- buf[47]=0x36;
- buf[48]=0x37;
- buf[49]=0x38;
- buf[50]=0x39;
- int ii=0;
- while(1){
- ret=sendto(fd,buf,51,0,(struct sockaddr *)&dest,destlen); //将构造的UDP帧发送到数据链路层
- if(ret<0){
- perror("error");
- }
- printf("%d\n",ii);
- ii++;
- sleep(1);
- }
- close(fd);
- printf("finished!\n");
- return 0;
- }
运行截图:
UDP发送端
UDP接收端
【3】Linux C UDP构造参考程序二
- /* Copyright (C) 2011-2013 P.D. Buchan (pdbuchan@yahoo.com)
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- // Send an IPv4 UDP packet via raw socket at the link layer (ethernet frame).
- // Need to have destination MAC address.
- // Includes some UDP data.
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h> // close()
- #include <string.h> // strcpy, memset(), and memcpy()
- #include <netdb.h> // struct addrinfo
- #include <sys/types.h> // needed for socket(), uint8_t, uint16_t, uint32_t
- #include <sys/socket.h> // needed for socket()
- #include <netinet/in.h> // IPPROTO_UDP, INET_ADDRSTRLEN
- #include <netinet/ip.h> // struct ip and IP_MAXPACKET (which is 65535)
- #include <netinet/udp.h> // struct udphdr
- #include <arpa/inet.h> // inet_pton() and inet_ntop()
- #include <sys/ioctl.h> // macro ioctl is defined
- #include <bits/ioctls.h> // defines values for argument "request" of ioctl.
- #include <net/if.h> // struct ifreq
- #include <linux/if_ether.h> // ETH_P_IP = 0x0800, ETH_P_IPV6 = 0x86DD
- #include <linux/if_packet.h> // struct sockaddr_ll (see man 7 packet)
- #include <net/ethernet.h>
- #include <errno.h> // errno, perror()
- // Define some constants.
- #define ETH_HDRLEN 14 // Ethernet header length
- #define IP4_HDRLEN 20 // IPv4 header length
- #define UDP_HDRLEN 8 // UDP header length, excludes data
- #define SEV_PORT 6666
- // Function prototypes
- uint16_t checksum (uint16_t *, int);
- uint16_t udp4_checksum (struct ip, struct udphdr, uint8_t *, int);
- char *allocate_strmem (int);
- uint8_t *allocate_ustrmem (int);
- int *allocate_intmem (int);
- int
- main (int argc, char **argv)
- {
- int temp_i=0;
- for(temp_i=0;temp_i<40;temp_i++)
- {
- int i, status, datalen, frame_length, sd, bytes, *ip_flags; char *interface, *target, *src_ip, *dst_ip;
- struct ip iphdr;
- struct udphdr udphdr;
- uint8_t *data, *src_mac, *dst_mac, *ether_frame;
- struct addrinfo hints, *res;
- struct sockaddr_in *ipv4;
- struct sockaddr_ll device;
- struct ifreq ifr;
- void *tmp;
- // Allocate memory for various arrays.
- src_mac = allocate_ustrmem (6);
- dst_mac = allocate_ustrmem (6);
- data = allocate_ustrmem (IP_MAXPACKET);
- ether_frame = allocate_ustrmem (IP_MAXPACKET);
- interface = allocate_strmem (40);
- target = allocate_strmem (40);
- src_ip = allocate_strmem (INET_ADDRSTRLEN);
- dst_ip = allocate_strmem (INET_ADDRSTRLEN);
- ip_flags = allocate_intmem (4);
- // Interface to send packet through.
- strcpy (interface, "eth0");
- // Submit request for a socket descriptor to look up interface.
- if ((sd = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0) {
- perror ("socket() failed to get socket descriptor for using ioctl() ");
- exit (EXIT_FAILURE);
- }
- // Use ioctl() to look up interface name and get its MAC address.
- memset (&ifr, 0, sizeof (ifr));
- snprintf (ifr.ifr_name, sizeof (ifr.ifr_name), "%s", interface);
- if (ioctl (sd, SIOCGIFHWADDR, &ifr) < 0) {
- perror ("ioctl() failed to get source MAC address ");
- return (EXIT_FAILURE);
- }
- close (sd);
- // Copy source MAC address.
- memcpy (src_mac, ifr.ifr_hwaddr.sa_data, 6 * sizeof (uint8_t));
- // Report source MAC address to stdout.
- printf ("MAC address for interface %s is ", interface);
- for (i=0; i<5; i++) {
- printf ("%02x:", src_mac[i]);
- }
- printf ("%02x\n", src_mac[5]);
- // Find interface index from interface name and store index in
- // struct sockaddr_ll device, which will be used as an argument of sendto().
- memset (&device, 0, sizeof (device));
- if ((device.sll_ifindex = if_nametoindex (interface)) == 0) {
- perror ("if_nametoindex() failed to obtain interface index ");
- exit (EXIT_FAILURE);
- }
- printf ("Index for interface %s is %i\n", interface, device.sll_ifindex);
- // Set destination MAC address: you need to fill these out
- dst_mac[0] = 0xff;
- dst_mac[1] = 0xff;
- dst_mac[2] = 0xff;
- dst_mac[3] = 0xff;
- dst_mac[4] = 0xff;
- dst_mac[5] = 0xff;
- // Source IPv4 address: you need to fill this out
- strcpy (src_ip, "192.168.3.209");
- // Destination URL or IPv4 address: you need to fill this out
- strcpy (target, "255.255.255.255");
- // Fill out hints for getaddrinfo().
- memset (&hints, 0, sizeof (struct addrinfo));
- hints.ai_family = AF_INET;
- hints.ai_socktype = SOCK_STREAM;
- hints.ai_flags = hints.ai_flags | AI_CANONNAME;
- // Resolve target using getaddrinfo().
- if ((status = getaddrinfo (target, NULL, &hints, &res)) != 0) {
- fprintf (stderr, "getaddrinfo() failed: %s\n", gai_strerror (status));
- exit (EXIT_FAILURE);
- }
- ipv4 = (struct sockaddr_in *) res->ai_addr;
- tmp = &(ipv4->sin_addr);
- if (inet_ntop (AF_INET, tmp, dst_ip, INET_ADDRSTRLEN) == NULL) {
- status = errno;
- fprintf (stderr, "inet_ntop() failed.\nError message: %s", strerror (status));
- exit (EXIT_FAILURE);
- }
- freeaddrinfo (res);
- // Fill out sockaddr_ll.
- device.sll_family = AF_PACKET;
- memcpy (device.sll_addr, src_mac, 6 * sizeof (uint8_t));
- device.sll_halen = htons (6);
- // UDP data
- datalen = 9;
- data[0] = '1';
- data[1] = '2';
- data[2] = '3';
- data[3] = '4';
- data[4] = '5';
- data[5] = '6';
- data[6] = '7';
- data[7] = '8';
- data[8] = '9';
- // IPv4 header
- // IPv4 header length (4 bits): Number of 32-bit words in header = 5
- 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 + UDP header + datalen
- iphdr.ip_len = htons (IP4_HDRLEN + UDP_HDRLEN + datalen);
- // ID sequence number (16 bits): unused, since single datagram
- //iphdr.ip_id = htons (0x2e20+temp_i);
- iphdr.ip_id = htons(0); //the ip_id can be constant or variable
- // 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): 17 for UDP
- iphdr.ip_p = IPPROTO_UDP;
- // Source IPv4 address (32 bits)
- if ((status = inet_pton (AF_INET, src_ip, &(iphdr.ip_src))) != 1) {
- fprintf (stderr, "inet_pton() failed.\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.\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 ((uint16_t *) &iphdr, IP4_HDRLEN);
- // UDP header
- // Source port number (16 bits): pick a number
- udphdr.source = htons (40622);
- // Destination port number (16 bits): pick a number
- udphdr.dest = htons (SEV_PORT);
- // Length of UDP datagram (16 bits): UDP header + UDP data
- udphdr.len = htons (UDP_HDRLEN + datalen);
- // UDP checksum (16 bits)
- udphdr.check = udp4_checksum (iphdr, udphdr, data, datalen);
- // Fill out ethernet frame header.
- // Ethernet frame length = ethernet header (MAC + MAC + ethernet type) + ethernet data (IP header + UDP header + UDP data)
- frame_length = 6 + 6 + 2 + IP4_HDRLEN + UDP_HDRLEN + datalen;
- // Destination and Source MAC addresses
- memcpy (ether_frame, dst_mac, 6 * sizeof (uint8_t));
- memcpy (ether_frame + 6, src_mac, 6 * sizeof (uint8_t));
- // Next is ethernet type code (ETH_P_IP for IPv4).
- // http://www.iana.org/assignments/ethernet-numbers
- ether_frame[12] = ETH_P_IP / 256;
- ether_frame[13] = ETH_P_IP % 256;
- // Next is ethernet frame data (IPv4 header + UDP header + UDP data).
- // IPv4 header
- memcpy (ether_frame + ETH_HDRLEN, &iphdr, IP4_HDRLEN * sizeof (uint8_t));
- // UDP header
- memcpy (ether_frame + ETH_HDRLEN + IP4_HDRLEN, &udphdr, UDP_HDRLEN * sizeof (uint8_t));
- // UDP data
- memcpy (ether_frame + ETH_HDRLEN + IP4_HDRLEN + UDP_HDRLEN, data, datalen * sizeof (uint8_t));
- // Submit request for a raw socket descriptor.
- if ((sd = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0) {
- perror ("socket() failed ");
- exit (EXIT_FAILURE);
- }
- // Send ethernet frame to socket.
- if ((bytes = sendto (sd, ether_frame, frame_length, 0, (struct sockaddr *) &device, sizeof (device))) <= 0) {
- perror ("sendto() failed");
- exit (EXIT_FAILURE);
- }
- // Close socket descriptor.
- close (sd);
- // Free allocated memory.
- free (src_mac);
- free (dst_mac);
- free (data);
- free (ether_frame);
- free (interface);
- free (target);
- free (src_ip);
- free (dst_ip);
- free (ip_flags);
- sleep(1);
- printf("%d\n",temp_i);
- }
- return (EXIT_SUCCESS);
- }
- // Checksum function
- uint16_t
- checksum (uint16_t *addr, int len)
- {
- int nleft = len;
- int sum = 0;
- uint16_t *w = addr;
- uint16_t answer = 0;
- while (nleft > 1) {
- sum += *w++;
- nleft -= sizeof (uint16_t);
- }
- if (nleft == 1) {
- *(uint8_t *) (&answer) = *(uint8_t *) w;
- sum += answer;
- }
- sum = (sum >> 16) + (sum & 0xFFFF);
- sum += (sum >> 16);
- answer = ~sum;
- return (answer);
- }
- // Build IPv4 UDP pseudo-header and call checksum function.
- uint16_t
- udp4_checksum (struct ip iphdr, struct udphdr udphdr, uint8_t *payload, int payloadlen)
- {
- char buf[IP_MAXPACKET];
- char *ptr;
- int chksumlen = 0;
- int i;
- ptr = &buf[0]; // ptr points to beginning of buffer buf
- // Copy source IP address into buf (32 bits)
- memcpy (ptr, &iphdr.ip_src.s_addr, sizeof (iphdr.ip_src.s_addr));
- ptr += sizeof (iphdr.ip_src.s_addr);
- chksumlen += sizeof (iphdr.ip_src.s_addr);
- // Copy destination IP address into buf (32 bits)
- memcpy (ptr, &iphdr.ip_dst.s_addr, sizeof (iphdr.ip_dst.s_addr));
- ptr += sizeof (iphdr.ip_dst.s_addr);
- chksumlen += sizeof (iphdr.ip_dst.s_addr);
- // Copy zero field to buf (8 bits)
- *ptr = 0; ptr++;
- chksumlen += 1;
- // Copy transport layer protocol to buf (8 bits)
- memcpy (ptr, &iphdr.ip_p, sizeof (iphdr.ip_p));
- ptr += sizeof (iphdr.ip_p);
- chksumlen += sizeof (iphdr.ip_p);
- // Copy UDP length to buf (16 bits)
- memcpy (ptr, &udphdr.len, sizeof (udphdr.len));
- ptr += sizeof (udphdr.len);
- chksumlen += sizeof (udphdr.len);
- // Copy UDP source port to buf (16 bits)
- memcpy (ptr, &udphdr.source, sizeof (udphdr.source));
- ptr += sizeof (udphdr.source);
- chksumlen += sizeof (udphdr.source);
- // Copy UDP destination port to buf (16 bits)
- memcpy (ptr, &udphdr.dest, sizeof (udphdr.dest));
- ptr += sizeof (udphdr.dest);
- chksumlen += sizeof (udphdr.dest);
- // Copy UDP length again to buf (16 bits)
- memcpy (ptr, &udphdr.len, sizeof (udphdr.len));
- ptr += sizeof (udphdr.len);
- chksumlen += sizeof (udphdr.len);
- // Copy UDP checksum to buf (16 bits)
- // Zero, since we don't know it yet
- *ptr = 0; ptr++;
- *ptr = 0; ptr++;
- chksumlen += 2;
- // Copy payload to buf
- memcpy (ptr, payload, payloadlen);
- ptr += payloadlen;
- chksumlen += payloadlen;
- // Pad to the next 16-bit boundary
- for (i=0; i<payloadlen%2; i++, ptr++) {
- *ptr = 0;
- ptr++;
- chksumlen++;
- }
- return checksum ((uint16_t *) buf, chksumlen);
- }
- // Allocate memory for an array of chars.
- char *
- allocate_strmem (int len)
- {
- void *tmp;
- if (len <= 0) {
- fprintf (stderr, "ERROR: Cannot allocate memory because len = %i in allocate_strmem().\n", len);
- exit (EXIT_FAILURE);
- }
- tmp = (char *) malloc (len * sizeof (char));
- if (tmp != NULL) {
- memset (tmp, 0, len * sizeof (char));
- return (tmp);
- } else {
- fprintf (stderr, "ERROR: Cannot allocate memory for array allocate_strmem().\n");
- exit (EXIT_FAILURE);
- }
- }
- // Allocate memory for an array of unsigned chars.
- uint8_t *
- allocate_ustrmem (int len)
- {
- void *tmp;
- if (len <= 0) {
- fprintf (stderr, "ERROR: Cannot allocate memory because len = %i in allocate_ustrmem().\n", len);
- exit (EXIT_FAILURE);
- }
- tmp = (uint8_t *) malloc (len * sizeof (uint8_t));
- if (tmp != NULL) {
- memset (tmp, 0, len * sizeof (uint8_t));
- return (tmp);
- } else {
- fprintf (stderr, "ERROR: Cannot allocate memory for array allocate_ustrmem().\n");
- exit (EXIT_FAILURE);
- }
- }
- // Allocate memory for an array of ints.
- int *
- allocate_intmem (int len)
- {
- void *tmp;
- if (len <= 0) {
- fprintf (stderr, "ERROR: Cannot allocate memory because len = %i in allocate_intmem().\n", len);
- exit (EXIT_FAILURE);
- }
- tmp = (int *) malloc (len * sizeof (int));
- if (tmp != NULL) {
- memset (tmp, 0, len * sizeof (int));
- return (tmp);
- } else {
- fprintf (stderr, "ERROR: Cannot allocate memory for array allocate_intmem().\n");
- exit (EXIT_FAILURE);
- }
- }
运行效果:
UDP发送端
UDP接收端
所有有参考价值的程序都在上面了,当然还有很多不明白的地方,一时搞不明白,以后遇到了再深入研究吧。