diff --git a/cmd/server/main.go b/cmd/server/main.go index 9096d62..e39c23d 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -368,6 +368,7 @@ func runroomsrv() error { var httpHandler http.Handler httpHandler = httpRateLimiter.RateLimit(webHandler) httpHandler = secureMiddleware.Handler(httpHandler) + httpHandler = roomsrv.Network.WebsockHandler(httpHandler) // all init was successfull level.Info(log).Log( diff --git a/go.mod b/go.mod index 3c88e8e..e7274a3 100644 --- a/go.mod +++ b/go.mod @@ -14,10 +14,9 @@ require ( github.com/gorilla/securecookie v1.1.1 github.com/gorilla/sessions v1.2.1 github.com/gorilla/websocket v1.4.2 - github.com/joefitzgerald/rainbow-reporter v0.1.0 // indirect github.com/keks/nocomment v0.0.0-20181007001506-30c6dcb4a472 github.com/mattn/go-sqlite3 v2.0.3+incompatible - github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0 // indirect + github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0 github.com/nicksnyder/go-i18n/v2 v2.1.2 github.com/pkg/errors v0.9.1 github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 @@ -30,7 +29,7 @@ require ( github.com/volatiletech/sqlboiler-sqlite3 v0.0.0-20210314195744-a1c697a68aef // indirect github.com/volatiletech/sqlboiler/v4 v4.5.0 github.com/volatiletech/strmangle v0.0.1 - go.cryptoscope.co/muxrpc/v2 v2.0.0-beta.1.0.20210308090127-5f1f5f9cbb59 + go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210414085324-530a7df6ea1c go.cryptoscope.co/netwrap v0.1.1 go.cryptoscope.co/secretstream v1.2.2 go.mindeco.de v1.11.0 @@ -39,7 +38,7 @@ require ( golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect golang.org/x/text v0.3.5 - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6 gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index 6b85041..04ddffd 100644 --- a/go.sum +++ b/go.sum @@ -225,7 +225,6 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -251,7 +250,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= @@ -304,6 +302,7 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nicksnyder/go-i18n/v2 v2.1.2 h1:QHYxcUJnGHBaq7XbvgunmZ2Pn0focXFqTD61CkH146c= github.com/nicksnyder/go-i18n/v2 v2.1.2/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= @@ -319,6 +318,7 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= @@ -382,7 +382,7 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= -github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= @@ -485,8 +485,18 @@ go.cryptoscope.co/margaret v0.0.5/go.mod h1:W+Q6lvzHIrF8+Yt3dItHHsx2R9/Xvj/NkJGM go.cryptoscope.co/margaret v0.0.8/go.mod h1:VbP0bqavqW5osTmdNvgukrqtmqvZC5sXyPSBUW5rzv8= go.cryptoscope.co/margaret v0.0.12-0.20190912103626-34323ad497f4 h1:gLSldWRujtUOfdnpA1XKD71xcCp3Wz1URMnT6xpUPV4= go.cryptoscope.co/margaret v0.0.12-0.20190912103626-34323ad497f4/go.mod h1:3rt+RmZTFZEgfvFxz0ZPDBIWtLJOouWtzV6YbBl6sek= -go.cryptoscope.co/muxrpc/v2 v2.0.0-beta.1.0.20210308090127-5f1f5f9cbb59 h1:Gv5pKkvHYJNc12uRZ/jMCsR17G7v6oFLLCrGAUVxhvo= -go.cryptoscope.co/muxrpc/v2 v2.0.0-beta.1.0.20210308090127-5f1f5f9cbb59/go.mod h1:MgaeojIkWY3lLuoNw1mlMT3b3jiZwOj/fgsoGZp/VNA= +go.cryptoscope.co/muxrpc/v2 v2.0.0 h1:Wboamtw7WZPlmEGnGHIPNrScw7HUasXycMmtz+HWXcQ= +go.cryptoscope.co/muxrpc/v2 v2.0.0/go.mod h1:MgaeojIkWY3lLuoNw1mlMT3b3jiZwOj/fgsoGZp/VNA= +go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210413143018-bd871b2ec7dd h1:XlPynETXT4tzlzEX8r/V3trDHUDk2ktGol4d3suyC2s= +go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210413143018-bd871b2ec7dd/go.mod h1:MgaeojIkWY3lLuoNw1mlMT3b3jiZwOj/fgsoGZp/VNA= +go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210414075132-c1a2adf3d317 h1:in3PvGIazNrexusC+IYXayQ5Ih3CdbWi/oLcArxfnyQ= +go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210414075132-c1a2adf3d317/go.mod h1:MgaeojIkWY3lLuoNw1mlMT3b3jiZwOj/fgsoGZp/VNA= +go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210414084152-08e3e6acc50d h1:PYOUNvgxKOeJLAF+s+uj31L9xf1xGhqPeHKpORANfTk= +go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210414084152-08e3e6acc50d/go.mod h1:MgaeojIkWY3lLuoNw1mlMT3b3jiZwOj/fgsoGZp/VNA= +go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210414085324-530a7df6ea1c h1:UQ2PCZ2VDKwvuWXtvyLkCXfdztJPr8tGhTnjsK36JGI= +go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210414085324-530a7df6ea1c/go.mod h1:MgaeojIkWY3lLuoNw1mlMT3b3jiZwOj/fgsoGZp/VNA= +go.cryptoscope.co/muxrpc/v2 v2.0.1 h1:U1dS1dsFk7/LygH8o2qsWy8dHB5g3YeLtm0lsN7c1CI= +go.cryptoscope.co/muxrpc/v2 v2.0.1/go.mod h1:MgaeojIkWY3lLuoNw1mlMT3b3jiZwOj/fgsoGZp/VNA= go.cryptoscope.co/netwrap v0.1.0/go.mod h1:7zcYswCa4CT+ct54e9uH9+IIbYYETEMHKDNpzl8Ukew= go.cryptoscope.co/netwrap v0.1.1 h1:JLzzGKEvrUrkKzu3iM0DhpHmt+L/gYqmpcf1lJMUyFs= go.cryptoscope.co/netwrap v0.1.1/go.mod h1:7zcYswCa4CT+ct54e9uH9+IIbYYETEMHKDNpzl8Ukew= @@ -495,10 +505,6 @@ go.cryptoscope.co/secretstream v1.2.2/go.mod h1:7nRGZ7fTqSgQAnv2Y4m8xQsS3MFxvB7I go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.mindeco.de v1.9.0 h1:/xli02DkzpIUZxp/rp1nj8z/OZ9MHvkMIr9TfDVcmBg= -go.mindeco.de v1.9.0/go.mod h1:ePOcyktbpqzhMPRBDv2gUaDd3h8QtT+DUU1DK+VbQZE= -go.mindeco.de v1.10.0 h1:H/bhL+dIgZZnUgBEDlKUJBisTszNiHDONeGZtGdiJJ0= -go.mindeco.de v1.10.0/go.mod h1:ePOcyktbpqzhMPRBDv2gUaDd3h8QtT+DUU1DK+VbQZE= go.mindeco.de v1.11.0 h1:zrNJFjRr7l+eoRywKxBetB1sgQwnrJ9NA65H/xeusTs= go.mindeco.de v1.11.0/go.mod h1:ePOcyktbpqzhMPRBDv2gUaDd3h8QtT+DUU1DK+VbQZE= go.mindeco.de/ssb-refs v0.1.1-0.20210108133850-cf1f44fea870 h1:TCI3AefMAaOYECvppn30+CfEB0Fn8IES1SKvvacc3/c= @@ -546,8 +552,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191116160921-f9c825593386 h1:ktbWvQrW08Txdxno1PiDpSxPXG6ndGsfnJjRRtkM0LQ= -golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201026091529-146b70c837a4 h1:awiuzyrRjJDb+OXi9ceHO3SDxVoN3JER57mhtqkdQBs= @@ -612,7 +616,6 @@ golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6 h1:rbvTkL9AkFts1cgI78+gG6Yu1pwaqX6hjSJAatB78E4= golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -649,8 +652,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= diff --git a/internal/network/interface.go b/internal/network/interface.go index 533fe98..9d28ea3 100644 --- a/internal/network/interface.go +++ b/internal/network/interface.go @@ -97,8 +97,11 @@ type Network interface { GetConnTracker() ConnTracker - // websock hack - HandleHTTP(handler http.Handler) + // WebsockHandler returns a "middleware" like thing that is able to upgrade a websocket request to a muxrpc connection and authenticate using shs. + // It calls the next handler if it fails to upgrade the connection to websocket. + // However, it will error on the request and not call the passed handler + // if the websocket upgrade is successfull. + WebsockHandler(next http.Handler) http.Handler io.Closer } diff --git a/internal/network/new.go b/internal/network/new.go index 3595d78..090b04f 100644 --- a/internal/network/new.go +++ b/internal/network/new.go @@ -43,8 +43,6 @@ type Options struct { // AfterSecureWrappers are applied afterwards, usefull to debug muxrpc content AfterSecureWrappers []netwrap.ConnWrapper - - WebsocketAddr string } type node struct { @@ -110,33 +108,6 @@ func New(opts Options) (Network, error) { n.log = opts.Logger - // local websocket - wsHandler := websockHandler(n) - httpHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - url := req.URL.String() - if url == "/" { - wsHandler(w, req) - return - } - // n.log.Log("http-url-req", url) - if n.httpHandler != nil { - n.httpHandler.ServeHTTP(w, req) - } - }) - - if addr := opts.WebsocketAddr; addr != "" { - n.httpLis, err = net.Listen("tcp", addr) - if err != nil { - return nil, err - } - - // TODO: move to serve - go func() { - err := http.Serve(n.httpLis, httpHandler) - level.Error(n.log).Log("conn", "ssb-ws :8998 listen exited", "err", err) - }() - } - return n, nil } diff --git a/internal/network/websocket.go b/internal/network/websocket.go index 593b7cd..b25dde6 100644 --- a/internal/network/websocket.go +++ b/internal/network/websocket.go @@ -10,128 +10,175 @@ import ( "net/http" "time" + kitlog "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/gorilla/websocket" "go.cryptoscope.co/muxrpc/v2" ) -func websockHandler(n *node) http.HandlerFunc { +// WebsockHandler returns a "middleware" like thing that is able to upgrade a websocket request to a muxrpc connection and authenticate using shs. +// It calls the next handler if it fails to upgrade the connection to websocket. +// However, it will error on the request and not call the passed handler +// if the websocket upgrade is successfull. +func (n *node) WebsockHandler(next http.Handler) http.Handler { var upgrader = websocket.Upgrader{ ReadBufferSize: 1024 * 4, WriteBufferSize: 1024 * 4, + CheckOrigin: func(_ *http.Request) bool { return true }, + + // 99% of the traffic will be ciphertext which is impossible to distingish from randomness and thus also hard to compress EnableCompression: false, + + // if upgrading fails, just call the next handler and ignore the error + Error: func(w http.ResponseWriter, req *http.Request, _ int, _ error) { + next.ServeHTTP(w, req) + }, } - return func(w http.ResponseWriter, req *http.Request) { - remoteAddr, err := net.ResolveTCPAddr("tcp", req.RemoteAddr) - if err != nil { - n.log.Log("warning", "failed wrap", "err", err, "remote", remoteAddr) - return - } - wsConn, err := upgrader.Upgrade(w, req, nil) - if err != nil { - n.log.Log("warning", "failed wrap", "err", err, "remote", remoteAddr) - return - } - var wc net.Conn - wc = &wrappedConn{ - remote: remoteAddr, - local: &net.TCPAddr{ - IP: nil, - Port: 8989, - }, - wsc: wsConn, - } + var wsh websocketHandelr + wsh.next = next + wsh.upgrader = &upgrader + wsh.muxnetwork = n - // comment out this block to get `noauth` instead of `shs` - // TODO: - // netwrap.WrapAddr(remoteAddr, secretstream.Addr{ - // PubKey: n.opts.KeyPair.Id.ID, - // }) - cw := n.secretServer.ConnWrapper() - wc, err = cw(wc) - if err != nil { - level.Error(n.log).Log("warning", "failed to crypt", "err", err, "remote", remoteAddr) - wsConn.Close() - return - } - - // debugging copy of all muxrpc frames - // can be handy for reversing applications - // wrapped, err := debug.WrapDump("webmux", cryptoConn) - // if err != nil { - // level.Error(n.log).Log("warning", "failed wrap", "err", err, "remote", remoteAddr) - // wsConn.Close() - // return - // } - - pkr := muxrpc.NewPacker(wc) - - h, err := n.opts.MakeHandler(wc) - if err != nil { - err = fmt.Errorf("unix sock make handler: %w", err) - level.Error(n.log).Log("warn", err) - wsConn.Close() - return - } - - edp := muxrpc.Handle(pkr, h, - muxrpc.WithContext(req.Context()), - muxrpc.WithRemoteAddr(wc.RemoteAddr())) - - srv := edp.(muxrpc.Server) - // TODO: bundle root and connection context - if err := srv.Serve(); err != nil { - level.Error(n.log).Log("conn", "serve exited", "err", err, "peer", remoteAddr) - } - wsConn.Close() - } + return wsh } -type wrappedConn struct { - remote net.Addr - local net.Addr +type websocketHandelr struct { + next http.Handler + muxnetwork *node + + upgrader *websocket.Upgrader +} + +func (wsh websocketHandelr) ServeHTTP(w http.ResponseWriter, req *http.Request) { + remoteAddrStr := req.Header.Get("X-Forwarded-For") + if remoteAddrStr == "" { + remoteAddrStr = req.RemoteAddr + } + + remoteAddr, err := net.ResolveTCPAddr("tcp", remoteAddrStr) + if err != nil { + wsh.next.ServeHTTP(w, req) + return + } + + wsConn, err := wsh.upgrader.Upgrade(w, req, nil) + if err != nil { + return + } + + errLog := level.Error(wsh.muxnetwork.log) + errLog = kitlog.With(errLog, "remote", remoteAddrStr) + + var wc net.Conn + wc = NewWebsockConn(wsConn) + + cw := wsh.muxnetwork.secretServer.ConnWrapper() + wc, err = cw(wc) + if err != nil { + errLog.Log("warning", "failed to authenticate", "err", err, "remote", remoteAddr) + wsConn.Close() + return + } + + // debugging copy of all muxrpc frames + // can be handy for reversing applications + // feed, err := GetFeedRefFromAddr(wc.RemoteAddr()) + // if err != nil { + // errLog.Log("warning", "failed to get feed after auth", "err", err, "remote", remoteAddr) + // wsConn.Close() + // return + // } + // dumpPath := filepath.Join("webmux", base64.URLEncoding.EncodeToString(feed.ID[:10]), remoteAddrStr) + // wc, err = debug.WrapDump(dumpPath, wc) + // if err != nil { + // errLog.Log("warning", "failed wrap", "err", err, "remote", remoteAddr) + // wsConn.Close() + // return + // } + + pkr := muxrpc.NewPacker(wc) + + h, err := wsh.muxnetwork.opts.MakeHandler(wc) + if err != nil { + err = fmt.Errorf("websocket make handler failed: %w", err) + errLog.Log("warn", err) + wsConn.Close() + return + } + + edp := muxrpc.Handle(pkr, h, + muxrpc.WithContext(req.Context()), + muxrpc.WithRemoteAddr(wc.RemoteAddr())) + + srv := edp.(muxrpc.Server) + if err := srv.Serve(); err != nil { + errLog.Log("conn", "serve exited", "err", err, "peer", remoteAddr) + } + wsConn.Close() +} + +// WebsockConn emulates a normal net.Conn from a websocket connection +type WebsockConn struct { r io.Reader wsc *websocket.Conn } -func (conn *wrappedConn) Read(data []byte) (int, error) { +func NewWebsockConn(wsc *websocket.Conn) *WebsockConn { + return &WebsockConn{ + wsc: wsc, + } +} + +func (conn *WebsockConn) Read(data []byte) (int, error) { if conn.r == nil { if err := conn.renewReader(); err != nil { return -1, err } } + n, err := conn.r.Read(data) if err == io.EOF { if err := conn.renewReader(); err != nil { return -1, err } - return conn.Read(data) + n, err = conn.Read(data) + if err != nil { + err = fmt.Errorf("wsConn: failed to read after renew(): %w", err) + return -1, err + } + return n, nil } - return n, err + if err != nil { + return -1, fmt.Errorf("wsConn: read failed: %w", err) + } + + return n, nil } -func (wc *wrappedConn) renewReader() error { - mt, r, err := wc.wsc.NextReader() +func (conn *WebsockConn) renewReader() error { + mt, r, err := conn.wsc.NextReader() if err != nil { - return fmt.Errorf("wsConn: failed to get reader: %w", err) + err = fmt.Errorf("wsConn: failed to get reader: %w", err) + return err } if mt != websocket.BinaryMessage { return fmt.Errorf("wsConn: not binary message: %v", mt) } - wc.r = r + + conn.r = r return nil } -func (conn wrappedConn) Write(data []byte) (int, error) { +func (conn *WebsockConn) Write(data []byte) (int, error) { writeCloser, err := conn.wsc.NextWriter(websocket.BinaryMessage) if err != nil { return -1, fmt.Errorf("wsConn: failed to create Reader: %w", err) @@ -141,21 +188,41 @@ func (conn wrappedConn) Write(data []byte) (int, error) { if err != nil { return -1, fmt.Errorf("wsConn: failed to copy data: %w", err) } + return int(n), writeCloser.Close() } -func (conn wrappedConn) Close() error { +func (conn *WebsockConn) Close() error { return conn.wsc.Close() } -func (c wrappedConn) LocalAddr() net.Addr { return c.local } -func (c wrappedConn) RemoteAddr() net.Addr { return c.remote } -func (c wrappedConn) SetDeadline(t time.Time) error { - return nil // c.conn.SetDeadline(t) +func (conn *WebsockConn) LocalAddr() net.Addr { return conn.wsc.LocalAddr() } +func (conn *WebsockConn) RemoteAddr() net.Addr { return conn.wsc.RemoteAddr() } + +func (conn *WebsockConn) SetDeadline(t time.Time) error { + rErr := conn.wsc.SetReadDeadline(t) + wErr := conn.wsc.SetWriteDeadline(t) + + var err error + + if rErr != nil { + err = fmt.Errorf("websock conn: failed to set read deadline: %w", rErr) + } + + if wErr != nil { + wErr = fmt.Errorf("websock conn: failed to set read deadline: %w", wErr) + if err != nil { + err = fmt.Errorf("both faild: %w and %s", err, wErr) + } else { + err = wErr + } + } + + return err } -func (c wrappedConn) SetReadDeadline(t time.Time) error { - return nil // c.conn.SetReadDeadline(t) +func (conn *WebsockConn) SetReadDeadline(t time.Time) error { + return conn.wsc.SetReadDeadline(t) } -func (c wrappedConn) SetWriteDeadline(t time.Time) error { - return nil // c.conn.SetWriteDeadline(t) +func (conn *WebsockConn) SetWriteDeadline(t time.Time) error { + return conn.wsc.SetWriteDeadline(t) } diff --git a/muxrpc/handlers/tunnel/server/state.go b/muxrpc/handlers/tunnel/server/state.go index 2df8ad6..49559be 100644 --- a/muxrpc/handlers/tunnel/server/state.go +++ b/muxrpc/handlers/tunnel/server/state.go @@ -6,6 +6,7 @@ import ( "context" "encoding/json" "fmt" + "sync" "time" "github.com/ssb-ngi-pointer/go-ssb-room/internal/network" @@ -98,23 +99,28 @@ func (h *Handler) endpoints(ctx context.Context, req *muxrpc.Request, snk *muxrp } type updateForwarder struct { + mu sync.Mutex // only one caller to forwarder at a time snk *muxrpc.ByteSink enc *json.Encoder } -func newForwarder(snk *muxrpc.ByteSink) updateForwarder { +func newForwarder(snk *muxrpc.ByteSink) *updateForwarder { enc := json.NewEncoder(snk) snk.SetEncoding(muxrpc.TypeJSON) - return updateForwarder{ + return &updateForwarder{ snk: snk, enc: enc, } } -func (uf updateForwarder) Update(members []string) error { +func (uf *updateForwarder) Update(members []string) error { + uf.mu.Lock() + defer uf.mu.Unlock() return uf.enc.Encode(members) } -func (uf updateForwarder) Close() error { +func (uf *updateForwarder) Close() error { + uf.mu.Lock() + defer uf.mu.Unlock() return uf.snk.Close() } diff --git a/muxrpc/test/go/websocket_test.go b/muxrpc/test/go/websocket_test.go new file mode 100644 index 0000000..8b31c01 --- /dev/null +++ b/muxrpc/test/go/websocket_test.go @@ -0,0 +1,134 @@ +package go_test + +import ( + "context" + "encoding/base64" + "encoding/json" + "net" + "net/http" + "net/url" + "os" + "path/filepath" + "testing" + "time" + + "go.cryptoscope.co/muxrpc/v2/debug" + "go.mindeco.de/encodedTime" + + "github.com/gorilla/websocket" + "github.com/stretchr/testify/require" + "go.cryptoscope.co/muxrpc/v2" + "go.cryptoscope.co/secretstream" + + "github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/keys" + "github.com/ssb-ngi-pointer/go-ssb-room/internal/network" + "github.com/ssb-ngi-pointer/go-ssb-room/roomdb" +) + +func TestWebsocketDialing(t *testing.T) { + r := require.New(t) + + testPath := filepath.Join("testrun", t.Name()) + os.RemoveAll(testPath) + + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + t.Cleanup(cancel) + + // create the roomsrv + serverMembers, server := makeNamedTestBot(t, "server", nil) + + // open a TCP listener for HTTP + l, err := net.Listen("tcp4", "localhost:0") + r.NoError(err) + + var fh = failHandler{t: t} + + // serve the websocket handler + handler := server.Network.WebsockHandler(fh) + go http.Serve(l, handler) + + // create a fresh keypair for the client + client, err := keys.NewKeyPair(nil) + r.NoError(err) + + // add it as a memeber + memberID, err := serverMembers.Add(ctx, client.Feed, roomdb.RoleMember) + r.NoError(err) + t.Log("client member:", memberID) + + // construct the websocket http address + var wsURL url.URL + wsURL.Scheme = "ws" + wsURL.Host = l.Addr().String() + wsURL.Path = "/" + + // create a websocket connection + conn, resp, err := websocket.DefaultDialer.DialContext(ctx, wsURL.String(), nil) + r.NoError(err) + t.Log(resp.Status) + r.Equal(http.StatusSwitchingProtocols, resp.StatusCode) + + // default app key for the secret-handshake connection + ak, err := base64.StdEncoding.DecodeString("1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=") + r.NoError(err) + + // create a shs client to authenticate and encrypt the connection + clientSHS, err := secretstream.NewClient(client.Pair, ak) + r.NoError(err) + + // the returned wrapper: func(net.Conn) (net.Conn, error) + // returns a new connection that went through shs and does boxstream + connAuther := clientSHS.ConnWrapper(server.Whoami().PubKey()) + + // turn a websocket conn into a net.Conn + wrappedConn := network.NewWebsockConn(conn) + + authedConn, err := connAuther(wrappedConn) + r.NoError(err) + + var muxMock muxrpc.FakeHandler + + debugConn := debug.Dump(filepath.Join(testPath, "client"), authedConn) + pkr := muxrpc.NewPacker(debugConn) + + wsEndpoint := muxrpc.Handle(pkr, &muxMock, muxrpc.WithContext(ctx)) + + srv := wsEndpoint.(muxrpc.Server) + go func() { + err = srv.Serve() + r.NoError(err) + t.Log("mux server error:", err) + }() + + // check we are talking to a room + var yup bool + err = wsEndpoint.Async(ctx, &yup, muxrpc.TypeJSON, muxrpc.Method{"tunnel", "isRoom"}) + r.NoError(err) + r.True(yup, "server is not a room?") + + // open the gossip.ping channel + src, snk, err := wsEndpoint.Duplex(ctx, muxrpc.TypeJSON, muxrpc.Method{"gossip", "ping"}) + r.NoError(err) + + pingTS := encodedTime.NewMillisecs(time.Now().Unix()) + err = json.NewEncoder(snk).Encode(pingTS) + r.NoError(err) + + r.True(src.Next(ctx)) + pong, err := src.Bytes() + r.NoError(err) + + var pongTS encodedTime.Millisecs + err = json.Unmarshal(pong, &pongTS) + r.NoError(err) + r.False(time.Time(pongTS).IsZero()) +} + +type failHandler struct { + t *testing.T +} + +func (fh failHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + fh.t.Error("next handler called", req.URL.String()) +} diff --git a/roomsrv/init_network.go b/roomsrv/init_network.go index c99f7b1..883db6e 100644 --- a/roomsrv/init_network.go +++ b/roomsrv/init_network.go @@ -60,8 +60,6 @@ func (s *Server) initNetwork() error { ConnTracker: s.networkConnTracker, BefreCryptoWrappers: s.preSecureWrappers, AfterSecureWrappers: s.postSecureWrappers, - - WebsocketAddr: s.wsAddr, } var err error diff --git a/roomsrv/manifest.go b/roomsrv/manifest.go index 0e8e6e7..fb8a635 100644 --- a/roomsrv/manifest.go +++ b/roomsrv/manifest.go @@ -32,6 +32,10 @@ const manifest manifestHandler = ` "whoami":"async", + "gossip": { + "ping": "duplex" + }, + "room": { "registerAlias": "async", "revokeAlias": "async", diff --git a/roomstate/roomstate.go b/roomstate/roomstate.go index e6bc72a..ed0214d 100644 --- a/roomstate/roomstate.go +++ b/roomstate/roomstate.go @@ -110,7 +110,8 @@ func (m *Manager) Remove(who refs.FeedRef) { m.roomMu.Unlock() } -// AlreadyAdded returns true if the peer was already added to the room +// AlreadyAdded returns true if the peer was already added to the room. +// if it isn't it will be added. func (m *Manager) AlreadyAdded(who refs.FeedRef, edp muxrpc.Endpoint) bool { m.roomMu.Lock()