在学习nmap源码过程中,发现端口扫描底层是使用libpcap设置过滤表达式来接收响应包,从而判断主机是否在线。官网发现tcpdump底层也是libpcap,加上之前的suricata中也有使用libpcap,所以整理了一下libpcap的使用。
一、源码编译
官方网站:https://www.tcpdump.org/
1、源码解压与编译安装:
(1)tar -xvf libpcap-1.9.1.tar.gz
(2)进入libpcap目录:cd libpcap-1.9.1/
(3)./configure
(4)make
(5)make install
2、安装后目录
(1)头文件
/usr/local/include
/usr/local/include/pcap
(2)动态库
/usr/local/lib/
二、测试示例
参考官方示例:libpcap-1.9.1\testprogs\capturetest.c
参考官方的文档:https://eecs.wsu.edu/~sshaikot/docs/lbpcap/libpcap-tutorial.pdf
//gcc capture2file.c -o capture2file -lpcap
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pcap.h>
#include <unistd.h>
//拷贝抓包过滤表达式
char* copy_argv(char** argv)
{
char** p;
u_int len = 0;
char* buf;
char* src, * dst;
p = argv;
if (*p == 0)
return 0;
while (*p)
len += strlen(*p++) + 1;
buf = (char*)malloc(len);
if (buf == NULL)
{
printf("copy_argv: malloc failed\n");
return 0;
}
p = argv;
dst = buf;
while ((src = *p++) != NULL) {
while ((*dst++ = *src++) != '\0')
;
dst[-1] = ' ';
}
dst[-1] = '\0';
return buf;
}
void capture(u_char* user, const struct pcap_pkthdr* pkthdr, const u_char* packet)
{
static int count = 1;
printf("capture packet<%d> of length %d\n", count, pkthdr->len);
pcap_dump(user, pkthdr, packet); //保存包
count++;
}
int main(int argc, char** argv)
{
char* device;
char* cmdbuf;
char* fname;
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t* descr;
pcap_dumper_t* dunmper;
int op;
int capturecout = 10;
bpf_u_int32 localnet, netmask;
char* netaddr;
char* maskaddr;
struct in_addr addr;
struct bpf_program fcode;
int ret;
opterr = 0;
while ((op = getopt(argc, argv, "i:c:w:")) != -1)
{
switch (op)
{
case 'i':
device = optarg; //网卡名称
break;
case 'c':
capturecout = atoi(optarg); //抓包个数
break;
case 'w':
fname = optarg; //抓包保存文件
break;
default:
break;
}
}
printf("capture device: %s\n", device);
//打印网卡信息
ret = pcap_lookupnet(device, &localnet, &netmask, errbuf);
if (ret == -1)
{
printf("pcap_lookupnet failed:%s\n", errbuf);
exit(1);
}
//打印网卡网络地址
addr.s_addr = localnet;
netaddr = inet_ntoa(addr);
if (netaddr == NULL)
{
printf("inet_ntoa localnet failed\n");
exit(1);
}
printf("localnet address: %s\n", netaddr);
addr.s_addr = netmask;
maskaddr = inet_ntoa(addr);
if (maskaddr == NULL)
{
printf("inet_ntoa maskaddr failed\n");
exit(1);
}
printf("mask address: %s\n", maskaddr);
//pcap_t * pcap_open_live(const char* device, int snaplen, int promisc, int to_ms, char* errbuf)
//snaplen对应int pcap_set_snaplen(pcap_t *p, int snaplen),抓取单个报文的最大长度
//promisc对应int pcap_set_promisc(pcap_t *p, int promisc),设置网卡的混杂模式,0非混杂模式只抓取网卡对应IP的流量
//to_ms对应int pcap_set_timeout(pcap_t *p, int to_ms),设置流量缓存超时时间
descr = pcap_open_live(device, 65535, 0, 1000, errbuf);
if (descr == NULL)
{
printf("pcap_open_live failed: %s\n", errbuf);
exit(1);
}
cmdbuf = copy_argv(&argv[optind]);
if (cmdbuf != NULL)
{
printf("filter expr: %s\n", cmdbuf);
if (pcap_compile(descr, &fcode, cmdbuf, 1, netmask) < 0)
printf("pcap_compile failed: %s\n", pcap_geterr(descr));
if (pcap_setfilter(descr, &fcode) < 0)
printf("pcap_setfilter failed: %s\n", pcap_geterr(descr));
}
//设置保存文件
dunmper = pcap_dump_open(descr, fname);
if (dunmper == NULL)
{
printf("pcap_dump_open failed\n");
exit(1);
}
pcap_loop(descr, capturecout, capture, (u_char*)dunmper);//将dunmper传给capture
pcap_dump_close(dunmper);
pcap_close(descr);
pcap_freecode(&fcode);
free(cmdbuf);
return 0;
}
1、编译:gcc capture2file.c -o capture2file -lpcap
2、测试抓包:./capture2file -i ens33 -c 5 -w /root/home/code/mytest/pcap_test/capture2file.pcap arp
测试示例说明:抓取网卡ens33,只抓5个包,保存到 /root/home/code/mytest/pcap_test/capture2file.pcap中,只抓arp包
3、测试结果:
wireshark查看
三、补充说明
关于pcap_open_live,查看libpcap-1.9.1\pcap.c的源码其实内部就是调用pcap_create、pcap_set_snaplen、pcap_set_promisc、pcap_set_timeout、pcap_activate
pcap_t *
pcap_open_live(const char *device, int snaplen, int promisc, int to_ms, char *errbuf)
{
pcap_t *p;
int status;
#ifdef ENABLE_REMOTE
char host[PCAP_BUF_SIZE + 1];
char port[PCAP_BUF_SIZE + 1];
char name[PCAP_BUF_SIZE + 1];
int srctype;
/*
* A null device name is equivalent to the "any" device -
* which might not be supported on this platform, but
* this means that you'll get a "not supported" error
* rather than, say, a crash when we try to dereference
* the null pointer.
*/
if (device == NULL)
device = "any";
/*
* Retrofit - we have to make older applications compatible with
* remote capture.
* So we're calling pcap_open_remote() from here; this is a very
* dirty hack.
* Obviously, we cannot exploit all the new features; for instance,
* we cannot send authentication, we cannot use a UDP data connection,
* and so on.
*/
if (pcap_parsesrcstr(device, &srctype, host, port, name, errbuf))
return (NULL);
if (srctype == PCAP_SRC_IFREMOTE) {
/*
* Although we already have host, port and iface, we prefer
* to pass only 'device' to pcap_open_rpcap(), so that it has
* to call pcap_parsesrcstr() again.
* This is less optimized, but much clearer.
*/
return (pcap_open_rpcap(device, snaplen,
promisc ? PCAP_OPENFLAG_PROMISCUOUS : 0, to_ms,
NULL, errbuf));
}
if (srctype == PCAP_SRC_FILE) {
snprintf(errbuf, PCAP_ERRBUF_SIZE, "unknown URL scheme \"file\"");
return (NULL);
}
if (srctype == PCAP_SRC_IFLOCAL) {
/*
* If it starts with rpcap://, that refers to a local device
* (no host part in the URL). Remove the rpcap://, and
* fall through to the regular open path.
*/
if (strncmp(device, PCAP_SRC_IF_STRING, strlen(PCAP_SRC_IF_STRING)) == 0) {
size_t len = strlen(device) - strlen(PCAP_SRC_IF_STRING) + 1;
if (len > 0)
device += strlen(PCAP_SRC_IF_STRING);
}
}
#endif /* ENABLE_REMOTE */
p = pcap_create(device, 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;
/*
* Mark this as opened with pcap_open_live(), so that, for
* example, we show the full list of DLT_ values, rather
* than just the ones that are compatible with capturing
* when not in monitor mode. That allows existing applications
* to work the way they used to work, but allows new applications
* that know about the new open API to, for example, find out the
* DLT_ values that they can select without changing whether
* the adapter is in monitor mode or not.
*/
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", device,
PCAP_ERRBUF_SIZE - 3, 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)", device,
pcap_statustostr(status), PCAP_ERRBUF_SIZE - 6, p->errbuf);
else
snprintf(errbuf, PCAP_ERRBUF_SIZE, "%s: %s", device,
pcap_statustostr(status));
pcap_close(p);
return (NULL);
}