Files
go-sip/internal/sipcall/offline_decoder_test.go
T

102 lines
4.2 KiB
Go

package sipcall
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// This opt-in test uses real tcpdump/tshark only as OFFLINE decoders inside a
// network-disabled disposable container. Packets are synthetic documentation
// addresses: no host capture, SIP traffic, human audio or live service calls.
func TestOfflineRealPacketDecoders(t *testing.T) {
image := os.Getenv("GO_SIP_TSHARK_TEST_IMAGE")
if image == "" {
t.Skip("offline decoder image opt-in not supplied")
}
docker, err := exec.LookPath("docker")
if err != nil {
t.Fatal(err)
}
tools := fakeTools(t, false)
dir := t.TempDir()
source := filepath.Join(tools, "synthetic.pcap")
if err = os.WriteFile(source, syntheticPCAP(), 0600); err != nil {
t.Fatal(err)
}
script, err := os.ReadFile(filepath.Join(tools, "tcpdump"))
if err != nil {
t.Fatal(err)
}
script = bytes.Replace(script, []byte("/bin/dd if=/dev/zero of=\"$out\" bs=24 count=1 2>/dev/null"), []byte("/bin/cp '"+source+"' \"$out\""), 1)
if err = os.WriteFile(filepath.Join(tools, "tcpdump"), script, 0700); err != nil {
t.Fatal(err)
}
decoder := fmt.Sprintf("#!/bin/sh\nexec '%s' run --rm --network none -v '%s:%s:ro' '%s' tshark \"$@\"\n", docker, dir, dir, image)
if err = os.WriteFile(filepath.Join(tools, "tshark"), []byte(decoder), 0700); err != nil {
t.Fatal(err)
}
c := fixtureConfig(t)
cap, err := startCapture(context.Background(), c, dir)
if err != nil {
t.Fatal(err)
}
e, err := cap.Stop(context.Background(), "sip-id-123")
if err != nil {
t.Fatal(err)
}
if e.Packets != 3 || e.FinalINVITECode != 200 || e.RTPFromServer != 1 || e.RTPToServer != 1 {
t.Fatalf("real offline decoding mismatch: %+v", e)
}
cmd := exec.Command(docker, "run", "--rm", "--network", "none", "-v", dir+":"+dir+":ro", image, "tcpdump", "-nn", "-r", e.PCAP)
if out, err := cmd.CombinedOutput(); err != nil || !strings.Contains(string(out), "INVITE") {
t.Fatalf("real tcpdump offline inspection failed: %v %s", err, out)
}
}
// Static fixture construction, not a runtime SIP/RTP stack. Checksums are
// disabled in these synthetic packets; no packet is ever sent to a socket.
func syntheticPCAP() []byte {
var pcap bytes.Buffer
for _, v := range []any{uint32(0xa1b2c3d4), uint16(2), uint16(4), uint32(0), uint32(0), uint32(65535), uint32(1)} {
_ = binary.Write(&pcap, binary.LittleEndian, v)
}
sdp := func(ip string, port int) string {
return fmt.Sprintf("v=0\r\no=- 1 1 IN IP4 %s\r\ns=offline\r\nc=IN IP4 %s\r\nt=0 0\r\nm=audio %d RTP/AVP 8\r\na=rtpmap:8 PCMA/8000\r\n", ip, ip, port)
}
message := func(first, cseq, branch, body string) []byte {
return []byte(fmt.Sprintf("%s\r\nVia: SIP/2.0/UDP 192.0.2.1:5060;branch=%s\r\nFrom: <sip:test@192.0.2.1>;tag=offline\r\nTo: <sip:123@192.0.2.10>\r\nCall-ID: sip-id-123\r\nCSeq: %s\r\nContent-Type: application/sdp\r\nContent-Length: %d\r\n\r\n%s", first, branch, cseq, len(body), body))
}
add := func(src, dst byte, sport, dport uint16, payload []byte) {
packet := make([]byte, 14+20+8+len(payload))
packet[12], packet[13] = 8, 0
ip := packet[14:34]
ip[0] = 0x45
binary.BigEndian.PutUint16(ip[2:], uint16(20+8+len(payload)))
ip[8], ip[9] = 64, 17
copy(ip[12:], []byte{192, 0, 2, src})
copy(ip[16:], []byte{192, 0, 2, dst})
udp := packet[34:42]
binary.BigEndian.PutUint16(udp, sport)
binary.BigEndian.PutUint16(udp[2:], dport)
binary.BigEndian.PutUint16(udp[4:], uint16(8+len(payload)))
copy(packet[42:], payload)
for _, v := range []uint32{1, uint32(pcap.Len()), uint32(len(packet)), uint32(len(packet))} {
_ = binary.Write(&pcap, binary.LittleEndian, v)
}
pcap.Write(packet)
}
add(1, 10, 5060, 5060, message("INVITE sip:123@192.0.2.10 SIP/2.0", "10 INVITE", "z9hG4bK-test", sdp("192.0.2.1", 10000)))
add(10, 1, 5060, 5060, message("SIP/2.0 200 OK", "10 INVITE", "z9hG4bK-test", sdp("192.0.2.10", 12000)))
add(10, 1, 5060, 5060, message("SIP/2.0 200 OK", "11 BYE", "z9hG4bK-bye", ""))
add(10, 1, 12000, 10000, append([]byte{0x80, 8, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1}, bytes.Repeat([]byte{0xd5}, 160)...))
add(1, 10, 10000, 12000, append([]byte{0x80, 8, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2}, bytes.Repeat([]byte{0xd5}, 160)...))
return pcap.Bytes()
}