diff --git a/.gitignore b/.gitignore index d62a84f..29f6dc3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ /TODO.md /proxysip /.vscode +certs +*.pcap +/self-signed-root-ca-and-certificates.sh diff --git a/README.md b/README.md index 67f37e2..ed26fb4 100644 --- a/README.md +++ b/README.md @@ -45,21 +45,25 @@ srv.OnBye(byeHandler) // For registrars // srv.OnRegister(registerHandler) - - -// Add listeners -srv.Listen("udp", "127.0.0.1:5060") -srv.Listen("tcp", "127.0.0.1:5061") -srv.Listen("ws", "127.0.0.1:5080") -... -// fire server -srv.Serve() +go srv.ListenAndServe(ctx, "tcp", "127.0.0.1:5061") +go srv.ListenAndServe(ctx, "ws", "127.0.0.1:5080") +go srv.ListenAndServe(ctx, "udp", "127.0.0.1:5060") +<-ctx.Done() ``` - Server handle creates listeners and reacts on incoming requests. [More on server transactions](#server-transaction) - Client handle allows creating transaction requests [More on client transactions](#client-transaction) +#### TLS transports +```go +// TLS +conf := sipgo.GenerateTLSConfig(certFile, keyFile , rootPems []byte) +srv.ListenAndServeTLS(ctx, "tcp", "127.0.0.1:5061", conf) + +``` + + ## Stateful Proxy build Proxy is combination client and server handle that creates server/client transaction. @@ -176,7 +180,7 @@ More on documentation you can find on [Go doc](https://pkg.go.dev/github.com/emi - [x] UDP - [x] TCP -- [ ] TLS +- [x] TLS - [x] WS - [ ] WSS diff --git a/example/dialog/main.go b/example/dialog/main.go index 978de0e..04a3eb5 100644 --- a/example/dialog/main.go +++ b/example/dialog/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "os" @@ -40,8 +41,7 @@ func main() { setupRoutes(srv, h) log.Info().Str("ip", *extIP).Str("dst", *dst).Msg("Starting server") - srv.Listen("udp", *extIP) - if err := srv.Serve(); err != nil { + if err := srv.ListenAndServe(context.TODO(), "udp", *extIP); err != nil { log.Error().Err(err).Msg("Fail to serve") } } diff --git a/example/proxysip/docker-compose-tls.yml b/example/proxysip/docker-compose-tls.yml new file mode 100644 index 0000000..539b66c --- /dev/null +++ b/example/proxysip/docker-compose-tls.yml @@ -0,0 +1,44 @@ +version: "2.4" + +services: + proxy: + build: + context: ../../ + + # image: golang:latest + # command: go run ./example/proxysip -t tcp -ip=10.5.0.10:5060 -dst 10.5.0.20:5060 -debug + command: go run ./example/proxysip -t tcp -ip=10.5.0.10:5060 -dst 10.5.0.20:5060 -cert=certs/localhost.dev.cert.pem -key=certs/localhost.dev.key.pem -rootpems=certs/ca/localtesting_ca.crt.pem -debug + # stdin_open: true + # command: bash + # working_dir: /usr/src/sipgo + volumes: + - ../../../sipgo:/go/sipgo + + cpus: 4.0 + cpuset: "0,1,2,3" + + networks: + mynet: + ipv4_address: 10.5.0.10 + + # mem_limit: 4G + # network_mode: "host" + environment: + - GOMAXPROCS=4 # Go does not match this by default + # - GODEBUG=madvdontneed=0 + # - GODEBUG=madvdontneed=1,gctrace=1 + # - GOGC=70 + + uas: # TODO + + uac: + # TODO + + +networks: + mynet: + driver: bridge + ipam: + config: + - subnet: 10.5.0.0/16 + gateway: 10.5.0.1 diff --git a/example/proxysip/main.go b/example/proxysip/main.go index 9a7589f..e05e989 100644 --- a/example/proxysip/main.go +++ b/example/proxysip/main.go @@ -1,8 +1,10 @@ package main import ( + "context" "encoding/json" "flag" + "io/ioutil" "net/http" "os" "runtime" @@ -30,6 +32,9 @@ func main() { extIP := flag.String("ip", "127.0.0.1:5060", "My exernal ip") dst := flag.String("dst", "", "Destination pbx, sip server") transportType := flag.String("t", "udp", "Transport, default will be determined by request") + certFile := flag.String("cert", "", "") + keyFile := flag.String("key", "", "") + rootPems := flag.String("rootpems", "", "") flag.Parse() if *pprof { @@ -61,8 +66,30 @@ func main() { // srv.TransportLayer().ConnectionReuse = false } - srv.Listen(*transportType, *extIP) - if err := srv.Serve(); err != nil { + if *certFile != "" { + rootPems, err := ioutil.ReadFile(*rootPems) + if err != nil { + log.Fatal().Err(err).Msg("Fail to read rootpems") + } + conf, err := sipgo.GenerateTLSConfig(*certFile, *keyFile, rootPems) + if err != nil { + log.Fatal().Err(err).Msg("Fail to generate conf") + } + + // keylog, err := os.OpenFile("key.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) + // if err != nil { + // log.Fatal().Err(err).Msg("Fail to open keylogd") + // } + + // conf.KeyLogWriter = + + if err := srv.ListenAndServeTLS(context.TODO(), *transportType, *extIP, conf); err != nil { + log.Error().Err(err).Msg("Fail to start sip server") + return + } + } + + if err := srv.ListenAndServe(context.TODO(), *transportType, *extIP); err != nil { log.Error().Err(err).Msg("Fail to start sip server") return } diff --git a/server.go b/server.go index 8d83404..e391c5d 100644 --- a/server.go +++ b/server.go @@ -2,6 +2,9 @@ package sipgo import ( "context" + "crypto/tls" + "crypto/x509" + "fmt" "github.com/emiago/sipgo/sip" "github.com/emiago/sipgo/transport" @@ -19,15 +22,11 @@ type Server struct { // requestHandlers map of all registered request handlers requestHandlers map[sip.RequestMethod]RequestHandler - listeners map[string]string //addr:network log zerolog.Logger requestMiddlewares []func(r *sip.Request) responseMiddlewares []func(r *sip.Response) - - // Default server behavior for sending request in preflight - RemoveViaHeader bool } type ServerOption func(s *Server) error @@ -59,7 +58,6 @@ func newBaseServer(ua *UserAgent, options ...ServerOption) (*Server, error) { requestMiddlewares: make([]func(r *sip.Request), 0), responseMiddlewares: make([]func(r *sip.Response), 0), requestHandlers: make(map[sip.RequestMethod]RequestHandler), - listeners: make(map[string]string), log: log.Logger.With().Str("caller", "Server").Logger(), } for _, o := range options { @@ -71,26 +69,14 @@ func newBaseServer(ua *UserAgent, options ...ServerOption) (*Server, error) { return s, nil } -// Listen adds listener for serve -func (srv *Server) Listen(network string, addr string) { - srv.listeners[addr] = network -} - -// Serve will fire all listeners -func (srv *Server) Serve() error { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - return srv.ServeWithContext(ctx) +// Serve will fire all listeners. Ctx allows canceling +func (srv *Server) ListenAndServe(ctx context.Context, network string, addr string) error { + return srv.tp.ListenAndServe(ctx, network, addr) } -// Serve will fire all listeners. Ctx allows canceling -func (srv *Server) ServeWithContext(ctx context.Context) error { - defer srv.shutdown() - for addr, network := range srv.listeners { - go srv.tp.ListenAndServe(ctx, network, addr) - } - <-ctx.Done() - return ctx.Err() +// Serve will fire all listeners that are secured. Ctx allows canceling +func (srv *Server) ListenAndServeTLS(ctx context.Context, network string, addr string, conf *tls.Config) error { + return srv.tp.ListenAndServeTLS(ctx, network, addr, conf) } // onRequest gets request from Transaction layer @@ -259,3 +245,27 @@ func (srv *Server) onTransportMessage(m sip.Message) { func (srv *Server) TransportLayer() *transport.Layer { return srv.tp } + +// GenerateTLSConfig creates basic tls.Config that you can pass for ServerTLS +// It needs rootPems for client side +func GenerateTLSConfig(certFile string, keyFile string, rootPems []byte) (*tls.Config, error) { + roots := x509.NewCertPool() + if rootPems != nil { + ok := roots.AppendCertsFromPEM(rootPems) + if !ok { + return nil, fmt.Errorf("failed to parse root certificate") + } + } + + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return nil, fmt.Errorf("fail to load cert. err=%w", err) + } + + conf := &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: roots, + } + + return conf, nil +} diff --git a/server_integration_test.go b/server_integration_test.go index ca63a37..7712faa 100644 --- a/server_integration_test.go +++ b/server_integration_test.go @@ -1,6 +1,7 @@ package sipgo import ( + "context" "testing" "time" @@ -30,10 +31,8 @@ func TestWebsocket(t *testing.T) { <-tx.Done() }) - srv.Listen("ws", "127.0.0.1:5060") - go func() { - if err := srv.Serve(); err != nil { + if err := srv.ListenAndServe(context.TODO(), "ws", "127.0.0.1:5060"); err != nil { log.Error().Err(err).Msg("Fail to serve") } }() @@ -45,9 +44,8 @@ func TestWebsocket(t *testing.T) { csrv, err := NewServer(ua) // Create server handle require.Nil(t, err) - csrv.Listen("ws", "127.0.0.2:5060") go func() { - if err := csrv.Serve(); err != nil { + if err := csrv.ListenAndServe(context.TODO(), "ws", "127.0.0.2:5060"); err != nil { log.Error().Err(err).Msg("Fail to serve") } }() diff --git a/transport/layer.go b/transport/layer.go index d21a45e..30a81a0 100644 --- a/transport/layer.go +++ b/transport/layer.go @@ -3,7 +3,6 @@ package transport import ( "context" "crypto/tls" - "crypto/x509" "errors" "fmt" "math/rand" @@ -131,30 +130,12 @@ func (l *Layer) ServeWS(c net.Listener) error { } // ServeTLS will listen on tcp connection. rootPems can be nil if there is no need for client use -func (l *Layer) ServeTLS(c net.Listener, certFile string, keyFile string, rootPems []byte) error { +func (l *Layer) ServeTLS(c net.Listener, conf *tls.Config) error { _, port, err := sip.ParseAddr(c.Addr().String()) if err != nil { return err } - roots := x509.NewCertPool() - if rootPems != nil { - ok := roots.AppendCertsFromPEM(rootPems) - if !ok { - return fmt.Errorf("failed to parse root certificate") - } - } - - cert, err := tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - return fmt.Errorf("fail to load cert. err=%w", err) - } - - conf := &tls.Config{ - Certificates: []tls.Certificate{cert}, - RootCAs: roots, - } - transport := NewTLSTransport(c.Addr().String(), parser.NewParser(), conf) l.addTransport(transport, "tls", port) @@ -200,7 +181,7 @@ func (l *Layer) ListenAndServe(ctx context.Context, network string, addr string) // Serve on any tls network. This function will block // Network supported: tcp -func (l *Layer) ListenAndServeTLS(ctx context.Context, network string, addr string, certFile string, keyFile string, rootPems []byte) error { +func (l *Layer) ListenAndServeTLS(ctx context.Context, network string, addr string, conf *tls.Config) error { network = strings.ToLower(network) _, port, err := sip.ParseAddr(addr) if err != nil { @@ -214,27 +195,9 @@ func (l *Layer) ListenAndServeTLS(ctx context.Context, network string, addr stri p := parser.NewParser() - roots := x509.NewCertPool() - if rootPems != nil { - ok := roots.AppendCertsFromPEM(rootPems) - if !ok { - return fmt.Errorf("failed to parse root certificate") - } - } - - cert, err := tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - return fmt.Errorf("fail to load cert. err=%w", err) - } - - conf := &tls.Config{ - Certificates: []tls.Certificate{cert}, - RootCAs: roots, - } - var t Transport switch network { - case "tcp": + case "tcp", "tls": t = NewTLSTransport(addr, p, conf) // case "ws": // t = NewWSTransport(addr, p) @@ -243,13 +206,14 @@ func (l *Layer) ListenAndServeTLS(ctx context.Context, network string, addr stri } // Add transport to list - l.addTransport(t, "tls", port) + l.addTransport(t, t.Network(), port) err = t.ListenAndServe(l.handleMessage) return err } func (l *Layer) addTransport(t Transport, network string, port int) { + network = NetworkToLower(network) if _, ok := l.listenPorts[network]; !ok { if l.listenPorts[network] == nil { l.listenPorts[network] = make([]int, 0) diff --git a/transport/tcp.go b/transport/tcp.go index a292da5..459aab9 100644 --- a/transport/tcp.go +++ b/transport/tcp.go @@ -19,20 +19,22 @@ var () // TCP transport implementation type TCPTransport struct { - addr string - listener net.Listener - parser parser.SIPParser - handler sip.MessageHandler - log zerolog.Logger + addr string + transport string + listener net.Listener + parser parser.SIPParser + handler sip.MessageHandler + log zerolog.Logger pool ConnectionPool } func NewTCPTransport(addr string, par parser.SIPParser) *TCPTransport { p := &TCPTransport{ - addr: addr, - parser: par, - pool: NewConnectionPool(), + addr: addr, + parser: par, + pool: NewConnectionPool(), + transport: "tcp", } p.log = log.Logger.With().Str("caller", "transport").Logger() return p @@ -48,7 +50,7 @@ func (t *TCPTransport) Addr() string { func (t *TCPTransport) Network() string { // return "tcp" - return TransportTCP + return t.transport } func (t *TCPTransport) Close() error { diff --git a/transport/tls.go b/transport/tls.go new file mode 100644 index 0000000..415006c --- /dev/null +++ b/transport/tls.go @@ -0,0 +1,81 @@ +package transport + +import ( + "crypto/tls" + "fmt" + "net" + + "github.com/emiago/sipgo/parser" + "github.com/emiago/sipgo/sip" + + "github.com/rs/zerolog/log" +) + +var () + +// TLS transport implementation +type TLSTransport struct { + *TCPTransport + + // rootPool *x509.CertPool + tlsConf *tls.Config +} + +func NewTLSTransport(addr string, par parser.SIPParser, tlsConf *tls.Config) *TLSTransport { + tcptrans := NewTCPTransport(addr, par) + tcptrans.transport = "tls" //Override transport + p := &TLSTransport{ + TCPTransport: tcptrans, + } + + // p.rootPool = roots + p.tlsConf = tlsConf + p.log = log.Logger.With().Str("caller", "transport").Logger() + return p +} + +func (t *TLSTransport) String() string { + return "transport" +} + +// This is more generic way to provide listener and it is blocking +func (t *TLSTransport) ListenAndServe(handler sip.MessageHandler) error { + addr := t.addr + laddr, err := net.ResolveTCPAddr("tcp", addr) + if err != nil { + return fmt.Errorf("fail to resolve address. err=%w", err) + } + + listener, err := tls.Listen("tcp", laddr.String(), t.tlsConf.Clone()) + if err != nil { + return fmt.Errorf("listen tls error. err=%w", err) + } + + return t.Serve(listener, handler) +} + +// CreateConnection creates TLS connection for TCP transport +func (t *TLSTransport) CreateConnection(addr string) (Connection, error) { + raddr, err := net.ResolveTCPAddr("tcp", addr) + if err != nil { + return nil, err + } + return t.createConnection(raddr) +} + +func (t *TLSTransport) createConnection(raddr *net.TCPAddr) (Connection, error) { + addr := raddr.String() + t.log.Debug().Str("raddr", addr).Msg("Dialing new connection") + + //TODO does this need to be each config + // SHould we make copy of rootPool? + // There is Clone of config + + conn, err := tls.Dial("tcp", raddr.String(), t.tlsConf) + if err != nil { + return nil, fmt.Errorf("%s dial err=%w", t, err) + } + + c := t.initConnection(conn, addr) + return c, nil +}