126 lines
4.1 KiB
Go
126 lines
4.1 KiB
Go
package sipcall
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const testTimeline = "1\t1.0\t192.0.2.1\t192.0.2.10\tINVITE sip:186@192.0.2.10 SIP/2.0\t\tINVITE\t10\tz9hG4bK-test\t\t192.0.2.1\taudio\t10000\n2\t1.1\t192.0.2.10\t192.0.2.1\t\t200\tINVITE\t10\tz9hG4bK-test\tSIP/2.0 200 OK\t192.0.2.10\taudio\t12000\n3\t2.1\t192.0.2.10\t192.0.2.1\t\t200\tBYE\t11\tz9hG4bK-bye\tSIP/2.0 200 OK\t\t\t\n"
|
|
|
|
func TestDecodedEvidenceUsesOriginalINVITEAndNotBYE(t *testing.T) {
|
|
e, ports, err := parseTimeline([]byte(testTimeline))
|
|
if err != nil || e.Packets != 3 || e.FinalINVITECode != 200 || len(ports) != 2 {
|
|
t.Fatalf("%+v %v %v", e, ports, err)
|
|
}
|
|
bad := strings.Replace(testTimeline, "\t200\tINVITE\t10\t", "\t480\tINVITE\t10\t", 1)
|
|
e, _, err = parseTimeline([]byte(bad))
|
|
if err != nil || e.FinalINVITECode != 480 {
|
|
t.Fatal("BYE 200 must not turn a rejected INVITE into a connected call")
|
|
}
|
|
if _, _, err = parseTimeline([]byte("unexpected\n")); err == nil {
|
|
t.Fatal("malformed decoder output accepted")
|
|
}
|
|
if _, _, err = parseTimeline(nil); err == nil {
|
|
t.Fatal("missing original INVITE accepted")
|
|
}
|
|
conflict := testTimeline + strings.Replace(strings.Split(testTimeline, "\n")[1], "\t200\t", "\t480\t", 1) + "\n"
|
|
if _, _, err = parseTimeline([]byte(conflict)); err == nil {
|
|
t.Fatal("conflicting final INVITE response accepted")
|
|
}
|
|
}
|
|
|
|
func fakeTools(t *testing.T, broken bool) string {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
tcpdump := `#!/bin/sh
|
|
out=""
|
|
while [ "$#" -gt 0 ]; do
|
|
if [ "$1" = "-w" ]; then shift; out="$1"; fi
|
|
shift
|
|
done
|
|
/bin/dd if=/dev/zero of="$out" bs=24 count=1 2>/dev/null
|
|
echo "tcpdump: listening on any" >&2
|
|
trap 'exit 0' INT TERM
|
|
while :; do /bin/sleep 0.05; done
|
|
`
|
|
if broken {
|
|
tcpdump = "#!/bin/sh\necho 'permission denied' >&2\nexit 1\n"
|
|
}
|
|
decoder := "#!/bin/sh\ncase \"$*\" in\n*'rtp &&'*) printf '160\\t192.0.2.10\\t192.0.2.1\\n160\\t192.0.2.1\\t192.0.2.10\\n' ;;\n*) /bin/cat '" + filepath.Join(dir, "timeline.tsv") + "' ;;\nesac\n"
|
|
for name, data := range map[string]string{"tcpdump": tcpdump, "tshark": decoder, "timeline.tsv": testTimeline} {
|
|
mode := os.FileMode(0700)
|
|
if name == "timeline.tsv" {
|
|
mode = 0600
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, name), []byte(data), mode); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
t.Setenv("PATH", dir)
|
|
return dir
|
|
}
|
|
|
|
func TestDebugCaptureOwnsProcessAndProducesPrivateLinkedEvidence(t *testing.T) {
|
|
fakeTools(t, false)
|
|
c := fixtureConfig(t)
|
|
dir := t.TempDir()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
cap, err := startCapture(ctx, c, dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = cap.Check(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
e, err := cap.Stop(ctx, "sip-id-123")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(e.PCAPSHA256) != 64 || e.FinalINVITECode != 200 || e.RTPFromServer != 1 || e.RTPToServer != 1 || e.RTPBytes != 320 {
|
|
t.Fatalf("wrong evidence: %+v", e)
|
|
}
|
|
for _, path := range []string{e.PCAP, e.SIPTimeline, filepath.Join(dir, "tcpdump.log"), filepath.Join(dir, "rtp.tsv")} {
|
|
info, err := os.Stat(path)
|
|
if err != nil || info.Mode().Perm() != 0600 {
|
|
t.Fatalf("private artifact expected: %s %v", path, err)
|
|
}
|
|
}
|
|
if err = cap.Check(); err == nil {
|
|
t.Fatal("ended capture still reported ready")
|
|
}
|
|
}
|
|
|
|
func TestDebugCaptureFailureDoesNotSilentlyDisableEvidence(t *testing.T) {
|
|
fakeTools(t, true)
|
|
c := fixtureConfig(t)
|
|
if _, err := startCapture(context.Background(), c, t.TempDir()); err == nil {
|
|
t.Fatal("broken tcpdump accepted")
|
|
}
|
|
t.Setenv("PATH", t.TempDir())
|
|
if _, err := startCapture(context.Background(), c, t.TempDir()); err == nil {
|
|
t.Fatal("missing tcpdump accepted")
|
|
}
|
|
}
|
|
|
|
func TestDebugCaptureRejectsUnsafeCallIDAndCancelledStartup(t *testing.T) {
|
|
fakeTools(t, false)
|
|
c := fixtureConfig(t)
|
|
cap, err := startCapture(context.Background(), c, t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err = cap.Stop(context.Background(), "id\" || ip"); err == nil {
|
|
t.Fatal("unsafe filter correlation accepted")
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
if _, err = startCapture(ctx, c, t.TempDir()); err == nil {
|
|
t.Fatal("cancelled capture accepted")
|
|
}
|
|
}
|