Skip to content

Commit 0188855

Browse files
authored
Merge pull request #3 from rtckit/v0.6.2
v0.6.2
2 parents 8e7110f + 47a724a commit 0188855

16 files changed

+292
-15
lines changed

CHANGELOG.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
### To Do
22
* Failed operations to throw Exceptions over issuing PHP warnings (via a toggle)
3+
* Improved documentation
4+
* IPv6 oriented tests
35
* Expose useful data pointers via `stream_metadata` (accessible through `stream_get_meta_data`)
4-
* Add tests covering stream closing
5-
* Add tests covering packet injection
6-
* Validate `php_url_parse` returns a `host` property (the network device name)
76

8-
### Upcoming Release
7+
### 0.6.2
98
* Improved UDP/DNS tests
109
* Selectable file descriptor (for `stream_cast`) is now cached
10+
* Validates `php_url_parse` succeeds and returns a `host` property (the network device name)
11+
* Packet injection tests, via ARP
12+
* Strict type enforcement within tests
1113

1214
### 0.6.0
1315
* First public release

Makefile

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,5 @@ REPOSITORY=rtckit/php-pcap-ext-dev
44
image:
55
docker build -t ${REPOSITORY} .
66

7-
local-image:
8-
docker build -v `pwd`:/usr/src/php-pcap-ext:rw -t ${REPOSITORY} .
9-
107
run: image
11-
docker run --rm -t ${REPOSITORY}
8+
docker run --rm -it ${REPOSITORY}

README.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
Stream driven PHP packet capture extension.
44

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)
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.2-green) ![License](https://img.shields.io/badge/license-MIT-blue)
66

77
## Usage
88

9-
The `pcap` extension has been developed against PHP 7.4+ and regularly tested against the upcoming PHP 8.
9+
The `pcap` extension has been developed against PHP 7.4+ and regularly tested against the nightly PHP 8 build; from an operating system perspective, the ubiquity of Linux makes it the only target. The supported architectures are x86_64 and arm64.
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)). 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.
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 (some relevant supporting libraries to be published soon).
1212

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

