Skip to content

Commit c6996c2

Browse files
authored
0.6.0
Release
1 parent ba7350a commit c6996c2

File tree

8 files changed

+333
-253
lines changed

8 files changed

+333
-253
lines changed

.travis.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
language: php
22

3+
arch:
4+
- amd64
5+
- arm64
6+
37
addons:
48
apt:
59
packages:
10+
- dnsutils
611
- libpcap-dev
712

813
php:
9-
- 7.4
10-
- 8.0
14+
- '7.4'
15+
- nightly
1116

1217
env:
1318
- REPORT_EXIT_STATUS=1 NO_INTERACTION=1
@@ -17,4 +22,4 @@ before_script:
1722
- phpize && ./configure && make
1823

1924
# Run PHPs run-tests.php
20-
script: TEST_PHP_ARGS="-q --show-out" make test
25+
script: sudo TEST_PHP_ARGS="-q --show-out" make test

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ RUN apk add $PHPIZE_DEPS && \
44
cd /usr/src && \
55
tar -xf php.tar.xz && \
66
cd php-* && \
7-
apk add libpcap libpcap-dev
7+
apk add bind-tools libpcap libpcap-dev
88

99
COPY . /usr/src/php-pcap-ext
1010

README.md

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,68 @@
22

33
Stream driven PHP packet capture extension.
44

