commit ef08e53ee37488a036de6aacf1895d843ab065a0
parent 707b0c4f5fd7c7acd55fa9e93b8f7344e3e83276
Author: Oliver Lowe <o@olowe.co>
Date: Sat, 22 Mar 2025 17:16:56 +1100
sip: validate Via header field on writing requests
Diffstat:
2 files changed, 50 insertions(+), 7 deletions(-)
diff --git a/internal/sip/sip.go b/internal/sip/sip.go
@@ -38,13 +38,32 @@ type Request struct {
const magicViaCookie = "z9hG4bK"
+const (
+ TransportUDP int = iota
+ TransportTCP
+)
+
+// Via represents the Via field in the header of requests.
type Via struct {
+ // Transport indicates whether TCP or UDP should be used in
+ // subsequent transactions.
+ Transport int
+ // Address is a hostname or IP address to which responses
+ // should be sent.
Address string
- Branch string
+ // Branch uniquely identifies transactions from a particular user-agent.
+ Branch string
}
func (v Via) String() string {
- return fmt.Sprintf("SIP/2.0/UDP %s;branch=%s%s", v.Address, magicViaCookie, v.Branch)
+ tport := "unknown"
+ switch v.Transport {
+ case TransportUDP:
+ tport = "UDP"
+ case TransportTCP:
+ tport = "TCP"
+ }
+ return fmt.Sprintf("SIP/2.0/%s %s;branch=%s%s", tport, v.Address, magicViaCookie, v.Branch)
}
func ReadRequest(r io.Reader) (*Request, error) {
@@ -83,6 +102,13 @@ func WriteRequest(w io.Writer, req *Request) (n int64, err error) {
return 0, fmt.Errorf("missing field %s in header", s)
}
}
+ if req.Via.Address == "" {
+ return 0, fmt.Errorf("empty address in via header field")
+ } else if req.Via.Branch == "" {
+ return 0, fmt.Errorf("empty branch in via header field")
+ }
+
+ req.Header.Set("Via", req.Via.String())
if req.Header.Get("Max-Forwards") == "" {
// TODO(otl): find section in RFC recommending 70.
// section x.x.x
@@ -91,7 +117,6 @@ func WriteRequest(w io.Writer, req *Request) (n int64, err error) {
if req.ContentLength > 0 {
req.Header.Set("Content-Length", strconv.Itoa(int(req.ContentLength)))
}
- req.Header.Set("Via", req.Via.String())
buf := &bytes.Buffer{}
fmt.Fprintf(buf, "%s %s SIP/2.0\r\n", req.Method, req.URI)
diff --git a/internal/sip/sip_test.go b/internal/sip/sip_test.go
@@ -1,13 +1,31 @@
package sip
import (
- "fmt"
+ "io"
+ "net/textproto"
"os"
"strings"
"testing"
)
-func TestRequest(t *testing.T) {
+func TestWriteRequest(t *testing.T) {
+ header := make(textproto.MIMEHeader)
+ header.Set("Call-ID", "blabla")
+ header.Set("To", "test <sip:test@example.com>")
+ header.Set("From", "Oliver <sip:o@olowe.co>")
+ header.Set("CSeq", "1 "+MethodRegister)
+ req := &Request{
+ Method: MethodRegister,
+ URI: "sip:test@example.com",
+ Header: header,
+ }
+ _, err := WriteRequest(io.Discard, req)
+ if err == nil {
+ t.Errorf("no error writing request with zero Via field")
+ }
+}
+
+func TestReadRequest(t *testing.T) {
f, err := os.Open("testdata/invite")
if err != nil {
t.Fatal(err)
@@ -17,6 +35,7 @@ func TestRequest(t *testing.T) {
if err != nil {
t.Fatal("read request:", err)
}
+
}
func TestResponse(t *testing.T) {
@@ -40,9 +59,8 @@ Content-Length: 131
if err != nil {
t.Fatal("read message:", err)
}
- resp, err := parseResponse(msg)
+ _, err = parseResponse(msg)
if err != nil {
t.Fatalf("parse response: %v", err)
}
- fmt.Printf("%+v\n", resp)
}