Swift和C混合Socket编程实现简单的ping命令

来源:转载

这个是用Mac下的Network Utility工具实现ping命令,用Wireshark抓取的ICMP数据包:

发送ICMP数据包内容

接受ICMP数据包内容

一.icmp结构

要真正了解ping命令实现原理,就要了解ping命令所使用到的TCP/IP协议。ICMP(Internet

Control

Message,网际控制报文协议)是为网关和目标主机而提供的一种差错控制机制,使它们在遇到差错时能把错误报告给报文源发方。ICMP协议是IP层的

一个协议,但是由于差错报告在发送给报文源发方时可能也要经过若干子网,因此牵涉到路由选择等问题,所以ICMP报文需通过IP协议来发送。

ICMP数据报的数据发送前需要两级封装:首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报

各种ICMP报文的前32bits都是三个长度固定的字段:type类型字段(8位)、code代码字段(8位)、checksum校验和字段(16位)

8bits类型和8bits代码字段:一起决定了ICMP报文的类型。常见的有:

类型8、代码0:回射请求。

类型0、代码0:回射应答。

类型11、代码0:超时。

16bits校验和字段:包括数据在内的整个ICMP数据包的校验和,其计算方法和IP头部校验和的计算方法是一样的。

对于ICMP回射请求和应答报文来说,接下来是16bits标识符字段:用于标识本ICMP进程。

最后是16bits序列号字段:用于判断回射应答数据报。

ICMP报文包含在IP数据报中,属于IP的一个用户,IP头部就在ICMP报文的前面

一个ICMP报文包括IP头部(20字节)、ICMP头部(8字节)和ICMP报文数据部分

ICMP报文格式,在Mac(Unix)下结构包含在ip_icmp.h中:

引入头文件#include //icmp数据包结构

struct icmp {

u_char icmp_type; /* type of message, see below */

u_char icmp_code; /* type sub code */

u_short icmp_cksum; /* ones complement cksum of struct */

union {

u_char ih_pptr; /* ICMP_PARAMPROB */

struct in_addr ih_gwaddr; /* ICMP_REDIRECT */

struct ih_idseq {

n_short icd_id;

n_short icd_seq;

} ih_idseq;

int ih_void;

/* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */

struct ih_pmtu {

n_short ipm_void;

n_short ipm_nextmtu;

} ih_pmtu;

struct ih_rtradv {

u_char irt_num_addrs;

u_char irt_wpa;

u_int16_t irt_lifetime;

} ih_rtradv;

} icmp_hun;

#define icmp_pptr icmp_hun.ih_pptr

#define icmp_gwaddr icmp_hun.ih_gwaddr

#define icmp_id icmp_hun.ih_idseq.icd_id

#define icmp_seq icmp_hun.ih_idseq.icd_seq

#define icmp_void icmp_hun.ih_void

#define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void

#define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu

#define icmp_num_addrs icmp_hun.ih_rtradv.irt_num_addrs

#define icmp_wpa icmp_hun.ih_rtradv.irt_wpa

#define icmp_lifetime icmp_hun.ih_rtradv.irt_lifetime

union {

struct id_ts {

n_time its_otime;

n_time its_rtime;

n_time its_ttime;

} id_ts;

struct id_ip {

struct ip idi_ip;

/* options and then 64 bits of data */

} id_ip;

struct icmp_ra_addr id_radv;

u_int32_t id_mask;

char id_data[1];

} icmp_dun;

#define icmp_otime icmp_dun.id_ts.its_otime

#define icmp_rtime icmp_dun.id_ts.its_rtime

#define icmp_ttime icmp_dun.id_ts.its_ttime

#define icmp_ip icmp_dun.id_ip.idi_ip

#define icmp_radv icmp_dun.id_radv

#define icmp_mask icmp_dun.id_mask

#define icmp_data icmp_dun.id_data

};

 

IP报头格式如下图:

/////////////////////////////////////////////////////////////////////////////////////////////////////////////

IP报文格式,在Mac(Unix)下结构包含在ip.h中:

引入头文件#include //ip数据包结构

struct ip {

#ifdef _IP_VHL

u_char ip_vhl; /* version << 4 | header length >> 2 */

#else

#if BYTE_ORDER == LITTLE_ENDIAN

u_int ip_hl:4, /* header length */

ip_v:4; /* version */

#endif

#if BYTE_ORDER == BIG_ENDIAN

u_int ip_v:4, /* version */

ip_hl:4; /* header length */

#endif

#endif /* not _IP_VHL */

u_char ip_tos; /* type of service */

u_short ip_len; /* total length */

u_short ip_id; /* identification */

u_short ip_off; /* fragment offset field */

#define IP_RF 0x8000 /* reserved fragment flag */

#define IP_DF 0x4000 /* dont fragment flag */

#define IP_MF 0x2000 /* more fragments flag */

#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */

u_char ip_ttl; /* time to live */

u_char ip_p; /* protocol */

u_short ip_sum; /* checksum */

struct in_addr ip_src,ip_dst; /* source and dest address */

};

 

