Skip to content

Commit 490538c

Browse files
committed
Implement cf-pcap to enable packet captures in apps
1 parent 2bf637e commit 490538c

12 files changed

Lines changed: 331 additions & 3 deletions

File tree

docs/090-cf-pcap.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# cf-pcap
2+
3+
## Enable the feature platform wide
4+
5+
Set the rep property `diego.rep.enable_cf_pcap` to true (defaults to false) and
6+
re-deploy diego.
7+
8+
This will set a file capability on the cf-pcap binary which is always inside the
9+
app container to allow the binary to perform packet captures.
10+
11+
## Usage
12+
13+
<!-- TODO: fix path -->
14+
The binary is placed in `/etc/cf-assets/...` and can be executed by any regular
15+
user. Invoking the binary will cause it to emit raw pcap on stdout which can
16+
mess up your terminal so make sure to redirect it into a file. The accepted
17+
parameters are:
18+
19+
* `-interface`: the network interface on which to capture (usually `lo` or
20+
`eth0`).
21+
* `-snaplen`: how many bytes to capture of each packet.
22+
* `-filter`: pcap-filter to specify which packets to capture.
23+
* `-v`: more verbose output.
24+
25+
## Notes in network traffic in app containers
26+
27+
When envoy is enabled, all traffic passes through both interfaces in the app
28+
container. First, envoy receives it on `eth0` and terminates TLS, then it
29+
forwards the traffic to the app via the `lo` device in plain text. For SSH
30+
traffic the same thing happens, but envoy won't terminate the SSH encryption.
31+
32+
To avoid capturing the SSH traffic which contains the just captured bytes, thus
33+
indefinitely capturing the same content over and over again, cf-pcap will try to
34+
determine the correct port to filter out. This is done on a best-effort basis
35+
and failure to determine the correct filter will not stop the capture from
36+
happening.
37+
38+
## Capturing traffic locally
39+
40+
SSH into the app instance you would like to capture traffic from. To capture the
41+
plain-text HTTP traffic for an app listening on port 8080 you specify the
42+
loopback interface and filter for TCP traffic on port 8080. Once you've copied
43+
the file to your local machine you can print out the captured traffic your
44+
tcpdumps `-r` to read form a previous capture file:
45+
46+
<!-- TODO: correct paths and output -->
47+
```
48+
$ cf ssh $app
49+
vcap@app $ cf-pcap -interface lo -filter "tcp port 8080" > capture01.pcap
50+
...
51+
^C
52+
...
53+
vcap@app $ ^D
54+
$ scp $app:capture01.pcap .
55+
$ tcpdump -Xr capture01.pcap
56+
```
57+
58+
## Capturing traffic remotely
59+
60+
Similar to the local capture, you start cf-pcap with the details on what you
61+
want to capture but this time wrap it in the `-c` of cf-ssh. This will send the
62+
pcap output to stdout and you can redirect the content to a local file.
63+
Afterwards, you can again use tcpdumps `-r` to inspect the content:
64+
65+
```
66+
cf ssh $app -c "cf-pcap -interface lo -filter 'tcp port 8080'" > capture02.pcap
67+
...
68+
^C
69+
...
70+
$ tcpdump -Xr capture02.pcap
71+
```

jobs/rep/spec

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,10 @@ properties:
245245
description: "Maximum container capacity per rep"
246246
default: 250
247247

248+
diego.rep.enable_cf_pcap:
249+
description: "Allow app developers to capture tcpdumps within their app containers."
250+
default: false
251+
248252
enable_healthcheck_metrics:
249253
description: "When set, enables the rep to emit healtcheck failure metrics."
250254
default: false

jobs/rep/templates/bpm.yml.erb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
processes:
33
- name: rep
44
executable: /var/vcap/jobs/rep/bin/rep
5+
<% if p("diego.rep.enable_cf_pcap", false) -%>
6+
# This is required to preserve the file capabilities of cf-pcap to allow the vcap user to capture
7+
# packets inside the app container without root.
8+
capabilities: [ "SETFCAP" ]
9+
<% end -%>
510
limits:
611
open_files: 100000
712
hooks:

