You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

507 lines
13 KiB

// Copyright 2020 Justine Alexandra Roberts Tunney
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// SIP Dialog Transport.
package sip
import (
"bytes"
"errors"
"flag"
"github.com/jart/gosip/rtp"
"github.com/jart/gosip/sdp"
"github.com/jart/gosip/util"
"log"
"net"
"time"
)
const (
DialogProceeding = iota
DialogRinging
DialogAnswered
DialogHangup
)
var (
looseSignalling = flag.Bool("looseSignalling", false, "Permit SIP messages from servers other than the next hop.")
resendInterval = flag.Int("resendInterval", 400, "Milliseconds between SIP resends.")
maxResends = flag.Int("maxResends", 2, "Max SIP message retransmits.")
)
// Dialog represents an outbound SIP phone call.
type Dialog struct {
OnErr <-chan error
OnState <-chan int
OnPeer <-chan *net.UDPAddr
Hangup chan<- bool
}
type dialogState struct {
sockMsgs <-chan *Msg
sockErrs <-chan error
csockMsgs <-chan *Msg
csockErrs <-chan error
errChan chan<- error
stateChan chan<- int
peerChan chan<- *net.UDPAddr
sendHangupChan <-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.
b bytes.Buffer // Outbound message buffer.
}
// NewDialog creates a phone call.
func NewDialog(invite *Msg) (dl *Dialog, err error) {
errChan := make(chan error)
stateChan := make(chan int)
peerChan := make(chan *net.UDPAddr)
sendHangupChan := make(chan bool, 4)
dls := &dialogState{
errChan: errChan,
stateChan: stateChan,
peerChan: peerChan,
sendHangupChan: sendHangupChan,
invite: invite,
}
go dls.run()
return &Dialog{
OnErr: errChan,
OnState: stateChan,
OnPeer: peerChan,
Hangup: sendHangupChan,
}, nil
}
func (dls *dialogState) run() {
defer dls.cleanup()
if !dls.sendRequest(dls.invite) {
return
}
for {
select {
case err := <-dls.sockErrs:
dls.sock.Close()
dls.sock = nil
if util.IsRefused(err) {
log.Printf("ICMP refusal: %s (%s)\r\n", dls.sock.RemoteAddr(), dls.dest)
if !dls.popRoute() {
return
}
} else {
dls.errChan <- err
return
}
case err := <-dls.csockErrs:
dls.csock.Close()
dls.csock = nil
dls.errChan <- err
return
case <-dls.requestTimer:
if !dls.resendRequest() {
return
}
case <-dls.responseTimer:
if !dls.resendResponse() {
return
}
case msg := <-dls.sockMsgs:
if !dls.handleMessage(msg) {
return
}
case msg := <-dls.csockMsgs:
if !dls.handleMessage(msg) {
return
}
case <-dls.sendHangupChan:
if !dls.sendHangup() {
return
}
}
}
}
func (dls *dialogState) sendRequest(request *Msg) bool {
host, port, err := RouteMessage(nil, nil, request)
if err != nil {
dls.errChan <- err
return false
}
wantSRV := dls.state < DialogAnswered
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()
}
dls.populate(dls.request)
if dls.state < DialogAnswered {
dls.rseq = 0
dls.remote = nil
dls.lseq = dls.request.CSeq
}
dls.requestResends = 0
dls.requestTimer = time.After(duration(resendInterval))
return dls.send(dls.request)
}
func (dls *dialogState) connect() bool {
if dls.sock == nil || dls.addr != dls.sock.RemoteAddr().String() {
// 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 the local machine.
dls.cleanupSock()
conn, err := net.Dial("udp", dls.addr)
if err != nil {
log.Printf("net.Dial(udp, %s) failed: %s\r\n", dls.addr, err)
return false
}
dls.sock = conn.(*net.UDPConn)
sockMsgs := make(chan *Msg)
sockErrs := make(chan error)
dls.sockMsgs = sockMsgs
dls.sockErrs = sockErrs
go ReceiveMessages(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 dls.csock == nil && *looseSignalling {
cconn, err := rtp.Listen("")
if err != nil {
log.Printf("Loose signalling not possible: %s\r\n", err)
return false
}
dls.csock = cconn.(*net.UDPConn)
csockMsgs := make(chan *Msg)
csockErrs := make(chan error)
dls.csockMsgs = csockMsgs
dls.csockErrs = csockErrs
go ReceiveMessages(dls.csock, csockMsgs, csockErrs)
}
}
return true
}
func (dls *dialogState) populate(msg *Msg) {
laddr := dls.sock.LocalAddr().(*net.UDPAddr)
lhost := laddr.IP.String()
lport := uint16(laddr.Port)
if msg.Via == nil {
msg.Via = &Via{Host: lhost}
}
msg.Via.Port = lport
branch := msg.Via.Param.Get("branch")
if branch != nil {
branch.Value = util.GenerateBranch()
} else {
msg.Via.Param = &Param{"branch", util.GenerateBranch(), msg.Via.Param}
}
if msg.Contact == nil {
msg.Contact = &Addr{Uri: &URI{Scheme: "sip", Host: lhost}}
}
if dls.csock != nil {
msg.Contact.Uri.Port = uint16(dls.csock.LocalAddr().(*net.UDPAddr).Port)
} else {
msg.Contact.Uri.Port = lport
}
if msg.Contact.Uri.Param.Get("transport") == nil {
msg.Contact.Uri.Param = &URIParam{"transport", "udp", msg.Contact.Uri.Param}
}
if msg.Method == MethodInvite {
if ms, ok := msg.Payload.(*sdp.SDP); ok {
if ms.Addr == "" {
ms.Addr = lhost
}
if ms.Origin.Addr == "" {
ms.Origin.Addr = lhost
}
if ms.Origin.ID == "" {
ms.Origin.ID = util.GenerateOriginID()
}
}
}
PopulateMessage(nil, nil, msg)
}
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\r\n")
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\r\n")
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)\r\n", 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)
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 ms, ok := msg.Payload.(*sdp.SDP); ok {
dls.peerChan <- &net.UDPAddr{IP: net.ParseIP(ms.Addr), Port: int(ms.Audio.Port)}
}
}
func (dls *dialogState) send(msg *Msg) bool {
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)
dls.b.Reset()
msg.Append(&dls.b)
if *tracing {
trace("send", dls.b.Bytes(), dls.sock.RemoteAddr())
}
_, err := dls.sock.Write(dls.b.Bytes())
if err != nil {
dls.errChan <- err
return false
}
return true
}
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(duration(resendInterval))
} else {
log.Printf("Timeout: %s (%s)\r\n", dls.sock.RemoteAddr(), dls.dest)
if !dls.popRoute() {
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(duration(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(duration(resendInterval))
} else {
// TODO(jart): If resending INVITE 200 OK, start sending BYE.
log.Printf("Timeout sending response: %s (%s)\r\n", dls.sock.RemoteAddr(), dls.dest)
if !dls.popRoute() {
return false
}
}
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))
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()
close(dls.errChan)
close(dls.stateChan)
close(dls.peerChan)
}
func (dls *dialogState) cleanupSock() {
if dls.sock != nil {
dls.sock.Close()
dls.sock = nil
_, _ = <-dls.sockMsgs
<-dls.sockErrs
}
}
func (dls *dialogState) cleanupCSock() {
if dls.csock != nil {
dls.csock.Close()
dls.csock = nil
_, _ = <-dls.csockMsgs
<-dls.csockErrs
}
}