102 lines
4.2 KiB
Go
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()
|
|
}
|