sipgo

repository·main·Indexed 21 days ago

https://github.com/emiago/sipgo

A high-performance Go library for building SIP services, including servers, clients, proxies, and registrars. Compliant with RFC 3261, it features an optimized parser and supports multiple transports including UDP, TCP, TLS, and WebSockets. The library provides tools for managing SIP dialogs via DialogClientCache and DialogServerCache, handling server and client transactions, and implementing stateful proxies.

Tokens
12.2K
Snippets
55
Records
66
Agent score
77%

What's inside sipgo

  1. Overview of the SIPGO stack

    main

    SIPGO is a SIP stack implemented in Go, compliant with RFC 3261. It is designed for high performance and includes the following core layers:

    • Parser: Optimized for fast encoding and decoding of SIP messages.
    • Transport Layer: Supports various protocols for message delivery.
    • Transaction Layer: Manages transaction sessions and state machines.
  2. Optimize proxysip performance and memory usage

    main

    The proxysip library uses heavy caching to reduce Go Garbage Collection (GC) pressure, which results in HIGH memory usage.

    To manage performance and memory:

    • To improve performance: Increase the GOGC environment variable (e.g., GOGC=200). This reduces GC frequency but increases memory consumption.
    • Container environments: Ensure GOMAXPROCS is correctly set to match your CPU quota. Alternatively, import go.uber.org/automaxprocs to handle this automatically.
    import _ "go.uber.org/automaxprocs"
  3. Handle Server Transactions

    main

    When a request is received by a Server, the handler receives a sip.ServerTransaction. You use this transaction to send provisional or final responses and to monitor the transaction lifecycle (ACKs or termination).

    // Incoming request handler
    srv.OnInvite(func(req *sip.Request, tx sip.ServerTransaction) {
        // Send provisional response (e.g., 100 Trying)
        res := sip.NewResponseFromRequest(req, 100, "Trying", nil)
        tx.Respond(res)
        
        // Send final response (e.g., 200 OK)
        res := sip.NewResponseFromRequest(req, 200, "OK", body)
        tx.Respond(res)
    
        // Wait for transaction termination
        select {
        case m := <-tx.Acks(): 
            // Handle ACK for response
        case <-tx.Done():
            // Signal transaction is done. 
            // Check tx.Err() for error details
            return
        }
    })
  4. Optimize SIP parsing performance with custom header parsers

    main

    The SIP parser is optimized for speed by default, automatically parsing common headers such as From, To, Via, Cseq, Content-Type, and Content-Length. These headers can be accessed via fast reference methods on the message object (e.g., msg.Via(), msg.From()).

    To increase performance in high-throughput scenarios, you can use WithHeadersParsers to reduce the set of headers that are parsed automatically. When a header is not in the pre-parsed set, the stack uses lazy parsing to access it when requested.

  5. Stress test UDP with sipp and proxysip

    main

    You can stress test the proxy using docker-compose with sipp running uac and uas scenarios. All traffic is proxied from the UAC to the UAS.

    To run the stress test environment, open three separate terminals and execute the following commands:

    1. Start the proxy service.
    2. Start the UAS (User Agent Server).
    3. Start the UAC (User Agent Client).

    Note: Performance results vary by hardware, but on an i7 (limited to 4 cores), it has demonstrated handling >2000 calls/s and peaks >12000 calls.

    # Run this in 3 terminals
    docker-compose run proxy
    docker-compose run uas
    docker-compose run uac
  6. Build a User Agent (UAS/UAC)

    main

    SIPGO uses a UA (User Agent) as the base for both servers (UAS) and clients (UAC). You create a UA using sipgo.NewUA(), then wrap it in either a Server handle to react to incoming requests or a Client handle to initiate outgoing requests.

    Supported transport protocols include udp, tcp, tls, ws, and wss.

    ua, _ := sipgo.NewUA() // Build user agent
    srv, _ := sipgo.NewServer(ua) // Creating server handle for ua
    client, _ := sipgo.NewClient(ua) // Creating client handle for ua
    
    // Register handlers for incoming requests
    srv.OnInvite(inviteHandler)
    srv.OnAck(ackHandler)
    srv.OnBye(byeHandler)
    
    // Start listening on various transports
    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()
  7. Handle Dialogs as a UAC (Client)

    main

    To manage a SIP dialog (e.g., a call session) as a client, use sipgo.NewDialogClientCache. This helper manages the dialog state and provides high-level methods like Invite, WaitAnswer, Ack, and Bye.

    ua, _ := sipgo.NewUA()
    srv, _ := sipgo.NewServer(ua)
    client, _ := sipgo.NewClient(ua)
    
    contactHDR := sip.ContactHeader{
        Address: sip.Uri{User: "test", Host: "127.0.0.200", Port: 5088},
    }
    dialogCli := sipgo.NewDialogClientCache(client, contactHDR)
    
    // Attach Bye handling to the server to manage the 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() 
    
    // Wait for answer
    err = dialog.WaitAnswer(ctx, AnswerOptions{})
    
    // Check response (e.g., SDP) and send ACK
    err = dialog.Ack(ctx)
    
    // Terminate call
    dialog.Bye(ctx)
  8. Run the SIP registration client example

    main

    To run the registration client example, use the go run command on the client directory. Use the following flags to configure the client:

    • -u: The username for registration.
    • -p: The password for registration.
    • -srv: The SIP server address (IP and port) to register with.
    go run ./client -u alice -p alice -srv 127.0.0.10:5060
    go run ./client -u bob -p bob -srv 127.0.0.10:5060
  9. Handle Dialogs as a UAS (Server)

    main

    To manage a SIP dialog as a server, use sipgo.NewDialogServerCache. This allows you to read incoming requests (like Invite, Ack, or Bye) and associate them with an existing dialog session.

    ua, _ := sipgo.NewUA()
    srv, _ := sipgo.NewServer(ua)
    client, _ := sipgo.NewClient(ua)
    
    uasContact := sip.ContactHeader{
        Address: sip.Uri{User: "test", Host: "127.0.0.200", Port: 5099},
    }
    dialogSrv := sipgo.NewDialogServerCache(client, uasContact)
    
    // Handle incoming INVITE
    srv.OnInvite(func(req *sip.Request, tx sip.ServerTransaction) {
        dlg, err := dialogSrv.ReadInvite(req, tx)
        if err != nil {
            return
        }
        defer dlg.Close() 
        
        dlg.Respond(sip.StatusTrying, "Trying", nil)
        dlg.Respond(sip.StatusOK, "OK", nil)
        
        <-dlg.Context().Done()
    })
    
    // Handle ACK and BYE for the dialog
    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)
    })
  10. Build a Stateful Proxy

    main

    A Stateful Proxy in sipgo is implemented by combining a Server handle and a Client handle that share the same User Agent (ua). This allows the proxy to create server/client transactions that are linked. To forward a request, you intercept it in a server handler, modify the destination on the sip.Request, and then use the client handle to initiate a new transaction. You can use options like sipgo.ClientRequestAddVia and sipgo.ClientRequestAddRecordRoute to ensure the relayed request has the necessary headers.

    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)
    })
  11. Enable SIP Debugging

    main

    To view full SIP messages dumped from the transport layer, enable the global sip.SIPDebug flag. This will output messages at the DEBUG log level, showing the raw SIP content including headers and body.

    sip.SIPDebug = true