From eae40bdf3ebc5b82027313e2b3874ac191f0e8e3 Mon Sep 17 00:00:00 2001 From: Justine Alexandra Roberts Tunney Date: Mon, 22 Dec 2014 19:26:44 -0500 Subject: [PATCH] I have no idea what I'm doing. --- README.md | 1 + example/echo/echo_test.go | 18 +-- example/echo2/echo2_test.go | 67 +++++----- example/options/options_test.go | 6 +- sip/addr.go | 30 ++++- sip/compact.go | 29 +++++ sip/messages.go | 77 +++++++----- sip/msg.go | 130 +++++++++++--------- sip/phrases.go | 96 --------------- sip/prefs.go | 53 ++++---- sip/status.go | 182 ++++++++++++++++++++++++++++ sip/transport.go | 208 +++++++++++++------------------- sip/via.go | 7 +- util/util.go | 8 ++ 14 files changed, 515 insertions(+), 397 deletions(-) create mode 100644 sip/compact.go delete mode 100755 sip/phrases.go create mode 100755 sip/status.go diff --git a/README.md b/README.md index 23e4b53..5f4ae18 100755 --- a/README.md +++ b/README.md @@ -66,3 +66,4 @@ This is what a sip stack looks like: - [SDP (RFC 4566)](https://tools.ietf.org/html/rfc4566) - [RTP (RFC 3550)](https://tools.ietf.org/html/rfc3550) - [RTP DTMF (RFC 4733)](https://tools.ietf.org/html/rfc4733) +- [SIP 100rel (RFC 3262)](https://tools.ietf.org/html/rfc3262) diff --git a/example/echo/echo_test.go b/example/echo/echo_test.go index 06dcb02..b3177bf 100755 --- a/example/echo/echo_test.go +++ b/example/echo/echo_test.go @@ -227,9 +227,9 @@ func TestCallToEchoApp(t *testing.T) { t.Fatal("read 100 trying:", err) } log.Printf("<<< %s\n%s\n", raddr, string(memory[0:amt])) - msg, err := sip.ParseMsg(string(memory[0:amt])) - if err != nil { - t.Fatal("parse 100 trying", err) + msg := sip.ParseMsg(string(memory[0:amt])) + if msg.Error != nil { + t.Fatal("parse 100 trying", msg.Error) } if !msg.IsResponse || msg.Status != 100 || msg.Phrase != "Trying" { t.Fatal("didn't get 100 trying :[") @@ -242,9 +242,9 @@ func TestCallToEchoApp(t *testing.T) { t.Fatal("read 200 ok:", err) } log.Printf("<<< %s\n%s\n", raddr, string(memory[0:amt])) - msg, err = sip.ParseMsg(string(memory[0:amt])) - if err != nil { - t.Fatal("parse 200 ok:", err) + msg = sip.ParseMsg(string(memory[0:amt])) + if msg.Error != nil { + t.Fatal("parse 200 ok:", msg.Error) } if !msg.IsResponse || msg.Status != 200 || msg.Phrase != "OK" { t.Fatal("wanted 200 ok but got:", msg.Status, msg.Phrase) @@ -346,9 +346,9 @@ func TestCallToEchoApp(t *testing.T) { if err != nil { t.Fatal(err) } - msg, err = sip.ParseMsg(string(memory[0:amt])) - if err != nil { - t.Fatal(err) + msg = sip.ParseMsg(string(memory[0:amt])) + if msg.Error != nil { + t.Fatal(msg.Error) } if !msg.IsResponse || msg.Status != 200 || msg.Phrase != "OK" { t.Fatal("wanted bye response 200 ok but got:", msg.Status, msg.Phrase) diff --git a/example/echo2/echo2_test.go b/example/echo2/echo2_test.go index 01fa40e..c5e2928 100755 --- a/example/echo2/echo2_test.go +++ b/example/echo2/echo2_test.go @@ -3,7 +3,6 @@ package echo2_test import ( - "bytes" "github.com/jart/gosip/rtp" "github.com/jart/gosip/sdp" "github.com/jart/gosip/sip" @@ -19,10 +18,11 @@ func TestCallToEchoApp(t *testing.T) { from := &sip.Addr{Uri: &sip.URI{Host: "127.0.0.1"}} // Create the SIP UDP transport layer. - tp, err := sip.NewUDPTransport(from) + tp, err := sip.NewTransport(from) if err != nil { t.Fatal(err) } + defer tp.Sock.Close() // Create an RTP socket. rtpsock, err := net.ListenPacket("udp", "108.61.60.146:0") @@ -40,46 +40,35 @@ func TestCallToEchoApp(t *testing.T) { t.Fatal(err) } - // Receive provisional 100 Trying. - conn.SetDeadline(time.Now().Add(time.Second)) - memory := make([]byte, 2048) - amt, err := conn.Read(memory) - if err != nil { - t.Fatal("read 100 trying:", err) - } - log.Printf("<<< %s\n%s\n", raddr, string(memory[0:amt])) - msg, err := sip.ParseMsg(string(memory[0:amt])) - if err != nil { - t.Fatal("parse 100 trying", err) - } - if !msg.IsResponse || msg.Status != 100 || msg.Phrase != "Trying" { - t.Fatal("didn't get 100 trying :[") - } - - // Receive 200 OK. - conn.SetDeadline(time.Now().Add(5 * time.Second)) - amt, err = conn.Read(memory) - if err != nil { - t.Fatal("read 200 ok:", err) - } - log.Printf("<<< %s\n%s\n", raddr, string(memory[0:amt])) - msg, err = sip.ParseMsg(string(memory[0:amt])) - if err != nil { - t.Fatal("parse 200 ok:", err) - } - 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" { - t.Fatal("200 ok didn't have sdp payload") + // Consume provisional messages until we receive answer. + var rrtpaddr *net.UDPAddr + for { + tp.Sock.SetDeadline(time.Now().Add(time.Second)) + msg := tp.Recv() + if msg.Error != nil { + t.Fatal(msg.Error) + } + if msg.Status < sip.StatusOK { + log.Printf("Got provisional %d %s", msg.Status, msg.Phrase) + } + if msg.Headers["Content-Type"] == "application/sdp" { + log.Printf("Establishing media session") + rsdp, err := sdp.Parse(msg.Payload) + if err != nil { + t.Fatal("failed to parse sdp", err) + } + rrtpaddr = &net.UDPAddr{IP: net.ParseIP(rsdp.Addr), Port: int(rsdp.Audio.Port)} + } + if msg.Status == sip.StatusOK { + log.Printf("Answered!") + break + } else if msg.Status > sip.StatusOK { + t.Fatalf("Got %d %s", msg.Status, msg.Phrase) + NewAck(invite) + } } // Figure out where they want us to send RTP. - rsdp, err := sdp.Parse(msg.Payload) - if err != nil { - t.Fatal("failed to parse sdp", err) - } - rrtpaddr := &net.UDPAddr{IP: net.ParseIP(rsdp.Addr), Port: int(rsdp.Audio.Port)} // Acknowledge the 200 OK to answer the call. var ack sip.Msg diff --git a/example/options/options_test.go b/example/options/options_test.go index 77217c0..990b021 100755 --- a/example/options/options_test.go +++ b/example/options/options_test.go @@ -81,9 +81,9 @@ func TestOptions(t *testing.T) { t.Fatal(err) } - msg, err := sip.ParseMsg(string(memory[0:amt])) - if err != nil { - t.Fatal(err) + msg := sip.ParseMsg(string(memory[0:amt])) + if msg.Error != nil { + t.Fatal(msg.Error) } if !msg.IsResponse || msg.Status != 200 || msg.Phrase != "OK" { diff --git a/sip/addr.go b/sip/addr.go index 0df57ad..fca34c7 100755 --- a/sip/addr.go +++ b/sip/addr.go @@ -22,7 +22,6 @@ import ( "bytes" "errors" "github.com/jart/gosip/util" - "log" "strings" ) @@ -45,7 +44,7 @@ func ParseAddr(s string) (addr *Addr, err error) { // Extract display. switch n := strings.IndexAny(s, "\"<"); { case n < 0: - return nil, errors.New("invalid address") + return nil, errors.New("Invalid address") case s[n] == '<': // Display is not quoted. addr.Display, s = strings.Trim(s[0:n], " "), s[n+1:] case s[n] == '"': // We found an opening quote. @@ -58,7 +57,7 @@ func ParseAddr(s string) (addr *Addr, err error) { break LOL case '\\': // Escape sequence. if len(s) < 2 { - return nil, errors.New("evil quote escape") + return nil, errors.New("Evil quote escape") } switch s[1] { case '"': @@ -73,7 +72,7 @@ func ParseAddr(s string) (addr *Addr, err error) { } } if s == "" { - return nil, errors.New("no closing quote in display") + return nil, errors.New("No closing quote in display") } for s != "" { c := s[0] @@ -111,7 +110,7 @@ func ParseAddr(s string) (addr *Addr, err error) { if s != "" { addr.Next, err = ParseAddr(s) if err != nil { - log.Println("[NOTICE]", "dropping invalid bonus addr:", s, err) + return nil, err } } } @@ -189,3 +188,24 @@ func (addr *Addr) Last() *Addr { } return addr } + +// Returns number of items in the linked list. +func (addr *Addr) Len() int { + count := 0 + for ; addr != nil; addr = addr.Next { + count++ + } + return count +} + +// Returns self with linked list reversed. +func (addr *Addr) Reversed() *Addr { + var res *Addr + for ; addr != nil; addr = addr.Next { + a := new(Addr) + *a = *addr + a.Next = res + res = a + } + return addr +} diff --git a/sip/compact.go b/sip/compact.go new file mode 100644 index 0000000..dca4e2e --- /dev/null +++ b/sip/compact.go @@ -0,0 +1,29 @@ +// SIP Compact Header Definitions + +package sip + +func uncompactHeader(k string) string { + if header, ok := compactHeaders[k]; ok { + return header + } + return k +} + +// http://www.cs.columbia.edu/sip/compact.html +var compactHeaders = map[string]string{ + "a": "Accept-Contact", + "b": "Referred-By", + "c": "Content-Type", + "e": "Content-Encoding", + "f": "From", + "i": "Call-ID", + "k": "Supported", + "l": "Content-Length", + "m": "Contact", + "o": "Event", + "r": "Refer-To", + "s": "Subject", + "t": "To", + "u": "Allow-Events", + "v": "Via", +} diff --git a/sip/messages.go b/sip/messages.go index 56c7d3b..aecb898 100644 --- a/sip/messages.go +++ b/sip/messages.go @@ -6,49 +6,55 @@ import ( "log" ) -func NewRequest(tp Transport, method string, to, from *Addr) *Msg { +const ( + GosipUserAgent = "gosip/1.o" + GosipAllow = "INVITE, ACK, CANCEL, BYE, OPTIONS" +) + +func NewRequest(tp *Transport, method string, to, from *Addr) *Msg { return &Msg{ Method: method, Request: to.Uri.Copy(), - Via: tp.Via().Branch(), - From: from.Or(tp.Contact()).Tag(), + Via: tp.Via.Copy().Branch(), + From: from.Or(tp.Contact).Tag(), To: to.Copy(), - Contact: tp.Contact(), + Contact: tp.Contact, CallID: util.GenerateCallID(), CSeq: util.GenerateCSeq(), CSeqMethod: method, - Headers: Headers{"User-Agent": "gosip/1.o"}, + Headers: DefaultHeaders(), } } func NewResponse(msg *Msg, status int) *Msg { return &Msg{ - IsResponse: true, - Status: status, - Phrase: Phrases[status], - Via: msg.Via, - From: msg.From, - To: msg.To, - CallID: msg.CallID, - CSeq: msg.CSeq, - CSeqMethod: msg.CSeqMethod, - Headers: Headers{"User-Agent": "gosip/1.o"}, + IsResponse: true, + Status: status, + Phrase: Phrase(status), + Via: msg.Via, + From: msg.From, + To: msg.To, + CallID: msg.CallID, + CSeq: msg.CSeq, + CSeqMethod: msg.CSeqMethod, + RecordRoute: msg.RecordRoute, + Headers: DefaultHeaders(), } } // http://tools.ietf.org/html/rfc3261#section-17.1.1.3 -func NewAck(invite *Msg) *Msg { +func NewAck(original, last *Msg) *Msg { return &Msg{ Method: "ACK", - Request: invite.Request, - Via: invite.Via, - From: invite.From, - To: invite.To, - CallID: invite.CallID, - CSeq: invite.CSeq, + Request: original.Request, + Via: original.Via.Copy().SetNext(nil), + From: original.From, + To: last.To, + CallID: original.CallID, + CSeq: original.CSeq, CSeqMethod: "ACK", - Route: invite.Route, - Headers: Headers{"User-Agent": "gosip/1.o"}, + Route: last.RecordRoute.Reversed(), + Headers: DefaultHeaders(), } } @@ -65,23 +71,23 @@ func NewCancel(invite *Msg) *Msg { CallID: invite.CallID, CSeq: invite.CSeq, CSeqMethod: "CANCEL", - Route: invite.Route, - Headers: Headers{"User-Agent": "gosip/1.o"}, + Route: invite.RecordRoute.Reversed(), + Headers: DefaultHeaders(), } } -func NewBye(last, invite, ok200 *Msg) *Msg { +func NewBye(invite, last *Msg) *Msg { return &Msg{ - Request: ok200.Contact.Uri, - Via: invite.Via.Branch(), + Method: "BYE", + Request: last.Contact.Uri, + Via: invite.Via, From: last.From, To: last.To, CallID: last.CallID, - Method: "BYE", CSeq: last.CSeq + 1, CSeqMethod: "BYE", - Route: ok200.RecordRoute, - Headers: make(map[string]string), + Route: last.RecordRoute.Reversed(), + Headers: DefaultHeaders(), } } @@ -109,3 +115,10 @@ func AttachSDP(msg *Msg, sdp *sdp.SDP) { msg.Headers["Content-Type"] = "application/sdp" msg.Payload = sdp.String() } + +func DefaultHeaders() Headers { + return Headers{ + "User-Agent": GosipUserAgent, + "Allow": GosipAllow, + } +} diff --git a/sip/msg.go b/sip/msg.go index 1acbecb..0259e08 100755 --- a/sip/msg.go +++ b/sip/msg.go @@ -5,7 +5,9 @@ package sip import ( "bytes" "errors" + "fmt" "log" + "net" "strconv" "strings" ) @@ -15,17 +17,19 @@ 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 { - magic int // Used for pseudo msgs or to mark direction - OutboundProxy string // Use Route instead if possible + // Special non-SIP fields. + Error error // Set to indicate an error with this message + SourceAddr *net.UDPAddr // Set by transport layer as received address - IsResponse bool // This is a response (like 404 GO DIE) + // 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 + // Mandatory headers. 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 @@ -60,21 +64,27 @@ func (msg *Msg) String() string { } // Parses a SIP message into a data structure. This takes ~70 µs on average. -func ParseMsg(packet string) (msg *Msg, err error) { +func ParseMsg(packet string) (msg *Msg) { msg = new(Msg) if packet == "" { - return nil, errors.New("empty msg") + msg.Error = errors.New("Empty msg") + return 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") + msg.Error = errors.New("Too few lines") + return msg } var k, v string var okVia, okTo, okFrom, okCallID, okComputer bool - err = msg.parseFirstLine(lines[0]) + err := msg.parseFirstLine(lines[0]) + if err != nil { + msg.Error = err + return msg + } hdrs := lines[1:] msg.Headers = make(map[string]string, len(hdrs)) msg.MaxForwards = 70 @@ -94,6 +104,7 @@ func ParseMsg(packet string) (msg *Msg, err error) { if k == "" || v == "" { log.Println("[NOTICE]", "blank header found", hdr) } + k = uncompactHeader(k) } else { log.Println("[NOTICE]", "header missing delimiter", hdr) continue @@ -107,27 +118,29 @@ func ParseMsg(packet string) (msg *Msg, err error) { okVia = true *viap, err = ParseVia(v) if err != nil { - return nil, errors.New("Via header - " + err.Error()) + msg.Error = errors.New("Bad Via header: " + err.Error()) + } else { + viap = &(*viap).Next } - viap = &(*viap).Next case "to": okTo = true msg.To, err = ParseAddr(v) if err != nil { - return nil, errors.New("To header - " + err.Error()) + msg.Error = errors.New("Bad To header: " + err.Error()) } case "from": okFrom = true msg.From, err = ParseAddr(v) if err != nil { - return nil, errors.New("From header - " + err.Error()) + msg.Error = errors.New("Bad From header: " + err.Error()) } case "contact": *contactp, err = ParseAddr(v) if err != nil { - return nil, errors.New("Contact header - " + err.Error()) + msg.Error = errors.New("Bad Contact header: " + err.Error()) + } else { + contactp = &(*contactp).Last().Next } - contactp = &(*contactp).Next case "cseq": okComputer = false if n := strings.Index(v, " "); n > 0 { @@ -138,90 +151,68 @@ func ParseMsg(packet string) (msg *Msg, err error) { } } if !okComputer { - return nil, errors.New("Bad CSeq Header") + msg.Error = errors.New("Bad CSeq Header") } case "content-length": if cl, err := strconv.Atoi(v); err == nil { - if cl > len(msg.Payload) { - msg.Payload = msg.Payload[0:cl] - log.Println("[DEBUG]", "discarding extra sip payload bytes") - } else if cl > len(msg.Payload) { - return nil, errors.New("content-length > len(payload)") + if cl != len(msg.Payload) { + msg.Error = 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") + msg.Error = 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") + msg.Error = 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") + msg.Error = 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") + msg.Error = errors.New("Bad Max-Forwards header") } case "route": *routep, err = ParseAddr(v) if err != nil { - return nil, errors.New("Bad Route header: " + err.Error()) + msg.Error = errors.New("Bad Route header: " + err.Error()) + } else { + routep = &(*routep).Last().Next } - routep = &(*routep).Next case "record-route": *rroutep, err = ParseAddr(v) if err != nil { - return nil, errors.New("Bad Record-Route header: " + err.Error()) + msg.Error = errors.New("Bad Record-Route header: " + err.Error()) + } else { + rroutep = &(*rroutep).Last().Next } - rroutep = &(*rroutep).Next case "p-asserted-identity": msg.Paid, err = ParseAddr(v) if err != nil { - return nil, errors.New("Bad P-Asserted-Identity header: " + err.Error()) + msg.Error = 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()) + msg.Error = 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") + msg.Error = errors.New("Missing mandatory headers") } - return msg, nil -} - -func (msg *Msg) parseFirstLine(s string) (err error) { - toks := strings.Split(s, " ") - if toks != nil && len(toks) == 3 && toks[2] == "SIP/2.0" { - msg.Phrase = "" - msg.Status = 0 - msg.Method = toks[0] - msg.Request = new(URI) - msg.Request, err = ParseURI(toks[1]) - } else if toks != nil && len(toks) == 3 && toks[0] == "SIP/2.0" { - msg.IsResponse = true - msg.Method = "" - msg.Request = nil - msg.Phrase = toks[2] - msg.Status, err = strconv.Atoi(toks[1]) - if err != nil { - return errors.New("Invalid status") - } - } else { - err = errors.New("Bad protocol or request line") - } - return err + return } func (msg *Msg) Copy() *Msg { @@ -267,11 +258,7 @@ func (msg *Msg) Append(b *bytes.Buffer) error { return errors.New("Msg.Status >= 700") } if msg.Phrase == "" { - if v, ok := Phrases[msg.Status]; ok { - msg.Phrase = v - } else { - return errors.New("Msg.Phrase not set and Msg.Status is unknown") - } + msg.Phrase = Phrase(msg.Status) } b.WriteString("SIP/2.0 ") b.WriteString(strconv.Itoa(msg.Status)) @@ -376,3 +363,26 @@ func (msg *Msg) Append(b *bytes.Buffer) error { return nil } + +func (msg *Msg) parseFirstLine(s string) (err error) { + toks := strings.Split(s, " ") + if toks != nil && len(toks) == 3 && toks[2] == "SIP/2.0" { + msg.Phrase = "" + msg.Status = 0 + msg.Method = toks[0] + msg.Request = new(URI) + msg.Request, err = ParseURI(toks[1]) + } else if toks != nil && len(toks) == 3 && toks[0] == "SIP/2.0" { + msg.IsResponse = true + msg.Method = "" + msg.Request = nil + msg.Phrase = toks[2] + msg.Status, err = strconv.Atoi(toks[1]) + if err != nil { + return errors.New("Invalid status") + } + } else { + err = errors.New("Bad protocol or request line") + } + return err +} diff --git a/sip/phrases.go b/sip/phrases.go deleted file mode 100755 index 39bd1a5..0000000 --- a/sip/phrases.go +++ /dev/null @@ -1,96 +0,0 @@ -// Standard SIP Protocol Messages -// -// http://www.iana.org/assignments/sip-parameters - -package sip - -var Phrases = map[int]string{ - // 1xx: Provisional -- request received, continuing to process the - // request; - 100: "Trying", // indicates server is not totally pwnd - 180: "Ringing", // i promise the remote phone is ringing - 181: "Call Is Being Forwarded", - 182: "Queued", - 183: "Session Progress", // establish early media (pstn ringback) - - // 2xx: Success -- the action was successfully received, understood, - // and accepted; - 200: "OK", // call is answered - 202: "Accepted", // [RFC3265] - 204: "No Notification", // [RFC5839] - - // 3xx: Redirection -- further action needs to be taken in order to - // complete the request; - 300: "Multiple Choices", - 301: "Moved Permanently", - 302: "Moved Temporarily", // send your call there instead kthx - 305: "Use Proxy", // you fool! send your call there instead - 380: "Alternative Service", - - // 4xx: Client Error -- the request contains bad syntax or cannot be - // fulfilled at this server; - 400: "Bad Request", // missing headers, bad format, etc. - 401: "Unauthorized", // resend request with auth header - 402: "Payment Required", // i am greedy - 403: "Forbidden", // gtfo - 404: "Not Found", // wat? - 405: "Method Not Allowed", // i don't support that type of request - 406: "Not Acceptable", - 407: "Proxy Authentication Required", - 408: "Request Timeout", - 409: "Conflict", - 410: "Gone", // shaniqua don't live here no more - 411: "Length Required", - 412: "Conditional Request Failed", // [RFC3903] - 413: "Request Entity Too Large", - 414: "Request-URI Too Long", - 415: "Unsupported Media Type", - 416: "Unsupported URI Scheme", - 417: "Unknown Resource-Priority", - 420: "Bad Extension", - 421: "Extension Required", - 422: "Session Interval Too Small", // [RFC4028] - 423: "Interval Too Brief", - 428: "Use Identity Header", // [RFC4474] - 429: "Provide Referrer Identity", // [RFC3892] - 430: "Flow Failed", // [RFC5626] - 433: "Anonymity Disallowed", // [RFC5079] - 436: "Bad Identity-Info", // [RFC4474] - 437: "Unsupported Certificate", // [RFC4474] - 438: "Invalid Identity Header", // [RFC4474] - 439: "First Hop Lacks Outbound Support", // [RFC5626] - 440: "Max-Breadth Exceeded", // [RFC5393] - 470: "Consent Needed", // [RFC5360] - 480: "Temporarily Unavailable", // fast busy or soft fail - 481: "Call/Transaction Does Not Exist", // bad news - 482: "Loop Detected", // froot looping - 483: "Too Many Hops", // froot looping - 484: "Address Incomplete", - 485: "Ambiguous", - 486: "Busy Here", - 487: "Request Terminated", - 488: "Not Acceptable Here", - 489: "Bad Event", // [RFC3265] - 491: "Request Pending", - 493: "Undecipherable", - 494: "Security Agreement Required", // [RFC3329] - - // 5xx: Server Error -- the server failed to fulfill an apparently - // valid request; - 500: "Internal Server Error", - 501: "Not Implemented", - 502: "Bad Gateway", - 503: "Service Unavailable", - 504: "Gateway Time-out", - 505: "Version Not Supported", - 513: "Message Too Large", - 580: "Precondition Failure", // [RFC3312] - - // 6xx: Global Failure -- the request cannot be fulfilled at any - // server. - 600: "Busy Everywhere", - 603: "Decline", - 604: "Does Not Exist Anywhere", - 606: "Not Acceptable", - 687: "Dialog Terminated", -} diff --git a/sip/prefs.go b/sip/prefs.go index c7118ed..6fee1a8 100755 --- a/sip/prefs.go +++ b/sip/prefs.go @@ -1,7 +1,7 @@ // Global Settings. You can change these at startup to fine tune // certain behaviors. -package gosip +package sip import ( "os" @@ -9,54 +9,51 @@ import ( ) var ( - // how often to check for shutdowns + // How often to check for shutdowns. DeathClock = 200 * time.Millisecond - // sip egress msg length must not exceed me. if you are brave and - // use jumbo frames you can increase this + // SIP egress msg length must not exceed me. If you are brave and use jumbo + // frames you can increase this value. MTU = 1450 - // maximum number SRV/Redirects/Etc. to entertain. more - // accurately, this is the maximum number of time's we're allowed - // to enter the "calling" state. + // Maximum number SRV/Redirects/Etc. to entertain. More accurately, this is + // the maximum number of time's we're allowed to enter the "calling" state. MaxAttempts = 10 - // this is how long to wait (in nanoseconds) for a 100 trying - // response before retransmitting the invite. multiply this by - // RetransmitAttempts and that's how long it'll be before we try - // another server. that might seem like a very long time but - // happy networks facilitate fast failover by sending hard errors - // (ICMP, 480, 500-599) + // This is how long to wait (in nanoseconds) for a 100 trying response before + // retransmitting the invite. Multiply this by RetransmitAttempts and that's + // how long it'll be before we try another server. That might seem like a + // very long time but happy networks facilitate fast failover by sending hard + // errors (ICMP, 480, 500-599) TryingTimeout = 500 * time.Millisecond - // how many times to attempt to retransmit before moving on + // How many times to attempt to retransmit before moving on. RetransmitAttempts = 2 - // number of nanoseconds (across all attempts) before we give up - // trying to connect a call. this doesn't mean the call has to - // have been answered but rather has moved beyond the "calling" - // state and doesn't go back. + // Number of nanoseconds (across all attempts) before we give up trying to + // connect a call. This doesn't mean the call has to have been answered but + // rather has moved beyond the "calling" state and doesn't go back. GiveupTimeout = 3 * time.Second - // ToDo: how long to wait before refreshing a call with a - // re-INVITE message. + // TODO(jart): How long to wait before refreshing a call with a re-INVITE + // message. RefreshTimeout = 15 * time.Minute - // how many times are proxies allowed to forward a message + // How many times are proxies allowed to forward a message. MaxForwards int = 70 - // aprox. how long the transaction engine remembers old Call-IDs - // to prevent a retransmit from accidentally opening a new dialog. + // Approx. how long the transaction engine remembers old Call-IDs to prevent + // a retransmit from accidentally opening a new dialog. CallIDBanDuration = 35 * time.Second - // aprox. how often to sweep old transactions from ban list + // Approx. how often to sweep old transactions from ban list. CallIDBanSweepDuration = 5 * time.Second - // should the transport layer add timestamps to Via headers - // (µsi/µse = utc microseconds since unix epoch ingress/egress) + // Should the transport layer add timestamps to Via headers (µsi/µse = UTC + // microseconds since unix epoch ingress/egress). ViaTimestampTagging = true - // use this feature to print out raw sip messages the moment they - // are sent/received by the transport layer + // Use this feature to print out raw sip messages the moment they are + // sent/received by the transport layer. TransportTrace = (os.Getenv("TPORT_LOG") == "true") ) diff --git a/sip/status.go b/sip/status.go new file mode 100755 index 0000000..5959295 --- /dev/null +++ b/sip/status.go @@ -0,0 +1,182 @@ +// SIP Protocol Statuses +// +// http://www.iana.org/assignments/sip-parameters + +package sip + +const ( + // 1xx: Provisional -- request received, continuing to process the request. + StatusTrying = 100 // Indicates server is not totally pwnd. + StatusRinging = 180 // Remote phone is definitely ringing. + StatusCallIsBeingForwarded = 181 + StatusQueued = 182 + StatusSessionProgress = 183 // Establish early media (PSTN ringback) + + // 2xx: Success -- the action was successfully received, understood, + // and accepted; + StatusOK = 200 // Call is answered + StatusAccepted = 202 // [RFC3265] + StatusNoNotification = 204 // [RFC5839] + + // 3xx: Redirection -- further action needs to be taken in order to + // complete the request; + StatusMultipleChoices = 300 + StatusMovedPermanently = 301 + StatusMovedTemporarily = 302 // Send your call there instead kthx. + StatusUseProxy = 305 // You fool! Send your call there instead. + StatusAlternativeService = 380 + + // 4xx: Client Error -- the request contains bad syntax or cannot be + // fulfilled at this server; + StatusBadRequest = 400 // Missing headers, bad format, etc. + StatusUnauthorized = 401 // Resend request with auth header. + StatusPaymentRequired = 402 // I am greedy. + StatusForbidden = 403 // gtfo + StatusNotFound = 404 // wat? + StatusMethodNotAllowed = 405 // I don't support that type of request. + StatusNotAcceptable = 406 + StatusProxyAuthenticationRequired = 407 + StatusRequestTimeout = 408 + StatusConflict = 409 + StatusGone = 410 // Shaniqua don't live here no more. + StatusLengthRequired = 411 + StatusConditionalRequestFailed = 412 // [RFC3903] + StatusRequestEntityTooLarge = 413 + StatusRequestURITooLong = 414 + StatusUnsupportedMediaType = 415 + StatusUnsupportedURIScheme = 416 + StatusUnknownResourcePriority = 417 + StatusBadExtension = 420 + StatusExtensionRequired = 421 + StatusSessionIntervalTooSmall = 422 // [RFC4028] + StatusIntervalTooBrief = 423 + StatusUseIdentityHeader = 428 // [RFC4474] + StatusProvideReferrerIdentity = 429 // [RFC3892] + StatusFlowFailed = 430 // [RFC5626] + StatusAnonymityDisallowed = 433 // [RFC5079] + StatusBadIdentityInfo = 436 // [RFC4474] + StatusUnsupportedCertificate = 437 // [RFC4474] + StatusInvalidIdentityHeader = 438 // [RFC4474] + StatusFirstHopLacksOutboundSupport = 439 // [RFC5626] + StatusMaxBreadthExceeded = 440 // [RFC5393] + StatusConsentNeeded = 470 // [RFC5360] + StatusTemporarilyUnavailable = 480 // fast busy or soft fail + StatusCallTransactionDoesNotExist = 481 // Bad news + StatusLoopDetected = 482 // Froot looping + StatusTooManyHops = 483 // Froot looping + StatusAddressIncomplete = 484 + StatusAmbiguous = 485 + StatusBusyHere = 486 + StatusRequestTerminated = 487 + StatusNotAcceptableHere = 488 + StatusBadEvent = 489 // [RFC3265] + StatusRequestPending = 491 + StatusUndecipherable = 493 + StatusSecurityAgreementRequired = 494 // [RFC3329] + + // 5xx: Server Error -- the server failed to fulfill an apparently + // valid request; + StatusInternalServerError = 500 + StatusNotImplemented = 501 + StatusBadGateway = 502 + StatusServiceUnavailable = 503 + StatusGatewayTimeout = 504 + StatusVersionNotSupported = 505 + StatusMessageTooLarge = 513 + StatusPreconditionFailure = 580 // [RFC3312] + + // 6xx: Global Failure -- the request cannot be fulfilled at any + // server. + StatusBusyEverywhere = 600 + StatusDecline = 603 + StatusDoesNotExistAnywhere = 604 + StatusNotAcceptable606 = 606 + StatusDialogTerminated = 687 + + // 8xx: Special gosip errors + Status8xxProgrammerError = 800 + Status8xxNetworkError = 801 +) + +func Phrase(status int) string { + if phrase, ok := phrases[status]; ok { + return phrase + } + return "Unknown Status Code" +} + +var phrases = map[int]string{ + StatusTrying: "Trying", + StatusRinging: "Ringing", + StatusCallIsBeingForwarded: "Call Is Being Forwarded", + StatusQueued: "Queued", + StatusSessionProgress: "Session Progress", + StatusOK: "OK", + StatusAccepted: "Accepted", + StatusNoNotification: "No Notification", + StatusMultipleChoices: "Multiple Choices", + StatusMovedPermanently: "Moved Permanently", + StatusMovedTemporarily: "Moved Temporarily", + StatusUseProxy: "Use Proxy", + StatusAlternativeService: "Alternative Service", + StatusBadRequest: "Bad Request", + StatusUnauthorized: "Unauthorized", + StatusPaymentRequired: "Payment Required", + StatusForbidden: "Forbidden", + StatusNotFound: "Not Found", + StatusMethodNotAllowed: "Method Not Allowed", + StatusNotAcceptable: "Not Acceptable", + StatusProxyAuthenticationRequired: "Proxy Authentication Required", + StatusRequestTimeout: "Request Timeout", + StatusConflict: "Conflict", + StatusGone: "Gone", + StatusLengthRequired: "Length Required", + StatusConditionalRequestFailed: "Conditional Request Failed", + StatusRequestEntityTooLarge: "Request Entity Too Large", + StatusRequestURITooLong: "Request-URI Too Long", + StatusUnsupportedMediaType: "Unsupported Media Type", + StatusUnsupportedURIScheme: "Unsupported URI Scheme", + StatusUnknownResourcePriority: "Unknown Resource-Priority", + StatusBadExtension: "Bad Extension", + StatusExtensionRequired: "Extension Required", + StatusSessionIntervalTooSmall: "Session Interval Too Small", + StatusIntervalTooBrief: "Interval Too Brief", + StatusUseIdentityHeader: "Use Identity Header", + StatusProvideReferrerIdentity: "Provide Referrer Identity", + StatusFlowFailed: "Flow Failed", + StatusAnonymityDisallowed: "Anonymity Disallowed", + StatusBadIdentityInfo: "Bad Identity-Info", + StatusUnsupportedCertificate: "Unsupported Certificate", + StatusInvalidIdentityHeader: "Invalid Identity Header", + StatusFirstHopLacksOutboundSupport: "First Hop Lacks Outbound Support", + StatusMaxBreadthExceeded: "Max-Breadth Exceeded", + StatusConsentNeeded: "Consent Needed", + StatusTemporarilyUnavailable: "Temporarily Unavailable", + StatusCallTransactionDoesNotExist: "Call/Transaction Does Not Exist", + StatusLoopDetected: "Loop Detected", + StatusTooManyHops: "Too Many Hops", + StatusAddressIncomplete: "Address StatusIncomplete", + StatusAmbiguous: "Ambiguous", + StatusBusyHere: "Busy Here", + StatusRequestTerminated: "Request Terminated", + StatusNotAcceptableHere: "Not Acceptable Here", + StatusBadEvent: "Bad Event", + StatusRequestPending: "Request Pending", + StatusUndecipherable: "Undecipherable", + StatusSecurityAgreementRequired: "Security Agreement Required", + StatusInternalServerError: "Internal Server Error", + StatusNotImplemented: "Not Implemented", + StatusBadGateway: "Bad Gateway", + StatusServiceUnavailable: "Service Unavailable", + StatusGatewayTimeout: "Gateway Time-out", + StatusVersionNotSupported: "Version Not Supported", + StatusMessageTooLarge: "Message Too Large", + StatusPreconditionFailure: "Precondition Failure", + StatusBusyEverywhere: "Busy Everywhere", + StatusDecline: "Decline", + StatusDoesNotExistAnywhere: "Does Not Exist Anywhere", + StatusNotAcceptable606: "Not Acceptable", + StatusDialogTerminated: "Dialog Terminated", + Status8xxProgrammerError: "Programmer Error", + Status8xxNetworkError: "Network Error", +} diff --git a/sip/transport.go b/sip/transport.go index 09f320b..80fc3b0 100755 --- a/sip/transport.go +++ b/sip/transport.go @@ -7,7 +7,6 @@ import ( "bytes" "errors" "flag" - "fmt" "github.com/jart/gosip/util" "log" "net" @@ -21,48 +20,34 @@ var ( timestampTagging = flag.Bool("timestampTagging", false, "Add microsecond timestamps to Via tags") ) -// Transport defines any object capable of sending and receiving SIP messages. -// Such objects are responsible for their own reliability. This means checking -// network errors, raising alarms, rebinding sockets, etc. -type Transport interface { - // Sends a SIP message. Will not modify msg. - Send(msg *Msg) error +// Transport sends and receives SIP messages over UDP with stateless routing. +type Transport struct { - // Receives a SIP message. Must only be called by one goroutine. The received - // address is injected into the first Via header as the "received" param. - Recv() (msg *Msg, err error) - - // Closes underlying resources. Please make sure all calls using this - // transport complete first. - Close() error + // Thing returned by ListenPacket + Sock *net.UDPConn // When you send an outbound request (not a response) you have to set the via - // tag: ``msg.Via = tport.Via().SetBranch().SetNext(msg.Via)``. The details - // of the branch parameter... are tricky. - Via() *Via + // tag: ``msg.Via = tp.Via.Copy().Branch().SetNext(msg.Via)``. The details of + // the branch parameter... are tricky. + Via *Via // Returns a linked list of all canonical names and/or IP addresses that may // be used to contact *this specific* transport. - Contact() *Addr -} + Contact *Addr -// Transport implementation that serializes messages to/from a UDP socket. -type udpTransport struct { - sock *net.UDPConn // thing returned by ListenUDP - addr *net.UDPAddr // handy for getting ip (contact might be host) - buf []byte // reusable memory for serialization - via *Via // who are we? - contact *Addr // uri that points to this specific transport + // Reusable memory for serialization + buf []byte } // Creates a new stateless network mechanism for transmitting and receiving SIP // signalling messages. // -// 'contact' is a SIP address, e.g. "", that tells how to bind -// sockets. This value is also used for contact headers which tell other -// user-agents where to send responses and hence should only contain an IP or -// canonical address. -func NewUDPTransport(contact *Addr) (tp Transport, err error) { +// contact is a SIP address, e.g. "", that tells how to bind +// sockets. If contact.Uri.Port is 0, then a port will be selected randomly. +// This value is also used for contact headers which tell other user-agents +// where to send responses and hence should only contain an IP or canonical +// address. +func NewTransport(contact *Addr) (tp *Transport, err error) { saddr := util.HostPortToString(contact.Uri.Host, contact.Uri.Port) sock, err := net.ListenPacket("udp", saddr) if err != nil { @@ -72,31 +57,29 @@ func NewUDPTransport(contact *Addr) (tp Transport, err error) { contact = contact.Copy() contact.Uri.Port = uint16(addr.Port) contact.Uri.Params["transport"] = addr.Network() - return &udpTransport{ - sock: sock.(*net.UDPConn), - addr: addr, - buf: make([]byte, 2048), - contact: contact, - via: &Via{ - Version: "2.0", - Proto: strings.ToUpper(addr.Network()), - Host: contact.Uri.Host, - Port: contact.Uri.Port, + return &Transport{ + Sock: sock.(*net.UDPConn), + Contact: contact, + Via: &Via{ + Host: contact.Uri.Host, + Port: contact.Uri.Port, }, }, nil } -func (tp *udpTransport) Send(msg *Msg) error { +// Sends a SIP message. +func (tp *Transport) Send(msg *Msg) error { msg, saddr, err := tp.route(msg) if err != nil { return err } addr, err := net.ResolveUDPAddr("ip", saddr) if err != nil { - return errors.New(fmt.Sprintf( - "udpTransport(%s) failed to resolve %s: %s", tp.addr, saddr, err)) + return err + } + if msg.MaxForwards > 0 { + msg.MaxForwards-- } - msg.MaxForwards-- ts := time.Now() addTimestamp(msg, ts) var b bytes.Buffer @@ -104,58 +87,47 @@ func (tp *udpTransport) Send(msg *Msg) error { if *tracing { tp.trace("send", b.String(), addr, ts) } - _, err = tp.sock.WriteTo(b.Bytes(), addr) + _, err = tp.Sock.WriteTo(b.Bytes(), addr) if err != nil { - return errors.New(fmt.Sprintf( - "udpTransport(%s) write failed: %s", tp.addr, err)) + return err } return nil } -func (tp *udpTransport) Recv() (msg *Msg, err error) { - for { - amt, addr, err := tp.sock.ReadFromUDP(tp.buf) - if err != nil { - return nil, errors.New(fmt.Sprintf( - "udpTransport(%s) read failed: %s", tp.addr, err)) - } - ts := time.Now() - packet := string(tp.buf[0:amt]) - if *tracing { - tp.trace("recv", packet, addr, ts) - } - // Validation: http://tools.ietf.org/html/rfc3261#section-16.3 - msg, err = ParseMsg(packet) - if err != nil { - log.Printf("udpTransport(%s) got bad message from %s: %s\n%s", tp.addr, addr, err, packet) - continue - } - if msg.Via.Host != addr.IP.String() || int(msg.Via.Port) != addr.Port { - msg.Via.Params["received"] = addr.String() - } - addTimestamp(msg, ts) - if !tp.sanityCheck(msg) { - continue - } - tp.preprocess(msg) - break +// Receives a SIP message. The received address is injected into the first Via +// header as the "received" param. The Error field of msg should be checked. If +// msg.Status is Status8xxNetworkError, it means the underlying socket died. If +// you set a deadline, you should check: util.IsTimeout(msg.Error). +// +// Warning: Must only be called by one goroutine. +func (tp *Transport) Recv() *Msg { + if tp.buf == nil { + tp.buf = make([]byte, 2048) } - return msg, nil -} - -func (tp *udpTransport) Via() *Via { - return tp.via -} - -func (tp *udpTransport) Contact() *Addr { - return tp.contact -} - -func (tp *udpTransport) Close() error { - return tp.sock.Close() + amt, addr, err := tp.Sock.ReadFromUDP(tp.buf) + if err != nil { + return &Msg{Status: Status8xxNetworkError, Error: err} + } + ts := time.Now() + packet := string(tp.buf[0:amt]) + if *tracing { + tp.trace("recv", packet, addr, ts) + } + // Validation: http://tools.ietf.org/html/rfc3261#section-16.3 + msg := ParseMsg(packet) + if msg.Error != nil { + return msg + } + addReceived(msg, addr) + addTimestamp(msg, ts) + if !tp.sanityCheck(msg) { + return msg + } + tp.preprocess(msg) + return msg } -func (tp *udpTransport) trace(dir, pkt string, addr net.Addr, t time.Time) { +func (tp *Transport) trace(dir, pkt string, addr *net.UDPAddr, t time.Time) { size := len(pkt) bar := strings.Repeat("-", 72) suffix := "\n " @@ -174,36 +146,33 @@ func (tp *udpTransport) trace(dir, pkt string, addr net.Addr, t time.Time) { bar) } -// Test if this message is acceptable. -func (tp *udpTransport) sanityCheck(msg *Msg) bool { +// Checks if message is acceptable, otherwise sets msg.Error and returns false. +func (tp *Transport) sanityCheck(msg *Msg) bool { if msg.MaxForwards <= 0 { - log.Printf("udpTransport(%s) froot loop detected\n%s", tp.addr, msg) go tp.Send(NewResponse(msg, 483)) - return false + msg.Error = errors.New("Froot loop detected") } if msg.IsResponse { if msg.Status >= 700 { - log.Printf("udpTransport(%s) msg has crazy status number\n%s", tp.addr, msg) go tp.Send(NewResponse(msg, 400)) - return false + msg.Error = errors.New("Crazy status number") } } else { if msg.CSeqMethod == "" || msg.CSeqMethod != msg.Method { - log.Printf("udpTransport(%s) bad cseq number\n%s", tp.addr, msg) go tp.Send(NewResponse(msg, 400)) - return false + msg.Error = errors.New("Bad CSeq") } } - return true + return msg.Error == nil } // Perform some ingress message mangling. -func (tp *udpTransport) preprocess(msg *Msg) { - if tp.contact.Compare(msg.Route) { - log.Printf("udpTransport(%s) removing our route header: %s", tp.addr, msg.Route) +func (tp *Transport) preprocess(msg *Msg) { + if tp.Contact.Compare(msg.Route) { + log.Printf("Removing our route header: %s", msg.Route) msg.Route = msg.Route.Next } - if _, ok := msg.Request.Params["lr"]; ok && msg.Route != nil && tp.contact.Uri.Compare(msg.Request) { + if _, ok := msg.Request.Params["lr"]; ok && msg.Route != nil && tp.Contact.Uri.Compare(msg.Request) { // RFC3261 16.4 Route Information Preprocessing // RFC3261 16.12.1.2: Traversing a Strict-Routing Proxy var oldReq, newReq *URI @@ -220,39 +189,34 @@ func (tp *udpTransport) preprocess(msg *Msg) { seclast.Next = nil msg.Route.Last() } - log.Printf("udpTransport(%s) fixing request uri after strict router traversal: %s -> %s", - tp.addr, oldReq, newReq) + log.Printf("Fixing request URI after strict router traversal: %s -> %s", oldReq, newReq) } } -func (tp *udpTransport) route(old *Msg) (msg *Msg, saddr string, err error) { +func (tp *Transport) route(old *Msg) (msg *Msg, saddr string, err error) { var host string var port uint16 msg = new(Msg) - *msg = *old // Shallow copy is sufficient. + *msg = *old // Start off with a shallow copy. if msg.IsResponse { msg.Via = old.Via.Copy() - if msg.Via.CompareAddr(tp.via) { + if msg.Via.CompareAddr(tp.Via) { // In proxy scenarios we have to remove our own Via. msg.Via = msg.Via.Next } if msg.Via == nil { - return nil, "", errors.New("Ran out of Via headers when forwarding Response!") + return nil, "", errors.New("Message missing Via header") } - if msg.Via != nil { - if received, ok := msg.Via.Params["received"]; ok { - return msg, received, nil - } else { - host, port = msg.Via.Host, msg.Via.Port - } + if received, ok := msg.Via.Params["received"]; ok { + return msg, received, nil } else { - return nil, "", errors.New("Message missing Via header") + host, port = msg.Via.Host, msg.Via.Port } } else { if msg.Request == nil { return nil, "", errors.New("Missing request URI") } - if !msg.Via.CompareAddr(tp.via) { + if !msg.Via.CompareAddr(tp.Via) { return nil, "", errors.New("You forgot to say: msg.Via = tp.Via(msg.Via)") } if msg.Route != nil { @@ -276,14 +240,16 @@ func (tp *udpTransport) route(old *Msg) (msg *Msg, saddr string, err error) { host, port = msg.Request.Host, msg.Request.Port } } - if msg.OutboundProxy != "" { - saddr = msg.OutboundProxy - } else { - saddr = util.HostPortToString(host, port) - } + saddr = util.HostPortToString(host, port) return } +func addReceived(msg *Msg, addr *net.UDPAddr) { + if msg.Via.Host != addr.IP.String() || int(msg.Via.Port) != addr.Port { + msg.Via.Params["received"] = addr.String() + } +} + func addTimestamp(msg *Msg, ts time.Time) { if *timestampTagging { msg.Via.Params["µsi"] = strconv.FormatInt(ts.UnixNano()/int64(time.Microsecond), 10) diff --git a/sip/via.go b/sip/via.go index a960778..d92f078 100755 --- a/sip/via.go +++ b/sip/via.go @@ -11,18 +11,18 @@ import ( ) var ( - ViaBadHeader = errors.New("bad via header") - ViaProtoBlank = errors.New("via.Proto blank") + ViaBadHeader = errors.New("Bad Via header") + ViaProtoBlank = errors.New("Via Proto blank") ) // Example: SIP/2.0/UDP 1.2.3.4:5060;branch=z9hG4bK556f77e6. type Via struct { - Next *Via // pointer to next via header if any Version string // protocol version e.g. "2.0" Proto string // transport type "UDP" Host string // name or ip of egress interface Port uint16 // network port number Params Params // params like branch, received, rport, etc. + Next *Via // pointer to next via header if any } // Parses a single SIP Via header, provided the part that comes after "Via: ". @@ -98,7 +98,6 @@ func (via *Via) Copy() *Via { // Sets newly generated branch ID and returns self. func (via *Via) Branch() *Via { - via = via.Copy() via.Params["branch"] = util.GenerateBranch() return via } diff --git a/util/util.go b/util/util.go index 81bcaa2..13d4e6d 100755 --- a/util/util.go +++ b/util/util.go @@ -8,6 +8,14 @@ import ( "strings" ) +// Returns true if error is an i/o timeout. +func IsTimeout(err error) bool { + if operr, ok := err.(*net.OpError); ok { + return operr.Timeout() + } + return false +} + // Returns true if IP contains a colon. func IsIPv6(ip string) bool { n := strings.Index(ip, ":")