二.具体实现代码

//xSocketPing.c#include "xSocketPing.h"

//statistics

void statistics(char* back){

double percent = ((double)sendPacketNumber - (double)recvPacketNumber) / (double)sendPacketNumber * 100;

sprintf(back, "---%s ping statistics---\n%d packets trasmitted, %d packet received, %0.1f%% packet loss",inet_ntoa(dstAddr.sin_addr),sendPacketNumber,recvPacketNumber,percent);

}

//check sum

unsigned short checkSum(unsigned short *buffer, int size){

unsigned long checkSum = 0;

while (size > 1) {

checkSum += *buffer++;

size -= sizeof(unsigned short);//unsigned short is 2 bytes = 16 bits

}

//if size is odd number

if (size == 1){

checkSum += *(unsigned short *)buffer;

}

checkSum = (checkSum >> 16) + (checkSum & 0xFFFF);

checkSum += (checkSum >> 16);

return ~checkSum;

}

//calculate time difference

double timeSubtract(struct timeval *recvTimeStamp, struct timeval *sendTimeStamp){

//calculate seconds

long timevalSec = recvTimeStamp->tv_sec - sendTimeStamp->tv_sec;

//calculate microsends

long timevalUsec = recvTimeStamp->tv_usec - sendTimeStamp->tv_usec;

//if microsends less then zero

if (timevalUsec < 0) {

timevalSec -= 1;

timevalUsec = - timevalUsec;

}

return (timevalSec * 1000.0 + timevalUsec) / 1000.0;

}

//fill icmp packet and return size of packet

int fillPacket(int packetSequence){

int packetSize = 0;

struct icmp *icmpHeader = (struct icmp *)sendBuffer;

icmpHeader->icmp_type = ICMP_ECHO;

icmpHeader->icmp_code = 0;

icmpHeader->icmp_cksum = 0;

icmpHeader->icmp_id = pid;

icmpHeader->icmp_seq = packetSequence;

packetSize = dataSize + 8;

tvSend = (struct timeval *)icmpHeader->icmp_data;

gettimeofday(tvSend, NULL);//get current of time

icmpHeader->icmp_cksum = checkSum((unsigned short *)icmpHeader, packetSize);

return packetSize;

}

//send icmp packet to dstAddr

int sendPacket(int packetSequence){

int packSize = 0;

packSize = fillPacket(packetSequence);

if ((sendto(socketfd, sendBuffer, packSize, 0, (struct sockaddr *)&dstAddr, sizeof(dstAddr))) < 0) {

printf("Send icmp packet Error\n");

sendPacketNumber--;

recvPacketNumber--;

return -1;

}

return 0;

}

//setting ip address

void settingIP(){

//initialize

bzero(&dstAddr,sizeof(dstAddr));

dstAddr.sin_family = AF_INET;

dstAddr.sin_addr.s_addr = inet_addr(ipAddr);

}

//get current process id

void getPid(){

pid = getpid();

}

//create socket

int createSocket(){

//原始套接字SOCK_RAW需要使用root权限,所以改用SOCK_DGRAM

if ((socketfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) < 0) {

printf("Create Socket Error\n");

return -1;

}

return 0;

}

//setting socket

void settingSocket(int timeout){

int size = 50 * 1024;

//setting timeout seconds or you can set it by microseconds

struct timeval timeOut;

timeOut.tv_sec = timeout;

//扩大套接字接收缓冲区到50K这样做主要为了减小接收缓冲区溢出的可能性,若无意中ping一个广播地址或多播地址,将会引来大量应答

setsockopt(socketfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));

setsockopt(socketfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeOut, sizeof(timeOut));

}

//destory socket

void destorySocket(){

close(socketfd);

}

//unpacket

