Browse Source

New SIP message parser and better SIP dialog handling.

pull/2/head
Justine Alexandra Roberts Tunney 11 years ago
parent
commit
9271e6f9b7
16 changed files with 26718 additions and 609 deletions
  1. +34
    -55
      README.md
  2. +7
    -9
      example/echo/echo_test.go
  3. +4
    -4
      example/echo2/echo2_test.go
  4. +6
    -7
      example/echo3/echo3_test.go
  5. +1
    -1
      example/options/options_test.go
  6. +368
    -154
      sip/dialog.go
  7. +14
    -0
      sip/errors.go
  8. +38
    -39
      sip/messages.go
  9. +333
    -263
      sip/msg.go
  10. +25259
    -0
      sip/msg_parse.go
  11. +476
    -0
      sip/msg_parse.rl
  12. +117
    -29
      sip/msg_test.go
  13. +24
    -26
      sip/route.go
  14. +5
    -15
      sip/transport.go
  15. +23
    -0
      sip/util.go
  16. +9
    -7
      sip/via.go

+ 34
- 55
README.md View File

@ -1,64 +1,43 @@
# gosip
Version: 0.1
Copyright: Copyright (c) 2010-2014 Justine Tunney
License: MIT
Copyright (c) 2010-2014 Justine Alexandra Roberts Tunney
## About
gosip (pronounced like the word "gossip") is a VoIP telephony library
written in Google's Go programming language.
gosip provides a barebones sip/sdp/rtp implementation suitable for use
over a trusted network. To talk to the outside world you should
deploy your gosip applications behind a session border controller
(like tube, FreeSWITCH, or OpenSER) which are more capable of dealing
with security, network quality issues and SIP interop.
I was originally going to write bindings for sofia-sip but ultimately
decided it'd be quicker, less buggy, and faster performing to write a
lightweight SIP stack from scratch.
## Installation
Once Go is installed, just run ``make`` to build/test/install.
## Learning
The following unit tests also serve as tutorials to help you
understand SIP and the abstractions provided by this library.
- sip/rawsip_test.go: How to do SIP the hard way
- sip/url_test.go: Shows you what the SIP URL data structures look like
- sip/addr_test.go: Addresses are pretty much URLs inside angle brackets
- sip/msg_test.go: How the data structure for SIP packets works
- sip/manualsip_test.go: Make SIP easier with parser/formatter objects
- sip/echo_test.go: Manually make a test call to an echo application
## Overview
This is what a sip stack looks like:
+-----------------------------------------------------------------------+
| 9. Application Layer (your code) |
+-----------------------------------------------------------------------+
| 8. Telephony API (tel/...) |
+---------------------------------------+-------------------------------+
| 6. SIP Transaction (sip/transact.go) | 6. Media Codecs (sip/dsp.go) |
+---------------------------------------+-------------------------------+
| 5. SIP Transport (sip/transport.go) | 5. RTP Transport (sip/rtp.go) |
+---------------------------------------+-------------------------------+
| 2/3/4. Network Transport Layer |
+-----------------------------------------------------------------------+
| 1. Tubes |
+-----------------------------------------------------------------------+
| 0. Electrons and Photons |
+-----------------------------------------------------------------------+
gosip (pronounced "gossip") is a library that lets you make phone calls using
the Go programming language. It provides a full-stack SIP/RTP implementation
that's tailored towards making calls over the PSTN through services such as
Flowroute.
gosip is most suitable for backend telephony applications. This is especially
true for apps that do interesting things with audio. gosip supports DSP out of
the box by providing SSE optimised audio mixing, an assembly implementation of
the µLaw codec (no other codecs are supported), and a comfort noise generator.
Telephony applications have traditionally been written on top of PBX systems
like Asterisk via an extension interface. Frameworks such as Adhearsion have
introduced further layers of abstraction to this unwieldy regime. These systems
are slow, difficult to administer, and in many cases superfluous to the needs
of the telephony app developer. gosip sets you free from Asterisk because you
can have everything in a single easy-to-deploy binary.
gosip has excellent support for SRV/NAPTR failover by way of timeouts, ICMP
refusal, and 502 Service Unavailable responses. It also supports SIP redirects
as well as changing the audio/signalling path mid-call. But most importantly,
gosip is lightweight enough that you can actually design your app to use a
single process for each phone call (assuming your app is in the audio path.) By
taking this non-monolithic approach to design, you can actually build a highly
available (five nines) global telephony service with zero interruptions during
software upgrades.
gosip parses SIP messages using the Ragel finite state machine compiler. Much
of the BNF was copied directly from the SIP RFC. This implementation approach
offers a fair amount of confidence that the parser is not only fast, but also
correct and secure.
gosip is less suitable for PBX, IVR, and VoIP reselling. For these things, you
should consider Asterisk, FreeSWITCH, or SER.
## RFCs


+ 7
- 9
example/echo/echo_test.go View File