packages/buildpack_app_lifecycle/packaging

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,16 @@ ldd $DEST/launcher && echo "launcher must be statically linked" && false
2727
cp /var/vcap/packages/diego-sshd/diego-sshd ${DEST}/diego-sshd
2828
cp /var/vcap/packages/diego-sshd/*.exe ${DEST}
2929
cp /var/vcap/packages/diego-sshd/winpty.dll ${DEST}/winpty.dll
30+
cp /var/vcap/packages/cf-pcap/cf-pcap ${DEST}/cf-pcap
3031
cp /var/vcap/packages/healthcheck/healthcheck ${DEST}/healthcheck
3132
cp /var/vcap/packages/healthcheck/healthcheck.exe ${DEST}/healthcheck.exe
3233

34+
setcap cap_net_raw+ep ${DEST}/cf-pcap
35+
3336
tar -czf ${BOSH_INSTALL_TARGET}/buildpack_app_lifecycle.tgz \
37+
--xattrs --xattrs-include='*' \
3438
-C ${DEST} \
35-
builder launcher shell healthcheck diego-sshd \
39+
builder launcher shell healthcheck diego-sshd cf-pcap \
3640
builder.exe launcher.exe getenv.exe healthcheck.exe diego-sshd.exe \
3741
winpty-agent.exe winpty.dll
3842

packages/buildpack_app_lifecycle/spec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ dependencies:
55
- golang-1.25-linux
66
- healthcheck
77
- diego-sshd
8+
- cf-pcap
89

910
files:
1011
- code.cloudfoundry.org/go.mod

packages/cf-pcap/packaging

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
set -e
2+
3+
tar -xzf libpcap/libpcap-*.tar.gz
4+
5+
LIBPCAP_DIR=$(ls -d libpcap-*)
6+
7+
pushd ${LIBPCAP_DIR}
8+
./configure
9+
make
10+
popd
11+
12+
source /var/vcap/packages/golang-*-linux/bosh/compile.env
13+
14+
pushd code.cloudfoundry.org
15+
# -I adds the local libpcap to the search path for the compiler
16+
export CGO_CFLAGS="-I${BOSH_COMPILE_TARGET}/${LIBPCAP_DIR}"
17+
# -L adds the local libpcap to the search path for the linker
18+
# -linkmode external: use an external linker: https://cs.opensource.google/go/go/+/refs/tags/go1.18:src/cmd/cgo/doc.go;l=794
19+
# -extldflags -static: pass -static to ld, see man page for details
20+
export CGO_LDFLAGS="-L${BOSH_COMPILE_TARGET}/${LIBPCAP_DIR} -static"
21+
go build -ldflags '-linkmode external' -o ${BOSH_INSTALL_TARGET}/cf-pcap -a -installsuffix static code.cloudfoundry.org/cf-pcap
22+
popd

packages/cf-pcap/spec

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
name: cf-pcap
3+
dependencies:
4+
- golang-1.25-linux
5+
6+
files:
7+
- libpcap/libpcap-*.tar.xz
8+
- code.cloudfoundry.org/go.mod
9+
- code.cloudfoundry.org/go.sum
10+
- code.cloudfoundry.org/vendor/modules.txt
11+
- code.cloudfoundry.org/cf-pcap/*.go # gosub
12+
- code.cloudfoundry.org/vendor/github.com/gopacket/gopacket/*.go # gosub
13+
- code.cloudfoundry.org/vendor/github.com/gopacket/gopacket/endian/*.go # gosub
14+
- code.cloudfoundry.org/vendor/github.com/gopacket/gopacket/layers/*.go # gosub
15+
- code.cloudfoundry.org/vendor/github.com/gopacket/gopacket/pcap/*.go # gosub
16+
- code.cloudfoundry.org/vendor/github.com/gopacket/gopacket/pcapgo/*.go # gosub
17+
- code.cloudfoundry.org/vendor/golang.org/x/net/bpf/*.go # gosub
18+
- code.cloudfoundry.org/vendor/golang.org/x/sys/unix/*.go # gosub
19+
- code.cloudfoundry.org/vendor/golang.org/x/sys/unix/*.s # gosub
20+
- code.cloudfoundry.org/vendor/golang.org/x/sys/windows/*.go # gosub

packages/cnb_app_lifecycle/packaging

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,12 @@ ldd $DEST/builder && echo "builder must be statically linked" && false
2020
ldd $DEST/launcher && echo "launcher must be statically linked" && false
2121

2222
cp /var/vcap/packages/diego-sshd/diego-sshd ${DEST}/diego-sshd
23+
cp /var/vcap/packages/cf-pcap/cf-pcap ${DEST}/cf-pcap
2324
cp /var/vcap/packages/healthcheck/healthcheck ${DEST}/healthcheck
2425

26+
setcap cap_net_raw+ep ${DEST}/cf-pcap
27+
2528
tar -czf ${BOSH_INSTALL_TARGET}/cnb_app_lifecycle.tgz \
29+
--xattrs --xattrs-include='*' \
2630
-C ${DEST} \
27-
builder launcher healthcheck diego-sshd
31+
builder launcher healthcheck diego-sshd cf-pcap

packages/cnb_app_lifecycle/spec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ dependencies:
55
- golang-1.25-linux
66
- healthcheck
77
- diego-sshd
8+
- cf-pcap
89

910
files:
1011
- cnbapplifecycle/go.mod

packages/docker_app_lifecycle/packaging

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ ldd ${DEST}/builder && echo "builder must be statically linked" && false
1919
ldd ${DEST}/launcher && echo "launcher must be statically linked" && false
2020

2121
cp /var/vcap/packages/diego-sshd/diego-sshd ${DEST}/diego-sshd
22+
cp /var/vcap/packages/cf-pcap/cf-pcap ${DEST}/cf-pcap
2223
cp /var/vcap/packages/healthcheck/healthcheck ${DEST}/healthcheck
2324

24-
tar -czf ${BOSH_INSTALL_TARGET}/docker_app_lifecycle.tgz -C ${DEST} builder launcher healthcheck diego-sshd
25+
setcap cap_net_raw+ep ${DEST}/cf-pcap
26+
27+
tar -czf ${BOSH_INSTALL_TARGET}/docker_app_lifecycle.tgz \
28+
--xattrs --xattrs-include='*' \
29+
-C ${DEST} builder launcher healthcheck diego-sshd cf-pcap

0 commit comments

Comments
 (0)