diff --git a/images/basic/tcpdump/Dockerfile b/images/basic/tcpdump/Dockerfile new file mode 100644 index 000000000..c6edc599b --- /dev/null +++ b/images/basic/tcpdump/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3 + +RUN apt-get update && apt-get install -y tcpdump + +COPY sender.py sender.py \ No newline at end of file diff --git a/images/basic/tcpdump/sender.py b/images/basic/tcpdump/sender.py new file mode 100644 index 000000000..b5b9cc153 --- /dev/null +++ b/images/basic/tcpdump/sender.py @@ -0,0 +1,29 @@ +"""Sender script for tcpdump test. +""" +# Copyright 2025 The gVisor Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import socket + + +def main(): + sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sk.bind(("127.0.0.1", 9999)) + sk.connect(("127.0.0.1", 9999)) + assert sk.send(b"test",) == 4 + assert sk.send(b"testtest",) == 8 + return 0 + +if __name__ == "__main__": + exit(main()) diff --git a/test/image/image_test.go b/test/image/image_test.go index 3d0ad71c7..6e5c07e7b 100644 --- a/test/image/image_test.go +++ b/test/image/image_test.go @@ -339,6 +339,53 @@ func TestStdio(t *testing.T) { } } +func TestTcpdump(t *testing.T) { + if testutil.IsRunningWithHostNet() { + t.Skip("docker doesn't work with hostinet") + } + ctx := context.Background() + // The "-docker" runtime comes with the net_raw capabilities enabled which are + // required for tcpdump. + d := dockerutil.MakeContainerWithRuntime(ctx, t, "-docker") + defer d.CleanUp(ctx) + + if err := d.Spawn(ctx, dockerutil.RunOpts{ + Image: "basic/tcpdump", + }, "sleep", "infinity"); err != nil { + t.Fatalf("docker run failed: %v", err) + } + + cmd := "tcpdump -c 2 -i lo port 9999" + tcpdumpProc, err := d.ExecProcess(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", cmd) + if err != nil { + t.Fatalf("docker run failed: %v", err) + } + cmd = "python3 sender.py" + senderProc, err := d.ExecProcess(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", cmd) + if err != nil { + t.Fatalf("docker exec failed: %v", err) + } + if status, err := senderProc.WaitExitStatus(ctx); err != nil || status != 0 { + t.Fatalf("docker exec failed: %v, status: %d", err, status) + } + if status, err := tcpdumpProc.WaitExitStatus(ctx); err != nil || status != 0 { + t.Fatalf("docker exec failed: %v, status: %d", err, status) + } + + expectedOutputStr1 := "IP localhost.9999 > localhost.9999: UDP, length 4" + logs, err := tcpdumpProc.Logs() + if err != nil { + t.Fatalf("docker exec failed: %v", err) + } + if !strings.Contains(logs, expectedOutputStr1) { + t.Fatalf("docker didn't get output: %q, got: %q", expectedOutputStr1, logs) + } + expectedOutputStr2 := "IP localhost.9999 > localhost.9999: UDP, length 8" + if !strings.Contains(logs, expectedOutputStr2) { + t.Fatalf("docker didn't get output: %q, got: %q", expectedOutputStr2, logs) + } +} + func dockerInGvisorCapabilities() []string { return []string{ "audit_write",