Giter Club home page Giter Club logo

sipgo's Introduction

SIPGO

Go Report Card Used By Coverage License GitHub go.mod Go version

SIPGO is library for writing fast SIP services in GO language.
It comes with SIP stack (RFC 3261|RFC3581) optimized for fast parsing.

For extra functionality checkout also

Fetch lib with:

go get github.com/emiago/sipgo

NOTE: LIB MAY HAVE API CHANGES UNTIL STABLE VERSION.

*If you like/use project currently or need additional help/support checkout Support section

You can follow on X/Twitter for more updates.

More on documentation you can find on Go doc

Supported protocols

  • UDP
  • TCP
  • TLS
  • WS
  • WSS

Examples

Also thanks to pion project sharing this example of using SIPgo with webrtc:

Tools developed:

  • CLI softphone for easy testing gophone
  • Simple proxy where NAT is problem psip
  • ... your tool can be here

Performance

As example you can find example/proxysip as simple version of statefull proxy. It is used for stress testing with sipp. To find out more about performance check the latest results:
example/proxysip

Used By

babelforce

If you are using in company, your logo can be here.

Usage

Lib allows you to write easily sip servers, clients, stateful proxies, registrars or any sip routing. Writing in GO we are not limited to handle SIP requests/responses in many ways, or to integrate and scale with any external services (databases, caches...).

UAS/UAC build

Using server or client handle for UA you can build incoming or outgoing requests.

ua, _ := sipgo.NewUA() // Build user agent
srv, _ := sipgo.NewServer(ua) // Creating server handle for ua
client, _ := sipgo.NewClient(ua) // Creating client handle for ua
srv.OnInvite(inviteHandler)
srv.OnAck(ackHandler)
srv.OnCancel(cancelHandler)
srv.OnBye(byeHandler)

// For registrars
// srv.OnRegister(registerHandler)
ctx, _ := signal.NotifyContext(ctx, os.Interrupt)
go srv.ListenAndServe(ctx, "udp", "127.0.0.1:5060")
go srv.ListenAndServe(ctx, "tcp", "127.0.0.1:5061")
go srv.ListenAndServe(ctx, "ws", "127.0.0.1:5080")
<-ctx.Done()

TLS transports

// TLS
conf :=  sipgo.GenerateTLSConfig(certFile, keyFile, rootPems)
srv.ListenAndServeTLS(ctx, "tcp", "127.0.0.1:5061", conf)
srv.ListenAndServeTLS(ctx, "ws", "127.0.0.1:5081", conf)

UAC first

If you are acting as client first, you can say to client which host:port to use, and this connection will be reused until closing UA. Any request received can be still processed with server handle.

ua, _ := sipgo.NewUA() // Build user agent
defer ua.Close()

client, _ := sipgo.NewClient(ua, sipgo.WithClientHostname("127.0.0.1"), sipgo.WithClientPort(5060))
server, _ := sipgo.NewServer(ua) 
srv.OnBye(func(req *sip.Request, tx sip.ServerTransaction)) {
    // This will be received on 127.0.0.1:5060
}

tx, err := client.TransactionRequest(ctx, sip.NewRequest(sip.INVITE, recipient)) 

Server Transaction

Server transaction is passed on handler

// Incoming request
srv.OnInvite(func(req *sip.Request, tx sip.ServerTransaction) {
    res := sip.NewResponseFromRequest(req, code, reason, body)
    // Send response
    tx.Respond(res)

    select {
        case m := <-tx.Acks(): // Handle ACK . ACKs on 2xx are send as different request
        case m := <-tx.Cancels(): // Handle Cancel 
        case <-tx.Done():
            // Signal transaction is done. 
            // Check any errors with tx.Err() to have more info why terminated
            return
    }

    // terminating handler terminates Server transaction automaticaly
})

Server stateless response

srv := sipgo.NewServer()
...
func ackHandler(req *sip.Request, tx sip.ServerTransaction) {
    res := sip.NewResponseFromRequest(req, code, reason, body)
    srv.WriteResponse(res)
}
srv.OnACK(ackHandler)

Client Transaction

Using client handle allows easy creating and sending request. Unless you customize transaction request with opts by default client.TransactionRequest will build all other headers needed to pass correct sip request.

Here is full example:

ctx := context.Background()
client, _ := sipgo.NewClient(ua) // Creating client handle

// Request is either from server request handler or created
req.SetDestination("10.1.2.3") // Change sip.Request destination
tx, err := client.TransactionRequest(ctx, req) // Send request and get client transaction handle

defer tx.Terminate() // Client Transaction must be terminated for cleanup
...

select {
    case res := <-tx.Responses():
    // Handle responses
    case <-tx.Done():
    // Wait for termination
    return
}

