diff --git a/.gitignore b/.gitignore index a0f856f..b985af1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ tcpsplice -monitor.go +static.go debian/tcpsplice debian/*debhelper* debian/*substvars* diff --git a/Makefile b/Makefile index a22fa24..fdb881d 100755 --- a/Makefile +++ b/Makefile @@ -1,16 +1,23 @@ #!/bin/sh -tcpsplice: *.go monitor.html - @(echo -n "package main\nimport \"encoding/base64\"\nconst _monitorContent = \""; base64 -w 0 < monitor.html; echo "\"\nvar monitorContent []byte\nfunc init() { monitorContent, _ = base64.StdEncoding.DecodeString(_monitorContent) }") >monitor.go - @export GOPATH=/tmp/go; export CGO_ENABLED=0; go build -trimpath -o tcpsplice && strip tcpsplice +# build targets +tcpsplice: static.go main.go + @env GOPATH=/tmp/go CGO_ENABLED=0 go build -trimpath -o tcpsplice + @-strip tcpsplice 2>/dev/null || true + @-upx -9 tcpsplice 2>/dev/null || true +static.go: rpack + @-./rpack static +rpack: + @-env GOBIN=`pwd` go get github.com/pyke369/golang-support/rpack/rpack clean: distclean: - @rm -rf tcpsplice monitor.go + @rm -rf tcpsplice static.go rpack deb: @debuild -e GOROOT -e GOPATH -e PATH -i -us -uc -b debclean: @debuild -- clean @rm -f ../tcpsplice_* +# run targets run: tcpsplice - @./tcpsplice tcpsplice.conf + @./tcpsplice conf/tcpsplice.conf diff --git a/conf/cert.pem b/conf/cert.pem new file mode 100644 index 0000000..2961cc8 --- /dev/null +++ b/conf/cert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICuDCCAaCgAwIBAgIJALltl03a8TCpMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV +BAMTCWxvY2FsaG9zdDAeFw0xNjEwMTAxODQwNTdaFw0yNjEwMDgxODQwNTdaMBQx +EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAKfI4D4LtZrfnujBiOQ13PItpS5ZGzAr0DxUVGvKviaqGJcFjUPikydkVayJ +fPoaZR4WZVL9ThqXQSctMnK16WO3U+6G5srnTHtKS7YPnuOEhBFJsAF7955cUGaY +ShU3eeKG9lkws/W3tdd8WsgDSIfBJ3mCCzfQaaWtD/zGxe4pCAW9x5IOKFby4+Sk +0QbAfx1Ngi1Czj1Uw3En8yvQphm8A0C8kXrGhDd1lUdPYHWcHCBlflLKMsLAdK2C +Rr+wfrhbTCZnULXN/K7/p4gthFIzajb//fuK4rQoTWfIeaaNUtZ3/Kahnw62MK/6 +Yg0ZNGDYPdz53z7AVLrAMh0u+tMCAwEAAaMNMAswCQYDVR0TBAIwADANBgkqhkiG +9w0BAQsFAAOCAQEANaDuPI/UOSmZxEWnYZ9olS9jW5pKHGmIkjAdHANotj47W4QX +vfVR6/JKSLAvWMDKlUdtbfe1qSdY+GRZHyLPI0dcVZq8fO5Vmc+D9rCkPR8Iy1Lq +NbYo5uFfn1ZoaXO7ex6dcyn9li+ex0G8lj7mR5EB/R0wd9E4ClZOnWGt43C90PwN +1lBzNGn/H3cGq6xaOK+pfx7aIPsnWwnPwgwWSDojJt4ayBJ+JnSGszOGtVjgKBl7 +NciXESNkm8bSDVvebVNtmj//Vu04KaO2bRCM/PAqPyy3zwrzvZ3dzqUDWzJ1EB94 +0sULH3G24rkQp3M9BTDANVNFTmnr9gD/8lZlxQ== +-----END CERTIFICATE----- diff --git a/conf/key.pem b/conf/key.pem new file mode 100644 index 0000000..f178e66 --- /dev/null +++ b/conf/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCnyOA+C7Wa357o +wYjkNdzyLaUuWRswK9A8VFRryr4mqhiXBY1D4pMnZFWsiXz6GmUeFmVS/U4al0En +LTJyteljt1PuhubK50x7Sku2D57jhIQRSbABe/eeXFBmmEoVN3nihvZZMLP1t7XX +fFrIA0iHwSd5ggs30GmlrQ/8xsXuKQgFvceSDihW8uPkpNEGwH8dTYItQs49VMNx +J/Mr0KYZvANAvJF6xoQ3dZVHT2B1nBwgZX5SyjLCwHStgka/sH64W0wmZ1C1zfyu +/6eILYRSM2o2//37iuK0KE1nyHmmjVLWd/ymoZ8OtjCv+mINGTRg2D3c+d8+wFS6 +wDIdLvrTAgMBAAECggEAVAQ5jbgtUwUhPKoU0znJjpeevCuwepml51/O+j8GorPQ +JmeISnL9ft82K3SZWV+4PK24RStEXfpZjLWMKna/DMizRaDVlsrluGMGsH738DPe +Mg31DXk3EFxngkhF6IPkC7PLTfQeWS+J5gKGLtu+CQSGsMiWt3csP+L+O3SJjmy7 +yn/uu2HwqpgUG9BpiG4yFjGXmefvUP0o/LGa+TzIIojlpWl7dGBzNJ12bi+0Dpq7 +Ztv/xJFxA+2RBkRVHSMIyVopdtWNWl09adZjDWZY2muMEWEf0kogOKD8tvJ5Eru5 +aMGH2/H4Fq26AKDpeS9BjQ61VmRFWr66WqCNVaq/4QKBgQDVY89lEB4Q10AUcKRB +v9dFX9KIcBfNqcKHELq5tthUFYqj/IyWIZ5GvAtgIO3yZ92EYW7g1WRz1rF37XIn +GafIk8m34Wq9+RJNc+hoPDRB/RTq5c97blUOl6FBhG/o35Wsr4N7jT844amSGZ/A +ct07yxgQWTIF4qQayFRnvUNyAwKBgQDJScqttoSeiu8E/7gIWODj5ZBF/vq8m74n +nKr6e2RZ6okhT0l52wK4rh91rGHdZoEJTE+0IZh4VlcjOarbpx//YcJCAHzDSlBM +yJ93Xu8zrTH64gitZfrzoPPvhFaPgQrShwHawhwm//BCFQ5MnpLZ2uMxUh+kQmfm +x4xj+K7i8QKBgQDO02P0P6/0gL1SUm9Sbv/W9O5ZYdQgedbbFML3OBrrPMnY8fLN +nR4MzzxzWtdmqXdSVSGj+BDaGhB+/f0zmrE+Psg4Wtsb8KrluV9ckGXSQ9ufZUk6 +CJGWiC87EoNpgjRPYPqeqSPLHSY/PmjRnkOCLfJP/jP28lo+v0bYGeCiQwKBgGFm +M4ybJNESqVXh50sitq+QBZ/ZIbriIcFJLfLGgmh/9JsJoqQ0NbznhJGMOE7Jqua0 +5lxjZUPVg5Sn8uUWmYUZ6MXHNpfI/dIpwgAhD94RkH21oj1Fe4kn+OGNR9Vou7Pj +YCJaiwTUE43mYmTw1l5UbFsRQf5Zo60oIea+DuIBAoGBAJuiSHfXlv9OdlLghiFk +XJs4spEqeAS+nfx2BScT2ft4MC5x0cUPovQ6AL/6YBCk+LDKqYn4UM4Aez5rnPPV +FqQXXmeGQndRvBrLb1tnviTi+uU8pGI/H5hiAC3B8nJKq8VlmBH34fKIuBR2Iuil +P85Yr82Fr8w1deA5U9Ear+gf +-----END PRIVATE KEY----- diff --git a/conf/tcpsplice.conf b/conf/tcpsplice.conf new file mode 100644 index 0000000..8325dac --- /dev/null +++ b/conf/tcpsplice.conf @@ -0,0 +1,47 @@ +server +{ + // log = "console()" +} + +monitor +{ + listen = "*:8000" + # acl + # { + # allow = [ "127.0.0.1" ] + # auth = [ "tcpsplice:monitor" ] + # } +} + +services +{ + rtmp + { + local = [ "*:1935" ] + remote = [ "my.server.com:1935" ] + // connect_timeout = 10 + // write_timeout = 10 + // idle_timeout = 60 + // incoming_buffer_size = 64k + // outgoing_buffer_size = 64k + // log_minimum_size = 1k + // session_minimum_size = 1k + // meta_size = 16k + // meta_scan = "[" + } + + rtmps + { + local = [ "*:1936,conf/cert.pem,conf/key.pem" ] + remote = [ "my.server.com:1935" ] + // connect_timeout = 10 + // write_timeout = 10 + // idle_timeout = 60 + // incoming_buffer_size = 64k + // outgoing_buffer_size = 64k + // log_minimum_size = 1k + // session_minimum_size = 1k + // meta_size = 16k + // meta_scan = "[" + } +} diff --git a/debian/changelog b/debian/changelog index 9c9978b..dde5264 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +tcpsplice (1.2.0) stable; urgency=medium + + * add native TLS support and modernize software packaging + + -- Pierre-Yves Kerembellec Thu, 11 Feb 2021 16:45:09 +0100 + tcpsplice (1.1.0) stable; urgency=medium * update to Go 1.15 (and Go modules) diff --git a/go.mod b/go.mod index d09e4e3..533469d 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ -module tcpsplice +module main go 1.15 -require github.com/pyke369/golang-support v0.0.0-20200321184731-cd1ed731523d +require github.com/pyke369/golang-support v0.0.0-20210211171524-9d13f1802e2b diff --git a/go.sum b/go.sum index 6289dec..e78c231 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -github.com/pyke369/golang-support v0.0.0-20200321184731-cd1ed731523d h1:bssjYWDUaYbjtBc7gZ6BI3YBerIg5EAn+kAM5hMQf2s= -github.com/pyke369/golang-support v0.0.0-20200321184731-cd1ed731523d/go.mod h1:0XGrzgrEp0fa/+JSV8XZePUwyjnU6C3bMc7Xz2bHHKI= +github.com/pyke369/golang-support v0.0.0-20210211171524-9d13f1802e2b h1:XamoXfiYhBSPMo9+rf5BIG0fmFzpoSyLUqsXTnnhydk= +github.com/pyke369/golang-support v0.0.0-20210211171524-9d13f1802e2b/go.mod h1:0XGrzgrEp0fa/+JSV8XZePUwyjnU6C3bMc7Xz2bHHKI= diff --git a/main.go b/main.go index 3efec30..8fead47 100644 --- a/main.go +++ b/main.go @@ -2,10 +2,9 @@ package main import ( "crypto/md5" + "crypto/tls" "encoding/json" "fmt" - "github.com/pyke369/golang-support/uconfig" - "github.com/pyke369/golang-support/ulog" "math" "math/rand" "net" @@ -16,10 +15,14 @@ import ( "strings" "sync" "time" + + "github.com/pyke369/golang-support/dynacert" + "github.com/pyke369/golang-support/uconfig" + "github.com/pyke369/golang-support/ulog" ) const progname = "tcpsplice" -const version = "1.1.0" +const version = "1.2.0" type Session struct { id, service, source, target, meta string @@ -40,9 +43,9 @@ var ( sessionsLock sync.RWMutex ) -func serviceHandler(service string, listener *net.TCPListener) { +func serviceHandler(service string, listener net.Listener) { for { - if source, err := listener.AcceptTCP(); err == nil { + if source, err := listener.Accept(); err == nil { remotes := config.GetPaths(fmt.Sprintf("services.%s.remote", service)) if len(remotes) > 0 { go func() { @@ -57,8 +60,10 @@ func serviceHandler(service string, listener *net.TCPListener) { idleTimeout := time.Second * time.Duration(config.GetDurationBounds(fmt.Sprintf("services.%s.idle_timeout", service), 60, 0, 300)) metaSize := int(config.GetSizeBounds(fmt.Sprintf("services.%s.meta_size", service), 16*1024, 0, 64*1024)) metaScan, _ := regexp.Compile(config.GetString(fmt.Sprintf("services.%s.meta_scan", service), "[")) - source.SetReadBuffer(int(incomingSize)) - source.SetWriteBuffer(int(incomingSize)) + if tsource, ok := source.(*net.TCPConn); ok { + tsource.SetReadBuffer(int(incomingSize)) + tsource.SetWriteBuffer(int(incomingSize)) + } target.(*net.TCPConn).SetReadBuffer(int(outgoingSize)) target.(*net.TCPConn).SetWriteBuffer(int(outgoingSize)) session := &Session{ @@ -208,23 +213,25 @@ func monitorHandler(response http.ResponseWriter, request *http.Request) { services := map[string]interface{}{} for _, service := range config.GetPaths("services") { service = strings.TrimPrefix(service, "services.") - s := map[string]interface{}{} + entries := map[string]interface{}{} sessionsLock.RLock() for id, session := range sessions { - s[id] = map[string]interface{}{ - "started": session.started.Unix(), - "duration": time.Now().Sub(session.started) / time.Second, - "source": session.source, - "target": session.target, - "bytes": [2]int64{session.sourceRead, session.targetRead}, - "mean": [2]float64{session.sourceMeanThroughput, session.targetMeanThroughput}, - "last": [2]float64{session.sourceLastThroughput, session.targetLastThroughput}, - "meta": session.meta, - "done": session.done, + if session.service == service { + entries[id] = map[string]interface{}{ + "started": session.started.Unix(), + "duration": time.Now().Sub(session.started) / time.Second, + "source": session.source, + "target": session.target, + "bytes": [2]int64{session.sourceRead, session.targetRead}, + "mean": [2]float64{session.sourceMeanThroughput, session.targetMeanThroughput}, + "last": [2]float64{session.sourceLastThroughput, session.targetLastThroughput}, + "meta": session.meta, + "done": session.done, + } } } sessionsLock.RUnlock() - services[service] = s + services[service] = entries } output["services"] = services response.Header().Set("Content-Type", "application/json") @@ -242,9 +249,6 @@ func monitorHandler(response http.ResponseWriter, request *http.Request) { } } sessionsLock.RUnlock() - } else { - response.Header().Set("Content-Type", "text/html; charset=utf-8") - response.Write(monitorContent) } } @@ -315,21 +319,27 @@ func main() { for _, service := range config.GetPaths("services") { for _, local := range config.GetPaths(fmt.Sprintf("%s.local", service)) { service = strings.TrimPrefix(service, "services.") - local = config.GetString(local, "") - if address, err := net.ResolveTCPAddr("tcp", strings.TrimLeft(local, "*")); err == nil { - if listener, err := net.ListenTCP("tcp", address); err == nil { - logger.Info(map[string]interface{}{"type": "service", "name": service, "listen": local}) + if parts := strings.Split(config.GetStringMatch(local, "_", "^.*?(:\\d+)?((,[^,]+){2})?$"), ","); parts[0] != "_" { + if listener, err := net.Listen("tcp", strings.TrimLeft(parts[0], "*")); err == nil { + if len(parts) > 1 { + loader := &dynacert.DYNACERT{Public: parts[1], Key: parts[2]} + listener = tls.NewListener(listener, dynacert.IntermediateTLSConfig(loader.GetCertificate)) + logger.Info(map[string]interface{}{"type": "service", "name": service, "listen": parts[0], "cert": parts[1], "key": parts[2]}) + } else { + logger.Info(map[string]interface{}{"type": "service", "name": service, "listen": parts[0]}) + } go serviceHandler(service, listener) } else { - logger.Warn(map[string]interface{}{"type": "service", "name": service, "listen": local, "error": err}) + logger.Warn(map[string]interface{}{"type": "service", "name": service, "listen": parts[0], "error": err}) } - } else { - logger.Warn(map[string]interface{}{"type": "service", "name": service, "listen": local, "error": err}) } } } - http.Handle("/", baseHandler(http.HandlerFunc(monitorHandler))) + http.Handle("/sessions.json", baseHandler(http.HandlerFunc(monitorHandler))) + http.Handle("/abort/", baseHandler(http.HandlerFunc(monitorHandler))) + http.Handle("/", baseHandler(http.StripPrefix("/", Resources(6*time.Hour)))) + if parts := strings.Split(config.GetStringMatch("monitor.listen", "_", "^(?:\\*|\\d+(?:\\.\\d+){3}|\\[[^\\]]+\\])(?::\\d+)?(?:(?:,[^,]+){2})?$"), ","); parts[0] != "_" { server := &http.Server{Addr: strings.TrimLeft(parts[0], "*")} if len(parts) > 1 { diff --git a/monitor.html b/static/index.html similarity index 100% rename from monitor.html rename to static/index.html diff --git a/tcpsplice.conf b/tcpsplice.conf deleted file mode 100644 index f1b6c9e..0000000 --- a/tcpsplice.conf +++ /dev/null @@ -1,32 +0,0 @@ -server -{ - // log = "console()" -} - -monitor -{ - listen = "*:8000" - // acl - // { - // allow = [ "127.0.0.1" ] - // auth = [ "tcpsplice:monitor" ] - // } -} - -services -{ - example - { - local = [ "*:12345" ] - remote = [ "my.origin.net:12345" ] - // connect_timeout = 10 - // write_timeout = 10 - // idle_timeout = 60 - // incoming_buffer_size = 64k - // outgoing_buffer_size = 64k - // log_minimum_size = 1k - // session_minimum_size = 1k - // meta_size = 16k - // meta_scan = "[" - } -}