void unPacket(char* packetBuffer,char* back, long size){

struct ip *ipHeader = NULL;

struct icmp *icmpHeader = NULL;

double rtt;//往返时间

int ipHeaderLength;//ip header length

ipHeader = (struct ip *)packetBuffer;

ipHeaderLength = ipHeader->ip_hl<<2;//求ip报头长度,即ip报头的长度标志乘4

icmpHeader = (struct icmp *)(packetBuffer + ipHeaderLength);//越过IP头,point to ICMP header

size -= ipHeaderLength;

if (size < 8){

back = "Unpacket Error:packet size minmum 8 bytes\n";

}

if ((icmpHeader->icmp_type == ICMP_ECHOREPLY) && (icmpHeader->icmp_id == pid)) {

tvSend = (struct timeval *)icmpHeader->icmp_data;

gettimeofday(&tvRecv, NULL);

//以毫秒为单位计算rtt

rtt = timeSubtract(&tvRecv, tvSend);

sprintf(back,"%ld bytes from %s: icmp_seq=%u ttl=%d time=%.1f ms\n",size,inet_ntoa(recvAddr.sin_addr),icmpHeader->icmp_seq,ipHeader->ip_ttl,rtt);

}else{

back = "Unpacket Error\n";

}

}

//receive packet

void receivePacket(char* back){

//claculate packet size

int packetSize = sizeof(recvAddr);

long size;

if ((size = recvfrom(socketfd, recvBuffer, sizeof(recvBuffer), 0, (struct sockaddr *)&recvAddr, (socklen_t *)&packetSize)) < 0) {

sprintf(back,"Receive timeout\n");

recvPacketNumber--;

}else{

gettimeofday(&tvRecv, NULL);

//char temp[100] = {0};

unPacket(recvBuffer, back, size);

//printf("%s\n",temp);

}

}

void ping(char *ipAddress, int number, int timeout){

int packetnumber = 0;

ipAddr = ipAddress;

sendPacketNumber = number;

recvPacketNumber = number;

settingIP();

getPid();

if (createSocket() != -1){

settingSocket(timeout);

printf("PING %s: %d bytes of data.\n",ipAddress,dataSize);

while(packetnumber < number){

if (sendPacket(packetnumber) != -1){

char back[100] = {0};

receivePacket(back);

printf("%s",back);

}

sleep(1);

packetnumber++;

}

char back[100] = {0};

statistics(back);

printf("%s\n",back);

destorySocket();

}

}

//xSocketPing.h#ifndef xSocketPing_h

#define xSocketPing_h

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/types.h>//基本系统数据类型

#include <sys/socket.h>

#include <netinet/in.h>

//get current of time

#include <sys/time.h>

#include <arpa/inet.h>//inet_ntoa将一个IP转换成一个互联网标准点分格式的字符串

#include <unistd.h>//close(int)

#include <netdb.h>//定义了与网络有关的结构、变量类型、宏、函数等

//ip packet structure

#include <netinet/ip.h>

//icmp packet structure

#include <netinet/ip_icmp.h>

//time to live

int ttl = 64;

//icmp data size ,icmp header 8bytes,data size 56bytes,the maximum of packet size 64bytes

int dataSize = 56;

//packet number

int sendPacketNumber;

int recvPacketNumber;

//ip address

char * ipAddr;

//send packet of time

struct timeval *tvSend;

//receive packet of time

struct timeval tvRecv;

//Socket address, internet style.

//the destination address

struct sockaddr_in dstAddr;

//the receive address

struct sockaddr_in recvAddr;

//send icmp buffer

char sendBuffer[1024] = {0};

//receive icmp replay buffer

char recvBuffer[1024] = {0};

//the current process of id

int pid;

//socket

int socketfd = 0;

void statistics(char* back);

unsigned short checkSum(unsigned short *buffer, int size);

double timeSubtract(struct timeval *recvTimeStamp, struct timeval *sendTimeStamp);

int fillPacket(int packetSequence);

int sendPacket(int packetSequence);

void settingIP();

void getPid();

int createSocket();

void settingSocket(int timeout);

void destorySocket();

void unPacket(char* packetBuffer,char* back, long size);

void receivePacket(char* back);

void ping(char *ipAddress, int number, int timeout);

#endif /* xSocketPing_h */

//xSocketPing.swift

import Foundation