5-
[![Build Status](https://travis-ci.org/rtckit/php-pcap-ext.svg?branch=master)](https://travis-ci.org/rtckit/php-pcap-ext) ![Version](https://img.shields.io/badge/version-v0.6.0-green) ![License](https://img.shields.io/badge/license-MIT-blue)
5+
[![Build Status](https://travis-ci.com/rtckit/php-pcap-ext.svg?branch=master)](https://travis-ci.com/rtckit/php-pcap-ext) ![Version](https://img.shields.io/badge/version-v0.6.0-green) ![License](https://img.shields.io/badge/license-MIT-blue)
66

77
## Usage
88

99
The `pcap` extension has been developed against PHP 7.4+ and regularly tested against the upcoming PHP 8.
1010

11-
The extension provides bindings for [libpcap](https://github.com/the-tcpdump-group/libpcap) and exposes its functionality via PHP streams; the packet formatting is consistent with the `pcap` file format (learn more [here](https://wiki.wireshark.org/Development/LibpcapFileFormat) and [here](https://formats.kaitai.io/pcap/index.html)).
11+
The extension provides bindings for [libpcap](https://github.com/the-tcpdump-group/libpcap) and exposes its functionality via PHP streams; the packet formatting is consistent with the `pcap` file format (learn more [here](https://wiki.wireshark.org/Development/LibpcapFileFormat) and [here](https://formats.kaitai.io/pcap/index.html)). The functionality is deliberately limited to I/O operations, the actual packet parsing/crafting should be performed using pure PHP; such supporting libraries will be open sourced soon.
1212

1313
It's also worth familiarizing yourself with [libpcap and tcpdump](https://www.tcpdump.org/index.html).
1414

15+
A typical capture session can be initiated as follows:
16+
17+
```php
18+
$fp = fopen('pcap://eth0', 'r');
19+
```
20+
21+
The above will initiate the capture session on the `eth0` interface; one can retrieve all network interfaces via `net_get_interfaces()`. An `any` meta-interface is also available.
22+
23+
There are several configuration options exposed through stream contexts:
24+
25+
```php
26+
$context = stream_context_create([
27+
'pcap' => [
28+
'snaplen' => 2048, // Snapshot length (truncates packets)
29+
'promisc' => true, // Enables promiscuous mode
30+
'immediate' => true, // Sets immediate mode (skips buffering)
31+
'blocking' => false, // Enables/disables blocking mode (useful in stream_select loops)
32+
'timeout' => 0.100, // I/O timeout, in seconds
33+
'filter' => 'dst port 53', // Reference: https://www.tcpdump.org/manpages/pcap-filter.7.html
34+
],
35+
]);
36+
37+
$fp = fopen('pcap://any', 'r', false, $context);
38+
```
39+
40+
All I/O operations are no different than any other PHP stream, for example:
41+
42+
```php
43+
$fp = fopen('pcap://eth0', 'r');
44+
45+
$header = unpack('LtsSec/LtsUsec/LcapLen/Llen', fread($fp, 16)); // pcap packet header, using local machine endianness
46+
$frame = fread($fp, $header['capLen']);
47+
48+
var_dump($header)
49+
/*
50+
array(4) {
51+
["tsSec"]=>
52+
int(1598997114)
53+
["tsUsec"]=>
54+
int(239648)
55+
["capLen"]=>
56+
int(96)
57+
["len"]=>
58+
int(96)
59+
}
60+
*/
61+
62+
// process($frame) ...
63+
```
64+
65+
The [tests](https://github.com/rtckit/php-pcap-ext/tree/master/tests) directory show cases some usage examples.
66+
1567
## Build
1668

1769
In order to build the extension from source, make sure the environment supports the typical C/C++ build essentials for your platform (`build-essential`), the PHP development files (`php-dev`) as well as the libpcap library and its respective development files (`libpcap-dev`).

pcap.c

Lines changed: 43 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,16 @@
1212
#include "ext/standard/url.h"
1313
#include "php_pcap.h"
1414
#include <Zend/zend_interfaces.h>
15+
#include <sys/socket.h>
16+
#include <netinet/ip.h>
1517
#include <pcap.h>
1618

1719
void pcap_close_session(pcap_capture_session_t *sess)
1820
{
21+
if (!sess) {
22+
return;
23+
}
24+
1925
if (sess->pcap) {
2026
pcap_close(sess->pcap);
2127
}
@@ -26,7 +32,12 @@ void pcap_close_session(pcap_capture_session_t *sess)
2632
efree(sess->filter);
2733
}
2834

35+
sess->pcap = NULL;
36+
sess->dev = NULL;
37+
sess->filter = NULL;
38+
2939
efree(sess);
40+
sess = NULL;
3041
}
3142

3243
pcap_capture_session_t * pcap_activate_session(pcap_capture_session_t *sess)
@@ -89,46 +100,26 @@ pcap_capture_session_t * pcap_activate_session(pcap_capture_session_t *sess)
89100

90101
if (pcap_set_snaplen(sess->pcap, sess->snaplen) < 0) {
91102
php_error_docref(NULL, E_WARNING, "Cannot set snapshot length %d on device %s", sess->snaplen, sess->dev);
92-
93-
pcap_close_session(sess);
94-
95-
return NULL;
96103
}
97104

98-
if (pcap_set_promisc(sess->pcap, sess->promisc) < 0) {
105+
if (sess->promisc && (pcap_set_promisc(sess->pcap, sess->promisc) < 0)) {
99106
php_error_docref(NULL, E_WARNING, "Cannot set promiscuous mode %d on device %s", sess->promisc, sess->dev);
100-
101-
pcap_close_session(sess);
102-
103-
return NULL;
104107
}
105108

106-
if (pcap_set_immediate_mode(sess->pcap, sess->immediate) < 0) {
109+
if (sess->immediate && (pcap_set_immediate_mode(sess->pcap, sess->immediate) < 0)) {
107110
php_error_docref(NULL, E_WARNING, "Cannot set immediate mode %d on device %s", sess->immediate, sess->dev);
108-
109-
pcap_close_session(sess);
110-
111-
return NULL;
112111
}
113112

114113
if (pcap_set_timeout(sess->pcap, sess->timeout) < 0) {
115114
php_error_docref(NULL, E_WARNING, "Cannot set timeout %ldms on device %s", sess->timeout, sess->dev);
116-
117-
pcap_close_session(sess);
118-
119-
return NULL;
120115
}
121116

122-
if (pcap_setnonblock(sess->pcap, sess->non_blocking, sess->errbuf) < 0) {
123-
php_error_docref(NULL, E_WARNING, "Cannot set blocking options on device %s: %s", sess->dev, sess->errbuf);
124-
125-
pcap_close_session(sess);
126-
127-
return NULL;
117+
if (sess->non_blocking && (pcap_setnonblock(sess->pcap, sess->non_blocking, sess->errbuf) < 0)) {
118+
php_error_docref(NULL, E_WARNING, "Cannot set blocking option on device %s: %s", sess->dev, pcap_geterr(sess->pcap));
128119
}
129120

130121
if (pcap_activate(sess->pcap) < 0) {
131-
php_error_docref(NULL, E_WARNING, "Cannot activate live capture on device %s", sess->dev);
122+
php_error_docref(NULL, E_WARNING, "Cannot activate live capture on device %s: %s", sess->dev, pcap_geterr(sess->pcap));
132123

133124
pcap_close_session(sess);
134125

@@ -140,18 +131,10 @@ pcap_capture_session_t * pcap_activate_session(pcap_capture_session_t *sess)
140131

141132
if (pcap_compile(sess->pcap, &fp, sess->filter, 0, PCAP_NETMASK_UNKNOWN) < 0) {
142133
php_error_docref(NULL, E_WARNING, "Cannot parse filter '%s' on device %s: %s", sess->filter, sess->dev, pcap_geterr(sess->pcap));
143-
144-
pcap_close_session(sess);
145-
146-
return NULL;
147-
}
148-
149-
if (pcap_setfilter(sess->pcap, &fp) < 0) {
150-
php_error_docref(NULL, E_WARNING, "Cannot install filter '%s' on device %s: %s", sess->filter, sess->dev, pcap_geterr(sess->pcap));
151-
152-
pcap_close_session(sess);
153-
154-
return NULL;
134+
} else {
135+
if (pcap_setfilter(sess->pcap, &fp) < 0) {
136+
php_error_docref(NULL, E_WARNING, "Cannot install filter '%s' on device %s: %s", sess->filter, sess->dev, pcap_geterr(sess->pcap));
137+
}
155138
}
156139
}
157140

@@ -163,7 +146,7 @@ static ssize_t php_pcap_stream_write(php_stream *stream, const char *buf, size_t
163146
pcap_capture_session_t *sess = (pcap_capture_session_t *) stream->abstract;
164147
ssize_t writestate = 0;
165148

166-
if (!sess->pcap && !pcap_activate_session(sess)) {
149+
if (!sess || (!sess->pcap && !pcap_activate_session(sess))) {
167150
return -1;
168151
}
169152

@@ -181,7 +164,7 @@ static ssize_t php_pcap_stream_read(php_stream *stream, char *buf, size_t count)
181164
pcap_capture_session_t *sess = (pcap_capture_session_t *) stream->abstract;
182165
ssize_t readstate = 0;
183166

184-
if (!sess->pcap && !pcap_activate_session(sess)) {
167+
if (!sess || (!sess->pcap && !pcap_activate_session(sess))) {
185168
return -1;
186169
}
187170

@@ -244,7 +227,9 @@ static int php_pcap_stream_close(php_stream *stream, int close_handle)
244227
{
245228
pcap_capture_session_t *sess = (pcap_capture_session_t *) stream->abstract;
246229

247-
pcap_close_session(sess);
230+
if (sess) {
231+
pcap_close_session(sess);
232+
}
248233

249234
return 0;
250235
}
@@ -254,7 +239,7 @@ static int php_pcap_stream_cast(php_stream *stream, int castas, void **ret)
254239
pcap_capture_session_t *sess = (pcap_capture_session_t *) stream->abstract;
255240
int fd = 0;
256241

257-
if (!sess->pcap && !pcap_activate_session(sess)) {
242+
if (!sess || (!sess->pcap && !pcap_activate_session(sess))) {
258243
return FAILURE;
259244
}
260245

@@ -283,10 +268,14 @@ static int php_pcap_stream_set_option(php_stream *stream, int option, int value,
283268
pcap_capture_session_t *sess = (pcap_capture_session_t *) stream->abstract;
284269
int ret = -1;
285270

271+
if(!sess || !sess->pcap) {
272+
return -1;
273+
}
274+
286275
switch (option) {
287276
case PHP_STREAM_OPTION_BLOCKING:
288277
if (sess->pcap && pcap_setnonblock(sess->pcap, !value, sess->errbuf) == PCAP_ERROR) {
289-
php_error_docref(NULL, E_WARNING, "Cannot set blocking option: %s", sess->errbuf);
278+
php_error_docref(NULL, E_WARNING, "Cannot set blocking option: %s", pcap_geterr(sess->pcap));
290279
} else {
291280
ret = !sess->non_blocking;
292281
sess->non_blocking = !value;
@@ -297,7 +286,7 @@ static int php_pcap_stream_set_option(php_stream *stream, int option, int value,
297286
sess->timeout = ((struct timeval *) ptrparam)->tv_sec * 1000 + (((struct timeval *) ptrparam)->tv_usec / 1000);
298287

299288
if (sess->pcap && (pcap_set_timeout(sess->pcap, sess->timeout) == PCAP_ERROR_ACTIVATED)) {
300-
php_error_docref(NULL, E_WARNING, "Cannot set timeout option on active session: %s", sess->errbuf);
289+
php_error_docref(NULL, E_WARNING, "Cannot set timeout option on active session: %s", pcap_geterr(sess->pcap));
301290
} else {
302291
ret = sess->timeout;
303292
}
@@ -322,15 +311,18 @@ php_stream_ops php_pcap_stream_ops = {
322311
/* {{{ php_pcap_fopen
323312
* pcap:// fopen wrapper
324313
*/
325-
static php_stream *php_pcap_fopen(
326-
php_stream_wrapper *wrapper,
327-
const char *path,
328-
const char *mode,
329-
int options,
330-
zend_string **opened_path,
331-
php_stream_context *context STREAMS_DC
332-
)
314+
static php_stream *php_pcap_fopen(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
333315
{
316+
int raw = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
317+
318+
if (raw < 0) {
319+
php_error_docref(NULL, E_WARNING, "Cannot open raw sockets (check privileges or CAP_NET_RAW capability)");
320+
321+
return NULL;
322+
}
323+
324+
close(raw);
325+
334326
php_url *parsed_url = php_url_parse(path);
335327
pcap_if_t* alldevsp = NULL;
336328

0 commit comments

Comments
 (0)