Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions examples/pfdump/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ package main
import (
"flag"
"fmt"
"github.com/google/gopacket/dumpcommand"
"github.com/google/gopacket/examples/util"
"github.com/google/gopacket/pfring"
"log"
"os"
"strings"

"github.com/google/gopacket/dumpcommand"
"github.com/google/gopacket/examples/util"
"github.com/google/gopacket/pfring"
)

var iface = flag.String("i", "eth0", "Interface to read packets from")
Expand Down
97 changes: 97 additions & 0 deletions layers/sip.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,103 @@ type SIP struct {
lastHeaderParsed string
}

// SerializeTo serializes the SIP layer into the given buffer, implementing gopacket.SerializableLayer.
func (s *SIP) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
var buf bytes.Buffer

// --- First line ---
if s.IsResponse {
// Response: SIP/2.0 200 OK
if s.Version == 0 {
s.Version = SIPVersion2 // default
}
if s.ResponseCode == 0 || s.ResponseStatus == "" {
return fmt.Errorf("SIP SerializeTo: missing response code or status")
}
fmt.Fprintf(&buf, "%s %d %s\r\n", s.Version.String(), s.ResponseCode, s.ResponseStatus)
} else {
// Request: INVITE space@example.com SIP/2.0
if s.Method == 0 || s.RequestURI == "" {
return fmt.Errorf("SIP SerializeTo: missing method or RequestURI")
}
version := s.Version.String()
if version == "" {
version = "SIP/2.0"
}
fmt.Fprintf(&buf, "%s %s %s\r\n", s.Method.String(), s.RequestURI, version)
}

// --- Headers ---
// Canonical SIP header order (common important headers first)
canonicalOrder := []string{
"via", "from", "to", "call-id", "cseq", "contact", "max-forwards", "content-type", "content-length",
}
written := make(map[string]bool)

// Write canonical headers first if present
for _, h := range canonicalOrder {
vals := s.GetHeader(h)
if len(vals) > 0 {
for _, v := range vals {
fmt.Fprintf(&buf, "%s: %s\r\n", canonicalHeaderName(h), v)
}
written[strings.ToLower(h)] = true
}
}
// Write remaining headers
for h, vals := range s.Headers {
lh := strings.ToLower(h)
if written[lh] {
continue
}
for _, v := range vals {
fmt.Fprintf(&buf, "%s: %s\r\n", canonicalHeaderName(h), v)
}
}

// --- Content-Length ---
payload := s.Payload()
if opts.FixLengths {
// Always set Content-Length if FixLengths is requested
plen := len(payload)
// Remove any existing content-length header
delete(s.Headers, "content-length")
fmt.Fprintf(&buf, "Content-Length: %d\r\n", plen)
}

// --- End headers ---
buf.WriteString("\r\n")

// --- Payload (body) ---
if len(payload) > 0 {
buf.Write(payload)
}

// Write to gopacket buffer
out, err := b.PrependBytes(buf.Len())
if err != nil {
return err
}
copy(out, buf.Bytes())
return nil
}

// canonicalHeaderName returns the compact or canonical SIP header name.
func canonicalHeaderName(h string) string {
lh := strings.ToLower(h)
if short, ok := compactSipHeadersCorrespondance[lh]; ok {
return short
}
// Capitalize first letter and after dashes (standard SIP style)
parts := strings.Split(lh, "-")
for i, p := range parts {
if len(p) > 0 {
parts[i] = strings.ToUpper(p[:1]) + p[1:]
}
}
return strings.Join(parts, "-")
}

// decodeSIP decodes the byte slice into a SIP type. It also
// setups the application Layer in PacketBuilder.
func decodeSIP(data []byte, p gopacket.PacketBuilder) error {
Expand Down
78 changes: 76 additions & 2 deletions layers/sip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,15 +158,14 @@ func TestSIPMain(t *testing.T) {
_TestPacketSIP(t, testPacketSIPCompactInvite, SIPMethodInvite, false, 1, expectedHeaders, "sip:sip.provider.com")
}

func _TestPacketSIP(t *testing.T, packetData []byte, methodWanted SIPMethod, isResponse bool, wantedCseq int64, expectedHeaders map[string]string, expectedRequestURI string) {

func _TestPacketSIP(t *testing.T, packetData []byte, methodWanted SIPMethod, isResponse bool, wantedCseq int64, expectedHeaders map[string]string, expectedRequestURI string) {
p := gopacket.NewPacket(packetData, LinkTypeEthernet, gopacket.Default)
if p.ErrorLayer() != nil {
t.Error("Failed to decode packet:", p.ErrorLayer().Error())
}

if got, ok := p.Layer(LayerTypeSIP).(*SIP); ok {

// Check method
if got.Method != methodWanted {
t.Errorf("SIP Packet should be a %s method, got : %s", methodWanted, got.Method)
Expand Down Expand Up @@ -197,3 +196,78 @@ func _TestPacketSIP(t *testing.T, packetData []byte, methodWanted SIPMethod, isR
}
}
}

func TestSIPSerializeTo_Request(t *testing.T) {
sip := &SIP{
Method: SIPMethodInvite,
RequestURI: "sip:alice@example.com",
Version: SIPVersion2,
Headers: map[string][]string{
"Via": {"SIP/2.0/UDP host:5060;branch=z9hG4bK"},
"From": {"\"Bob\" <sip:bob@example.com>;tag=123"},
"To": {"\"Alice\" <sip:alice@example.com>"},
"Call-ID": {"callid123"},
"CSeq": {"1 INVITE"},
"Contact": {"<sip:bob@host:5060>"},
"Max-Forwards": {"70"},
"Content-Type": {"application/sdp"},
},
BaseLayer: BaseLayer{Payload: []byte("body")},
}
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{FixLengths: true}
if err := sip.SerializeTo(buf, opts); err != nil {
t.Fatalf("SerializeTo failed: %v", err)
}
serialized := buf.Bytes()

// Parse back
parsed := NewSIP()
if err := parsed.DecodeFromBytes(serialized, nil); err != nil {
t.Fatalf("DecodeFromBytes failed: %v", err)
}
if parsed.Method != SIPMethodInvite || parsed.RequestURI != "sip:alice@example.com" {
t.Errorf("Round-trip method/URI mismatch: got %v %v", parsed.Method, parsed.RequestURI)
}
if parsed.GetFirstHeader("Call-ID") != "callid123" {
t.Errorf("Header mismatch: got %v", parsed.GetFirstHeader("Call-ID"))
}
if string(parsed.Payload()) != "body" {
t.Errorf("Payload mismatch: got %q", string(parsed.Payload()))
}
}

func TestSIPSerializeTo_Response(t *testing.T) {
sip := &SIP{
IsResponse: true,
Version: SIPVersion2,
ResponseCode: 200,
ResponseStatus: "OK",
Headers: map[string][]string{
"Via": {"SIP/2.0/UDP host:5060;branch=z9hG4bK"},
"From": {"\"Bob\" <sip:bob@example.com>;tag=123"},
"To": {"\"Alice\" <sip:alice@example.com>"},
"Call-ID": {"callid123"},
"CSeq": {"1 INVITE"},
"Contact": {"<sip:bob@host:5060>"},
},
BaseLayer: BaseLayer{Payload: nil},
}
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{FixLengths: true}
if err := sip.SerializeTo(buf, opts); err != nil {
t.Fatalf("SerializeTo failed: %v", err)
}
serialized := buf.Bytes()

parsed := NewSIP()
if err := parsed.DecodeFromBytes(serialized, nil); err != nil {
t.Fatalf("DecodeFromBytes failed: %v", err)
}
if !parsed.IsResponse || parsed.ResponseCode != 200 || parsed.ResponseStatus != "OK" {
t.Errorf("Round-trip response mismatch: got %v %v %v", parsed.IsResponse, parsed.ResponseCode, parsed.ResponseStatus)
}
if parsed.GetFirstHeader("Call-ID") != "callid123" {
t.Errorf("Header mismatch: got %v", parsed.GetFirstHeader("Call-ID"))
}
}