diff --git a/README.md b/README.md index f5fb7a7..0737938 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,15 @@ To find out more about performance check the latest results: (Contributions are welcome, I would place your results here) ## Usage -Lib allows you to write easily stateful proxies, registrar or any sip routing. +Lib allows you to write easily client or server or to build up stateful proxies, registrar 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 build + ```go -srv := sipgo.NewServer() +ua, _ := sipgo.NewUA() // Build user agent +srv, _ := sipgo.NewServer(ua) // Creating server handle srv.OnRegister(registerHandler) srv.OnInvite(inviteHandler) srv.OnAck(ackHandler) @@ -31,6 +35,7 @@ srv.Listen("tcp", "127.0.0.1:5061") // Start serving srv.Serve() ``` + ### Server Transaction @@ -56,13 +61,32 @@ srv.OnInvite(func(req *sip.Request, tx sip.ServerTransaction) { ``` +### Stateless response + +```go +srv := sipgo.NewServer() +... +func ackHandler(req *sip.Request, tx sip.ServerTransaction) { + res := sip.NewResponseFromRequest(req, code, reason, body) + srv.WriteResponse(res) +} +srv.OnACK(ackHandler) +``` + + +### UAC build +```go +ua, _ := sipgo.NewUA() // Build user agent +client, _ := sipgo.NewClient(ua) // Creating client handle +``` + ### Client Transaction ```go // Request is either from server request handler or created req.SetDestination("10.1.2.3") // Change sip.Request destination -tx, err := srv.TransactionRequest(req) // Send request and get client transaction handle +tx, err := client.TransactionRequest(req) // Send request and get client transaction handle defer tx.Terminate() // Client Transaction must be terminated for cleanup ... @@ -77,25 +101,18 @@ select { ``` -### Stateless response +## Proxy build -```go -srv := sipgo.NewServer() -... -func ackHandler(req *sip.Request, tx sip.ServerTransaction) { - res := sip.NewResponseFromRequest(req, code, reason, body) - srv.WriteResponse(res) -} -srv.OnACK(ackHandler) -``` +Proxy is combination client and server handle. +Checkout `/example` directory for examples how to build simple stateful proxy. -### Dialogs +### Dialogs (experiment) -ServerDialog is extended type of Server with Dialog support. -More features may come. +`ServerDialog` is extended type of Server with Dialog support. +For now this is in experiment. ```go -srv, err := sipgo.NewServerDialog() +srv, err := sipgo.NewServerDialog(ua) ... srv.OnDialog(func(d sip.Dialog) { switch d.State { @@ -110,6 +127,8 @@ srv.OnDialog(func(d sip.Dialog) { ``` +`ClientDialog` TODO... + ## Documentation More on documentation you can find on [Go doc](https://pkg.go.dev/github.com/emiraganov/sipgo) diff --git a/client.go b/client.go index d3533d3..de8996f 100644 --- a/client.go +++ b/client.go @@ -12,9 +12,6 @@ import ( type Client struct { *UserAgent log zerolog.Logger - - AddViaHeader bool - AddRecordRoute bool } type ClientOption func(c *Client) error @@ -36,47 +33,70 @@ func NewClient(ua *UserAgent, options ...ClientOption) (*Client, error) { } // TransactionRequest uses transaction layer to send request -func (c *Client) TransactionRequest(req *sip.Request) (sip.ClientTransaction, error) { - c.updateRequest(req) +func (c *Client) TransactionRequest(req *sip.Request, options ...ClientRequestOption) (sip.ClientTransaction, error) { + for _, o := range options { + if err := o(c, req); err != nil { + return nil, err + } + } return c.tx.Request(req) } +// WriteRequest sends request directly to transport layer func (c *Client) WriteRequest(req *sip.Request) error { return c.tp.WriteMsg(req) } -func (c *Client) updateRequest(r *sip.Request) { - // We handle here only INVITE and BYE - // https://www.rfc-editor.org/rfc/rfc3261.html#section-16.6 - if c.AddViaHeader { - if via, exists := r.Via(); exists { - newvia := via.Clone() - newvia.Host = c.host - newvia.Port = c.port - r.PrependHeader(newvia) +type ClientRequestOption func(c *Client, req *sip.Request) error - if via.Params.Has("rport") { - h, p, _ := net.SplitHostPort(r.Source()) - via.Params.Add("rport", p) - via.Params.Add("received", h) - } +// ClientRequestAddVia is option for adding via header +// https://www.rfc-editor.org/rfc/rfc3261.html#section-16.6 +func ClientRequestAddVia(c *Client, r *sip.Request) error { + if via, exists := r.Via(); exists { + newvia := via.Clone() + newvia.Host = c.host + newvia.Port = c.port + r.PrependHeader(newvia) + + if via.Params.Has("rport") { + h, p, _ := net.SplitHostPort(r.Source()) + via.Params.Add("rport", p) + via.Params.Add("received", h) } } + return nil +} - if c.AddRecordRoute { - rr := &sip.RecordRouteHeader{ - Address: sip.Uri{ - Host: c.host, - Port: c.port, - UriParams: sip.HeaderParams{ - // Transport must be provided as well - // https://datatracker.ietf.org/doc/html/rfc5658 - "transport": transport.NetworkToLower(r.Transport()), - "lr": "", - }, +// ClientRequestAddRecordRoute is option for adding record route header +func ClientRequestAddRecordRoute(c *Client, r *sip.Request) error { + rr := &sip.RecordRouteHeader{ + Address: sip.Uri{ + Host: c.host, + Port: c.port, + UriParams: sip.HeaderParams{ + // Transport must be provided as well + // https://datatracker.ietf.org/doc/html/rfc5658 + "transport": transport.NetworkToLower(r.Transport()), + "lr": "", }, - } + }, + } - r.PrependHeader(rr) + r.PrependHeader(rr) + return nil +} + +// ClientResponseRemoveVia is needed when handling client transaction response, where previously used in +// TransactionRequest with ClientRequestAddVia +func ClientResponseRemoveVia(c *Client, r *sip.Response) { + if via, exists := r.Via(); exists { + if via.Host == c.host { + // In case it is multipart Via remove only one + if via.Next != nil { + via.Remove() + } else { + r.RemoveHeader("Via") + } + } } } diff --git a/example/dialog/main.go b/example/dialog/main.go index c1236de..ca27ca9 100644 --- a/example/dialog/main.go +++ b/example/dialog/main.go @@ -93,7 +93,7 @@ func (h *Handler) route(req *sip.Request, tx sip.ServerTransaction) { } // Start client transaction and relay our request - clTx, err := h.c.TransactionRequest(req) + clTx, err := h.c.TransactionRequest(req, sipgo.ClientRequestAddVia, sipgo.ClientRequestAddRecordRoute) if err != nil { log.Error().Err(err).Msg("RequestWithContext failed") reply(tx, req, 500, "") @@ -108,7 +108,8 @@ func (h *Handler) route(req *sip.Request, tx sip.ServerTransaction) { return } res.SetDestination(req.Source()) - if err := h.s.TransactionReply(tx, res); err != nil { + sipgo.ClientResponseRemoveVia(h.c, res) + if err := tx.Respond(res); err != nil { log.Error().Err(err).Msg("ResponseHandler transaction respond failed") } diff --git a/example/proxysip/main.go b/example/proxysip/main.go index 287b8c4..882e86a 100644 --- a/example/proxysip/main.go +++ b/example/proxysip/main.go @@ -99,10 +99,6 @@ func setupSipProxy(proxydst string, ip string) *sipgo.Server { log.Fatal().Err(err).Msg("Fail to setup client handle") } - client.AddViaHeader = true // Adds via header before shiping request - client.AddRecordRoute = true // Adds record route header before shiping request - srv.RemoveViaHeader = true - registry := NewRegistry() var reply = func(tx sip.ServerTransaction, req *sip.Request, code sip.StatusCode, reason string) { @@ -130,7 +126,7 @@ func setupSipProxy(proxydst string, ip string) *sipgo.Server { req.SetDestination(dst) // Start client transaction and relay our request - clTx, err := client.TransactionRequest(req) + clTx, err := client.TransactionRequest(req, sipgo.ClientRequestAddVia, sipgo.ClientRequestAddRecordRoute) if err != nil { log.Error().Err(err).Msg("RequestWithContext failed") reply(tx, req, 500, "") @@ -148,7 +144,8 @@ func setupSipProxy(proxydst string, ip string) *sipgo.Server { return } res.SetDestination(req.Source()) - if err := srv.TransactionReply(tx, res); err != nil { + sipgo.ClientResponseRemoveVia(client, res) // For now this needs to be called manually + if err := tx.Respond(res); err != nil { log.Error().Err(err).Msg("ResponseHandler transaction respond failed") } diff --git a/server.go b/server.go index 67d8fb5..2db9459 100644 --- a/server.go +++ b/server.go @@ -133,38 +133,11 @@ func (srv *Server) handleRequest(req *sip.Request, tx sip.ServerTransaction) { } } -// TransactionReply is wrapper for calling tx.Respond -// it handles removing Via header by default -func (srv *Server) TransactionReply(tx sip.ServerTransaction, res *sip.Response) error { - srv.updateResponse(res) - return tx.Respond(res) -} - // WriteResponse will proxy message to transport layer. Use it in stateless mode func (srv *Server) WriteResponse(r *sip.Response) error { return srv.tp.WriteMsg(r) } -func (srv *Server) updateResponse(r *sip.Response) { - if srv.RemoveViaHeader { - srv.RemoveVia(r) - } -} - -// RemoveVia can be used in case of sending response. -func (srv *Server) RemoveVia(r *sip.Response) { - if via, exists := r.Via(); exists { - if via.Host == srv.host { - // In case it is multipart Via remove only one - if via.Next != nil { - via.Remove() - } else { - r.RemoveHeader("Via") - } - } - } -} - // Shutdown gracefully shutdowns SIP server func (srv *Server) shutdown() { // stop transaction layer @@ -286,24 +259,3 @@ func (srv *Server) onTransportMessage(m sip.Message) { func (srv *Server) TransportLayer() *transport.Layer { return srv.tp } - -// func (srv *Server) OnDialog(f func(d Dialog)) { -// dialogs := make(map[string]Dialog) - -// srv.responseMiddlewares = append(srv.responseMiddlewares, func(r *sip.Response) { -// if r.IsInvite() { - -// } - -// sip.MakeDialogIDFromMessage() -// }) - -// srv.requestMiddlewares = append(srv.requestMiddlewares, func(r *sip.Request) { -// if r.IsInvite() { - -// } - -// sip.MakeDialogIDFromMessage() -// }) - -// }