当一个设备连接到局域网以后,我们可能不知道它的Static IP,也可能这个IP正好对于该网段是一个无效的IP,那么我们就需要通过数据链路层来发送UDP包来与设备通信,并设置有效的IP。
我们可以把整个过程分为两个阶段:DISCOVER阶段,CONVERSE阶段。
DISCOVER
这个阶段比较简单。因为无论设备的IP是多少,只要正确建立基于UDP的socket,则设备就能够接收到网络上的广播。所以下面给出简单的示意代码:
int fd;
struct sockaddr_in my_addr;
fd_set fdset;
struct timeval timeout;
fd = socket(PF_INET, SOCK_DGRAM, 0);
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(SERVER_PORT);
my_addr.sin_addr.s_addr = INADDR_ANY;
bind(fd, (struct sockaddr *) &my_addr, sizeof(my_addr));
FD_ZERO(&fdset);
FD_SET(fd, &fdset);
timeout.tv_sec = TIMEOUT;
timeout.tv_usec = 0;
select(fd + 1, &fdset, NULL, NULL, &timeout);
recvfrom(fd, buff, BUFFSIZ - 1, 0, NULL, NULL);
buff[BUFFSIZE] = '\0';
CONVERSE
与前一阶段相比,这个阶段稍微要麻烦一点。首先需要建立一个Packet socket:
fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
这里的SOCK_DGRAM指出这个socket是针对cooked packets,即packet中是没有link level header信息的。所以我们需要包含有link level header信息的结构体sockaddr_ll:
struct sockaddr_ll dest;
struct ifreq ifq;
memcpy(ifq.ifr_name, "eth0", IFNAMSIZ);
octl(fd, SIOCGIFINDEX, &ifq);
bzero(&dest, sizeof(dest));
dest.sll_family = AF_PACKET;
dest.sll_protocol = htons(ETH_P_IP);
dest.sll_ifindex = ifq.ifr_ifindex;
dest.sll_addr[0] = 0xFF;
dest.sll_addr[1] = 0xFF;
dest.sll_addr[2] = 0xFF;
dest.sll_addr[3] = 0xFF;
dest.sll_addr[4] = 0xFF;
dest.sll_addr[5] = 0xFF;
dest.sll_addr[6] = '\0';
bind(fd, (struct sockaddr *) &dest, sizeof(dest));
这样,我们需要的link level header就设置好了。
接下来就是自行购建IP包和UDP包了:
struct { struct iphdr ip; struct udphdr udp; struct content data } packet;
ioctl(fd, SIOCGIFADDR, &ifq);
bzero(&packet, sizeof(packet));
packet.ip.protocol = IPPROTO_UDP;
packet.ip.saddr = ((struct sockaddrin *) &ifq.ifr_addr)->sin_addr.s_addr;
packet.ip.daddr = INADDR_BROADCAST;
packet.udp.source = htons(SERVER_PORT);
packet.udp.dest = htons(DISCOVER_PORT);
packet.udp.len = htons(sizeof(packet.udp) + sizeof(packet.data));
packet.ip.tot_len = packet.udp.len;
packet.udp.check = checksum(&packet, sizeof(packet));
packet.ip.tot_len = htons(sizeof(packet));
packet.ip.ihl = sizeof(packet.ip) >> 2;
packet.ip.version = IPVERSION;
packet.ip.ttl = IOPDEFTTL;
packet.ip.check = checksum(&packet.ip, sizeof(packet.ip));
相关的成员设置可以参考TCP/IP的教程进行理解。
最后就可以将数据发送出去了:
sendto(fd, &packet, sizeof(packet), 0, (struct sockaddr *) &dest, sizeof(dest));
好啦!
用Wireshark抓一下包吧……记得设置成“非法”IP哦! 
Tagged DISCOVER, packet socket, UDP广播, 寻找设备