public class xSocketPing{

private var ipAddress:String

//default packet number 3 or you can setting it by yourself

private var packetNumber:Int = 3

private var timeout:Int = 1

//refresh UI

weak var delegate:refreshTextDelegate?

init(ipAddress:String, delegate:refreshTextDelegate){

self.ipAddress = ipAddress

self.delegate = delegate

}

convenience init(ipAddress:String, packetNumber:Int, delegate:refreshTextDelegate){

self.init(ipAddress: ipAddress,delegate: delegate)

self.packetNumber = packetNumber

}

convenience init(ipAddress:String, packetNumber:Int, timeout:Int, delegate:refreshTextDelegate){

self.init(ipAddress: ipAddress,packetNumber: packetNumber,delegate: delegate)

self.timeout = timeout

}

public func xPing(){

let tempIpAddress:UnsafeMutablePointer = UnsafeMutablePointer<Int8>((ipAddress as NSString).UTF8String)

ipAddr = tempIpAddress

sendPacketNumber = Int32(packetNumber)

recvPacketNumber = Int32(packetNumber)

settingIP()

getPid()

if createSocket() != -1 {

settingSocket(Int32(timeout))

let message:String = "PING \(ipAddress): 56 bytes of data.\n"

refresh(message, speed: 0.0)

//将String转换为UnsafeMutablePointer<CChar>,相当于char tempmessage[100]

let tempmessage:UnsafeMutablePointer = UnsafeMutablePointer<CChar>.alloc(100)

var packetsequence:Int = 0

var speed:Float = 0.0

while packetsequence < packetNumber {

if sendPacket(Int32(packetsequence)) != -1 {

receivePacket(tempmessage)

//Calculate percentage

speed = (Float(packetNumber) - (Float(packetNumber) - Float(packetsequence))) / Float(packetNumber)

//将UnsafeMutablePointer<CChar>转换为String

refresh(String.fromCString(tempmessage)!, speed: speed)

}

sleep(1);

packetsequence++

}

statistics(tempmessage)

refresh(String.fromCString(tempmessage)!, speed: 1)

destorySocket()

}

}

func refresh(text:String, speed:Float){

delegate?.refresh(text, speed: speed)

}

deinit{

print("xSocketPing destory")

}

}

 

//ViewController.swift

import UIKit

protocol refreshTextDelegate:NSObjectProtocol{

func refresh(text:String, speed:Float)

}

class ViewController: UIViewController,refreshTextDelegate {

@IBOutlet weak var xprogress: UIProgressView!

@IBOutlet weak var xip: UITextField!

@IBOutlet weak var xnumber: UITextField!

@IBOutlet weak var xtext: UITextView!

var number:Int = 3

var ip:String = "192.168.1.1"

override func viewDidLoad() {

super.viewDidLoad()

// Do any additional setup after loading the view, typically from a nib.

xtext.text = ""

}

override func didReceiveMemoryWarning() {

super.didReceiveMemoryWarning()

// Dispose of any resources that can be recreated.

}

@IBAction func xNumberChange(sender: UIStepper) {

xnumber.text = "\(Int(sender.value))"

self.number = Int(sender.value)

}

@IBAction func beginPing(sender: UIButton) {

sender.enabled = false

xip.enabled = false

xnumber.enabled = false

xprogress.progress = 0

xtext.text = ""

let ip = xip.text

if ip == ""{

let alert = UIAlertView(title: "Error", message: "No IP Address", delegate: self, cancelButtonTitle: "OK")

alert.show()

}else{

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {

let ping:xSocketPing = xSocketPing(ipAddress: "\(ip!)", packetNumber: self.number, delegate: self)

ping.xPing()

})

}

xnumber.enabled = true

xip.enabled = true

sender.enabled = true

}

func refresh(text:String, speed:Float) {

dispatch_async(dispatch_get_main_queue(), {

//更新进度条

self.xprogress.progress = speed

//更新UITextView,追加内容并滚动到最下面

self.xtext.text.appendContentsOf(text)

self.xtext.scrollRangeToVisible(NSMakeRange(self.xtext.text.characters.count, 0))

})

}

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

self.view.endEditing(true)

}

}

运行结果图:

运行环境OS

X10.10.5,Xcode7.0,Swift2.1,iOS9.0模拟器

真机测试的时候有可能找不到#include

,猜测是苹果不给在机子上使用icmp数据包,如果只是在自己机子上测试,可以将模拟器的ip_icmp.h文件复制到真机环境,再进行编译运行.

部分出现的疑惑或者问题,可以看我之前的新浪博客 Wireshark找不到网卡

Swift和C语言交互-String和UnsafeMutablePointer转换

Swift和c的类型转换

Swift与c的交互

GitHub项目地址:Swift和C混合Socket编程实现简单的ping命令

本程序还有很多不足之处,后续会持续更新,并更新GitHub项目,需要改进的,请在博客上留言

抓包的图片是不更新的,所以会和运行结果图不一样.

更新于2016-1-13

修复bug,优化部分显示,更好,更安全的处理指针类型.

更新于2016-1-12

新增功能:优化显示,将返回的结果显示到模拟器中.发送数据包在额外的线程中进行,不会阻塞主线程.由于程序使用的是Swift和C语言混合编程,用到了指针,交互的时候容易crash.后续会继续修复bug.

更新2016-1-7

新增功能:优化显示,将返回结果集中到主函数中,方便调用,修复部分bug,添加超时处理,防止程序一直处于阻塞状态.,

分享给朋友:
您可能感兴趣的文章:
随机阅读: