Skip to content
Merged
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
37 changes: 36 additions & 1 deletion layers/sip.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ func decodeSIP(data []byte, p gopacket.PacketBuilder) error {
func NewSIP() *SIP {
s := new(SIP)
s.Headers = make(map[string][]string)
s.contentLength = -1
return s
}

Expand Down Expand Up @@ -296,11 +297,41 @@ func (s *SIP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {

countLines++
}
s.BaseLayer = BaseLayer{Contents: data[:offset], Payload: data[offset:]}
s.setBaseLayer(data, offset, df)

return nil
}

// setBaseLayer is used to set the base layer of the SIP packet.
//
// According to "RFC3261 - 20.14 - Content-Length" the Content-Length header is mandatory
// for SIP packages transported over TCP. When transporting over UDP, the message
// body length CAN be determined by the length of the UDP datagram.
//
// So by using the Content-Length header, if present we can mark the packet as truncated if we do not
// have enough data. We are also able to limit the payload to the number bytes actually specified.
// If the header is not present, we can assume that the data was transported over UDP we can then
// use the length of the data as payload length.
func (s *SIP) setBaseLayer(data []byte, offset int, df gopacket.DecodeFeedback) {
// The content-length header was not present in the packet, we use the rest of the packet as payload
if s.contentLength == -1 {
s.BaseLayer = BaseLayer{Contents: data[:offset], Payload: data[offset:]}
} else if s.contentLength == 0 {
// We have a zero Content-Length, no payload
s.BaseLayer = BaseLayer{Contents: data[:offset], Payload: []byte{}} // no payload
} else if len(data) < offset+int(s.contentLength) {
// Not enough data to fulfill the Content-Length. We set the packet as truncated
// and return what we have. The receiver of the packet will be able to determine this
// by comparing the SIP.ContentLength with the length of the SIP.Payload.
df.SetTruncated()
s.BaseLayer = BaseLayer{Contents: data[:offset], Payload: data[offset:]}
} else {
// we have at least enough data, to fulfill the Content-Length. But we only add the number
// of bytes specified in the Content-Length header to the payload.
s.BaseLayer = BaseLayer{Contents: data[:offset], Payload: data[offset : offset+int(s.contentLength)]}
}
}

// ParseFirstLine will compute the first line of a SIP packet.
// The first line will tell us if it's a request or a response.
//
Expand Down Expand Up @@ -529,6 +560,10 @@ func (s *SIP) GetUserAgent() string {
// GetContentLength will return the parsed integer
// Content-Length header of the current SIP packet
func (s *SIP) GetContentLength() int64 {
// to keep compatibility with previous versions, we return 0 if contentLength is not set by header
if s.contentLength == -1 {
return 0
}
return s.contentLength
}

Expand Down
106 changes: 101 additions & 5 deletions layers/sip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package layers

import (
"bytes"
"testing"

"github.com/gopacket/gopacket"
Expand Down Expand Up @@ -145,20 +146,105 @@ var testPacketSIPCompactInvite = []byte{
0x30, 0x3e, 0x0d, 0x0a, 0x6c, 0x3a, 0x30, 0x0d, 0x0a, 0x0d, 0x0a,
}

// Fourth packet is an INVITE with a payload
// This only contains the SIP Content and Payload, for more readability of the SIP layer
var testPacketSIPOnlyInviteWithPayload = []byte(
"INVITE sip:sip.provider.com SIP/2.0\r\n" +
"Via: SIP/2.0/TCP 172.16.254.66:5060;branch=z9hG4bK.xIOVvIsyy;rport\r\n" +
"From: \"Bob\" <sip:[email protected]>\r\n" +
"To: \"Alice\" <sip:[email protected]>\r\n" +
"Call-Id: 306366781@172_16_254_66\r\n" +
"CSeq: 1 INVITE\r\n" +
"Allow: INVITE,ACK,CANCEL,BYE,OPTIONS,INFO,SUBSCRIBE,NOTIFY,REFER,UPDATE\r\n" +
"Contact: <sip:[email protected]:5060>\r\n" +
"Content-Type: application/sdp\r\n" +
"Content-Length: 100\r\n" +
"\r\n" +
"v=0\r\n" +
"o=bob 4096 1976 IN IP4 172.16.254.66\r\n" +
"s=Talk\r\n" +
"c=IN 172.16.254.66\r\n" +
"t=0 0\r\n" +
"m=audio 6000 RTP/AVP 0",
)

// Fifth packet is an INVITE with an over-sized payload
// This packet is generated by appending an extra attribute to the payload of the previous packet
var testOverSizedPacketSIPOnlyInviteWithPayload = append(testPacketSIPOnlyInviteWithPayload, []byte("\r\na=This_is_beyond_the_content_length")...)

// Sixth packet is an INVITE identical to the fifth packet, except the 'Content-Length' is absent.
var testOverSizedPacketSIPOnlyInviteWithPayloadNoLength = bytes.Replace(testOverSizedPacketSIPOnlyInviteWithPayload, []byte("Content-Length: 100\r\n"), []byte{}, 1)

// Seventh packet is an INVITE with a payload but no Content-Length header
// This only contains the SIP Content and Payload, for more readability of the SIP layer
var testPacketSIPOnlyInviteWithPayloadNoContentLength = []byte(
"INVITE sip:sip.provider.com SIP/2.0\r\n" +
"Via: SIP/2.0/TCP 172.16.254.66:5060;branch=z9hG4bK.xIOVvIsyy;rport\r\n" +
"From: \"Bob\" <sip:[email protected]>\r\n" +
"To: \"Alice\" <sip:[email protected]>\r\n" +
"Call-Id: 306366781@172_16_254_66\r\n" +
"CSeq: 1 INVITE\r\n" +
"Allow: INVITE,ACK,CANCEL,BYE,OPTIONS,INFO,SUBSCRIBE,NOTIFY,REFER,UPDATE\r\n" +
"Contact: <sip:[email protected]:5060>\r\n" +
"Content-Type: application/sdp\r\n" +
"\r\n" +
"v=0\r\n" +
"o=bob 4096 1976 IN IP4 172.16.254.66\r\n" +
"s=Talk\r\n" +
"c=IN 172.16.254.66\r\n" +
"t=0 0\r\n" +
"m=audio 6000 RTP/AVP 0",
)

// Eighth packet is an INVITE with no payload and no Content-Length header
// This only contains the SIP Content and Payload, for more readability of the SIP layer
var testPacketSIPOnlyInviteNoContentLength = []byte(
"INVITE sip:sip.provider.com SIP/2.0\r\n" +
"Via: SIP/2.0/TCP 172.16.254.66:5060;branch=z9hG4bK.xIOVvIsyy;rport\r\n" +
"From: \"Bob\" <sip:[email protected]>\r\n" +
"To: \"Alice\" <sip:[email protected]>\r\n" +
"Call-Id: 306366781@172_16_254_66\r\n" +
"CSeq: 1 INVITE\r\n" +
"Allow: INVITE,ACK,CANCEL,BYE,OPTIONS,INFO,SUBSCRIBE,NOTIFY,REFER,UPDATE\r\n" +
"Contact: <sip:[email protected]:5060>\r\n" +
"Content-Type: application/sdp\r\n" +
"\r\n",
)

func TestSIPMain(t *testing.T) {
expectedHeaders := map[string]string{"Call-ID": "306366781@172_16_254_66", "Contact": "<sip:[email protected]:5060>"}
_TestPacketSIP(t, testPacketSIPRequest, SIPMethodRegister, false, 3, expectedHeaders, "sip:sip.provider.com")
_TestPacketSIP(t, LinkTypeEthernet, testPacketSIPRequest, SIPMethodRegister, false, 3, 0, expectedHeaders, "sip:sip.provider.com", []byte{})

expectedHeaders = map[string]string{"Call-ID": "306366781@172_16_254_66", "Contact": "<sip:[email protected]:5060>;expires=1800"}
_TestPacketSIP(t, testPacketSIPResponse, SIPMethodRegister, true, 3, expectedHeaders, "")
_TestPacketSIP(t, LinkTypeEthernet, testPacketSIPResponse, SIPMethodRegister, true, 3, 0, expectedHeaders, "", []byte{})

expectedHeaders = map[string]string{"Call-ID": "306366781@172_16_254_66", "Contact": "<sip:[email protected]:5060>", "f": "\"Bob\" <sip:[email protected]>"}
_TestPacketSIP(t, testPacketSIPCompactInvite, SIPMethodInvite, false, 1, expectedHeaders, "sip:sip.provider.com")
_TestPacketSIP(t, LinkTypeEthernet, testPacketSIPCompactInvite, SIPMethodInvite, false, 1, 0, expectedHeaders, "sip:sip.provider.com", []byte{})

expectedHeaders = map[string]string{"Call-ID": "306366781@172_16_254_66", "Contact": "<sip:[email protected]:5060>", "From": "\"Bob\" <sip:[email protected]>", "Content-Type": "application/sdp", "Content-Length": "100"}
expectedPayload := []byte("v=0\r\no=bob 4096 1976 IN IP4 172.16.254.66\r\ns=Talk\r\nc=IN 172.16.254.66\r\nt=0 0\r\nm=audio 6000 RTP/AVP 0")
_TestPacketSIP(t, LayerTypeSIP, testPacketSIPOnlyInviteWithPayload, SIPMethodInvite, false, 1, 100, expectedHeaders, "sip:sip.provider.com", expectedPayload)

expectedHeaders = map[string]string{"Call-ID": "306366781@172_16_254_66", "Contact": "<sip:[email protected]:5060>", "From": "\"Bob\" <sip:[email protected]>", "Content-Type": "application/sdp", "Content-Length": "100"}
expectedPayload = []byte("v=0\r\no=bob 4096 1976 IN IP4 172.16.254.66\r\ns=Talk\r\nc=IN 172.16.254.66\r\nt=0 0\r\nm=audio 6000 RTP/AVP 0")
_TestPacketSIP(t, LayerTypeSIP, testOverSizedPacketSIPOnlyInviteWithPayload, SIPMethodInvite, false, 1, 100, expectedHeaders, "sip:sip.provider.com", expectedPayload)

expectedHeaders = map[string]string{"Call-ID": "306366781@172_16_254_66", "Contact": "<sip:[email protected]:5060>", "From": "\"Bob\" <sip:[email protected]>", "Content-Type": "application/sdp"}
expectedPayload = []byte("v=0\r\no=bob 4096 1976 IN IP4 172.16.254.66\r\ns=Talk\r\nc=IN 172.16.254.66\r\nt=0 0\r\nm=audio 6000 RTP/AVP 0\r\na=This_is_beyond_the_content_length")
_TestPacketSIP(t, LayerTypeSIP, testOverSizedPacketSIPOnlyInviteWithPayloadNoLength, SIPMethodInvite, false, 1, 0, expectedHeaders, "sip:sip.provider.com", expectedPayload)

expectedHeaders = map[string]string{"Call-ID": "306366781@172_16_254_66", "Contact": "<sip:[email protected]:5060>", "From": "\"Bob\" <sip:[email protected]>", "Content-Type": "application/sdp"}
expectedPayload = []byte("v=0\r\no=bob 4096 1976 IN IP4 172.16.254.66\r\ns=Talk\r\nc=IN 172.16.254.66\r\nt=0 0\r\nm=audio 6000 RTP/AVP 0")
_TestPacketSIP(t, LayerTypeSIP, testPacketSIPOnlyInviteWithPayloadNoContentLength, SIPMethodInvite, false, 1, 0, expectedHeaders, "sip:sip.provider.com", expectedPayload)

expectedHeaders = map[string]string{"Call-ID": "306366781@172_16_254_66", "Contact": "<sip:[email protected]:5060>", "From": "\"Bob\" <sip:[email protected]>", "Content-Type": "application/sdp"}
expectedPayload = []byte("")
_TestPacketSIP(t, LayerTypeSIP, testPacketSIPOnlyInviteNoContentLength, SIPMethodInvite, false, 1, 0, expectedHeaders, "sip:sip.provider.com", expectedPayload)
}

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

p := gopacket.NewPacket(packetData, LinkTypeEthernet, gopacket.Default)
p := gopacket.NewPacket(packetData, firstLayerDecoder, gopacket.Default)
if p.ErrorLayer() != nil {
t.Error("Failed to decode packet:", p.ErrorLayer().Error())
}
Expand Down Expand Up @@ -193,5 +279,15 @@ func _TestPacketSIP(t *testing.T, packetData []byte, methodWanted SIPMethod, isR
if got.GetCSeq() != wantedCseq {
t.Errorf("SIP Packet should be %d. Got : %d", wantedCseq, got.GetCSeq())
}

// Check content length
if got.GetContentLength() != contentLength {
t.Errorf("SIP Payload length should be %d. Got : %d", contentLength, got.GetContentLength())
}

// Check payload
if bytes.Compare(got.Payload(), expectedPayload) != 0 {
t.Errorf("SIP Payload should be:\n\t%v\nGot:\n\t%v", expectedPayload, got.Payload())
}
}
}