Client Do request

Unless you need more control over Client Transaction you can simply go with client Do request and wait final response.

req := sip.NewRequest(sip.INVITE, sip.Uri{User:"bob", Host: "example.com"})
res, err := client.Do(req)

Client stateless request

client, _ := sipgo.NewClient(ua) // Creating client handle
req := sip.NewRequest(method, recipient)
// Send request and forget
client.WriteRequest(req)

Dialog handling

DialogClient and DialogServer allow easier managing multiple dialog (Calls) sessions. They are seperated based on your request context, but they act more like peer. They both need client handle to be able send request and server handle to accept request.

UAC:

ua, _ := sipgo.NewUA() // Build user agent
srv, _ := sipgo.NewServer(ua) // Creating server handle
client, _ := sipgo.NewClient(ua) // Creating client handle

contactHDR := sip.ContactHeader{
    Address: sip.Uri{User: "test", Host: "127.0.0.200", Port: 5088},
}
dialogCli := sipgo.NewDialogClient(client, contactHDR)

// Attach Bye handling for dialog
srv.OnBye(func(req *sip.Request, tx sip.ServerTransaction) {
    err := dialogCli.ReadBye(req, tx)
    //handle error
})

// Create dialog session
dialog, err := dialogCli.Invite(ctx, recipientURI, nil)
defer dialog.Close() // Cleans up from dialog pool
// Wait for answer
err = dialog.WaitAnswer(ctx, AnswerOptions{})
// Check dialog response dialog.InviteResponse (SDP) and return ACK
err = dialog.Ack(ctx)
// Send BYE to terminate call
err = dialog.Bye(ctx)

UAS:

ua, _ := sipgo.NewUA() // Build user agent
srv, _ := sipgo.NewServer(ua) // Creating server handle
client, _ := sipgo.NewClient(ua) // Creating client handle

uasContact := sip.ContactHeader{
    Address: sip.Uri{User: "test", Host: "127.0.0.200", Port: 5099},
}
dialogSrv := sipgo.NewDialogServer(client, uasContact)

srv.OnInvite(func(req *sip.Request, tx sip.ServerTransaction) {
    dlg, err := dialogSrv.ReadInvite(req, tx)
    // handle error
    dlg.Respond(sip.StatusTrying, "Trying", nil)
    dlg.Respond(sip.StatusOK, "OK", nil)
    
    // Instead Done also dlg.State() can be used for granular state checking
    <-dlg.Context().Done()
})

srv.OnAck(func(req *sip.Request, tx sip.ServerTransaction) {
    dialogSrv.ReadAck(req, tx)
})

srv.OnBye(func(req *sip.Request, tx sip.ServerTransaction) {
    dialogSrv.ReadBye(req, tx)
})

Stateful Proxy build

Proxy is combination client and server handle that creates server/client transaction. They need to share same ua same like uac/uas build.

Forwarding request is done via client handle:

ua, _ := sipgo.NewUA() // Build user agent
srv, _ := sipgo.NewServer(ua) // Creating server handle
client, _ := sipgo.NewClient(ua) // Creating client handle

srv.OnInvite(func(req *sip.Request, tx sip.ServerTransaction) {
    ctx := context.Background()
    req.SetDestination("10.1.2.3") // Change sip.Request destination
    // Start client transaction and relay our request. Add Via and Record-Route header
    clTx, err := client.TransactionRequest(ctx, req, sipgo.ClientRequestAddVia, sipgo.ClientRequestAddRecordRoute)
    // Send back response
    res := <-cltx.Responses()
    tx.Respond(res)
})

SIP Debug

You can have full SIP messages dumped from transport into Debug level message.

Example:

sip.SIPDebug = true
Feb 24 23:32:26.493191 DBG UDP read 10.10.0.10:5060 <- 10.10.0.100:5060:
SIP/2.0 100 Trying
Via: SIP/2.0/UDP 10.10.0.10:5060;rport=5060;received=10.10.0.10;branch=z9hG4bK.G3nCwpXAKJQ0T2oZUII70wuQx9NeXc61;alias
Via: SIP/2.0/UDP 10.10.1.1:5060;branch=z9hG4bK-1-1-0
Record-Route: <sip:10.10.0.10;transport=udp;lr>
Call-ID: [email protected]
From: "sipp" <sip:[email protected]>;tag=1SIPpTag001
To: "uac" <sip:[email protected]>
CSeq: 1 INVITE
Server: Asterisk PBX 18.16.0
Content-Length:  0

Support

If you find this project interesting for bigger support or consulting, you can contact me on mail

For bugs features pls create issue.

Extra

E2E/integration testing

If you are interested using lib for your testing services then checkout article on how easy you can make calls and other

Tests

