代码拉取完成,页面将自动刷新
同步操作将从 -卓然-/udp_relay 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
/********************************************************************************************
*** Modification histroy:
*** 2012/09/07 JaxonJia rewrite module and add new feature
*** 支持以下功能:
*** 1)从指定的地址接收并保存成cap文件(可定时和分析丢包).
*** 2)支持直接从指定的地址接收流并直接保存成mpg文件(可定时和分析丢包).
*** 3)将cap文件还原成流发送到指定的地址(支持单个文件的循环发送)
*** 4)支持将cap文件转换成可播放的ts文件(可边转边分析丢包)
*** 5)支持分析指定文件(cap,ts)或指定组播流是否存在RTP丢包和TS丢包
*** 6)支持通过指定组播地址和端口获取源IP地址和端口
*** 7)支持后台运行
*** 8)支持自动分析RTP头长度(cap_convert, got_packet中)
*** 9)支持组播转发到新的组播或单播地址(并可以指定丢包率)
*** 10)支持分析发包间隔是否超过设定的阈值
*********************************************************************************************/
#include "ur.h"
#include <errno.h>
#include <net/if.h>
#include <sys/ioctl.h>
#define LOG_FILE "/var/log/ur.log"
bool g_exit = false;
bool g_repeat = false;
int g_promisc = 1; // >0时使用混杂模式进行数据包的抓取
#define ASSERT(b) {if (!(b)) { printf("###### Assert at %s:%d\n", __FILE__, __LINE__); return;}}
#define ASSERT_EX(b, ret) {if (!(b)) { printf("###### Assert at %s:%d\n", __FILE__, __LINE__); return ret;}}
/*********************************************************************
* 函数名称:udp_replay
* 函数描述:将cap文件数据发往指定组播地址和端口
* 输入参数:capfile char* cap文件路径
* ip char* 目标组播IP地址
* port unsigned short 目标组播端口
* offset u_int32 转发时需要进行偏移的长度
* pkt_loss_rate u_int16 丢包率
* 输出参数:N/A
* 返 回 值:正常返回0,异常返回负值
*********************************************************************/
int udp_replay(const char* capfile, const char* ip, unsigned short port, u_int32 offset, u_int16 pkt_loss_rate)
{
ASSERT_EX(NULL != capfile && NULL != ip && offset <= MAX_OFFSET_LEN, ERROR_INVALID_PARAM);
pcap_t *fp = NULL;
char errbuf[ERR_BUF_SIZE] = {0};
struct pcap_pkthdr *header = NULL;
const unsigned char *pkt_data = NULL;
int res = 0;
struct timeval pkt_ts_begin = {0};
struct timeval pkt_ts_current = {0};
struct timeval pkt_ts_gap = {0};
struct timeval sys_ts_begin = {0};
struct timeval sys_ts_current = {0};
struct timeval sys_ts_gap = {0};
signal(SIGINT, sighander);
//open the offline file
fp = pcap_open_offline(capfile, errbuf);
if (NULL == fp)
{
printf("[%s]Can not open the file: %s, error: %s\n", __FUNCTION__, capfile, errbuf);
return ERROR_OPEN_FILE;
}
int socksend = -1;
struct sockaddr_in saddrsend = {0};
saddrsend.sin_family = AF_INET;
saddrsend.sin_port = htons(port);
saddrsend.sin_addr.s_addr = inet_addr(ip);
res = sock_send(socksend, errbuf);
if (res != 0 )
{
printf("[%s]Create send error and return for %s.\n", __FUNCTION__, errbuf);
return ERROR_SOCK_CREAT;
}
long sendbytes = 0;
long sendtime = 0;
long temptime = 0;
//read the first pakcet for timestamp
res = pcap_next_ex( fp, &header, &pkt_data);
if (res < 0)
{
printf("[%s]The first read error and return:%d.\n", __FUNCTION__, res);
return ERROR_READ_FILE;
}
memmove(&pkt_ts_begin, &(header->ts), sizeof(struct timeval));
memmove(&pkt_ts_current, &(header->ts), sizeof(struct timeval));
gettimeofday(&sys_ts_begin, (struct timezone*)NULL);
memmove(&sys_ts_current, &sys_ts_begin, sizeof(struct timeval));
//Reopen the file for send
pcap_close(fp);
fp = pcap_open_offline(capfile, errbuf);
if (NULL == fp)
{
printf("[%s]Can not open the file: %s.\n", __FUNCTION__, capfile);
return ERROR_OPEN_FILE;
}
u_int64 pkt_num = 0;
/* Retrieve the packets from the file */
printf("[%s]Reach the main loop to send.\n", __FUNCTION__);
while ((res = pcap_next_ex( fp, &header, &pkt_data)) >= 0)
{
if (header->caplen != header->len)
{
printf("[%s]Frame length != Capture length, now will be return.\n", __FUNCTION__);
pcap_close(fp);
return ERROR_CAPTURE;
}
gettsgap(pkt_ts_current, pkt_ts_begin, &pkt_ts_gap);
gettsgap(sys_ts_current, sys_ts_begin, &sys_ts_gap);
while (gettsgap(sys_ts_gap, pkt_ts_gap) < -1000)
{
usleep(0);
gettimeofday(&sys_ts_current, (struct timezone*)NULL);
gettsgap(sys_ts_current, sys_ts_begin, &sys_ts_gap);
}
int ret = 0;
memmove(&pkt_ts_current, &(header->ts), sizeof(struct timeval));
if (header->caplen > PACKET_HEADER_LEN + offset)
{
// 如果需要丢包,就不需要sendto发送
if (!is_loss_pkt(pkt_loss_rate))
{
ret = sendto(socksend, (char*)pkt_data + PACKET_HEADER_LEN + offset,
header->caplen - PACKET_HEADER_LEN - offset, 0,
(struct sockaddr*)&saddrsend, sizeof(saddrsend));
if (-1 == ret)
{
printf("[%s]Send error and return %d. errno %d\n", __FUNCTION__, ret, errno);
return ERROR_SOCK_SEND;
}
}
pkt_num++;
//printf("[%s]Send %4d bytes, pkt_num=%lu.\n", __FUNCTION__, ret, pkt_num);
sendbytes += ret;
sendtime = gettsgap(pkt_ts_current, pkt_ts_begin);
}
if (sendtime - temptime > 1000000) // 1s
{
printf("[%s]Send the packet at %.3f Mbps continued %02d:%02d:%02d\n",
__FUNCTION__,
(double)(sendbytes * 8)/(sendtime),
(int)(sendtime/ONE_SECOND)/3600,
(int)((sendtime/ONE_SECOND)%3600)/60,
(int)(sendtime/ONE_SECOND)%60);
temptime = sendtime;
}
if (g_exit)
break;
}
pcap_close(fp);
close(socksend);
if (-1 == res)
{
printf("[%s]Error reading the packets: %s.\n", __FUNCTION__, pcap_geterr(fp));
return ERROR_READ_FILE;
}
return OK;
}
/*********************************************************************
* 函数名称:cap_convert
* 函数描述:将cap包转换成mpg文件
* 输入参数:input_file char* 要转换的cap文件名
* output_file char* 转换后的ts文件名
* workmod u_int32 指定是否需要检查丢包
* logfile char* 发现丢包时记录丢包的log文件名
* offset u_int32 数据部分需要进行偏移的长度(跳过RTP头)
* 输出参数:N/A
* 返 回 值:正常返回0,异常返回负值
* 创 建:Jaxon Jia (sz082169)
* 创建日期:2012/09/05
*********************************************************************/
int cap_convert(const char* input_file, const char* output_file,
u_int32 workmod, const char* logfile, u_int32 offset)
{
ASSERT_EX(NULL != input_file && NULL != output_file
&& NULL != logfile && offset <= MAX_OFFSET_LEN, ERROR_INVALID_PARAM);
signal(SIGINT, sighander); //ctrl+c的处理
char errbuf[ERR_BUF_SIZE] = {0};
// 调用pcap函数接口打开cap文件
pcap_t *fp_cap = pcap_open_offline(input_file, errbuf);
if (NULL == fp_cap)
{
printf("[%s]Can not open the file: %s. errno %d\n", __FUNCTION__, input_file, errno);
return ERROR_OPEN_FILE;
}
FILE *fp_mpg = fopen(output_file,"wb");
if (NULL == fp_mpg)
{
printf("[%s]Can not open the file: %s. errno %d\n", __FUNCTION__, output_file, errno);
return ERROR_OPEN_FILE;
}
FILE* fp_logfile = NULL;
if (workmod & CHECK_LOSS)
{
fp_logfile = fopen(logfile, "w");
if (NULL == fp_logfile)
{
printf("[%s]Couldn't open log file of packet loss: %s. errno %d\n",
__FUNCTION__, logfile, errno);
return ERROR_OPEN_FILE;
}
printf("[%s]Check packet loss. The packet loss info will write to: %s.\n",
__FUNCTION__, logfile);
}
/* Retrieve the packets from the cap file */
printf("[%s]Reach the main loop to convert.\n", __FUNCTION__);
struct pcap_pkthdr *header = NULL;
const unsigned char *pkt_data = NULL;
int res = 0;
long convertbytes = 0;
u_int32 writelen = 0;
int last_rtp_sn = -1;
int is_first_run = 1; //首次运行标识
unsigned long cap_pkt_num = 0; //cap文件中的包序号
char src_ip_port[MAX_IP_LEN+MAX_PORT_LEN] = {0}; //源IP:Port
struct timeval ts_begin = {0};
while (!g_exit && (res = pcap_next_ex( fp_cap, &header, &pkt_data)) >= 0)
{// 调用pcap函数pcap_next_ex获取下一个数据包
if (header->caplen != header->len)
{
printf("[%s]Frame length != Capture length, now will be return.\n", __FUNCTION__);
pcap_close(fp_cap);
fclose(fp_mpg);
return ERROR_CAPTURE;
}
cap_pkt_num++;
if (is_first_run)
{
is_first_run = 0;
//获取源IP:Port
strcpy(src_ip_port, get_src_ip_port(pkt_data));
printf("[%s]source ip:port = %s.\n", __FUNCTION__, src_ip_port);
if (0 == offset)
{//自动分析RTP头长度 2013/1/24
offset = header->len % TS_PKT_LEN - PACKET_HEADER_LEN;
printf("[%s]Auto skip RTP header [offset]: %d Byte.\n", __FUNCTION__, offset);
}
}
else if (strcmp(src_ip_port, get_src_ip_port(pkt_data)) != 0)
{//跳过混淆包
printf("[%s][CapNo=%u]src ip:port not match, skip it.\n", __FUNCTION__, cap_pkt_num);
continue;
}
if (header->caplen > PACKET_HEADER_LEN + offset)
{// 跳过包头(MAC+IP+UDP)和RTP头将数据写入MPG文件
writelen = fwrite(pkt_data + PACKET_HEADER_LEN + offset, 1,
header->caplen - PACKET_HEADER_LEN - offset, fp_mpg);
if (writelen != header->caplen - PACKET_HEADER_LEN - offset)
{
printf("[%s]Write error. Write length %d < %d, now will be return.\n",
__FUNCTION__, writelen, header->caplen - PACKET_HEADER_LEN - offset);
pcap_close(fp_cap);
fclose(fp_mpg);
return ERROR_WRITE_FILE;
}
if (workmod & CHECK_LOSS)
{
int ret = 0;
if (offset > 0)
{
// 含有RTP包头
ret = check_rtp_sn(pkt_data + PACKET_HEADER_LEN, last_rtp_sn, fp_logfile, cap_pkt_num);
}
if (0 == ret)
{
check_ts_continuity_counter(pkt_data + PACKET_HEADER_LEN + offset,
header->caplen - PACKET_HEADER_LEN - offset, fp_logfile, cap_pkt_num);
}
}
convertbytes += writelen;
if (time_interval(ts_begin, ONE_SECOND))
{// 1s输出一次信息
printf("[%s]convert bytes: %.3f MB.\n", __FUNCTION__,
((double)convertbytes)/1024/1024);
}
}
}
pcap_close(fp_cap);
fclose(fp_mpg);
if (-1 == res)
{
printf("[%s]Error reading the packets: %s.\n", __FUNCTION__, pcap_geterr(fp_cap));
return ERROR_READ_FILE;
}
printf("[%s]Convert complete. Convert bytes=%ld.\n", __FUNCTION__, convertbytes);
return OK;
}
/*********************************************************************
* 函数名称:check_rtp_sn
* 函数描述:检查RTP的连续性
* 输入参数:rtpdata u_char* 指向RTP头的指针
* last_rtp_sn int 上一个包的RTP SN序列号
* logfile FILE* 用于记录丢包信息的日志文件句柄
* 输出参数:last_rtp_sn int 更新为当前RTP SN序列号
* 返 回 值:0-ts_over_rtp, 1-not ts_over_rtp
* 创 建:Jaxon Jia (sz082169)
* 创建日期:2012/09/06
*********************************************************************/
int check_rtp_sn(const u_char *rtpdata, int &last_rtp_sn, FILE* fp_logfile, int cap_pkt_idx)
{
ASSERT_EX(NULL != rtpdata && NULL != fp_logfile, ERROR_INVALID_PARAM);
int pt = rtpdata[1] & 0x7F;
if (TS_PT_IN_RTP != pt) return 1; // 跳过非TS的RTP包
// 获取RTP包头的sequence number 16bit
int cur_rtp_sn = *(rtpdata + RTP_SN_OFFSET);
cur_rtp_sn = (cur_rtp_sn << 8) | *(rtpdata + RTP_SN_OFFSET + 1);
int expect_rtp_sn = (last_rtp_sn + 1) & 0xffff;
char cur_time_str[TIME_STRING_LEN] = {0};
get_time_str(cur_time_str);
if ((-1 != last_rtp_sn) && (cur_rtp_sn != expect_rtp_sn))
{// 检查RTP SN的连续性, 确定是否丢包
printf("[%s][No.%d]RTP packet loss found! last_sn=%5d, expect_sn=%5d, current_sn=%5d.\n",
__FUNCTION__, cap_pkt_idx, last_rtp_sn, last_rtp_sn + 1, cur_rtp_sn);
fprintf(fp_logfile, "[%s][No.%d]RTP Packet loss found! last_sn=%5d, expect_sn=%5d, current_sn=%5d.\n",
cur_time_str, cap_pkt_idx,last_rtp_sn, last_rtp_sn + 1, cur_rtp_sn);
}
last_rtp_sn = cur_rtp_sn;
return 0;
}
/*********************************************************************
* 函数名称:check_ts_continuity_counter
* 函数描述:检查TS流的连续性
* 输入参数:tsdata u_char* 指向TS数据开始部分的指针
* tslen int TS数据部分的长度
* logfile FILE* 用于记录丢包信息的日志文件句柄
* 输出参数:N/A
* 返 回 值:正常返回0,异常返回负值
* 创 建:Jaxon Jia (sz082169)
* 创建日期:2012/09/06
*********************************************************************/
int check_ts_continuity_counter(const u_char *tsdata, int tslen, FILE* logfile, int cap_pkt_idx)
{
ASSERT_EX(NULL != tsdata && tslen >0 && NULL != logfile, ERROR_INVALID_PARAM);
const u_char* ts_pkt = tsdata; // 指向TS数据开始部分
static int pid_list[MAX_PID] = {0}; // 记录已经出现过的PID
static int ts_cc_list[MAX_PID] = {0}; // 记录PID对应的TS CC
static bool is_init = false; // pid_list和ts_cc_list是否初始化的标志
static int pkt_counter = 0; // 用于TS包计数
if (!is_init)
{// 初始化PID表和ts cc表
is_init = true;
memset(pid_list, -1, MAX_PID);
memset(ts_cc_list, -1, MAX_PID);
}
while (ts_pkt < tsdata + tslen)
{
// 同步字节0x47 8bit
u_char sync_byte = ts_pkt[0];
if (TS_SYNC_BYTE != sync_byte)
{
printf("[%s][No.%d]Invalid TS packet.\n", __FUNCTION__, cap_pkt_idx);
//fprintf(logfile, "[%s]Invalid TS packet.\n", __FUNCTION__);
return ERROR_INVALID_PKT;
}
pkt_counter++; //TS包计数
// 取得PID 13bit
int cur_pid = (ts_pkt[1] & 0x1f) << 8 | ts_pkt[2];
// 自适应区控制 2bit
int adaptation_field_control = (ts_pkt[3] >> 4) & 0x3;
// TS包中的continuity_counter, 4bit (pid=0x1fff的空分组此字段无意义)
// 计数范围0-15, 具有相同的PID的TS分组传输时每次加1, 到15后清0
int cur_ts_cc = ts_pkt[3] & 0xf;
int adptation_field_length = 0;
int discontinuity_indicator = 0;
if (adaptation_field_control & 0x2)
{// 自适应区存在时获取自适应区长度和不连续性指示位
// 自适应区长度8bit
adptation_field_length = ts_pkt[4];
if (adptation_field_length > 0)
{
// 不连续性指示位1bit
discontinuity_indicator = (ts_pkt[5] >> 8) & 0x1;
}
}
// 仅PID相同的TS包的continuity_counter连续,
// 空分组(pid=0x1fff)此字段无意义
if (0x1fff == cur_pid)
{
// 处理下一个TS包
ts_pkt += TS_PKT_LEN;
continue;
}
for (int i = 0; i < MAX_PID; i++)
{// 查找pid表, 更新对应ts cc; 找不到插入pid
if (-1 == pid_list[i])
{
pid_list[i] = cur_pid;
ts_cc_list[i] = cur_ts_cc;
break;
}
else if (pid_list[i] == cur_pid)
{
int last_ts_cc = ts_cc_list[i];
ts_cc_list[i] = cur_ts_cc; //更新ts cc
int expect_cc = 0;
if ((adaptation_field_control & 0x1) && (0 == discontinuity_indicator))
{// 有payload且discontinuity_indicator不为1, ts cc加1
expect_cc = (last_ts_cc + 1) & 0xf;
}
else
{
expect_cc = last_ts_cc & 0xf;
}
if ((adaptation_field_control & 0x2)
&& adptation_field_length > 0
&& discontinuity_indicator
)
{
char cur_time_str[TIME_STRING_LEN] = {0};
get_time_str(cur_time_str);
printf("[%s]discontinuity_indicator = 0x1.\n", __FUNCTION__);
fprintf(logfile, "[%s][pkt_no=%4d]discontinuity_indicator = 0x1.\n",
cur_time_str, pkt_counter);
break;
}
if (cur_ts_cc != expect_cc)
{
char cur_time_str[TIME_STRING_LEN] = {0};
get_time_str(cur_time_str);
if (cur_ts_cc > expect_cc)
{
printf("[%s][No.%d]TS packet loss found! pkt_no=%4d, pid=0x%x, last_cc=%2d, current_cc=%2d.\n",
__FUNCTION__, cap_pkt_idx, pkt_counter, cur_pid, last_ts_cc, cur_ts_cc);
fprintf(logfile, "[%s][No.%d][pkt_no=%4d]TS packet loss found! pid=0x%x, last_cc=%2d, expect_cc=%2d, current_cc=%2d. data_byte %s.\n",
cur_time_str, cap_pkt_idx, pkt_counter, cur_pid, last_ts_cc, expect_cc, cur_ts_cc,
(adaptation_field_control & 0x1) ? "exist" : "not exist");
}
else //cur_ts_cc < expect_cc
{
printf("[%s][No.%d]TS packet order error! pkt_no=%4d, pid=0x%x, last_cc=%2d, current_cc=%2d.\n",
__FUNCTION__, cap_pkt_idx, pkt_counter, cur_pid, last_ts_cc, cur_ts_cc);
fprintf(logfile, "[%s][No.%d][pkt_no=%4d]TS packet order error! pid=0x%x, last_cc=%2d, expect_cc=%2d, current_cc=%2d. data_byte %s.\n",
cur_time_str, cap_pkt_idx, pkt_counter, cur_pid, last_ts_cc, expect_cc, cur_ts_cc,
(adaptation_field_control & 0x1) ? "exist" : "not exist");
}
}
break;
}// end of else if (pid_list[i] == cur_pid)
}
// 处理下一个TS包
ts_pkt += TS_PKT_LEN;
}
return OK;
}
/*********************************************************************
* 函数名称:init_capture
* 函数描述:pcap包捕获功能初始化
* 输入参数:filter_string char* 过滤字符串
* dev char* 网卡(如eth0, band0)
* 输出参数:cap_handle pcap_t*& pcap_open_live打开捕获设备返回的句柄
* 返 回 值:正常返回0,异常返回负值
* 创 建:Jaxon Jia (sz082169)
* 创建日期:2012/10/10
*********************************************************************/
int init_capture(const char* filter_string, const char* dev, pcap_t* &cap_handle)
{
ASSERT_EX(filter_string != NULL && dev != NULL, ERROR_INVALID_PARAM);
char errbuf[PCAP_ERRBUF_SIZE] = {0};
const char* filter_exp = filter_string; // 过滤字符串
struct bpf_program bpf_prg = {0}; // 编译生成的过滤程序
bpf_u_int32 mask = 0; // 子网掩码
bpf_u_int32 net = 0; // IP
// 检查捕获设备(网卡)
if (0 == strlen(dev))
{
// 如果未指定网卡, 查找可用网卡设备, 取第一个可用的网卡用于捕获
dev = pcap_lookupdev(errbuf);
if (NULL == dev)
{
printf("[%s]Couldn't find default device: %s\n", __FUNCTION__, errbuf);
return PCAP_ERROR_DEVICE;
}
}
// 根据捕获设备(网卡)查找其对应的IP和子网掩码
if (-1 == pcap_lookupnet(dev, &net, &mask, errbuf))
{
printf("[%s]Couldn't get netmask for device %s: %s\n", __FUNCTION__, dev, errbuf);
net = 0;
mask = 0;
}
// 打印用于捕获的参数信息
printf("[%s]Device: %s\n", __FUNCTION__, dev);
printf("[%s]Filter expression: %s\n", __FUNCTION__, filter_exp);
// 打开用于捕获的网络设备
cap_handle = pcap_open_live_ex(dev, SNAP_LEN, g_promisc, 1000, errbuf);
if (NULL == cap_handle)
{
printf("[%s]Couldn't open device %s: %s\n", __FUNCTION__, dev, errbuf);
return ERROR_OPEN_FILE;
}
// 编译过滤字符串到过滤程序中
if (-1 == pcap_compile(cap_handle, &bpf_prg, filter_exp, 0, net))
{
printf("[%s]Couldn't parse filter %s: %s\n", __FUNCTION__, filter_exp, pcap_geterr(cap_handle));
pcap_freecode(&bpf_prg);
pcap_close(cap_handle);
return PCAP_ERROR_COMPILE;
}
// 制定一个过滤程序
if (-1 == pcap_setfilter(cap_handle, &bpf_prg))
{
printf("[%s]Couldn't install filter %s: %s\n", __FUNCTION__,
filter_exp, pcap_geterr(cap_handle));
pcap_freecode(&bpf_prg);
pcap_close(cap_handle);
return PCAP_ERROR_SETFILTER;
}
return OK;
}
/*********************************************************************
* 函数名称:got_packet
* 函数描述:pcap_loop的回调函数, 对捕获到的数据包进行处理(转存、分析丢包)
* 输入参数:args u_char* 从函数pcap_loop()传递过来的参数
(即pcap_loop的最后一个参数)
* header struct pcap_pkthdr * 捕获到的数据包基本信息,包括时间,长度等信息
* packet u_char* 捕获到的数据包的内容
* 输出参数:N/A
* 返 回 值:N/A
* 创 建:Jaxon Jia (sz082169)
* 创建日期:2012/09/05
*********************************************************************/
void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet)
{
ASSERT(NULL != args && NULL != header && NULL != packet);
CAPTURE_PARAM* cap_param = (CAPTURE_PARAM*)args;
if (header->caplen != header->len)
{
printf("[%s]Frame length != Capture length, now will be exit.\n", __FUNCTION__);
pcap_breakloop(cap_param->handle);
return ;
}
signal(SIGINT, sighander); //ctrl+c的处理
if (cap_param->is_first_run && 0 == cap_param->offset)
{
cap_param->is_first_run = 0;
//自动分析RTP头长度 2013/1/24
cap_param->offset = header->len % TS_PKT_LEN - PACKET_HEADER_LEN;
printf("[%s]Auto skip RTP header [offset]: %d Byte.\n", __FUNCTION__, cap_param->offset);
}
int tslen = header->caplen - PACKET_HEADER_LEN - cap_param->offset;
static u_int64 sum_gotbytes = 0;
static u_int64 last_sum_gotbytes = 0;
static struct timeval ts_begin = {0};
sum_gotbytes += header->caplen;
if (time_interval(ts_begin, ONE_SECOND))
{// 1s输出一次信息
char cur_time_str[TIME_STRING_LEN] = {0};
get_time_str(cur_time_str);
printf("%s[%s]bitrate: %.3f Mbps. RTP header: %u Byte\n", cur_time_str, __FUNCTION__,
(sum_gotbytes-last_sum_gotbytes)*8.0/1024/1024, cap_param->offset);
last_sum_gotbytes = sum_gotbytes;
}
u_int32 cap_us = cap_param->minute * 60 * ONE_SECOND;// 捕获定时时间, 单位纳秒
if (g_exit || (cap_param->minute > 0 && time_interval(cap_param->cap_begin, cap_us)))
{// 按下ctrl+c或抓包定时时间满,终止抓包
pcap_breakloop(cap_param->handle);
printf("[%s]Got bytes: %.3f MB.\n", __FUNCTION__, ((double)sum_gotbytes)/1024/1024);
return ;
}
if (cap_param->workmod & DUMP2CAP)
{// 向调用pcap_dump_open()函数打开的文件输出一个数据包
pcap_dump((u_char*)cap_param->fp_capfile, header, packet);
//printf("[%s]call pcap_dump.\n", __FUNCTION__);
}
else if (cap_param->workmod & DUMP2TS)
{
// 跳过RTP头将数据写入MPG文件
int writelen = fwrite(packet + PACKET_HEADER_LEN + cap_param->offset,
1, tslen, (FILE*)cap_param->fp_capfile);
if(writelen != tslen)
{
printf("[%s]Write error. Write length %d < %d, now will be exit.\n", __FUNCTION__, writelen, tslen);
//fclose((FILE*)cap_param->fp_capfile);
pcap_breakloop(cap_param->handle);
return ;
}
}
if (cap_param->workmod & CHECK_LOSS)
{
int ret = 0;
if (cap_param->offset > 0)
{// 含有RTP包头
ret = check_rtp_sn(packet + PACKET_HEADER_LEN, cap_param->last_rtp_sn, cap_param->fp_logfile);
}
if (0 == ret)
{
check_ts_continuity_counter(packet + PACKET_HEADER_LEN + cap_param->offset,
tslen, cap_param->fp_logfile);
}
}
fflush(cap_param->fp_logfile);
fflush(stdout);
}
/*********************************************************************
* 函数名称:capture
* 函数描述:从指定IP地址和端口接收流, 然后将接收到的流转存成cap文件或ts文件
* 分析RTP是否丢包及ts裸流是否丢包, 或边存文件边分析丢包。
* 输入参数:ip char* 组播流的IP地址
* port unsigned short 组播流的端口号
* workmod u_int32 指定仅抓包, 仅检查丢包, 还是两者同时进行
* outfile char* 输出文件路径
* logfile char* 发现丢包时记录丢包的log文件名
* dev char* 用于捕获数据包的网络设备名, 如eth0
* offset u_int32 数据部分需要进行偏移的长度(跳过RTP头)
* minute u_int32 抓包定时的时间,取0认为不限制时间
* 输出参数:N/A
* 返 回 值:正常返回0,异常返回负值
* 创 建:Jaxon Jia (sz082169)
* 创建日期:2012/09/05
* 修改日期:2012/10/10
*********************************************************************/
int capture(const char* ip, unsigned short port, const char* outfile, u_int32 workmod,
const char* logfile, const char* dev, u_int32 offset, u_int32 minute)
{
ASSERT_EX(NULL != ip && NULL != outfile && NULL != logfile
&& NULL != dev && offset <= MAX_OFFSET_LEN, ERROR_INVALID_PARAM);
char errbuf[PCAP_ERRBUF_SIZE] = {0};
int sock = -1;
if (is_multicast_ip(ip))
{
for (int i = 0; i < MAX_RETRY_TIMES; i++)
{
//Add multicast group
if (0 == sock_recv(sock, ip, port, errbuf, dev))
{
break;
}
}
if (sock < 0)
{
printf("[%s]Add multicast group failure!", __FUNCTION__);
return ERROR_SOCK_CREAT;
}
}
char filter_exp[FILTER_EXP_LEN] = {0}; // 过滤字符串
int num = sprintf(filter_exp, "udp and dst %s", ip);
if (ANY_PORT != port)
{
sprintf(filter_exp+num, " and port %d", port);
}
CAPTURE_PARAM cap_param = {0};
cap_param.workmod = workmod;
cap_param.offset = offset;
cap_param.last_rtp_sn = -1;
cap_param.minute = minute;
cap_param.is_first_run = 1;
int res = init_capture(filter_exp, dev, cap_param.handle);
if (res < 0)
{
close(sock);
printf("[%s]Init Capture Error.\n", __FUNCTION__);
return res;
}
if (workmod & DUMP2CAP)
{// 需要转存成CAP文件
if (NULL == (cap_param.fp_capfile = (void*)pcap_dump_open(cap_param.handle, outfile)))
{// 打开文件失败的处理
printf("[%s]Couldn't open dump file: %s.\n", __FUNCTION__,
pcap_geterr(cap_param.handle));
close(sock);
pcap_close(cap_param.handle);
return ERROR_OPEN_FILE;
}
printf("[%s]Stream will dump to: %s.\n", __FUNCTION__, outfile);
}
else if (workmod & DUMP2TS)
{// 需要转存成TS文件
if (NULL == (cap_param.fp_capfile = (void*)fopen(outfile,"wb")))
{// 打开文件失败的处理
printf("[%s]Couldn't open dump file %s.\n", __FUNCTION__, outfile);
close(sock);
pcap_close(cap_param.handle);
return ERROR_OPEN_FILE;
}
printf("[%s]Stream will dump to: %s.\n", __FUNCTION__, outfile);
}
if (workmod & CHECK_LOSS)
{// 需要进行丢包检查(RTP连续性和TS连续性)
char new_logfile[MAX_PATH] = {0};
snprintf(new_logfile, sizeof(new_logfile), "%s_%d_%s", ip, port, logfile);
if (NULL == (cap_param.fp_logfile = fopen(logfile, "w")))
{// 打开文件失败的处理
printf("[%s]Couldn't open log file of packet loss: %s.\n", __FUNCTION__, new_logfile);
close(sock);
if (workmod & DUMP2CAP)
{
pcap_dump_close((pcap_dumper_t*)cap_param.fp_capfile);
}
else if (workmod & DUMP2TS)
{
fclose((FILE*)cap_param.fp_capfile);
}
pcap_close(cap_param.handle);
fclose(cap_param.fp_logfile);
return ERROR_OPEN_FILE;
}
printf("[%s]Check packet loss. The packet loss info will write to: %s.\n", __FUNCTION__, new_logfile);
}
if (cap_param.minute > 0)
{
gettimeofday(&(cap_param.cap_begin), 0); // 获取抓包开始时间
}
// 无限循环捕获,指定回调函数got_packet来处理数据包
pcap_loop(cap_param.handle, -1, got_packet, (u_char*)&cap_param);
// 下面是关闭和清理操作
close(sock);
if (workmod & DUMP2CAP)
{
pcap_dump_close((pcap_dumper_t*)cap_param.fp_capfile);
}
else if (workmod & DUMP2TS)
{
fclose((FILE*)cap_param.fp_capfile);
}
cap_param.fp_capfile = NULL;
if (workmod & CHECK_LOSS)
{
fclose(cap_param.fp_logfile);
cap_param.fp_logfile = NULL;
}
pcap_close(cap_param.handle);
cap_param.handle = NULL;
printf("[%s]capture complete.\n", __FUNCTION__);
return OK;
}
/*********************************************************************
* 函数名称:check_offline_file
* 函数描述:检查cap文件或ts文件是否存在RTP丢包和ts丢包
或检查mpg文件是否存在ts丢包
* 输入参数:filename char* cap文件名或mpg文件名
* logfile char* 发现丢包时记录丢包的log文件名
* offset u_int32 数据部分需要进行偏移的长度(跳过RTP头)
* max_interval long 最大包间隔
* 输出参数:N/A
* 返 回 值:正常返回0,异常返回负值
* 创 建:Jaxon Jia (sz082169)
* 创建日期:2012/09/06
*********************************************************************/
int check_offline_file(const char* filename, const char* logfile, u_int32 offset, long max_interval)
{
ASSERT_EX(NULL != filename && NULL != logfile && offset <= MAX_OFFSET_LEN, ERROR_INVALID_PARAM);
FILE *fp_logfile = fopen(logfile,"w");
if (NULL == fp_logfile)
{
printf("[%s]Can not open the log file: %s. errno %d\n", __FUNCTION__, logfile, errno);
return ERROR_OPEN_FILE;
}
printf("[%s]Check packet loss. The packet loss info will write to: %s.\n", __FUNCTION__, logfile);
signal(SIGINT, sighander); //ctrl+c的处理
bool is_capfile = string_ends_with(filename, ".cap") || string_ends_with(filename, ".pcap") || string_ends_with(filename, ".pcapng");//是否cap文件
printf("[%s]file <%s> is cap: %s\n", __FUNCTION__, filename, is_capfile ? "true" : "false");
struct timeval ts_begin = {0};
double sum_gotbytes = 0;
if (is_capfile)
{// cap文件进行丢包检查处理
char errbuf[ERR_BUF_SIZE] = {0};
struct pcap_pkthdr *header = NULL;
const unsigned char *pkt_data = NULL;
pcap_t *fp = pcap_open_offline(filename, errbuf);
if(fp == NULL)
{
printf("[%s]Can not open the file: %s. error %d\n", __FUNCTION__, filename, errbuf);
fclose(fp_logfile);
return ERROR_OPEN_FILE;
}
int res = 0;
int last_rtp_sn = -1;
int is_first_run = 1;//首次运行标识
unsigned long cap_pkt_num = 0;//cap文件中的包序号
char src_ip_port[MAX_IP_LEN+MAX_PORT_LEN] = {0}; //源IP:Port
struct timeval last_pkt_time = {0}; // 上一个包的时间
while (!g_exit && (res = pcap_next_ex(fp, &header, &pkt_data)) >= 0)
{// 调用pcap函数pcap_next_ex获取下一个数据包
++cap_pkt_num;
if (is_first_run)
{
is_first_run = 0;
//获取源IP:Port
strcpy(src_ip_port, get_src_ip_port(pkt_data));
printf("[%s]source ip:port = %s.\n", __FUNCTION__, src_ip_port);
if (0 == offset)
{//自动分析RTP头长度 2013/1/24
offset = header->len % TS_PKT_LEN - PACKET_HEADER_LEN;
printf("[%s]Auto skip RTP header [offset]: %d Byte.\n", __FUNCTION__, offset);
}
// 保存首包时戳
memmove(&last_pkt_time, &(header->ts), sizeof(last_pkt_time));
}
else if (strcmp(src_ip_port, get_src_ip_port(pkt_data)) != 0)
{//跳过混淆包
printf("[%s][CapNo=%u]src ip:port not match, skip it.\n", __FUNCTION__, cap_pkt_num);
continue;
}
if (header->caplen > PACKET_HEADER_LEN + offset)
{
int ret = 0;
if (offset > 0)
{// 含有RTP包头
ret = check_rtp_sn(pkt_data + PACKET_HEADER_LEN, last_rtp_sn, fp_logfile, cap_pkt_num);
}
if (0 == ret && header->caplen == header->len)
{
check_ts_continuity_counter(pkt_data + PACKET_HEADER_LEN + offset,
header->caplen - PACKET_HEADER_LEN - offset, fp_logfile, cap_pkt_num);
}
sum_gotbytes += header->caplen;
if (time_interval(ts_begin, ONE_SECOND))
{// 1s输出一次信息
printf("[%s]check file process: %.3f MB.\n", __FUNCTION__, sum_gotbytes/1024/1024);
}
}
// 检查包间隔
if (max_interval > 0)
{
long interval = gettsgap(header->ts, last_pkt_time);
if (interval > max_interval)
{
printf("[%s][PktNum=%ld]Find packet interval<%ld> is greater than max interval<%ld>!!\n",
__FUNCTION__, cap_pkt_num, interval, max_interval);
fprintf(fp_logfile, "[%s][PktNum=%ld]Find packet interval<%ld> is greater than max interval<%ld>!!\n",
__FUNCTION__, cap_pkt_num, interval, max_interval);
}
// 更新last_pkt_time
memmove(&last_pkt_time, &(header->ts), sizeof(last_pkt_time));
}
}// end of while
if (-1 == res)
{
printf("[%s][PktNum=%ld]Error reading the packets: %s.\n",
__FUNCTION__, cap_pkt_num, pcap_geterr(fp));
return ERROR_READ_FILE;
}
pcap_close(fp);
}// end of if (is_capfile)
else
{// ts文件丢包检查处理
FILE* fp = fopen(filename, "rb");
if (NULL == fp)
{
printf("[%s]Can not open the log file: %s. errno %d\n", __FUNCTION__, filename, errno);
fclose(fp_logfile);
return ERROR_OPEN_FILE;
}
u_char pkt_data[TS_FRAME_LEN] = {0};
while (!g_exit && (0 == feof(fp)))
{
int readlen = fread(pkt_data, 1, TS_FRAME_LEN, fp);
check_ts_continuity_counter(pkt_data, readlen, fp_logfile);
sum_gotbytes += readlen;
if (time_interval(ts_begin, ONE_SECOND))
{// 1s输出一次信息
printf("[%s]check file process: %.3f MB.\n", __FUNCTION__,
sum_gotbytes/1024/1024);
}
}
fclose(fp);
}
printf("[%s]Check packet loss of file %s complete. sum_gotbytes<%.2fMB>\n",
__FUNCTION__, filename, sum_gotbytes/1024/1024);
fclose(fp_logfile);
return OK;
}
char* get_src_ip_port(const unsigned char *pkt_data, char* ip, u_int16* port)
{
static char ip_port[MAX_IP_LEN + MAX_PORT_LEN] = {0};
u_int32 src_ip = *(u_int32*)(pkt_data + ETHERNET_HEADER_LEN + 12);//IP协议头中的源IP
inet_ntop(AF_INET, &src_ip, ip_port, MAX_IP_LEN);
u_int16 src_port = *(u_int16*)(pkt_data + ETHERNET_HEADER_LEN + IP_HEADER_LEN);//UDP头中的源端口
src_port = ntohs(src_port);
if (NULL != ip) strcpy(ip, ip_port);
if (NULL != port) *port = src_port;
sprintf(ip_port, "%s:%u", ip_port, src_port);
return ip_port;
}
/*********************************************************************
* 函数名称:print_ip
* 函数描述:指定一个组播IP地址和端口,获取对应的源IP和端口
* 输入参数:ip char* 组播流的IP地址
* port unsigned short 组播流的端口号
* dev char* 用于捕获数据包的网络设备名, 如eth0
* 输出参数:N/A
* 返 回 值:正常返回0,异常返回-1
* 创 建:Jaxon Jia (sz082169)
* 创建日期:2012/09/24
* 修改日期:2012/10/10
*********************************************************************/
int print_ip(const char* ip, unsigned short port, const char* dev)
{
ASSERT_EX(NULL != ip, ERROR_INVALID_PARAM);
char errbuf[PCAP_ERRBUF_SIZE] = {0};
int sock = -1;
if (is_multicast_ip(ip))
{
for (int i = 0; i < MAX_RETRY_TIMES; i++)
{
//Add multicast group
if (0 == sock_recv(sock, ip, port, errbuf, dev))
{
break;
}
}
if (sock < 0)
{
printf("[%s]Add multicast group failure!", __FUNCTION__);
return ERROR_SOCK_CREAT;
}
}
char filter_exp[FILTER_EXP_LEN] = {0}; // 过滤字符串
int num = sprintf(filter_exp, "udp and dst %s", ip);
if (ANY_PORT != port)
{
sprintf(filter_exp+num, " and port %d", port);
}
pcap_t* cap_handle = NULL;
int res = init_capture(filter_exp, dev, cap_handle);
if (res < 0)
{
close(sock);
printf("[%s]Init Capture Error.\n", __FUNCTION__);
return res;
}
struct pcap_pkthdr *header = NULL;
const unsigned char *pkt_data = NULL;
if (pcap_next_ex( cap_handle, &header, &pkt_data) > 0)
{// 获取到一个包后, 取其中的源IP和源端口
if (header->caplen >= ETHERNET_HEADER_LEN + IP_HEADER_LEN)
{
printf("\n[Source] %s, [Destination] %s:%d\n\n", get_src_ip_port(pkt_data), ip, port);
}
}
else
{
printf("\n[ERROR]Can't receive packet!\n\n");
}
pcap_close(cap_handle);
cap_handle = NULL;
close(sock);
return OK;
}
/*********************************************************************
* 函数名称:is_loss_pkt
* 函数描述:是否为应随机丢掉的数据包
* 输入参数:rate unsigned short 丢包率百分比
* 输出参数:N/A
* 返 回 值:是返回true,否返回false
*********************************************************************/
bool is_loss_pkt(u_int16 rate)
{
static int recv_count = 0; //收包计数
static int pkt_loss_arr[MAX_RAND_BUF_LEN] = {0};//丢包序号数组
if (rate > 0 && recv_count == 0)
{//重新生成丢包序列
srand(time(NULL));; //随机数种子产生
memset(&pkt_loss_arr, 0, sizeof(pkt_loss_arr));
for (int i = 0; i < rate; i++)
{
pkt_loss_arr[rand()%100] = 1; //生成[0,99]范围内的随机数
}
}
recv_count %= 100;//recv_count值为0-99
return pkt_loss_arr[recv_count++];
}
/*********************************************************************
* 函数名称:udp_transfer
* 函数描述:组播转发,将指定组播源数据发往新的IP地址和端口
* 输入参数:srcip char* 源IP
* srcport u_int16 源端口
* dstip char* 新的组播或单播IP地址
* dstport u_int16 新的端口
* offset u_int32 转发时跳过的偏移长度
* pkt_loss_rate u_int16 丢包率
* 输出参数:N/A
* 返 回 值:正常返回0,异常返回负值
*********************************************************************/
int udp_transfer(char* srcip, u_int16 srcport, char* dstip, u_int16 dstport, u_int16 offset, u_int16 pkt_loss_rate, const char* dev)
{
int res = 0;
char errbuf[ERR_BUF_SIZE] = {0};
sockaddr_in fromaddr = {0}, toaddr = {0};
socklen_t fromlen = sizeof(fromaddr), tolen = 0;
printf("Prepare to receive data from: %s:%d and transfer to: %s:%d\n", srcip, srcport, dstip, dstport);
signal(SIGINT, sighander);
toaddr.sin_family = AF_INET;
toaddr.sin_port = htons(dstport);
toaddr.sin_addr.s_addr = inet_addr(dstip);
tolen = sizeof(toaddr);
int sockrecv = -1, socksend = -1;
res = sock_recv(sockrecv, srcip, srcport, errbuf, dev);
if(res)
{
printf("Error: %s\n", errbuf);
return ERROR_SOCK_CREAT;
}
res = sock_send(socksend, errbuf);
if(res)
{
printf("Error: %s\n", errbuf);
return ERROR_SOCK_CREAT;
}
char recvbuf[MAX_PACKET_SIZE] = {0};
struct timeval ts = {0};
fd_set readset;
long relaybytes = 0, relaytimegap = 0;
struct timeval ts_begin = {0};
while(!g_exit)
{
FD_ZERO(&readset);
FD_SET(sockrecv, &readset);
ts.tv_sec = 3;
ts.tv_usec = 0;
res = select(FD_SETSIZE, &readset, NULL, NULL, &ts);
if(res == 0)
{
printf("Recivie udp packet timeout.\n");
}
else if(res > 0)
{
res = recvfrom(sockrecv, recvbuf, MAX_PACKET_SIZE, 0, (struct sockaddr*)&fromaddr, &fromlen);
if(res > offset)
{
if (pkt_loss_rate > 0 && is_loss_pkt(pkt_loss_rate))
{//随机丢包
if ((res % TS_PKT_LEN) > 0)
{//is rtp
int rtp_seq = ntohs(*(short*)((char*)recvbuf + 2));//real sequence
printf("[%s]loss RTP pkt [%d bytes, seq = %d].\n", __FUNCTION__, res, rtp_seq);
}
else
printf("[%s]loss pkt [%d bytes].\n", __FUNCTION__, res);
}
else
{
res = sendto(socksend, recvbuf + offset, res - offset, 0, (struct sockaddr*)&toaddr, tolen);
if(res < 0)
{
printf("Error at send data.\n");
}
relaybytes += res;
}
if (time_interval(ts_begin, ONE_SECOND))
{// 1s输出一次信息
printf("Relay bitrate: %.3f Mbps\n", (double)(relaybytes * 8) / ONE_SECOND);
relaybytes = 0;
}
}
else
{
printf("Receive retrun %d.\n\n", res);
}
}
else
{
if(!g_exit)
printf("\nSelect error.\n");
}
}
close(sockrecv);
close(socksend);
return OK;
}
int print_usage(char* program_name)
{
printf("Usage: ");
printf("%s [ACTION] [OPTION]\n", program_name);
printf("ACTION:\n");
printf(" --catch,-c Capture a stream as a cap file or ts file.\n");
printf(" --send,-s Send the cap file to multicast/unicast address.\n");
printf(" --convert,-n Convert cap file to ts stream file.\n");
printf(" --analyze,-a Analyze a cap file or ts file or a stream exist packet loss or not.\n");
printf(" --print-ip,-P Get the source ip and port of multicast address.\n");
printf(" --transfer,-S Transfer a udp stream from multicast/unicast address to another address.\n");
printf("\nOPTION:\n");
printf(" --inputfile,-i filepath Input file for 'send' or 'convert' or 'analyze'\n");
printf(" --multicast,-m ip:port Multicast/unicast address and port supply to 'catch' or 'analyze'\n");
printf(" --out2cap,-o output_file_path Save 'catch' data to a cap file\n");
printf(" --out2ts,-t output_file_path Save 'catch' data or 'convert' a cap file to a ts file\n");
printf(" --losslog,-l loss_logfile_path Record packet loss info to this file\n");
printf(" --offset,-f offset Video offset in udp data\n");
printf(" --device,-d nic_name The device used for capture (e.g. eth0, eth1, bond0)\n");
printf(" --timer,-T minute Set the 'catch' time('0' indicates no time limit)\n");
printf(" --newaddress,-w new_ip:new_port New address and port supply to 'transfer'\n");
printf(" --lossrate,-R pkt_loss_rate Packet loss rate, default is 0%, the value range is 0 to 100.\n");
printf(" --interval,-I pkt_interval Max packet interval, unit: us(1s = 1000ms = 1000000us).\n\n");
printf(" --repeat,-r Repeat to play the cap file\n");
printf(" --background,-b Run the programe in background(Linux Only)\n");
printf(" --not_promisc,-p Don't put the interface into promiscuous mode\n\n");
printf(" --version,-v Get version of this program\n");
printf(" --help,-h Print usage of this program\n");
printf("\nEXAMPLE:");
printf("\n 1.Dump a stream as a cap file: \n");
printf(" ./ur --catch -m 230.9.9.103:6000 -o 1.cap [-l loss.log] [-f 24] [-d eth1] [-T 5] [-b]\n");
printf("\n 2.Dump a stream as a mpg file: \n");
printf(" ./ur --catch -m 230.9.9.103:6000 -t 1.mpg [-l loss.log] [-f 24] [-d eth1] [-T 5] [-b]\n");
printf("\n 3.Send the cap file to multicast address: \n");
printf(" ./ur --send -i 1.cap -m 230.9.9.103:6000 [-f 24] [-r] [-R 3] [-b]\n");
printf("\n 4.Convert a cap file to ts file: \n");
printf(" ./ur --convert -i 1.cap -t 1.mpg [-l loss.log] [-f 24] [-b]\n");
printf("\n 5.Analyze a cap file or ts file or a stream whether the packet loss occurs: \n");
printf(" ./ur --analyze -i 1.cap [-l loss.log] [-f 24] [-I 1000] [-b]\n");
printf(" ./ur --analyze -i 1.mpg [-l loss.log] [-b]\n");
printf(" ./ur --analyze -m 230.9.9.103:6000 -l loss.log [-f 24] [-d eth1] [-b]\n");
printf("\n 6.Get the source ip and port of multicast address: \n");
printf(" ./ur --print-ip -m 230.9.9.103:6000 [-d eth1]\n");
printf("\n 7.Transfer a udp stream from multicast address to unicast address: \n");
printf(" ./ur --transfer -m 230.9.9.103:6000 -w 172.19.148.236:1234\n");
printf("\n 8.Transfer a udp stream from a multicast to another multicast: \n");
printf(" ./ur --transfer -m 230.9.9.103:6000 -w 239.1.1.100:1234\n");
printf("\n 9.Transfer a udp stream from a multicast to another multicast with 3%% pkt loss: \n");
printf(" ./ur --transfer -m 230.9.9.103:6000 -w 239.1.1.100:1234 -R 3\n");
return OK;
}
void sighander(int sig)
{
g_exit = true;
g_repeat = false;
signal(SIGINT, SIG_DFL);
}
int main(int argc, char** argv)
{
char ip[MAX_IP_LEN] = {0};
u_int32 port = DEFAULT_PORT;
char action = 0;
int c, option_index = 0;
u_int32 offset = 0;
u_int32 workmod = 0;
u_int32 minute = 0;
char dev[MAX_PATH] = {0};
char input_file[MAX_PATH] = {0};
char output_file[MAX_PATH] = {0};
char loss_logfile[MAX_PATH] = {"loss.log"};
bool analyze_online = false;
int res = 0;
char new_ip[MAX_IP_LEN] = {0};
u_int32 new_port = DEFAULT_PORT;
u_int32 pkt_loss_rate = 0;
long pkt_max_interval = 0;// us
static struct option long_options[] =
{
// 六大类功能: 抓cap或mpg、发cap、cap转mpg、分析丢包、获取组播源的源IP地址、组播转发
{"catch", no_argument, NULL, 'c'},
{"send", no_argument, NULL, 's'},
{"convert", no_argument, NULL, 'n'},
{"analyze", no_argument, NULL, 'a'},
{"print-ip", no_argument, NULL, 'P'},
{"transfer", no_argument, NULL, 'S'},
// 组合参数
{"inputfile", required_argument, NULL, 'i'},
{"multicast", required_argument, NULL, 'm'},
{"out2cap", required_argument, NULL, 'o'},
{"out2ts", required_argument, NULL, 't'},
{"losslog", required_argument, NULL, 'l'},
{"offset", required_argument, NULL, 'f'},
{"device", required_argument, NULL, 'd'},
{"timer", required_argument, NULL, 'T'},
{"newaddress", required_argument, NULL, 'w'},
{"lossrate", required_argument, NULL, 'R'},
{"interval", required_argument, NULL, 'I'},
{"repeat", no_argument, NULL, 'r'},
{"background", no_argument, NULL, 'b'},
{"not_promisc", no_argument, NULL, 'p'},
{"version", no_argument, NULL, 'v'},
{"help", no_argument, NULL, 'h'},
{0, 0, 0, 0}
};
if (argc < 2)
{
print_usage(argv[0]);
exit(0);
}
while ((c = getopt_long(argc, argv, "csnaPSi:m:o:t:l:f:d:T:w:R:I:rbpvh", long_options, &option_index)) != -1)
{
switch(c)
{
case 'c': //catch
case 's': //send
case 'n': //convert
case 'a': //analyze
case 'P': //print-ip
case 'S': //transfer
action = c;
break;
case 'i': //inputfile
strcpy(input_file, optarg);
break;
case 'm': //multicast
analyze_online = true;
{// 加{}防止交叉初始化错误
// 从IP:Port格式的字符串中提取IP和Port[2012/09/07 JaxonJia add]
char* ch_colon = strchr(optarg, ':');
if (NULL != ch_colon)
{
*ch_colon = 0;
port = (ANY_PORT == ch_colon[1]) ? ANY_PORT : atoi(ch_colon + 1);
}
if (port < 0)
{
printf("Invalid param [-m ip:port].\n");
exit(0);
}
strcpy(ip, optarg);
}
break;
case 'o': //out2cap
workmod |= DUMP2CAP;
strcpy(output_file, optarg);
break;
case 't': //out2ts
workmod |= DUMP2TS;
strcpy(output_file, optarg);
break;
case 'l': //losslog
workmod |= CHECK_LOSS;
strcpy(loss_logfile, optarg);
break;
case 'f': //offset
offset = atoi(optarg);
if (offset < 0 || offset > MAX_OFFSET_LEN)
{
printf("Invalid param [-f offset].\n");
exit(0);
}
break;
case 'd': //device
strcpy(dev, optarg);
break;
case 'T': //Timer
minute = atoi(optarg);
break;
case 'w': //newaddress
{// 加{}防止交叉初始化错误
// 从IP:Port格式的字符串中提取IP和Port[2012/09/07 JaxonJia add]
char* ch_colon = strchr(optarg, ':');
new_port = (NULL == ch_colon) ? DEFAULT_PORT : (*ch_colon = 0, atoi(ch_colon + 1));
if (new_port < 0)
{
printf("Invalid param [-w ip:port].\n");
exit(0);
}
strcpy(new_ip, optarg);
}
break;
case 'R': //lossrate
pkt_loss_rate = atoi(optarg);
break;
case 'I': //pkt max interval
pkt_max_interval = atoi(optarg);
break;
case 'r': //repeat
g_repeat = true;
printf("Choose repeat to send the file.\n");
break;
case 'p': //not_promisc
g_promisc = 0;
printf("Not use promiscuous mode.\n");
break;
case 'b': //background
printf("ur run at background. The log will write to %s\n", LOG_FILE );
freopen(LOG_FILE, "w", stdout); // 将标准输出重定向到LOG_FILE
if (daemon(1, 1) < 0)
{
printf("UR can not run at background.\n");
}
break;
case 'v': //version
printf("\t%s \n\tVersion: %s \n\tBuild time:%s %s\n",
argv[0], MSVERSION, __DATE__, __TIME__);
exit(0);
break;
case 'h': //help
print_usage(argv[0]);
exit(0);
case ':':
printf("Option [%c] needs an argument\n", optopt);
exit(0);
case '?': /* getopt_long already printed an error message. */
break;
default:
break;
}
}
switch(action)
{
case 'c': //catch
if (0 == strlen(output_file) || 0 == strlen(ip))
{// 漏掉输入参数时退出程序
printf("\nLack of input parameters!\n");
exit(0);
}
res = capture(ip, port, output_file, workmod, loss_logfile, dev, offset, minute);
printf("\nCapture return:%d.\n", res);
break;
case 's': //send
if (0 == strlen(input_file) || 0 == strlen(ip))
{// 漏掉输入参数时退出程序
printf("\nLack of input parameters!\n");
exit(0);
}
do
{
res = udp_replay(input_file, ip, port, offset, pkt_loss_rate);
workmod = 0;//仅分析一次即可
printf("\nUdpReplay return %d.\n", res);
if(res)
break;
}while(g_repeat);
break;
// [2012/09/05 JaxonJia add]
case 'n': //convert
if (0 == strlen(input_file) || 0 == strlen(output_file))
{// 漏掉输入参数时退出程序
printf("\nLack of input parameters!\n");
exit(0);
}
res = cap_convert(input_file, output_file, workmod, loss_logfile, offset);
printf("\nCapConvert return:%d.\n", res);
break;
case 'a': //analyze
if (analyze_online)
{
if (0 == strlen(ip))
{// 漏掉输入参数时退出程序
printf("\nLack of input parameters!\n");
exit(0);
}
if (strlen(loss_logfile) > 0) workmod |= CHECK_LOSS;
res = capture(ip, port, output_file, workmod, loss_logfile, dev, offset);
printf("\nCapture return:%d.\n", res);
}
else
{
if (0 == strlen(input_file) || 0 == strlen(loss_logfile))
{// 漏掉输入参数时退出程序
printf("\nLack of input parameters!\n");
exit(0);
}
res = check_offline_file(input_file, loss_logfile, offset, pkt_max_interval);
printf("\nCheckOfflineFile return:%d.\n", res);
}
break;
case 'P': //print-ip
if (0 == strlen(ip))
{// 漏掉输入参数时退出程序
printf("\nLack of input parameters!\n");
exit(0);
}
res = print_ip(ip, port, dev);
break;
case 'S': //transfer
if (0 == strlen(ip) || 0 == strlen(new_ip))
{// 漏掉输入参数时退出程序
printf("\nLack of input parameters!\n");
exit(0);
}
res = udp_transfer(ip, port, new_ip, new_port, offset, pkt_loss_rate, dev);
break;
default:
break;
}// end of switch(action)
}
//common interface for create socket
in_addr_t get_addr_by_interface(const char* interface)
{
int fd = -1;
struct ifreq ifr = {0};
if (NULL == interface || 0 == strlen(interface))
{
return INADDR_ANY;
}
fd = socket(AF_INET, SOCK_DGRAM, 0);
ifr.ifr_addr.sa_family = AF_INET;
strncpy(ifr.ifr_name, interface, IFNAMSIZ-1);
ioctl(fd, SIOCGIFADDR, &ifr);
const char *ip = inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr);
printf("[%s] Use local ip: %s(%s)\n", __FUNCTION__, ip, interface);
return ((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr.s_addr;
}
int sock_recv(int &sock, const char* ip, u_int16 port, char* errbuf, const char*dev)
{
ASSERT_EX(NULL != ip && NULL != errbuf, ERROR_INVALID_PARAM);
struct ip_mreq mreq = {0};
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
sprintf(errbuf, "Create socket error.\n");
return ERROR_SOCK_CREAT;
}
int reuse = 1;
int res = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(int));
if (res < 0)
{
sprintf(errbuf, "Error to set reuse socket\n");
return ERROR_SOCK_SET_OPT;
}
mreq.imr_multiaddr.s_addr = inet_addr(ip);
mreq.imr_interface.s_addr = get_addr_by_interface(dev);
sockaddr_in local = {0};
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip);
res = bind(sock, (sockaddr*)(&local), sizeof(local));
if (res < 0)
{
sprintf(errbuf, "Error to bind socket\n");
return ERROR_SOCK_BINDING;
}
int rcvbufszie = RCV_BUF_SIZE; //2 * 1024 * 1024;
res = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&rcvbufszie, sizeof(int));
if (res < 0)
{
sprintf(errbuf, "Error to set receive buf size.\n");
return ERROR_SOCK_SET_OPT;
}
if (ntohl(mreq.imr_multiaddr.s_addr) > 0xe0000000
&& ntohl(mreq.imr_multiaddr.s_addr) < 0xefffffff)
{
res = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq));
if(res < 0 )
{
sprintf(errbuf, "Error to add membership.\n");
return ERROR_SOCK_SET_OPT;
}
}
return OK;
}
int sock_send(int &sock, char* errbuf)
{
ASSERT_EX(NULL != errbuf, ERROR_INVALID_PARAM);
//create socket
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
sprintf(errbuf, "Create socket error.\n");
return ERROR_SOCK_CREAT;
}
//set send buf size
int sendbufszie = 0;
int res = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&sendbufszie, sizeof(int));
if (res < 0)
{
sprintf(errbuf, "Error to set send buf size.\n");
return ERROR_SOCK_SET_OPT;
}
//set ttl value
unsigned char ttl = 255;
res = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&ttl, sizeof(ttl));
if (res < 0 )
{
sprintf(errbuf, "Error to set ttl .\n");
return ERROR_SOCK_SET_OPT;
}
return OK;
}
//ts1 - ts2
long gettsgap(struct timeval ts1, struct timeval ts2)
{
return (ts1.tv_sec - ts2.tv_sec) * ONE_SECOND + (ts1.tv_usec - ts2.tv_usec);
}
//ts = ts1 -ts2
int gettsgap(struct timeval ts1, struct timeval ts2, struct timeval *ts)
{
ASSERT_EX(NULL != ts, ERROR_INVALID_PARAM);
ts->tv_usec = ts1.tv_usec - ts2.tv_usec;
if (ts->tv_usec < 0)
{
ts->tv_usec += ONE_SECOND;
ts->tv_sec = ts1.tv_sec - ts2.tv_sec - 1;
}
else
{
ts->tv_sec = ts1.tv_sec - ts2.tv_sec;
}
return OK;
}
/*********************************************************************
* 函数名称:time_interval
* 函数描述:提供一定的计时功能,
当前时间与开始时间的时间间隔超出指定时间就返回true,并将开始时间更新为当前时间
(1 s = 10^3 ms = 10^6 us = 10^9 ns 秒毫秒微秒纳秒)
* 输入参数:ts_begin struct timeval 开始时间
* us u_int32 指定时间间隔, 单位纳秒
* 输出参数:N/A
* 返 回 值:如果当前时间与开始时间的间隔超过指定参数值, 返回true, 否则返回false
* 创 建:Jaxon Jia (sz082169)
* 创建日期:2012/09/07
*********************************************************************/
bool time_interval(struct timeval &ts_begin, u_int32 us)
{
if (0 == ts_begin.tv_sec && 0 == ts_begin.tv_usec)
{// 第一次调用time_interval
gettimeofday(&ts_begin, (struct timezone*)NULL);//获取开始时间
}
//获取当前系统时间
struct timeval ts_current = {0};
gettimeofday(&ts_current, (struct timezone*)NULL);
if (gettsgap(ts_current, ts_begin) > us)
{
ts_begin.tv_sec = ts_current.tv_sec;
ts_begin.tv_usec = ts_current.tv_usec;
return true;
}
return false;
}
/*********************************************************************
* 函数名称:get_time_str
* 函数描述:获取日志时间字符串函数,用于日志信息打印时
* 输入参数:N/A
* 输出参数:time_str char* 指向保存返回时间字符串的字符数组
* 返 回 值:N/A
* 创 建:Jaxon Jia (sz082169)
* 创建日期:2012/09/10
*********************************************************************/
void get_time_str(char *time_str)
{
ASSERT(NULL != time_str);
struct timeval timep = {0};
struct tm *ptime = NULL;
time_t tt = {0};
time(&tt);
gettimeofday(&timep, 0);
ptime = localtime(&tt);
sprintf(time_str, "%04d-%02d-%02d %02d:%02d:%02d,%03ld",
(1900+ptime->tm_year),(1+ptime->tm_mon), ptime->tm_mday,
ptime->tm_hour,ptime->tm_min,ptime->tm_sec,timep.tv_usec/1000);
}
// 扩展pcap_open_live接口,增加bufsize设置
pcap_t *pcap_open_live_ex(const char *source, int snaplen, int promisc, int to_ms, char *errbuf, int bufsize)
{
pcap_t *p;
int status;
p = pcap_create(source, errbuf);
if (p == NULL) return (NULL);
status = pcap_set_snaplen(p, snaplen);
if (status < 0) goto fail;
status = pcap_set_promisc(p, promisc);
if (status < 0) goto fail;
status = pcap_set_timeout(p, to_ms);
if (status < 0) goto fail;
status = pcap_set_buffer_size(p, bufsize);
if (status < 0) goto fail;
p->oldstyle = 1;
status = pcap_activate(p);
if (status < 0) goto fail;
return (p);
fail:
if (status == PCAP_ERROR)
snprintf(errbuf, PCAP_ERRBUF_SIZE, "%s: %s", source, p->errbuf);
else if (status == PCAP_ERROR_NO_SUCH_DEVICE || status == PCAP_ERROR_PERM_DENIED || status == PCAP_ERROR_PROMISC_PERM_DENIED)
snprintf(errbuf, PCAP_ERRBUF_SIZE, "%s: %s (%s)", source, pcap_statustostr(status), p->errbuf);
else
snprintf(errbuf, PCAP_ERRBUF_SIZE, "%s: %s", source, pcap_statustostr(status));
pcap_close(p);
return (NULL);
}
int string_ends_with(const char *str, const char *suffix)
{
int str_len = strlen(str);
int suffix_len = strlen(suffix);
return (str_len >= suffix_len) && (0 == strcmp(str + (str_len-suffix_len), suffix));
}
bool is_multicast_ip(const char *ip)
{
if (NULL == ip) return false;
struct ip_mreq mreq = {0};
mreq.imr_multiaddr.s_addr = inet_addr(ip);
return (ntohl(mreq.imr_multiaddr.s_addr) > 0xe0000000
&& ntohl(mreq.imr_multiaddr.s_addr) < 0xefffffff);
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。