@ -204,11 +204,9 @@ func TestCallToEchoApp(t *testing.T) {
Port: uint16(laddr.Port),
},
},
Headers: sip.Headers{
"Content-Type": "application/sdp",
"User-Agent": "gosip/1.o",
},
Payload: sdp.New(rtpaddr, sdp.ULAWCodec, sdp.DTMFCodec).String(),
ContentType: "application/sdp",
UserAgent: "gosip/1.o",
Payload: sdp.New(rtpaddr, sdp.ULAWCodec, sdp.DTMFCodec).String(),
}
// Turn invite message into a packet and send via UDP socket.
@ -231,7 +229,7 @@ func TestCallToEchoApp(t *testing.T) {
if err != nil {
t.Fatal("parse 100 trying", err)
}
if !msg.IsResponse || msg.Status != 100 || msg.Phrase != "Trying" {
if !msg.IsResponse() || msg.Status != 100 || msg.Phrase != "Trying" {
t.Fatal("didn't get 100 trying :[")
}
@ -246,10 +244,10 @@ func TestCallToEchoApp(t *testing.T) {
if err != nil {
t.Fatal("parse 200 ok:", err)
}
if !msg.IsResponse || msg.Status != 200 || msg.Phrase != "OK" {
if !msg.IsResponse() || msg.Status != 200 || msg.Phrase != "OK" {
t.Fatal("wanted 200 ok but got:", msg.Status, msg.Phrase)
}
if msg.Payload == "" || msg.Headers["Content-Type"] != "application/sdp" {
if msg.Payload == "" || msg.ContentType != "application/sdp" {
t.Fatal("200 ok didn't have sdp payload")
}
@ -350,7 +348,7 @@ func TestCallToEchoApp(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if !msg.IsResponse || msg.Status != 200 || msg.Phrase != "OK" {
if !msg.IsResponse() || msg.Status != 200 || msg.Phrase != "OK" {
t.Fatal("wanted bye response 200 ok but got:", msg.Status, msg.Phrase)
}
}

+ 4
- 4
example/echo2/echo2_test.go View File

@ -75,9 +75,9 @@ loop:
t.Fatal("RTP send failed:", err)
}
case msg = <-tp.C:
if msg.IsResponse {
if msg.IsResponse() {
if msg.Status >= sip.StatusOK && msg.CSeq == invite.CSeq {
err = tp.Send(sip.NewAck(invite, msg))
err = tp.Send(sip.NewAck(msg, invite))
if err != nil {
t.Fatal("SIP send failed:", err)
}
@ -108,7 +108,7 @@ loop:
t.Errorf("Got %d %s", msg.Status, msg.Phrase)
return
}
if msg.Headers["Content-Type"] == sdp.ContentType {
if msg.ContentType == sdp.ContentType {
log.Printf("Establishing media session")
ms, err := sdp.Parse(msg.Payload)
if err != nil {
@ -138,7 +138,7 @@ loop:
resendTimer = time.After(resendInterval)
case <-deathTimer:
if answered {
resend = sip.NewBye(invite, msg)
resend = sip.NewBye(invite, msg, nil)
} else {
resend = sip.NewCancel(invite)
}


+ 6
- 7
example/echo3/echo3_test.go View File

@ -14,11 +14,12 @@ import (
func TestCallToEchoApp(t *testing.T) {
invite := &sip.Msg{
Method: sip.MethodInvite,
Request: &sip.URI{User: "echo", Host: "127.0.0.1", Port: 5060},
}
// Create RTP audio session.
rs, err := rtp.NewSession("")
rs, err := rtp.NewSession("127.0.0.1")
if err != nil {
t.Fatal(err)
}
@ -57,7 +58,7 @@ func TestCallToEchoApp(t *testing.T) {
if err := rs.Send(&frame); err != nil {
t.Fatal("RTP send failed:", err)
}
case <-dl.OnErr:
case err := <-dl.OnErr:
t.Error(err)
return
case state := <-dl.OnState:
@ -65,6 +66,9 @@ func TestCallToEchoApp(t *testing.T) {
case sip.DialogAnswered:
answered = true
case sip.DialogHangup:
if !answered {
t.Error("Call didn't get answered!")
}
return
}
case ms := <-dl.OnSDP:
@ -73,9 +77,4 @@ func TestCallToEchoApp(t *testing.T) {
dl.Hangup <- true
}
}
// The dialog has shut down cleanly. Was it answered?
if !answered {
t.Error("Call didn't get answered!")
}
}

+ 1
- 1
example/options/options_test.go View File

@ -86,7 +86,7 @@ func TestOptions(t *testing.T) {
t.Fatal(err)
}
if !msg.IsResponse || msg.Status != 200 || msg.Phrase != "OK" {
if !msg.IsResponse() || msg.Status != 200 || msg.Phrase != "OK" {
t.Error("Not OK :[")
}
if options.CallID != msg.CallID {


+ 368
- 154
sip/dialog.go View File

@ -1,11 +1,11 @@
// SIP Transport Layer. Responsible for serializing messages to/from
// your network.
// SIP Dialog Transport.
package sip
import (
"bytes"
"errors"
"flag"
"github.com/jart/gosip/sdp"
"github.com/jart/gosip/util"
"log"
@ -14,15 +14,20 @@ import (
)
const (
DialogConnected = 1
DialogRinging = 2
DialogAnswered = 3
DialogHangup = 4
resendInterval = 200 * time.Millisecond
maxResends = 2
DialogInit = 0
DialogProceeding = 1
DialogRinging = 2
DialogAnswered = 3
DialogHangup = 4
resendInterval = 200 * time.Millisecond
maxResends = 2
)
// Dialog represents an outbound phone call.
var (
looseSignalling = flag.Bool("looseSignalling", false, "Permit SIP messages from servers other than the next hop.")
)
// Dialog represents an outbound SIP phone call.
type Dialog struct {
OnErr <-chan error
OnState <-chan int
@ -31,31 +36,34 @@ type Dialog struct {
}
type dialogState struct {
sock *net.UDPConn
sockMsgs <-chan *Msg
sockErrs <-chan error
errChan chan<- error
sdpChan chan<- *sdp.SDP
stateChan chan<- int
doHangupChan <-chan bool
routes *AddressRoute
invite *Msg
response *Msg
resend *Msg
resends int
timer <-chan time.Time
sockMsgs <-chan *Msg
sockErrs <-chan error
csockMsgs <-chan *Msg
csockErrs <-chan error
errChan chan<- error
sdpChan chan<- *sdp.SDP
stateChan chan<- int
doHangupChan <-chan bool
state int // Current state of the dialog.
dest string // Destination hostname (or IP).
addr string // Destination ip:port.
sock *net.UDPConn // Outbound message socket (connected for ICMP)
csock *net.UDPConn // Inbound socket for Contact field.
routes *AddressRoute // List of SRV addresses to attempt contacting.
invite *Msg // Our INVITE that established the dialog.
remote *Msg // Message from remote UA that established dialog.
request *Msg // Current outbound request message.
requestResends int // Number of REsends of message so far.
requestTimer <-chan time.Time // Resend timer for message.
response *Msg // Current outbound request message.
responseResends int // Number of REsends of message so far.
responseTimer <-chan time.Time // Resend timer for message.
lseq int // Local CSeq value.
rseq int // Remote CSeq value.
}
// NewDialog creates a phone call.
func NewDialog(invite *Msg) (dl *Dialog, err error) {
invite, host, port, err := RouteMessage(nil, nil, invite)
if err != nil {
return nil, err
}
routes, err := RouteAddress(host, port)
if err != nil {
return nil, err
}
errChan := make(chan error)
sdpChan := make(chan *sdp.SDP)
stateChan := make(chan int)
@ -66,7 +74,6 @@ func NewDialog(invite *Msg) (dl *Dialog, err error) {
stateChan: stateChan,
doHangupChan: doHangupChan,
invite: invite,
routes: routes,
}
go dls.run()
return &Dialog{
@ -77,57 +84,17 @@ func NewDialog(invite *Msg) (dl *Dialog, err error) {
}, nil
}
func (dls *dialogState) popRoute() bool {
if dls.routes == nil {
dls.errChan <- errors.New("failed to contact host")
return false
}
dls.cleanup()
conn, err := net.Dial("udp", dls.routes.Address)
dls.routes = dls.routes.Next
if err != nil {
log.Println("net.Dial() failed:", err)
return dls.popRoute()
}
dls.sock = conn.(*net.UDPConn)
laddr := conn.LocalAddr().(*net.UDPAddr)
lhost := laddr.IP.String()
lport := uint16(laddr.Port)
dls.invite.Via = &Via{
Host: lhost,
Port: lport,
Params: Params{"branch": util.GenerateBranch()},
}
dls.invite.Contact = &Addr{
Uri: &URI{
Scheme: "sip",
Host: lhost,
Port: lport,
Params: Params{"transport": "udp"},
},
}
PopulateMessage(nil, nil, dls.invite)
dls.resend = dls.invite
dls.timer = time.After(resendInterval)
dls.resends = 0
sockMsgs := make(chan *Msg)
sockErrs := make(chan error)
dls.sockMsgs = sockMsgs
dls.sockErrs = sockErrs
go ReceiveMessages(dls.invite.Contact, dls.sock, sockMsgs, sockErrs)
return dls.send(dls.resend)
}
func (dls *dialogState) run() {
defer dls.sabotage()
defer dls.cleanup()
if !dls.popRoute() {
if !dls.sendRequest(dls.invite, true) {
return
}
for {
select {
case err := <-dls.sockErrs:
if util.IsRefused(err) {
log.Printf("ICMP refusal: %s (%s)", dls.sock.RemoteAddr(), dls.dest)
if !dls.popRoute() {
return
}
@ -135,93 +102,271 @@ func (dls *dialogState) run() {
dls.errChan <- err
return
}
case <-dls.timer:
if dls.resends < maxResends {
if !dls.send(dls.resend) {
return
}
dls.resends++
dls.timer = time.After(resendInterval)
} else {
if !dls.popRoute() {
return
}
case err := <-dls.csockErrs:
dls.errChan <- err
return
case <-dls.requestTimer:
if !dls.resendRequest() {
return
}
case <-dls.responseTimer:
if !dls.resendResponse() {
return
}
case <-dls.doHangupChan:
if !dls.hangup() {
if !dls.sendHangup() {
return
}
case msg := <-dls.sockMsgs:
if msg.CallID != dls.invite.CallID {
continue
if !dls.handleMessage(msg) {
return
}
if msg.IsResponse {
if msg.Status >= StatusOK && msg.CSeq == dls.invite.CSeq {
if msg.Contact != nil {
if !dls.send(NewAck(dls.invite, msg)) {
return
}
}
if msg.Status > StatusOK {
dls.errChan <- errors.New(msg.Phrase)
return
}
}
switch msg.Status {
case StatusTrying:
dls.routes = nil
dls.timer = nil
dls.stateChan <- DialogConnected
case StatusRinging, StatusSessionProgress:
dls.stateChan <- DialogRinging
case StatusOK:
switch msg.CSeqMethod {
case dls.invite.Method:
if dls.response == nil {
dls.stateChan <- DialogAnswered
}
dls.response = msg
case MethodBye, MethodCancel:
dls.stateChan <- DialogHangup
return
default:
dls.errChan <- errors.New("Bad CSeq Method")
return
}
}
if msg.Headers["Content-Type"] == sdp.ContentType {
ms, err := sdp.Parse(msg.Payload)
if err != nil {
log.Println("Bad SDP payload:", err)
} else {
dls.sdpChan <- ms
}
}
} else {
if msg.MaxForwards <= 0 {
if !dls.send(NewResponse(msg, StatusTooManyHops)) {
return
}
dls.errChan <- errors.New("Froot loop detected")
return
}
switch msg.Method {
case MethodBye:
if !dls.send(NewResponse(msg, StatusOK)) {
return
}
dls.stateChan <- DialogHangup
return
}
case msg := <-dls.csockMsgs:
if !dls.handleMessage(msg) {
return
}
}
}
}
func (dls *dialogState) sendRequest(request *Msg, wantSRV bool) bool {
host, port, err := RouteMessage(nil, nil, request)
if err != nil {
dls.errChan <- err
return false
}
routes, err := RouteAddress(host, port, wantSRV)
if err != nil {
dls.errChan <- err
return false
}
dls.request = request
dls.routes = routes
dls.dest = host
return dls.popRoute()
}
func (dls *dialogState) popRoute() bool {
if dls.routes == nil {
dls.errChan <- errors.New("Failed to contact: " + dls.dest)
return false
}
dls.addr = dls.routes.Address
dls.routes = dls.routes.Next
if !dls.connect() {
return dls.popRoute()
}
PopulateMessage(nil, nil, dls.request)
dls.lseq = dls.request.CSeq
dls.requestResends = 0
dls.requestTimer = time.After(resendInterval)
return dls.send(dls.request)
}
func (dls *dialogState) connect() bool {
if dls.sock != nil && dls.sock.RemoteAddr().String() == dls.addr {
return true
}
// Create socket through which we send messages. This socket is connected to
// the remote address so we can receive ICMP unavailable errors. It also
// allows us to discover the appropriate IP address for this machine.
dls.cleanupSock()
conn, err := net.Dial("udp", dls.addr)
if err != nil {
log.Printf("net.Dial(udp, %s) failed: %s", dls.addr, err)
return false
}
dls.sock = conn.(*net.UDPConn)
dls.rseq = 0
dls.remote = nil
laddr := conn.LocalAddr().(*net.UDPAddr)
lhost := laddr.IP.String()
lport := uint16(laddr.Port)
dls.request.Via = &Via{
Host: lhost,
Port: lport,
Params: Params{"branch": util.GenerateBranch()},
}
sockMsgs := make(chan *Msg)
sockErrs := make(chan error)
dls.sockMsgs = sockMsgs
dls.sockErrs = sockErrs
go ReceiveMessages(dls.request.Contact, dls.sock, sockMsgs, sockErrs)
// But a connected UDP socket can only receive packets from a single host.
// SIP signalling paths can change depending on the environment, so we need
// to be able to accept packets from anyone.
if *looseSignalling {
if dls.csock == nil {
cconn, err := net.ListenPacket("udp", ":0")
if err != nil {
log.Printf("net.ListenPacket(udp, :0) failed: %s", err)
return false
}
dls.csock = cconn.(*net.UDPConn)
dls.request.Contact = &Addr{
Uri: &URI{
Scheme: "sip",
Host: lhost,
Port: uint16(dls.csock.LocalAddr().(*net.UDPAddr).Port),
Params: Params{"transport": "udp"},
},
}
} else {
dls.request.Contact.Uri.Host = lhost
}
csockMsgs := make(chan *Msg)
csockErrs := make(chan error)
dls.csockMsgs = csockMsgs
dls.csockErrs = csockErrs
go ReceiveMessages(dls.request.Contact, dls.csock, csockMsgs, csockErrs)
} else {
dls.request.Contact = &Addr{
Uri: &URI{
Scheme: "sip",
Host: lhost,
Port: lport,
Params: Params{"transport": "udp"},
},
}
}
return true
}
func (dls *dialogState) handleMessage(msg *Msg) bool {
if msg.VersionMajor != 2 || msg.VersionMinor != 0 {
if !dls.send(NewResponse(msg, StatusVersionNotSupported)) {
return false
}
dls.errChan <- errors.New("Remote UA is using a strange SIP version")
return false
}
if msg.CallID != dls.request.CallID {
log.Printf("Received message doesn't match dialog")
return dls.send(NewResponse(msg, StatusCallTransactionDoesNotExist))
}
if msg.IsResponse() {
return dls.handleResponse(msg)
} else {
return dls.handleRequest(msg)
}
}
func (dls *dialogState) handleResponse(msg *Msg) bool {
if !ResponseMatch(dls.request, msg) {
log.Println("Received response doesn't match transaction")
return true
}
if msg.Status >= StatusOK && dls.request.Method == MethodInvite {
if msg.Contact == nil {
dls.errChan <- errors.New("Remote UA sent >=200 response w/o Contact")
return false
}
if !dls.send(NewAck(msg, dls.request)) {
return false
}
}
dls.routes = nil
dls.requestTimer = nil
if msg.Status <= StatusOK {
dls.checkSDP(msg)
}
switch msg.Status {
case StatusTrying:
dls.transition(DialogProceeding)
case StatusRinging, StatusSessionProgress:
dls.transition(DialogRinging)
case StatusOK:
switch msg.CSeqMethod {
case MethodInvite:
if dls.remote == nil {
dls.transition(DialogAnswered)
}
dls.remote = msg
case MethodBye, MethodCancel:
dls.transition(DialogHangup)
return false
}
case StatusServiceUnavailable:
if dls.request == dls.invite {
log.Printf("Service unavailable: %s (%s)", dls.sock.RemoteAddr(), dls.dest)
return dls.popRoute()
} else {
dls.errChan <- &ResponseError{Msg: msg}
return false
}
case StatusMovedPermanently, StatusMovedTemporarily:
dls.invite.Request = msg.Contact.Uri
dls.invite.Route = nil
return dls.sendRequest(dls.invite, true)
default:
if msg.Status > StatusOK {
dls.errChan <- &ResponseError{Msg: msg}
return false
}
}
return true
}
func (dls *dialogState) handleRequest(msg *Msg) bool {
if msg.MaxForwards <= 0 {
if !dls.send(NewResponse(msg, StatusTooManyHops)) {
return false
}
dls.errChan <- errors.New("Remote froot loop detected")
return false
}
if dls.rseq == 0 {
dls.rseq = msg.CSeq
} else {
if msg.CSeq < dls.rseq {
// RFC 3261 mandates a 500 response for out of order requests.
return dls.send(NewResponse(msg, StatusInternalServerError))
}
dls.rseq = msg.CSeq
}
switch msg.Method {
case MethodBye:
if !dls.send(NewResponse(msg, StatusOK)) {
return false
}
dls.transition(DialogHangup)
return false
case MethodOptions: // Probably a keep-alive ping.
return dls.send(NewResponse(msg, StatusOK))
case MethodInvite: // Re-INVITEs are used to change the RTP or signalling path.
dls.remote = msg
dls.checkSDP(msg)
return dls.sendResponse(NewResponse(msg, StatusOK))
case MethodAck: // Re-INVITE response has been ACK'd.
dls.response = nil
dls.responseTimer = nil
return true
default:
return dls.send(NewResponse(msg, StatusMethodNotAllowed))
}
}
func (dls *dialogState) checkSDP(msg *Msg) {
if msg.Headers["Content-Type"] == sdp.ContentType {
ms, err := sdp.Parse(msg.Payload)
if err != nil {
log.Println("Bad SDP payload:", err)
} else {
dls.sdpChan <- ms
}
}
}
func (dls *dialogState) send(msg *Msg) bool {
// TODO(jart): Double-check route matches socket binding.
if msg.MaxForwards > 0 {
msg.MaxForwards--
if msg.MaxForwards == 0 {
dls.errChan <- errors.New("Local froot loop detected")
return false
}
}
ts := time.Now()
addTimestamp(msg, ts)
@ -238,21 +383,81 @@ func (dls *dialogState) send(msg *Msg) bool {
return true
}
func (dls *dialogState) hangup() bool {
if dls.response != nil {
dls.resend = NewBye(dls.invite, dls.response)
func (dls *dialogState) resendRequest() bool {
if dls.request == nil {
return true
}
if dls.requestResends < maxResends {
if !dls.send(dls.request) {
return false
}
dls.requestResends++
dls.requestTimer = time.After(resendInterval)
} else {
dls.resend = NewCancel(dls.invite)
log.Printf("Timeout: %s (%s)", dls.sock.RemoteAddr(), dls.dest)
if !dls.popRoute() {
return false
}
}
if !dls.send(dls.resend) {
return false
return true
}
// sendResponse is used to reliably send a response to an INVITE only.
func (dls *dialogState) sendResponse(msg *Msg) bool {
dls.response = msg
dls.responseResends = 0
dls.responseTimer = time.After(resendInterval)
return dls.send(dls.response)
}
func (dls *dialogState) resendResponse() bool {
if dls.response == nil {
return true
}
if dls.responseResends < maxResends {
if !dls.send(dls.response) {
return false
}
dls.responseResends++
dls.responseTimer = time.After(resendInterval)
} else {
// TODO(jart): If resending INVITE 200 OK, start sending BYE.
log.Printf("Timeout sending response: %s (%s)", dls.sock.RemoteAddr(), dls.dest)
if !dls.popRoute() {
return false
}
}
dls.resends = 0
dls.timer = time.After(resendInterval)
return true
}
func (dls *dialogState) sendHangup() bool {
switch dls.state {
case DialogProceeding, DialogRinging:
return dls.send(NewCancel(dls.invite))
case DialogAnswered:
return dls.sendRequest(NewBye(dls.invite, dls.remote, &dls.lseq), false)
case DialogHangup:
panic("Why didn't the event loop break?")
default:
// o A UA or proxy cannot send CANCEL for a transaction until it gets a
// provisional response for the request. This was allowed in RFC 2543
// but leads to potential race conditions.
dls.transition(DialogHangup)
return false
}
}
func (dls *dialogState) transition(state int) {
dls.state = state
dls.stateChan <- state
}
func (dls *dialogState) cleanup() {
dls.cleanupSock()
dls.cleanupCSock()
}
func (dls *dialogState) cleanupSock() {
if dls.sock != nil {
dls.sock.Close()
dls.sock = nil
@ -261,6 +466,15 @@ func (dls *dialogState) cleanup() {
}
}
func (dls *dialogState) cleanupCSock() {
if dls.csock != nil {
dls.csock.Close()
dls.csock = nil
_, _ = <-dls.csockMsgs
<-dls.csockErrs
}
}
func (dls *dialogState) sabotage() {
close(dls.errChan)
close(dls.sdpChan)


+ 14
- 0
sip/errors.go View File

@ -0,0 +1,14 @@
package sip
import (
"fmt"
)
// ResponseError encapsulates an unhandled >=400 SIP error response.
type ResponseError struct {
Msg *Msg
}
func (err *ResponseError) Error() string {
return fmt.Sprintf("%s: %d", err.Msg.Status, err.Msg.Phrase)
}

+ 38
- 39
sip/messages.go View File

@ -7,8 +7,8 @@ import (
)
const (
GosipUserAgent = "gosip/1.o"
GosipAllow = MethodInvite + ", " + MethodAck + ", " + MethodCancel + ", " + MethodBye + ", " + MethodOptions
GosipUA = "gosip/0.1"
GosipAllow = "INVITE, ACK, CANCEL, BYE, OPTIONS"
)
func NewRequest(tp *Transport, method string, to, from *Addr) *Msg {
@ -21,13 +21,12 @@ func NewRequest(tp *Transport, method string, to, from *Addr) *Msg {
CallID: util.GenerateCallID(),
CSeq: util.GenerateCSeq(),
CSeqMethod: method,
Headers: DefaultHeaders(),
UserAgent: GosipUA,
}
}
func NewResponse(msg *Msg, status int) *Msg {
return &Msg{
IsResponse: true,
Status: status,
Phrase: Phrase(status),
Via: msg.Via,
@ -37,28 +36,31 @@ func NewResponse(msg *Msg, status int) *Msg {
CSeq: msg.CSeq,
CSeqMethod: msg.CSeqMethod,
RecordRoute: msg.RecordRoute,
Headers: DefaultHeaders(),
UserAgent: GosipUA,
Allow: GosipAllow,
}
}
// http://tools.ietf.org/html/rfc3261#section-17.1.1.3
func NewAck(original, msg *Msg) *Msg {
func NewAck(msg, invite *Msg) *Msg {
return &Msg{
Method: MethodAck,
Request: msg.Contact.Uri,
Via: original.Via.Copy().SetNext(nil),
From: msg.From,
To: msg.To,
CallID: original.CallID,
CSeq: original.CSeq,
CSeqMethod: "ACK",
Route: msg.RecordRoute.Reversed(),
Headers: DefaultHeaders(),
Method: MethodAck,
Request: msg.Contact.Uri,
From: msg.From,
To: msg.To,
Via: msg.Via.Detach(),
CallID: msg.CallID,
CSeq: msg.CSeq,
CSeqMethod: "ACK",
Route: msg.RecordRoute.Reversed(),
Authorization: invite.Authorization,
ProxyAuthorization: invite.ProxyAuthorization,
UserAgent: GosipUA,
}
}
func NewCancel(invite *Msg) *Msg {
if invite.IsResponse || invite.Method != MethodInvite {
if invite.IsResponse() || invite.Method != MethodInvite {
log.Printf("Can't CANCEL anything non-INVITE:\n%s", invite)
}
return &Msg{
@ -71,39 +73,43 @@ func NewCancel(invite *Msg) *Msg {
CSeq: invite.CSeq,
CSeqMethod: MethodCancel,
Route: invite.Route,
Headers: DefaultHeaders(),
}
}
func NewBye(invite, last *Msg) *Msg {
func NewBye(invite, remote *Msg, lseq *int) *Msg {
if lseq == nil {
lseq = new(int)
*lseq = invite.CSeq
}
*lseq++
return &Msg{
Method: MethodBye,
Request: last.Contact.Uri,
Via: invite.Via,
Request: remote.Contact.Uri,
Via: invite.Via.Copy().Branch(),
From: invite.From,
To: last.To,
To: remote.To,
CallID: invite.CallID,
CSeq: invite.CSeq + 1,
CSeq: *lseq,
CSeqMethod: MethodBye,
Route: last.RecordRoute.Reversed(),
Headers: DefaultHeaders(),
Route: remote.RecordRoute.Reversed(),
}
}
// Returns true if `resp` can be considered an appropriate response to `msg`.
// Do not use for ACKs.
func ResponseMatch(msg, resp *Msg) bool {
return (resp.IsResponse &&
resp.CSeq == msg.CSeq &&
resp.CSeqMethod == msg.Method &&
resp.Via.Last().CompareHostPort(msg.Via))
func ResponseMatch(req, rsp *Msg) bool {
return (rsp.IsResponse() &&
rsp.CSeq == req.CSeq &&
rsp.CSeqMethod == req.Method &&
rsp.Via.Last().CompareHostPort(req.Via) &&
rsp.Via.Last().CompareBranch(req.Via))
}
// Returns true if `ack` can be considered an appropriate response to `msg`.
// We don't enforce a matching Via because some VoIP software will generate a
// new branch for ACKs.
func AckMatch(msg, ack *Msg) bool {
return (!ack.IsResponse &&
return (!ack.IsResponse() &&
ack.Method == MethodAck &&
ack.CSeq == msg.CSeq &&
ack.CSeqMethod == MethodAck &&
@ -114,13 +120,6 @@ func AttachSDP(msg *Msg, ms *sdp.SDP) {
if msg.Headers == nil {
msg.Headers = Headers{}
}
msg.Headers["Content-Type"] = sdp.ContentType
msg.ContentType = sdp.ContentType
msg.Payload = ms.String()
}
func DefaultHeaders() Headers {
return Headers{
"User-Agent": GosipUserAgent,
"Allow": GosipAllow,
}
}

+ 333
- 263
sip/msg.go View File

@ -5,11 +5,9 @@ package sip
import (
"bytes"
"errors"
"fmt"
"log"
"net"
"strconv"
"strings"
)
type Headers map[string]string
@ -17,39 +15,83 @@ type Headers map[string]string
// Msg represents a SIP message. This can either be a request or a response.
// These fields are never nil unless otherwise specified.
type Msg struct {
// Fields that aren't headers.
VersionMajor uint8
VersionMinor uint8
Method string // Indicates type of request (if request)
Request *URI // dest URI (nil if response)
Status int // Indicates happiness of response (if response)
Phrase string // Explains happiness of response (if response)
Payload string // Stuff that comes after two line breaks
// Special non-SIP fields.
SourceAddr *net.UDPAddr // Set by transport layer as received address
SourceAddr *net.UDPAddr // Set by transport layer as received address.
// Fields that aren't headers.
IsResponse bool // This is a response (like 404 Not Found)
Method string // Indicates type of request (if request)
Request *URI // dest URI (nil if response)
Status int // Indicates happiness of response (if response)
Phrase string // Explains happiness of response (if response)
Payload string // Stuff that comes after two line breaks
// Mandatory headers.
// Important headers should be further up in the struct.
From *Addr // Logical sender of message
To *Addr // Logical destination of message
Via *Via // Linked list of agents traversed (must have one)
Route *Addr // Used for goose routing and loose routing
RecordRoute *Addr // Used for loose routing
From *Addr // Logical sender of message
To *Addr // Logical destination of message
Contact *Addr // Where we send response packets or nil
CallID string // Identifies call from invite to bye
CSeq int // Counter for network packet ordering
CSeqMethod string // Helps with matching to orig message
MaxForwards int // 0 has context specific meaning
UserAgent string
ContentType string
// Convenience headers.
MaxForwards int // 0 has context specific meaning
MinExpires int // Registrars need this when responding
Expires int // Seconds registration should expire
Paid *Addr // P-Asserted-Identity or nil (used for PSTN ANI)
Rpid *Addr // Remote-Party-Id or nil
Contact *Addr // Where we send response packets or nil
// All the other RFC 3261 headers in plus some extras.
Accept string
AcceptContact string
AcceptEncoding string
AcceptLanguage string
AlertInfo string
Allow string
AllowEvents string
AuthenticationInfo string
Authorization string
CallInfo string
ContentDisposition string
ContentEncoding string
ContentLanguage string
Date string
ErrorInfo string
Event string
Expires int // Seconds registration should expire.
InReplyTo string
MIMEVersion string
MinExpires int // Registrars need this when responding
Organization string
PAssertedIdentity *Addr // P-Asserted-Identity or nil (used for PSTN ANI)
Priority string
ProxyAuthenticate string
ProxyAuthorization string
ProxyRequire string
ReferTo string
ReferredBy string
RemotePartyID *Addr // Evil twin of P-Asserted-Identity.
ReplyTo string
Require string
RetryAfter string
Server string
Subject string
Supported string
Timestamp string
Unsupported string
WWWAuthenticate string
Warning string
// All the other headers (never nil)
// Extension headers.
Headers Headers
}
//go:generate ragel -Z -G2 -o msg_parse.go msg_parse.rl
func (msg *Msg) IsResponse() bool {
return msg.Method == ""
}
func (msg *Msg) String() string {
if msg == nil {
return ""
@ -62,182 +104,31 @@ func (msg *Msg) String() string {
return b.String()
}
// Parses a SIP message into a data structure. This takes ~70 µs on average.
func ParseMsg(packet string) (msg *Msg, err error) {
msg = new(Msg)
if packet == "" {
return nil, errors.New("Empty msg")
}
if n := strings.Index(packet, "\r\n\r\n"); n > 0 {
packet, msg.Payload = packet[0:n], packet[n+4:]
}
lines := strings.Split(packet, "\r\n")
if lines == nil || len(lines) < 2 {
return nil, errors.New("Too few lines")
}
var k, v string
var okVia, okTo, okFrom, okCallID, okComputer bool
err = msg.parseFirstLine(lines[0])
if err != nil {
return nil, err
}
hdrs := lines[1:]
msg.Headers = make(map[string]string, len(hdrs))
msg.MaxForwards = 70
viap := &msg.Via
contactp := &msg.Contact
routep := &msg.Route
rroutep := &msg.RecordRoute
for _, hdr := range hdrs {
if hdr == "" {
continue
}
if hdr[0] == ' ' || hdr[0] == '\t' {
v = strings.Trim(hdr, "\t ") // Line continuation.
} else {
if i := strings.Index(hdr, ": "); i > 0 {
k, v = hdr[0:i], hdr[i+2:]
k = strings.Trim(k, " \t")
v = strings.Trim(v, " \t")
k = uncompactHeader(k)
if k == "" || v == "" {
log.Println("Blank header found:", hdr)
}
} else {
log.Println("Header missing delimiter:", hdr)
continue
}
}
switch strings.ToLower(k) {
case "call-id":
okCallID = true
msg.CallID = v
case "via":
okVia = true
*viap, err = ParseVia(v)
if err != nil {
return nil, errors.New("Bad Via header: " + err.Error())
} else {
viap = &(*viap).Next
}
case "to":
okTo = true
msg.To, err = ParseAddr(v)
if err != nil {
return nil, errors.New("Bad To header: " + err.Error())
}
case "from":
okFrom = true
msg.From, err = ParseAddr(v)
if err != nil {
return nil, errors.New("Bad From header: " + err.Error())
}
case "contact":
*contactp, err = ParseAddr(v)
if err != nil {
return nil, errors.New("Bad Contact header: " + err.Error())
} else {
contactp = &(*contactp).Last().Next
}
case "cseq":
okComputer = false
if n := strings.Index(v, " "); n > 0 {
sseq, method := v[0:n], v[n+1:]
if seq, err := strconv.Atoi(sseq); err == nil {
msg.CSeq, msg.CSeqMethod = seq, method
okComputer = true
}
}
if !okComputer {
return nil, errors.New("Bad CSeq Header")
}
case "content-length":
if cl, err := strconv.Atoi(v); err == nil {
if cl != len(msg.Payload) {
return nil, errors.New(fmt.Sprintf(
"Content-Length (%d) differs from payload length (%d)",
cl, len(msg.Payload)))
}
} else {
return nil, errors.New("Bad Content-Length header")
}
case "expires":
if cl, err := strconv.Atoi(v); err == nil && cl >= 0 {
msg.Expires = cl
} else {
return nil, errors.New("Bad Expires header")
}
case "min-expires":
if cl, err := strconv.Atoi(v); err == nil && cl > 0 {
msg.MinExpires = cl
} else {
return nil, errors.New("Bad Min-Expires header")
}
case "max-forwards":
if cl, err := strconv.Atoi(v); err == nil && cl > 0 {
msg.MaxForwards = cl
} else {
return nil, errors.New("Bad Max-Forwards header")
}
case "route":
*routep, err = ParseAddr(v)
if err != nil {
return nil, errors.New("Bad Route header: " + err.Error())
} else {
routep = &(*routep).Last().Next
}
case "record-route":
*rroutep, err = ParseAddr(v)
if err != nil {
return nil, errors.New("Bad Record-Route header: " + err.Error())
} else {
rroutep = &(*rroutep).Last().Next
}
case "p-asserted-identity":
msg.Paid, err = ParseAddr(v)
if err != nil {
return nil, errors.New("Bad P-Asserted-Identity header: " + err.Error())
}
case "remote-party-id":
msg.Rpid, err = ParseAddr(v)
if err != nil {
return nil, errors.New("Bad Remote-Party-ID header: " + err.Error())
}
default:
msg.Headers[k] = v
}
}
if !okVia || !okTo || !okFrom || !okCallID || !okComputer {
return nil, errors.New("Missing mandatory headers")
}
return
}
func (msg *Msg) Copy() *Msg {
if msg == nil {
return nil
}
res := new(Msg)
*res = *msg
res.Request = msg.Request.Copy()
res.To = msg.To.Copy()
res.From = msg.From.Copy()
res.Via = msg.Via.Copy()
res.Paid = msg.Paid.Copy()
res.Rpid = msg.Rpid.Copy()
res.PAssertedIdentity = msg.PAssertedIdentity.Copy()
res.RemotePartyID = msg.RemotePartyID.Copy()
res.Route = msg.Route.Copy()
res.Request = msg.Request.Copy()
res.Contact = msg.Contact.Copy()
res.RecordRoute = msg.RecordRoute.Copy()
res.Headers = make(map[string]string, len(msg.Headers))
res.Headers = make(Headers, len(msg.Headers))
for k, v := range msg.Headers {
res.Headers[k] = v
}
return res
}
// i turn a sip message back into a packet
// I turn a SIP message back into a packet.
func (msg *Msg) Append(b *bytes.Buffer) error {
if !msg.IsResponse {
if !msg.IsResponse() {
if msg.Method == "" {
return errors.New("Msg.Method not set")
}
@ -247,27 +138,29 @@ func (msg *Msg) Append(b *bytes.Buffer) error {
b.WriteString(msg.Method)
b.WriteString(" ")
msg.Request.Append(b)
b.WriteString(" SIP/2.0\r\n")
b.WriteString(" ")
msg.appendVersion(b)
b.WriteString("\r\n")
} else {
if msg.Status < 100 {
return errors.New("Msg.Status < 100")
}
if msg.Status >= 700 {
return errors.New("Msg.Status >= 700")
}
if msg.Phrase == "" {
msg.Phrase = Phrase(msg.Status)
}
b.WriteString("SIP/2.0 ")
msg.appendVersion(b)
b.WriteString(" ")
b.WriteString(strconv.Itoa(msg.Status))
b.WriteString(" ")
b.WriteString(msg.Phrase)
b.WriteString("\r\n")
}
if msg.Via == nil {
return errors.New("Need moar Via headers")
}
b.WriteString("From: ")
msg.From.Append(b)
b.WriteString("\r\n")
b.WriteString("To: ")
msg.To.Append(b)
b.WriteString("\r\n")
for viap := msg.Via; viap != nil; viap = viap.Next {
b.WriteString("Via: ")
if err := viap.Append(b); err != nil {
@ -292,121 +185,298 @@ func (msg *Msg) Append(b *bytes.Buffer) error {
b.WriteString("\r\n")
}
if msg.MaxForwards < 0 {
return errors.New("MaxForwards is less than 0!!")
} else if msg.MaxForwards == 0 {
b.WriteString("Max-Forwards: 70\r\n")
} else {
b.WriteString("Max-Forwards: ")
b.WriteString(strconv.Itoa(msg.MaxForwards))
if msg.Contact != nil {
b.WriteString("Contact: ")
msg.Contact.Append(b)
b.WriteString("\r\n")
}
b.WriteString("From: ")
msg.From.Append(b)
b.WriteString("\r\n")
b.WriteString("To: ")
msg.To.Append(b)
b.WriteString("\r\n")
if msg.CallID == "" {
return errors.New("CallID is blank")
}
b.WriteString("Call-ID: ")
b.WriteString(msg.CallID)
b.WriteString("\r\n")
if msg.CSeq < 0 || msg.CSeqMethod == "" {
return errors.New("Bad CSeq")
}
b.WriteString("CSeq: ")
b.WriteString(strconv.Itoa(msg.CSeq))
b.WriteString(" ")
b.WriteString(msg.CSeqMethod)
b.WriteString("\r\n")
if msg.Contact != nil {
b.WriteString("Contact: ")
msg.Contact.Append(b)
if msg.UserAgent != "" {
b.WriteString("User-Agent: ")
b.WriteString(msg.UserAgent)
b.WriteString("\r\n")
}
if !msg.IsResponse() {
if msg.MaxForwards == 0 {
b.WriteString("Max-Forwards: 70\r\n")
} else {
b.WriteString("Max-Forwards: ")
b.WriteString(strconv.Itoa(msg.MaxForwards))
b.WriteString("\r\n")
}
}
if msg.ContentType != "" {
b.WriteString("Content-Type: ")
b.WriteString(msg.ContentType)
b.WriteString("\r\n")
}
b.WriteString("Content-Length: ")
b.WriteString(strconv.Itoa(len(msg.Payload)))
b.WriteString("\r\n")
if msg.Accept != "" {
b.WriteString("Accept: ")
b.WriteString(msg.Accept)
b.WriteString("\r\n")
}
if msg.AcceptEncoding != "" {
b.WriteString("Accept-Encoding: ")
b.WriteString(msg.AcceptEncoding)
b.WriteString("\r\n")
}
if msg.AcceptLanguage != "" {
b.WriteString("Accept-Language: ")
b.WriteString(msg.AcceptLanguage)
b.WriteString("\r\n")
}
if msg.AlertInfo != "" {
b.WriteString("Alert-Info: ")
b.WriteString(msg.AlertInfo)
b.WriteString("\r\n")
}
if msg.Allow != "" {
b.WriteString("Allow: ")
b.WriteString(msg.Allow)
b.WriteString("\r\n")
}
if msg.AllowEvents != "" {
b.WriteString("Allow-Events: ")
b.WriteString(msg.AllowEvents)
b.WriteString("\r\n")
}
if msg.AuthenticationInfo != "" {
b.WriteString("Authentication-Info: ")
b.WriteString(msg.AuthenticationInfo)
b.WriteString("\r\n")
}
if msg.Authorization != "" {
b.WriteString("Authorization: ")
b.WriteString(msg.Authorization)
b.WriteString("\r\n")
}
if msg.CallInfo != "" {
b.WriteString("Call-Info: ")
b.WriteString(msg.CallInfo)
b.WriteString("\r\n")
}
if msg.ContentDisposition != "" {
b.WriteString("Content-Disposition: ")
b.WriteString(msg.ContentDisposition)
b.WriteString("\r\n")
}
if msg.ContentEncoding != "" {
b.WriteString("Content-Encoding: ")
b.WriteString(msg.ContentEncoding)
b.WriteString("\r\n")
}
if msg.ContentLanguage != "" {
b.WriteString("Content-Language: ")
b.WriteString(msg.ContentLanguage)
b.WriteString("\r\n")
}
if msg.Date != "" {
b.WriteString("Date: ")
b.WriteString(msg.Date)
b.WriteString("\r\n")
}
if msg.ErrorInfo != "" {
b.WriteString("Error-Info: ")
b.WriteString(msg.ErrorInfo)
b.WriteString("\r\n")
}
if msg.Event != "" {
b.WriteString("Event: ")
b.WriteString(msg.Event)
b.WriteString("\r\n")
}
// Expires is allowed to be 0 for for REGISTER stuff.
if msg.Expires > 0 ||
msg.Method == "REGISTER" || msg.CSeqMethod == "REGISTER" {
if msg.Expires > 0 || msg.Method == "REGISTER" || msg.CSeqMethod == "REGISTER" {
b.WriteString("Expires: ")
b.WriteString(strconv.Itoa(msg.Expires))
b.WriteString("\r\n")
}
if msg.InReplyTo != "" {
b.WriteString("In-Reply-To: ")
b.WriteString(msg.InReplyTo)
b.WriteString("\r\n")
}
if msg.MIMEVersion != "" {
b.WriteString("MIME-Version: ")
b.WriteString(msg.MIMEVersion)
b.WriteString("\r\n")
}
if msg.MinExpires > 0 {
b.WriteString("Min-Expires: ")
b.WriteString(strconv.Itoa(msg.MinExpires))
b.WriteString("\r\n")
}
if msg.Headers != nil {
for k, v := range msg.Headers {
if k == "" || v == "" {
return errors.New("Header blank")
}
b.WriteString(k)
b.WriteString(": ")
b.WriteString(v)
b.WriteString("\r\n")
}
if msg.Organization != "" {
b.WriteString("Organization: ")
b.WriteString(msg.Organization)
b.WriteString("\r\n")
}
if msg.Paid != nil {
if msg.PAssertedIdentity != nil {
b.WriteString("P-Asserted-Identity: ")
msg.Paid.Append(b)
msg.PAssertedIdentity.Append(b)
b.WriteString("\r\n")
}
if msg.Priority != "" {
b.WriteString("Priority: ")
b.WriteString(msg.Priority)
b.WriteString("\r\n")
}
if msg.ProxyAuthenticate != "" {
b.WriteString("Proxy-Authenticate: ")
b.WriteString(msg.ProxyAuthenticate)
b.WriteString("\r\n")
}
if msg.ProxyAuthorization != "" {
b.WriteString("Proxy-Authorization: ")
b.WriteString(msg.ProxyAuthorization)
b.WriteString("\r\n")
}
if msg.ProxyRequire != "" {
b.WriteString("Proxy-Require: ")
b.WriteString(msg.ProxyRequire)
b.WriteString("\r\n")
}
if msg.ReferTo != "" {
b.WriteString("Refer-To: ")
b.WriteString(msg.ReferTo)
b.WriteString("\r\n")
}
if msg.ReferredBy != "" {
b.WriteString("Referred-By: ")
b.WriteString(msg.ReferredBy)
b.WriteString("\r\n")
}
if msg.Rpid != nil {
if msg.RemotePartyID != nil {
b.WriteString("Remote-Party-ID: ")
msg.Rpid.Append(b)
msg.RemotePartyID.Append(b)
b.WriteString("\r\n")
}
b.WriteString("Content-Length: ")
b.WriteString(strconv.Itoa(len(msg.Payload)))
b.WriteString("\r\n\r\n")
b.WriteString(msg.Payload)
if msg.ReplyTo != "" {
b.WriteString("Reply-To: ")
b.WriteString(msg.ReplyTo)
b.WriteString("\r\n")
}
if msg.Require != "" {
b.WriteString("Require: ")
b.WriteString(msg.Require)
b.WriteString("\r\n")
}
if msg.RetryAfter != "" {
b.WriteString("RetryAfter: ")
b.WriteString(msg.RetryAfter)
b.WriteString("\r\n")
}
if msg.Server != "" {
b.WriteString("Server: ")
b.WriteString(msg.Server)
b.WriteString("\r\n")
}
if msg.Subject != "" {
b.WriteString("Subject: ")
b.WriteString(msg.Subject)
b.WriteString("\r\n")
}
if msg.Supported != "" {
b.WriteString("Supported: ")
b.WriteString(msg.Supported)
b.WriteString("\r\n")
}
if msg.Timestamp != "" {
b.WriteString("Timestamp: ")
b.WriteString(msg.Timestamp)
b.WriteString("\r\n")
}
if msg.Unsupported != "" {
b.WriteString("Unsupported: ")
b.WriteString(msg.Unsupported)
b.WriteString("\r\n")
}
if msg.Warning != "" {
b.WriteString("Warning: ")
b.WriteString(msg.Warning)
b.WriteString("\r\n")
}
if msg.WWWAuthenticate != "" {
b.WriteString("WWW-Authenticate: ")
b.WriteString(msg.WWWAuthenticate)
b.WriteString("\r\n")
}
if msg.Headers != nil {
for k, v := range msg.Headers {
b.WriteString(k)
b.WriteString(": ")
b.WriteString(v)
b.WriteString("\r\n")
}
}
b.WriteString("\r\n")
b.WriteString(msg.Payload)
return nil
}
func (msg *Msg) parseFirstLine(s string) error {
i := strings.Index(s, "SIP/2.0")
if i == -1 {
return errors.New("Not a SIP message")
} else if i == 0 {
msg.IsResponse = true
toks := strings.SplitN(s, " ", 3)
if len(toks) < 2 {
return errors.New("Bad response status line")
}
s, err := strconv.Atoi(toks[1])
if err != nil {
return errors.New("Bad response status code")
}
msg.Status = s
if len(toks) == 3 {
msg.Phrase = toks[2]
} else {
msg.Phrase = Phrase(msg.Status)
}
func (msg *Msg) appendVersion(b *bytes.Buffer) {
b.WriteString("SIP/")
if msg.VersionMajor == 0 {
b.WriteString("2.0")
} else {
j := strings.Index(s, " ")
msg.Method = s[:j]
msg.Request = new(URI)
r, err := ParseURI(s[j+1 : i-1])
msg.Request = r
if err != nil {
return err
}
b.WriteString(strconv.FormatUint(uint64(msg.VersionMajor), 10))
b.WriteString(".")
b.WriteString(strconv.FormatUint(uint64(msg.VersionMinor), 10))
}
return nil
}

+ 25259
- 0
sip/msg_parse.go
File diff suppressed because it is too large
View File


+ 476
- 0
sip/msg_parse.rl View File

@ -0,0 +1,476 @@
package sip
import (
// "bytes"
"errors"
"fmt"
)
%% machine msg;
%% write data;
// ParseMsg turns a a SIP message into a data structure.
func ParseMsg(s string) (msg *Msg, err error) {
if s == "" {
return nil, errors.New("Empty SIP message")
}
return ParseMsgBytes([]byte(s))
}
// ParseMsg turns a a SIP message byte slice into a data structure.
func ParseMsgBytes(data []byte) (msg *Msg, err error) {
if data == nil {
return nil, nil
}
msg = new(Msg)
cs := 0
p := 0
pe := len(data)
line := 1
linep := 0
buf := make([]byte, len(data))
amt := 0
mark := 0
clen := 0
// var b1 string
var hex byte
%%{
action break {
fbreak;
}
action mark {
mark = p
}
action start {
amt = 0
}
action append {
buf[amt] = fc
amt++
}
action collapse {
amt = appendCollapse(buf, amt, fc)
}
action hexHi {
hex = unhex(fc) * 16
}
action hexLo {
hex += unhex(fc)
buf[amt] = hex
amt++
}
action lower {
amt = appendLower(buf, amt, fc)
}
action Method {
msg.Method = string(data[mark:p])
}
action VersionMajor {
msg.VersionMajor = msg.VersionMajor * 10 + (fc - 0x30)
}
action VersionMinor {
msg.VersionMinor = msg.VersionMinor * 10 + (fc - 0x30)
}
action RequestURI {
msg.Request, err = ParseURIBytes(data[mark:p])
if err != nil { return nil, err }
}
action StatusCode {
msg.Status = msg.Status * 10 + (int(fc) - 0x30)
}
action ReasonPhrase {
msg.Phrase = string(buf[0:amt])
}
action extHeaderName {
b1 = string(bytes.ToLower(data[mark:p]))
}
action extHeaderValue {
if msg.Headers == nil {
msg.Headers = Headers{}
}
msg.Headers[b1] = string(data[mark:p])
}
action Accept {
msg.Accept = string(data[mark:p])
}
action AcceptValue {
msg.AcceptContact = string(data[mark:p])
}
action AcceptEncoding {
msg.AcceptEncoding = string(data[mark:p])
}
action AcceptLanguage {
msg.AcceptLanguage = string(data[mark:p])
}
action Allow {
msg.Allow = string(data[mark:p])
}
action AllowEvents {
msg.AllowEvents = string(data[mark:p])
}
action AlertInfo {
msg.AlertInfo = string(data[mark:p])
}
action AuthenticationInfo {
msg.AuthenticationInfo = string(data[mark:p])
}
action Authorization {
msg.Authorization = string(data[mark:p])
}
action CallID {
msg.CallID = string(data[mark:p])
}
action Contact {
msg.Contact, err = ParseAddr(string(data[mark:p]))
if err != nil { return nil, err }
}
action ContentDisposition {
msg.ContentDisposition = string(data[mark:p])
}
action ContentLanguage {
msg.ContentLanguage = string(data[mark:p])
}
action ContentLength {
clen = clen * 10 + (int(fc) - 0x30)
}
action ContentEncoding {
msg.ContentEncoding = string(data[mark:p])
}
action ContentType {
msg.ContentType = string(data[mark:p])
}
action CSeq {
msg.CSeq = msg.CSeq * 10 + (int(fc) - 0x30)
}
action CSeqMethod {
msg.CSeqMethod = string(data[mark:p])
}
action CallInfo {
msg.CallInfo = string(data[mark:p])
}
action Date {
msg.Date = string(data[mark:p])
}
action ErrorInfo {
msg.ErrorInfo = string(data[mark:p])
}
action Event {
msg.Event = string(data[mark:p])
}
action Expires {
msg.Expires = msg.Expires * 10 + (int(fc) - 0x30)
}
action From {
msg.From, err = ParseAddr(string(data[mark:p]))
if err != nil { return nil, err }
}
action InReplyTo {
msg.InReplyTo = string(data[mark:p])
}
action MaxForwardsZero {
msg.MaxForwards = 0
}
action MaxForwards {
msg.MaxForwards = msg.MaxForwards * 10 + (int(fc) - 0x30)
}
action MinExpires {
msg.MinExpires = msg.MinExpires * 10 + (int(fc) - 0x30)
}
action ReplyTo {
msg.ReplyTo = string(data[mark:p])
}
action MIMEVersion {
msg.MIMEVersion = string(data[mark:p])
}
action Organization {
msg.Organization = string(data[mark:p])
}
action PAssertedIdentity {
msg.PAssertedIdentity, err = ParseAddr(string(data[mark:p]))
if err != nil { return nil, err }
}
action Priority {
msg.Priority = string(data[mark:p])
}
action ProxyAuthenticate {
msg.ProxyAuthenticate = string(data[mark:p])
}
action ProxyAuthorization {
msg.ProxyAuthorization = string(data[mark:p])
}
action ProxyRequire {
msg.ProxyRequire = string(data[mark:p])
}
action RecordRoute {
msg.RecordRoute, err = ParseAddr(string(data[mark:p]))
if err != nil { return nil, err }
}
action ReferTo {
msg.ReferTo = string(data[mark:p])
}
action ReferredBy {
msg.ReferredBy = string(data[mark:p])
}
action RemotePartyID {
msg.RemotePartyID, err = ParseAddr(string(data[mark:p]))
if err != nil { return nil, err }
}
action Require {
msg.Require = string(data[mark:p])
}
action RetryAfter {
msg.RetryAfter = string(data[mark:p])
}
action Route {
msg.Route, err = ParseAddr(string(data[mark:p]))
if err != nil { return nil, err }
}
action Server {
msg.Server = string(data[mark:p])
}
action Subject {
msg.Subject = string(data[mark:p])
}
action Supported {
msg.Supported = string(data[mark:p])
}
action Timestamp {
msg.Timestamp = string(data[mark:p])
}
action To {
msg.To, err = ParseAddr(string(data[mark:p]))
if err != nil { return nil, err }
}
action Unsupported {
msg.Unsupported = string(data[mark:p])
}
action UserAgent {
msg.UserAgent = string(data[mark:p])
}
action Via {
msg.Via, err = ParseVia(string(data[mark:p]))
if err != nil { return nil, err }
}
action Warning {
msg.Warning = string(data[mark:p])
}
action WWWAuthenticate {
msg.WWWAuthenticate = string(data[mark:p])
}
action lookAheadWSP { p + 2 < pe && (data[p+2] == ' ' || data[p+2] == '\t') }
# https://tools.ietf.org/html/rfc2234
SP = " ";
HTAB = "\t";
CR = "\r";
LF = "\n" @{ line++; linep = p; };
CRLF = CR LF;
WSP = SP | HTAB;
LWS = ( WSP* ( CR when lookAheadWSP ) LF )? WSP+;
SWS = LWS?;
UTF8_NONASCII = 0x80..0xFD;
UTF8 = 0x21..0x7F | UTF8_NONASCII;
UTF8_TRIM = ( UTF8+ (LWS* UTF8)* ) >start @collapse;
# https://tools.ietf.org/html/rfc3261#section-25.1
reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," ;
mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" ;
unreserved = alpha | digit | mark ;
HCOLON = WSP* ":" SWS ;
tokenc = alpha | digit | "-" | "." | "!" | "%" | "*" | "_"
| "+" | "`" | "'" | "~" ;
token = tokenc+ >mark ;
separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\"
| "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP
| HTAB ;
wordc = alpha | digit | "-" | "." | "!" | "%" | "*" | "_"
| "+" | "`" | "'" | "~" | "(" | ")" | "<" | ">" | ":"
| "\\" | "\"" | "/" | "[" | "]" | "?" | "{" | "}" ;
word = wordc+ ;
STAR = SWS "*" SWS ;
SLASH = SWS "/" SWS ;
EQUAL = SWS "=" SWS ;
LPAREN = SWS "(" SWS ;
RPAREN = SWS ")" SWS ;
RAQUOT = ">" SWS ;
LAQUOT = SWS "<" ;
COMMA = SWS "," SWS ;
SEMI = SWS ";" SWS ;
COLON = SWS ":" SWS ;
LDQUOT = SWS "\"" ;
RDQUOT = "\"" SWS ;
ctext = 0x21..0x27 | 0x2A..0x5B | 0x5D..0x7E | UTF8_NONASCII | LWS ;
quoted_pair = "\\" ( 0x00..0x09 | 0x0B..0x0C | 0x0E..0x7F ) ;
comment = LPAREN ( ctext | quoted_pair )* <: RPAREN ; # TODO(jart): Nested parens
qdtext = LWS | 0x21 | 0x23..0x5B | 0x5D..0x7E | UTF8_NONASCII ;
quoted_string = SWS "\"" ( qdtext | quoted_pair )* <: "\"" ;
escaped = "%" ( xdigit @hexHi ) ( xdigit @hexLo ) ;
uric = reserved | unreserved | escaped ;
uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" | "&" | "="
| "+" | "$" | "," ;
reasonc = reserved | unreserved | UTF8_NONASCII | SP | HTAB ;
reasonmc = escaped | ( reasonc @append ) ;
cid = word ( "@" word )? ;
Method = token %Method;
SIPVersionNo = digit+ @VersionMajor "." digit+ @VersionMinor;
RequestURI = ^SP+ >mark %RequestURI;
StatusCode = ( digit @StatusCode ) {3};
ReasonPhrase = reasonmc+ >start %ReasonPhrase;
hval = ( UTF8 | LWS )* >mark;
extHeader = token %extHeaderName HCOLON hval %extHeaderValue;
# http://www.iana.org/assignments/sip-parameters/sip-parameters.xhtml
stdHeader = "Accept"i HCOLON hval %Accept
| ("Accept-Contact"i | "a"i) HCOLON hval %AcceptValue
| "Accept-Encoding"i HCOLON hval %AcceptEncoding
| "Accept-Language"i HCOLON hval %AcceptLanguage
| ("Allow"i | "u"i) HCOLON hval %Allow
| ("Allow-Events"i | "u"i) HCOLON hval %AllowEvents
| "Alert-Info"i HCOLON hval %AlertInfo
| "Authentication-Info"i HCOLON hval %AuthenticationInfo
| "Authorization"i HCOLON hval %Authorization
| ("Call-ID"i | "i"i) HCOLON cid >mark %CallID
| ("Contact"i | "m"i) HCOLON hval %Contact
| "Content-Disposition"i HCOLON hval %ContentDisposition
| "Content-Language"i HCOLON hval %ContentLanguage
| ("Content-Length"i | "l"i) HCOLON digit+ @ContentLength
| ("Content-Encoding"i | "e"i) HCOLON hval %ContentEncoding
| ("Content-Type"i | "c"i) HCOLON hval %ContentType
| "CSeq"i HCOLON (digit+ @CSeq) LWS token >mark %CSeqMethod
| "Call-Info"i HCOLON hval %CallInfo
| "Date"i HCOLON hval %Date
| "Error-Info"i HCOLON hval %ErrorInfo
| ("Event"i | "o"i) HCOLON hval %Event
| ("Expires"i | "l"i) HCOLON digit+ @Expires
| ("From"i | "f"i) HCOLON hval %From
| "In-Reply-To"i HCOLON hval %InReplyTo
| ("Max-Forwards"i | "l"i) HCOLON digit+ >MaxForwardsZero @MaxForwards
| ("Min-Expires"i | "l"i) HCOLON digit+ @MinExpires
| "Reply-To"i HCOLON hval %ReplyTo
| "MIME-Version"i HCOLON hval %MIMEVersion
| "Organization"i HCOLON hval %Organization
| "P-Asserted-Identity"i HCOLON hval %PAssertedIdentity
| "Priority"i HCOLON hval %Priority
| "Proxy-Authenticate"i HCOLON hval %ProxyAuthenticate
| "Proxy-Authorization"i HCOLON hval %ProxyAuthorization
| "Proxy-Require"i HCOLON hval %ProxyRequire
| "Record-Route"i HCOLON hval %RecordRoute
| ("Refer-To"i | "r"i) HCOLON hval %ReferTo
| ("Referred-By"i | "b"i) HCOLON hval %ReferredBy
| "Remote-Party-ID"i HCOLON hval %RemotePartyID
| "Require"i HCOLON hval %Require
| "Retry-After"i HCOLON hval %RetryAfter
| "Route"i HCOLON hval %Route
| "Server"i HCOLON hval %Server
| ("Subject"i | "s"i) HCOLON hval %Subject
| ("Supported"i | "k"i) HCOLON hval %Supported
| "Timestamp"i HCOLON hval %Timestamp
| ("To"i | "t"i) HCOLON hval %To
| "Unsupported"i HCOLON hval %Unsupported
| "User-Agent"i HCOLON hval %UserAgent
| ("Via"i | "v"i) HCOLON hval %Via
| "Warning"i HCOLON hval %Warning
| "WWW-Authenticate"i HCOLON hval %WWWAuthenticate
;
header = stdHeader CRLF;
headers = header* CR LF @break;
SIPVersion = "SIP/" SIPVersionNo;
RequestLine = Method SP RequestURI SP SIPVersion CRLF;
StatusLine = SIPVersion SP StatusCode SP ReasonPhrase CRLF;
Request = RequestLine headers;
Response = StatusLine headers;
main := Request | Response;
write init;
write exec;
}%%
if cs < msg_first_final {
if p == pe {
return nil, errors.New(fmt.Sprintf("Incomplete SIP message: %s", data))
} else {
return nil, errors.New(fmt.Sprintf("Error in SIP message at line %d offset %d:\n%s", line, p - linep, data))
}
}
if clen > 0 {
if clen != len(data) - p {
return nil, errors.New(fmt.Sprintf("Content-Length incorrect: %d != %d", clen, len(data) - p))
}
msg.Payload = string(data[p:len(data)])
}
return msg, nil
}

+ 117
- 29
sip/msg_test.go View File

@ -30,11 +30,12 @@ var msgTests = []msgTest{
"Content-Length: 0\r\n" +
"\r\n",
msg: sip.Msg{
Method: "OPTIONS",
CSeqMethod: "OPTIONS",
MaxForwards: 60,
CallID: "e71a163e-c440-474d-a4ec-5cd85a0309c6",
CSeq: 36612,
VersionMajor: 2,
Method: "OPTIONS",
CSeqMethod: "OPTIONS",
MaxForwards: 60,
CallID: "e71a163e-c440-474d-a4ec-5cd85a0309c6",
CSeq: 36612,
Request: &sip.URI{
Scheme: "sip",
Host: "10.11.34.37",
@ -70,10 +71,97 @@ var msgTests = []msgTest{
Port: 42367,
},
},
Headers: map[string]string{
"User-Agent": "ghoul/0.1",
"Accept": "application/sdp",
UserAgent: "ghoul/0.1",
Accept: "application/sdp",
},
},
msgTest{
s: "SIP/2.0 200 OK\r\n" +
"Via: SIP/2.0/UDP 127.0.0.1:52711;branch=z9hG4bK-03d1d81e94a0;received=127.0.0.1;rport=52711\r\n" +
"From: <sip:127.0.0.1:52711;transport=udp>;tag=4568e274dbd8\r\n" +
"To: <sip:echo@127.0.0.1:5060>;tag=as71a0fa72\r\n" +
"Call-ID: 99042736-d40b-4d96-a81b-867321443ff5\r\n" +
"CSeq: 16378 INVITE\r\n" +
"Server: Asterisk PBX 10.11.1\r\n" +
"Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH\r\n" +
"Supported: replaces, timer\r\n" +
"Contact: <sip:echo@127.0.0.1:5060>\r\n" +
"Content-Type: application/sdp\r\n" +
"Content-Length: 255\r\n" +
"\r\n" +
"v=0\r\n" +
"o=root 736606944 736606944 IN IP4 127.0.0.1\r\n" +
"s=Asterisk PBX 10.11.1\r\n" +
"c=IN IP4 127.0.0.1\r\n" +
"t=0 0\r\n" +
"m=audio 23452 RTP/AVP 0 101\r\n" +
"a=rtpmap:0 PCMU/8000\r\n" +
"a=rtpmap:101 telephone-event/8000\r\n" +
"a=fmtp:101 0-16\r\n" +
"a=silenceSupp:off - - - -\r\n" +
"a=ptime:20\r\n" +
"a=sendrecv\r\n",
msg: sip.Msg{
VersionMajor: 2,
Status: 200,
Phrase: "OK",
CallID: "99042736-d40b-4d96-a81b-867321443ff5",
CSeq: 16378,
CSeqMethod: "INVITE",
Server: "Asterisk PBX 10.11.1",
Allow: "INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH",
Supported: "replaces, timer",
Via: &sip.Via{
Version: "2.0",
Proto: "UDP",
Host: "127.0.0.1",
Port: 52711,
Params: sip.Params{
"branch": "z9hG4bK-03d1d81e94a0",
"received": "127.0.0.1",
"rport": "52711",
},
},
From: &sip.Addr{
Uri: &sip.URI{
Scheme: "sip",
Host: "127.0.0.1",
Port: 52711,
Params: sip.Params{"transport": "udp"},
},
Params: sip.Params{"tag": "4568e274dbd8"},
},
To: &sip.Addr{
Uri: &sip.URI{
Scheme: "sip",
User: "echo",
Host: "127.0.0.1",
Port: 5060,
},
Params: sip.Params{"tag": "as71a0fa72"},
},
Contact: &sip.Addr{
Uri: &sip.URI{
Scheme: "sip",
User: "echo",
Host: "127.0.0.1",
Port: 5060,
},
},
ContentType: "application/sdp",
Payload: "v=0\r\n" +
"o=root 736606944 736606944 IN IP4 127.0.0.1\r\n" +
"s=Asterisk PBX 10.11.1\r\n" +
"c=IN IP4 127.0.0.1\r\n" +
"t=0 0\r\n" +
"m=audio 23452 RTP/AVP 0 101\r\n" +
"a=rtpmap:0 PCMU/8000\r\n" +
"a=rtpmap:101 telephone-event/8000\r\n" +
"a=fmtp:101 0-16\r\n" +
"a=silenceSupp:off - - - -\r\n" +
"a=ptime:20\r\n" +
"a=sendrecv\r\n",
},
},
@ -86,8 +174,9 @@ var msgTests = []msgTest{
"To: <sip:10.11.34.37>\r\n" +
"Call-ID: 87704115-03b8-122e-08b5-001bfcce6bdf\r\n" +
"CSeq: 133097268 INVITE\r\n" +
// "Contact: <sip:10.11.34.37:59516>,\r\n" +
// " <sip:10.11.34.38:59516>\r\n" +
"Contact: <sip:10.11.34.37:59516>\r\n" +
" <sip:10.11.34.38:59516>\r\n" +
"User-Agent: tube/0.1\r\n" +
"Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, PRACK, MESSAGE, SUBSCRIBE, NOTIFY, REFER, UPDATE, INFO\r\n" +
"Supported: timer, 100rel\r\n" +
@ -107,11 +196,12 @@ var msgTests = []msgTest{
"a=fmtp:101 0-16\r\n" +
"a=ptime:20\r\n",
msg: sip.Msg{
Method: "INVITE",
CSeqMethod: "INVITE",
MaxForwards: 70,
CallID: "87704115-03b8-122e-08b5-001bfcce6bdf",
CSeq: 133097268,
VersionMajor: 2,
Method: "INVITE",
CSeqMethod: "INVITE",
MaxForwards: 70,
CallID: "87704115-03b8-122e-08b5-001bfcce6bdf",
CSeq: 133097268,
Request: &sip.URI{
Scheme: "sip",
Host: "10.11.34.37",
@ -143,22 +233,20 @@ var msgTests = []msgTest{
Host: "10.11.34.37",
Port: 59516,
},
Next: &sip.Addr{
Uri: &sip.URI{
Scheme: "sip",
Host: "10.11.34.38",
Port: 59516,
},
},
},
Headers: map[string]string{
"User-Agent": "tube/0.1",
"Allow": "INVITE, ACK, BYE, CANCEL, OPTIONS, PRACK, MESSAGE, SUBSCRIBE, NOTIFY, REFER, UPDATE, INFO",
"Allow-Events": "talk",
"Content-Disposition": "session",
"Supported": "timer, 100rel",
"Content-Type": "application/sdp",
// Next: &sip.Addr{
// Uri: &sip.URI{
// Scheme: "sip",
// Host: "10.11.34.38",
// Port: 59516,
// },
// },
},
UserAgent: "tube/0.1",
Allow: "INVITE, ACK, BYE, CANCEL, OPTIONS, PRACK, MESSAGE, SUBSCRIBE, NOTIFY, REFER, UPDATE, INFO",
AllowEvents: "talk",
ContentDisposition: "session",
Supported: "timer, 100rel",
ContentType: "application/sdp",
Payload: "v=0\r\n" +
"o=- 2862054018559638081 6057228511765453924 IN IP4 10.11.34.37\r\n" +
"s=-\r\n" +


+ 24
- 26
sip/route.go View File

@ -13,10 +13,7 @@ type AddressRoute struct {
}
func PopulateMessage(via *Via, contact *Addr, msg *Msg) {
if !msg.IsResponse {
if msg.Method == "" {
msg.Method = "INVITE"
}
if !msg.IsResponse() {
if msg.Via == nil {
msg.Via = via
}
@ -27,7 +24,8 @@ func PopulateMessage(via *Via, contact *Addr, msg *Msg) {
msg.To = &Addr{Uri: msg.Request}
}
if msg.From == nil {
msg.From = msg.Contact
msg.From = msg.Contact.Copy()
msg.From.Uri.Params = nil
}
if msg.CallID == "" {
msg.CallID = util.GenerateCallID()
@ -41,6 +39,9 @@ func PopulateMessage(via *Via, contact *Addr, msg *Msg) {
if msg.MaxForwards == 0 {
msg.MaxForwards = 70
}
if msg.UserAgent == "" {
msg.UserAgent = GosipUA
}
if _, ok := msg.Via.Params["branch"]; !ok {
msg.Via = msg.Via.Copy()
msg.Via.Params["branch"] = util.GenerateBranch()
@ -49,16 +50,11 @@ func PopulateMessage(via *Via, contact *Addr, msg *Msg) {
msg.From = msg.From.Copy()
msg.From.Params["tag"] = util.GenerateTag()
}
if _, ok := msg.Headers["User-Agent"]; !ok {
msg.Headers["User-Agent"] = GosipUserAgent
}
}
}
func RouteMessage(via *Via, contact *Addr, old *Msg) (msg *Msg, host string, port uint16, err error) {
msg = new(Msg)
*msg = *old // Start off with a shallow copy.
if msg.IsResponse {
func RouteMessage(via *Via, contact *Addr, msg *Msg) (host string, port uint16, err error) {
if msg.IsResponse() {
if via.CompareHostPort(msg.Via) {
msg.Via = msg.Via.Next
}
@ -72,14 +68,14 @@ func RouteMessage(via *Via, contact *Addr, old *Msg) (msg *Msg, host string, por
}
if msg.Route != nil {
if msg.Method == "REGISTER" {
return nil, "", 0, errors.New("Don't route REGISTER requests")
return "", 0, errors.New("Don't route REGISTER requests")
}
if msg.Route.Uri.Params.Has("lr") {
// RFC3261 16.12.1.1 Basic SIP Trapezoid
host, port = msg.Route.Uri.Host, msg.Route.Uri.Port
} else {
// RFC3261 16.12.1.2: Traversing a Strict-Routing Proxy
msg.Route = old.Route.Copy()
msg.Route = msg.Route.Copy()
msg.Route.Last().Next = &Addr{Uri: msg.Request}
msg.Request = msg.Route.Uri
msg.Route = msg.Route.Next
@ -92,7 +88,7 @@ func RouteMessage(via *Via, contact *Addr, old *Msg) (msg *Msg, host string, por
return
}
func RouteAddress(host string, port uint16) (routes *AddressRoute, err error) {
func RouteAddress(host string, port uint16, wantSRV bool) (routes *AddressRoute, err error) {
if net.ParseIP(host) != nil {
if port == 0 {
port = 5060
@ -100,20 +96,22 @@ func RouteAddress(host string, port uint16) (routes *AddressRoute, err error) {
return &AddressRoute{Address: net.JoinHostPort(host, portstr(port))}, nil
}
if port == 0 {
_, srvs, err := net.LookupSRV("sip", "udp", host)
if err == nil && len(srvs) > 0 {
s := ""
for i := len(srvs) - 1; i >= 0; i-- {
routes = &AddressRoute{
Address: net.JoinHostPort(srvs[i].Target, portstr(srvs[i].Port)),
Next: routes,
if wantSRV {
_, srvs, err := net.LookupSRV("sip", "udp", host)
if err == nil && len(srvs) > 0 {
s := ""
for i := len(srvs) - 1; i >= 0; i-- {
routes = &AddressRoute{
Address: net.JoinHostPort(srvs[i].Target, portstr(srvs[i].Port)),
Next: routes,
}
s = " " + routes.Address + s
}
s = " " + routes.Address + s
log.Printf("%s routes to: %s", host, s)
return routes, nil
}
log.Printf("%s routes to: %s", host, s)
return routes, nil
log.Println("net.LookupSRV(sip, udp, %s) failed: %s", err)
}
log.Println("net.LookupSRV(sip, udp, %s) failed: %s", err)
port = 5060
}
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(host, portstr(port)))


+ 5
- 15
sip/transport.go View File

@ -11,20 +11,10 @@ import (
// Transport sends and receives SIP messages over UDP with stateless routing.
type Transport struct {
// Channel to which received SIP messages and errors are published.
C <-chan *Msg
E <-chan error
// Underlying UDP socket.
Sock *net.UDPConn
// When you send an outbound request (not a response) you have to set the via
// tag: ``msg.Via = tp.Via.Copy().Branch().SetNext(msg.Via)``. The details of
// the branch parameter... are tricky.
Via *Via
// Contact that gets put in outbound SIP messages.
C <-chan *Msg
E <-chan error
Sock *net.UDPConn
Via *Via
Contact *Addr
}
@ -67,7 +57,7 @@ func NewTransport(contact *Addr) (tp *Transport, err error) {
// Sends a SIP message.
func (tp *Transport) Send(msg *Msg) error {
PopulateMessage(tp.Via, tp.Contact, msg)
msg, host, port, err := RouteMessage(tp.Via, tp.Contact, msg)
host, port, err := RouteMessage(tp.Via, tp.Contact, msg)
if err != nil {
return err
}


+ 23
- 0
sip/util.go View File

@ -94,3 +94,26 @@ func unhex(b byte) byte {
}
return 0
}
func appendCollapse(buf []byte, amt int, fc byte) int {
switch fc {
case ' ', '\t', '\r', '\n':
if amt == 0 || buf[amt-1] != ' ' {
buf[amt] = ' '
amt++
}
default:
buf[amt] = fc
amt++
}
return amt
}
func appendLower(buf []byte, amt int, fc byte) int {
if 'A' <= fc && fc <= 'Z' {
buf[amt] = fc + 0x20
} else {
buf[amt] = fc
}
return amt + 1
}

+ 9
- 7
sip/via.go View File

@ -81,7 +81,7 @@ func (via *Via) Append(b *bytes.Buffer) error {
return nil
}
// deep copies a new Via object
// Copy returns a deep copy of via.
func (via *Via) Copy() *Via {
if via == nil {
return nil
@ -96,19 +96,21 @@ func (via *Via) Copy() *Via {
return res
}
// Sets newly generated branch ID and returns self.
// Branch mutates via with a newly generated branch ID.
func (via *Via) Branch() *Via {
via.Params["branch"] = util.GenerateBranch()
return via
}
// Sets Next field and returns self.
func (via *Via) SetNext(next *Via) *Via {
via.Next = next
return via
// Detach returns a shallow copy of via with Next set to nil.
func (via *Via) Detach() *Via {
res := new(Via)
*res = *via
res.Next = nil
return res
}
// returns pointer to last via in linked list.
// Last returns pointer to last via in linked list.
func (via *Via) Last() *Via {
if via != nil {
for ; via.Next != nil; via = via.Next {


Loading…
Cancel
Save