Library will be covered with more tests. Focus is more on benchmarking currently.

go test ./...  

Credits

This project was influenced by gosip, project by @ghetovoice, but started as new project to achieve best/better performance and to improve API. This unfortunately required many design changes, therefore this libraries are not compatible.

sipgo's People

Contributors

a12n avatar anshulmalik avatar dennwc avatar emiago avatar hateeyan avatar innovativeus avatar kingdanx avatar maratk1n avatar oystedal avatar sms10086 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sipgo's Issues

BYE doesn't terminate transaction

Here is an excerpt from my app using this library. I have to register the srv.OnAck and srv.OnBye otherwise they will be rejected. My issue is that after my successful sip call when the BYE message arrives, the tx.Done() channel will not fire. I've also noticed that after a timeout, regardless if I'm in the middle of a call or not, the transaction gets terminated and then the channel fires and the code runs. How can I control this behaviour? I tried doing a tx.Terminate() in the BYE handler, but even that did not work.

func onBye(req *sip.Request, tx sip.ServerTransaction) {
  _ = tx.Respond(sip.NewResponseFromRequest(req, sip.StatusOK, "OK", nil))
}

func onACK(req *sip.Request, tx sip.ServerTransaction) {
  return
}

func onInvite(req *sip.Request, tx sip.ServerTransaction) {
  // ---- sdp offer & response ---
  sdpOffer, _ := sdp.Parse(req.Body())
  session, _ := engine.NewSession(*sdpOffer)
  defer engine.CloseSession(&session)
  res := sip.NewSDPResponseFromRequest(req, session.Response.Bytes())
  _ = tx.Respond(res)
  // -----------------------------

for {
  select {
  case m := <-tx.Cancels():
    // this never gets called
    _ = tx.Respond(sip.NewResponseFromRequest(m, 200, "OK", nil))
  case <-tx.Done():
    // this doesn't get called on BYE
    return
  case <-tx.Acks():
    // I've also never seen this called
    return
  }
}

func main() {
  ua, _ := sipgo.NewUA()
  srv, _ := sipgo.NewServerDialog(ua)

  srv.OnInvite(onInvite)
  srv.OnAck(onACK)
  srv.OnBye(onBye)
  if err := srv.ListenAndServe(context.TODO(), "udp", *extIP); err != nil {
    panic(err.Error())
  }
}

Sip Dialog terminating mid call

I have had success using the new dialogue server. I used the examples and added my own logic to properly respond to the SDP offer sent in the INVITE. The parts that are working are the dialogue starting when the call starts after the INVITE, and the dialogue properly stops when receiving BYE. My issue is if the call is longer than 30sec, the dialogue also just stops and kills the call. I have a hunch that this is probably due to a timeout somewhere, but I'm not sure what kind of keepalives are needed other than the code here. I've tried this with multiple sip phones and all have the same behaviour. I'm using UDP. Am I doing something wrong?

Here is an excerpt of the code I'm using:

func main() {
	ua, _ := sipgo.NewUA(sipgo.WithUserAgent("foobar"))
	uasContact := sip.ContactHeader{
		Address: sip.Uri{User: "foobar", Host: "192.168.22.122", Port: 5060},
	}
	cli, _ := sipgo.NewClient(ua)
	srv, _ := sipgo.NewServer(ua)
	dialogSrv := sipgo.NewDialogServer(cli, uasContact)

	srv.OnRegister(func(req *sip.Request, tx sip.ServerTransaction) {
		_ = tx.Respond(sip.NewResponseFromRequest(req, sip.StatusOK, "OK", nil))
	})

	srv.OnInvite(func(req *sip.Request, tx sip.ServerTransaction) {
		fmt.Printf("DIALOG STARTED!!!\n")
		dlg, err := dialogSrv.ReadInvite(req, tx)
		if err != nil {
			panic(err.Error())
		}
		sdpResp := generateSdpResponse(req.Body())
		_ = dlg.Respond(sip.StatusOK, "OK", sdpResp, sip.NewHeader("Content-Type", "application/sdp"))
		<-tx.Done()
		fmt.Printf("DIALOG DONE!!!\n")
	})

	srv.OnAck(func(req *sip.Request, tx sip.ServerTransaction) {
		_ = dialogSrv.ReadAck(req, tx)
	})

	srv.OnBye(func(req *sip.Request, tx sip.ServerTransaction) {
		_ = dialogSrv.ReadBye(req, tx)
	})

	ctx, _ := signal.NotifyContext(context.TODO(), os.Interrupt)
	go srv.ListenAndServe(ctx, "udp", "192.168.22.122:5060")
	<-ctx.Done()
}

Dialog server example for echo rtp packet

Hi,
Appreciate u for all of your contributions and helps.

Could you please guide how to use dialog server for recording and playback the arrive rtp packet payload. say rtp payload echo example.

I would like to streaming out/in the payload to/from an external service.

Thanks in advance.

Generate branch optimization

Performance dropped from v0.6.0 due to forcing on generate new via branch.
sip.GenerateBranch function needs to be fixed and optimized to remove allocs

Code restructure

sip.NewServer instead sipgo.NewServer for better ux. (same as std http?)

problems:
sip,transport,transaction need to be merged to avoid import cycle

Is it worth it? Is it better for maintance?

Adjustable T1, T2 and T4 timeouts

These are constants now and can't be changed.

Probably, the values should be per client/server or per transaction. In the simplest case, they just can be made var. This would allow to change the values at startup. Something like this:

diff --git a/transaction/transaction.go b/transaction/transaction.go
index 3f446fb..cef8198 100644
--- a/transaction/transaction.go
+++ b/transaction/transaction.go
@@ -12,23 +12,27 @@ import (
 	"github.com/emiago/sipgo/sip"
 )
 
+var (
+	T1 = 500 * time.Millisecond
+	T2 = 4 * time.Second
+	T4 = 5 * time.Second
+)
+
+func Timer_A() time.Duration { return T1 }
+func Timer_B() time.Duration { return 64 * T1 }
+func Timer_E() time.Duration { return T1 }
+func Timer_F() time.Duration { return 64 * T1 }
+func Timer_G() time.Duration { return T1 }
+func Timer_H() time.Duration { return 64 * T1 }
+func Timer_I() time.Duration { return T4 }
+func Timer_J() time.Duration { return 64 * T1 }
+func Timer_K() time.Duration { return T4 }
+func Timer_L() time.Duration { return 64 * T1 }
+func Timer_M() time.Duration { return 64 * T1 }
+
 const (
-	T1        = 500 * time.Millisecond
-	T2        = 4 * time.Second
-	T4        = 5 * time.Second
-	Timer_A   = T1
-	Timer_B   = 64 * T1
 	Timer_D   = 32 * time.Second
-	Timer_E   = T1
-	Timer_F   = 64 * T1
-	Timer_G   = T1
-	Timer_H   = 64 * T1
-	Timer_I   = T4
-	Timer_J   = 64 * T1
-	Timer_K   = T4
 	Timer_1xx = 200 * time.Millisecond
-	Timer_L   = 64 * T1
-	Timer_M   = 64 * T1
 
 	TxSeperator = "__"
 )
diff --git a/transaction/client_tx.go b/transaction/client_tx.go
index 110b83b..37d428c 100644
--- a/transaction/client_tx.go
+++ b/transaction/client_tx.go
@@ -58,10 +58,10 @@ func (tx *ClientTx) Init() error {
 		// If a reliable transport is being used, the client transaction SHOULD NOT
 		// start timer A (Timer A controls request retransmissions).
 		// Timer A - retransmission
-		// tx.log.Tracef("timer_a set to %v", Timer_A)
+		// tx.log.Tracef("timer_a set to %v", Timer_A())
 
 		tx.mu.Lock()
-		tx.timer_a_time = Timer_A
+		tx.timer_a_time = Timer_A()
 
 		tx.timer_a = time.AfterFunc(tx.timer_a_time, func() {
 			tx.spinFsm(client_input_timer_a)
@@ -73,7 +73,7 @@ func (tx *ClientTx) Init() error {
 
 	// Timer B - timeout
 	tx.mu.Lock()
-	tx.timer_b = time.AfterFunc(Timer_B, func() {
+	tx.timer_b = time.AfterFunc(Timer_B(), func() {
 		tx.mu.Lock()
 		tx.lastErr = fmt.Errorf("Timer_B timed out. %w", ErrTimeout)
 		tx.mu.Unlock()
diff --git a/transaction/client_tx_fsm.go b/transaction/client_tx_fsm.go
index ac3d318..5d1b7be 100644
--- a/transaction/client_tx_fsm.go
+++ b/transaction/client_tx_fsm.go
@@ -320,13 +320,13 @@ func (tx *ClientTx) actCancelTimeout() FsmInput {
 
 	tx.cancel()
 
-	// tx.Log().Tracef("timer_b set to %v", Timer_B)
+	// tx.Log().Tracef("timer_b set to %v", Timer_B())
 
 	tx.mu.Lock()
 	if tx.timer_b != nil {
 		tx.timer_b.Stop()
 	}
-	tx.timer_b = time.AfterFunc(Timer_B, func() {
+	tx.timer_b = time.AfterFunc(Timer_B(), func() {
 		tx.spinFsm(client_input_timer_b)
 	})
 	tx.mu.Unlock()
@@ -401,9 +401,9 @@ func (tx *ClientTx) actPassupAccept() FsmInput {
 		tx.timer_b = nil
 	}
 
-	// tx.Log().Tracef("timer_m set to %v", Timer_M)
+	// tx.Log().Tracef("timer_m set to %v", Timer_M())
 
-	tx.timer_m = time.AfterFunc(Timer_M, func() {
+	tx.timer_m = time.AfterFunc(Timer_M(), func() {
 		select {
 		case <-tx.done:
 			return

The drawback here is the changed type of Timer_*, the values have to be functions on T1 and T4. Although the values are used only inside transaction package in sipgo they're still exported and available for external code.

Maybe, you have a better idea on making the timers configurable?

Example, register/client SIGSEGV case when Algorithm in lowercase.

Hi,
I would like to report an issue when register to an Asterisk with the example/register/client/main.go

$  ./client -ip 192.168.219.101:15060 -srv 192.168.219.101:5060 -u 100 -p 100
...
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x648827]

goroutine 1 [running]:
github.com/icholy/digest.(*Credentials).String(0x0)
        /home/vagrant/go/pkg/mod/github.com/icholy/[email protected]/credentials.go:76 +0x67
main.main()
        /home/vagrant/sipgo/example/register/client/main.go:101 +0xe05

Could be fixed by changing Algorithm to uppercase as it was expected by digest.Digest method.

...
		wwwAuth := res.GetHeader("WWW-Authenticate")
		chal, err := digest.ParseChallenge(wwwAuth.Value())
		if err != nil {
			log.Fatal().Str("wwwauth", wwwAuth.Value()).Err(err).Msg("Fail to parse challenge")
		}
		chal.Algorithm = strings.ToUpper(chal.Algorithm)   //##### fixing
...

Asterisk's WWW-Authenticate parsed challenge:

github.com/icholy/digest.Challenge {Realm: "asterisk", Domain: []string len: 0, cap: 0, nil, Nonce: "1685669327/dfd8ceb694344a6f663b95f91eb48096", Opaque: "2ac5572d63798b56", Stale: false, Algorithm: "md5", QOP: []string len: 1, cap: 1, ["auth"], Charset: "", Userhash: false}

Thanks.

Random free local port in `Client`

Hi Emir,

Looks like zero port value in sipgo.Client means sip.Default{Tcp,Udp}Port1 and not "any random available port from the OS". It's a bit surprising: usually in client contexts zero/unspecified port is a way to say "I don't care about the port, choose any". E.g., standard net.Dial{TCP,UDP} functions work that way, SIPp also chooses local port randomly by default (excerpt from sipp usage):

   -p               : Set the local port number.  Default is a random free port chosen by the
                      system.

What I would expect:

  • sipgo.NewClient(ua) or sipgo.NewClient(ua, sipgo.WithClientPort(0)) will use any available client port.
  • sipgo.NewClient(ua, sipgo.WithClientPort(sip.DefaultPort)) will use the registered SIP port 5060.

I'm not sure whether it's too late to make such breaking change (different meaning for zero port value). If it is, maybe add some special constant (const AnyPort = -1?) for sipgo.WithClientPort? Something like this:

diff --git a/client.go b/client.go
index 1a0a215..0792256 100644
--- a/client.go
+++ b/client.go
@@ -41,7 +41,11 @@ func WithClientHostname(hostname string) ClientOption {
 	}
 }
 
-// WithClientPort allows setting default route port
+// WithClientPort allows setting default route port.
+//
+// If zero, [sip.DefaultUdpPort] or [sip.DefaultTcpPort] will be
+// used. If -1, the client port will be chosen automatically by the
+// OS.
 func WithClientPort(port int) ClientOption {
 	return func(s *Client) error {
 		s.port = port
diff --git a/transport/tcp.go b/transport/tcp.go
index 22d4bec..1dc63b5 100644
--- a/transport/tcp.go
+++ b/transport/tcp.go
@@ -94,7 +94,10 @@ func (t *TCPTransport) CreateConnection(laddr Addr, raddr Addr, handler sip.Mess
 			Port: laddr.Port,
 		}
 
-		if tladdr.Port == 0 {
+		switch tladdr.Port {
+		case -1: // Use any available port.
+			tladdr.Port = 0
+		case 0: // Use default port.
 			tladdr.Port = sip.DefaultTcpPort
 		}
 	}
diff --git a/transport/udp.go b/transport/udp.go
index 8da541c..d112453 100644
--- a/transport/udp.go
+++ b/transport/udp.go
@@ -130,7 +130,10 @@ func (t *UDPTransport) CreateConnection(laddr Addr, raddr Addr, handler sip.Mess
 			Port: laddr.Port,
 		}
 
-		if uladdr.Port == 0 {
+		switch uladdr.Port {
+		case -1: // Use any available port.
+			uladdr.Port = 0
+		case 0: // Use default port.
 			uladdr.Port = sip.DefaultUdpPort
 		}
 	}
diff --git a/transport/ws.go b/transport/ws.go
index 168f859..6fc9177 100644
--- a/transport/ws.go
+++ b/transport/ws.go
@@ -224,7 +224,10 @@ func (t *WSTransport) CreateConnection(laddr Addr, raddr Addr, handler sip.Messa
 			Port: laddr.Port,
 		}
 
-		if tladdr.Port == 0 {
+		switch tladdr.Port {
+		case -1: // Use any available port.
+			tladdr.Port = 0
+		case 0: // Use default port.
 			tladdr.Port = sip.DefaultTcpPort
 		}
 	}

Footnotes

  1. It's not obvious why two distinct port constants: the port number is the same for UDP, TCP and SCTP transports.

Contact header parsing problem when using wss(jssip.js)

When processing SIP with jssip.js, there is a problem in which parsing does not work properly if the param information in the header contains a value wrapped in quoted string.

The problem is that quoted-string among the values of generic-param cannot be processed in the BNF related to the RFC2833 Contact header below.

Contact: <sip:[email protected]>;+sip.ice;reg-id=1;+sip.instance="<urn:uuid:a369bd8d-f310-4a95-8328-98c7ed3d5439>";expires=300
contact-param  =  (name-addr / addr-spec) *(SEMI contact-params)
contact-params     =  c-p-q / c-p-expires
                      / contact-extension
contact-extension  =  generic-param
generic-param  =  token [ EQUAL gen-value ]
gen-value      =  token / host / quoted-string

It can be improved with the following modifications.

diff --git a/sip/parse_address.go b/sip/parse_address.go
index 826350d..4ff5847 100644
--- a/sip/parse_address.go
+++ b/sip/parse_address.go
@@ -15,10 +15,17 @@ func ParseAddressValue(addressText string, uri *Uri, headerParams HeaderParams)
        var semicolon, equal, startQuote, endQuote int = -1, -1, -1, -1
        var name string
        var uriStart, uriEnd int = 0, -1
-       var inBrackets bool
+       var inBrackets, inQuotes bool
        for i, c := range addressText {
+               if inQuotes {
+                       continue
+               }
                switch c {
                case '"':
+                       if equal > 0 {
+                               inQuotes = !inQuotes
+                               continue
+                       }
                        if startQuote < 0 {
                                startQuote = i
                        } else {

some trouble on comment

I have some trouble to understand this comment:
“Multiple readers makes problem, which can delay writing response”
Why multi UDPReadWorkers Goroutine can delay writing response?

func cloneRequest(req *Request) *Request { not generate new branch ID

req.Clone() used in

newReq := req.Clone()
does not generate new branch ID on Authorization request. Therefore the request will end up in errors like this:

Dec 30 19:20:35.279400 INF Received status status=401
Dec 30 19:20:35.279494 FTL Fail to create transaction error="transaction \"z9hG4bK.Bw6JZkBt28fHVwSe__REGISTER\" already exists"

By RFC3261, the branch ID must be unique for each new transaction.

server recv a unrecgnized method may blocked all incoming request.

If recv a register request, but server did not found handler by method, this tx will be blocked, and server routinue blocked.

The next response will not be reponsed.

`func (srv *Server) handleRequest(req *sip.Request, tx sip.ServerTransaction) {
for _, mid := range srv.requestMiddlewares {
mid(req)
}

handler := srv.getHandler(req.Method)

if handler == nil {
	srv.log.Warn().Msg("SIP request handler not found")
	res := sip.NewResponseFromRequest(req, 405, "Method Not Allowed", nil)
	if err := srv.WriteResponse(res); err != nil {
		srv.log.Error().Msgf("respond '405 Method Not Allowed' failed: %s", err)
	}
            //. no timer trigger. or we shoud destroy this tx.
	for {
		select {
		case <-tx.Done():
			srv.log.Warn().Msgf("recv tx.Done() tx:%s", tx)
			return
		case err, ok := <-tx.Errors():
			srv.log.Warn().Msgf("recv tx.Errors() tx:%s, err:%s", tx, err)
			if !ok {
				return
			}
			srv.log.Warn().Msgf("error from SIP server transaction %s: %s", tx, err)
		}
	}
}

handler(req, tx)
if tx != nil {
	// Must be called to prevent any transaction leaks
	tx.Terminate()
}

}`

register response start line may split by LF

Server may get sipVersion ends with LF if request start line ends with LF ranther than CRLF.
Although it's documented here. https://www.rfc-editor.org/rfc/rfc3261.html#section-7
If consider return err while startline ends with LF or CR ?
SIP/2.0 200 OK Via: SIP/2.0/UDP 192.168.10.8:60719;rport=60719;branch=z9hG4bK1377739937 From: <sip:34020000002000000719@3402000000>;tag=1534571172 To: <sip:34020000002000000719@3402000000>;tag=b70dda5e-f487-4b46-8e21-42b7c5dfcfa0 Call-ID: 1566767517 CSeq: 1 REGISTER Content-Length: 0

Example for TCP sip client

Hey Emiago, do you have an example for an TCP SIP client for connecting to tcp sip server ?

I'm looking to make a small private utils to test if my home TCP sip server are down or not. Can you provide a TCP client example ?

Thanks -Kalvelign

there is some suggestion

maybe we need function like this
1689263889939
adapt some situation ext addr is not 5060
image
so we can prevent the influence of record route.

rfc4475 test

SIP parser torture tests to be added.

More we add the better.

Lazy header parsing

To gain more performance we could implement lazy parsing within sip headers.
Currently we have some default parsing which could be reduced
https://github.com/emiago/sipgo/blob/main/parser/parse_header.go#L18

Currently lazy parsing can only be done for rest of headers as they are only stored as key:value
Some of parser optimized functions are exposed like
ParseUri ParseAddressValue
but we could expose more?
So in case where header is not preparsed like Via, To, From .. caller must do this manually
`
hdr := msg.GetHeader("XXX")
uri, _ := parser.ParseUri(hdr.Value())

Now parsing can be also moved to fetching header, where caching is done on demand. This could
increase performance, specially in case where messages get discarded.

client mode not reusing connection, reference drop

image

14:02:12.449963 INF Got 1xx code code=100
14:02:12.464058 INF Got 1xx code code=180
14:02:12.467912 INF Received 200
14:02:12.468111 WRN UDP ref went negative dst=172.18.0.3:5060 ref=-1 src=172.18.0.1:48145
14:02:12.468178 INF Pls cancel for hangup

Examples of basic server/client

Hello,
Are there any examples available of basic server with authentication or a client ?
Besides the 2 examples that are there.

Thanks!

Headers comma seperated values without linking

Parsing with linking list was faster way todo but we could avoid this by default.
AppendHeader should keep only top first reference

Does SIP has any case where it handles comma separated values differently?

Does the 4** status code response also need to add a tag to the TO header?

Does the 4** status code response also need to add a tag to the header?

// RFC 3261 - 8.2.6
func NewResponseFromRequest(

   ........

if statusCode == 100 {
	CopyHeaders("Timestamp", req, res)
}

if statusCode == 200 {
	if _, ok := res.to.Params["tag"]; !ok {
		uuid, _ := uuid.NewV4()
		res.to.Params["tag"] = uuid.String()
	}
}
    
    ........

    return res

}

Client handle improvements and UDP NAT

Building client needs more features.

  • building request headers on request send (done, needs docs)
  • UDP needs to handle creating connection
  • Decouple from server handle (Default transports problem). No need to create listener.

Need some advice

I am planning to write a SIP REC based recorder to work with Cisco SIP telephony. Can someone mention the steps that will be involved. I believe the SIP server would be sending me all the call session details. I need to do some sort of arbitration to selectively record only desired calls and discard the remaining not required ones. Post that media starts streaming (not sure what protocol it is but I believe it will be some sort of UDP). Is my understanding correct or am I missing something?

Now can some one confirm whether this project can help me with this? And how can I get the media data stream, so I can create a store the audio file in disk, once the call is completed.

If there is some sort of example on similar lines. Please send me the link.

Maybe I can use this project instead of ops

I took a look at the code and felt inspired by ghettovoice, I used ghettovoice to develop webphone, it was a wonderful process, maybe in some small scenarios, I could use this project to replace kamailio and just use freeswitch as a media server

Create new request from incoming request

Is it possible to generate a new SIP request based on an existing one, preserving attributes such as Call ID and body, while only modifying the 'To', 'From' and 'Contact' fields to align with the proxy's requirement for forwarding to the client?

example/proxysip - rtpproxy

Hi,

This is really a nice project and good to see how powerfull go is for voip solutions as well ;-)
One question remains however on the proxy part, the primary focus is now on the SIP handling, what would be needed to intregrate and RTP proxy as well?

Client register example - parser package not found

When trying to run register client example, get the following error

main.go:14:2: no required module provides package github.com/emiago/sipgo/parser; to add it:
	go get github.com/emiago/sipgo/parser

recipient fields can be assigned individually I guess.

Running go mod tidy return following

github.com/emiago/sipgo/parser: module github.com/emiago/sipgo@latest found (v0.15.2), but does not contain package github.com/emiago/sipgo/parser

About using SIPGO as a full SIP server like Asterisk

I'm interested in utilizing SIPGO as a comprehensive SIP server similar to Asterisk for my project. I have reviewed the README and examples provided in the repository, and I'm impressed with the performance and flexibility offered by SIPGO. However, I would like to understand the extent to which SIPGO can serve as a replacement for Asterisk in terms of its capabilities and features.

Specifically, I'm looking to achieve the following functionalities with SIPGO:

Handling and routing of SIP calls: I want to be able to receive incoming SIP calls, process them based on certain rules (such as authentication, call routing, etc.), and forward them to the appropriate destination.
Support for SIP extensions and features: It would be beneficial if SIPGO supports common SIP extensions and features like call transfers, call forwarding, conferencing, voicemail, IVR (Interactive Voice Response), and presence management.
Integration with external services: I would like to integrate SIPGO with external databases, caches, and other services to enhance its functionality. This would enable me to store and retrieve SIP-related information, perform authentication and authorization checks, and incorporate additional business logic.
Scalability and performance: As my application may experience high call volumes, it is crucial to have a SIP server that can handle a large number of concurrent connections and deliver reliable performance under heavy load.
I understand that SIPGO is still in development, and I appreciate any insights, recommendations, or limitations that you can provide regarding its suitability for building a production-ready SIP server.

Thank you!

p.s maybe in the future to create a similar to asterisk+freepbx

How to use SIP Stateless Proxy build, Forwarding request

Stateless Proxy Server

A stateless proxy server simply forwards the message it receives. This kind of server does not store any information of the call or transaction.

Stateless proxies forget about the SIP request once it has been forwarded.
Transaction will be fast via stateless proxie

Using example

Hey, im playing around with the proxy, im trying to put in in between an asterisk box and a sip client, however none of the messages make it through

proxysip % go run . -ip=127.0.0.1:55600 -dst 111.111.11.111:55600

Nov 10 15:35:56.865082 INF Runtime cpus=8
Nov 10 15:35:56.865839 INF Server routes setuped
Nov 10 15:35:56.866605 INF Http server started address=:8080
Nov 10 15:36:00.598282 WRN Server > SIP request handler not found
Nov 10 15:36:00.598282 WRN Server > SIP request handler not found
Nov 10 15:36:03.683762 ERR RequestWithContext failed error="udp conn 127.0.0.1:55600 err. write udp 127.0.0.1:55600->111.111.11.111:55600: sendto: can't assign requested address. transaction transport error"
Nov 10 15:36:04.183834 ERR RequestWithContext failed error="udp conn 127.0.0.1:55600 err. write udp 127.0.0.1:55600->111.111.11.111:55600: sendto: can't assign requested address. transaction transport error"
Nov 10 15:36:05.185031 ERR RequestWithContext failed error="udp conn 127.0.0.1:55600 err. write udp 127.0.0.1:55600->111.111.11.111:55600: sendto: can't assign requested address. transaction transport error"
Nov 10 15:36:07.188550 ERR RequestWithContext failed error="udp conn 127.0.0.1:55600 err. write udp 127.0.0.1:55600->111.111.11.111:55600: sendto: can't assign requested address. transaction transport error"
Nov 10 15:36:10.769874 WRN Server > SIP request handler not found
Nov 10 15:36:11.193927 ERR RequestWithContext failed error="udp conn 127.0.0.1:55600 err. write udp 127.0.0.1:55600->111.111.11.111:55600: sendto: can't assign requested address. transaction transport error"

Client transaction on :5060 via UDP

Due to connected UDP connections client transaction will fail to get responses
if they sent request on unspecified IP like 0.0.0.0:5060.

It works if is send with 0.0.0.0 but not in all other cases.
This is due to Go UDP connected connection handling.

Headers parsing and doc

Support more headers for parsing if needed
https://www.rfc-editor.org/rfc/rfc3261#section-20
Currently GenericHeader needs to be used for all unsupported headers. Can this be wrapped with key value function?

Let caller todo unmarshaling and avoid parsing non used headers
Add missing functions and docs.

This are required to be parsed: Via, Cseq, CallID, From, To
Currently this are also exported via fast reference.

Parser needs more docs!

PSIP extension for SIPGO for easy creating proxies

Here sharing IDEA for extra library for building state full proxy with focus on routing like kamailio,opensips but with better control. As it is in GO lot of functions is already there by many libs out there.

There is lot of complexity that needs to be done, so it may be worth to wrap it but still keeping lot of control over request handling.

If there will be enough interest in this project I would share my current POC lib.

More info you can find on

https://github.com/emiago/sipgox/tree/main/psip

Related to questions
#31 by @leeyongda
#40 by @dekke046

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.