@@ -60,6 +60,9 @@ array(4) {
6060
*/
6161

6262
// process($frame) ...
63+
64+
// Inject raw packets (including the link layer data) by writing to the stream
65+
$count = fwrite($fp, $packet);
6366
```
6467

6568
The [tests](https://github.com/rtckit/php-pcap-ext/tree/master/tests) directory show cases some usage examples.
@@ -76,19 +79,23 @@ make
7679

7780
## Tests
7881

79-
Before running the test suite, make sure the user has the ability to capture network packets (root or CAP_RAW).
82+
Before running the test suite, make sure the user has the ability to capture network packets (root or `CAP_NET_RAW`).
8083

8184
```sh
8285
make test
8386
```
8487

88+
## FFI Alternative
89+
90+
A fully compilable [FFI packet capture](https://github.com/rtckit/php-pcap-ffi) package is also available; the underlying environment would still have the provide the libpcap library as well as the FFI dependencies (libffi and the PHP FFI extension). Otherwise, the FFI package can be used as a drop-in replacement when it makes sense to do so.
91+
8592
## License
8693

8794
MIT, see [LICENSE file](LICENSE).
8895

8996
### Acknowledgments
9097

91-
* [libpcap](https://github.com/the-tcpdump-group/libpcap)
98+
* [libpcap](https://github.com/the-tcpdump-group/libpcap) by The Tcpdump Group, BSD licensed.
9299

93100
### Contributing
94101

pcap.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,19 @@ static php_stream *php_pcap_fopen(php_stream_wrapper *wrapper, const char *path,
332332
close(raw);
333333

334334
php_url *parsed_url = php_url_parse(path);
335-
pcap_if_t* alldevsp = NULL;
335+
336+
if (!parsed_url) {
337+
php_error_docref(NULL, E_WARNING, "Cannot parse pcap URL");
338+
339+
return NULL;
340+
}
341+
342+
if (!parsed_url->host || !strlen(parsed_url->host->val)) {
343+
php_error_docref(NULL, E_WARNING, "Missing host, should be device's name");
344+
php_url_free(parsed_url);
345+
346+
return NULL;
347+
}
336348

337349
if (!parsed_url->scheme) {
338350
php_error_docref(NULL, E_WARNING, "Missing scheme, should be 'pcap'");
@@ -381,6 +393,8 @@ static php_stream *php_pcap_fopen(php_stream_wrapper *wrapper, const char *path,
381393

382394
php_url_free(parsed_url);
383395

396+
pcap_if_t* alldevsp = NULL;
397+
384398
if (pcap_findalldevs(&alldevsp, sess->errbuf)) {
385399
php_error_docref(NULL, E_WARNING, "Cannot enumerate network devices: %s", sess->errbuf);
386400
pcap_close_session(sess);

php_pcap.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
extern zend_module_entry pcap_module_entry;
1010
# define phpext_pcap_ptr &pcap_module_entry
1111

12-
# define PHP_PCAP_VERSION "0.6.0"
12+
# define PHP_PCAP_VERSION "0.6.2"
1313

1414
# if defined(ZTS) && defined(COMPILE_DL_PCAP)
1515
ZEND_TSRMLS_CACHE_EXTERN()

tests/001.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ extension_loaded pcap
55
--FILE--
66
<?php
77

8+
declare(strict_types = 1);
9+
810
var_dump(extension_loaded('pcap'));
911

1012
print "done!";

tests/002.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ stream_get_wrappers pcap
55
--FILE--
66
<?php
77

8+
declare(strict_types = 1);
9+
810
var_dump(in_array('pcap', stream_get_wrappers()));
911

1012
print "done!";

tests/003.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ fopen devices from net_get_interfaces
55
--FILE--
66
<?php
77

8+
declare(strict_types = 1);
9+
810
$devs = 0;
911

1012
foreach (array_keys(net_get_interfaces()) as $dev) {

tests/004.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ fopen against bogus devices
55
--FILE--
66
<?php
77

8+
declare(strict_types = 1);
9+
810
$fp = fopen('pcap://' . uniqid('bogus'), 'r');
911
var_dump($fp);
1012

tests/005.phpt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ fopen against bogus URLs
55
--FILE--
66
<?php
77

8+
declare(strict_types = 1);
9+
810
$dev = current(array_keys(net_get_interfaces()));
911
if (!$dev) {
1012
die('Cannot find any viable network devices');
@@ -13,6 +15,15 @@ if (!$dev) {
1315
$fp = fopen('pcap.bogus://' . $dev, 'r');
1416
var_dump($fp);
1517

18+
$fp = fopen('pcap://', 'r');
19+
var_dump($fp);
20+
21+
$fp = fopen('pcap:///', 'r');
22+
var_dump($fp);
23+
24+
$fp = fopen('pcap:///path', 'r');
25+
var_dump($fp);
26+
1627
$fp = fopen('pcap://' . $dev . '/path', 'r');
1728
var_dump($fp);
1829

@@ -27,6 +38,21 @@ Warning: fopen(): Unable to find the wrapper "pcap.bogus" - did you forget to en
2738
Warning: fopen(pcap.bogus://%s): %s to open stream: No such file or directory in %s on line %d
2839
bool(false)
2940

41+
Warning: fopen(): Cannot parse pcap URL in %s on line %d
42+
43+
Warning: fopen(pcap://): %s to open stream: operation failed in %s on line %d
44+
bool(false)
45+
46+
Warning: fopen(): Cannot parse pcap URL in %s on line %d
47+
48+
Warning: fopen(pcap:///): %s to open stream: operation failed in %s on line %d
49+
bool(false)
50+
51+
Warning: fopen(): Cannot parse pcap URL in %s on line %d
52+
53+
Warning: fopen(pcap:///path): %s to open stream: operation failed in %s on line %d
54+
bool(false)
55+
3056
Warning: fopen(): Unsupported path: /path in %s on line %d
3157

3258
Warning: fopen(pcap://%s/path): %s to open stream: operation failed in %s on line %d

tests/006.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ fopen without privileges
55
--FILE--
66
<?php
77

8+
declare(strict_types = 1);
9+
810
$dev = current(array_keys(net_get_interfaces()));
911
if (!$dev) {
1012
die('Cannot find any viable network devices');

tests/007.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ fread ICMP ping traffic
55
--FILE--
66
<?php
77

8+
declare(strict_types = 1);
9+
810
require('helpers.php');
911

1012
$ip = gethostbyname('example.com');

tests/008.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ fread UDP DNS traffic
55
--FILE--
66
<?php
77

8+
declare(strict_types = 1);
9+
810
require('helpers.php');
911

1012
// Use OpenDNS public nameservers

tests/009.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ fread TCP HTTP traffic
55
--FILE--
66
<?php
77

8+
declare(strict_types = 1);
9+
810
require('helpers.php');
911

1012
$ip = gethostbyname('example.com');

tests/010.phpt

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
--TEST--
2+
fwrite/fread ARP to IPv4 gateway
3+
--SKIPIF--
4+
<?php if (!extension_loaded('pcap')) { echo 'skip'; } ?>
5+
--FILE--
6+
<?php
7+
8+
declare(strict_types = 1);
9+
10+
require('helpers.php');
11+
12+
$dev = $gw = null;
13+
14+
foreach (getRoutingTable() as $record) {
15+
if (!empty($record['Iface']) && !empty($record['Gateway']) && ($record['Gateway'] !== '00000000')) {
16+
$dev = $record['Iface'];
17+
$hex = $record['Gateway'];
18+
$gw = [];
19+
20+
while (strlen($hex)) {
21+
$byte = hexdec(substr($hex, -2));
22+
$hex = substr($hex, 0, -2);
23+
$gw[] = $byte;
24+
}
25+
26+
$gw = implode('.', $gw);
27+
break;
28+
}
29+
}
30+
31+
if (is_null($dev)) {
32+
die('Cannot find a suitable network device');
33+
}
34+
35+
$mac = trim(file_get_contents('/sys/class/net/' . $dev . '/address'));
36+
$ip = null;
37+
38+
foreach (net_get_interfaces()[$dev]['unicast'] as $config) {
39+
if ($config['family'] == 2) {
40+
$ip = $config['address'];
41+
break;
42+
}
43+
}
44+
45+
var_dump($dev);
46+
var_dump($mac);
47+
var_dump($ip);
48+
var_dump($gw);
49+
50+
$packet = craftEthernet2Frame([
51+
'destination' => 'ff:ff:ff:ff:ff:ff',
52+
'source' => $mac,
53+
'etherType' => 0x0806,
54+
'data' => craftArpFrame([
55+
'htype' => 1,
56+
'ptype' => 0x0800,
57+
'hsize' => 6,
58+
'psize' => 4,
59+
'opcode' => 1,
60+
'senderEtherAddress' => $mac,
61+
'senderProtoAddress' => $ip,
62+
'targetEtherAddress' => '00:00:00:00:00:00',
63+
'targetProtoAddress' => $gw,
64+
]),
65+
]);
66+
67+
var_dump(strlen($packet));
68+
69+
$context = stream_context_create([
70+
'pcap' => [
71+
'snaplen' => 2048,
72+
'immediate' => true,
73+
'timeout' => 0.100,
74+
'filter' => 'arp',
75+
],
76+
]);
77+
78+
$fp = fopen('pcap://' . $dev, 'rw', false, $context);
79+
80+
if (!$fp) {
81+
die('Cannot initiate packet capture');
82+
}
83+
84+
var_dump($fp);
85+
86+
// Trigger capture activation, expect nothing to read
87+
var_dump(fread($fp, 16));
88+
89+
$bytes = fwrite($fp, $packet);
90+
var_dump($bytes);
91+
92+
$captures = [$fp];
93+
$read = [];
94+
$write = $except = null;
95+
96+
$gwMac = null;
97+
98+
while (!$gwMac) {
99+
$read = $captures;
100+
101+
if (stream_select($read, $write, $except, 0, 100000)) {
102+
foreach ($read as $r) {
103+
while ($_header = fread($r, 16)) {
104+
$header = unpack('LtsSec/LtsUsec/LcapLen/Llen', $_header);
105+
$frame = parseEthernet2Frame(fread($r, $header['capLen']));
106+
107+
if ($frame['etherType'] == 0x0806) { // ARP
108+
$arp = parseArpFrame($frame['data']);
109+
110+
if (($arp['opcode'] == 2) && ($arp['senderProtoAddress'] == $gw) && ($arp['targetProtoAddress'] == $ip) && ($arp['targetEtherAddress'] == $mac)) {
111+
$gwMac = $arp['senderEtherAddress'];
112+
break;
113+
}
114+
}
115+
}
116+
}
117+
}
118+
}
119+
120+
var_dump($gwMac);
121+
122+
print "done!";
123+
?>
124+
--EXPECTF--
125+
string(%d) "%s"
126+
string(17) "%x:%x:%x:%x:%x:%x"
127+
string(%d) "%d.%d.%d.%d"
128+
string(%d) "%d.%d.%d.%d"
129+
int(42)
130+
resource(%d) of type (stream)
131+
string(0) ""
132+
int(42)
133+
string(17) "%x:%x:%x:%x:%x:%x"
134+
done!

0 commit comments

Comments
 (0)