From 199eed0f181ebdd42769ad1f61990c7dd15ef574 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Tue, 20 Aug 2019 20:58:44 +0300 Subject: [PATCH 01/62] Update dependencies --- go.mod | 20 +++++++++++--------- go.sum | 53 +++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index 6ddf2ba..4f297c9 100644 --- a/go.mod +++ b/go.mod @@ -4,27 +4,29 @@ replace github.com/golang/lint => github.com/golang/lint v0.0.0-20190227174305-8 require ( github.com/OneOfOne/xxhash v1.2.5 // indirect - github.com/allegro/bigcache v1.2.0 + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect + github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 // indirect + github.com/allegro/bigcache v1.2.1 github.com/beevik/ntp v0.2.0 github.com/cespare/xxhash v1.1.0 github.com/dustin/go-humanize v1.0.0 github.com/gofrs/uuid v3.2.0+incompatible - github.com/juju/errors v0.0.0-20190207033735-e65537c515d7 + github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect - github.com/juju/testing v0.0.0-20190429233213-dfc56b8c09fc // indirect + github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 // indirect github.com/kr/pretty v0.1.0 // indirect github.com/pkg/errors v0.8.1 // indirect - github.com/prometheus/client_golang v0.9.4 + github.com/prometheus/client_golang v1.1.0 + github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/stretchr/testify v1.3.0 + github.com/stretchr/testify v1.4.0 go.uber.org/atomic v1.4.0 // indirect go.uber.org/multierr v1.1.0 // indirect go.uber.org/zap v1.10.0 - golang.org/x/net v0.0.0-20190607181551-461777fb6f67 // indirect - golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae // indirect + golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect + golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/alexcesaro/statsd.v2 v2.0.0 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect - gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect - gopkg.in/yaml.v2 v2.2.2 // indirect + gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect ) diff --git a/go.sum b/go.sum index f67bdde..5e49b8c 100644 --- a/go.sum +++ b/go.sum @@ -3,16 +3,22 @@ github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/allegro/bigcache v1.2.0 h1:qDaE0QoF29wKBb3+pXFrJFy1ihe5OT9OiXhg1t85SxM= -github.com/allegro/bigcache v1.2.0/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= +github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw= github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -22,6 +28,7 @@ github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -31,13 +38,18 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/juju/errors v0.0.0-20190207033735-e65537c515d7 h1:dMIPRDg6gi7CUp0Kj2+HxqJ5kTr1iAdzsXYIrLCNSmU= -github.com/juju/errors v0.0.0-20190207033735-e65537c515d7/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc= +github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI= github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= -github.com/juju/testing v0.0.0-20190429233213-dfc56b8c09fc h1:5xUWujf6ES9tEpFHFzI34vcHm8U07lGjxAuJML3qwqM= -github.com/juju/testing v0.0.0-20190429233213-dfc56b8c09fc/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= +github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 h1:Pp8RxiF4rSoXP9SED26WCfNB28/dwTDpPXS8XMJR8rc= +github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -48,7 +60,9 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= @@ -58,18 +72,25 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.4 h1:Y8E/JaaPbmFSW2V81Ab/d8yZFYQQGbni1b1jPcG9Y6A= -github.com/prometheus/client_golang v0.9.4/go.mod h1:oCXIBxdI62A4cR6aTRJCgetEjecSIYzOEaeAn4iYEpM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d h1:GoAlyOgbOEIFdaDqxJVlbOQ1DtGmZWs/Qau0hIlk+WQ= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= @@ -80,6 +101,8 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= @@ -89,16 +112,18 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190607181551-461777fb6f67 h1:rJJxsykSlULwd2P2+pg/rtnwN2FrWp4IuCxOSyS0V00= -golang.org/x/net v0.0.0-20190607181551-461777fb6f67/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae h1:xiXzMMEQdQcric9hXtr1QU98MHunKK7OTtsoU6bYWs4= -golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -107,8 +132,8 @@ gopkg.in/alexcesaro/statsd.v2 v2.0.0/go.mod h1:i0ubccKGzBVNBpdGV5MocxyA/XlLUJzA7 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= From 09c7ce45d2472117abd54082c4314b6db97e4e75 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 26 Aug 2019 18:21:19 +0300 Subject: [PATCH 02/62] Add new config --- config2/config.go | 275 ++++++++++++++++++++++++++++++++++++++++++ config2/global_ips.go | 79 ++++++++++++ config2/urls.go | 86 +++++++++++++ 3 files changed, 440 insertions(+) create mode 100644 config2/config.go create mode 100644 config2/global_ips.go create mode 100644 config2/urls.go diff --git a/config2/config.go b/config2/config.go new file mode 100644 index 0000000..cab9372 --- /dev/null +++ b/config2/config.go @@ -0,0 +1,275 @@ +package config2 + +import ( + "bytes" + "context" + "encoding/json" + "net" + "strconv" + "sync" + "time" + + "github.com/juju/errors" + statsd "gopkg.in/alexcesaro/statsd.v2" +) + +type SecretType byte + +func (s SecretType) String() string { + switch s { + case SecretTypeMain: + return "main" + case SecretTypeSecured: + return "secured" + default: + return "tls" + } +} + +const ( + SecretTypeMain = 1 << iota + SecretTypeSecured + SecretTypeTLS +) + +const ( + FlagDebug = "debug" + FlagVerbose = "verbose" + + FlagBindIP = "bind-ip" + FlagBindPort = "bind-port" + FlagPublicIPv4 = "public-ipv4" + FlagPublicIPv4Port = "public-ipv4-port" + FlagPublicIPv6 = "public-ipv6" + FlagPublicIPv6Port = "public-ipv6-port" + FlagStatsIP = "stats-ip" + FlagStatsPort = "stats-port" + + FlagStatsdIP = "statsd-ip" + FlagStatsdPort = "statsd-port" + FlagStatsdNetwork = "statsd-network" + FlagStatsdPrefix = "statsd-prefix" + FlagStatsdTagsFormat = "statsd-tags-format" + FlagStatsdTags = "statsd-tags" + + FlagPrometheusPrefix = "prometheus-prefix" + + FlagWriteBufferSize = "write-buffer" + FlagReadBufferSize = "read-buffer" + + FlagSecureOnly = "secure-only" + + FlagAntiReplayMaxSize = "anti-replay-max-size" + FlagAntiReplayEvictionTime = "anti-replay-eviction-time" + + FlagSecret = "secret" + FlagAdtag = "adtag" +) + +type BufferSize struct { + Read int `json:"read"` + Write int `json:"write"` +} + +type AntiReplay struct { + MaxSize int `json:"max_size"` + EvictionTime time.Duration `json:"duration"` +} + +type Stats struct { + Prefix string `json:"prefix"` + Enabled bool `json:"enabled"` +} + +type StatsdStats struct { + Stats + + Addr Addr `json:"addr"` + Tags map[string]string `json:"tags"` + TagsFormat statsd.TagFormat `json:"format"` +} + +type PrometheusStats struct { + Stats +} + +type Addr struct { + IP net.IP `json:"ip"` + Port int `json:"port"` + net string +} + +func (a Addr) Network() string { + if a.net == "" { + return "tcp" + } + return a.net +} + +func (a Addr) String() string { + return net.JoinHostPort(a.IP.String(), strconv.Itoa(a.Port)) +} + +func (a Addr) MarshalJSON() ([]byte, error) { + data := map[string]string{ + "network": a.Network(), + "addr": a.String(), + } + return json.Marshal(data) +} + +type Config struct { + BufferSize BufferSize `json:"buffer_size"` + AntiReplay AntiReplay `json:"anti_replay"` + + ListenAddr Addr `json:"listen_addr"` + PublicIPv4Addr Addr `json:"public_ipv4_addr"` + PublicIPv6Addr Addr `json:"public_ipv6_addr"` + StatsAddr Addr `json:"stats_addr"` + + StatsdStats StatsdStats `json:"stats_statsd"` + PrometheusStats PrometheusStats `json:"stats_prometheus"` + + Debug bool `json:"debug"` + Verbose bool `json:"verbose"` + SecureOnly bool `json:"secure_only"` + SecretType SecretType `json:"secret_type"` + Secret []byte `json:"secret"` + AdTag []byte `json:"adtag"` +} + +func (c Config) String() string { + data, _ := json.Marshal(c) + return string(data) +} + +type ConfigOpt struct { + Name string + Value interface{} +} + +var C = Config{} + +func Init(options ...ConfigOpt) error { // nolint: gocyclo + for _, opt := range options { + switch opt.Name { + case FlagDebug: + C.Debug = opt.Value.(bool) + case FlagVerbose: + C.Verbose = opt.Value.(bool) + case FlagBindIP: + C.ListenAddr.IP = opt.Value.(net.IP) + case FlagBindPort: + C.ListenAddr.Port = opt.Value.(int) + case FlagPublicIPv4: + C.PublicIPv4Addr.IP = opt.Value.(net.IP) + case FlagPublicIPv4Port: + C.PublicIPv4Addr.Port = opt.Value.(int) + case FlagPublicIPv6: + C.PublicIPv6Addr.IP = opt.Value.(net.IP) + case FlagPublicIPv6Port: + C.PublicIPv6Addr.Port = opt.Value.(int) + case FlagStatsIP: + C.StatsAddr.IP = opt.Value.(net.IP) + case FlagStatsPort: + C.StatsAddr.Port = opt.Value.(int) + case FlagStatsdIP: + C.StatsdStats.Addr.IP = opt.Value.(net.IP) + case FlagStatsdPort: + C.StatsdStats.Addr.Port = opt.Value.(int) + case FlagStatsdNetwork: + C.StatsdStats.Addr.net = opt.Value.(string) + case FlagStatsdPrefix: + C.StatsdStats.Prefix = opt.Value.(string) + case FlagStatsdTagsFormat: + value := opt.Value.(string) + switch value { + case "datadog": + C.StatsdStats.TagsFormat = statsd.Datadog + case "influxdb": + C.StatsdStats.TagsFormat = statsd.InfluxDB + default: + return errors.Errorf("Incorrect statsd tag %s", value) + } + case FlagStatsdTags: + C.StatsdStats.Tags = opt.Value.(map[string]string) + case FlagPrometheusPrefix: + C.PrometheusStats.Prefix = opt.Value.(string) + case FlagWriteBufferSize: + C.BufferSize.Write = opt.Value.(int) + case FlagReadBufferSize: + C.BufferSize.Read = opt.Value.(int) + case FlagAntiReplayMaxSize: + C.AntiReplay.MaxSize = opt.Value.(int) + case FlagAntiReplayEvictionTime: + C.AntiReplay.EvictionTime = opt.Value.(time.Duration) + case FlagSecureOnly: + C.SecureOnly = opt.Value.(bool) + case FlagSecret: + C.Secret = opt.Value.([]byte) + case FlagAdtag: + C.AdTag = opt.Value.([]byte) + } + } + + var defaultStatsdTags statsd.TagFormat + if C.StatsdStats.TagsFormat == defaultStatsdTags { + C.StatsdStats.TagsFormat = statsd.Datadog + } + if C.StatsdStats.Addr.net == "" { + C.StatsdStats.Addr.net = "udp" + } + + switch { + case len(C.Secret) == 17 && bytes.HasPrefix(C.Secret, []byte{0xdd}): + C.SecretType = SecretTypeSecured + C.Secret = bytes.TrimPrefix(C.Secret, []byte{0xdd}) + case len(C.Secret) == 16: + C.SecretType = SecretTypeMain + default: + return errors.New("Incorrect secret") + } + + return nil +} + +func InitPublicAddress() error { + if C.PublicIPv4Addr.Port == 0 { + C.PublicIPv4Addr.Port = C.ListenAddr.Port + } + if C.PublicIPv6Addr.Port == 0 { + C.PublicIPv6Addr.Port = C.ListenAddr.Port + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + wg := &sync.WaitGroup{} + done := make(chan struct{}) + + if C.PublicIPv4Addr.IP == nil { + wg.Add(1) + go func() { + getGlobalIPv4(ctx, cancel) + wg.Done() + }() + } + if C.PublicIPv6Addr.IP == nil { + wg.Add(1) + go func() { + getGlobalIPv6(ctx, cancel) + wg.Done() + + }() + } + go func() { + wg.Wait() + close(done) + }() + + select { + case <-done: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} diff --git a/config2/global_ips.go b/config2/global_ips.go new file mode 100644 index 0000000..98ccc51 --- /dev/null +++ b/config2/global_ips.go @@ -0,0 +1,79 @@ +package config2 + +import ( + "context" + "io" + "io/ioutil" + "net" + "net/http" + "strings" + "time" + + "github.com/juju/errors" + "go.uber.org/zap" +) + +const ( + ifconfigAddress = "https://ifconfig.co/ip" + ifconfigTimeout = 10 * time.Second +) + +func getGlobalIPv4(ctx context.Context, cancel context.CancelFunc) { + ip, err := fetchIP(ctx, "tcp4") + if err != nil || ip.To4() == nil { + cancel() + zap.S().Errorw("Cannot find public ipv4 address", "error", err) + return + } + C.PublicIPv4Addr.IP = ip +} + +func getGlobalIPv6(ctx context.Context, cancel context.CancelFunc) { + ip, err := fetchIP(ctx, "tcp6") + if err != nil || ip.To4() != nil { + cancel() + zap.S().Errorw("Cannot find public ipv6 address", "error", err) + return + } + C.PublicIPv6Addr.IP = ip +} + +func fetchIP(ctx context.Context, network string) (net.IP, error) { + dialer := &net.Dialer{FallbackDelay: -1} + client := &http.Client{ + Jar: nil, + Timeout: ifconfigTimeout, + Transport: &http.Transport{ + DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) { + return dialer.DialContext(ctx, network, addr) + }, + }, + } + + req, err := http.NewRequest("GET", ifconfigAddress, nil) + if err != nil { + panic(err) + } + + resp, err := client.Do(req.WithContext(ctx)) + if err != nil { + if resp.Body != nil { + io.Copy(ioutil.Discard, resp.Body) // nolint: errcheck + } + return nil, errors.Annotate(err, "Cannot perform a request") + } + defer resp.Body.Close() // nolint: errcheck + + respDataBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, errors.Annotate(err, "Cannot read response body") + } + respData := strings.TrimSpace(string(respDataBytes)) + + ip := net.ParseIP(respData) + if ip == nil { + return nil, errors.Errorf("ifconfig.co returns incorrect IP %s", respData) + } + + return ip, nil +} diff --git a/config2/urls.go b/config2/urls.go new file mode 100644 index 0000000..23c8a93 --- /dev/null +++ b/config2/urls.go @@ -0,0 +1,86 @@ +package config2 + +import ( + "encoding/hex" + "net/url" +) + +type URLs struct { + TG string `json:"tg_url"` + TMe string `json:"tme_url"` + TGQRCode string `json:"tg_qrcode"` + TMeQRCode string `json:"tme_qrcode"` +} + +type IPURLs struct { + IPv4 URLs `json:"ipv4"` + IPv6 URLs `json:"ipv6"` + BotSecret string `json:"secret_for_mtproxybot"` +} + +func GetURLs() (urls IPURLs) { + secret := "" + switch C.SecretType { + case SecretTypeMain, SecretTypeSecured: + secret = hex.EncodeToString(C.Secret) + if C.SecureOnly { + secret = "dd" + secret + } + } + + urls.IPv4 = makeURLs(&C.PublicIPv4Addr, secret) + urls.IPv6 = makeURLs(&C.PublicIPv6Addr, secret) + urls.BotSecret = secret + + return urls +} + +func makeURLs(addr *Addr, secret string) (urls URLs) { + values := url.Values{} + values.Set("address", addr.String()) + values.Set("secret", secret) + + urls.TG = makeTGURL(values) + urls.TMe = makeTMeURL(values) + urls.TGQRCode = makeQRCodeURL(urls.TG) + urls.TMeQRCode = makeQRCodeURL(urls.TG) + + return +} + +func makeTGURL(values url.Values) string { + tgURL := url.URL{ + Scheme: "tg", + Host: "proxy", + RawQuery: values.Encode(), + } + + return tgURL.String() +} + +func makeTMeURL(values url.Values) string { + tMeURL := url.URL{ + Scheme: "https", + Host: "t.me", + Path: "proxy", + RawQuery: values.Encode(), + } + + return tMeURL.String() +} + +func makeQRCodeURL(data string) string { + qr := url.URL{ + Scheme: "https", + Host: "api.qrserver.com", + Path: "v1/create-qr-code", + } + + values := url.Values{} + values.Set("qzone", "4") + values.Set("format", "svg") + values.Set("data", data) + qr.RawQuery = values.Encode() + + return qr.String() +} From 07985cf418c3b6d7e0b59de2d29e7e927bedf401 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 29 Aug 2019 13:13:05 +0300 Subject: [PATCH 03/62] Reworked base --- go.mod | 2 + main.go | 202 ++++++++++--------------- newantireplay/cache.go | 32 ++++ newantireplay/hasher.go | 9 ++ newcli/generate.go | 25 ++++ newcli/proxy.go | 61 ++++++++ newcli/utils.go | 39 +++++ {config2 => newconfig}/config.go | 213 ++++++++++++--------------- {config2 => newconfig}/global_ips.go | 34 ++--- {config2 => newconfig}/urls.go | 11 +- newobfuscated2/client_protocol.go | 95 ++++++++++++ newobfuscated2/frame.go | 54 +++++++ newobfuscated2/telegram_protocol.go | 61 ++++++++ newprotocol/base_protocol.go | 7 + newprotocol/connection_protocol.go | 19 +++ newprotocol/connection_type.go | 27 ++++ newproxy/proxy.go | 1 + newstats/stats.go | 93 ++++++++++++ newstats/stats_json.go | 127 ++++++++++++++++ newstats/stats_prometheus.go | 110 ++++++++++++++ newstats/stats_statsd.go | 87 +++++++++++ newwrappers/interfaces.go | 117 +++++++++++++++ newwrappers/wrapper_conn.go | 178 ++++++++++++++++++++++ newwrappers/wrapper_obfuscated2.go | 80 ++++++++++ newwrappers/wrapper_stats.go | 66 +++++++++ ntp/ntp.go | 2 +- 26 files changed, 1481 insertions(+), 271 deletions(-) create mode 100644 newantireplay/cache.go create mode 100644 newantireplay/hasher.go create mode 100644 newcli/generate.go create mode 100644 newcli/proxy.go create mode 100644 newcli/utils.go rename {config2 => newconfig}/config.go (51%) rename {config2 => newconfig}/global_ips.go (61%) rename {config2 => newconfig}/urls.go (91%) create mode 100644 newobfuscated2/client_protocol.go create mode 100644 newobfuscated2/frame.go create mode 100644 newobfuscated2/telegram_protocol.go create mode 100644 newprotocol/base_protocol.go create mode 100644 newprotocol/connection_protocol.go create mode 100644 newprotocol/connection_type.go create mode 100644 newproxy/proxy.go create mode 100644 newstats/stats.go create mode 100644 newstats/stats_json.go create mode 100644 newstats/stats_prometheus.go create mode 100644 newstats/stats_statsd.go create mode 100644 newwrappers/interfaces.go create mode 100644 newwrappers/wrapper_conn.go create mode 100644 newwrappers/wrapper_obfuscated2.go create mode 100644 newwrappers/wrapper_stats.go diff --git a/go.mod b/go.mod index 4f297c9..92856ed 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,8 @@ require ( github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 // indirect github.com/kr/pretty v0.1.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pkg/errors v0.8.1 // indirect github.com/prometheus/client_golang v1.1.0 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect diff --git a/main.go b/main.go index cf644ac..022d1ff 100644 --- a/main.go +++ b/main.go @@ -1,23 +1,16 @@ package main import ( - "encoding/json" - "fmt" - "io" "math/rand" "os" "syscall" "time" "github.com/juju/errors" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" kingpin "gopkg.in/alecthomas/kingpin.v2" - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/ntp" - "github.com/9seconds/mtg/proxy" - "github.com/9seconds/mtg/stats" + "github.com/9seconds/mtg/newcli" + "github.com/9seconds/mtg/newconfig" ) var version = "dev" // this has to be set by build ld flags @@ -25,201 +18,172 @@ var version = "dev" // this has to be set by build ld flags var ( app = kingpin.New("mtg", "Simple MTPROTO proxy.") - debug = app.Flag("debug", + generateSecretCommand = app.Command("generate-secret", + "Generate new secret") + generateSecretType = generateSecretCommand.Arg("type", + "A type of secret to generate. Valid options are 'simple', 'secured' and 'tls'"). + Required(). + Enum("simple", "secured", "tls") + + proxyCommand = app.Command("proxy", + "Run new proxy instance") + proxyDebug = proxyCommand.Flag("debug", "Run in debug mode."). Short('d'). Envar("MTG_DEBUG"). Bool() - verbose = app.Flag("verbose", + proxyVerbose = proxyCommand.Flag("verbose", "Run in verbose mode."). Short('v'). Envar("MTG_VERBOSE"). Bool() - - bindIP = app.Flag("bind-ip", + proxyBindIP = proxyCommand.Flag("bind-ip", "Which IP to bind to."). Short('b'). Envar("MTG_IP"). Default("127.0.0.1"). IP() - bindPort = app.Flag("bind-port", + proxyBindPort = proxyCommand.Flag("bind-port", "Which port to bind to."). Short('p'). Envar("MTG_PORT"). Default("3128"). Uint16() - - publicIPv4 = app.Flag("public-ipv4", + proxyPublicIPv4 = proxyCommand.Flag("public-ipv4", "Which IPv4 address is public."). Short('4'). Envar("MTG_IPV4"). IP() - publicIPv4Port = app.Flag("public-ipv4-port", + proxyPublicIPv4Port = proxyCommand.Flag("public-ipv4-port", "Which IPv4 port is public. Default is 'bind-port' value."). Envar("MTG_IPV4_PORT"). Uint16() - - publicIPv6 = app.Flag("public-ipv6", + proxyPublicIPv6 = proxyCommand.Flag("public-ipv6", "Which IPv6 address is public."). Short('6'). Envar("MTG_IPV6"). IP() - publicIPv6Port = app.Flag("public-ipv6-port", + proxyPublicIPv6Port = proxyCommand.Flag("public-ipv6-port", "Which IPv6 port is public. Default is 'bind-port' value."). Envar("MTG_IPV6_PORT"). Uint16() - - statsIP = app.Flag("stats-ip", + proxyStatsIP = proxyCommand.Flag("stats-ip", "Which IP bind stats server to."). Short('t'). Envar("MTG_STATS_IP"). Default("127.0.0.1"). IP() - statsPort = app.Flag("stats-port", + proxyStatsPort = proxyCommand.Flag("stats-port", "Which port bind stats to."). Short('q'). Envar("MTG_STATS_PORT"). Default("3129"). Uint16() - - statsdIP = app.Flag("statsd-ip", + proxyStatsdIP = proxyCommand.Flag("statsd-ip", "Which IP should we use for working with statsd."). Envar("MTG_STATSD_IP"). - String() - statsdPort = app.Flag("statsd-port", + IP() + proxyStatsdPort = proxyCommand.Flag("statsd-port", "Which port should we use for working with statsd."). Envar("MTG_STATSD_PORT"). Default("8125"). Uint16() - statsdNetwork = app.Flag("statsd-network", + proxyStatsdNetwork = proxyCommand.Flag("statsd-network", "Which network is used to work with statsd. Only 'tcp' and 'udp' are supported."). Envar("MTG_STATSD_NETWORK"). Default("udp"). - String() - statsdPrefix = app.Flag("statsd-prefix", + Enum("udp", "tcp") + proxyStatsdPrefix = proxyCommand.Flag("statsd-prefix", "Which bucket prefix should we use for sending stats to statsd."). Envar("MTG_STATSD_PREFIX"). Default("mtg"). String() - statsdTagsFormat = app.Flag("statsd-tags-format", + proxyStatsdTagsFormat = proxyCommand.Flag("statsd-tags-format", "Which tag format should we use to send stats metrics. Valid options are 'datadog' and 'influxdb'."). Envar("MTG_STATSD_TAGS_FORMAT"). - String() - statsdTags = app.Flag("statsd-tags", + Default("influxdb"). + Enum("datadog", "influxdb") + proxyStatsdTags = proxyCommand.Flag("statsd-tags", "Tags to use for working with statsd (specified as 'key=value')."). Envar("MTG_STATSD_TAGS"). StringMap() - - prometheusPrefix = app.Flag("prometheus-prefix", + proxyPrometheusPrefix = proxyCommand.Flag("prometheus-prefix", "Which namespace to use to send stats to Prometheus."). Envar("MTG_PROMETHEUS_PREFIX"). Default("mtg"). String() - - writeBufferSize = app.Flag("write-buffer", + proxyWriteBufferSize = proxyCommand.Flag("write-buffer", "Write buffer size in bytes. You can think about it as a buffer from client to Telegram."). Short('w'). Envar("MTG_BUFFER_WRITE"). Default("65536"). Uint32() - readBufferSize = app.Flag("read-buffer", + proxyReadBufferSize = proxyCommand.Flag("read-buffer", "Read buffer size in bytes. You can think about it as a buffer from Telegram to client."). Short('r'). Envar("MTG_BUFFER_READ"). Default("131072"). Uint32() - secureOnly = app.Flag("secure-only", - "Support clients with dd-secrets only."). - Short('s'). - Envar("MTG_SECURE_ONLY"). - Bool() - - antiReplayMaxSize = app.Flag("anti-replay-max-size", + proxyAntiReplayMaxSize = proxyCommand.Flag("anti-replay-max-size", "Max size of antireplay cache in megabytes."). Envar("MTG_ANTIREPLAY_MAXSIZE"). Default("128"). Int() - antiReplayEvictionTime = app.Flag("anti-replay-eviction-time", + proxyAntiReplayEvictionTime = proxyCommand.Flag("anti-replay-eviction-time", "Eviction time period for obfuscated2 handshakes"). Envar("MTG_ANTIREPLAY_EVICTIONTIME"). Default("168h"). Duration() - - secret = app.Arg("secret", "Secret of this proxy.").Required().HexBytes() - adtag = app.Arg("adtag", "ADTag of the proxy.").HexBytes() + proxySecret = proxyCommand.Arg("secret", "Secret of this proxy.").Required().HexBytes() + proxyAdtag = proxyCommand.Arg("adtag", "ADTag of the proxy.").HexBytes() ) -func main() { // nolint: gocyclo +func main() { rand.Seed(time.Now().UTC().UnixNano()) app.Version(version) app.HelpFlag.Short('h') - kingpin.MustParse(app.Parse(os.Args[1:])) - - err := setRLimit() - if err != nil { - usage(err.Error()) + if err := setRLimit(); err != nil { + newcli.Fatal(err.Error()) } - conf, err := config.NewConfig(*debug, *verbose, - *writeBufferSize, *readBufferSize, - *bindIP, *publicIPv4, *publicIPv6, *statsIP, - *bindPort, *publicIPv4Port, *publicIPv6Port, *statsPort, *statsdPort, - *statsdIP, *statsdNetwork, *statsdPrefix, *statsdTagsFormat, - *statsdTags, *prometheusPrefix, *secureOnly, - *antiReplayMaxSize, *antiReplayEvictionTime, - *secret, *adtag, - ) - if err != nil { - usage(err.Error()) - } + switch kingpin.MustParse(app.Parse(os.Args[1:])) { + case generateSecretCommand.FullCommand(): + newcli.Generate(*generateSecretType) - atom := zap.NewAtomicLevel() - switch { - case conf.Debug: - atom.SetLevel(zapcore.DebugLevel) - case conf.Verbose: - atom.SetLevel(zapcore.InfoLevel) - default: - atom.SetLevel(zapcore.ErrorLevel) - } - encoderCfg := zap.NewProductionEncoderConfig() - logger := zap.New(zapcore.NewCore( - zapcore.NewJSONEncoder(encoderCfg), - zapcore.Lock(os.Stderr), - atom, - )) - zap.ReplaceGlobals(logger) - defer logger.Sync() // nolint: errcheck - - printURLs(conf.GetURLs()) - zap.S().Debugw("Configuration", "config", conf) - - if conf.UseMiddleProxy() { - zap.S().Infow("Use middle proxy connection to Telegram") - if diff, err := ntp.Fetch(); err != nil { - zap.S().Warnw("Could not fetch time data from NTP") - } else { - if diff >= time.Second { - usage(fmt.Sprintf("You choose to use middle proxy but your clock drift (%s) "+ - "is bigger than 1 second. Please, sync your time", diff)) - } - go ntp.AutoUpdate() + case proxyCommand.FullCommand(): + err := newconfig.Init( + newconfig.ConfigOpt{Option: newconfig.OptionTypeDebug, Value: *proxyDebug}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeVerbose, Value: *proxyVerbose}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeBindIP, Value: *proxyBindIP}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeBindPort, Value: *proxyBindPort}, + newconfig.ConfigOpt{Option: newconfig.OptionTypePublicIPv4, Value: *proxyPublicIPv4}, + newconfig.ConfigOpt{Option: newconfig.OptionTypePublicIPv4Port, Value: *proxyPublicIPv4Port}, + newconfig.ConfigOpt{Option: newconfig.OptionTypePublicIPv6, Value: *proxyPublicIPv6}, + newconfig.ConfigOpt{Option: newconfig.OptionTypePublicIPv6Port, Value: *proxyPublicIPv6Port}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeStatsIP, Value: *proxyStatsIP}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeStatsPort, Value: *proxyStatsPort}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeStatsdIP, Value: *proxyStatsdIP}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeStatsdPort, Value: *proxyStatsdPort}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeStatsdNetwork, Value: *proxyStatsdNetwork}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeStatsdPrefix, Value: *proxyStatsdPrefix}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeStatsdTagsFormat, Value: *proxyStatsdTagsFormat}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeStatsdTags, Value: *proxyStatsdTags}, + newconfig.ConfigOpt{Option: newconfig.OptionTypePrometheusPrefix, Value: *proxyPrometheusPrefix}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeWriteBufferSize, Value: *proxyWriteBufferSize}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeReadBufferSize, Value: *proxyReadBufferSize}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeAntiReplayMaxSize, Value: *proxyAntiReplayMaxSize}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeAntiReplayEvictionTime, Value: *proxyAntiReplayEvictionTime}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeSecret, Value: *proxySecret}, + newconfig.ConfigOpt{Option: newconfig.OptionTypeAdtag, Value: *proxyAdtag}, + ) + if err != nil { + newcli.Fatal(err.Error()) } - } else { - zap.S().Infow("Use direct connection to Telegram") - } - if err := stats.Init(conf); err != nil { - panic(err) - } - - server, err := proxy.NewProxy(conf) - if err != nil { - panic(err) - } - if err := server.Serve(); err != nil { - zap.S().Fatalw("Server stopped", "error", err) + if err := newcli.Proxy(); err != nil { + newcli.Fatal(err.Error()) + } } } @@ -239,19 +203,3 @@ func setRLimit() (err error) { return } - -func printURLs(data interface{}) { - encoder := json.NewEncoder(os.Stdout) - encoder.SetEscapeHTML(false) - encoder.SetIndent("", " ") - - err := encoder.Encode(data) - if err != nil { - panic(err) - } -} - -func usage(msg string) { - io.WriteString(os.Stderr, msg+"\n") // nolint: errcheck, gosec - os.Exit(1) -} diff --git a/newantireplay/cache.go b/newantireplay/cache.go new file mode 100644 index 0000000..71f8d4d --- /dev/null +++ b/newantireplay/cache.go @@ -0,0 +1,32 @@ +package newantireplay + +import ( + "github.com/allegro/bigcache" + + "github.com/9seconds/mtg/newconfig" +) + +var cache *bigcache.BigCache + +func Add(data []byte) { + cache.Set(string(data), nil) +} + +func Has(data []byte) bool { + _, err := cache.Get(string(data)) + return err == nil +} + +func Init() { + c, err := bigcache.NewBigCache(bigcache.Config{ + Shards: 1024, + LifeWindow: newconfig.C.AntiReplay.EvictionTime, + Hasher: hasher{}, + HardMaxCacheSize: newconfig.C.AntiReplay.MaxSize, + }) + if err != nil { + panic(err) + } + + cache = c +} diff --git a/newantireplay/hasher.go b/newantireplay/hasher.go new file mode 100644 index 0000000..68cb1fd --- /dev/null +++ b/newantireplay/hasher.go @@ -0,0 +1,9 @@ +package newantireplay + +import "github.com/cespare/xxhash" + +type hasher struct{} + +func (h hasher) Sum64(value string) uint64 { + return xxhash.Sum64String(value) +} diff --git a/newcli/generate.go b/newcli/generate.go new file mode 100644 index 0000000..dcc544a --- /dev/null +++ b/newcli/generate.go @@ -0,0 +1,25 @@ +package newcli + +import ( + "crypto/rand" + "encoding/hex" + + "github.com/9seconds/mtg/newconfig" +) + +func Generate(secretType string) { + data := make([]byte, newconfig.SimpleSecretLength) + if _, err := rand.Read(data); err != nil { + panic(err) + } + secret := hex.EncodeToString(data) + + switch secretType { + case "simple": + PrintStdout(secret) + case "secured": + PrintStdout("dd" + secret) + default: + Fatal("Unknown secret type " + secret) + } +} diff --git a/newcli/proxy.go b/newcli/proxy.go new file mode 100644 index 0000000..ac9757b --- /dev/null +++ b/newcli/proxy.go @@ -0,0 +1,61 @@ +package newcli + +import ( + "os" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/9seconds/mtg/newconfig" + "github.com/9seconds/mtg/newstats" + "github.com/9seconds/mtg/ntp" +) + +func Proxy() error { + atom := zap.NewAtomicLevel() + switch { + case newconfig.C.Debug: + atom.SetLevel(zapcore.DebugLevel) + case newconfig.C.Verbose: + atom.SetLevel(zapcore.InfoLevel) + default: + atom.SetLevel(zapcore.ErrorLevel) + } + + encoderCfg := zap.NewProductionEncoderConfig() + logger := zap.New(zapcore.NewCore( + zapcore.NewJSONEncoder(encoderCfg), + zapcore.Lock(os.Stderr), + atom, + )) + zap.ReplaceGlobals(logger) + defer logger.Sync() // nolint: errcheck + + if err := newconfig.InitPublicAddress(); err != nil { + Fatal(err.Error()) + } + zap.S().Debugw("Configuration", "config", newconfig.C) + + if len(newconfig.C.AdTag) > 0 { + zap.S().Infow("Use middle proxy connection to Telegram") + diff, err := ntp.Fetch() + if err != nil { + Fatal("Cannot fetch time data from NTP") + } + if diff > time.Second { + Fatal("Your local time is skewed and drift is bigger than a second. Please sync your time.") + } + go ntp.AutoUpdate() + } else { + zap.S().Infow("Use direct connection to Telegram") + } + + PrintJSONStdout(newconfig.GetURLs()) + + if err := newstats.Init(); err != nil { + Fatal(err.Error()) + } + + return nil +} diff --git a/newcli/utils.go b/newcli/utils.go new file mode 100644 index 0000000..64f5234 --- /dev/null +++ b/newcli/utils.go @@ -0,0 +1,39 @@ +package newcli + +import ( + "encoding/json" + "fmt" + "io" + "os" +) + +func Fatal(args ...interface{}) { + PrintStderr(args...) + os.Exit(1) +} + +func PrintStderr(args ...interface{}) { + fmt.Fprintln(os.Stderr, args...) +} + +func PrintStdout(args ...interface{}) { + fmt.Println(args...) +} + +func PrintJSONStderr(data interface{}) { + printJSON(os.Stderr, data) +} + +func PrintJSONStdout(data interface{}) { + printJSON(os.Stdout, data) +} + +func printJSON(writer io.Writer, data interface{}) { + encoder := json.NewEncoder(writer) + encoder.SetEscapeHTML(false) + encoder.SetIndent("", " ") + + if err := encoder.Encode(data); err != nil { + panic(err) + } +} diff --git a/config2/config.go b/newconfig/config.go similarity index 51% rename from config2/config.go rename to newconfig/config.go index cab9372..b15de57 100644 --- a/config2/config.go +++ b/newconfig/config.go @@ -1,69 +1,68 @@ -package config2 +package newconfig import ( "bytes" - "context" "encoding/json" "net" "strconv" - "sync" "time" "github.com/juju/errors" + "go.uber.org/zap" statsd "gopkg.in/alexcesaro/statsd.v2" ) -type SecretType byte +type SecretMode uint8 -func (s SecretType) String() string { +func (s SecretMode) String() string { switch s { - case SecretTypeMain: - return "main" - case SecretTypeSecured: + case SecretModeSimple: + return "simple" + case SecretModeSecured: return "secured" - default: - return "tls" } + return "tls" } const ( - SecretTypeMain = 1 << iota - SecretTypeSecured - SecretTypeTLS + SecretModeSimple SecretMode = iota + SecretModeSecured + SecretModeTLS ) +const SimpleSecretLength = 16 + +type OptionType uint8 + const ( - FlagDebug = "debug" - FlagVerbose = "verbose" + OptionTypeDebug OptionType = iota + OptionTypeVerbose - FlagBindIP = "bind-ip" - FlagBindPort = "bind-port" - FlagPublicIPv4 = "public-ipv4" - FlagPublicIPv4Port = "public-ipv4-port" - FlagPublicIPv6 = "public-ipv6" - FlagPublicIPv6Port = "public-ipv6-port" - FlagStatsIP = "stats-ip" - FlagStatsPort = "stats-port" + OptionTypeBindIP + OptionTypeBindPort + OptionTypePublicIPv4 + OptionTypePublicIPv4Port + OptionTypePublicIPv6 + OptionTypePublicIPv6Port + OptionTypeStatsIP + OptionTypeStatsPort - FlagStatsdIP = "statsd-ip" - FlagStatsdPort = "statsd-port" - FlagStatsdNetwork = "statsd-network" - FlagStatsdPrefix = "statsd-prefix" - FlagStatsdTagsFormat = "statsd-tags-format" - FlagStatsdTags = "statsd-tags" + OptionTypeStatsdIP + OptionTypeStatsdPort + OptionTypeStatsdNetwork + OptionTypeStatsdPrefix + OptionTypeStatsdTagsFormat + OptionTypeStatsdTags + OptionTypePrometheusPrefix - FlagPrometheusPrefix = "prometheus-prefix" + OptionTypeWriteBufferSize + OptionTypeReadBufferSize - FlagWriteBufferSize = "write-buffer" - FlagReadBufferSize = "read-buffer" + OptionTypeAntiReplayMaxSize + OptionTypeAntiReplayEvictionTime - FlagSecureOnly = "secure-only" - - FlagAntiReplayMaxSize = "anti-replay-max-size" - FlagAntiReplayEvictionTime = "anti-replay-eviction-time" - - FlagSecret = "secret" - FlagAdtag = "adtag" + OptionTypeSecret + OptionTypeAdtag ) type BufferSize struct { @@ -77,8 +76,7 @@ type AntiReplay struct { } type Stats struct { - Prefix string `json:"prefix"` - Enabled bool `json:"enabled"` + Prefix string `json:"prefix"` } type StatsdStats struct { @@ -132,8 +130,7 @@ type Config struct { Debug bool `json:"debug"` Verbose bool `json:"verbose"` - SecureOnly bool `json:"secure_only"` - SecretType SecretType `json:"secret_type"` + SecretMode SecretMode `json:"secret_mode"` Secret []byte `json:"secret"` AdTag []byte `json:"adtag"` } @@ -144,44 +141,44 @@ func (c Config) String() string { } type ConfigOpt struct { - Name string - Value interface{} + Option OptionType + Value interface{} } var C = Config{} func Init(options ...ConfigOpt) error { // nolint: gocyclo for _, opt := range options { - switch opt.Name { - case FlagDebug: + switch opt.Option { + case OptionTypeDebug: C.Debug = opt.Value.(bool) - case FlagVerbose: + case OptionTypeVerbose: C.Verbose = opt.Value.(bool) - case FlagBindIP: + case OptionTypeBindIP: C.ListenAddr.IP = opt.Value.(net.IP) - case FlagBindPort: - C.ListenAddr.Port = opt.Value.(int) - case FlagPublicIPv4: + case OptionTypeBindPort: + C.ListenAddr.Port = int(opt.Value.(uint16)) + case OptionTypePublicIPv4: C.PublicIPv4Addr.IP = opt.Value.(net.IP) - case FlagPublicIPv4Port: - C.PublicIPv4Addr.Port = opt.Value.(int) - case FlagPublicIPv6: + case OptionTypePublicIPv4Port: + C.PublicIPv4Addr.Port = int(opt.Value.(uint16)) + case OptionTypePublicIPv6: C.PublicIPv6Addr.IP = opt.Value.(net.IP) - case FlagPublicIPv6Port: - C.PublicIPv6Addr.Port = opt.Value.(int) - case FlagStatsIP: + case OptionTypePublicIPv6Port: + C.PublicIPv6Addr.Port = int(opt.Value.(uint16)) + case OptionTypeStatsIP: C.StatsAddr.IP = opt.Value.(net.IP) - case FlagStatsPort: - C.StatsAddr.Port = opt.Value.(int) - case FlagStatsdIP: + case OptionTypeStatsPort: + C.StatsAddr.Port = int(opt.Value.(uint16)) + case OptionTypeStatsdIP: C.StatsdStats.Addr.IP = opt.Value.(net.IP) - case FlagStatsdPort: - C.StatsdStats.Addr.Port = opt.Value.(int) - case FlagStatsdNetwork: + case OptionTypeStatsdPort: + C.StatsdStats.Addr.Port = int(opt.Value.(uint16)) + case OptionTypeStatsdNetwork: C.StatsdStats.Addr.net = opt.Value.(string) - case FlagStatsdPrefix: + case OptionTypeStatsdPrefix: C.StatsdStats.Prefix = opt.Value.(string) - case FlagStatsdTagsFormat: + case OptionTypeStatsdTagsFormat: value := opt.Value.(string) switch value { case "datadog": @@ -191,41 +188,33 @@ func Init(options ...ConfigOpt) error { // nolint: gocyclo default: return errors.Errorf("Incorrect statsd tag %s", value) } - case FlagStatsdTags: + case OptionTypeStatsdTags: C.StatsdStats.Tags = opt.Value.(map[string]string) - case FlagPrometheusPrefix: + case OptionTypePrometheusPrefix: C.PrometheusStats.Prefix = opt.Value.(string) - case FlagWriteBufferSize: - C.BufferSize.Write = opt.Value.(int) - case FlagReadBufferSize: - C.BufferSize.Read = opt.Value.(int) - case FlagAntiReplayMaxSize: + case OptionTypeWriteBufferSize: + C.BufferSize.Write = int(opt.Value.(uint32)) + case OptionTypeReadBufferSize: + C.BufferSize.Read = int(opt.Value.(uint32)) + case OptionTypeAntiReplayMaxSize: C.AntiReplay.MaxSize = opt.Value.(int) - case FlagAntiReplayEvictionTime: + case OptionTypeAntiReplayEvictionTime: C.AntiReplay.EvictionTime = opt.Value.(time.Duration) - case FlagSecureOnly: - C.SecureOnly = opt.Value.(bool) - case FlagSecret: + case OptionTypeSecret: C.Secret = opt.Value.([]byte) - case FlagAdtag: + case OptionTypeAdtag: C.AdTag = opt.Value.([]byte) + default: + return errors.Errorf("Unknown tag %v", opt.Option) } } - var defaultStatsdTags statsd.TagFormat - if C.StatsdStats.TagsFormat == defaultStatsdTags { - C.StatsdStats.TagsFormat = statsd.Datadog - } - if C.StatsdStats.Addr.net == "" { - C.StatsdStats.Addr.net = "udp" - } - switch { - case len(C.Secret) == 17 && bytes.HasPrefix(C.Secret, []byte{0xdd}): - C.SecretType = SecretTypeSecured + case len(C.Secret) == 1+SimpleSecretLength && bytes.HasPrefix(C.Secret, []byte{0xdd}): + C.SecretMode = SecretModeSecured C.Secret = bytes.TrimPrefix(C.Secret, []byte{0xdd}) - case len(C.Secret) == 16: - C.SecretType = SecretTypeMain + case len(C.Secret) == SimpleSecretLength: + C.SecretMode = SecretModeSimple default: return errors.New("Incorrect secret") } @@ -241,35 +230,29 @@ func InitPublicAddress() error { C.PublicIPv6Addr.Port = C.ListenAddr.Port } - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - wg := &sync.WaitGroup{} - done := make(chan struct{}) - + foundAddress := C.PublicIPv4Addr.IP != nil || C.PublicIPv6Addr.IP != nil if C.PublicIPv4Addr.IP == nil { - wg.Add(1) - go func() { - getGlobalIPv4(ctx, cancel) - wg.Done() - }() + ip, err := getGlobalIPv4() + if err != nil { + zap.S().Warnw("Cannot resolve public address", "error", err) + } else { + C.PublicIPv4Addr.IP = ip + foundAddress = true + } } if C.PublicIPv6Addr.IP == nil { - wg.Add(1) - go func() { - getGlobalIPv6(ctx, cancel) - wg.Done() - - }() + ip, err := getGlobalIPv6() + if err != nil { + zap.S().Warnw("Cannot resolve public address", "error", err) + } else { + C.PublicIPv6Addr.IP = ip + foundAddress = true + } } - go func() { - wg.Wait() - close(done) - }() - select { - case <-done: - return nil - case <-ctx.Done(): - return ctx.Err() + if !foundAddress { + return errors.New("Cannot resolve any public address") } + + return nil } diff --git a/config2/global_ips.go b/newconfig/global_ips.go similarity index 61% rename from config2/global_ips.go rename to newconfig/global_ips.go index 98ccc51..dd21b6a 100644 --- a/config2/global_ips.go +++ b/newconfig/global_ips.go @@ -1,4 +1,4 @@ -package config2 +package newconfig import ( "context" @@ -10,7 +10,6 @@ import ( "time" "github.com/juju/errors" - "go.uber.org/zap" ) const ( @@ -18,27 +17,23 @@ const ( ifconfigTimeout = 10 * time.Second ) -func getGlobalIPv4(ctx context.Context, cancel context.CancelFunc) { - ip, err := fetchIP(ctx, "tcp4") +func getGlobalIPv4() (net.IP, error) { + ip, err := fetchIP("tcp4") if err != nil || ip.To4() == nil { - cancel() - zap.S().Errorw("Cannot find public ipv4 address", "error", err) - return + return nil, errors.Annotate(err, "Cannot find public ipv4 address") } - C.PublicIPv4Addr.IP = ip + return ip, nil } -func getGlobalIPv6(ctx context.Context, cancel context.CancelFunc) { - ip, err := fetchIP(ctx, "tcp6") +func getGlobalIPv6() (net.IP, error) { + ip, err := fetchIP("tcp6") if err != nil || ip.To4() != nil { - cancel() - zap.S().Errorw("Cannot find public ipv6 address", "error", err) - return + return nil, errors.Annotate(err, "Cannot find public ipv6 address") } - C.PublicIPv6Addr.IP = ip + return ip, nil } -func fetchIP(ctx context.Context, network string) (net.IP, error) { +func fetchIP(network string) (net.IP, error) { dialer := &net.Dialer{FallbackDelay: -1} client := &http.Client{ Jar: nil, @@ -50,14 +45,9 @@ func fetchIP(ctx context.Context, network string) (net.IP, error) { }, } - req, err := http.NewRequest("GET", ifconfigAddress, nil) + resp, err := client.Get(ifconfigAddress) if err != nil { - panic(err) - } - - resp, err := client.Do(req.WithContext(ctx)) - if err != nil { - if resp.Body != nil { + if resp != nil { io.Copy(ioutil.Discard, resp.Body) // nolint: errcheck } return nil, errors.Annotate(err, "Cannot perform a request") diff --git a/config2/urls.go b/newconfig/urls.go similarity index 91% rename from config2/urls.go rename to newconfig/urls.go index 23c8a93..ff589d6 100644 --- a/config2/urls.go +++ b/newconfig/urls.go @@ -1,4 +1,4 @@ -package config2 +package newconfig import ( "encoding/hex" @@ -20,12 +20,11 @@ type IPURLs struct { func GetURLs() (urls IPURLs) { secret := "" - switch C.SecretType { - case SecretTypeMain, SecretTypeSecured: + switch C.SecretMode { + case SecretModeSimple: secret = hex.EncodeToString(C.Secret) - if C.SecureOnly { - secret = "dd" + secret - } + case SecretModeSecured: + secret = "dd" + hex.EncodeToString(C.Secret) } urls.IPv4 = makeURLs(&C.PublicIPv4Addr, secret) diff --git a/newobfuscated2/client_protocol.go b/newobfuscated2/client_protocol.go new file mode 100644 index 0000000..b60ca7d --- /dev/null +++ b/newobfuscated2/client_protocol.go @@ -0,0 +1,95 @@ +package newobfuscated2 + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "encoding/binary" + "io" + "time" + + "github.com/juju/errors" + + "github.com/9seconds/mtg/newantireplay" + "github.com/9seconds/mtg/newconfig" + "github.com/9seconds/mtg/newprotocol" + "github.com/9seconds/mtg/newwrappers" +) + +const clientProtocolHandshakeTimeout = 10 * time.Second + +type ClientProtocol struct { + newprotocol.BaseProtocol +} + +func (c *ClientProtocol) Handshake(socket newwrappers.StreamReadWriteCloser) (newwrappers.StreamReadWriteCloser, error) { + fm, err := c.ReadFrame(socket) + if err != nil { + return nil, errors.Annotate(err, "Cannot make client handshake") + } + + decHasher := sha256.New() + decHasher.Write(fm.key()) // nolint: errcheck + decHasher.Write(newconfig.C.Secret) // nolint: errcheck + decryptor := makeStreamCipher(decHasher.Sum(nil), fm.iv()) + + invertedFrame := fm.invert() + encHasher := sha256.New() + encHasher.Write(invertedFrame.key()) // nolint: errcheck + encHasher.Write(newconfig.C.Secret) // nolint: errcheck + encryptor := makeStreamCipher(encHasher.Sum(nil), invertedFrame.iv()) + + decryptedFrame := frame{} + decryptor.XORKeyStream(decryptedFrame.bytes(), fm.bytes()) + + magic := decryptedFrame.magic() + switch { + case bytes.Equal(magic, newprotocol.ConnectionTagAbridged): + c.ConnectionType = newprotocol.ConnectionTypeAbridged + case bytes.Equal(magic, newprotocol.ConnectionTagIntermediate): + c.ConnectionType = newprotocol.ConnectionTypeIntermediate + case bytes.Equal(magic, newprotocol.ConnectionTagSecure): + c.ConnectionType = newprotocol.ConnectionTypeSecure + default: + return nil, errors.New("Unknown connection type") + } + + c.ConnectionProtocol = newprotocol.ConnectionProtocolIPv4 + if socket.LocalAddr().IP.To4() == nil { + c.ConnectionProtocol = newprotocol.ConnectionProtocolIPv6 + } + + buf := bytes.NewReader(decryptedFrame.dc()) + if err := binary.Read(buf, binary.LittleEndian, &c.DC); err != nil { + c.DC = 1 + } + + antiReplayKey := decryptedFrame.unique() + if newantireplay.Has(antiReplayKey) { + return nil, errors.New("Replay attack is detected") + } + newantireplay.Add(antiReplayKey) + + return newwrappers.NewObfuscated2(socket, encryptor, decryptor), nil +} + +func (c *ClientProtocol) ReadFrame(socket newwrappers.StreamReader) (fm frame, err error) { + if _, err := io.ReadFull(handshakeReader{socket}, fm.bytes()); err != nil { + err = errors.Annotate(err, "Cannot extract obfuscated2 frame") + } + return +} + +type handshakeReader struct { + parent newwrappers.StreamReader +} + +func (h handshakeReader) Read(p []byte) (int, error) { + return h.parent.ReadTimeout(p, clientProtocolHandshakeTimeout) +} + +func makeStreamCipher(key, iv []byte) cipher.Stream { + block, _ := aes.NewCipher(key) // nolint: gosec + return cipher.NewCTR(block, iv) +} diff --git a/newobfuscated2/frame.go b/newobfuscated2/frame.go new file mode 100644 index 0000000..fab7efc --- /dev/null +++ b/newobfuscated2/frame.go @@ -0,0 +1,54 @@ +package newobfuscated2 + +const ( + frameLenKey = 32 + frameLenIV = 16 + frameLenMagic = 4 + frameLenDC = 2 + + frameOffsetFirst = 8 + frameOffsetKey = frameOffsetFirst + frameLenKey + frameOffsetIV = frameOffsetKey + frameLenIV + frameOffsetMagic = frameOffsetIV + frameLenMagic + frameOffsetDC = frameOffsetMagic + frameLenDC + + frameLen = 64 +) + +// [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd] +type frame struct { + data [frameLen]byte +} + +func (f *frame) bytes() []byte { + return f.data[:] +} + +func (f *frame) key() []byte { + return f.data[frameOffsetFirst:frameOffsetKey] +} + +func (f *frame) iv() []byte { + return f.data[frameOffsetKey:frameOffsetIV] +} + +func (f *frame) magic() []byte { + return f.data[frameOffsetIV:frameOffsetMagic] +} + +func (f *frame) dc() []byte { + return f.data[frameOffsetMagic:frameOffsetDC] +} + +func (f *frame) unique() []byte { + return f.data[frameOffsetFirst:frameOffsetDC] +} + +func (f *frame) invert() (nf frame) { + nf = *f + for i := 0; i < frameLenKey+frameLenIV; i++ { + nf.data[frameOffsetFirst+i] = nf.data[frameOffsetIV-1-i] + } + + return +} diff --git a/newobfuscated2/telegram_protocol.go b/newobfuscated2/telegram_protocol.go new file mode 100644 index 0000000..4286bd0 --- /dev/null +++ b/newobfuscated2/telegram_protocol.go @@ -0,0 +1,61 @@ +package newobfuscated2 + +import ( + "crypto/rand" + + "github.com/juju/errors" + + "github.com/9seconds/mtg/newprotocol" + "github.com/9seconds/mtg/newwrappers" +) + +type TelegramProtocol struct { + newprotocol.BaseProtocol +} + +func (t *TelegramProtocol) Handshake(socketRaw newwrappers.Wrap, client *ClientProtocol) (newwrappers.StreamReadWriteCloser, error) { + socket := socketRaw.(newwrappers.StreamReadWriteCloser) + fm := generateFrame(client) + data := fm.bytes() + + encryptor := makeStreamCipher(fm.key(), fm.iv()) + decryptedFrame := fm.invert() + decryptor := makeStreamCipher(decryptedFrame.key(), decryptedFrame.iv()) + + copyFrame := make([]byte, frameLen) + copy(copyFrame[:frameOffsetIV], data[:frameOffsetIV]) + encryptor.XORKeyStream(data, data) + copy(data[:frameOffsetIV], copyFrame[:frameOffsetIV]) + + if _, err := socket.Write(data); err != nil { + return nil, errors.Annotate(err, "Cannot write handshate frame to Telegram") + } + + return newwrappers.NewObfuscated2(socket, encryptor, decryptor), nil +} + +func generateFrame(client *ClientProtocol) (fm frame) { + for { + data := fm.bytes() + if _, err := rand.Read(data); err != nil { + continue + } + if data[0] == 0xef { + continue + } + + val := (uint32(data[3]) << 24) | (uint32(data[2]) << 16) | (uint32(data[1]) << 8) | uint32(data[0]) + if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee { + continue + } + + val = (uint32(data[7]) << 24) | (uint32(data[6]) << 16) | (uint32(data[5]) << 8) | uint32(data[4]) + if val == 0x00000000 { + continue + } + + copy(fm.magic(), client.ConnectionType.Tag()) + + return + } +} diff --git a/newprotocol/base_protocol.go b/newprotocol/base_protocol.go new file mode 100644 index 0000000..5ea7345 --- /dev/null +++ b/newprotocol/base_protocol.go @@ -0,0 +1,7 @@ +package newprotocol + +type BaseProtocol struct { + ConnectionType ConnectionType + ConnectionProtocol ConnectionProtocol + DC int16 +} diff --git a/newprotocol/connection_protocol.go b/newprotocol/connection_protocol.go new file mode 100644 index 0000000..621c60c --- /dev/null +++ b/newprotocol/connection_protocol.go @@ -0,0 +1,19 @@ +package newprotocol + +type ConnectionProtocol uint8 + +func (c ConnectionProtocol) String() string { + switch c { + case ConnectionProtocolAny: + return "any" + case ConnectionProtocolIPv4: + return "ipv4" + } + return "ipv6" +} + +const ( + ConnectionProtocolIPv4 ConnectionProtocol = 1 + ConnectionProtocolIPv6 = ConnectionProtocolIPv4 << 1 + ConnectionProtocolAny = ConnectionProtocolIPv4 | ConnectionProtocolIPv6 +) diff --git a/newprotocol/connection_type.go b/newprotocol/connection_type.go new file mode 100644 index 0000000..374a5e1 --- /dev/null +++ b/newprotocol/connection_type.go @@ -0,0 +1,27 @@ +package newprotocol + +type ConnectionType uint8 + +const ( + ConnectionTypeUnknown ConnectionType = iota + ConnectionTypeAbridged + ConnectionTypeIntermediate + ConnectionTypeSecure +) + +var ( + ConnectionTagAbridged = []byte{0xef, 0xef, 0xef, 0xef} + ConnectionTagIntermediate = []byte{0xee, 0xee, 0xee, 0xee} + ConnectionTagSecure = []byte{0xdd, 0xdd, 0xdd, 0xdd} +) + +func (t ConnectionType) Tag() []byte { + switch t { + case ConnectionTypeAbridged: + return ConnectionTagAbridged + case ConnectionTypeIntermediate: + return ConnectionTagIntermediate + default: + return ConnectionTagSecure + } +} diff --git a/newproxy/proxy.go b/newproxy/proxy.go new file mode 100644 index 0000000..aa648ba --- /dev/null +++ b/newproxy/proxy.go @@ -0,0 +1 @@ +package newproxy diff --git a/newstats/stats.go b/newstats/stats.go new file mode 100644 index 0000000..8d83480 --- /dev/null +++ b/newstats/stats.go @@ -0,0 +1,93 @@ +package newstats + +import ( + "net" + "net/http" + + "github.com/juju/errors" + + "github.com/9seconds/mtg/newconfig" + "github.com/9seconds/mtg/newprotocol" +) + +type Stats interface { + IngressTraffic(int) + EgressTraffic(int) + ClientConnected(newprotocol.ConnectionType, *net.TCPAddr) + ClientDisconnected(newprotocol.ConnectionType, *net.TCPAddr) + Crash() + AntiReplayDetected() +} + +type multiStats []Stats + +func (m multiStats) IngressTraffic(traffic int) { + for i := range m { + go m[i].IngressTraffic(traffic) + } +} + +func (m multiStats) EgressTraffic(traffic int) { + for i := range m { + go m[i].EgressTraffic(traffic) + } +} + +func (m multiStats) ClientConnected(connectionType newprotocol.ConnectionType, addr *net.TCPAddr) { + for i := range m { + go m[i].ClientConnected(connectionType, addr) + } +} + +func (m multiStats) ClientDisconnected(connectionType newprotocol.ConnectionType, addr *net.TCPAddr) { + for i := range m { + go m[i].ClientDisconnected(connectionType, addr) + } +} + +func (m multiStats) Crash() { + for i := range m { + go m[i].Crash() + } +} + +func (m multiStats) AntiReplayDetected() { + for i := range m { + go m[i].AntiReplayDetected() + } +} + +var S Stats + +func Init() error { + mux := http.NewServeMux() + + instanceJSON := newStatsJSON(mux) + instancePrometheus, err := newStatsPrometheus(mux) + if err != nil { + return errors.Annotate(err, "Cannot initialize Prometheus") + } + + stats := []Stats{instanceJSON, instancePrometheus} + if newconfig.C.StatsdStats.Addr.IP != nil { + instanceStatsd, err := newStatsStatsd() + if err != nil { + return errors.Annotate(err, "Cannot initialize StatsD") + } + stats = append(stats, instanceStatsd) + } + + listener, err := net.Listen("tcp", newconfig.C.StatsAddr.String()) + if err != nil { + return errors.Annotate(err, "Cannot initialize stats server") + } + + srv := http.Server{ + Handler: mux, + } + go srv.Serve(listener) // nolint: errcheck + + S = multiStats(stats) + + return nil +} diff --git a/newstats/stats_json.go b/newstats/stats_json.go new file mode 100644 index 0000000..a47f964 --- /dev/null +++ b/newstats/stats_json.go @@ -0,0 +1,127 @@ +package newstats + +import ( + "encoding/json" + "net" + "net/http" + "sync/atomic" + "time" + + "go.uber.org/zap" + + "github.com/9seconds/mtg/newprotocol" +) + +type statsJSON struct { + Connections statsJSONConnections `json:"connections"` + Traffic statsJSONTraffic `json:"traffic"` + Uptime statsJSONUptime `json:"uptime"` + Crashes uint32 `json:"crashes"` + AntiReplays uint32 `json:"anti_replay_detected"` +} + +type statsBaseJSONConnections struct { + All statsJSONConnectionType `json:"all"` + Abridged statsJSONConnectionType `json:"abridged"` + Intermediate statsJSONConnectionType `json:"intermediate"` + Secured statsJSONConnectionType `json:"secured"` +} + +type statsJSONConnections struct { + statsBaseJSONConnections +} + +type statsJSONConnectionType struct { + IPv4 uint32 `json:"ipv4"` + IPv6 uint32 `json:"ipv6"` +} + +func (c statsJSONConnections) MarshalJSON() ([]byte, error) { + c.All.IPv4 = c.Abridged.IPv4 + c.Intermediate.IPv4 + c.Secured.IPv4 + c.All.IPv6 = c.Abridged.IPv6 + c.Intermediate.IPv6 + c.Secured.IPv6 + + return json.Marshal(c.statsBaseJSONConnections) +} + +type statsJSONTraffic struct { + Ingress uint64 `json:"ingress"` + Egress uint64 `json:"egress"` +} + +type statsJSONUptime time.Time + +func (s statsJSONUptime) MarshalJSON() ([]byte, error) { + return json.Marshal(time.Since(time.Time(s)).Seconds()) +} + +func (s *statsJSON) IngressTraffic(traffic int) { + atomic.AddUint64(&s.Traffic.Ingress, uint64(traffic)) +} + +func (s *statsJSON) EgressTraffic(traffic int) { + atomic.AddUint64(&s.Traffic.Egress, uint64(traffic)) +} + +func (s *statsJSON) ClientConnected(connectionType newprotocol.ConnectionType, addr *net.TCPAddr) { + s.changeConnections(connectionType, addr, 1) +} + +func (s *statsJSON) ClientDisconnected(connectionType newprotocol.ConnectionType, addr *net.TCPAddr) { + s.changeConnections(connectionType, addr, ^uint32(0)) +} + +func (s *statsJSON) changeConnections(connectionType newprotocol.ConnectionType, addr *net.TCPAddr, value uint32) { + var connections *statsJSONConnectionType + + switch connectionType { + case newprotocol.ConnectionTypeAbridged: + connections = &s.Connections.Abridged + case newprotocol.ConnectionTypeSecure: + connections = &s.Connections.Secured + default: + connections = &s.Connections.Intermediate + } + + if addr.IP.To4() == nil { + atomic.AddUint32(&connections.IPv4, value) + } else { + atomic.AddUint32(&connections.IPv6, value) + } +} + +func (s *statsJSON) Crash() { + atomic.AddUint32(&s.Crashes, 1) +} + +func (s *statsJSON) AntiReplayDetected() { + atomic.AddUint32(&s.AntiReplays, 1) +} + +func newStatsJSON(mux *http.ServeMux) Stats { + instance := &statsJSON{} + logger := zap.S().Named("stats") + + mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + first, err := json.Marshal(instance) + if err != nil { + logger.Errorw("Cannot encode json", "error", err) + http.Error(w, "Internal server error", http.StatusServiceUnavailable) + return + } + + interim := map[string]interface{}{} + if err := json.Unmarshal(first, &interim); err != nil { + panic(err) + } + + encoder := json.NewEncoder(w) + encoder.SetEscapeHTML(false) + encoder.SetIndent("", " ") + if err := encoder.Encode(interim); err != nil { + logger.Errorw("Cannot encode json", "error", err) + } + }) + + return instance +} diff --git a/newstats/stats_prometheus.go b/newstats/stats_prometheus.go new file mode 100644 index 0000000..592f189 --- /dev/null +++ b/newstats/stats_prometheus.go @@ -0,0 +1,110 @@ +package newstats + +import ( + "net" + "net/http" + + "github.com/juju/errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + + "github.com/9seconds/mtg/newconfig" + "github.com/9seconds/mtg/newprotocol" +) + +type statsPrometheus struct { + connections *prometheus.GaugeVec + traffic *prometheus.GaugeVec + crashes prometheus.Gauge + antiReplays prometheus.Gauge +} + +func (s *statsPrometheus) IngressTraffic(traffic int) { + s.traffic.WithLabelValues("ingress").Add(float64(traffic)) +} + +func (s *statsPrometheus) EgressTraffic(traffic int) { + s.traffic.WithLabelValues("egress").Add(float64(traffic)) +} + +func (s *statsPrometheus) ClientConnected(connectionType newprotocol.ConnectionType, addr *net.TCPAddr) { + s.changeConnections(connectionType, addr, 1.0) +} + +func (s *statsPrometheus) ClientDisconnected(connectionType newprotocol.ConnectionType, addr *net.TCPAddr) { + s.changeConnections(connectionType, addr, -1.0) +} + +func (s *statsPrometheus) changeConnections(connectionType newprotocol.ConnectionType, + addr *net.TCPAddr, + increment float64) { + var labels [2]string + + switch connectionType { + case newprotocol.ConnectionTypeAbridged: + labels[0] = "abridged" + case newprotocol.ConnectionTypeSecure: + labels[0] = "secured" + default: + labels[0] = "intermediate" + } + + labels[1] = "ipv4" + if addr.IP.To4() == nil { + labels[1] = "ipv6" + } + + s.connections.WithLabelValues(labels[:]...).Add(increment) +} + +func (s *statsPrometheus) Crash() { + s.crashes.Inc() +} + +func (s *statsPrometheus) AntiReplayDetected() { + s.antiReplays.Inc() +} + +func newStatsPrometheus(mux *http.ServeMux) (Stats, error) { + registry := prometheus.NewRegistry() + instance := &statsPrometheus{ + connections: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: newconfig.C.PrometheusStats.Prefix, + Name: "connections", + Help: "Current number of connections to the proxy.", + }, []string{"type", "protocol"}), + traffic: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: newconfig.C.PrometheusStats.Prefix, + Name: "traffic", + Help: "Traffic passed through the proxy in bytes.", + }, []string{"direction"}), + crashes: prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: newconfig.C.PrometheusStats.Prefix, + Name: "crashes", + Help: "How many crashes happened.", + }), + antiReplays: prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: newconfig.C.PrometheusStats.Prefix, + Name: "anti_replays", + Help: "How many anti replay attacks were prevented.", + }), + } + + if err := registry.Register(instance.connections); err != nil { + return nil, errors.Annotate(err, "Cannot register metrics for connections") + } + if err := registry.Register(instance.traffic); err != nil { + return nil, errors.Annotate(err, "Cannot register metrics for traffic") + } + if err := registry.Register(instance.crashes); err != nil { + return nil, errors.Annotate(err, "Cannot register metrics for crashes") + } + if err := registry.Register(instance.antiReplays); err != nil { + return nil, errors.Annotate(err, "Cannot register metrics for anti replays") + } + + handler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) + mux.Handle("/prometheus", handler) + + return instance, nil +} diff --git a/newstats/stats_statsd.go b/newstats/stats_statsd.go new file mode 100644 index 0000000..f977a5a --- /dev/null +++ b/newstats/stats_statsd.go @@ -0,0 +1,87 @@ +package newstats + +import ( + "net" + "strings" + + "gopkg.in/alexcesaro/statsd.v2" + + "github.com/9seconds/mtg/newconfig" + "github.com/9seconds/mtg/newprotocol" + "github.com/juju/errors" +) + +type statsStatsd struct { + client *statsd.Client +} + +func (s *statsStatsd) IngressTraffic(traffic int) { + s.client.Count("traffic.ingress", traffic) +} + +func (s *statsStatsd) EgressTraffic(traffic int) { + s.client.Count("traffic.egress", traffic) +} + +func (s *statsStatsd) ClientConnected(connectionType newprotocol.ConnectionType, addr *net.TCPAddr) { + s.changeConnections(connectionType, addr, 1) +} + +func (s *statsStatsd) ClientDisconnected(connectionType newprotocol.ConnectionType, addr *net.TCPAddr) { + s.changeConnections(connectionType, addr, -1) +} + +func (s *statsStatsd) changeConnections(connectionType newprotocol.ConnectionType, addr *net.TCPAddr, value int) { + var labels [3]string + + labels[0] = "connections" + switch connectionType { + case newprotocol.ConnectionTypeAbridged: + labels[1] = "abridged" + case newprotocol.ConnectionTypeSecure: + labels[1] = "secured" + default: + labels[1] = "intermediate" + } + + labels[2] = "ipv4" + if addr.IP.To4() == nil { + labels[2] = "ipv6" + } + + s.client.Count(strings.Join(labels[:], "."), value) +} + +func (s *statsStatsd) Crash() { + s.client.Increment("crashes") +} + +func (s *statsStatsd) AntiReplayDetected() { + s.client.Increment("anti_replays") +} + +func newStatsStatsd() (Stats, error) { + options := []statsd.Option{ + statsd.Prefix(newconfig.C.StatsdStats.Prefix), + statsd.Network(newconfig.C.StatsdStats.Addr.Network()), + statsd.Address(newconfig.C.StatsdStats.Addr.String()), + statsd.TagsFormat(newconfig.C.StatsdStats.TagsFormat), + } + + if len(newconfig.C.StatsdStats.Tags) > 0 { + tags := make([]string, len(newconfig.C.StatsdStats.Tags)*2) + for k, v := range newconfig.C.StatsdStats.Tags { + tags = append(tags, k, v) + } + options = append(options, statsd.Tags(tags...)) + } + + client, err := statsd.New(options...) + if err != nil { + return nil, errors.Annotate(err, "Cannot initialize a client") + } + + return &statsStatsd{ + client: client, + }, nil +} diff --git a/newwrappers/interfaces.go b/newwrappers/interfaces.go new file mode 100644 index 0000000..dbc9998 --- /dev/null +++ b/newwrappers/interfaces.go @@ -0,0 +1,117 @@ +package newwrappers + +import ( + "io" + "net" + "time" + + "go.uber.org/zap" +) + +type Packet []byte + +// Wrap is a base interface for all wrappers in this package. +type Wrap interface { + Conn() net.Conn + Logger() *zap.SugaredLogger + LocalAddr() *net.TCPAddr + RemoteAddr() *net.TCPAddr +} + +type BaseReaderWithTimeout interface { + ReadTimeout([]byte, time.Duration) (int, error) +} + +type BaseWriterWithTimeout interface { + WriteTimeout([]byte, time.Duration) (int, error) +} + +type BasePacketReader interface { + Read() (Packet, error) +} + +type BasePacketWriter interface { + Write(Packet) error +} + +type StreamReader interface { + Wrap + io.Reader + BaseReaderWithTimeout +} + +type StreamWriter interface { + Wrap + io.Writer + BaseWriterWithTimeout +} + +type StreamCloser interface { + Wrap + io.Closer +} + +type StreamReadCloser interface { + Wrap + io.ReadCloser + BaseReaderWithTimeout +} + +type StreamWriteCloser interface { + Wrap + io.WriteCloser + BaseWriterWithTimeout +} + +type StreamReadWriter interface { + Wrap + io.ReadWriter + BaseReaderWithTimeout +} + +type StreamReadWriteCloser interface { + Wrap + io.ReadWriteCloser + BaseReaderWithTimeout + BaseWriterWithTimeout +} + +type PacketReader interface { + Wrap + BasePacketReader +} + +type PacketWriter interface { + Wrap + BasePacketWriter +} + +type PacketCloser interface { + Wrap + io.Closer +} + +type PacketReadCloser interface { + Wrap + BasePacketReader + io.Closer +} + +type PacketWriteCloser interface { + Wrap + BasePacketWriter + io.Closer +} + +type PacketReadWriter interface { + Wrap + BasePacketWriter + BasePacketReader +} + +type PacketReadWriteCloser interface { + Wrap + BasePacketWriter + BasePacketReader + io.Closer +} diff --git a/newwrappers/wrapper_conn.go b/newwrappers/wrapper_conn.go new file mode 100644 index 0000000..7bd4b9d --- /dev/null +++ b/newwrappers/wrapper_conn.go @@ -0,0 +1,178 @@ +package newwrappers + +import ( + "context" + "crypto/rand" + "encoding/hex" + "net" + "time" + + "github.com/juju/errors" + "go.uber.org/zap" + + "github.com/9seconds/mtg/newconfig" +) + +const ConnIDLength = 8 + +type ConnID [ConnIDLength]byte + +func (c ConnID) String() string { + return hex.EncodeToString(c[:]) +} + +type connPurpose uint8 + +const ( + connPurposeClient connPurpose = 1 << iota + connPurposeTelegram +) + +const ( + connTimeoutRead = 2 * time.Minute + connTimeoutWrite = 2 * time.Minute +) + +type wrapperConn struct { + parent net.Conn + ctx context.Context + cancel context.CancelFunc + connID ConnID + logger *zap.SugaredLogger + localAddr *net.TCPAddr + remoteAddr *net.TCPAddr +} + +func (w *wrapperConn) WriteTimeout(p []byte, timeout time.Duration) (int, error) { + select { + case <-w.ctx.Done(): + w.Close() + return 0, errors.Annotate(w.ctx.Err(), "Cannot write because context was closed") + + default: + if err := w.parent.SetWriteDeadline(time.Now().Add(timeout)); err != nil { + w.Close() // nolint: gosec + return 0, errors.Annotate(err, "Cannot set write deadline to the socket") + } + + n, err := w.parent.Write(p) + w.logger.Debugw("Write to stream", "bytes", n, "error", err) + if err != nil { + w.Close() // nolint: gosec + } + + return n, err + } +} + +func (w *wrapperConn) Write(p []byte) (int, error) { + return w.WriteTimeout(p, connTimeoutWrite) +} + +func (w *wrapperConn) ReadTimeout(p []byte, timeout time.Duration) (int, error) { + select { + case <-w.ctx.Done(): + w.Close() + return 0, errors.Annotate(w.ctx.Err(), "Cannot read because context was closed") + + default: + if err := w.parent.SetReadDeadline(time.Now().Add(timeout)); err != nil { + w.Close() + return 0, errors.Annotate(err, "Cannot set read deadline to the socket") + } + + n, err := w.parent.Read(p) + w.logger.Debugw("Read from stream", "bytes", n, "error", err) + if err != nil { + w.Close() + } + + return n, err + } +} + +func (w *wrapperConn) Read(p []byte) (int, error) { + return w.ReadTimeout(p, connTimeoutRead) +} + +func (w *wrapperConn) Close() error { + w.logger.Debugw("Close connection") + w.cancel() + + return w.parent.Close() +} + +func (w *wrapperConn) Conn() net.Conn { + return w.parent +} + +func (w *wrapperConn) Logger() *zap.SugaredLogger { + return w.logger +} + +func (w *wrapperConn) LocalAddr() *net.TCPAddr { + return w.localAddr +} + +func (w *wrapperConn) RemoteAddr() *net.TCPAddr { + return w.remoteAddr +} + +func newConn(ctx context.Context, + cancel context.CancelFunc, + parent net.Conn, + connID ConnID, + purpose connPurpose) StreamReadWriteCloser { + localAddr := *parent.LocalAddr().(*net.TCPAddr) + + if parent.RemoteAddr().(*net.TCPAddr).IP.To4() != nil { + if newconfig.C.PublicIPv4Addr.IP != nil { + localAddr.IP = newconfig.C.PublicIPv4Addr.IP + } + } else if newconfig.C.PublicIPv6Addr.IP != nil { + localAddr.IP = newconfig.C.PublicIPv6Addr.IP + } + + logger := zap.S().With( + "local_address", localAddr, + "remote_address", parent.RemoteAddr(), + ).Named("conn") + + if purpose == connPurposeClient { + logger = logger.With("connection_id", connID.String()) + } + + return &wrapperConn{ + parent: parent, + ctx: ctx, + cancel: cancel, + connID: connID, + logger: logger, + remoteAddr: parent.RemoteAddr().(*net.TCPAddr), + localAddr: &localAddr, + } +} + +func NewClientConn(ctx context.Context, + cancel context.CancelFunc, + parent net.Conn, + connID ConnID) StreamReadWriteCloser { + return newConn(ctx, cancel, parent, connID, connPurposeClient) +} + +func NewTelegramConn(ctx context.Context, + cancel context.CancelFunc, + parent net.Conn, + connID ConnID) StreamReadWriteCloser { + return newConn(ctx, cancel, parent, connID, connPurposeTelegram) +} + +func NewConnID() ConnID { + var id ConnID + + if _, err := rand.Read(id[:]); err != nil { + panic(err) + } + + return id +} diff --git a/newwrappers/wrapper_obfuscated2.go b/newwrappers/wrapper_obfuscated2.go new file mode 100644 index 0000000..70122ae --- /dev/null +++ b/newwrappers/wrapper_obfuscated2.go @@ -0,0 +1,80 @@ +package newwrappers + +import ( + "crypto/cipher" + "net" + "time" + + "github.com/juju/errors" + "go.uber.org/zap" +) + +type wrapperObfuscated2 struct { + encryptor cipher.Stream + decryptor cipher.Stream + parent StreamReadWriteCloser +} + +func (w *wrapperObfuscated2) ReadTimeout(p []byte, timeout time.Duration) (int, error) { + n, err := w.parent.ReadTimeout(p, timeout) + if err != nil { + return 0, errors.Annotate(err, "Cannot read stream ciphered data") + } + w.decryptor.XORKeyStream(p, p[:n]) + + return n, nil +} + +func (w *wrapperObfuscated2) Read(p []byte) (int, error) { + n, err := w.parent.Read(p) + if err != nil { + return 0, errors.Annotate(err, "Cannot read stream ciphered data") + } + w.decryptor.XORKeyStream(p, p[:n]) + + return n, nil +} + +func (w *wrapperObfuscated2) WriteTimeout(p []byte, timeout time.Duration) (int, error) { + buf := make([]byte, len(p)) + copy(buf, p) + w.encryptor.XORKeyStream(buf, buf) + + return w.parent.WriteTimeout(buf, timeout) +} + +func (w *wrapperObfuscated2) Write(p []byte) (int, error) { + buf := make([]byte, len(p)) + copy(buf, p) + w.encryptor.XORKeyStream(buf, buf) + + return w.parent.Write(buf) +} + +func (w *wrapperObfuscated2) Conn() net.Conn { + return w.parent.Conn() +} + +func (w *wrapperObfuscated2) Logger() *zap.SugaredLogger { + return w.parent.Logger().Named("obfuscated2") +} + +func (w *wrapperObfuscated2) LocalAddr() *net.TCPAddr { + return w.parent.LocalAddr() +} + +func (w *wrapperObfuscated2) RemoteAddr() *net.TCPAddr { + return w.parent.RemoteAddr() +} + +func (w *wrapperObfuscated2) Close() error { + return w.parent.Close() +} + +func NewObfuscated2(socket StreamReadWriteCloser, encryptor, decryptor cipher.Stream) StreamReadWriteCloser { + return &wrapperObfuscated2{ + parent: socket, + encryptor: encryptor, + decryptor: decryptor, + } +} diff --git a/newwrappers/wrapper_stats.go b/newwrappers/wrapper_stats.go new file mode 100644 index 0000000..eaac6d5 --- /dev/null +++ b/newwrappers/wrapper_stats.go @@ -0,0 +1,66 @@ +package newwrappers + +import ( + "net" + "time" + + "go.uber.org/zap" + + "github.com/9seconds/mtg/newstats" +) + +type wrapperStats struct { + parent StreamReadWriteCloser +} + +func (w *wrapperStats) Write(p []byte) (int, error) { + n, err := w.parent.Write(p) + newstats.S.EgressTraffic(n) + + return n, err +} + +func (w *wrapperStats) WriteTimeout(p []byte, timeout time.Duration) (int, error) { + n, err := w.parent.WriteTimeout(p, timeout) + newstats.S.EgressTraffic(n) + + return n, err +} + +func (w *wrapperStats) Read(p []byte) (int, error) { + n, err := w.parent.Read(p) + newstats.S.IngressTraffic(n) + + return n, err +} + +func (w *wrapperStats) ReadTimeout(p []byte, timeout time.Duration) (int, error) { + n, err := w.parent.ReadTimeout(p, timeout) + newstats.S.IngressTraffic(n) + + return n, err +} + +func (w *wrapperStats) Conn() net.Conn { + return w.parent.Conn() +} + +func (w *wrapperStats) Logger() *zap.SugaredLogger { + return w.parent.Logger().Named("traffic") +} + +func (w *wrapperStats) LocalAddr() *net.TCPAddr { + return w.parent.LocalAddr() +} + +func (w *wrapperStats) RemoteAddr() *net.TCPAddr { + return w.parent.RemoteAddr() +} + +func (w *wrapperStats) Close() error { + return w.parent.Close() +} + +func NewTraffic(parent StreamReadWriteCloser) StreamReadWriteCloser { + return &wrapperStats{parent} +} diff --git a/ntp/ntp.go b/ntp/ntp.go index 5aee499..e89d7bf 100644 --- a/ntp/ntp.go +++ b/ntp/ntp.go @@ -11,7 +11,7 @@ import ( const autoUpdatePeriod = time.Minute -var ntpEndpoints = []string{ +var ntpEndpoints = [...]string{ "0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org", From 2492a47d0ac343df5714bdb024261ed912d3eda4 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Wed, 4 Sep 2019 10:19:01 +0300 Subject: [PATCH 04/62] Direct proxy works --- _antireplay/cache.go | 37 ++ {newantireplay => _antireplay}/hasher.go | 2 +- {client => _client}/client.go | 0 {client => _client}/direct.go | 0 {client => _client}/middle.go | 0 _config/config.go | 224 +++++++++ {newconfig => _config}/global_ips.go | 31 +- {newconfig => _config}/urls.go | 39 +- {mtproto => _mtproto}/connection_options.go | 0 .../rpc/handshake_request.go | 0 .../rpc/handshake_response.go | 0 {mtproto => _mtproto}/rpc/nonce_request.go | 0 {mtproto => _mtproto}/rpc/nonce_response.go | 0 {mtproto => _mtproto}/rpc/proxy_flags.go | 0 {mtproto => _mtproto}/rpc/proxy_request.go | 0 {mtproto => _mtproto}/rpc/rpc.go | 0 _obfuscated2/frame.go | 121 +++++ {obfuscated2 => _obfuscated2}/frame_test.go | 0 {obfuscated2 => _obfuscated2}/obfuscated2.go | 0 .../obfuscated2_test.go | 0 _proxy/proxy.go | 178 ++++++++ {stats => _stats}/channels.go | 0 {stats => _stats}/init.go | 0 {stats => _stats}/prometheus.go | 0 {stats => _stats}/server.go | 0 _stats/stats.go | 175 +++++++ {stats => _stats}/statsd.go | 0 {telegram => _telegram}/dialer.go | 0 _telegram/direct.go | 79 ++++ {telegram => _telegram}/middle.go | 0 {telegram => _telegram}/middle_caller.go | 0 {telegram => _telegram}/telegram.go | 0 {utils => _utils}/read_current_data.go | 0 _utils/reverse_bytes.go | 15 + _utils/uint24.go | 15 + {wrappers => _wrappers}/blockcipher.go | 0 {wrappers => _wrappers}/conn.go | 0 {wrappers => _wrappers}/mtproto_abridged.go | 0 {wrappers => _wrappers}/mtproto_cipher.go | 0 {wrappers => _wrappers}/mtproto_frame.go | 0 .../mtproto_intermediate.go | 0 .../mtproto_intermediate_secure.go | 0 {wrappers => _wrappers}/mtproto_proxy.go | 0 {wrappers => _wrappers}/streamcipher.go | 0 {wrappers => _wrappers}/streamcipher_pool.go | 0 {wrappers => _wrappers}/wrap.go | 0 antireplay/cache.go | 31 +- {newcli => cli}/generate.go | 6 +- cli/proxy.go | 86 ++++ {newcli => cli}/utils.go | 2 +- config/config.go | 426 ++++++++++-------- config/global_ips.go | 29 +- config/urls.go | 37 +- conntypes/dc.go | 5 + .../protocol.go | 2 +- .../connection_type.go => conntypes/type.go | 2 +- main.go | 62 +-- newantireplay/cache.go | 32 -- newcli/proxy.go | 61 --- newconfig/config.go | 258 ----------- newobfuscated2/client_protocol.go | 95 ---- newobfuscated2/frame.go | 54 --- newobfuscated2/telegram_protocol.go | 61 --- newprotocol/base_protocol.go | 7 - newproxy/proxy.go | 1 - newstats/stats.go | 93 ---- obfuscated2/client_protocol.go | 94 ++++ obfuscated2/frame.go | 123 ++--- obfuscated2/telegram_protocol.go | 78 ++++ protocol/base_protocol.go | 21 + protocol/interfaces.go | 22 + protocol/request.go | 18 + proxy/proxy.go | 200 +++----- stats/stats.go | 212 +++------ {newstats => stats}/stats_json.go | 24 +- {newstats => stats}/stats_prometheus.go | 24 +- {newstats => stats}/stats_statsd.go | 32 +- telegram/base.go | 70 +++ telegram/direct.go | 58 +-- telegram/interfaces.go | 15 + utils/init_tcp.go | 25 + utils/stream_cipher.go | 11 + utils/uint24.go | 4 - {newwrappers => wrappers}/interfaces.go | 2 +- {newwrappers => wrappers}/wrapper_conn.go | 17 +- .../wrapper_obfuscated2.go | 2 +- {newwrappers => wrappers}/wrapper_stats.go | 12 +- 87 files changed, 1883 insertions(+), 1447 deletions(-) create mode 100644 _antireplay/cache.go rename {newantireplay => _antireplay}/hasher.go (86%) rename {client => _client}/client.go (100%) rename {client => _client}/direct.go (100%) rename {client => _client}/middle.go (100%) create mode 100644 _config/config.go rename {newconfig => _config}/global_ips.go (55%) rename {newconfig => _config}/urls.go (53%) rename {mtproto => _mtproto}/connection_options.go (100%) rename {mtproto => _mtproto}/rpc/handshake_request.go (100%) rename {mtproto => _mtproto}/rpc/handshake_response.go (100%) rename {mtproto => _mtproto}/rpc/nonce_request.go (100%) rename {mtproto => _mtproto}/rpc/nonce_response.go (100%) rename {mtproto => _mtproto}/rpc/proxy_flags.go (100%) rename {mtproto => _mtproto}/rpc/proxy_request.go (100%) rename {mtproto => _mtproto}/rpc/rpc.go (100%) create mode 100644 _obfuscated2/frame.go rename {obfuscated2 => _obfuscated2}/frame_test.go (100%) rename {obfuscated2 => _obfuscated2}/obfuscated2.go (100%) rename {obfuscated2 => _obfuscated2}/obfuscated2_test.go (100%) create mode 100644 _proxy/proxy.go rename {stats => _stats}/channels.go (100%) rename {stats => _stats}/init.go (100%) rename {stats => _stats}/prometheus.go (100%) rename {stats => _stats}/server.go (100%) create mode 100644 _stats/stats.go rename {stats => _stats}/statsd.go (100%) rename {telegram => _telegram}/dialer.go (100%) create mode 100644 _telegram/direct.go rename {telegram => _telegram}/middle.go (100%) rename {telegram => _telegram}/middle_caller.go (100%) rename {telegram => _telegram}/telegram.go (100%) rename {utils => _utils}/read_current_data.go (100%) create mode 100644 _utils/reverse_bytes.go create mode 100644 _utils/uint24.go rename {wrappers => _wrappers}/blockcipher.go (100%) rename {wrappers => _wrappers}/conn.go (100%) rename {wrappers => _wrappers}/mtproto_abridged.go (100%) rename {wrappers => _wrappers}/mtproto_cipher.go (100%) rename {wrappers => _wrappers}/mtproto_frame.go (100%) rename {wrappers => _wrappers}/mtproto_intermediate.go (100%) rename {wrappers => _wrappers}/mtproto_intermediate_secure.go (100%) rename {wrappers => _wrappers}/mtproto_proxy.go (100%) rename {wrappers => _wrappers}/streamcipher.go (100%) rename {wrappers => _wrappers}/streamcipher_pool.go (100%) rename {wrappers => _wrappers}/wrap.go (100%) rename {newcli => cli}/generate.go (76%) create mode 100644 cli/proxy.go rename {newcli => cli}/utils.go (97%) create mode 100644 conntypes/dc.go rename newprotocol/connection_protocol.go => conntypes/protocol.go (95%) rename newprotocol/connection_type.go => conntypes/type.go (96%) delete mode 100644 newantireplay/cache.go delete mode 100644 newcli/proxy.go delete mode 100644 newconfig/config.go delete mode 100644 newobfuscated2/client_protocol.go delete mode 100644 newobfuscated2/frame.go delete mode 100644 newobfuscated2/telegram_protocol.go delete mode 100644 newprotocol/base_protocol.go delete mode 100644 newproxy/proxy.go delete mode 100644 newstats/stats.go create mode 100644 obfuscated2/client_protocol.go create mode 100644 obfuscated2/telegram_protocol.go create mode 100644 protocol/base_protocol.go create mode 100644 protocol/interfaces.go create mode 100644 protocol/request.go rename {newstats => stats}/stats_json.go (81%) rename {newstats => stats}/stats_prometheus.go (79%) rename {newstats => stats}/stats_statsd.go (58%) create mode 100644 telegram/base.go create mode 100644 telegram/interfaces.go create mode 100644 utils/init_tcp.go create mode 100644 utils/stream_cipher.go rename {newwrappers => wrappers}/interfaces.go (98%) rename {newwrappers => wrappers}/wrapper_conn.go (90%) rename {newwrappers => wrappers}/wrapper_obfuscated2.go (98%) rename {newwrappers => wrappers}/wrapper_stats.go (86%) diff --git a/_antireplay/cache.go b/_antireplay/cache.go new file mode 100644 index 0000000..4923d52 --- /dev/null +++ b/_antireplay/cache.go @@ -0,0 +1,37 @@ +package antireplay + +import ( + "github.com/allegro/bigcache" + "github.com/juju/errors" + + "github.com/9seconds/mtg/config" +) + +// Cache defines storage for obfuscated2 handshake frames. +type Cache struct { + cache *bigcache.BigCache +} + +func (a Cache) Add(frame []byte) { + a.cache.Set(string(frame), nil) // nolint: errcheck +} + +func (a Cache) Has(frame []byte) bool { + _, err := a.cache.Get(string(frame)) + + return err == nil +} + +func NewCache(config *config.Config) (Cache, error) { + cache, err := bigcache.NewBigCache(bigcache.Config{ + Shards: 1024, + LifeWindow: config.AntiReplayEvictionTime, + Hasher: hasher{}, + HardMaxCacheSize: config.AntiReplayMaxSize, + }) + if err != nil { + return Cache{}, errors.Annotate(err, "Cannot make cache") + } + + return Cache{cache}, nil +} diff --git a/newantireplay/hasher.go b/_antireplay/hasher.go similarity index 86% rename from newantireplay/hasher.go rename to _antireplay/hasher.go index 68cb1fd..acaf9b6 100644 --- a/newantireplay/hasher.go +++ b/_antireplay/hasher.go @@ -1,4 +1,4 @@ -package newantireplay +package antireplay import "github.com/cespare/xxhash" diff --git a/client/client.go b/_client/client.go similarity index 100% rename from client/client.go rename to _client/client.go diff --git a/client/direct.go b/_client/direct.go similarity index 100% rename from client/direct.go rename to _client/direct.go diff --git a/client/middle.go b/_client/middle.go similarity index 100% rename from client/middle.go rename to _client/middle.go diff --git a/_config/config.go b/_config/config.go new file mode 100644 index 0000000..dc43864 --- /dev/null +++ b/_config/config.go @@ -0,0 +1,224 @@ +package config + +import ( + "bytes" + "encoding/hex" + "fmt" + "net" + "strconv" + "time" + + "github.com/juju/errors" + statsd "gopkg.in/alexcesaro/statsd.v2" +) + +// Config represents common configuration of mtg. +type Config struct { + Debug bool + Verbose bool + SecureMode bool + SecureOnly bool + + ReadBufferSize int + WriteBufferSize int + + BindPort uint16 + PublicIPv4Port uint16 + PublicIPv6Port uint16 + StatsPort uint16 + + BindIP net.IP + PublicIPv4 net.IP + PublicIPv6 net.IP + StatsIP net.IP + + AntiReplayMaxSize int + AntiReplayEvictionTime time.Duration + + StatsD struct { + Addr net.Addr + Prefix string + Tags map[string]string + TagsFormat statsd.TagFormat + Enabled bool + } + Prometheus struct { + Prefix string + } + + Secret []byte + AdTag []byte +} + +// URLs contains links to the proxy (tg://, t.me) and their QR codes. +type URLs struct { + TG string `json:"tg_url"` + TMe string `json:"tme_url"` + TGQRCode string `json:"tg_qrcode"` + TMeQRCode string `json:"tme_qrcode"` +} + +// IPURLs contains links to both ipv4 and ipv6 of the proxy. +type IPURLs struct { + IPv4 URLs `json:"ipv4"` + IPv6 URLs `json:"ipv6"` + BotSecret string `json:"secret_for_mtproxybot"` +} + +// BindAddr returns connection for this server to bind to. +func (c *Config) BindAddr() string { + return getAddr(c.BindIP, c.BindPort) +} + +// StatAddr returns connection string to the stats API. +func (c *Config) StatAddr() string { + return getAddr(c.StatsIP, c.StatsPort) +} + +// UseMiddleProxy defines if this proxy has to connect middle proxies +// which supports promoted channels or directly access Telegram. +func (c *Config) UseMiddleProxy() bool { + return len(c.AdTag) > 0 +} + +// BotSecretString returns secret string which should work with MTProxybot. +func (c *Config) BotSecretString() string { + return hex.EncodeToString(c.Secret) +} + +// SecretString returns a secret in a form entered on the start of the +// application. +func (c *Config) SecretString() string { + secret := c.BotSecretString() + if c.SecureMode { + return "dd" + secret + } + return secret +} + +// GetURLs returns configured IPURLs instance with links to this server. +func (c *Config) GetURLs() IPURLs { + urls := IPURLs{} + secret := c.SecretString() + if c.PublicIPv4 != nil { + urls.IPv4 = getURLs(c.PublicIPv4, c.PublicIPv4Port, secret) + } + if c.PublicIPv6 != nil { + urls.IPv6 = getURLs(c.PublicIPv6, c.PublicIPv6Port, secret) + } + urls.BotSecret = c.BotSecretString() + + return urls +} + +func getAddr(host fmt.Stringer, port uint16) string { + return net.JoinHostPort(host.String(), strconv.Itoa(int(port))) +} + +// NewConfig returns new configuration. If required, it manages and +// fetches data from external sources. Parameters passed to this +// function, should come from command line arguments. +func NewConfig(debug, verbose bool, // nolint: gocyclo + writeBufferSize, readBufferSize uint32, + bindIP, publicIPv4, publicIPv6, statsIP net.IP, + bindPort, publicIPv4Port, publicIPv6Port, statsPort, statsdPort uint16, + statsdIP, statsdNetwork, statsdPrefix, statsdTagsFormat string, + statsdTags map[string]string, prometheusPrefix string, + secureOnly bool, + antiReplayMaxSize int, antiReplayEvictionTime time.Duration, + secret, adtag []byte) (*Config, error) { + secureMode := secureOnly + if bytes.HasPrefix(secret, []byte{0xdd}) && len(secret) == 17 { + secureMode = true + secret = bytes.TrimPrefix(secret, []byte{0xdd}) + } else if len(secret) != 16 { + return nil, errors.New("Telegram demands secret of length 32") + } + + var err error + if publicIPv4 == nil { + publicIPv4, err = getGlobalIPv4() + if err != nil { + publicIPv4 = nil + } else if publicIPv4.To4() == nil { + return nil, errors.Errorf("IP %s is not IPv4", publicIPv4.String()) + } + } + if publicIPv4Port == 0 { + publicIPv4Port = bindPort + } + + if publicIPv6 == nil { + publicIPv6, err = getGlobalIPv6() + if err != nil { + publicIPv6 = nil + } else if publicIPv6.To4() != nil { + return nil, errors.Errorf("IP %s is not IPv6", publicIPv6.String()) + } + } + if publicIPv6Port == 0 { + publicIPv6Port = bindPort + } + + if statsIP == nil { + statsIP = publicIPv4 + } + + conf := &Config{ + Debug: debug, + Verbose: verbose, + SecureOnly: secureOnly, + BindIP: bindIP, + BindPort: bindPort, + PublicIPv4: publicIPv4, + PublicIPv4Port: publicIPv4Port, + PublicIPv6: publicIPv6, + PublicIPv6Port: publicIPv6Port, + StatsIP: statsIP, + StatsPort: statsPort, + Secret: secret, + AdTag: adtag, + SecureMode: secureMode, + ReadBufferSize: int(readBufferSize), + WriteBufferSize: int(writeBufferSize), + AntiReplayMaxSize: antiReplayMaxSize, + AntiReplayEvictionTime: antiReplayEvictionTime, + } + conf.Prometheus.Prefix = prometheusPrefix + + if statsdIP != "" { + conf.StatsD.Enabled = true + conf.StatsD.Prefix = statsdPrefix + conf.StatsD.Tags = statsdTags + + var ( + addr net.Addr + err error + ) + hostPort := net.JoinHostPort(statsdIP, strconv.Itoa(int(statsdPort))) + switch statsdNetwork { + case "tcp": + addr, err = net.ResolveTCPAddr("tcp", hostPort) + case "udp": + addr, err = net.ResolveUDPAddr("udp", hostPort) + default: + err = errors.Errorf("Unknown network %s", statsdNetwork) + } + if err != nil { + return nil, errors.Annotate(err, "Cannot resolve statsd address") + } + conf.StatsD.Addr = addr + + switch statsdTagsFormat { + case "datadog": + conf.StatsD.TagsFormat = statsd.Datadog + case "influxdb": + conf.StatsD.TagsFormat = statsd.InfluxDB + case "": + default: + return nil, errors.Errorf("Unknown tags format %s", statsdTagsFormat) + } + } + + return conf, nil +} diff --git a/newconfig/global_ips.go b/_config/global_ips.go similarity index 55% rename from newconfig/global_ips.go rename to _config/global_ips.go index dd21b6a..9a144a6 100644 --- a/newconfig/global_ips.go +++ b/_config/global_ips.go @@ -1,43 +1,29 @@ -package newconfig +package config import ( "context" - "io" "io/ioutil" "net" "net/http" "strings" - "time" "github.com/juju/errors" ) -const ( - ifconfigAddress = "https://ifconfig.co/ip" - ifconfigTimeout = 10 * time.Second -) +const ifconfigAddress = "https://ifconfig.co/ip" func getGlobalIPv4() (net.IP, error) { - ip, err := fetchIP("tcp4") - if err != nil || ip.To4() == nil { - return nil, errors.Annotate(err, "Cannot find public ipv4 address") - } - return ip, nil + return fetchIP("tcp4") } func getGlobalIPv6() (net.IP, error) { - ip, err := fetchIP("tcp6") - if err != nil || ip.To4() != nil { - return nil, errors.Annotate(err, "Cannot find public ipv6 address") - } - return ip, nil + return fetchIP("tcp6") } func fetchIP(network string) (net.IP, error) { dialer := &net.Dialer{FallbackDelay: -1} client := &http.Client{ - Jar: nil, - Timeout: ifconfigTimeout, + Jar: nil, Transport: &http.Transport{ DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) { return dialer.DialContext(ctx, network, addr) @@ -47,16 +33,13 @@ func fetchIP(network string) (net.IP, error) { resp, err := client.Get(ifconfigAddress) if err != nil { - if resp != nil { - io.Copy(ioutil.Discard, resp.Body) // nolint: errcheck - } - return nil, errors.Annotate(err, "Cannot perform a request") + return nil, err } defer resp.Body.Close() // nolint: errcheck respDataBytes, err := ioutil.ReadAll(resp.Body) if err != nil { - return nil, errors.Annotate(err, "Cannot read response body") + return nil, err } respData := strings.TrimSpace(string(respDataBytes)) diff --git a/newconfig/urls.go b/_config/urls.go similarity index 53% rename from newconfig/urls.go rename to _config/urls.go index ff589d6..5f4e6d8 100644 --- a/newconfig/urls.go +++ b/_config/urls.go @@ -1,42 +1,15 @@ -package newconfig +package config import ( - "encoding/hex" + "net" "net/url" + "strconv" ) -type URLs struct { - TG string `json:"tg_url"` - TMe string `json:"tme_url"` - TGQRCode string `json:"tg_qrcode"` - TMeQRCode string `json:"tme_qrcode"` -} - -type IPURLs struct { - IPv4 URLs `json:"ipv4"` - IPv6 URLs `json:"ipv6"` - BotSecret string `json:"secret_for_mtproxybot"` -} - -func GetURLs() (urls IPURLs) { - secret := "" - switch C.SecretMode { - case SecretModeSimple: - secret = hex.EncodeToString(C.Secret) - case SecretModeSecured: - secret = "dd" + hex.EncodeToString(C.Secret) - } - - urls.IPv4 = makeURLs(&C.PublicIPv4Addr, secret) - urls.IPv6 = makeURLs(&C.PublicIPv6Addr, secret) - urls.BotSecret = secret - - return urls -} - -func makeURLs(addr *Addr, secret string) (urls URLs) { +func getURLs(addr net.IP, port uint16, secret string) (urls URLs) { values := url.Values{} - values.Set("address", addr.String()) + values.Set("server", addr.String()) + values.Set("port", strconv.Itoa(int(port))) values.Set("secret", secret) urls.TG = makeTGURL(values) diff --git a/mtproto/connection_options.go b/_mtproto/connection_options.go similarity index 100% rename from mtproto/connection_options.go rename to _mtproto/connection_options.go diff --git a/mtproto/rpc/handshake_request.go b/_mtproto/rpc/handshake_request.go similarity index 100% rename from mtproto/rpc/handshake_request.go rename to _mtproto/rpc/handshake_request.go diff --git a/mtproto/rpc/handshake_response.go b/_mtproto/rpc/handshake_response.go similarity index 100% rename from mtproto/rpc/handshake_response.go rename to _mtproto/rpc/handshake_response.go diff --git a/mtproto/rpc/nonce_request.go b/_mtproto/rpc/nonce_request.go similarity index 100% rename from mtproto/rpc/nonce_request.go rename to _mtproto/rpc/nonce_request.go diff --git a/mtproto/rpc/nonce_response.go b/_mtproto/rpc/nonce_response.go similarity index 100% rename from mtproto/rpc/nonce_response.go rename to _mtproto/rpc/nonce_response.go diff --git a/mtproto/rpc/proxy_flags.go b/_mtproto/rpc/proxy_flags.go similarity index 100% rename from mtproto/rpc/proxy_flags.go rename to _mtproto/rpc/proxy_flags.go diff --git a/mtproto/rpc/proxy_request.go b/_mtproto/rpc/proxy_request.go similarity index 100% rename from mtproto/rpc/proxy_request.go rename to _mtproto/rpc/proxy_request.go diff --git a/mtproto/rpc/rpc.go b/_mtproto/rpc/rpc.go similarity index 100% rename from mtproto/rpc/rpc.go rename to _mtproto/rpc/rpc.go diff --git a/_obfuscated2/frame.go b/_obfuscated2/frame.go new file mode 100644 index 0000000..33a8dd4 --- /dev/null +++ b/_obfuscated2/frame.go @@ -0,0 +1,121 @@ +package obfuscated2 + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "io" + + "github.com/juju/errors" + + "github.com/9seconds/mtg/mtproto" +) + +// [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd] +const ( + frameLenKey = 32 + frameLenIV = 16 + frameLenMagic = 4 + frameLenDC = 2 + + frameOffsetFirst = 8 + frameOffsetKey = frameOffsetFirst + frameLenKey + frameOffsetIV = frameOffsetKey + frameLenIV + frameOffsetMagic = frameOffsetIV + frameLenMagic + frameOffsetDC = frameOffsetMagic + frameLenDC + + FrameLen = 64 +) + +// Frame represents handshake frame. Telegram sends 64 bytes of obfuscated2 +// initialization data first. +// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/ +type Frame []byte + +// Key returns AES encryption key. +func (f Frame) Key() []byte { + return f[frameOffsetFirst:frameOffsetKey] +} + +// IV returns AES encryption initialization vector +func (f Frame) IV() []byte { + return f[frameOffsetKey:frameOffsetIV] +} + +// Magic returns magic bytes from last 8 bytes of frame. Telegram checks +// for values there. If after decryption magic is not as expected, +// connection considered as failed. +func (f Frame) Magic() []byte { + return f[frameOffsetIV:frameOffsetMagic] +} + +// DC returns number of datacenter IP client wants to use. +func (f Frame) DC() (n int16) { + buf := bytes.NewReader(f[frameOffsetMagic:frameOffsetDC]) + if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { + n = 1 + } + + return +} + +// ConnectionType identifies connection type of the handshake frame. +func (f Frame) ConnectionType() (mtproto.ConnectionType, error) { + return mtproto.ConnectionTagFromHandshake(f.Magic()) +} + +// Invert inverts frame for extracting encryption keys. Pkease check that link: +// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/ +func (f Frame) Invert() Frame { + reversed := make(Frame, FrameLen) + copy(reversed, f) + + for i := 0; i < frameLenKey+frameLenIV; i++ { + reversed[frameOffsetFirst+i] = f[frameOffsetIV-1-i] + } + + return reversed +} + +// ExtractFrame extracts exact obfuscated2 handshake frame from given reader. +func ExtractFrame(conn io.Reader) (Frame, error) { + frame := make(Frame, FrameLen) + buf := bytes.NewBuffer(frame) + buf.Reset() + + if _, err := io.CopyN(buf, conn, FrameLen); err != nil { + return nil, errors.Annotate(err, "Cannot extract obfuscated header") + } + copy(frame, buf.Bytes()) + + return frame, nil +} + +func generateFrame(connectionType mtproto.ConnectionType) Frame { + frame := make(Frame, FrameLen) + + for { + if _, err := rand.Read(frame); err != nil { + continue + } + if frame[0] == 0xef { + continue + } + + val := (uint32(frame[3]) << 24) | (uint32(frame[2]) << 16) | (uint32(frame[1]) << 8) | uint32(frame[0]) + if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee { + continue + } + + val = (uint32(frame[7]) << 24) | (uint32(frame[6]) << 16) | (uint32(frame[5]) << 8) | uint32(frame[4]) + if val == 0x00000000 { + continue + } + + // error has to be checked before calling this function + tag, _ := connectionType.Tag() // nolint: errcheck, gosec + copy(frame.Magic(), tag) + + return frame + } +} diff --git a/obfuscated2/frame_test.go b/_obfuscated2/frame_test.go similarity index 100% rename from obfuscated2/frame_test.go rename to _obfuscated2/frame_test.go diff --git a/obfuscated2/obfuscated2.go b/_obfuscated2/obfuscated2.go similarity index 100% rename from obfuscated2/obfuscated2.go rename to _obfuscated2/obfuscated2.go diff --git a/obfuscated2/obfuscated2_test.go b/_obfuscated2/obfuscated2_test.go similarity index 100% rename from obfuscated2/obfuscated2_test.go rename to _obfuscated2/obfuscated2_test.go diff --git a/_proxy/proxy.go b/_proxy/proxy.go new file mode 100644 index 0000000..ecda26a --- /dev/null +++ b/_proxy/proxy.go @@ -0,0 +1,178 @@ +package proxy + +import ( + "context" + "io" + "net" + "sync" + + "github.com/gofrs/uuid" + "github.com/juju/errors" + "go.uber.org/zap" + + "github.com/9seconds/mtg/antireplay" + "github.com/9seconds/mtg/client" + "github.com/9seconds/mtg/config" + "github.com/9seconds/mtg/mtproto" + "github.com/9seconds/mtg/stats" + "github.com/9seconds/mtg/telegram" + "github.com/9seconds/mtg/wrappers" +) + +// Proxy is a core of this program. +type Proxy struct { + antiReplayCache antireplay.Cache + clientInit client.Init + tg telegram.Telegram + conf *config.Config +} + +// Serve runs TCP proxy server. +func (p *Proxy) Serve() error { + lsock, err := net.Listen("tcp", p.conf.BindAddr()) + if err != nil { + return errors.Annotate(err, "Cannot create listen socket") + } + + for { + if conn, err := lsock.Accept(); err != nil { + zap.S().Errorw("Cannot allocate incoming connection", "error", err) + } else { + go p.accept(conn) + } + } +} + +func (p *Proxy) accept(conn net.Conn) { + connID := uuid.Must(uuid.NewV4()).String() + log := zap.S().With("connection_id", connID).Named("main") + ctx, cancel := context.WithCancel(context.Background()) + + defer func() { + cancel() + conn.Close() // nolint: errcheck, gosec + + if err := recover(); err != nil { + stats.NewCrash() + log.Errorw("Crash of accept handler", "error", err) + } + }() + + log.Infow("Client connected", "addr", conn.RemoteAddr()) + + clientConn, opts, err := p.clientInit(ctx, cancel, conn, connID, p.antiReplayCache, p.conf) + if err != nil { + log.Errorw("Cannot initialize client connection", "error", err) + return + } + defer clientConn.(io.Closer).Close() // nolint: errcheck + + if p.conf.SecureOnly && opts.ConnectionType != mtproto.ConnectionTypeSecure { + log.Errorw("Proxy supports only secure connections", "connection_type", opts.ConnectionType) + return + } + + stats.ClientConnected(opts.ConnectionType, clientConn.RemoteAddr()) + defer stats.ClientDisconnected(opts.ConnectionType, clientConn.RemoteAddr()) + + serverConn, err := p.getTelegramConn(ctx, cancel, opts, connID) + if err != nil { + log.Errorw("Cannot initialize server connection", "error", err) + return + } + defer serverConn.(io.Closer).Close() // nolint: errcheck + + go func() { + <-ctx.Done() + serverConn.(io.Closer).Close() // nolint: gosec + clientConn.(io.Closer).Close() // nolint: gosec + }() + + wait := &sync.WaitGroup{} + wait.Add(2) + + if p.conf.UseMiddleProxy() { + clientPacket := clientConn.(wrappers.PacketReadWriteCloser) + serverPacket := serverConn.(wrappers.PacketReadWriteCloser) + go p.middlePipe(clientPacket, serverPacket, wait, &opts.ReadHacks) + p.middlePipe(serverPacket, clientPacket, wait, &opts.WriteHacks) + } else { + clientStream := clientConn.(wrappers.StreamReadWriteCloser) + serverStream := serverConn.(wrappers.StreamReadWriteCloser) + go p.directPipe(clientStream, serverStream, wait, p.conf.ReadBufferSize) + p.directPipe(serverStream, clientStream, wait, p.conf.WriteBufferSize) + } + + wait.Wait() + + log.Infow("Client disconnected", "addr", conn.RemoteAddr()) +} + +func (p *Proxy) getTelegramConn(ctx context.Context, cancel context.CancelFunc, + opts *mtproto.ConnectionOpts, connID string) (wrappers.Wrap, error) { + streamConn, err := p.tg.Dial(ctx, cancel, connID, opts) + if err != nil { + return nil, errors.Annotate(err, "Cannot dial to Telegram") + } + + packetConn, err := p.tg.Init(opts, streamConn) + if err != nil { + return nil, errors.Annotate(err, "Cannot handshake telegram") + } + + return packetConn, nil +} + +func (p *Proxy) middlePipe(src wrappers.PacketReadCloser, dst io.Writer, wait *sync.WaitGroup, hacks *mtproto.Hacks) { + defer wait.Done() + + for { + hacks.SimpleAck = false + hacks.QuickAck = false + + packet, err := src.Read() + if err != nil { + src.Logger().Warnw("Cannot read packet", "error", err) + return + } + if _, err = dst.Write(packet); err != nil { + src.Logger().Warnw("Cannot write packet", "error", err) + return + } + } +} + +func (p *Proxy) directPipe(src wrappers.StreamReadCloser, dst io.Writer, wait *sync.WaitGroup, bufferSize int) { + defer wait.Done() + + buffer := make([]byte, bufferSize) + if _, err := io.CopyBuffer(dst, src, buffer); err != nil { + src.Logger().Warnw("Cannot pump sockets", "error", err) + } +} + +// NewProxy returns new proxy instance. +func NewProxy(conf *config.Config) (*Proxy, error) { + var clientInit client.Init + var tg telegram.Telegram + + cache, err := antireplay.NewCache(conf) + if err != nil { + return nil, errors.Annotate(err, "Cannot make proxy") + } + + if conf.UseMiddleProxy() { + clientInit = client.MiddleInit + tg = telegram.NewMiddleTelegram(conf) + } else { + clientInit = client.DirectInit + tg = telegram.NewDirectTelegram(conf) + } + + return &Proxy{ + antiReplayCache: cache, + conf: conf, + clientInit: clientInit, + tg: tg, + }, nil +} diff --git a/stats/channels.go b/_stats/channels.go similarity index 100% rename from stats/channels.go rename to _stats/channels.go diff --git a/stats/init.go b/_stats/init.go similarity index 100% rename from stats/init.go rename to _stats/init.go diff --git a/stats/prometheus.go b/_stats/prometheus.go similarity index 100% rename from stats/prometheus.go rename to _stats/prometheus.go diff --git a/stats/server.go b/_stats/server.go similarity index 100% rename from stats/server.go rename to _stats/server.go diff --git a/_stats/stats.go b/_stats/stats.go new file mode 100644 index 0000000..686163d --- /dev/null +++ b/_stats/stats.go @@ -0,0 +1,175 @@ +package stats + +import ( + "encoding/json" + "fmt" + "strconv" + "time" + + humanize "github.com/dustin/go-humanize" + + "github.com/9seconds/mtg/config" + "github.com/9seconds/mtg/mtproto" +) + +type uptime time.Time + +func (u uptime) MarshalJSON() ([]byte, error) { + duration := time.Since(time.Time(u)) + value := map[string]string{ + "seconds": strconv.Itoa(int(duration.Seconds())), + "human": humanize.Time(time.Time(u)), + } + + return json.Marshal(value) +} + +type connectionType struct { + IPv6 uint32 `json:"ipv6"` + IPv4 uint32 `json:"ipv4"` +} + +type baseConnections struct { + All connectionType `json:"all"` + Abridged connectionType `json:"abridged"` + Intermediate connectionType `json:"intermediate"` + Secure connectionType `json:"secure"` +} + +type connections struct { + baseConnections +} + +func (c connections) MarshalJSON() ([]byte, error) { + c.All.IPv4 = c.Abridged.IPv4 + c.Intermediate.IPv4 + c.Secure.IPv4 + c.All.IPv6 = c.Abridged.IPv6 + c.Intermediate.IPv6 + c.Secure.IPv6 + + return json.Marshal(c.baseConnections) +} + +type traffic struct { + ingress uint64 + egress uint64 +} + +func (t *traffic) dumpValue(value uint64) map[string]interface{} { + return map[string]interface{}{ + "bytes": value, + "human": humanize.Bytes(value), + } +} + +func (t traffic) MarshalJSON() ([]byte, error) { + value := map[string]map[string]interface{}{ + "ingress": t.dumpValue(t.ingress), + "egress": t.dumpValue(t.egress), + } + + return json.Marshal(value) +} + +type speed struct { + ingress uint64 + egress uint64 +} + +func (s *speed) dumpValue(value uint64) map[string]interface{} { + return map[string]interface{}{ + "bytes/s": value, + "human": fmt.Sprintf("%s/s", humanize.Bytes(value)), + } +} + +func (s speed) MarshalJSON() ([]byte, error) { + value := map[string]map[string]interface{}{ + "ingress": s.dumpValue(s.ingress), + "egress": s.dumpValue(s.egress), + } + + return json.Marshal(value) +} + +// Stats represents a statistics of the proxy. +type Stats struct { + URLs config.IPURLs `json:"urls"` + Connections connections `json:"connections"` + Traffic traffic `json:"traffic"` + Speed speed `json:"speed"` + Uptime uptime `json:"uptime"` + Crashes uint32 `json:"crashes"` + + previousTraffic traffic +} + +func (s *Stats) start() { + speedChan := time.Tick(time.Second) + + for { + select { + case <-speedChan: + s.handleSpeed() + case event := <-trafficChan: + s.handleTraffic(event) + case event := <-connectionsChan: + s.handleConnection(event) + case getStatsChan := <-statsChan: + s.handleGetStats(getStatsChan) + case <-crashesChan: + s.handleCrash() + } + } +} + +func (s *Stats) handleTraffic(evt trafficData) { + if evt.ingress { + s.Traffic.ingress += uint64(evt.traffic) + } else { + s.Traffic.egress += uint64(evt.traffic) + } +} + +func (s *Stats) handleSpeed() { + s.Speed.ingress = s.Traffic.ingress - s.previousTraffic.ingress + s.Speed.egress = s.Traffic.egress - s.previousTraffic.egress + s.previousTraffic.ingress = s.Traffic.ingress + s.previousTraffic.egress = s.Traffic.egress +} + +func (s *Stats) handleConnection(evt connectionData) { + var inc uint32 = 1 + if !evt.connected { + inc = ^uint32(0) + } + + var conn *connectionType + switch evt.connectionType { + case mtproto.ConnectionTypeAbridged: + conn = &s.Connections.Abridged + case mtproto.ConnectionTypeSecure: + conn = &s.Connections.Secure + default: + conn = &s.Connections.Intermediate + } + + if evt.addr.IP.To4() != nil { + conn.IPv4 += inc + } else { + conn.IPv6 += inc + } +} + +func (s *Stats) handleGetStats(getStatsChan chan<- Stats) { + getStatsChan <- *s +} + +func (s *Stats) handleCrash() { + s.Crashes++ +} + +// NewStats creates a new instance of Stats structure. +func NewStats(conf *config.Config) *Stats { + return &Stats{ + URLs: conf.GetURLs(), + Uptime: uptime(time.Now()), + } +} diff --git a/stats/statsd.go b/_stats/statsd.go similarity index 100% rename from stats/statsd.go rename to _stats/statsd.go diff --git a/telegram/dialer.go b/_telegram/dialer.go similarity index 100% rename from telegram/dialer.go rename to _telegram/dialer.go diff --git a/_telegram/direct.go b/_telegram/direct.go new file mode 100644 index 0000000..f8a083a --- /dev/null +++ b/_telegram/direct.go @@ -0,0 +1,79 @@ +package telegram + +import ( + "context" + "net" + + "github.com/juju/errors" + + "github.com/9seconds/mtg/config" + "github.com/9seconds/mtg/mtproto" + "github.com/9seconds/mtg/obfuscated2" + "github.com/9seconds/mtg/wrappers" +) + +const ( + directV4DefaultIdx = 1 + directV6DefaultIdx = 1 +) + +var ( + directV4Addresses = map[int16][]string{ + 0: {"149.154.175.50:443"}, + 1: {"149.154.167.51:443"}, + 2: {"149.154.175.100:443"}, + 3: {"149.154.167.91:443"}, + 4: {"149.154.171.5:443"}, + } + directV6Addresses = map[int16][]string{ + 0: {"[2001:b28:f23d:f001::a]:443"}, + 1: {"[2001:67c:04e8:f002::a]:443"}, + 2: {"[2001:b28:f23d:f003::a]:443"}, + 3: {"[2001:67c:04e8:f004::a]:443"}, + 4: {"[2001:b28:f23f:f005::a]:443"}, + } +) + +type directTelegram struct { + baseTelegram +} + +func (t *directTelegram) Dial(ctx context.Context, cancel context.CancelFunc, + connID string, connOpts *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error) { + dc := connOpts.DC + if dc < 0 { + dc = -dc + } else if dc == 0 { + dc = 1 + } + + return t.baseTelegram.dial(ctx, cancel, dc-1, connID, connOpts.ConnectionProto) +} + +func (t *directTelegram) Init(connOpts *mtproto.ConnectionOpts, + conn wrappers.StreamReadWriteCloser) (wrappers.Wrap, error) { + obfs2, frame := obfuscated2.MakeTelegramObfuscated2Frame(connOpts) + + if _, err := conn.Write(frame); err != nil { + return nil, errors.Annotate(err, "Cannot write hadnshake frame") + } + + return wrappers.NewStreamCipher(conn, obfs2.Encryptor, obfs2.Decryptor), nil +} + +// NewDirectTelegram returns Telegram instance which connects directly +// to Telegram bypassing middleproxies. +func NewDirectTelegram(conf *config.Config) Telegram { + return &directTelegram{ + baseTelegram: baseTelegram{ + dialer: tgDialer{ + Dialer: net.Dialer{Timeout: telegramDialTimeout}, + conf: conf, + }, + v4DefaultIdx: directV4DefaultIdx, + v6DefaultIdx: directV6DefaultIdx, + v4Addresses: directV4Addresses, + v6Addresses: directV6Addresses, + }, + } +} diff --git a/telegram/middle.go b/_telegram/middle.go similarity index 100% rename from telegram/middle.go rename to _telegram/middle.go diff --git a/telegram/middle_caller.go b/_telegram/middle_caller.go similarity index 100% rename from telegram/middle_caller.go rename to _telegram/middle_caller.go diff --git a/telegram/telegram.go b/_telegram/telegram.go similarity index 100% rename from telegram/telegram.go rename to _telegram/telegram.go diff --git a/utils/read_current_data.go b/_utils/read_current_data.go similarity index 100% rename from utils/read_current_data.go rename to _utils/read_current_data.go diff --git a/_utils/reverse_bytes.go b/_utils/reverse_bytes.go new file mode 100644 index 0000000..ab7cd5b --- /dev/null +++ b/_utils/reverse_bytes.go @@ -0,0 +1,15 @@ +package utils + +// ReverseBytes is a common slice reverser. +func ReverseBytes(data []byte) []byte { + dataLen := len(data) + rv := make([]byte, dataLen) + + rv[dataLen/2] = data[dataLen/2] + for i := dataLen/2 - 1; i >= 0; i-- { + opp := dataLen - i - 1 + rv[i], rv[opp] = data[opp], data[i] + } + + return rv +} diff --git a/_utils/uint24.go b/_utils/uint24.go new file mode 100644 index 0000000..66c5f1b --- /dev/null +++ b/_utils/uint24.go @@ -0,0 +1,15 @@ +package utils + +// Uint24 is a replacement for the absent Go uint24 data type. +// This data type is little endian. +type Uint24 [3]byte + +// ToUint24 converts number to Uint24. +func ToUint24(number uint32) Uint24 { + return Uint24{byte(number), byte(number >> 8), byte(number >> 16)} +} + +// FromUint24 converts Uint24 to number. +func FromUint24(number Uint24) uint32 { + return uint32(number[0]) + (uint32(number[1]) << 8) + (uint32(number[2]) << 16) +} diff --git a/wrappers/blockcipher.go b/_wrappers/blockcipher.go similarity index 100% rename from wrappers/blockcipher.go rename to _wrappers/blockcipher.go diff --git a/wrappers/conn.go b/_wrappers/conn.go similarity index 100% rename from wrappers/conn.go rename to _wrappers/conn.go diff --git a/wrappers/mtproto_abridged.go b/_wrappers/mtproto_abridged.go similarity index 100% rename from wrappers/mtproto_abridged.go rename to _wrappers/mtproto_abridged.go diff --git a/wrappers/mtproto_cipher.go b/_wrappers/mtproto_cipher.go similarity index 100% rename from wrappers/mtproto_cipher.go rename to _wrappers/mtproto_cipher.go diff --git a/wrappers/mtproto_frame.go b/_wrappers/mtproto_frame.go similarity index 100% rename from wrappers/mtproto_frame.go rename to _wrappers/mtproto_frame.go diff --git a/wrappers/mtproto_intermediate.go b/_wrappers/mtproto_intermediate.go similarity index 100% rename from wrappers/mtproto_intermediate.go rename to _wrappers/mtproto_intermediate.go diff --git a/wrappers/mtproto_intermediate_secure.go b/_wrappers/mtproto_intermediate_secure.go similarity index 100% rename from wrappers/mtproto_intermediate_secure.go rename to _wrappers/mtproto_intermediate_secure.go diff --git a/wrappers/mtproto_proxy.go b/_wrappers/mtproto_proxy.go similarity index 100% rename from wrappers/mtproto_proxy.go rename to _wrappers/mtproto_proxy.go diff --git a/wrappers/streamcipher.go b/_wrappers/streamcipher.go similarity index 100% rename from wrappers/streamcipher.go rename to _wrappers/streamcipher.go diff --git a/wrappers/streamcipher_pool.go b/_wrappers/streamcipher_pool.go similarity index 100% rename from wrappers/streamcipher_pool.go rename to _wrappers/streamcipher_pool.go diff --git a/wrappers/wrap.go b/_wrappers/wrap.go similarity index 100% rename from wrappers/wrap.go rename to _wrappers/wrap.go diff --git a/antireplay/cache.go b/antireplay/cache.go index 4923d52..3b4706d 100644 --- a/antireplay/cache.go +++ b/antireplay/cache.go @@ -2,36 +2,29 @@ package antireplay import ( "github.com/allegro/bigcache" - "github.com/juju/errors" "github.com/9seconds/mtg/config" ) -// Cache defines storage for obfuscated2 handshake frames. -type Cache struct { - cache *bigcache.BigCache +var cache *bigcache.BigCache + +func Add(data []byte) { + cache.Set(string(data), nil) // nolint: errcheck } -func (a Cache) Add(frame []byte) { - a.cache.Set(string(frame), nil) // nolint: errcheck -} - -func (a Cache) Has(frame []byte) bool { - _, err := a.cache.Get(string(frame)) - +func Has(data []byte) bool { + _, err := cache.Get(string(data)) return err == nil } -func NewCache(config *config.Config) (Cache, error) { - cache, err := bigcache.NewBigCache(bigcache.Config{ +func Init() error { + c, err := bigcache.NewBigCache(bigcache.Config{ Shards: 1024, - LifeWindow: config.AntiReplayEvictionTime, + LifeWindow: config.C.AntiReplay.EvictionTime, Hasher: hasher{}, - HardMaxCacheSize: config.AntiReplayMaxSize, + HardMaxCacheSize: config.C.AntiReplay.MaxSize, }) - if err != nil { - return Cache{}, errors.Annotate(err, "Cannot make cache") - } + cache = c - return Cache{cache}, nil + return err } diff --git a/newcli/generate.go b/cli/generate.go similarity index 76% rename from newcli/generate.go rename to cli/generate.go index dcc544a..8010280 100644 --- a/newcli/generate.go +++ b/cli/generate.go @@ -1,14 +1,14 @@ -package newcli +package cli import ( "crypto/rand" "encoding/hex" - "github.com/9seconds/mtg/newconfig" + "github.com/9seconds/mtg/config" ) func Generate(secretType string) { - data := make([]byte, newconfig.SimpleSecretLength) + data := make([]byte, config.SimpleSecretLength) if _, err := rand.Read(data); err != nil { panic(err) } diff --git a/cli/proxy.go b/cli/proxy.go new file mode 100644 index 0000000..bccddfc --- /dev/null +++ b/cli/proxy.go @@ -0,0 +1,86 @@ +package cli + +import ( + "net" + "os" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/9seconds/mtg/antireplay" + "github.com/9seconds/mtg/config" + "github.com/9seconds/mtg/ntp" + "github.com/9seconds/mtg/obfuscated2" + "github.com/9seconds/mtg/proxy" + "github.com/9seconds/mtg/stats" + "github.com/9seconds/mtg/telegram" +) + +func Proxy() error { + atom := zap.NewAtomicLevel() + switch { + case config.C.Debug: + atom.SetLevel(zapcore.DebugLevel) + case config.C.Verbose: + atom.SetLevel(zapcore.InfoLevel) + default: + atom.SetLevel(zapcore.ErrorLevel) + } + + encoderCfg := zap.NewProductionEncoderConfig() + logger := zap.New(zapcore.NewCore( + zapcore.NewJSONEncoder(encoderCfg), + zapcore.Lock(os.Stderr), + atom, + )) + zap.ReplaceGlobals(logger) + defer logger.Sync() // nolint: errcheck + + if err := config.InitPublicAddress(); err != nil { + Fatal(err.Error()) + } + zap.S().Debugw("Configuration", "config", config.C) + + if len(config.C.AdTag) > 0 { + zap.S().Infow("Use middle proxy connection to Telegram") + diff, err := ntp.Fetch() + if err != nil { + Fatal("Cannot fetch time data from NTP") + } + if diff > time.Second { + Fatal("Your local time is skewed and drift is bigger than a second. Please sync your time.") + } + go ntp.AutoUpdate() + } else { + zap.S().Infow("Use direct connection to Telegram") + } + + PrintJSONStdout(config.GetURLs()) + + if err := antireplay.Init(); err != nil { + Fatal(err.Error()) + } + if err := stats.Init(); err != nil { + Fatal(err.Error()) + } + proxyListener, err := net.Listen("tcp", config.C.ListenAddr.String()) + if err != nil { + Fatal(err.Error()) + } + + app := &proxy.Proxy{ + Logger: zap.S().Named("proxy"), + } + if len(config.C.AdTag) == 0 { + app.TelegramProtocolMaker = obfuscated2.MakeTelegramProtocol + app.TelegramDialer = telegram.NewDirectTelegram() + } + if config.C.SecretMode != config.SecretModeTLS { + app.ClientProtocolMaker = obfuscated2.MakeClientProtocol + } + + app.Serve(proxyListener) + + return nil +} diff --git a/newcli/utils.go b/cli/utils.go similarity index 97% rename from newcli/utils.go rename to cli/utils.go index 64f5234..80bba2c 100644 --- a/newcli/utils.go +++ b/cli/utils.go @@ -1,4 +1,4 @@ -package newcli +package cli import ( "encoding/json" diff --git a/config/config.go b/config/config.go index dc43864..d9b9adb 100644 --- a/config/config.go +++ b/config/config.go @@ -2,223 +2,257 @@ package config import ( "bytes" - "encoding/hex" - "fmt" + "encoding/json" "net" "strconv" "time" "github.com/juju/errors" + "go.uber.org/zap" statsd "gopkg.in/alexcesaro/statsd.v2" ) -// Config represents common configuration of mtg. +type SecretMode uint8 + +func (s SecretMode) String() string { + switch s { + case SecretModeSimple: + return "simple" + case SecretModeSecured: + return "secured" + } + return "tls" +} + +const ( + SecretModeSimple SecretMode = iota + SecretModeSecured + SecretModeTLS +) + +const SimpleSecretLength = 16 + +type OptionType uint8 + +const ( + OptionTypeDebug OptionType = iota + OptionTypeVerbose + + OptionTypeBindIP + OptionTypeBindPort + OptionTypePublicIPv4 + OptionTypePublicIPv4Port + OptionTypePublicIPv6 + OptionTypePublicIPv6Port + OptionTypeStatsIP + OptionTypeStatsPort + + OptionTypeStatsdIP + OptionTypeStatsdPort + OptionTypeStatsdNetwork + OptionTypeStatsdPrefix + OptionTypeStatsdTagsFormat + OptionTypeStatsdTags + OptionTypePrometheusPrefix + + OptionTypeWriteBufferSize + OptionTypeReadBufferSize + + OptionTypeAntiReplayMaxSize + OptionTypeAntiReplayEvictionTime + + OptionTypeSecret + OptionTypeAdtag +) + +type BufferSize struct { + Read int `json:"read"` + Write int `json:"write"` +} + +type AntiReplay struct { + MaxSize int `json:"max_size"` + EvictionTime time.Duration `json:"duration"` +} + +type Stats struct { + Prefix string `json:"prefix"` +} + +type StatsdStats struct { + Stats + + Addr Addr `json:"addr"` + Tags map[string]string `json:"tags"` + TagsFormat statsd.TagFormat `json:"format"` +} + +type PrometheusStats struct { + Stats +} + +type Addr struct { + IP net.IP `json:"ip"` + Port int `json:"port"` + net string +} + +func (a Addr) Network() string { + if a.net == "" { + return "tcp" + } + return a.net +} + +func (a Addr) String() string { + return net.JoinHostPort(a.IP.String(), strconv.Itoa(a.Port)) +} + +func (a Addr) MarshalJSON() ([]byte, error) { + data := map[string]string{ + "network": a.Network(), + "addr": a.String(), + } + return json.Marshal(data) +} + type Config struct { - Debug bool - Verbose bool - SecureMode bool - SecureOnly bool + BufferSize BufferSize `json:"buffer_size"` + AntiReplay AntiReplay `json:"anti_replay"` - ReadBufferSize int - WriteBufferSize int + ListenAddr Addr `json:"listen_addr"` + PublicIPv4Addr Addr `json:"public_ipv4_addr"` + PublicIPv6Addr Addr `json:"public_ipv6_addr"` + StatsAddr Addr `json:"stats_addr"` - BindPort uint16 - PublicIPv4Port uint16 - PublicIPv6Port uint16 - StatsPort uint16 + StatsdStats StatsdStats `json:"stats_statsd"` + PrometheusStats PrometheusStats `json:"stats_prometheus"` - BindIP net.IP - PublicIPv4 net.IP - PublicIPv6 net.IP - StatsIP net.IP - - AntiReplayMaxSize int - AntiReplayEvictionTime time.Duration - - StatsD struct { - Addr net.Addr - Prefix string - Tags map[string]string - TagsFormat statsd.TagFormat - Enabled bool - } - Prometheus struct { - Prefix string - } - - Secret []byte - AdTag []byte + Debug bool `json:"debug"` + Verbose bool `json:"verbose"` + SecretMode SecretMode `json:"secret_mode"` + Secret []byte `json:"secret"` + AdTag []byte `json:"adtag"` } -// URLs contains links to the proxy (tg://, t.me) and their QR codes. -type URLs struct { - TG string `json:"tg_url"` - TMe string `json:"tme_url"` - TGQRCode string `json:"tg_qrcode"` - TMeQRCode string `json:"tme_qrcode"` +func (c Config) String() string { + data, _ := json.Marshal(c) + return string(data) } -// IPURLs contains links to both ipv4 and ipv6 of the proxy. -type IPURLs struct { - IPv4 URLs `json:"ipv4"` - IPv6 URLs `json:"ipv6"` - BotSecret string `json:"secret_for_mtproxybot"` +type ConfigOpt struct { + Option OptionType + Value interface{} } -// BindAddr returns connection for this server to bind to. -func (c *Config) BindAddr() string { - return getAddr(c.BindIP, c.BindPort) -} +var C = Config{} -// StatAddr returns connection string to the stats API. -func (c *Config) StatAddr() string { - return getAddr(c.StatsIP, c.StatsPort) -} - -// UseMiddleProxy defines if this proxy has to connect middle proxies -// which supports promoted channels or directly access Telegram. -func (c *Config) UseMiddleProxy() bool { - return len(c.AdTag) > 0 -} - -// BotSecretString returns secret string which should work with MTProxybot. -func (c *Config) BotSecretString() string { - return hex.EncodeToString(c.Secret) -} - -// SecretString returns a secret in a form entered on the start of the -// application. -func (c *Config) SecretString() string { - secret := c.BotSecretString() - if c.SecureMode { - return "dd" + secret - } - return secret -} - -// GetURLs returns configured IPURLs instance with links to this server. -func (c *Config) GetURLs() IPURLs { - urls := IPURLs{} - secret := c.SecretString() - if c.PublicIPv4 != nil { - urls.IPv4 = getURLs(c.PublicIPv4, c.PublicIPv4Port, secret) - } - if c.PublicIPv6 != nil { - urls.IPv6 = getURLs(c.PublicIPv6, c.PublicIPv6Port, secret) - } - urls.BotSecret = c.BotSecretString() - - return urls -} - -func getAddr(host fmt.Stringer, port uint16) string { - return net.JoinHostPort(host.String(), strconv.Itoa(int(port))) -} - -// NewConfig returns new configuration. If required, it manages and -// fetches data from external sources. Parameters passed to this -// function, should come from command line arguments. -func NewConfig(debug, verbose bool, // nolint: gocyclo - writeBufferSize, readBufferSize uint32, - bindIP, publicIPv4, publicIPv6, statsIP net.IP, - bindPort, publicIPv4Port, publicIPv6Port, statsPort, statsdPort uint16, - statsdIP, statsdNetwork, statsdPrefix, statsdTagsFormat string, - statsdTags map[string]string, prometheusPrefix string, - secureOnly bool, - antiReplayMaxSize int, antiReplayEvictionTime time.Duration, - secret, adtag []byte) (*Config, error) { - secureMode := secureOnly - if bytes.HasPrefix(secret, []byte{0xdd}) && len(secret) == 17 { - secureMode = true - secret = bytes.TrimPrefix(secret, []byte{0xdd}) - } else if len(secret) != 16 { - return nil, errors.New("Telegram demands secret of length 32") - } - - var err error - if publicIPv4 == nil { - publicIPv4, err = getGlobalIPv4() - if err != nil { - publicIPv4 = nil - } else if publicIPv4.To4() == nil { - return nil, errors.Errorf("IP %s is not IPv4", publicIPv4.String()) - } - } - if publicIPv4Port == 0 { - publicIPv4Port = bindPort - } - - if publicIPv6 == nil { - publicIPv6, err = getGlobalIPv6() - if err != nil { - publicIPv6 = nil - } else if publicIPv6.To4() != nil { - return nil, errors.Errorf("IP %s is not IPv6", publicIPv6.String()) - } - } - if publicIPv6Port == 0 { - publicIPv6Port = bindPort - } - - if statsIP == nil { - statsIP = publicIPv4 - } - - conf := &Config{ - Debug: debug, - Verbose: verbose, - SecureOnly: secureOnly, - BindIP: bindIP, - BindPort: bindPort, - PublicIPv4: publicIPv4, - PublicIPv4Port: publicIPv4Port, - PublicIPv6: publicIPv6, - PublicIPv6Port: publicIPv6Port, - StatsIP: statsIP, - StatsPort: statsPort, - Secret: secret, - AdTag: adtag, - SecureMode: secureMode, - ReadBufferSize: int(readBufferSize), - WriteBufferSize: int(writeBufferSize), - AntiReplayMaxSize: antiReplayMaxSize, - AntiReplayEvictionTime: antiReplayEvictionTime, - } - conf.Prometheus.Prefix = prometheusPrefix - - if statsdIP != "" { - conf.StatsD.Enabled = true - conf.StatsD.Prefix = statsdPrefix - conf.StatsD.Tags = statsdTags - - var ( - addr net.Addr - err error - ) - hostPort := net.JoinHostPort(statsdIP, strconv.Itoa(int(statsdPort))) - switch statsdNetwork { - case "tcp": - addr, err = net.ResolveTCPAddr("tcp", hostPort) - case "udp": - addr, err = net.ResolveUDPAddr("udp", hostPort) +func Init(options ...ConfigOpt) error { // nolint: gocyclo + for _, opt := range options { + switch opt.Option { + case OptionTypeDebug: + C.Debug = opt.Value.(bool) + case OptionTypeVerbose: + C.Verbose = opt.Value.(bool) + case OptionTypeBindIP: + C.ListenAddr.IP = opt.Value.(net.IP) + case OptionTypeBindPort: + C.ListenAddr.Port = int(opt.Value.(uint16)) + case OptionTypePublicIPv4: + C.PublicIPv4Addr.IP = opt.Value.(net.IP) + case OptionTypePublicIPv4Port: + C.PublicIPv4Addr.Port = int(opt.Value.(uint16)) + case OptionTypePublicIPv6: + C.PublicIPv6Addr.IP = opt.Value.(net.IP) + case OptionTypePublicIPv6Port: + C.PublicIPv6Addr.Port = int(opt.Value.(uint16)) + case OptionTypeStatsIP: + C.StatsAddr.IP = opt.Value.(net.IP) + case OptionTypeStatsPort: + C.StatsAddr.Port = int(opt.Value.(uint16)) + case OptionTypeStatsdIP: + C.StatsdStats.Addr.IP = opt.Value.(net.IP) + case OptionTypeStatsdPort: + C.StatsdStats.Addr.Port = int(opt.Value.(uint16)) + case OptionTypeStatsdNetwork: + C.StatsdStats.Addr.net = opt.Value.(string) + case OptionTypeStatsdPrefix: + C.StatsdStats.Prefix = opt.Value.(string) + case OptionTypeStatsdTagsFormat: + value := opt.Value.(string) + switch value { + case "datadog": + C.StatsdStats.TagsFormat = statsd.Datadog + case "influxdb": + C.StatsdStats.TagsFormat = statsd.InfluxDB + default: + return errors.Errorf("Incorrect statsd tag %s", value) + } + case OptionTypeStatsdTags: + C.StatsdStats.Tags = opt.Value.(map[string]string) + case OptionTypePrometheusPrefix: + C.PrometheusStats.Prefix = opt.Value.(string) + case OptionTypeWriteBufferSize: + C.BufferSize.Write = int(opt.Value.(uint32)) + case OptionTypeReadBufferSize: + C.BufferSize.Read = int(opt.Value.(uint32)) + case OptionTypeAntiReplayMaxSize: + C.AntiReplay.MaxSize = opt.Value.(int) + case OptionTypeAntiReplayEvictionTime: + C.AntiReplay.EvictionTime = opt.Value.(time.Duration) + case OptionTypeSecret: + C.Secret = opt.Value.([]byte) + case OptionTypeAdtag: + C.AdTag = opt.Value.([]byte) default: - err = errors.Errorf("Unknown network %s", statsdNetwork) - } - if err != nil { - return nil, errors.Annotate(err, "Cannot resolve statsd address") - } - conf.StatsD.Addr = addr - - switch statsdTagsFormat { - case "datadog": - conf.StatsD.TagsFormat = statsd.Datadog - case "influxdb": - conf.StatsD.TagsFormat = statsd.InfluxDB - case "": - default: - return nil, errors.Errorf("Unknown tags format %s", statsdTagsFormat) + return errors.Errorf("Unknown tag %v", opt.Option) } } - return conf, nil + switch { + case len(C.Secret) == 1+SimpleSecretLength && bytes.HasPrefix(C.Secret, []byte{0xdd}): + C.SecretMode = SecretModeSecured + C.Secret = bytes.TrimPrefix(C.Secret, []byte{0xdd}) + case len(C.Secret) == SimpleSecretLength: + C.SecretMode = SecretModeSimple + default: + return errors.New("Incorrect secret") + } + + return nil +} + +func InitPublicAddress() error { + if C.PublicIPv4Addr.Port == 0 { + C.PublicIPv4Addr.Port = C.ListenAddr.Port + } + if C.PublicIPv6Addr.Port == 0 { + C.PublicIPv6Addr.Port = C.ListenAddr.Port + } + + foundAddress := C.PublicIPv4Addr.IP != nil || C.PublicIPv6Addr.IP != nil + if C.PublicIPv4Addr.IP == nil { + ip, err := getGlobalIPv4() + if err != nil { + zap.S().Warnw("Cannot resolve public address", "error", err) + } else { + C.PublicIPv4Addr.IP = ip + foundAddress = true + } + } + if C.PublicIPv6Addr.IP == nil { + ip, err := getGlobalIPv6() + if err != nil { + zap.S().Warnw("Cannot resolve public address", "error", err) + } else { + C.PublicIPv6Addr.IP = ip + foundAddress = true + } + } + + if !foundAddress { + return errors.New("Cannot resolve any public address") + } + + return nil } diff --git a/config/global_ips.go b/config/global_ips.go index 9a144a6..f2e72de 100644 --- a/config/global_ips.go +++ b/config/global_ips.go @@ -2,28 +2,42 @@ package config import ( "context" + "io" "io/ioutil" "net" "net/http" "strings" + "time" "github.com/juju/errors" ) -const ifconfigAddress = "https://ifconfig.co/ip" +const ( + ifconfigAddress = "https://ifconfig.co/ip" + ifconfigTimeout = 10 * time.Second +) func getGlobalIPv4() (net.IP, error) { - return fetchIP("tcp4") + ip, err := fetchIP("tcp4") + if err != nil || ip.To4() == nil { + return nil, errors.Annotate(err, "Cannot find public ipv4 address") + } + return ip, nil } func getGlobalIPv6() (net.IP, error) { - return fetchIP("tcp6") + ip, err := fetchIP("tcp6") + if err != nil || ip.To4() != nil { + return nil, errors.Annotate(err, "Cannot find public ipv6 address") + } + return ip, nil } func fetchIP(network string) (net.IP, error) { dialer := &net.Dialer{FallbackDelay: -1} client := &http.Client{ - Jar: nil, + Jar: nil, + Timeout: ifconfigTimeout, Transport: &http.Transport{ DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) { return dialer.DialContext(ctx, network, addr) @@ -33,13 +47,16 @@ func fetchIP(network string) (net.IP, error) { resp, err := client.Get(ifconfigAddress) if err != nil { - return nil, err + if resp != nil { + io.Copy(ioutil.Discard, resp.Body) // nolint: errcheck + } + return nil, errors.Annotate(err, "Cannot perform a request") } defer resp.Body.Close() // nolint: errcheck respDataBytes, err := ioutil.ReadAll(resp.Body) if err != nil { - return nil, err + return nil, errors.Annotate(err, "Cannot read response body") } respData := strings.TrimSpace(string(respDataBytes)) diff --git a/config/urls.go b/config/urls.go index 5f4e6d8..2d95486 100644 --- a/config/urls.go +++ b/config/urls.go @@ -1,15 +1,42 @@ package config import ( - "net" + "encoding/hex" "net/url" - "strconv" ) -func getURLs(addr net.IP, port uint16, secret string) (urls URLs) { +type URLs struct { + TG string `json:"tg_url"` + TMe string `json:"tme_url"` + TGQRCode string `json:"tg_qrcode"` + TMeQRCode string `json:"tme_qrcode"` +} + +type IPURLs struct { + IPv4 URLs `json:"ipv4"` + IPv6 URLs `json:"ipv6"` + BotSecret string `json:"secret_for_mtproxybot"` +} + +func GetURLs() (urls IPURLs) { + secret := "" + switch C.SecretMode { + case SecretModeSimple: + secret = hex.EncodeToString(C.Secret) + case SecretModeSecured: + secret = "dd" + hex.EncodeToString(C.Secret) + } + + urls.IPv4 = makeURLs(&C.PublicIPv4Addr, secret) + urls.IPv6 = makeURLs(&C.PublicIPv6Addr, secret) + urls.BotSecret = secret + + return urls +} + +func makeURLs(addr *Addr, secret string) (urls URLs) { values := url.Values{} - values.Set("server", addr.String()) - values.Set("port", strconv.Itoa(int(port))) + values.Set("address", addr.String()) values.Set("secret", secret) urls.TG = makeTGURL(values) diff --git a/conntypes/dc.go b/conntypes/dc.go new file mode 100644 index 0000000..3cf2850 --- /dev/null +++ b/conntypes/dc.go @@ -0,0 +1,5 @@ +package conntypes + +type DC int16 + +const DCDefaultIdx DC = 1 diff --git a/newprotocol/connection_protocol.go b/conntypes/protocol.go similarity index 95% rename from newprotocol/connection_protocol.go rename to conntypes/protocol.go index 621c60c..21da2c0 100644 --- a/newprotocol/connection_protocol.go +++ b/conntypes/protocol.go @@ -1,4 +1,4 @@ -package newprotocol +package conntypes type ConnectionProtocol uint8 diff --git a/newprotocol/connection_type.go b/conntypes/type.go similarity index 96% rename from newprotocol/connection_type.go rename to conntypes/type.go index 374a5e1..11634f2 100644 --- a/newprotocol/connection_type.go +++ b/conntypes/type.go @@ -1,4 +1,4 @@ -package newprotocol +package conntypes type ConnectionType uint8 diff --git a/main.go b/main.go index 022d1ff..054292e 100644 --- a/main.go +++ b/main.go @@ -9,8 +9,8 @@ import ( "github.com/juju/errors" kingpin "gopkg.in/alecthomas/kingpin.v2" - "github.com/9seconds/mtg/newcli" - "github.com/9seconds/mtg/newconfig" + "github.com/9seconds/mtg/cli" + "github.com/9seconds/mtg/config" ) var version = "dev" // this has to be set by build ld flags @@ -144,45 +144,45 @@ func main() { app.HelpFlag.Short('h') if err := setRLimit(); err != nil { - newcli.Fatal(err.Error()) + cli.Fatal(err.Error()) } switch kingpin.MustParse(app.Parse(os.Args[1:])) { case generateSecretCommand.FullCommand(): - newcli.Generate(*generateSecretType) + cli.Generate(*generateSecretType) case proxyCommand.FullCommand(): - err := newconfig.Init( - newconfig.ConfigOpt{Option: newconfig.OptionTypeDebug, Value: *proxyDebug}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeVerbose, Value: *proxyVerbose}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeBindIP, Value: *proxyBindIP}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeBindPort, Value: *proxyBindPort}, - newconfig.ConfigOpt{Option: newconfig.OptionTypePublicIPv4, Value: *proxyPublicIPv4}, - newconfig.ConfigOpt{Option: newconfig.OptionTypePublicIPv4Port, Value: *proxyPublicIPv4Port}, - newconfig.ConfigOpt{Option: newconfig.OptionTypePublicIPv6, Value: *proxyPublicIPv6}, - newconfig.ConfigOpt{Option: newconfig.OptionTypePublicIPv6Port, Value: *proxyPublicIPv6Port}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeStatsIP, Value: *proxyStatsIP}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeStatsPort, Value: *proxyStatsPort}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeStatsdIP, Value: *proxyStatsdIP}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeStatsdPort, Value: *proxyStatsdPort}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeStatsdNetwork, Value: *proxyStatsdNetwork}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeStatsdPrefix, Value: *proxyStatsdPrefix}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeStatsdTagsFormat, Value: *proxyStatsdTagsFormat}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeStatsdTags, Value: *proxyStatsdTags}, - newconfig.ConfigOpt{Option: newconfig.OptionTypePrometheusPrefix, Value: *proxyPrometheusPrefix}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeWriteBufferSize, Value: *proxyWriteBufferSize}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeReadBufferSize, Value: *proxyReadBufferSize}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeAntiReplayMaxSize, Value: *proxyAntiReplayMaxSize}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeAntiReplayEvictionTime, Value: *proxyAntiReplayEvictionTime}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeSecret, Value: *proxySecret}, - newconfig.ConfigOpt{Option: newconfig.OptionTypeAdtag, Value: *proxyAdtag}, + err := config.Init( + config.ConfigOpt{Option: config.OptionTypeDebug, Value: *proxyDebug}, + config.ConfigOpt{Option: config.OptionTypeVerbose, Value: *proxyVerbose}, + config.ConfigOpt{Option: config.OptionTypeBindIP, Value: *proxyBindIP}, + config.ConfigOpt{Option: config.OptionTypeBindPort, Value: *proxyBindPort}, + config.ConfigOpt{Option: config.OptionTypePublicIPv4, Value: *proxyPublicIPv4}, + config.ConfigOpt{Option: config.OptionTypePublicIPv4Port, Value: *proxyPublicIPv4Port}, + config.ConfigOpt{Option: config.OptionTypePublicIPv6, Value: *proxyPublicIPv6}, + config.ConfigOpt{Option: config.OptionTypePublicIPv6Port, Value: *proxyPublicIPv6Port}, + config.ConfigOpt{Option: config.OptionTypeStatsIP, Value: *proxyStatsIP}, + config.ConfigOpt{Option: config.OptionTypeStatsPort, Value: *proxyStatsPort}, + config.ConfigOpt{Option: config.OptionTypeStatsdIP, Value: *proxyStatsdIP}, + config.ConfigOpt{Option: config.OptionTypeStatsdPort, Value: *proxyStatsdPort}, + config.ConfigOpt{Option: config.OptionTypeStatsdNetwork, Value: *proxyStatsdNetwork}, + config.ConfigOpt{Option: config.OptionTypeStatsdPrefix, Value: *proxyStatsdPrefix}, + config.ConfigOpt{Option: config.OptionTypeStatsdTagsFormat, Value: *proxyStatsdTagsFormat}, + config.ConfigOpt{Option: config.OptionTypeStatsdTags, Value: *proxyStatsdTags}, + config.ConfigOpt{Option: config.OptionTypePrometheusPrefix, Value: *proxyPrometheusPrefix}, + config.ConfigOpt{Option: config.OptionTypeWriteBufferSize, Value: *proxyWriteBufferSize}, + config.ConfigOpt{Option: config.OptionTypeReadBufferSize, Value: *proxyReadBufferSize}, + config.ConfigOpt{Option: config.OptionTypeAntiReplayMaxSize, Value: *proxyAntiReplayMaxSize}, + config.ConfigOpt{Option: config.OptionTypeAntiReplayEvictionTime, Value: *proxyAntiReplayEvictionTime}, + config.ConfigOpt{Option: config.OptionTypeSecret, Value: *proxySecret}, + config.ConfigOpt{Option: config.OptionTypeAdtag, Value: *proxyAdtag}, ) if err != nil { - newcli.Fatal(err.Error()) + cli.Fatal(err.Error()) } - if err := newcli.Proxy(); err != nil { - newcli.Fatal(err.Error()) + if err := cli.Proxy(); err != nil { + cli.Fatal(err.Error()) } } } diff --git a/newantireplay/cache.go b/newantireplay/cache.go deleted file mode 100644 index 71f8d4d..0000000 --- a/newantireplay/cache.go +++ /dev/null @@ -1,32 +0,0 @@ -package newantireplay - -import ( - "github.com/allegro/bigcache" - - "github.com/9seconds/mtg/newconfig" -) - -var cache *bigcache.BigCache - -func Add(data []byte) { - cache.Set(string(data), nil) -} - -func Has(data []byte) bool { - _, err := cache.Get(string(data)) - return err == nil -} - -func Init() { - c, err := bigcache.NewBigCache(bigcache.Config{ - Shards: 1024, - LifeWindow: newconfig.C.AntiReplay.EvictionTime, - Hasher: hasher{}, - HardMaxCacheSize: newconfig.C.AntiReplay.MaxSize, - }) - if err != nil { - panic(err) - } - - cache = c -} diff --git a/newcli/proxy.go b/newcli/proxy.go deleted file mode 100644 index ac9757b..0000000 --- a/newcli/proxy.go +++ /dev/null @@ -1,61 +0,0 @@ -package newcli - -import ( - "os" - "time" - - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - - "github.com/9seconds/mtg/newconfig" - "github.com/9seconds/mtg/newstats" - "github.com/9seconds/mtg/ntp" -) - -func Proxy() error { - atom := zap.NewAtomicLevel() - switch { - case newconfig.C.Debug: - atom.SetLevel(zapcore.DebugLevel) - case newconfig.C.Verbose: - atom.SetLevel(zapcore.InfoLevel) - default: - atom.SetLevel(zapcore.ErrorLevel) - } - - encoderCfg := zap.NewProductionEncoderConfig() - logger := zap.New(zapcore.NewCore( - zapcore.NewJSONEncoder(encoderCfg), - zapcore.Lock(os.Stderr), - atom, - )) - zap.ReplaceGlobals(logger) - defer logger.Sync() // nolint: errcheck - - if err := newconfig.InitPublicAddress(); err != nil { - Fatal(err.Error()) - } - zap.S().Debugw("Configuration", "config", newconfig.C) - - if len(newconfig.C.AdTag) > 0 { - zap.S().Infow("Use middle proxy connection to Telegram") - diff, err := ntp.Fetch() - if err != nil { - Fatal("Cannot fetch time data from NTP") - } - if diff > time.Second { - Fatal("Your local time is skewed and drift is bigger than a second. Please sync your time.") - } - go ntp.AutoUpdate() - } else { - zap.S().Infow("Use direct connection to Telegram") - } - - PrintJSONStdout(newconfig.GetURLs()) - - if err := newstats.Init(); err != nil { - Fatal(err.Error()) - } - - return nil -} diff --git a/newconfig/config.go b/newconfig/config.go deleted file mode 100644 index b15de57..0000000 --- a/newconfig/config.go +++ /dev/null @@ -1,258 +0,0 @@ -package newconfig - -import ( - "bytes" - "encoding/json" - "net" - "strconv" - "time" - - "github.com/juju/errors" - "go.uber.org/zap" - statsd "gopkg.in/alexcesaro/statsd.v2" -) - -type SecretMode uint8 - -func (s SecretMode) String() string { - switch s { - case SecretModeSimple: - return "simple" - case SecretModeSecured: - return "secured" - } - return "tls" -} - -const ( - SecretModeSimple SecretMode = iota - SecretModeSecured - SecretModeTLS -) - -const SimpleSecretLength = 16 - -type OptionType uint8 - -const ( - OptionTypeDebug OptionType = iota - OptionTypeVerbose - - OptionTypeBindIP - OptionTypeBindPort - OptionTypePublicIPv4 - OptionTypePublicIPv4Port - OptionTypePublicIPv6 - OptionTypePublicIPv6Port - OptionTypeStatsIP - OptionTypeStatsPort - - OptionTypeStatsdIP - OptionTypeStatsdPort - OptionTypeStatsdNetwork - OptionTypeStatsdPrefix - OptionTypeStatsdTagsFormat - OptionTypeStatsdTags - OptionTypePrometheusPrefix - - OptionTypeWriteBufferSize - OptionTypeReadBufferSize - - OptionTypeAntiReplayMaxSize - OptionTypeAntiReplayEvictionTime - - OptionTypeSecret - OptionTypeAdtag -) - -type BufferSize struct { - Read int `json:"read"` - Write int `json:"write"` -} - -type AntiReplay struct { - MaxSize int `json:"max_size"` - EvictionTime time.Duration `json:"duration"` -} - -type Stats struct { - Prefix string `json:"prefix"` -} - -type StatsdStats struct { - Stats - - Addr Addr `json:"addr"` - Tags map[string]string `json:"tags"` - TagsFormat statsd.TagFormat `json:"format"` -} - -type PrometheusStats struct { - Stats -} - -type Addr struct { - IP net.IP `json:"ip"` - Port int `json:"port"` - net string -} - -func (a Addr) Network() string { - if a.net == "" { - return "tcp" - } - return a.net -} - -func (a Addr) String() string { - return net.JoinHostPort(a.IP.String(), strconv.Itoa(a.Port)) -} - -func (a Addr) MarshalJSON() ([]byte, error) { - data := map[string]string{ - "network": a.Network(), - "addr": a.String(), - } - return json.Marshal(data) -} - -type Config struct { - BufferSize BufferSize `json:"buffer_size"` - AntiReplay AntiReplay `json:"anti_replay"` - - ListenAddr Addr `json:"listen_addr"` - PublicIPv4Addr Addr `json:"public_ipv4_addr"` - PublicIPv6Addr Addr `json:"public_ipv6_addr"` - StatsAddr Addr `json:"stats_addr"` - - StatsdStats StatsdStats `json:"stats_statsd"` - PrometheusStats PrometheusStats `json:"stats_prometheus"` - - Debug bool `json:"debug"` - Verbose bool `json:"verbose"` - SecretMode SecretMode `json:"secret_mode"` - Secret []byte `json:"secret"` - AdTag []byte `json:"adtag"` -} - -func (c Config) String() string { - data, _ := json.Marshal(c) - return string(data) -} - -type ConfigOpt struct { - Option OptionType - Value interface{} -} - -var C = Config{} - -func Init(options ...ConfigOpt) error { // nolint: gocyclo - for _, opt := range options { - switch opt.Option { - case OptionTypeDebug: - C.Debug = opt.Value.(bool) - case OptionTypeVerbose: - C.Verbose = opt.Value.(bool) - case OptionTypeBindIP: - C.ListenAddr.IP = opt.Value.(net.IP) - case OptionTypeBindPort: - C.ListenAddr.Port = int(opt.Value.(uint16)) - case OptionTypePublicIPv4: - C.PublicIPv4Addr.IP = opt.Value.(net.IP) - case OptionTypePublicIPv4Port: - C.PublicIPv4Addr.Port = int(opt.Value.(uint16)) - case OptionTypePublicIPv6: - C.PublicIPv6Addr.IP = opt.Value.(net.IP) - case OptionTypePublicIPv6Port: - C.PublicIPv6Addr.Port = int(opt.Value.(uint16)) - case OptionTypeStatsIP: - C.StatsAddr.IP = opt.Value.(net.IP) - case OptionTypeStatsPort: - C.StatsAddr.Port = int(opt.Value.(uint16)) - case OptionTypeStatsdIP: - C.StatsdStats.Addr.IP = opt.Value.(net.IP) - case OptionTypeStatsdPort: - C.StatsdStats.Addr.Port = int(opt.Value.(uint16)) - case OptionTypeStatsdNetwork: - C.StatsdStats.Addr.net = opt.Value.(string) - case OptionTypeStatsdPrefix: - C.StatsdStats.Prefix = opt.Value.(string) - case OptionTypeStatsdTagsFormat: - value := opt.Value.(string) - switch value { - case "datadog": - C.StatsdStats.TagsFormat = statsd.Datadog - case "influxdb": - C.StatsdStats.TagsFormat = statsd.InfluxDB - default: - return errors.Errorf("Incorrect statsd tag %s", value) - } - case OptionTypeStatsdTags: - C.StatsdStats.Tags = opt.Value.(map[string]string) - case OptionTypePrometheusPrefix: - C.PrometheusStats.Prefix = opt.Value.(string) - case OptionTypeWriteBufferSize: - C.BufferSize.Write = int(opt.Value.(uint32)) - case OptionTypeReadBufferSize: - C.BufferSize.Read = int(opt.Value.(uint32)) - case OptionTypeAntiReplayMaxSize: - C.AntiReplay.MaxSize = opt.Value.(int) - case OptionTypeAntiReplayEvictionTime: - C.AntiReplay.EvictionTime = opt.Value.(time.Duration) - case OptionTypeSecret: - C.Secret = opt.Value.([]byte) - case OptionTypeAdtag: - C.AdTag = opt.Value.([]byte) - default: - return errors.Errorf("Unknown tag %v", opt.Option) - } - } - - switch { - case len(C.Secret) == 1+SimpleSecretLength && bytes.HasPrefix(C.Secret, []byte{0xdd}): - C.SecretMode = SecretModeSecured - C.Secret = bytes.TrimPrefix(C.Secret, []byte{0xdd}) - case len(C.Secret) == SimpleSecretLength: - C.SecretMode = SecretModeSimple - default: - return errors.New("Incorrect secret") - } - - return nil -} - -func InitPublicAddress() error { - if C.PublicIPv4Addr.Port == 0 { - C.PublicIPv4Addr.Port = C.ListenAddr.Port - } - if C.PublicIPv6Addr.Port == 0 { - C.PublicIPv6Addr.Port = C.ListenAddr.Port - } - - foundAddress := C.PublicIPv4Addr.IP != nil || C.PublicIPv6Addr.IP != nil - if C.PublicIPv4Addr.IP == nil { - ip, err := getGlobalIPv4() - if err != nil { - zap.S().Warnw("Cannot resolve public address", "error", err) - } else { - C.PublicIPv4Addr.IP = ip - foundAddress = true - } - } - if C.PublicIPv6Addr.IP == nil { - ip, err := getGlobalIPv6() - if err != nil { - zap.S().Warnw("Cannot resolve public address", "error", err) - } else { - C.PublicIPv6Addr.IP = ip - foundAddress = true - } - } - - if !foundAddress { - return errors.New("Cannot resolve any public address") - } - - return nil -} diff --git a/newobfuscated2/client_protocol.go b/newobfuscated2/client_protocol.go deleted file mode 100644 index b60ca7d..0000000 --- a/newobfuscated2/client_protocol.go +++ /dev/null @@ -1,95 +0,0 @@ -package newobfuscated2 - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/sha256" - "encoding/binary" - "io" - "time" - - "github.com/juju/errors" - - "github.com/9seconds/mtg/newantireplay" - "github.com/9seconds/mtg/newconfig" - "github.com/9seconds/mtg/newprotocol" - "github.com/9seconds/mtg/newwrappers" -) - -const clientProtocolHandshakeTimeout = 10 * time.Second - -type ClientProtocol struct { - newprotocol.BaseProtocol -} - -func (c *ClientProtocol) Handshake(socket newwrappers.StreamReadWriteCloser) (newwrappers.StreamReadWriteCloser, error) { - fm, err := c.ReadFrame(socket) - if err != nil { - return nil, errors.Annotate(err, "Cannot make client handshake") - } - - decHasher := sha256.New() - decHasher.Write(fm.key()) // nolint: errcheck - decHasher.Write(newconfig.C.Secret) // nolint: errcheck - decryptor := makeStreamCipher(decHasher.Sum(nil), fm.iv()) - - invertedFrame := fm.invert() - encHasher := sha256.New() - encHasher.Write(invertedFrame.key()) // nolint: errcheck - encHasher.Write(newconfig.C.Secret) // nolint: errcheck - encryptor := makeStreamCipher(encHasher.Sum(nil), invertedFrame.iv()) - - decryptedFrame := frame{} - decryptor.XORKeyStream(decryptedFrame.bytes(), fm.bytes()) - - magic := decryptedFrame.magic() - switch { - case bytes.Equal(magic, newprotocol.ConnectionTagAbridged): - c.ConnectionType = newprotocol.ConnectionTypeAbridged - case bytes.Equal(magic, newprotocol.ConnectionTagIntermediate): - c.ConnectionType = newprotocol.ConnectionTypeIntermediate - case bytes.Equal(magic, newprotocol.ConnectionTagSecure): - c.ConnectionType = newprotocol.ConnectionTypeSecure - default: - return nil, errors.New("Unknown connection type") - } - - c.ConnectionProtocol = newprotocol.ConnectionProtocolIPv4 - if socket.LocalAddr().IP.To4() == nil { - c.ConnectionProtocol = newprotocol.ConnectionProtocolIPv6 - } - - buf := bytes.NewReader(decryptedFrame.dc()) - if err := binary.Read(buf, binary.LittleEndian, &c.DC); err != nil { - c.DC = 1 - } - - antiReplayKey := decryptedFrame.unique() - if newantireplay.Has(antiReplayKey) { - return nil, errors.New("Replay attack is detected") - } - newantireplay.Add(antiReplayKey) - - return newwrappers.NewObfuscated2(socket, encryptor, decryptor), nil -} - -func (c *ClientProtocol) ReadFrame(socket newwrappers.StreamReader) (fm frame, err error) { - if _, err := io.ReadFull(handshakeReader{socket}, fm.bytes()); err != nil { - err = errors.Annotate(err, "Cannot extract obfuscated2 frame") - } - return -} - -type handshakeReader struct { - parent newwrappers.StreamReader -} - -func (h handshakeReader) Read(p []byte) (int, error) { - return h.parent.ReadTimeout(p, clientProtocolHandshakeTimeout) -} - -func makeStreamCipher(key, iv []byte) cipher.Stream { - block, _ := aes.NewCipher(key) // nolint: gosec - return cipher.NewCTR(block, iv) -} diff --git a/newobfuscated2/frame.go b/newobfuscated2/frame.go deleted file mode 100644 index fab7efc..0000000 --- a/newobfuscated2/frame.go +++ /dev/null @@ -1,54 +0,0 @@ -package newobfuscated2 - -const ( - frameLenKey = 32 - frameLenIV = 16 - frameLenMagic = 4 - frameLenDC = 2 - - frameOffsetFirst = 8 - frameOffsetKey = frameOffsetFirst + frameLenKey - frameOffsetIV = frameOffsetKey + frameLenIV - frameOffsetMagic = frameOffsetIV + frameLenMagic - frameOffsetDC = frameOffsetMagic + frameLenDC - - frameLen = 64 -) - -// [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd] -type frame struct { - data [frameLen]byte -} - -func (f *frame) bytes() []byte { - return f.data[:] -} - -func (f *frame) key() []byte { - return f.data[frameOffsetFirst:frameOffsetKey] -} - -func (f *frame) iv() []byte { - return f.data[frameOffsetKey:frameOffsetIV] -} - -func (f *frame) magic() []byte { - return f.data[frameOffsetIV:frameOffsetMagic] -} - -func (f *frame) dc() []byte { - return f.data[frameOffsetMagic:frameOffsetDC] -} - -func (f *frame) unique() []byte { - return f.data[frameOffsetFirst:frameOffsetDC] -} - -func (f *frame) invert() (nf frame) { - nf = *f - for i := 0; i < frameLenKey+frameLenIV; i++ { - nf.data[frameOffsetFirst+i] = nf.data[frameOffsetIV-1-i] - } - - return -} diff --git a/newobfuscated2/telegram_protocol.go b/newobfuscated2/telegram_protocol.go deleted file mode 100644 index 4286bd0..0000000 --- a/newobfuscated2/telegram_protocol.go +++ /dev/null @@ -1,61 +0,0 @@ -package newobfuscated2 - -import ( - "crypto/rand" - - "github.com/juju/errors" - - "github.com/9seconds/mtg/newprotocol" - "github.com/9seconds/mtg/newwrappers" -) - -type TelegramProtocol struct { - newprotocol.BaseProtocol -} - -func (t *TelegramProtocol) Handshake(socketRaw newwrappers.Wrap, client *ClientProtocol) (newwrappers.StreamReadWriteCloser, error) { - socket := socketRaw.(newwrappers.StreamReadWriteCloser) - fm := generateFrame(client) - data := fm.bytes() - - encryptor := makeStreamCipher(fm.key(), fm.iv()) - decryptedFrame := fm.invert() - decryptor := makeStreamCipher(decryptedFrame.key(), decryptedFrame.iv()) - - copyFrame := make([]byte, frameLen) - copy(copyFrame[:frameOffsetIV], data[:frameOffsetIV]) - encryptor.XORKeyStream(data, data) - copy(data[:frameOffsetIV], copyFrame[:frameOffsetIV]) - - if _, err := socket.Write(data); err != nil { - return nil, errors.Annotate(err, "Cannot write handshate frame to Telegram") - } - - return newwrappers.NewObfuscated2(socket, encryptor, decryptor), nil -} - -func generateFrame(client *ClientProtocol) (fm frame) { - for { - data := fm.bytes() - if _, err := rand.Read(data); err != nil { - continue - } - if data[0] == 0xef { - continue - } - - val := (uint32(data[3]) << 24) | (uint32(data[2]) << 16) | (uint32(data[1]) << 8) | uint32(data[0]) - if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee { - continue - } - - val = (uint32(data[7]) << 24) | (uint32(data[6]) << 16) | (uint32(data[5]) << 8) | uint32(data[4]) - if val == 0x00000000 { - continue - } - - copy(fm.magic(), client.ConnectionType.Tag()) - - return - } -} diff --git a/newprotocol/base_protocol.go b/newprotocol/base_protocol.go deleted file mode 100644 index 5ea7345..0000000 --- a/newprotocol/base_protocol.go +++ /dev/null @@ -1,7 +0,0 @@ -package newprotocol - -type BaseProtocol struct { - ConnectionType ConnectionType - ConnectionProtocol ConnectionProtocol - DC int16 -} diff --git a/newproxy/proxy.go b/newproxy/proxy.go deleted file mode 100644 index aa648ba..0000000 --- a/newproxy/proxy.go +++ /dev/null @@ -1 +0,0 @@ -package newproxy diff --git a/newstats/stats.go b/newstats/stats.go deleted file mode 100644 index 8d83480..0000000 --- a/newstats/stats.go +++ /dev/null @@ -1,93 +0,0 @@ -package newstats - -import ( - "net" - "net/http" - - "github.com/juju/errors" - - "github.com/9seconds/mtg/newconfig" - "github.com/9seconds/mtg/newprotocol" -) - -type Stats interface { - IngressTraffic(int) - EgressTraffic(int) - ClientConnected(newprotocol.ConnectionType, *net.TCPAddr) - ClientDisconnected(newprotocol.ConnectionType, *net.TCPAddr) - Crash() - AntiReplayDetected() -} - -type multiStats []Stats - -func (m multiStats) IngressTraffic(traffic int) { - for i := range m { - go m[i].IngressTraffic(traffic) - } -} - -func (m multiStats) EgressTraffic(traffic int) { - for i := range m { - go m[i].EgressTraffic(traffic) - } -} - -func (m multiStats) ClientConnected(connectionType newprotocol.ConnectionType, addr *net.TCPAddr) { - for i := range m { - go m[i].ClientConnected(connectionType, addr) - } -} - -func (m multiStats) ClientDisconnected(connectionType newprotocol.ConnectionType, addr *net.TCPAddr) { - for i := range m { - go m[i].ClientDisconnected(connectionType, addr) - } -} - -func (m multiStats) Crash() { - for i := range m { - go m[i].Crash() - } -} - -func (m multiStats) AntiReplayDetected() { - for i := range m { - go m[i].AntiReplayDetected() - } -} - -var S Stats - -func Init() error { - mux := http.NewServeMux() - - instanceJSON := newStatsJSON(mux) - instancePrometheus, err := newStatsPrometheus(mux) - if err != nil { - return errors.Annotate(err, "Cannot initialize Prometheus") - } - - stats := []Stats{instanceJSON, instancePrometheus} - if newconfig.C.StatsdStats.Addr.IP != nil { - instanceStatsd, err := newStatsStatsd() - if err != nil { - return errors.Annotate(err, "Cannot initialize StatsD") - } - stats = append(stats, instanceStatsd) - } - - listener, err := net.Listen("tcp", newconfig.C.StatsAddr.String()) - if err != nil { - return errors.Annotate(err, "Cannot initialize stats server") - } - - srv := http.Server{ - Handler: mux, - } - go srv.Serve(listener) // nolint: errcheck - - S = multiStats(stats) - - return nil -} diff --git a/obfuscated2/client_protocol.go b/obfuscated2/client_protocol.go new file mode 100644 index 0000000..c035bcf --- /dev/null +++ b/obfuscated2/client_protocol.go @@ -0,0 +1,94 @@ +package obfuscated2 + +import ( + "bytes" + "crypto/sha256" + "encoding/binary" + "io" + "time" + + "github.com/juju/errors" + + "github.com/9seconds/mtg/antireplay" + "github.com/9seconds/mtg/config" + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/protocol" + "github.com/9seconds/mtg/utils" + "github.com/9seconds/mtg/wrappers" +) + +const clientProtocolHandshakeTimeout = 10 * time.Second + +type ClientProtocol struct { + protocol.BaseProtocol +} + +func (c *ClientProtocol) Handshake(socket wrappers.StreamReadWriteCloser) (wrappers.StreamReadWriteCloser, error) { + fm, err := c.ReadFrame(socket) + if err != nil { + return nil, errors.Annotate(err, "Cannot make client handshake") + } + + decHasher := sha256.New() + decHasher.Write(fm.Key()) // nolint: errcheck + decHasher.Write(config.C.Secret) // nolint: errcheck + decryptor := utils.MakeStreamCipher(decHasher.Sum(nil), fm.IV()) + + invertedFrame := fm.Invert() + encHasher := sha256.New() + encHasher.Write(invertedFrame.Key()) // nolint: errcheck + encHasher.Write(config.C.Secret) // nolint: errcheck + encryptor := utils.MakeStreamCipher(encHasher.Sum(nil), invertedFrame.IV()) + + decryptedFrame := Frame{} + decryptor.XORKeyStream(decryptedFrame.Bytes(), fm.Bytes()) + + magic := decryptedFrame.Magic() + switch { + case bytes.Equal(magic, conntypes.ConnectionTagAbridged): + c.ConnectionType = conntypes.ConnectionTypeAbridged + case bytes.Equal(magic, conntypes.ConnectionTagIntermediate): + c.ConnectionType = conntypes.ConnectionTypeIntermediate + case bytes.Equal(magic, conntypes.ConnectionTagSecure): + c.ConnectionType = conntypes.ConnectionTypeSecure + default: + return nil, errors.New("Unknown connection type") + } + + c.ConnectionProtocol = conntypes.ConnectionProtocolIPv4 + if socket.LocalAddr().IP.To4() == nil { + c.ConnectionProtocol = conntypes.ConnectionProtocolIPv6 + } + + buf := bytes.NewReader(decryptedFrame.DC()) + if err := binary.Read(buf, binary.LittleEndian, &c.DC); err != nil { + c.DC = conntypes.DCDefaultIdx + } + + antiReplayKey := decryptedFrame.Unique() + if antireplay.Has(antiReplayKey) { + return nil, errors.New("Replay attack is detected") + } + antireplay.Add(antiReplayKey) + + return wrappers.NewObfuscated2(socket, encryptor, decryptor), nil +} + +func (c *ClientProtocol) ReadFrame(socket wrappers.StreamReader) (fm Frame, err error) { + if _, err = io.ReadFull(handshakeReader{socket}, fm.Bytes()); err != nil { + err = errors.Annotate(err, "Cannot extract obfuscated2 frame") + } + return +} + +type handshakeReader struct { + parent wrappers.StreamReader +} + +func (h handshakeReader) Read(p []byte) (int, error) { + return h.parent.ReadTimeout(p, clientProtocolHandshakeTimeout) +} + +func MakeClientProtocol() protocol.ClientProtocol { + return &ClientProtocol{} +} diff --git a/obfuscated2/frame.go b/obfuscated2/frame.go index 33a8dd4..6df81c8 100644 --- a/obfuscated2/frame.go +++ b/obfuscated2/frame.go @@ -1,17 +1,5 @@ package obfuscated2 -import ( - "bytes" - "crypto/rand" - "encoding/binary" - "io" - - "github.com/juju/errors" - - "github.com/9seconds/mtg/mtproto" -) - -// [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd] const ( frameLenKey = 32 frameLenIV = 16 @@ -24,98 +12,43 @@ const ( frameOffsetMagic = frameOffsetIV + frameLenMagic frameOffsetDC = frameOffsetMagic + frameLenDC - FrameLen = 64 + frameLen = 64 ) -// Frame represents handshake frame. Telegram sends 64 bytes of obfuscated2 -// initialization data first. -// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/ -type Frame []byte - -// Key returns AES encryption key. -func (f Frame) Key() []byte { - return f[frameOffsetFirst:frameOffsetKey] +// [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd] +type Frame struct { + data [frameLen]byte } -// IV returns AES encryption initialization vector -func (f Frame) IV() []byte { - return f[frameOffsetKey:frameOffsetIV] +func (f *Frame) Bytes() []byte { + return f.data[:] } -// Magic returns magic bytes from last 8 bytes of frame. Telegram checks -// for values there. If after decryption magic is not as expected, -// connection considered as failed. -func (f Frame) Magic() []byte { - return f[frameOffsetIV:frameOffsetMagic] +func (f *Frame) Key() []byte { + return f.data[frameOffsetFirst:frameOffsetKey] } -// DC returns number of datacenter IP client wants to use. -func (f Frame) DC() (n int16) { - buf := bytes.NewReader(f[frameOffsetMagic:frameOffsetDC]) - if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { - n = 1 +func (f *Frame) IV() []byte { + return f.data[frameOffsetKey:frameOffsetIV] +} + +func (f *Frame) Magic() []byte { + return f.data[frameOffsetIV:frameOffsetMagic] +} + +func (f *Frame) DC() []byte { + return f.data[frameOffsetMagic:frameOffsetDC] +} + +func (f *Frame) Unique() []byte { + return f.data[frameOffsetFirst:frameOffsetDC] +} + +func (f *Frame) Invert() (nf Frame) { + nf = *f + for i := 0; i < frameLenKey+frameLenIV; i++ { + nf.data[frameOffsetFirst+i] = f.data[frameOffsetIV-1-i] } return } - -// ConnectionType identifies connection type of the handshake frame. -func (f Frame) ConnectionType() (mtproto.ConnectionType, error) { - return mtproto.ConnectionTagFromHandshake(f.Magic()) -} - -// Invert inverts frame for extracting encryption keys. Pkease check that link: -// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/ -func (f Frame) Invert() Frame { - reversed := make(Frame, FrameLen) - copy(reversed, f) - - for i := 0; i < frameLenKey+frameLenIV; i++ { - reversed[frameOffsetFirst+i] = f[frameOffsetIV-1-i] - } - - return reversed -} - -// ExtractFrame extracts exact obfuscated2 handshake frame from given reader. -func ExtractFrame(conn io.Reader) (Frame, error) { - frame := make(Frame, FrameLen) - buf := bytes.NewBuffer(frame) - buf.Reset() - - if _, err := io.CopyN(buf, conn, FrameLen); err != nil { - return nil, errors.Annotate(err, "Cannot extract obfuscated header") - } - copy(frame, buf.Bytes()) - - return frame, nil -} - -func generateFrame(connectionType mtproto.ConnectionType) Frame { - frame := make(Frame, FrameLen) - - for { - if _, err := rand.Read(frame); err != nil { - continue - } - if frame[0] == 0xef { - continue - } - - val := (uint32(frame[3]) << 24) | (uint32(frame[2]) << 16) | (uint32(frame[1]) << 8) | uint32(frame[0]) - if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee { - continue - } - - val = (uint32(frame[7]) << 24) | (uint32(frame[6]) << 16) | (uint32(frame[5]) << 8) | uint32(frame[4]) - if val == 0x00000000 { - continue - } - - // error has to be checked before calling this function - tag, _ := connectionType.Tag() // nolint: errcheck, gosec - copy(frame.Magic(), tag) - - return frame - } -} diff --git a/obfuscated2/telegram_protocol.go b/obfuscated2/telegram_protocol.go new file mode 100644 index 0000000..230193c --- /dev/null +++ b/obfuscated2/telegram_protocol.go @@ -0,0 +1,78 @@ +package obfuscated2 + +import ( + "crypto/rand" + + "github.com/juju/errors" + + "github.com/9seconds/mtg/protocol" + "github.com/9seconds/mtg/telegram" + "github.com/9seconds/mtg/utils" + "github.com/9seconds/mtg/wrappers" +) + +type TelegramProtocol struct { + protocol.BaseProtocol + + dialer telegram.Telegram +} + +func (t *TelegramProtocol) Handshake(req *protocol.TelegramRequest) (wrappers.Wrap, error) { + socket, err := t.dialer.Dial(req.Ctx, + req.Cancel, + req.ClientProtocol.GetDC(), + req.ClientProtocol.GetConnectionProtocol()) + if err != nil { + return nil, errors.Annotate(err, "Cannot dial to Telegram") + } + fm := generateFrame(req.ClientProtocol) + data := fm.Bytes() + + encryptor := utils.MakeStreamCipher(fm.Key(), fm.IV()) + decryptedFrame := fm.Invert() + decryptor := utils.MakeStreamCipher(decryptedFrame.Key(), decryptedFrame.IV()) + + copyFrame := make([]byte, frameLen) + copy(copyFrame[:frameOffsetIV], data[:frameOffsetIV]) + encryptor.XORKeyStream(data, data) + copy(data[:frameOffsetIV], copyFrame[:frameOffsetIV]) + + if _, err := socket.Write(data); err != nil { + return nil, errors.Annotate(err, "Cannot write handshate frame to Telegram") + } + + return wrappers.NewObfuscated2(socket, encryptor, decryptor), nil +} + +func MakeTelegramProtocol(dialer telegram.Telegram) protocol.TelegramProtocol { + return &TelegramProtocol{ + dialer: dialer, + } +} + +func generateFrame(cp protocol.ClientProtocol) (fm Frame) { + data := fm.Bytes() + + for { + if _, err := rand.Read(data); err != nil { + continue + } + if data[0] == 0xef { + continue + } + + val := (uint32(data[3]) << 24) | (uint32(data[2]) << 16) | (uint32(data[1]) << 8) | uint32(data[0]) + if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee { + continue + } + + val = (uint32(data[7]) << 24) | (uint32(data[6]) << 16) | (uint32(data[5]) << 8) | uint32(data[4]) + if val == 0x00000000 { + continue + } + + copy(fm.Magic(), cp.GetConnectionType().Tag()) + + return + } +} diff --git a/protocol/base_protocol.go b/protocol/base_protocol.go new file mode 100644 index 0000000..158ee1f --- /dev/null +++ b/protocol/base_protocol.go @@ -0,0 +1,21 @@ +package protocol + +import "github.com/9seconds/mtg/conntypes" + +type BaseProtocol struct { + ConnectionType conntypes.ConnectionType + ConnectionProtocol conntypes.ConnectionProtocol + DC conntypes.DC +} + +func (b *BaseProtocol) GetConnectionType() conntypes.ConnectionType { + return b.ConnectionType +} + +func (b *BaseProtocol) GetConnectionProtocol() conntypes.ConnectionProtocol { + return b.ConnectionProtocol +} + +func (b *BaseProtocol) GetDC() conntypes.DC { + return b.DC +} diff --git a/protocol/interfaces.go b/protocol/interfaces.go new file mode 100644 index 0000000..4b81ab7 --- /dev/null +++ b/protocol/interfaces.go @@ -0,0 +1,22 @@ +package protocol + +import ( + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/telegram" + "github.com/9seconds/mtg/wrappers" +) + +type ClientProtocol interface { + Handshake(wrappers.StreamReadWriteCloser) (wrappers.StreamReadWriteCloser, error) + GetConnectionType() conntypes.ConnectionType + GetConnectionProtocol() conntypes.ConnectionProtocol + GetDC() conntypes.DC +} + +type ClientProtocolMaker func() ClientProtocol + +type TelegramProtocol interface { + Handshake(*TelegramRequest) (wrappers.Wrap, error) +} + +type TelegramProtocolMaker func(telegram.Telegram) TelegramProtocol diff --git a/protocol/request.go b/protocol/request.go new file mode 100644 index 0000000..ce8728d --- /dev/null +++ b/protocol/request.go @@ -0,0 +1,18 @@ +package protocol + +import ( + "context" + + "go.uber.org/zap" + + "github.com/9seconds/mtg/wrappers" +) + +type TelegramRequest struct { + Logger *zap.SugaredLogger + ClientConn wrappers.StreamReadWriteCloser + ConnID wrappers.ConnID + Ctx context.Context + Cancel context.CancelFunc + ClientProtocol ClientProtocol +} diff --git a/proxy/proxy.go b/proxy/proxy.go index ecda26a..96108b6 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -6,173 +6,123 @@ import ( "net" "sync" - "github.com/gofrs/uuid" - "github.com/juju/errors" "go.uber.org/zap" - "github.com/9seconds/mtg/antireplay" - "github.com/9seconds/mtg/client" "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/mtproto" + "github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/stats" "github.com/9seconds/mtg/telegram" + "github.com/9seconds/mtg/utils" "github.com/9seconds/mtg/wrappers" ) -// Proxy is a core of this program. +const directPipeBufferSize = 1024 * 1024 + type Proxy struct { - antiReplayCache antireplay.Cache - clientInit client.Init - tg telegram.Telegram - conf *config.Config + Logger *zap.SugaredLogger + ClientProtocolMaker protocol.ClientProtocolMaker + TelegramProtocolMaker protocol.TelegramProtocolMaker + TelegramDialer telegram.Telegram } -// Serve runs TCP proxy server. -func (p *Proxy) Serve() error { - lsock, err := net.Listen("tcp", p.conf.BindAddr()) - if err != nil { - return errors.Annotate(err, "Cannot create listen socket") - } - +func (p *Proxy) Serve(listener net.Listener) { for { - if conn, err := lsock.Accept(); err != nil { - zap.S().Errorw("Cannot allocate incoming connection", "error", err) - } else { - go p.accept(conn) + conn, err := listener.Accept() + if err != nil { + p.Logger.Errorw("Cannot allocate incoming connection", "error", err) + continue } + go p.accept(conn) } } func (p *Proxy) accept(conn net.Conn) { - connID := uuid.Must(uuid.NewV4()).String() - log := zap.S().With("connection_id", connID).Named("main") - ctx, cancel := context.WithCancel(context.Background()) - defer func() { - cancel() - conn.Close() // nolint: errcheck, gosec - + conn.Close() if err := recover(); err != nil { - stats.NewCrash() - log.Errorw("Crash of accept handler", "error", err) + stats.S.Crash() + p.Logger.Errorw("Crash of accept handler", "error", err) } }() - log.Infow("Client connected", "addr", conn.RemoteAddr()) + connID := wrappers.NewConnID() + logger := p.Logger.With("connection_id", connID) - clientConn, opts, err := p.clientInit(ctx, cancel, conn, connID, p.antiReplayCache, p.conf) + if err := utils.InitTCP(conn); err != nil { + logger.Errorw("Cannot initialize client TCP connection", "error", err) + return + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + wrappedConn := wrappers.NewClientConn(ctx, cancel, conn, connID) + wrappedConn = wrappers.NewTraffic(wrappedConn) + defer wrappedConn.Close() + + clientProtocol := p.ClientProtocolMaker() + wrappedConn, err := clientProtocol.Handshake(wrappedConn) if err != nil { - log.Errorw("Cannot initialize client connection", "error", err) + logger.Warnw("Cannot perform client handshake", "error", err) return } - defer clientConn.(io.Closer).Close() // nolint: errcheck + defer wrappedConn.Close() - if p.conf.SecureOnly && opts.ConnectionType != mtproto.ConnectionTypeSecure { - log.Errorw("Proxy supports only secure connections", "connection_type", opts.ConnectionType) - return + stats.S.ClientConnected(clientProtocol.GetConnectionType(), wrappedConn.RemoteAddr()) + defer stats.S.ClientDisconnected(clientProtocol.GetConnectionType(), wrappedConn.RemoteAddr()) + logger.Infow("Client connected", "addr", conn.RemoteAddr()) + + req := &protocol.TelegramRequest{ + Logger: logger, + ClientConn: wrappedConn, + ConnID: connID, + Ctx: ctx, + Cancel: cancel, + ClientProtocol: clientProtocol, } - stats.ClientConnected(opts.ConnectionType, clientConn.RemoteAddr()) - defer stats.ClientDisconnected(opts.ConnectionType, clientConn.RemoteAddr()) - - serverConn, err := p.getTelegramConn(ctx, cancel, opts, connID) - if err != nil { - log.Errorw("Cannot initialize server connection", "error", err) - return - } - defer serverConn.(io.Closer).Close() // nolint: errcheck - - go func() { - <-ctx.Done() - serverConn.(io.Closer).Close() // nolint: gosec - clientConn.(io.Closer).Close() // nolint: gosec - }() - - wait := &sync.WaitGroup{} - wait.Add(2) - - if p.conf.UseMiddleProxy() { - clientPacket := clientConn.(wrappers.PacketReadWriteCloser) - serverPacket := serverConn.(wrappers.PacketReadWriteCloser) - go p.middlePipe(clientPacket, serverPacket, wait, &opts.ReadHacks) - p.middlePipe(serverPacket, clientPacket, wait, &opts.WriteHacks) + if len(config.C.AdTag) > 0 { + err = p.acceptMiddleProxyConnection(req) } else { - clientStream := clientConn.(wrappers.StreamReadWriteCloser) - serverStream := serverConn.(wrappers.StreamReadWriteCloser) - go p.directPipe(clientStream, serverStream, wait, p.conf.ReadBufferSize) - p.directPipe(serverStream, clientStream, wait, p.conf.WriteBufferSize) + err = p.acceptDirectConnection(req) } - wait.Wait() - - log.Infow("Client disconnected", "addr", conn.RemoteAddr()) + logger.Infow("Client disconnected", "error", err, "addr", conn.RemoteAddr()) } -func (p *Proxy) getTelegramConn(ctx context.Context, cancel context.CancelFunc, - opts *mtproto.ConnectionOpts, connID string) (wrappers.Wrap, error) { - streamConn, err := p.tg.Dial(ctx, cancel, connID, opts) +func (p *Proxy) acceptDirectConnection(request *protocol.TelegramRequest) error { + telegramProtocol := p.TelegramProtocolMaker(p.TelegramDialer) + telegramConnRaw, err := telegramProtocol.Handshake(request) if err != nil { - return nil, errors.Annotate(err, "Cannot dial to Telegram") + return err } + telegramConn := telegramConnRaw.(wrappers.StreamReadWriteCloser) + defer telegramConn.Close() - packetConn, err := p.tg.Init(opts, streamConn) - if err != nil { - return nil, errors.Annotate(err, "Cannot handshake telegram") - } + wg := &sync.WaitGroup{} + wg.Add(2) - return packetConn, nil + go p.directPipe(telegramConn, request.ClientConn, wg, request.Logger) + go p.directPipe(request.ClientConn, telegramConn, wg, request.Logger) + + <-request.Ctx.Done() + wg.Wait() + + return request.Ctx.Err() } -func (p *Proxy) middlePipe(src wrappers.PacketReadCloser, dst io.Writer, wait *sync.WaitGroup, hacks *mtproto.Hacks) { - defer wait.Done() +func (p *Proxy) directPipe(dst io.Writer, + src io.Reader, + wg *sync.WaitGroup, + logger *zap.SugaredLogger) { + defer wg.Done() - for { - hacks.SimpleAck = false - hacks.QuickAck = false - - packet, err := src.Read() - if err != nil { - src.Logger().Warnw("Cannot read packet", "error", err) - return - } - if _, err = dst.Write(packet); err != nil { - src.Logger().Warnw("Cannot write packet", "error", err) - return - } + buf := make([]byte, directPipeBufferSize) + if _, err := io.CopyBuffer(dst, src, buf); err != nil { + logger.Debugw("Cannot pump sockets", "error", err) } } -func (p *Proxy) directPipe(src wrappers.StreamReadCloser, dst io.Writer, wait *sync.WaitGroup, bufferSize int) { - defer wait.Done() - - buffer := make([]byte, bufferSize) - if _, err := io.CopyBuffer(dst, src, buffer); err != nil { - src.Logger().Warnw("Cannot pump sockets", "error", err) - } -} - -// NewProxy returns new proxy instance. -func NewProxy(conf *config.Config) (*Proxy, error) { - var clientInit client.Init - var tg telegram.Telegram - - cache, err := antireplay.NewCache(conf) - if err != nil { - return nil, errors.Annotate(err, "Cannot make proxy") - } - - if conf.UseMiddleProxy() { - clientInit = client.MiddleInit - tg = telegram.NewMiddleTelegram(conf) - } else { - clientInit = client.DirectInit - tg = telegram.NewDirectTelegram(conf) - } - - return &Proxy{ - antiReplayCache: cache, - conf: conf, - clientInit: clientInit, - tg: tg, - }, nil +func (p *Proxy) acceptMiddleProxyConnection(request *protocol.TelegramRequest) error { + return nil } diff --git a/stats/stats.go b/stats/stats.go index 686163d..5793552 100644 --- a/stats/stats.go +++ b/stats/stats.go @@ -1,175 +1,93 @@ package stats import ( - "encoding/json" - "fmt" - "strconv" - "time" + "net" + "net/http" - humanize "github.com/dustin/go-humanize" + "github.com/juju/errors" "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/mtproto" + "github.com/9seconds/mtg/conntypes" ) -type uptime time.Time - -func (u uptime) MarshalJSON() ([]byte, error) { - duration := time.Since(time.Time(u)) - value := map[string]string{ - "seconds": strconv.Itoa(int(duration.Seconds())), - "human": humanize.Time(time.Time(u)), - } - - return json.Marshal(value) +type Stats interface { + IngressTraffic(int) + EgressTraffic(int) + ClientConnected(conntypes.ConnectionType, *net.TCPAddr) + ClientDisconnected(conntypes.ConnectionType, *net.TCPAddr) + Crash() + AntiReplayDetected() } -type connectionType struct { - IPv6 uint32 `json:"ipv6"` - IPv4 uint32 `json:"ipv4"` -} +type multiStats []Stats -type baseConnections struct { - All connectionType `json:"all"` - Abridged connectionType `json:"abridged"` - Intermediate connectionType `json:"intermediate"` - Secure connectionType `json:"secure"` -} - -type connections struct { - baseConnections -} - -func (c connections) MarshalJSON() ([]byte, error) { - c.All.IPv4 = c.Abridged.IPv4 + c.Intermediate.IPv4 + c.Secure.IPv4 - c.All.IPv6 = c.Abridged.IPv6 + c.Intermediate.IPv6 + c.Secure.IPv6 - - return json.Marshal(c.baseConnections) -} - -type traffic struct { - ingress uint64 - egress uint64 -} - -func (t *traffic) dumpValue(value uint64) map[string]interface{} { - return map[string]interface{}{ - "bytes": value, - "human": humanize.Bytes(value), +func (m multiStats) IngressTraffic(traffic int) { + for i := range m { + go m[i].IngressTraffic(traffic) } } -func (t traffic) MarshalJSON() ([]byte, error) { - value := map[string]map[string]interface{}{ - "ingress": t.dumpValue(t.ingress), - "egress": t.dumpValue(t.egress), - } - - return json.Marshal(value) -} - -type speed struct { - ingress uint64 - egress uint64 -} - -func (s *speed) dumpValue(value uint64) map[string]interface{} { - return map[string]interface{}{ - "bytes/s": value, - "human": fmt.Sprintf("%s/s", humanize.Bytes(value)), +func (m multiStats) EgressTraffic(traffic int) { + for i := range m { + go m[i].EgressTraffic(traffic) } } -func (s speed) MarshalJSON() ([]byte, error) { - value := map[string]map[string]interface{}{ - "ingress": s.dumpValue(s.ingress), - "egress": s.dumpValue(s.egress), +func (m multiStats) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) { + for i := range m { + go m[i].ClientConnected(connectionType, addr) + } +} + +func (m multiStats) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) { + for i := range m { + go m[i].ClientDisconnected(connectionType, addr) + } +} + +func (m multiStats) Crash() { + for i := range m { + go m[i].Crash() + } +} + +func (m multiStats) AntiReplayDetected() { + for i := range m { + go m[i].AntiReplayDetected() + } +} + +var S Stats + +func Init() error { + mux := http.NewServeMux() + + instanceJSON := newStatsJSON(mux) + instancePrometheus, err := newStatsPrometheus(mux) + if err != nil { + return errors.Annotate(err, "Cannot initialize Prometheus") } - return json.Marshal(value) -} - -// Stats represents a statistics of the proxy. -type Stats struct { - URLs config.IPURLs `json:"urls"` - Connections connections `json:"connections"` - Traffic traffic `json:"traffic"` - Speed speed `json:"speed"` - Uptime uptime `json:"uptime"` - Crashes uint32 `json:"crashes"` - - previousTraffic traffic -} - -func (s *Stats) start() { - speedChan := time.Tick(time.Second) - - for { - select { - case <-speedChan: - s.handleSpeed() - case event := <-trafficChan: - s.handleTraffic(event) - case event := <-connectionsChan: - s.handleConnection(event) - case getStatsChan := <-statsChan: - s.handleGetStats(getStatsChan) - case <-crashesChan: - s.handleCrash() + stats := []Stats{instanceJSON, instancePrometheus} + if config.C.StatsdStats.Addr.IP != nil { + instanceStatsd, err := newStatsStatsd() + if err != nil { + return errors.Annotate(err, "Cannot initialize StatsD") } - } -} - -func (s *Stats) handleTraffic(evt trafficData) { - if evt.ingress { - s.Traffic.ingress += uint64(evt.traffic) - } else { - s.Traffic.egress += uint64(evt.traffic) - } -} - -func (s *Stats) handleSpeed() { - s.Speed.ingress = s.Traffic.ingress - s.previousTraffic.ingress - s.Speed.egress = s.Traffic.egress - s.previousTraffic.egress - s.previousTraffic.ingress = s.Traffic.ingress - s.previousTraffic.egress = s.Traffic.egress -} - -func (s *Stats) handleConnection(evt connectionData) { - var inc uint32 = 1 - if !evt.connected { - inc = ^uint32(0) + stats = append(stats, instanceStatsd) } - var conn *connectionType - switch evt.connectionType { - case mtproto.ConnectionTypeAbridged: - conn = &s.Connections.Abridged - case mtproto.ConnectionTypeSecure: - conn = &s.Connections.Secure - default: - conn = &s.Connections.Intermediate + listener, err := net.Listen("tcp", config.C.StatsAddr.String()) + if err != nil { + return errors.Annotate(err, "Cannot initialize stats server") } - if evt.addr.IP.To4() != nil { - conn.IPv4 += inc - } else { - conn.IPv6 += inc + srv := http.Server{ + Handler: mux, } -} + go srv.Serve(listener) // nolint: errcheck -func (s *Stats) handleGetStats(getStatsChan chan<- Stats) { - getStatsChan <- *s -} + S = multiStats(stats) -func (s *Stats) handleCrash() { - s.Crashes++ -} - -// NewStats creates a new instance of Stats structure. -func NewStats(conf *config.Config) *Stats { - return &Stats{ - URLs: conf.GetURLs(), - Uptime: uptime(time.Now()), - } + return nil } diff --git a/newstats/stats_json.go b/stats/stats_json.go similarity index 81% rename from newstats/stats_json.go rename to stats/stats_json.go index a47f964..b180779 100644 --- a/newstats/stats_json.go +++ b/stats/stats_json.go @@ -1,15 +1,16 @@ -package newstats +package stats import ( "encoding/json" "net" "net/http" + "strconv" "sync/atomic" "time" "go.uber.org/zap" - "github.com/9seconds/mtg/newprotocol" + "github.com/9seconds/mtg/conntypes" ) type statsJSON struct { @@ -51,7 +52,8 @@ type statsJSONTraffic struct { type statsJSONUptime time.Time func (s statsJSONUptime) MarshalJSON() ([]byte, error) { - return json.Marshal(time.Since(time.Time(s)).Seconds()) + seconds := strconv.Itoa(int(time.Since(time.Time(s)).Seconds())) + return []byte(seconds), nil } func (s *statsJSON) IngressTraffic(traffic int) { @@ -62,27 +64,27 @@ func (s *statsJSON) EgressTraffic(traffic int) { atomic.AddUint64(&s.Traffic.Egress, uint64(traffic)) } -func (s *statsJSON) ClientConnected(connectionType newprotocol.ConnectionType, addr *net.TCPAddr) { +func (s *statsJSON) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) { s.changeConnections(connectionType, addr, 1) } -func (s *statsJSON) ClientDisconnected(connectionType newprotocol.ConnectionType, addr *net.TCPAddr) { +func (s *statsJSON) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) { s.changeConnections(connectionType, addr, ^uint32(0)) } -func (s *statsJSON) changeConnections(connectionType newprotocol.ConnectionType, addr *net.TCPAddr, value uint32) { +func (s *statsJSON) changeConnections(connectionType conntypes.ConnectionType, addr *net.TCPAddr, value uint32) { var connections *statsJSONConnectionType switch connectionType { - case newprotocol.ConnectionTypeAbridged: + case conntypes.ConnectionTypeAbridged: connections = &s.Connections.Abridged - case newprotocol.ConnectionTypeSecure: + case conntypes.ConnectionTypeSecure: connections = &s.Connections.Secured default: connections = &s.Connections.Intermediate } - if addr.IP.To4() == nil { + if addr.IP.To4() != nil { atomic.AddUint32(&connections.IPv4, value) } else { atomic.AddUint32(&connections.IPv6, value) @@ -98,7 +100,9 @@ func (s *statsJSON) AntiReplayDetected() { } func newStatsJSON(mux *http.ServeMux) Stats { - instance := &statsJSON{} + instance := &statsJSON{ + Uptime: statsJSONUptime(time.Now()), + } logger := zap.S().Named("stats") mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { diff --git a/newstats/stats_prometheus.go b/stats/stats_prometheus.go similarity index 79% rename from newstats/stats_prometheus.go rename to stats/stats_prometheus.go index 592f189..d4b0d03 100644 --- a/newstats/stats_prometheus.go +++ b/stats/stats_prometheus.go @@ -1,4 +1,4 @@ -package newstats +package stats import ( "net" @@ -8,8 +8,8 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/9seconds/mtg/newconfig" - "github.com/9seconds/mtg/newprotocol" + "github.com/9seconds/mtg/config" + "github.com/9seconds/mtg/conntypes" ) type statsPrometheus struct { @@ -27,23 +27,23 @@ func (s *statsPrometheus) EgressTraffic(traffic int) { s.traffic.WithLabelValues("egress").Add(float64(traffic)) } -func (s *statsPrometheus) ClientConnected(connectionType newprotocol.ConnectionType, addr *net.TCPAddr) { +func (s *statsPrometheus) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) { s.changeConnections(connectionType, addr, 1.0) } -func (s *statsPrometheus) ClientDisconnected(connectionType newprotocol.ConnectionType, addr *net.TCPAddr) { +func (s *statsPrometheus) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) { s.changeConnections(connectionType, addr, -1.0) } -func (s *statsPrometheus) changeConnections(connectionType newprotocol.ConnectionType, +func (s *statsPrometheus) changeConnections(connectionType conntypes.ConnectionType, addr *net.TCPAddr, increment float64) { var labels [2]string switch connectionType { - case newprotocol.ConnectionTypeAbridged: + case conntypes.ConnectionTypeAbridged: labels[0] = "abridged" - case newprotocol.ConnectionTypeSecure: + case conntypes.ConnectionTypeSecure: labels[0] = "secured" default: labels[0] = "intermediate" @@ -69,22 +69,22 @@ func newStatsPrometheus(mux *http.ServeMux) (Stats, error) { registry := prometheus.NewRegistry() instance := &statsPrometheus{ connections: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: newconfig.C.PrometheusStats.Prefix, + Namespace: config.C.PrometheusStats.Prefix, Name: "connections", Help: "Current number of connections to the proxy.", }, []string{"type", "protocol"}), traffic: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: newconfig.C.PrometheusStats.Prefix, + Namespace: config.C.PrometheusStats.Prefix, Name: "traffic", Help: "Traffic passed through the proxy in bytes.", }, []string{"direction"}), crashes: prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: newconfig.C.PrometheusStats.Prefix, + Namespace: config.C.PrometheusStats.Prefix, Name: "crashes", Help: "How many crashes happened.", }), antiReplays: prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: newconfig.C.PrometheusStats.Prefix, + Namespace: config.C.PrometheusStats.Prefix, Name: "anti_replays", Help: "How many anti replay attacks were prevented.", }), diff --git a/newstats/stats_statsd.go b/stats/stats_statsd.go similarity index 58% rename from newstats/stats_statsd.go rename to stats/stats_statsd.go index f977a5a..d78e9b0 100644 --- a/newstats/stats_statsd.go +++ b/stats/stats_statsd.go @@ -1,14 +1,14 @@ -package newstats +package stats import ( "net" "strings" + "github.com/juju/errors" "gopkg.in/alexcesaro/statsd.v2" - "github.com/9seconds/mtg/newconfig" - "github.com/9seconds/mtg/newprotocol" - "github.com/juju/errors" + "github.com/9seconds/mtg/config" + "github.com/9seconds/mtg/conntypes" ) type statsStatsd struct { @@ -23,22 +23,22 @@ func (s *statsStatsd) EgressTraffic(traffic int) { s.client.Count("traffic.egress", traffic) } -func (s *statsStatsd) ClientConnected(connectionType newprotocol.ConnectionType, addr *net.TCPAddr) { +func (s *statsStatsd) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) { s.changeConnections(connectionType, addr, 1) } -func (s *statsStatsd) ClientDisconnected(connectionType newprotocol.ConnectionType, addr *net.TCPAddr) { +func (s *statsStatsd) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) { s.changeConnections(connectionType, addr, -1) } -func (s *statsStatsd) changeConnections(connectionType newprotocol.ConnectionType, addr *net.TCPAddr, value int) { +func (s *statsStatsd) changeConnections(connectionType conntypes.ConnectionType, addr *net.TCPAddr, value int) { var labels [3]string labels[0] = "connections" switch connectionType { - case newprotocol.ConnectionTypeAbridged: + case conntypes.ConnectionTypeAbridged: labels[1] = "abridged" - case newprotocol.ConnectionTypeSecure: + case conntypes.ConnectionTypeSecure: labels[1] = "secured" default: labels[1] = "intermediate" @@ -62,15 +62,15 @@ func (s *statsStatsd) AntiReplayDetected() { func newStatsStatsd() (Stats, error) { options := []statsd.Option{ - statsd.Prefix(newconfig.C.StatsdStats.Prefix), - statsd.Network(newconfig.C.StatsdStats.Addr.Network()), - statsd.Address(newconfig.C.StatsdStats.Addr.String()), - statsd.TagsFormat(newconfig.C.StatsdStats.TagsFormat), + statsd.Prefix(config.C.StatsdStats.Prefix), + statsd.Network(config.C.StatsdStats.Addr.Network()), + statsd.Address(config.C.StatsdStats.Addr.String()), + statsd.TagsFormat(config.C.StatsdStats.TagsFormat), } - if len(newconfig.C.StatsdStats.Tags) > 0 { - tags := make([]string, len(newconfig.C.StatsdStats.Tags)*2) - for k, v := range newconfig.C.StatsdStats.Tags { + if len(config.C.StatsdStats.Tags) > 0 { + tags := make([]string, len(config.C.StatsdStats.Tags)*2) + for k, v := range config.C.StatsdStats.Tags { tags = append(tags, k, v) } options = append(options, statsd.Tags(tags...)) diff --git a/telegram/base.go b/telegram/base.go new file mode 100644 index 0000000..8d5b6cb --- /dev/null +++ b/telegram/base.go @@ -0,0 +1,70 @@ +package telegram + +import ( + "context" + "math/rand" + "net" + "time" + + "github.com/juju/errors" + + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/utils" + "github.com/9seconds/mtg/wrappers" +) + +const telegramDialTimeout = 10 * time.Second + +type baseTelegram struct { + dialer net.Dialer + + v4DefaultDC conntypes.DC + V6DefaultDC conntypes.DC + v4Addresses map[conntypes.DC][]string + v6Addresses map[conntypes.DC][]string +} + +func (b *baseTelegram) dialToAddress(ctx context.Context, + cancel context.CancelFunc, + addr string) (wrappers.StreamReadWriteCloser, error) { + conn, err := b.dialer.Dial("tcp", addr) + if err != nil { + return nil, errors.Annotate(err, "Dial has failed") + } + + if err := utils.InitTCP(conn); err != nil { + return nil, errors.Annotate(err, "Cannot initialize TCP socket") + } + + return wrappers.NewTelegramConn(ctx, cancel, conn), nil +} + +func (b *baseTelegram) dial(ctx context.Context, + cancel context.CancelFunc, + dc conntypes.DC, + protocol conntypes.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) { + addr := "" + + switch protocol { + case conntypes.ConnectionProtocolIPv4: + addr = b.chooseAddress(b.v4Addresses, dc, b.v4DefaultDC) + default: + addr = b.chooseAddress(b.v6Addresses, dc, b.V6DefaultDC) + } + + return b.dialToAddress(ctx, cancel, addr) +} + +func (b *baseTelegram) chooseAddress(addresses map[conntypes.DC][]string, + dc, defaultDC conntypes.DC) string { + addrs, ok := addresses[dc] + if !ok { + addrs, _ = addresses[defaultDC] + } + + if len(addrs) > 0 { + return addrs[rand.Intn(len(addrs))] + } + + return "" +} diff --git a/telegram/direct.go b/telegram/direct.go index f8a083a..775a7d8 100644 --- a/telegram/direct.go +++ b/telegram/direct.go @@ -4,28 +4,24 @@ import ( "context" "net" - "github.com/juju/errors" - - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/mtproto" - "github.com/9seconds/mtg/obfuscated2" + "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/wrappers" ) const ( - directV4DefaultIdx = 1 - directV6DefaultIdx = 1 + directV4DefaultIdx conntypes.DC = 1 + directV6DefaultIdx conntypes.DC = 1 ) var ( - directV4Addresses = map[int16][]string{ + directV4Addresses = map[conntypes.DC][]string{ 0: {"149.154.175.50:443"}, 1: {"149.154.167.51:443"}, 2: {"149.154.175.100:443"}, 3: {"149.154.167.91:443"}, 4: {"149.154.171.5:443"}, } - directV6Addresses = map[int16][]string{ + directV6Addresses = map[conntypes.DC][]string{ 0: {"[2001:b28:f23d:f001::a]:443"}, 1: {"[2001:67c:04e8:f002::a]:443"}, 2: {"[2001:b28:f23d:f003::a]:443"}, @@ -38,42 +34,28 @@ type directTelegram struct { baseTelegram } -func (t *directTelegram) Dial(ctx context.Context, cancel context.CancelFunc, - connID string, connOpts *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error) { - dc := connOpts.DC - if dc < 0 { +func (d *directTelegram) Dial(ctx context.Context, + cancel context.CancelFunc, + dc conntypes.DC, + protocol conntypes.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) { + switch { + case dc < 0: dc = -dc - } else if dc == 0 { - dc = 1 + case dc == 0: + dc = conntypes.DCDefaultIdx } - return t.baseTelegram.dial(ctx, cancel, dc-1, connID, connOpts.ConnectionProto) + return d.baseTelegram.dial(ctx, cancel, dc-1, protocol) } -func (t *directTelegram) Init(connOpts *mtproto.ConnectionOpts, - conn wrappers.StreamReadWriteCloser) (wrappers.Wrap, error) { - obfs2, frame := obfuscated2.MakeTelegramObfuscated2Frame(connOpts) - - if _, err := conn.Write(frame); err != nil { - return nil, errors.Annotate(err, "Cannot write hadnshake frame") - } - - return wrappers.NewStreamCipher(conn, obfs2.Encryptor, obfs2.Decryptor), nil -} - -// NewDirectTelegram returns Telegram instance which connects directly -// to Telegram bypassing middleproxies. -func NewDirectTelegram(conf *config.Config) Telegram { +func NewDirectTelegram() Telegram { return &directTelegram{ baseTelegram: baseTelegram{ - dialer: tgDialer{ - Dialer: net.Dialer{Timeout: telegramDialTimeout}, - conf: conf, - }, - v4DefaultIdx: directV4DefaultIdx, - v6DefaultIdx: directV6DefaultIdx, - v4Addresses: directV4Addresses, - v6Addresses: directV6Addresses, + dialer: net.Dialer{Timeout: telegramDialTimeout}, + v4DefaultDC: directV4DefaultIdx, + V6DefaultDC: directV6DefaultIdx, + v4Addresses: directV4Addresses, + v6Addresses: directV6Addresses, }, } } diff --git a/telegram/interfaces.go b/telegram/interfaces.go new file mode 100644 index 0000000..b75f057 --- /dev/null +++ b/telegram/interfaces.go @@ -0,0 +1,15 @@ +package telegram + +import ( + "context" + + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/wrappers" +) + +type Telegram interface { + Dial(context.Context, + context.CancelFunc, + conntypes.DC, + conntypes.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) +} diff --git a/utils/init_tcp.go b/utils/init_tcp.go new file mode 100644 index 0000000..f34b4d2 --- /dev/null +++ b/utils/init_tcp.go @@ -0,0 +1,25 @@ +package utils + +import ( + "net" + + "github.com/juju/errors" + + "github.com/9seconds/mtg/config" +) + +func InitTCP(conn net.Conn) error { + tcpConn := conn.(*net.TCPConn) + + if err := tcpConn.SetNoDelay(true); err != nil { + return errors.Annotate(err, "Cannot set NO_DELAY") + } + if err := tcpConn.SetReadBuffer(config.C.BufferSize.Read); err != nil { + return errors.Annotate(err, "Cannot set read buffer size") + } + if err := tcpConn.SetWriteBuffer(config.C.BufferSize.Write); err != nil { + return errors.Annotate(err, "Cannot set write buffer size") + } + + return nil +} diff --git a/utils/stream_cipher.go b/utils/stream_cipher.go new file mode 100644 index 0000000..072d607 --- /dev/null +++ b/utils/stream_cipher.go @@ -0,0 +1,11 @@ +package utils + +import ( + "crypto/aes" + "crypto/cipher" +) + +func MakeStreamCipher(key, iv []byte) cipher.Stream { + block, _ := aes.NewCipher(key) // nolint: gosec + return cipher.NewCTR(block, iv) +} diff --git a/utils/uint24.go b/utils/uint24.go index 66c5f1b..350f3d5 100644 --- a/utils/uint24.go +++ b/utils/uint24.go @@ -1,15 +1,11 @@ package utils -// Uint24 is a replacement for the absent Go uint24 data type. -// This data type is little endian. type Uint24 [3]byte -// ToUint24 converts number to Uint24. func ToUint24(number uint32) Uint24 { return Uint24{byte(number), byte(number >> 8), byte(number >> 16)} } -// FromUint24 converts Uint24 to number. func FromUint24(number Uint24) uint32 { return uint32(number[0]) + (uint32(number[1]) << 8) + (uint32(number[2]) << 16) } diff --git a/newwrappers/interfaces.go b/wrappers/interfaces.go similarity index 98% rename from newwrappers/interfaces.go rename to wrappers/interfaces.go index dbc9998..fd06f59 100644 --- a/newwrappers/interfaces.go +++ b/wrappers/interfaces.go @@ -1,4 +1,4 @@ -package newwrappers +package wrappers import ( "io" diff --git a/newwrappers/wrapper_conn.go b/wrappers/wrapper_conn.go similarity index 90% rename from newwrappers/wrapper_conn.go rename to wrappers/wrapper_conn.go index 7bd4b9d..09f056c 100644 --- a/newwrappers/wrapper_conn.go +++ b/wrappers/wrapper_conn.go @@ -1,4 +1,4 @@ -package newwrappers +package wrappers import ( "context" @@ -10,7 +10,7 @@ import ( "github.com/juju/errors" "go.uber.org/zap" - "github.com/9seconds/mtg/newconfig" + "github.com/9seconds/mtg/config" ) const ConnIDLength = 8 @@ -126,11 +126,11 @@ func newConn(ctx context.Context, localAddr := *parent.LocalAddr().(*net.TCPAddr) if parent.RemoteAddr().(*net.TCPAddr).IP.To4() != nil { - if newconfig.C.PublicIPv4Addr.IP != nil { - localAddr.IP = newconfig.C.PublicIPv4Addr.IP + if config.C.PublicIPv4Addr.IP != nil { + localAddr.IP = config.C.PublicIPv4Addr.IP } - } else if newconfig.C.PublicIPv6Addr.IP != nil { - localAddr.IP = newconfig.C.PublicIPv6Addr.IP + } else if config.C.PublicIPv6Addr.IP != nil { + localAddr.IP = config.C.PublicIPv6Addr.IP } logger := zap.S().With( @@ -162,9 +162,8 @@ func NewClientConn(ctx context.Context, func NewTelegramConn(ctx context.Context, cancel context.CancelFunc, - parent net.Conn, - connID ConnID) StreamReadWriteCloser { - return newConn(ctx, cancel, parent, connID, connPurposeTelegram) + parent net.Conn) StreamReadWriteCloser { + return newConn(ctx, cancel, parent, ConnID{}, connPurposeTelegram) } func NewConnID() ConnID { diff --git a/newwrappers/wrapper_obfuscated2.go b/wrappers/wrapper_obfuscated2.go similarity index 98% rename from newwrappers/wrapper_obfuscated2.go rename to wrappers/wrapper_obfuscated2.go index 70122ae..9cfaed7 100644 --- a/newwrappers/wrapper_obfuscated2.go +++ b/wrappers/wrapper_obfuscated2.go @@ -1,4 +1,4 @@ -package newwrappers +package wrappers import ( "crypto/cipher" diff --git a/newwrappers/wrapper_stats.go b/wrappers/wrapper_stats.go similarity index 86% rename from newwrappers/wrapper_stats.go rename to wrappers/wrapper_stats.go index eaac6d5..4f33eee 100644 --- a/newwrappers/wrapper_stats.go +++ b/wrappers/wrapper_stats.go @@ -1,4 +1,4 @@ -package newwrappers +package wrappers import ( "net" @@ -6,7 +6,7 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/newstats" + "github.com/9seconds/mtg/stats" ) type wrapperStats struct { @@ -15,28 +15,28 @@ type wrapperStats struct { func (w *wrapperStats) Write(p []byte) (int, error) { n, err := w.parent.Write(p) - newstats.S.EgressTraffic(n) + stats.S.EgressTraffic(n) return n, err } func (w *wrapperStats) WriteTimeout(p []byte, timeout time.Duration) (int, error) { n, err := w.parent.WriteTimeout(p, timeout) - newstats.S.EgressTraffic(n) + stats.S.EgressTraffic(n) return n, err } func (w *wrapperStats) Read(p []byte) (int, error) { n, err := w.parent.Read(p) - newstats.S.IngressTraffic(n) + stats.S.IngressTraffic(n) return n, err } func (w *wrapperStats) ReadTimeout(p []byte, timeout time.Duration) (int, error) { n, err := w.parent.ReadTimeout(p, timeout) - newstats.S.IngressTraffic(n) + stats.S.IngressTraffic(n) return n, err } From 299a34252e6e2f9c8f7fe42c44f3e4199b7f8b28 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Wed, 4 Sep 2019 10:30:21 +0300 Subject: [PATCH 05/62] Move rlimit to utils --- go.mod | 2 +- main.go | 22 ++-------------------- utils/rlimit.go | 26 ++++++++++++++++++++++++++ utils/rlimit_windows.go | 7 +++++++ 4 files changed, 36 insertions(+), 21 deletions(-) create mode 100644 utils/rlimit.go create mode 100644 utils/rlimit_windows.go diff --git a/go.mod b/go.mod index 92856ed..f3e79de 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( go.uber.org/multierr v1.1.0 // indirect go.uber.org/zap v1.10.0 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect - golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect + golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/alexcesaro/statsd.v2 v2.0.0 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect diff --git a/main.go b/main.go index 054292e..4fc07d0 100644 --- a/main.go +++ b/main.go @@ -3,14 +3,13 @@ package main import ( "math/rand" "os" - "syscall" "time" - "github.com/juju/errors" kingpin "gopkg.in/alecthomas/kingpin.v2" "github.com/9seconds/mtg/cli" "github.com/9seconds/mtg/config" + "github.com/9seconds/mtg/utils" ) var version = "dev" // this has to be set by build ld flags @@ -143,7 +142,7 @@ func main() { app.Version(version) app.HelpFlag.Short('h') - if err := setRLimit(); err != nil { + if err := utils.SetLimits(); err != nil { cli.Fatal(err.Error()) } @@ -186,20 +185,3 @@ func main() { } } } - -func setRLimit() (err error) { - rLimit := syscall.Rlimit{} - err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) - if err != nil { - err = errors.Annotate(err, "Cannot get rlimit") - return - } - rLimit.Cur = rLimit.Max - - err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) - if err != nil { - err = errors.Annotate(err, "Cannot set rlimit") - } - - return -} diff --git a/utils/rlimit.go b/utils/rlimit.go new file mode 100644 index 0000000..98ed947 --- /dev/null +++ b/utils/rlimit.go @@ -0,0 +1,26 @@ +// +build !windows + +package utils + +import ( + "golang.org/x/sys/unix" + + "github.com/juju/errors" +) + +func SetLimits() (err error) { + rLimit := unix.Rlimit{} + err = unix.Getrlimit(unix.RLIMIT_NOFILE, &rLimit) + if err != nil { + err = errors.Annotate(err, "Cannot get rlimit") + return + } + rLimit.Cur = rLimit.Max + + err = unix.Setrlimit(unix.RLIMIT_NOFILE, &rLimit) + if err != nil { + err = errors.Annotate(err, "Cannot set rlimit") + } + + return +} diff --git a/utils/rlimit_windows.go b/utils/rlimit_windows.go new file mode 100644 index 0000000..58bdf39 --- /dev/null +++ b/utils/rlimit_windows.go @@ -0,0 +1,7 @@ +// +build windows + +package utils + +func SetLimits() error { + return nil +} From 701f62ed4cb9fd1e41aceb8d197fbdf020acb403 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Wed, 4 Sep 2019 10:54:11 +0300 Subject: [PATCH 06/62] More reasonable shutdowns --- cli/proxy.go | 14 +++++++--- config/config.go | 11 ++++---- config/global_ips.go | 17 +++++++----- main.go | 46 ++++++++++++++++----------------- proxy/proxy.go | 14 +++++++--- stats/stats.go | 7 ++++- utils/signal_context.go | 24 +++++++++++++++++ utils/signal_context_windows.go | 23 +++++++++++++++++ 8 files changed, 115 insertions(+), 41 deletions(-) create mode 100644 utils/signal_context.go create mode 100644 utils/signal_context_windows.go diff --git a/cli/proxy.go b/cli/proxy.go index bccddfc..a08f71c 100644 --- a/cli/proxy.go +++ b/cli/proxy.go @@ -15,9 +15,12 @@ import ( "github.com/9seconds/mtg/proxy" "github.com/9seconds/mtg/stats" "github.com/9seconds/mtg/telegram" + "github.com/9seconds/mtg/utils" ) func Proxy() error { + ctx := utils.GetSignalContext() + atom := zap.NewAtomicLevel() switch { case config.C.Debug: @@ -37,7 +40,7 @@ func Proxy() error { zap.ReplaceGlobals(logger) defer logger.Sync() // nolint: errcheck - if err := config.InitPublicAddress(); err != nil { + if err := config.InitPublicAddress(ctx); err != nil { Fatal(err.Error()) } zap.S().Debugw("Configuration", "config", config.C) @@ -61,16 +64,21 @@ func Proxy() error { if err := antireplay.Init(); err != nil { Fatal(err.Error()) } - if err := stats.Init(); err != nil { + if err := stats.Init(ctx); err != nil { Fatal(err.Error()) } proxyListener, err := net.Listen("tcp", config.C.ListenAddr.String()) if err != nil { Fatal(err.Error()) } + go func() { + <-ctx.Done() + proxyListener.Close() + }() app := &proxy.Proxy{ - Logger: zap.S().Named("proxy"), + Logger: zap.S().Named("proxy"), + Context: ctx, } if len(config.C.AdTag) == 0 { app.TelegramProtocolMaker = obfuscated2.MakeTelegramProtocol diff --git a/config/config.go b/config/config.go index d9b9adb..2aec548 100644 --- a/config/config.go +++ b/config/config.go @@ -2,6 +2,7 @@ package config import ( "bytes" + "context" "encoding/json" "net" "strconv" @@ -140,14 +141,14 @@ func (c Config) String() string { return string(data) } -type ConfigOpt struct { +type Opt struct { Option OptionType Value interface{} } var C = Config{} -func Init(options ...ConfigOpt) error { // nolint: gocyclo +func Init(options ...Opt) error { // nolint: gocyclo for _, opt := range options { switch opt.Option { case OptionTypeDebug: @@ -222,7 +223,7 @@ func Init(options ...ConfigOpt) error { // nolint: gocyclo return nil } -func InitPublicAddress() error { +func InitPublicAddress(ctx context.Context) error { if C.PublicIPv4Addr.Port == 0 { C.PublicIPv4Addr.Port = C.ListenAddr.Port } @@ -232,7 +233,7 @@ func InitPublicAddress() error { foundAddress := C.PublicIPv4Addr.IP != nil || C.PublicIPv6Addr.IP != nil if C.PublicIPv4Addr.IP == nil { - ip, err := getGlobalIPv4() + ip, err := getGlobalIPv4(ctx) if err != nil { zap.S().Warnw("Cannot resolve public address", "error", err) } else { @@ -241,7 +242,7 @@ func InitPublicAddress() error { } } if C.PublicIPv6Addr.IP == nil { - ip, err := getGlobalIPv6() + ip, err := getGlobalIPv6(ctx) if err != nil { zap.S().Warnw("Cannot resolve public address", "error", err) } else { diff --git a/config/global_ips.go b/config/global_ips.go index f2e72de..719000a 100644 --- a/config/global_ips.go +++ b/config/global_ips.go @@ -17,23 +17,23 @@ const ( ifconfigTimeout = 10 * time.Second ) -func getGlobalIPv4() (net.IP, error) { - ip, err := fetchIP("tcp4") +func getGlobalIPv4(ctx context.Context) (net.IP, error) { + ip, err := fetchIP(ctx, "tcp4") if err != nil || ip.To4() == nil { return nil, errors.Annotate(err, "Cannot find public ipv4 address") } return ip, nil } -func getGlobalIPv6() (net.IP, error) { - ip, err := fetchIP("tcp6") +func getGlobalIPv6(ctx context.Context) (net.IP, error) { + ip, err := fetchIP(ctx, "tcp6") if err != nil || ip.To4() != nil { return nil, errors.Annotate(err, "Cannot find public ipv6 address") } return ip, nil } -func fetchIP(network string) (net.IP, error) { +func fetchIP(ctx context.Context, network string) (net.IP, error) { dialer := &net.Dialer{FallbackDelay: -1} client := &http.Client{ Jar: nil, @@ -45,7 +45,12 @@ func fetchIP(network string) (net.IP, error) { }, } - resp, err := client.Get(ifconfigAddress) + req, err := http.NewRequest("GET", ifconfigAddress, nil) + if err != nil { + return nil, errors.Annotate(err, "Cannot create a request") + } + + resp, err := client.Do(req.WithContext(ctx)) if err != nil { if resp != nil { io.Copy(ioutil.Discard, resp.Body) // nolint: errcheck diff --git a/main.go b/main.go index 4fc07d0..3abdf20 100644 --- a/main.go +++ b/main.go @@ -152,29 +152,29 @@ func main() { case proxyCommand.FullCommand(): err := config.Init( - config.ConfigOpt{Option: config.OptionTypeDebug, Value: *proxyDebug}, - config.ConfigOpt{Option: config.OptionTypeVerbose, Value: *proxyVerbose}, - config.ConfigOpt{Option: config.OptionTypeBindIP, Value: *proxyBindIP}, - config.ConfigOpt{Option: config.OptionTypeBindPort, Value: *proxyBindPort}, - config.ConfigOpt{Option: config.OptionTypePublicIPv4, Value: *proxyPublicIPv4}, - config.ConfigOpt{Option: config.OptionTypePublicIPv4Port, Value: *proxyPublicIPv4Port}, - config.ConfigOpt{Option: config.OptionTypePublicIPv6, Value: *proxyPublicIPv6}, - config.ConfigOpt{Option: config.OptionTypePublicIPv6Port, Value: *proxyPublicIPv6Port}, - config.ConfigOpt{Option: config.OptionTypeStatsIP, Value: *proxyStatsIP}, - config.ConfigOpt{Option: config.OptionTypeStatsPort, Value: *proxyStatsPort}, - config.ConfigOpt{Option: config.OptionTypeStatsdIP, Value: *proxyStatsdIP}, - config.ConfigOpt{Option: config.OptionTypeStatsdPort, Value: *proxyStatsdPort}, - config.ConfigOpt{Option: config.OptionTypeStatsdNetwork, Value: *proxyStatsdNetwork}, - config.ConfigOpt{Option: config.OptionTypeStatsdPrefix, Value: *proxyStatsdPrefix}, - config.ConfigOpt{Option: config.OptionTypeStatsdTagsFormat, Value: *proxyStatsdTagsFormat}, - config.ConfigOpt{Option: config.OptionTypeStatsdTags, Value: *proxyStatsdTags}, - config.ConfigOpt{Option: config.OptionTypePrometheusPrefix, Value: *proxyPrometheusPrefix}, - config.ConfigOpt{Option: config.OptionTypeWriteBufferSize, Value: *proxyWriteBufferSize}, - config.ConfigOpt{Option: config.OptionTypeReadBufferSize, Value: *proxyReadBufferSize}, - config.ConfigOpt{Option: config.OptionTypeAntiReplayMaxSize, Value: *proxyAntiReplayMaxSize}, - config.ConfigOpt{Option: config.OptionTypeAntiReplayEvictionTime, Value: *proxyAntiReplayEvictionTime}, - config.ConfigOpt{Option: config.OptionTypeSecret, Value: *proxySecret}, - config.ConfigOpt{Option: config.OptionTypeAdtag, Value: *proxyAdtag}, + config.Opt{Option: config.OptionTypeDebug, Value: *proxyDebug}, + config.Opt{Option: config.OptionTypeVerbose, Value: *proxyVerbose}, + config.Opt{Option: config.OptionTypeBindIP, Value: *proxyBindIP}, + config.Opt{Option: config.OptionTypeBindPort, Value: *proxyBindPort}, + config.Opt{Option: config.OptionTypePublicIPv4, Value: *proxyPublicIPv4}, + config.Opt{Option: config.OptionTypePublicIPv4Port, Value: *proxyPublicIPv4Port}, + config.Opt{Option: config.OptionTypePublicIPv6, Value: *proxyPublicIPv6}, + config.Opt{Option: config.OptionTypePublicIPv6Port, Value: *proxyPublicIPv6Port}, + config.Opt{Option: config.OptionTypeStatsIP, Value: *proxyStatsIP}, + config.Opt{Option: config.OptionTypeStatsPort, Value: *proxyStatsPort}, + config.Opt{Option: config.OptionTypeStatsdIP, Value: *proxyStatsdIP}, + config.Opt{Option: config.OptionTypeStatsdPort, Value: *proxyStatsdPort}, + config.Opt{Option: config.OptionTypeStatsdNetwork, Value: *proxyStatsdNetwork}, + config.Opt{Option: config.OptionTypeStatsdPrefix, Value: *proxyStatsdPrefix}, + config.Opt{Option: config.OptionTypeStatsdTagsFormat, Value: *proxyStatsdTagsFormat}, + config.Opt{Option: config.OptionTypeStatsdTags, Value: *proxyStatsdTags}, + config.Opt{Option: config.OptionTypePrometheusPrefix, Value: *proxyPrometheusPrefix}, + config.Opt{Option: config.OptionTypeWriteBufferSize, Value: *proxyWriteBufferSize}, + config.Opt{Option: config.OptionTypeReadBufferSize, Value: *proxyReadBufferSize}, + config.Opt{Option: config.OptionTypeAntiReplayMaxSize, Value: *proxyAntiReplayMaxSize}, + config.Opt{Option: config.OptionTypeAntiReplayEvictionTime, Value: *proxyAntiReplayEvictionTime}, + config.Opt{Option: config.OptionTypeSecret, Value: *proxySecret}, + config.Opt{Option: config.OptionTypeAdtag, Value: *proxyAdtag}, ) if err != nil { cli.Fatal(err.Error()) diff --git a/proxy/proxy.go b/proxy/proxy.go index 96108b6..7ead6d9 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -20,17 +20,25 @@ const directPipeBufferSize = 1024 * 1024 type Proxy struct { Logger *zap.SugaredLogger + Context context.Context ClientProtocolMaker protocol.ClientProtocolMaker TelegramProtocolMaker protocol.TelegramProtocolMaker TelegramDialer telegram.Telegram } func (p *Proxy) Serve(listener net.Listener) { + doneChan := p.Context.Done() + for { conn, err := listener.Accept() if err != nil { - p.Logger.Errorw("Cannot allocate incoming connection", "error", err) - continue + select { + case <-doneChan: + return + default: + p.Logger.Errorw("Cannot allocate incoming connection", "error", err) + continue + } } go p.accept(conn) } @@ -53,7 +61,7 @@ func (p *Proxy) accept(conn net.Conn) { return } - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(p.Context) defer cancel() wrappedConn := wrappers.NewClientConn(ctx, cancel, conn, connID) diff --git a/stats/stats.go b/stats/stats.go index 5793552..6412132 100644 --- a/stats/stats.go +++ b/stats/stats.go @@ -1,6 +1,7 @@ package stats import ( + "context" "net" "net/http" @@ -59,7 +60,7 @@ func (m multiStats) AntiReplayDetected() { var S Stats -func Init() error { +func Init(ctx context.Context) error { mux := http.NewServeMux() instanceJSON := newStatsJSON(mux) @@ -86,6 +87,10 @@ func Init() error { Handler: mux, } go srv.Serve(listener) // nolint: errcheck + go func() { + <-ctx.Done() + srv.Shutdown(context.Background()) // nolint: errcheck + }() S = multiStats(stats) diff --git a/utils/signal_context.go b/utils/signal_context.go new file mode 100644 index 0000000..40e2b6f --- /dev/null +++ b/utils/signal_context.go @@ -0,0 +1,24 @@ +// +build !windows + +package utils + +import ( + "context" + "os" + "os/signal" + "syscall" +) + +func GetSignalContext() context.Context { + ctx, cancel := context.WithCancel(context.Background()) + sigChan := make(chan os.Signal, 1) + + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + go func() { + for range sigChan { + cancel() + } + }() + + return ctx +} diff --git a/utils/signal_context_windows.go b/utils/signal_context_windows.go new file mode 100644 index 0000000..d18ce71 --- /dev/null +++ b/utils/signal_context_windows.go @@ -0,0 +1,23 @@ +// +build windows + +package utils + +import ( + "context" + "os" + "os/signal" +) + +func GetSignalContext() context.Context { + ctx, cancel := context.WithCancel(context.Background()) + sigChan := make(chan os.Signal, 1) + + signal.Notify(sigChan, os.Interrupt) + go func() { + for range sigChan { + cancel() + } + }() + + return ctx +} From 2918ed11e5e53445d19030f65594bb83e559bfa5 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Fri, 6 Sep 2019 16:10:55 +0300 Subject: [PATCH 07/62] Remove juju/errors --- antireplay/cache.go | 4 ++++ cli/proxy.go | 8 ++++---- cli/utils.go | 7 +++++-- config/config.go | 7 ++++--- config/global_ips.go | 15 +++++++-------- go.mod | 12 +++--------- go.sum | 12 ------------ main.go | 6 +++--- ntp/ntp.go | 4 ++-- obfuscated2/client_protocol.go | 8 ++++---- obfuscated2/telegram_protocol.go | 7 +++---- stats/stats.go | 9 ++++----- stats/stats_prometheus.go | 10 +++++----- stats/stats_statsd.go | 4 ++-- telegram/base.go | 7 +++---- utils/init_tcp.go | 9 ++++----- utils/rlimit.go | 19 ++++++++----------- wrappers/wrapper_conn.go | 10 +++++----- wrappers/wrapper_obfuscated2.go | 6 +++--- 19 files changed, 73 insertions(+), 91 deletions(-) diff --git a/antireplay/cache.go b/antireplay/cache.go index 3b4706d..359cb39 100644 --- a/antireplay/cache.go +++ b/antireplay/cache.go @@ -1,6 +1,9 @@ package antireplay import ( + "errors" + "fmt" + "github.com/allegro/bigcache" "github.com/9seconds/mtg/config" @@ -25,6 +28,7 @@ func Init() error { HardMaxCacheSize: config.C.AntiReplay.MaxSize, }) cache = c + err = fmt.Errorf("qqq: %w", errors.New("tt")) return err } diff --git a/cli/proxy.go b/cli/proxy.go index a08f71c..df0bc18 100644 --- a/cli/proxy.go +++ b/cli/proxy.go @@ -41,7 +41,7 @@ func Proxy() error { defer logger.Sync() // nolint: errcheck if err := config.InitPublicAddress(ctx); err != nil { - Fatal(err.Error()) + Fatal(err) } zap.S().Debugw("Configuration", "config", config.C) @@ -62,14 +62,14 @@ func Proxy() error { PrintJSONStdout(config.GetURLs()) if err := antireplay.Init(); err != nil { - Fatal(err.Error()) + Fatal(err) } if err := stats.Init(ctx); err != nil { - Fatal(err.Error()) + Fatal(err) } proxyListener, err := net.Listen("tcp", config.C.ListenAddr.String()) if err != nil { - Fatal(err.Error()) + Fatal(err) } go func() { <-ctx.Done() diff --git a/cli/utils.go b/cli/utils.go index 80bba2c..36f1128 100644 --- a/cli/utils.go +++ b/cli/utils.go @@ -7,8 +7,11 @@ import ( "os" ) -func Fatal(args ...interface{}) { - PrintStderr(args...) +func Fatal(arg interface{}) { + if value, ok := arg.(error); ok { + arg = fmt.Errorf("fatal error: %+v", value) + } + PrintStderr(arg) os.Exit(1) } diff --git a/config/config.go b/config/config.go index 2aec548..d729e8f 100644 --- a/config/config.go +++ b/config/config.go @@ -4,11 +4,12 @@ import ( "bytes" "context" "encoding/json" + "errors" + "fmt" "net" "strconv" "time" - "github.com/juju/errors" "go.uber.org/zap" statsd "gopkg.in/alexcesaro/statsd.v2" ) @@ -187,7 +188,7 @@ func Init(options ...Opt) error { // nolint: gocyclo case "influxdb": C.StatsdStats.TagsFormat = statsd.InfluxDB default: - return errors.Errorf("Incorrect statsd tag %s", value) + return fmt.Errorf("Incorrect statsd tag %s", value) } case OptionTypeStatsdTags: C.StatsdStats.Tags = opt.Value.(map[string]string) @@ -206,7 +207,7 @@ func Init(options ...Opt) error { // nolint: gocyclo case OptionTypeAdtag: C.AdTag = opt.Value.([]byte) default: - return errors.Errorf("Unknown tag %v", opt.Option) + return fmt.Errorf("Unknown tag %v", opt.Option) } } diff --git a/config/global_ips.go b/config/global_ips.go index 719000a..be5fe5c 100644 --- a/config/global_ips.go +++ b/config/global_ips.go @@ -2,14 +2,13 @@ package config import ( "context" + "fmt" "io" "io/ioutil" "net" "net/http" "strings" "time" - - "github.com/juju/errors" ) const ( @@ -20,7 +19,7 @@ const ( func getGlobalIPv4(ctx context.Context) (net.IP, error) { ip, err := fetchIP(ctx, "tcp4") if err != nil || ip.To4() == nil { - return nil, errors.Annotate(err, "Cannot find public ipv4 address") + return nil, fmt.Errorf("cannot find public ipv4 address: %w", err) } return ip, nil } @@ -28,7 +27,7 @@ func getGlobalIPv4(ctx context.Context) (net.IP, error) { func getGlobalIPv6(ctx context.Context) (net.IP, error) { ip, err := fetchIP(ctx, "tcp6") if err != nil || ip.To4() != nil { - return nil, errors.Annotate(err, "Cannot find public ipv6 address") + return nil, fmt.Errorf("cannot find public ipv6 address: %w", err) } return ip, nil } @@ -47,7 +46,7 @@ func fetchIP(ctx context.Context, network string) (net.IP, error) { req, err := http.NewRequest("GET", ifconfigAddress, nil) if err != nil { - return nil, errors.Annotate(err, "Cannot create a request") + return nil, fmt.Errorf("cannot create a request: %w", err) } resp, err := client.Do(req.WithContext(ctx)) @@ -55,19 +54,19 @@ func fetchIP(ctx context.Context, network string) (net.IP, error) { if resp != nil { io.Copy(ioutil.Discard, resp.Body) // nolint: errcheck } - return nil, errors.Annotate(err, "Cannot perform a request") + return nil, fmt.Errorf("cannot perform a request: %w", err) } defer resp.Body.Close() // nolint: errcheck respDataBytes, err := ioutil.ReadAll(resp.Body) if err != nil { - return nil, errors.Annotate(err, "Cannot read response body") + return nil, fmt.Errorf("cannot read response body: %w", err) } respData := strings.TrimSpace(string(respDataBytes)) ip := net.ParseIP(respData) if ip == nil { - return nil, errors.Errorf("ifconfig.co returns incorrect IP %s", respData) + return nil, fmt.Errorf("ifconfig.co returns incorrect IP %s", respData) } return ip, nil diff --git a/go.mod b/go.mod index f3e79de..e2a1821 100644 --- a/go.mod +++ b/go.mod @@ -9,19 +9,12 @@ require ( github.com/allegro/bigcache v1.2.1 github.com/beevik/ntp v0.2.0 github.com/cespare/xxhash v1.1.0 - github.com/dustin/go-humanize v1.0.0 - github.com/gofrs/uuid v3.2.0+incompatible - github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d - github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect - github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 // indirect github.com/kr/pretty v0.1.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pkg/errors v0.8.1 // indirect github.com/prometheus/client_golang v1.1.0 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/stretchr/testify v1.4.0 + github.com/stretchr/testify v1.4.0 // indirect go.uber.org/atomic v1.4.0 // indirect go.uber.org/multierr v1.1.0 // indirect go.uber.org/zap v1.10.0 @@ -30,5 +23,6 @@ require ( gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/alexcesaro/statsd.v2 v2.0.0 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect - gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect ) + +go 1.13 diff --git a/go.sum b/go.sum index 5e49b8c..0ffeeaf 100644 --- a/go.sum +++ b/go.sum @@ -24,14 +24,10 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= @@ -44,12 +40,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc= -github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= -github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI= -github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= -github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 h1:Pp8RxiF4rSoXP9SED26WCfNB28/dwTDpPXS8XMJR8rc= -github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -132,8 +122,6 @@ gopkg.in/alexcesaro/statsd.v2 v2.0.0/go.mod h1:i0ubccKGzBVNBpdGV5MocxyA/XlLUJzA7 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= -gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/main.go b/main.go index 3abdf20..774a3fc 100644 --- a/main.go +++ b/main.go @@ -143,7 +143,7 @@ func main() { app.HelpFlag.Short('h') if err := utils.SetLimits(); err != nil { - cli.Fatal(err.Error()) + cli.Fatal(err) } switch kingpin.MustParse(app.Parse(os.Args[1:])) { @@ -177,11 +177,11 @@ func main() { config.Opt{Option: config.OptionTypeAdtag, Value: *proxyAdtag}, ) if err != nil { - cli.Fatal(err.Error()) + cli.Fatal(err) } if err := cli.Proxy(); err != nil { - cli.Fatal(err.Error()) + cli.Fatal(err) } } } diff --git a/ntp/ntp.go b/ntp/ntp.go index e89d7bf..ee5d515 100644 --- a/ntp/ntp.go +++ b/ntp/ntp.go @@ -1,11 +1,11 @@ package ntp import ( + "fmt" "math/rand" "time" "github.com/beevik/ntp" - "github.com/juju/errors" "go.uber.org/zap" ) @@ -23,7 +23,7 @@ func Fetch() (time.Duration, error) { url := ntpEndpoints[rand.Intn(len(ntpEndpoints))] resp, err := ntp.Query(url) if err != nil { - return 0, errors.Annotatef(err, "Cannot fetch NTP server %s", url) + return 0, fmt.Errorf("Cannot fetch NTP server %s: %w", url, err) } offsetInt := int64(resp.ClockOffset) diff --git a/obfuscated2/client_protocol.go b/obfuscated2/client_protocol.go index c035bcf..44a87e3 100644 --- a/obfuscated2/client_protocol.go +++ b/obfuscated2/client_protocol.go @@ -4,11 +4,11 @@ import ( "bytes" "crypto/sha256" "encoding/binary" + "errors" + "fmt" "io" "time" - "github.com/juju/errors" - "github.com/9seconds/mtg/antireplay" "github.com/9seconds/mtg/config" "github.com/9seconds/mtg/conntypes" @@ -26,7 +26,7 @@ type ClientProtocol struct { func (c *ClientProtocol) Handshake(socket wrappers.StreamReadWriteCloser) (wrappers.StreamReadWriteCloser, error) { fm, err := c.ReadFrame(socket) if err != nil { - return nil, errors.Annotate(err, "Cannot make client handshake") + return nil, fmt.Errorf("cannot make a client handshake: %w", err) } decHasher := sha256.New() @@ -76,7 +76,7 @@ func (c *ClientProtocol) Handshake(socket wrappers.StreamReadWriteCloser) (wrapp func (c *ClientProtocol) ReadFrame(socket wrappers.StreamReader) (fm Frame, err error) { if _, err = io.ReadFull(handshakeReader{socket}, fm.Bytes()); err != nil { - err = errors.Annotate(err, "Cannot extract obfuscated2 frame") + err = fmt.Errorf("cannot extract obfuscated2 frame: %w", err) } return } diff --git a/obfuscated2/telegram_protocol.go b/obfuscated2/telegram_protocol.go index 230193c..cce6cc5 100644 --- a/obfuscated2/telegram_protocol.go +++ b/obfuscated2/telegram_protocol.go @@ -2,8 +2,7 @@ package obfuscated2 import ( "crypto/rand" - - "github.com/juju/errors" + "fmt" "github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/telegram" @@ -23,7 +22,7 @@ func (t *TelegramProtocol) Handshake(req *protocol.TelegramRequest) (wrappers.Wr req.ClientProtocol.GetDC(), req.ClientProtocol.GetConnectionProtocol()) if err != nil { - return nil, errors.Annotate(err, "Cannot dial to Telegram") + return nil, fmt.Errorf("cannot dial to telegram: %w", err) } fm := generateFrame(req.ClientProtocol) data := fm.Bytes() @@ -38,7 +37,7 @@ func (t *TelegramProtocol) Handshake(req *protocol.TelegramRequest) (wrappers.Wr copy(data[:frameOffsetIV], copyFrame[:frameOffsetIV]) if _, err := socket.Write(data); err != nil { - return nil, errors.Annotate(err, "Cannot write handshate frame to Telegram") + return nil, fmt.Errorf("cannot write handshake frame to telegram: %w", err) } return wrappers.NewObfuscated2(socket, encryptor, decryptor), nil diff --git a/stats/stats.go b/stats/stats.go index 6412132..b3742ec 100644 --- a/stats/stats.go +++ b/stats/stats.go @@ -2,11 +2,10 @@ package stats import ( "context" + "fmt" "net" "net/http" - "github.com/juju/errors" - "github.com/9seconds/mtg/config" "github.com/9seconds/mtg/conntypes" ) @@ -66,21 +65,21 @@ func Init(ctx context.Context) error { instanceJSON := newStatsJSON(mux) instancePrometheus, err := newStatsPrometheus(mux) if err != nil { - return errors.Annotate(err, "Cannot initialize Prometheus") + return fmt.Errorf("cannot initialize prometheus: %w", err) } stats := []Stats{instanceJSON, instancePrometheus} if config.C.StatsdStats.Addr.IP != nil { instanceStatsd, err := newStatsStatsd() if err != nil { - return errors.Annotate(err, "Cannot initialize StatsD") + return fmt.Errorf("cannot inialize statsd: %w", err) } stats = append(stats, instanceStatsd) } listener, err := net.Listen("tcp", config.C.StatsAddr.String()) if err != nil { - return errors.Annotate(err, "Cannot initialize stats server") + return fmt.Errorf("cannot initialize stats server: %w", err) } srv := http.Server{ diff --git a/stats/stats_prometheus.go b/stats/stats_prometheus.go index d4b0d03..80c512a 100644 --- a/stats/stats_prometheus.go +++ b/stats/stats_prometheus.go @@ -1,10 +1,10 @@ package stats import ( + "fmt" "net" "net/http" - "github.com/juju/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -91,16 +91,16 @@ func newStatsPrometheus(mux *http.ServeMux) (Stats, error) { } if err := registry.Register(instance.connections); err != nil { - return nil, errors.Annotate(err, "Cannot register metrics for connections") + return nil, fmt.Errorf("cannot register metrics for connections: %w", err) } if err := registry.Register(instance.traffic); err != nil { - return nil, errors.Annotate(err, "Cannot register metrics for traffic") + return nil, fmt.Errorf("cannot register metrics for traffic: %w", err) } if err := registry.Register(instance.crashes); err != nil { - return nil, errors.Annotate(err, "Cannot register metrics for crashes") + return nil, fmt.Errorf("cannot register metrics for crashes: %w", err) } if err := registry.Register(instance.antiReplays); err != nil { - return nil, errors.Annotate(err, "Cannot register metrics for anti replays") + return nil, fmt.Errorf("cannot register metrics for anti replays: %w", err) } handler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) diff --git a/stats/stats_statsd.go b/stats/stats_statsd.go index d78e9b0..a975b10 100644 --- a/stats/stats_statsd.go +++ b/stats/stats_statsd.go @@ -1,10 +1,10 @@ package stats import ( + "fmt" "net" "strings" - "github.com/juju/errors" "gopkg.in/alexcesaro/statsd.v2" "github.com/9seconds/mtg/config" @@ -78,7 +78,7 @@ func newStatsStatsd() (Stats, error) { client, err := statsd.New(options...) if err != nil { - return nil, errors.Annotate(err, "Cannot initialize a client") + return nil, fmt.Errorf("cannot initialize a client: %w", err) } return &statsStatsd{ diff --git a/telegram/base.go b/telegram/base.go index 8d5b6cb..b4d7717 100644 --- a/telegram/base.go +++ b/telegram/base.go @@ -2,12 +2,11 @@ package telegram import ( "context" + "fmt" "math/rand" "net" "time" - "github.com/juju/errors" - "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/utils" "github.com/9seconds/mtg/wrappers" @@ -29,11 +28,11 @@ func (b *baseTelegram) dialToAddress(ctx context.Context, addr string) (wrappers.StreamReadWriteCloser, error) { conn, err := b.dialer.Dial("tcp", addr) if err != nil { - return nil, errors.Annotate(err, "Dial has failed") + return nil, fmt.Errorf("dial has failed: %w", err) } if err := utils.InitTCP(conn); err != nil { - return nil, errors.Annotate(err, "Cannot initialize TCP socket") + return nil, fmt.Errorf("cannot initialize tcp socket: %w", err) } return wrappers.NewTelegramConn(ctx, cancel, conn), nil diff --git a/utils/init_tcp.go b/utils/init_tcp.go index f34b4d2..3fe7055 100644 --- a/utils/init_tcp.go +++ b/utils/init_tcp.go @@ -1,10 +1,9 @@ package utils import ( + "fmt" "net" - "github.com/juju/errors" - "github.com/9seconds/mtg/config" ) @@ -12,13 +11,13 @@ func InitTCP(conn net.Conn) error { tcpConn := conn.(*net.TCPConn) if err := tcpConn.SetNoDelay(true); err != nil { - return errors.Annotate(err, "Cannot set NO_DELAY") + return fmt.Errorf("cannot set TCP_NO_DELAY: %w", err) } if err := tcpConn.SetReadBuffer(config.C.BufferSize.Read); err != nil { - return errors.Annotate(err, "Cannot set read buffer size") + return fmt.Errorf("cannot set read buffer size: %w", err) } if err := tcpConn.SetWriteBuffer(config.C.BufferSize.Write); err != nil { - return errors.Annotate(err, "Cannot set write buffer size") + return fmt.Errorf("cannot set write buffer size: %w", err) } return nil diff --git a/utils/rlimit.go b/utils/rlimit.go index 98ed947..d62d3fa 100644 --- a/utils/rlimit.go +++ b/utils/rlimit.go @@ -3,24 +3,21 @@ package utils import ( - "golang.org/x/sys/unix" + "fmt" - "github.com/juju/errors" + "golang.org/x/sys/unix" ) -func SetLimits() (err error) { +func SetLimits() error { rLimit := unix.Rlimit{} - err = unix.Getrlimit(unix.RLIMIT_NOFILE, &rLimit) - if err != nil { - err = errors.Annotate(err, "Cannot get rlimit") - return + if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rLimit); err != nil { + return fmt.Errorf("cannot get rlimit: %w", err) } rLimit.Cur = rLimit.Max - err = unix.Setrlimit(unix.RLIMIT_NOFILE, &rLimit) - if err != nil { - err = errors.Annotate(err, "Cannot set rlimit") + if err := unix.Setrlimit(unix.RLIMIT_NOFILE, &rLimit); err != nil { + return fmt.Errorf("cannot set rlimit: %w", err) } - return + return nil } diff --git a/wrappers/wrapper_conn.go b/wrappers/wrapper_conn.go index 09f056c..19fd9f6 100644 --- a/wrappers/wrapper_conn.go +++ b/wrappers/wrapper_conn.go @@ -4,10 +4,10 @@ import ( "context" "crypto/rand" "encoding/hex" + "fmt" "net" "time" - "github.com/juju/errors" "go.uber.org/zap" "github.com/9seconds/mtg/config" @@ -47,12 +47,12 @@ func (w *wrapperConn) WriteTimeout(p []byte, timeout time.Duration) (int, error) select { case <-w.ctx.Done(): w.Close() - return 0, errors.Annotate(w.ctx.Err(), "Cannot write because context was closed") + return 0, fmt.Errorf("cannot write because context was closed: %w", w.ctx.Err()) default: if err := w.parent.SetWriteDeadline(time.Now().Add(timeout)); err != nil { w.Close() // nolint: gosec - return 0, errors.Annotate(err, "Cannot set write deadline to the socket") + return 0, fmt.Errorf("cannot set write deadline to the socket: %w", err) } n, err := w.parent.Write(p) @@ -73,12 +73,12 @@ func (w *wrapperConn) ReadTimeout(p []byte, timeout time.Duration) (int, error) select { case <-w.ctx.Done(): w.Close() - return 0, errors.Annotate(w.ctx.Err(), "Cannot read because context was closed") + return 0, fmt.Errorf("cannot read because context was closed: %w", w.ctx.Err()) default: if err := w.parent.SetReadDeadline(time.Now().Add(timeout)); err != nil { w.Close() - return 0, errors.Annotate(err, "Cannot set read deadline to the socket") + return 0, fmt.Errorf("cannot set read deadline to the socket: %w", err) } n, err := w.parent.Read(p) diff --git a/wrappers/wrapper_obfuscated2.go b/wrappers/wrapper_obfuscated2.go index 9cfaed7..4ef601a 100644 --- a/wrappers/wrapper_obfuscated2.go +++ b/wrappers/wrapper_obfuscated2.go @@ -2,10 +2,10 @@ package wrappers import ( "crypto/cipher" + "fmt" "net" "time" - "github.com/juju/errors" "go.uber.org/zap" ) @@ -18,7 +18,7 @@ type wrapperObfuscated2 struct { func (w *wrapperObfuscated2) ReadTimeout(p []byte, timeout time.Duration) (int, error) { n, err := w.parent.ReadTimeout(p, timeout) if err != nil { - return 0, errors.Annotate(err, "Cannot read stream ciphered data") + return 0, fmt.Errorf("cannot read stream ciphered data: %w", err) } w.decryptor.XORKeyStream(p, p[:n]) @@ -28,7 +28,7 @@ func (w *wrapperObfuscated2) ReadTimeout(p []byte, timeout time.Duration) (int, func (w *wrapperObfuscated2) Read(p []byte) (int, error) { n, err := w.parent.Read(p) if err != nil { - return 0, errors.Annotate(err, "Cannot read stream ciphered data") + return n, err } w.decryptor.XORKeyStream(p, p[:n]) From 3816dbf5b1b85245f1f6cecc4c10c195f1af8c9f Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 9 Sep 2019 10:25:38 +0300 Subject: [PATCH 08/62] Add telegram api --- conntypes/id.go | 24 +++++++++ go.mod | 1 + go.sum | 2 + protocol/request.go | 3 +- proxy/proxy.go | 3 +- telegram/api/addresses.go | 106 ++++++++++++++++++++++++++++++++++++++ telegram/api/api.go | 38 ++++++++++++++ telegram/api/secret.go | 23 +++++++++ wrappers/wrapper_conn.go | 29 ++--------- 9 files changed, 203 insertions(+), 26 deletions(-) create mode 100644 conntypes/id.go create mode 100644 telegram/api/addresses.go create mode 100644 telegram/api/api.go create mode 100644 telegram/api/secret.go diff --git a/conntypes/id.go b/conntypes/id.go new file mode 100644 index 0000000..1aa482c --- /dev/null +++ b/conntypes/id.go @@ -0,0 +1,24 @@ +package conntypes + +import ( + "crypto/rand" + "encoding/hex" +) + +const ConnIDLength = 8 + +type ConnID [ConnIDLength]byte + +func (c ConnID) String() string { + return hex.EncodeToString(c[:]) +} + +func NewConnID() ConnID { + var id ConnID + + if _, err := rand.Read(id[:]); err != nil { + panic(err) + } + + return id +} diff --git a/go.mod b/go.mod index e2a1821..4387fe3 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/allegro/bigcache v1.2.1 github.com/beevik/ntp v0.2.0 github.com/cespare/xxhash v1.1.0 + github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d github.com/kr/pretty v0.1.0 // indirect github.com/pkg/errors v0.8.1 // indirect github.com/prometheus/client_golang v1.1.0 diff --git a/go.sum b/go.sum index 0ffeeaf..9b41aba 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc= +github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= diff --git a/protocol/request.go b/protocol/request.go index ce8728d..e73489d 100644 --- a/protocol/request.go +++ b/protocol/request.go @@ -5,13 +5,14 @@ import ( "go.uber.org/zap" + "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/wrappers" ) type TelegramRequest struct { Logger *zap.SugaredLogger ClientConn wrappers.StreamReadWriteCloser - ConnID wrappers.ConnID + ConnID conntypes.ConnID Ctx context.Context Cancel context.CancelFunc ClientProtocol ClientProtocol diff --git a/proxy/proxy.go b/proxy/proxy.go index 7ead6d9..e4d910f 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -9,6 +9,7 @@ import ( "go.uber.org/zap" "github.com/9seconds/mtg/config" + "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/stats" "github.com/9seconds/mtg/telegram" @@ -53,7 +54,7 @@ func (p *Proxy) accept(conn net.Conn) { } }() - connID := wrappers.NewConnID() + connID := conntypes.NewConnID() logger := p.Logger.With("connection_id", connID) if err := utils.InitTCP(conn); err != nil { diff --git a/telegram/api/addresses.go b/telegram/api/addresses.go new file mode 100644 index 0000000..f65fcac --- /dev/null +++ b/telegram/api/addresses.go @@ -0,0 +1,106 @@ +package api + +import ( + "bufio" + "fmt" + "net" + "regexp" + "strconv" + "strings" + + "github.com/9seconds/mtg/conntypes" +) + +const ( + addressesURLV4 = "https://core.telegram.org/getProxyConfig" // nolint: gas + addressesURLV6 = "https://core.telegram.org/getProxyConfigV6" // nolint: gas +) + +var addressesProxyForSplitter = regexp.MustCompile(`\s+`) + +func AddressesV4() (map[conntypes.DC][]string, conntypes.DC, error) { + return getAddresses(addressesURLV4) +} + +func AddressesV6() (map[conntypes.DC][]string, conntypes.DC, error) { + return getAddresses(addressesURLV6) +} + +func getAddresses(url string) (map[conntypes.DC][]string, conntypes.DC, error) { + resp, err := request(url) + if err != nil { + return nil, 0, fmt.Errorf("cannot get http response: %w", err) + } + defer resp.Close() + + scanner := bufio.NewScanner(resp) + data := map[conntypes.DC][]string{} + + var defaultDC = conntypes.DCDefaultIdx + for scanner.Scan() { + text := strings.TrimSpace(scanner.Text()) + switch { + case strings.HasPrefix(text, "#"): + continue + + case strings.HasPrefix(text, "proxy_for"): + addr, idx, err := addressesParseProxyFor(text) + if err != nil { + return nil, 0, fmt.Errorf("cannot parse 'proxy_for' section: %w", err) + } + if addresses, ok := data[idx]; ok { + data[idx] = append(addresses, addr) + } else { + data[idx] = []string{addr} + } + + case strings.HasPrefix(text, "default"): + idx, err := addressesParseDefault(text) + if err != nil { + return nil, 0, fmt.Errorf("cannot parse 'default' section: %w", err) + } + defaultDC = idx + } + } + + err = scanner.Err() + if err != nil { + return nil, 0, fmt.Errorf("cannot parse http response: %w", err) + } + + return data, defaultDC, nil +} + +func addressesParseProxyFor(text string) (string, conntypes.DC, error) { + chunks := addressesProxyForSplitter.Split(text, 3) + if len(chunks) != 3 || chunks[0] != "proxy_for" { + return "", 0, fmt.Errorf("incorrect config %s", text) + } + + dc, err := strconv.ParseInt(chunks[1], 10, 16) + if err != nil { + return "", 0, fmt.Errorf("incorrect config '%s': %w", text, err) + } + + addr := strings.TrimRight(chunks[2], ";") + if _, _, err = net.SplitHostPort(addr); err != nil { + return "", 0, fmt.Errorf("incorrect config '%s': %w", text, err) + } + + return addr, conntypes.DC(dc), nil +} + +func addressesParseDefault(text string) (conntypes.DC, error) { + chunks := addressesProxyForSplitter.Split(text, 2) + if len(chunks) != 2 || chunks[0] != "default" { + return 0, fmt.Errorf("incorrect config '%s'", text) + } + + dcString := strings.TrimRight(chunks[1], ";") + dc, err := strconv.ParseInt(dcString, 10, 16) + if err != nil { + return 0, fmt.Errorf("incorrect config '%s': %w", text, err) + } + + return conntypes.DC(dc), nil +} diff --git a/telegram/api/api.go b/telegram/api/api.go new file mode 100644 index 0000000..8b36d83 --- /dev/null +++ b/telegram/api/api.go @@ -0,0 +1,38 @@ +package api + +import ( + "fmt" + "io" + "io/ioutil" + "net/http" + "time" +) + +const ( + apiUserAgent = "mtg" + apiHTTPTimeout = 30 * time.Second +) + +var httpClient = http.Client{ + Timeout: apiHTTPTimeout, +} + +func request(url string) (io.ReadCloser, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + panic(err) + } + req.Header.Set("Accept", "text/plan") + req.Header.Set("User-Agent", apiUserAgent) + + resp, err := httpClient.Do(req) + if err != nil { + if resp != nil { + io.Copy(ioutil.Discard, resp.Body) + resp.Body.Close() + } + return nil, fmt.Errorf("cannot perform a request: %w", err) + } + + return resp.Body, err +} diff --git a/telegram/api/secret.go b/telegram/api/secret.go new file mode 100644 index 0000000..c2b2e43 --- /dev/null +++ b/telegram/api/secret.go @@ -0,0 +1,23 @@ +package api + +import ( + "fmt" + "io/ioutil" +) + +const secretURL = "https://core.telegram.org/getProxySecret" // nolint: gas + +func Secret() ([]byte, error) { + resp, err := request(secretURL) + if err != nil { + return nil, fmt.Errorf("cannot access telegram server: %w", err) + } + defer resp.Close() + + secret, err := ioutil.ReadAll(resp) + if err != nil { + return nil, fmt.Errorf("cannot read response: %w", err) + } + + return secret, nil +} diff --git a/wrappers/wrapper_conn.go b/wrappers/wrapper_conn.go index 19fd9f6..1649cd1 100644 --- a/wrappers/wrapper_conn.go +++ b/wrappers/wrapper_conn.go @@ -2,8 +2,6 @@ package wrappers import ( "context" - "crypto/rand" - "encoding/hex" "fmt" "net" "time" @@ -11,16 +9,9 @@ import ( "go.uber.org/zap" "github.com/9seconds/mtg/config" + "github.com/9seconds/mtg/conntypes" ) -const ConnIDLength = 8 - -type ConnID [ConnIDLength]byte - -func (c ConnID) String() string { - return hex.EncodeToString(c[:]) -} - type connPurpose uint8 const ( @@ -37,7 +28,7 @@ type wrapperConn struct { parent net.Conn ctx context.Context cancel context.CancelFunc - connID ConnID + connID conntypes.ConnID logger *zap.SugaredLogger localAddr *net.TCPAddr remoteAddr *net.TCPAddr @@ -121,7 +112,7 @@ func (w *wrapperConn) RemoteAddr() *net.TCPAddr { func newConn(ctx context.Context, cancel context.CancelFunc, parent net.Conn, - connID ConnID, + connID conntypes.ConnID, purpose connPurpose) StreamReadWriteCloser { localAddr := *parent.LocalAddr().(*net.TCPAddr) @@ -156,22 +147,12 @@ func newConn(ctx context.Context, func NewClientConn(ctx context.Context, cancel context.CancelFunc, parent net.Conn, - connID ConnID) StreamReadWriteCloser { + connID conntypes.ConnID) StreamReadWriteCloser { return newConn(ctx, cancel, parent, connID, connPurposeClient) } func NewTelegramConn(ctx context.Context, cancel context.CancelFunc, parent net.Conn) StreamReadWriteCloser { - return newConn(ctx, cancel, parent, ConnID{}, connPurposeTelegram) -} - -func NewConnID() ConnID { - var id ConnID - - if _, err := rand.Read(id[:]); err != nil { - panic(err) - } - - return id + return newConn(ctx, cancel, parent, conntypes.ConnID{}, connPurposeTelegram) } From d431feb0ba10446c061dae91a3dade076d55132c Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 9 Sep 2019 12:41:53 +0300 Subject: [PATCH 09/62] Add base wrappers for mtproto --- go.mod | 2 +- mtproto/consts.go | 32 ++++ mtproto/proxy_flags.go | 59 +++++++ telegram/middle.go | 56 ++++++ wrappers/blockcipher.go | 157 +++++++++++++++++ wrappers/{wrapper_conn.go => conn.go} | 0 wrappers/mtproto_frame.go | 159 ++++++++++++++++++ ...{wrapper_obfuscated2.go => obfuscated2.go} | 0 wrappers/{wrapper_stats.go => stats.go} | 0 9 files changed, 464 insertions(+), 1 deletion(-) create mode 100644 mtproto/consts.go create mode 100644 mtproto/proxy_flags.go create mode 100644 telegram/middle.go create mode 100644 wrappers/blockcipher.go rename wrappers/{wrapper_conn.go => conn.go} (100%) create mode 100644 wrappers/mtproto_frame.go rename wrappers/{wrapper_obfuscated2.go => obfuscated2.go} (100%) rename wrappers/{wrapper_stats.go => stats.go} (100%) diff --git a/go.mod b/go.mod index 4387fe3..abc3da9 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/cespare/xxhash v1.1.0 github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d github.com/kr/pretty v0.1.0 // indirect - github.com/pkg/errors v0.8.1 // indirect + github.com/pkg/errors v0.8.1 github.com/prometheus/client_golang v1.1.0 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect diff --git a/mtproto/consts.go b/mtproto/consts.go new file mode 100644 index 0000000..a8330a7 --- /dev/null +++ b/mtproto/consts.go @@ -0,0 +1,32 @@ +package mtproto + +// SeqNo* is the number of the sequence which have special meaning for +// the Telegram. +const ( + SeqNoNonce = -2 + SeqNoHandshake = -1 +) + +// Different constants for RPC protocol +var ( + TagCloseExt = []byte{0xa2, 0x34, 0xb6, 0x5e} + TagProxyAns = []byte{0x0d, 0xda, 0x03, 0x44} + TagSimpleAck = []byte{0x9b, 0x40, 0xac, 0x3b} + TagHandshake = []byte{0xf5, 0xee, 0x82, 0x76} + TagNonce = []byte{0xaa, 0x87, 0xcb, 0x7a} + TagProxyRequest = []byte{0xee, 0xf1, 0xce, 0x36} + + NonceCryptoAES = []byte{0x01, 0x00, 0x00, 0x00} + + HandshakeFlags = []byte{0x00, 0x00, 0x00, 0x00} + + ProxyRequestExtraSize = []byte{0x18, 0x00, 0x00, 0x00} + ProxyRequestProxyTag = []byte{0xae, 0x26, 0x1e, 0xdb} + + HandshakeSenderPID = []byte("IPIPPRPDTIME") + HandshakePeerPID = []byte("IPIPPRPDTIME") + + HandshakeRequest = append(TagHandshake, + append(HandshakeFlags, + append(HandshakeSenderPID, HandshakePeerPID...)...)...) +) diff --git a/mtproto/proxy_flags.go b/mtproto/proxy_flags.go new file mode 100644 index 0000000..bd7cd69 --- /dev/null +++ b/mtproto/proxy_flags.go @@ -0,0 +1,59 @@ +package mtproto + +import ( + "encoding/binary" + "strings" +) + +type proxyRequestFlags uint32 + +const ( + proxyRequestFlagsHasAdTag proxyRequestFlags = 0x8 + proxyRequestFlagsEncrypted proxyRequestFlags = 0x2 + proxyRequestFlagsMagic proxyRequestFlags = 0x1000 + proxyRequestFlagsExtMode2 proxyRequestFlags = 0x20000 + proxyRequestFlagsIntermediate proxyRequestFlags = 0x20000000 + proxyRequestFlagsAbdridged proxyRequestFlags = 0x40000000 + proxyRequestFlagsQuickAck proxyRequestFlags = 0x80000000 + proxyRequestFlagsPad proxyRequestFlags = 0x8000000 +) + +var proxyRequestFlagsEncryptedPrefix [8]byte + +func (r proxyRequestFlags) Bytes() []byte { + converted := make([]byte, 4) + binary.LittleEndian.PutUint32(converted, uint32(r)) + + return converted +} + +func (r proxyRequestFlags) String() string { + flags := make([]string, 0, 7) + + if r&proxyRequestFlagsHasAdTag != 0 { + flags = append(flags, "HAS_AD_TAG") + } + if r&proxyRequestFlagsEncrypted != 0 { + flags = append(flags, "ENCRYPTED") + } + if r&proxyRequestFlagsMagic != 0 { + flags = append(flags, "MAGIC") + } + if r&proxyRequestFlagsExtMode2 != 0 { + flags = append(flags, "EXT_MODE_2") + } + if r&proxyRequestFlagsIntermediate != 0 { + flags = append(flags, "INTERMEDIATE") + } + if r&proxyRequestFlagsAbdridged != 0 { + flags = append(flags, "ABRIDGED") + } + if r&proxyRequestFlagsQuickAck != 0 { + flags = append(flags, "QUICK_ACK") + } + if r&proxyRequestFlagsPad != 0 { + flags = append(flags, "PAD") + } + + return strings.Join(flags, " | ") +} diff --git a/telegram/middle.go b/telegram/middle.go new file mode 100644 index 0000000..3ffc3b1 --- /dev/null +++ b/telegram/middle.go @@ -0,0 +1,56 @@ +package telegram + +import ( + "context" + "fmt" + "sync" + + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/telegram/api" + "github.com/9seconds/mtg/wrappers" +) + +type middleTelegram struct { + baseTelegram + + secret []byte + mutex sync.RWMutex +} + +func (m *middleTelegram) update() error { + secret, err := api.Secret() + if err != nil { + return fmt.Errorf("cannot fetch secret: %w", err) + } + + v4Addresses, v4DefaultDC, err := api.AddressesV4() + if err != nil { + return fmt.Errorf("cannot fetch addresses for ipv4: %w", err) + } + + v6Addresses, v6DefaultDC, err := api.AddressesV6() + if err != nil { + return fmt.Errorf("cannot fetch addresses for ipv6: %w", err) + } + + m.mutex.Lock() + m.secret = secret + m.v4DefaultDC = v4DefaultDC + m.V6DefaultDC = v6DefaultDC + m.v4Addresses = v4Addresses + m.v6Addresses = v6Addresses + m.mutex.Unlock() + + return nil +} + +func (m *middleTelegram) Dial(ctx context.Context, + cancel context.CancelFunc, + dc conntypes.DC, + protocol conntypes.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) { + if dc == 0 { + dc = conntypes.DCDefaultIdx + } + + return m.baseTelegram.dial(ctx, cancel, dc, protocol) +} diff --git a/wrappers/blockcipher.go b/wrappers/blockcipher.go new file mode 100644 index 0000000..c6a00b5 --- /dev/null +++ b/wrappers/blockcipher.go @@ -0,0 +1,157 @@ +package wrappers + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "errors" + "fmt" + "net" + "time" + + "go.uber.org/zap" +) + +const blockCipherReadCurrentDataBufferSize = 1024 + 1 // +1 because telegram operates with blocks mod 4 + +type wrapperBlockCipher struct { + buf bytes.Buffer + + parent StreamReadWriteCloser + encryptor cipher.BlockMode + decryptor cipher.BlockMode +} + +func (w *wrapperBlockCipher) Write(p []byte) (int, error) { + encrypted, err := w.encrypt(p) + if err != nil { + return 0, err + } + return w.parent.Write(encrypted) +} + +func (w *wrapperBlockCipher) WriteTimeout(p []byte, timeout time.Duration) (int, error) { + encrypted, err := w.encrypt(p) + if err != nil { + return 0, err + } + return w.parent.WriteTimeout(encrypted, timeout) +} + +func (w *wrapperBlockCipher) Read(p []byte) (int, error) { + return w.read(p, readAll) + +} + +func (w *wrapperBlockCipher) ReadTimeout(p []byte, timeout time.Duration) (int, error) { + return w.read(p, readAllTimeout(timeout)) +} + +func (w *wrapperBlockCipher) read(p []byte, reader func(StreamReadWriteCloser) ([]byte, error)) (int, error) { + if w.buf.Len() > 0 { + return w.flush(p) + } + + var buf []byte + for len(buf) == 0 || len(buf)%aes.BlockSize != 0 { + rv, err := reader(w.parent) + if err != nil { + return 0, fmt.Errorf("cannot read from socket: %w", err) + } + buf = append(buf, rv...) + } + + w.decryptor.CryptBlocks(buf, buf) + w.buf.Write(buf) + + return w.flush(p) +} + +func (w *wrapperBlockCipher) flush(p []byte) (int, error) { + if w.buf.Len() > len(p) { + return w.buf.Read(p) + } + + sizeToReturn := w.buf.Len() + copy(p, w.buf.Bytes()) + w.buf.Reset() + + return sizeToReturn, nil +} + +func (w *wrapperBlockCipher) encrypt(p []byte) ([]byte, error) { + if len(p)%aes.BlockSize > 0 { + return nil, fmt.Errorf("incorrect block size %d", len(p)) + } + + encrypted := make([]byte, len(p)) + w.encryptor.CryptBlocks(encrypted, p) + + return encrypted, nil +} + +func readAll(src StreamReadWriteCloser) (rv []byte, err error) { + buf := make([]byte, blockCipherReadCurrentDataBufferSize) + n := blockCipherReadCurrentDataBufferSize + + for n == len(buf) { + n, err = src.Read(buf) + if err != nil { + return nil, err + } + rv = append(rv, buf[:n]...) + } + + return rv, nil +} + +func readAllTimeout(timeout time.Duration) func(StreamReadWriteCloser) ([]byte, error) { + return func(src StreamReadWriteCloser) (rv []byte, err error) { + tmo := timeout + buf := make([]byte, blockCipherReadCurrentDataBufferSize) + n := blockCipherReadCurrentDataBufferSize + + for n == len(buf) { + if tmo <= 0 { + return nil, errors.New("timeout") + } + startTime := time.Now() + n, err = src.ReadTimeout(buf, tmo) + if err != nil { + return nil, err + } + rv = append(rv, buf[:n]...) + tmo -= time.Since(startTime) + } + + return rv, nil + } +} + +func (w *wrapperBlockCipher) Close() error { + return w.parent.Close() +} + +func (w *wrapperBlockCipher) Conn() net.Conn { + return w.parent.Conn() +} + +func (w *wrapperBlockCipher) Logger() *zap.SugaredLogger { + return w.parent.Logger().Named("block-cipher") +} + +func (w *wrapperBlockCipher) LocalAddr() *net.TCPAddr { + return w.parent.LocalAddr() +} + +func (w *wrapperBlockCipher) RemoteAddr() *net.TCPAddr { + return w.parent.RemoteAddr() +} + +func NewBlockCipher(parent StreamReadWriteCloser, encryptor, decryptor cipher.BlockMode) StreamReadWriteCloser { + return &wrapperBlockCipher{ + parent: parent, + encryptor: encryptor, + decryptor: decryptor, + } +} diff --git a/wrappers/wrapper_conn.go b/wrappers/conn.go similarity index 100% rename from wrappers/wrapper_conn.go rename to wrappers/conn.go diff --git a/wrappers/mtproto_frame.go b/wrappers/mtproto_frame.go new file mode 100644 index 0000000..3fecc56 --- /dev/null +++ b/wrappers/mtproto_frame.go @@ -0,0 +1,159 @@ +package wrappers + +import ( + "bytes" + "crypto/aes" + "encoding/binary" + "fmt" + "hash/crc32" + "io" + "io/ioutil" + "net" + + "go.uber.org/zap" +) + +const ( + mtprotoFrameMinMessageLength = 12 + mtprotoFrameMaxMessageLength = 16777216 +) + +var mtprotoFramePadding = []byte{0x04, 0x00, 0x00, 0x00} + +// MTProtoFrame is a wrapper which converts written data to the MTProtoFrame. +// The format of the frame: +// +// [ MSGLEN(4) | SEQNO(4) | MSG(...) | CRC32(4) | PADDING(4*x) ] +// +// MSGLEN is the length of the message + len of seqno and msglen. +// SEQNO is the number of frame in the receive/send sequence. If client +// sends a message with SeqNo 18, it has to receive message with SeqNo 18. +// MSG is the data which has to be written +// CRC32 is the CRC32 checksum of MSGLEN + SEQNO + MSG +// PADDING is custom padding schema to complete frame length to such that +// len(frame) % 16 == 0 +type wrapperMtprotoFrame struct { + parent StreamReadWriteCloser + logger *zap.SugaredLogger + readSeqNo int32 + writeSeqNo int32 +} + +func (w *wrapperMtprotoFrame) Read() (Packet, error) { + buf := &bytes.Buffer{} + sum := crc32.NewIEEE() + writer := io.MultiWriter(buf, sum) + + for { + buf.Reset() + sum.Reset() + if _, err := io.CopyN(writer, w.parent, 4); err != nil { + return nil, fmt.Errorf("cannot read frame padding: %w", err) + } + if !bytes.Equal(buf.Bytes(), mtprotoFramePadding) { + break + } + } + + messageLength := binary.LittleEndian.Uint32(buf.Bytes()) + w.logger.Debugw("Read MTProto frame", + "messageLength", messageLength, + "sequence_number", w.readSeqNo, + ) + if messageLength%4 != 0 || messageLength < mtprotoFrameMinMessageLength || + messageLength > mtprotoFrameMaxMessageLength { + return nil, fmt.Errorf("Incorrect frame message length %d", messageLength) + } + + buf.Reset() + buf.Grow(int(messageLength) - 4 - 4) + if _, err := io.CopyN(writer, w.parent, int64(messageLength)-4-4); err != nil { + return nil, fmt.Errorf("cannot read the message frame: %w", err) + } + + var seqNo int32 + binary.Read(buf, binary.LittleEndian, &seqNo) // nolint: errcheck, gosec + if seqNo != w.readSeqNo { + return nil, fmt.Errorf("unexpected sequence number %d (wait for %d)", seqNo, w.readSeqNo) + } + + data, _ := ioutil.ReadAll(buf) // nolint: gosec + buf.Reset() + // write to buf, not to writer. This is because we are going to fetch + // crc32 checksum. + if _, err := io.CopyN(buf, w.parent, 4); err != nil { + return nil, fmt.Errorf("cannot read checksum: %w", err) + } + + checksum := binary.LittleEndian.Uint32(buf.Bytes()) + if checksum != sum.Sum32() { + return nil, fmt.Errorf("CRC32 checksum mismatch. wait for %d, got %d", sum.Sum32(), checksum) + } + + w.logger.Debugw("Read MTProto frame", + "messageLength", messageLength, + "sequence_number", w.readSeqNo, + "dataLength", len(data), + "checksum", checksum, + ) + w.readSeqNo++ + + return data, nil +} + +func (w *wrapperMtprotoFrame) Write(p Packet) error { + messageLength := 4 + 4 + len(p) + 4 + paddingLength := (aes.BlockSize - messageLength%aes.BlockSize) % aes.BlockSize + + buf := &bytes.Buffer{} + buf.Grow(messageLength + paddingLength) + + binary.Write(buf, binary.LittleEndian, uint32(messageLength)) + binary.Write(buf, binary.LittleEndian, w.writeSeqNo) + buf.Write(p) + + checksum := crc32.ChecksumIEEE(buf.Bytes()) + binary.Write(buf, binary.LittleEndian, checksum) + buf.Write(bytes.Repeat(mtprotoFramePadding, paddingLength/4)) + + w.logger.Debugw("Write MTProto frame", + "length", len(p), + "sequence_number", w.writeSeqNo, + "crc32", checksum, + "frame_length", buf.Len(), + ) + w.writeSeqNo++ + + _, err := w.parent.Write(buf.Bytes()) + + return err +} + +func (w *wrapperMtprotoFrame) Close() error { + return w.parent.Close() +} + +func (w *wrapperMtprotoFrame) Conn() net.Conn { + return w.parent.Conn() +} + +func (w *wrapperMtprotoFrame) Logger() *zap.SugaredLogger { + return w.logger +} + +func (w *wrapperMtprotoFrame) LocalAddr() *net.TCPAddr { + return w.parent.LocalAddr() +} + +func (w *wrapperMtprotoFrame) RemoteAddr() *net.TCPAddr { + return w.parent.RemoteAddr() +} + +func NewMtprotoFrame(parent StreamReadWriteCloser, seqNo int32) PacketReadWriteCloser { + return &wrapperMtprotoFrame{ + parent: parent, + logger: parent.Logger().Named("mtproto-frame"), + readSeqNo: seqNo, + writeSeqNo: seqNo, + } +} diff --git a/wrappers/wrapper_obfuscated2.go b/wrappers/obfuscated2.go similarity index 100% rename from wrappers/wrapper_obfuscated2.go rename to wrappers/obfuscated2.go diff --git a/wrappers/wrapper_stats.go b/wrappers/stats.go similarity index 100% rename from wrappers/wrapper_stats.go rename to wrappers/stats.go From 7827d1255a18a4196d54a7777a03c950117e210a Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 9 Sep 2019 17:02:37 +0300 Subject: [PATCH 10/62] Add background update for middle proxy --- mtproto/consts.go | 4 --- mtproto/rpc/handshake_request.go | 7 ++++ mtproto/rpc/handshake_response.go | 55 ++++++++++++++++++++++++++++ mtproto/rpc/nonce_request.go | 51 ++++++++++++++++++++++++++ mtproto/rpc/nonce_response.go | 60 +++++++++++++++++++++++++++++++ mtproto/{ => rpc}/proxy_flags.go | 2 +- telegram/middle.go | 32 +++++++++++++++++ 7 files changed, 206 insertions(+), 5 deletions(-) create mode 100644 mtproto/rpc/handshake_request.go create mode 100644 mtproto/rpc/handshake_response.go create mode 100644 mtproto/rpc/nonce_request.go create mode 100644 mtproto/rpc/nonce_response.go rename mtproto/{ => rpc}/proxy_flags.go (98%) diff --git a/mtproto/consts.go b/mtproto/consts.go index a8330a7..1ce67e1 100644 --- a/mtproto/consts.go +++ b/mtproto/consts.go @@ -25,8 +25,4 @@ var ( HandshakeSenderPID = []byte("IPIPPRPDTIME") HandshakePeerPID = []byte("IPIPPRPDTIME") - - HandshakeRequest = append(TagHandshake, - append(HandshakeFlags, - append(HandshakeSenderPID, HandshakePeerPID...)...)...) ) diff --git a/mtproto/rpc/handshake_request.go b/mtproto/rpc/handshake_request.go new file mode 100644 index 0000000..48b02f4 --- /dev/null +++ b/mtproto/rpc/handshake_request.go @@ -0,0 +1,7 @@ +package rpc + +import "github.com/9seconds/mtg/mtproto" + +var HandshakeRequest = append(mtproto.TagHandshake, + append(mtproto.HandshakeFlags, + append(mtproto.HandshakeSenderPID, mtproto.HandshakePeerPID...)...)...) diff --git a/mtproto/rpc/handshake_response.go b/mtproto/rpc/handshake_response.go new file mode 100644 index 0000000..effe63f --- /dev/null +++ b/mtproto/rpc/handshake_response.go @@ -0,0 +1,55 @@ +package rpc + +import ( + "bytes" + "errors" + "fmt" + + "github.com/9seconds/mtg/mtproto" +) + +type HandshakeResponse struct { + Type []byte + Flags []byte + SenderPID []byte + PeerPID []byte +} + +// Bytes returns a serialized handshake response. +func (r *HandshakeResponse) Bytes() []byte { + buf := &bytes.Buffer{} + + buf.Write(r.Type) // nolint: gosec + buf.Write(r.Flags) // nolint: gosec + buf.Write(r.SenderPID) // nolint: gosec + buf.Write(r.PeerPID) // nolint: gosec + + return buf.Bytes() +} + +// Valid checks that handshake response compliments request. +func (r *HandshakeResponse) Valid() error { + if !bytes.Equal(r.Type, mtproto.TagHandshake) { + return errors.New("Unexpected handshake tag") + } + if !bytes.Equal(r.PeerPID, mtproto.HandshakeSenderPID) { + return errors.New("Incorrect sender PID") + } + + return nil +} + +// NewHandshakeResponse constructs new handshake response from the given +// data. +func NewHandshakeResponse(data []byte) (*HandshakeResponse, error) { + if len(data) != 32 { + return nil, fmt.Errorf("Incorrect handshake response length %d", len(data)) + } + + return &HandshakeResponse{ + Type: data[:4], + Flags: data[4:8], + SenderPID: data[8:20], + PeerPID: data[20:], + }, nil +} diff --git a/mtproto/rpc/nonce_request.go b/mtproto/rpc/nonce_request.go new file mode 100644 index 0000000..6d3b976 --- /dev/null +++ b/mtproto/rpc/nonce_request.go @@ -0,0 +1,51 @@ +package rpc + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "fmt" + "time" + + "github.com/9seconds/mtg/mtproto" +) + +type NonceRequest struct { + KeySelector []byte + CryptoTS []byte + Nonce []byte +} + +// Bytes returns serialized nonce request. +func (r *NonceRequest) Bytes() []byte { + buf := &bytes.Buffer{} + + buf.Write(mtproto.TagNonce) // nolint: gosec + buf.Write(r.KeySelector) // nolint: gosec + buf.Write(mtproto.NonceCryptoAES) // nolint: gosec + buf.Write(r.CryptoTS) // nolint: gosec + buf.Write(r.Nonce) // nolint: gosec + + return buf.Bytes() +} + +// NewNonceRequest builds new none request based on proxy secret. +func NewNonceRequest(proxySecret []byte) (*NonceRequest, error) { + nonce := make([]byte, 16) + keySelector := make([]byte, 4) + cryptoTS := make([]byte, 4) + + if _, err := rand.Read(nonce); err != nil { + return nil, fmt.Errorf("cannot generate nonce: %w", err) + } + copy(keySelector, proxySecret) + + timestamp := time.Now().Truncate(time.Second).Unix() % 4294967296 // 256 ^ 4 - do not know how to name + binary.LittleEndian.PutUint32(cryptoTS, uint32(timestamp)) + + return &NonceRequest{ + KeySelector: keySelector, + CryptoTS: cryptoTS, + Nonce: nonce, + }, nil +} diff --git a/mtproto/rpc/nonce_response.go b/mtproto/rpc/nonce_response.go new file mode 100644 index 0000000..5e78be5 --- /dev/null +++ b/mtproto/rpc/nonce_response.go @@ -0,0 +1,60 @@ +package rpc + +import ( + "bytes" + "errors" + "fmt" + + "github.com/9seconds/mtg/mtproto" +) + +type NonceResponse struct { + NonceRequest + + Type []byte + Crypto []byte +} + +// Bytes returns serialized form of the nonce response. +func (r *NonceResponse) Bytes() []byte { + buf := &bytes.Buffer{} + + buf.Write(r.Type) // nolint: gosec + buf.Write(r.KeySelector) // nolint: gosec + buf.Write(r.Crypto) // nolint: gosec + buf.Write(r.CryptoTS) // nolint: gosec + buf.Write(r.Nonce) // nolint: gosec + + return buf.Bytes() +} + +func (r *NonceResponse) Valid(req *NonceRequest) error { + if !bytes.Equal(r.Type, mtproto.TagNonce) { + return errors.New("Unexpected RPC type") + } + if !bytes.Equal(r.Crypto, mtproto.NonceCryptoAES) { + return errors.New("Unexpected crypto type") + } + if !bytes.Equal(r.KeySelector, req.KeySelector) { + return errors.New("Unexpected key selector") + } + + return nil +} + +// NewNonceResponse build new nonce response based on the given data. +func NewNonceResponse(data []byte) (*NonceResponse, error) { + if len(data) != 32 { + return nil, fmt.Errorf("Unexpected message length %d", len(data)) + } + + return &NonceResponse{ + NonceRequest: NonceRequest{ + KeySelector: data[4:8], + CryptoTS: data[12:16], + Nonce: data[16:], + }, + Type: data[:4], + Crypto: data[8:12], + }, nil +} diff --git a/mtproto/proxy_flags.go b/mtproto/rpc/proxy_flags.go similarity index 98% rename from mtproto/proxy_flags.go rename to mtproto/rpc/proxy_flags.go index bd7cd69..c59d33f 100644 --- a/mtproto/proxy_flags.go +++ b/mtproto/rpc/proxy_flags.go @@ -1,4 +1,4 @@ -package mtproto +package rpc import ( "encoding/binary" diff --git a/telegram/middle.go b/telegram/middle.go index 3ffc3b1..fdc06e0 100644 --- a/telegram/middle.go +++ b/telegram/middle.go @@ -3,13 +3,19 @@ package telegram import ( "context" "fmt" + "net" "sync" + "time" + + "go.uber.org/zap" "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/telegram/api" "github.com/9seconds/mtg/wrappers" ) +const middleTelegramBackgroundUpdateEvery = time.Hour + type middleTelegram struct { baseTelegram @@ -44,6 +50,15 @@ func (m *middleTelegram) update() error { return nil } +func (m *middleTelegram) backgroundUpdate() { + logger := zap.S().Named("telegram") + for range time.Tick(middleTelegramBackgroundUpdateEvery) { + if err := m.update(); err != nil { + logger.Warnw("Cannot update Telegram proxies", "error", err) + } + } +} + func (m *middleTelegram) Dial(ctx context.Context, cancel context.CancelFunc, dc conntypes.DC, @@ -52,5 +67,22 @@ func (m *middleTelegram) Dial(ctx context.Context, dc = conntypes.DCDefaultIdx } + m.mutex.RLock() + defer m.mutex.RUnlock() + return m.baseTelegram.dial(ctx, cancel, dc, protocol) } + +func NewMiddleTelegram() Telegram { + tg := &middleTelegram{ + baseTelegram: baseTelegram{ + dialer: net.Dialer{Timeout: telegramDialTimeout}, + }, + } + if err := tg.update(); err != nil { + panic(err) + } + go tg.backgroundUpdate() + + return tg +} From 9534cb19d0c2c8e7db6cad65774e2370e97d2712 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 9 Sep 2019 17:11:40 +0300 Subject: [PATCH 11/62] Move mtproto consts to rpc --- mtproto/{ => rpc}/consts.go | 2 +- mtproto/rpc/handshake_request.go | 8 +++----- mtproto/rpc/handshake_response.go | 6 ++---- mtproto/rpc/nonce_request.go | 12 +++++------- mtproto/rpc/nonce_response.go | 6 ++---- 5 files changed, 13 insertions(+), 21 deletions(-) rename mtproto/{ => rpc}/consts.go (98%) diff --git a/mtproto/consts.go b/mtproto/rpc/consts.go similarity index 98% rename from mtproto/consts.go rename to mtproto/rpc/consts.go index 1ce67e1..59512a3 100644 --- a/mtproto/consts.go +++ b/mtproto/rpc/consts.go @@ -1,4 +1,4 @@ -package mtproto +package rpc // SeqNo* is the number of the sequence which have special meaning for // the Telegram. diff --git a/mtproto/rpc/handshake_request.go b/mtproto/rpc/handshake_request.go index 48b02f4..e0971cc 100644 --- a/mtproto/rpc/handshake_request.go +++ b/mtproto/rpc/handshake_request.go @@ -1,7 +1,5 @@ package rpc -import "github.com/9seconds/mtg/mtproto" - -var HandshakeRequest = append(mtproto.TagHandshake, - append(mtproto.HandshakeFlags, - append(mtproto.HandshakeSenderPID, mtproto.HandshakePeerPID...)...)...) +var HandshakeRequest = append(TagHandshake, + append(HandshakeFlags, + append(HandshakeSenderPID, HandshakePeerPID...)...)...) diff --git a/mtproto/rpc/handshake_response.go b/mtproto/rpc/handshake_response.go index effe63f..659ce7c 100644 --- a/mtproto/rpc/handshake_response.go +++ b/mtproto/rpc/handshake_response.go @@ -4,8 +4,6 @@ import ( "bytes" "errors" "fmt" - - "github.com/9seconds/mtg/mtproto" ) type HandshakeResponse struct { @@ -29,10 +27,10 @@ func (r *HandshakeResponse) Bytes() []byte { // Valid checks that handshake response compliments request. func (r *HandshakeResponse) Valid() error { - if !bytes.Equal(r.Type, mtproto.TagHandshake) { + if !bytes.Equal(r.Type, TagHandshake) { return errors.New("Unexpected handshake tag") } - if !bytes.Equal(r.PeerPID, mtproto.HandshakeSenderPID) { + if !bytes.Equal(r.PeerPID, HandshakeSenderPID) { return errors.New("Incorrect sender PID") } diff --git a/mtproto/rpc/nonce_request.go b/mtproto/rpc/nonce_request.go index 6d3b976..2915dad 100644 --- a/mtproto/rpc/nonce_request.go +++ b/mtproto/rpc/nonce_request.go @@ -6,8 +6,6 @@ import ( "encoding/binary" "fmt" "time" - - "github.com/9seconds/mtg/mtproto" ) type NonceRequest struct { @@ -20,11 +18,11 @@ type NonceRequest struct { func (r *NonceRequest) Bytes() []byte { buf := &bytes.Buffer{} - buf.Write(mtproto.TagNonce) // nolint: gosec - buf.Write(r.KeySelector) // nolint: gosec - buf.Write(mtproto.NonceCryptoAES) // nolint: gosec - buf.Write(r.CryptoTS) // nolint: gosec - buf.Write(r.Nonce) // nolint: gosec + buf.Write(TagNonce) + buf.Write(r.KeySelector) + buf.Write(NonceCryptoAES) + buf.Write(r.CryptoTS) + buf.Write(r.Nonce) return buf.Bytes() } diff --git a/mtproto/rpc/nonce_response.go b/mtproto/rpc/nonce_response.go index 5e78be5..08b9a4b 100644 --- a/mtproto/rpc/nonce_response.go +++ b/mtproto/rpc/nonce_response.go @@ -4,8 +4,6 @@ import ( "bytes" "errors" "fmt" - - "github.com/9seconds/mtg/mtproto" ) type NonceResponse struct { @@ -29,10 +27,10 @@ func (r *NonceResponse) Bytes() []byte { } func (r *NonceResponse) Valid(req *NonceRequest) error { - if !bytes.Equal(r.Type, mtproto.TagNonce) { + if !bytes.Equal(r.Type, TagNonce) { return errors.New("Unexpected RPC type") } - if !bytes.Equal(r.Crypto, mtproto.NonceCryptoAES) { + if !bytes.Equal(r.Crypto, NonceCryptoAES) { return errors.New("Unexpected crypto type") } if !bytes.Equal(r.KeySelector, req.KeySelector) { From 691954bd16d8fc5c0c0b5be9540f3c5e2a341088 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 9 Sep 2019 17:34:58 +0300 Subject: [PATCH 12/62] Simplify internals --- cli/proxy.go | 16 ++++++++-------- obfuscated2/client_protocol.go | 30 ++++++++++++++++++++++-------- obfuscated2/telegram_protocol.go | 20 +++++++------------- protocol/base_protocol.go | 21 --------------------- protocol/interfaces.go | 12 +++++------- proxy/proxy.go | 8 +++----- telegram/direct.go | 4 +++- telegram/middle.go | 2 ++ 8 files changed, 50 insertions(+), 63 deletions(-) delete mode 100644 protocol/base_protocol.go diff --git a/cli/proxy.go b/cli/proxy.go index df0bc18..5a85a2e 100644 --- a/cli/proxy.go +++ b/cli/proxy.go @@ -14,7 +14,6 @@ import ( "github.com/9seconds/mtg/obfuscated2" "github.com/9seconds/mtg/proxy" "github.com/9seconds/mtg/stats" - "github.com/9seconds/mtg/telegram" "github.com/9seconds/mtg/utils" ) @@ -79,14 +78,15 @@ func Proxy() error { app := &proxy.Proxy{ Logger: zap.S().Named("proxy"), Context: ctx, + ClientProtocolMaker: obfuscated2.MakeClientProtocol, + TelegramProtocolMaker: obfuscated2.MakeTelegramProtocol, } - if len(config.C.AdTag) == 0 { - app.TelegramProtocolMaker = obfuscated2.MakeTelegramProtocol - app.TelegramDialer = telegram.NewDirectTelegram() - } - if config.C.SecretMode != config.SecretModeTLS { - app.ClientProtocolMaker = obfuscated2.MakeClientProtocol - } + // if len(config.C.AdTag) == 0 { + // app.TelegramProtocolMaker = obfuscated2.MakeTelegramProtocol + // } + // if config.C.SecretMode != config.SecretModeTLS { + // app.ClientProtocolMaker = obfuscated2.MakeClientProtocol + // } app.Serve(proxyListener) diff --git a/obfuscated2/client_protocol.go b/obfuscated2/client_protocol.go index 44a87e3..37d9800 100644 --- a/obfuscated2/client_protocol.go +++ b/obfuscated2/client_protocol.go @@ -20,7 +20,21 @@ import ( const clientProtocolHandshakeTimeout = 10 * time.Second type ClientProtocol struct { - protocol.BaseProtocol + connectionType conntypes.ConnectionType + connectionProtocol conntypes.ConnectionProtocol + dc conntypes.DC +} + +func (c *ClientProtocol) ConnectionType() conntypes.ConnectionType { + return c.connectionType +} + +func (c *ClientProtocol) ConnectionProtocol() conntypes.ConnectionProtocol { + return c.connectionProtocol +} + +func (c *ClientProtocol) DC() conntypes.DC { + return c.dc } func (c *ClientProtocol) Handshake(socket wrappers.StreamReadWriteCloser) (wrappers.StreamReadWriteCloser, error) { @@ -46,23 +60,23 @@ func (c *ClientProtocol) Handshake(socket wrappers.StreamReadWriteCloser) (wrapp magic := decryptedFrame.Magic() switch { case bytes.Equal(magic, conntypes.ConnectionTagAbridged): - c.ConnectionType = conntypes.ConnectionTypeAbridged + c.connectionType = conntypes.ConnectionTypeAbridged case bytes.Equal(magic, conntypes.ConnectionTagIntermediate): - c.ConnectionType = conntypes.ConnectionTypeIntermediate + c.connectionType = conntypes.ConnectionTypeIntermediate case bytes.Equal(magic, conntypes.ConnectionTagSecure): - c.ConnectionType = conntypes.ConnectionTypeSecure + c.connectionType = conntypes.ConnectionTypeSecure default: return nil, errors.New("Unknown connection type") } - c.ConnectionProtocol = conntypes.ConnectionProtocolIPv4 + c.connectionProtocol = conntypes.ConnectionProtocolIPv4 if socket.LocalAddr().IP.To4() == nil { - c.ConnectionProtocol = conntypes.ConnectionProtocolIPv6 + c.connectionProtocol = conntypes.ConnectionProtocolIPv6 } buf := bytes.NewReader(decryptedFrame.DC()) - if err := binary.Read(buf, binary.LittleEndian, &c.DC); err != nil { - c.DC = conntypes.DCDefaultIdx + if err := binary.Read(buf, binary.LittleEndian, &c.dc); err != nil { + c.dc = conntypes.DCDefaultIdx } antiReplayKey := decryptedFrame.Unique() diff --git a/obfuscated2/telegram_protocol.go b/obfuscated2/telegram_protocol.go index cce6cc5..b3b1a45 100644 --- a/obfuscated2/telegram_protocol.go +++ b/obfuscated2/telegram_protocol.go @@ -10,17 +10,13 @@ import ( "github.com/9seconds/mtg/wrappers" ) -type TelegramProtocol struct { - protocol.BaseProtocol - - dialer telegram.Telegram -} +type TelegramProtocol struct{} func (t *TelegramProtocol) Handshake(req *protocol.TelegramRequest) (wrappers.Wrap, error) { - socket, err := t.dialer.Dial(req.Ctx, + socket, err := telegram.Direct.Dial(req.Ctx, req.Cancel, - req.ClientProtocol.GetDC(), - req.ClientProtocol.GetConnectionProtocol()) + req.ClientProtocol.DC(), + req.ClientProtocol.ConnectionProtocol()) if err != nil { return nil, fmt.Errorf("cannot dial to telegram: %w", err) } @@ -43,10 +39,8 @@ func (t *TelegramProtocol) Handshake(req *protocol.TelegramRequest) (wrappers.Wr return wrappers.NewObfuscated2(socket, encryptor, decryptor), nil } -func MakeTelegramProtocol(dialer telegram.Telegram) protocol.TelegramProtocol { - return &TelegramProtocol{ - dialer: dialer, - } +func MakeTelegramProtocol() protocol.TelegramProtocol { + return &TelegramProtocol{} } func generateFrame(cp protocol.ClientProtocol) (fm Frame) { @@ -70,7 +64,7 @@ func generateFrame(cp protocol.ClientProtocol) (fm Frame) { continue } - copy(fm.Magic(), cp.GetConnectionType().Tag()) + copy(fm.Magic(), cp.ConnectionType().Tag()) return } diff --git a/protocol/base_protocol.go b/protocol/base_protocol.go deleted file mode 100644 index 158ee1f..0000000 --- a/protocol/base_protocol.go +++ /dev/null @@ -1,21 +0,0 @@ -package protocol - -import "github.com/9seconds/mtg/conntypes" - -type BaseProtocol struct { - ConnectionType conntypes.ConnectionType - ConnectionProtocol conntypes.ConnectionProtocol - DC conntypes.DC -} - -func (b *BaseProtocol) GetConnectionType() conntypes.ConnectionType { - return b.ConnectionType -} - -func (b *BaseProtocol) GetConnectionProtocol() conntypes.ConnectionProtocol { - return b.ConnectionProtocol -} - -func (b *BaseProtocol) GetDC() conntypes.DC { - return b.DC -} diff --git a/protocol/interfaces.go b/protocol/interfaces.go index 4b81ab7..09f2c59 100644 --- a/protocol/interfaces.go +++ b/protocol/interfaces.go @@ -2,21 +2,19 @@ package protocol import ( "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/telegram" "github.com/9seconds/mtg/wrappers" ) type ClientProtocol interface { Handshake(wrappers.StreamReadWriteCloser) (wrappers.StreamReadWriteCloser, error) - GetConnectionType() conntypes.ConnectionType - GetConnectionProtocol() conntypes.ConnectionProtocol - GetDC() conntypes.DC + ConnectionType() conntypes.ConnectionType + ConnectionProtocol() conntypes.ConnectionProtocol + DC() conntypes.DC } -type ClientProtocolMaker func() ClientProtocol - type TelegramProtocol interface { Handshake(*TelegramRequest) (wrappers.Wrap, error) } -type TelegramProtocolMaker func(telegram.Telegram) TelegramProtocol +type ClientProtocolMaker func() ClientProtocol +type TelegramProtocolMaker func() TelegramProtocol diff --git a/proxy/proxy.go b/proxy/proxy.go index e4d910f..e11e001 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -12,7 +12,6 @@ import ( "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/stats" - "github.com/9seconds/mtg/telegram" "github.com/9seconds/mtg/utils" "github.com/9seconds/mtg/wrappers" ) @@ -24,7 +23,6 @@ type Proxy struct { Context context.Context ClientProtocolMaker protocol.ClientProtocolMaker TelegramProtocolMaker protocol.TelegramProtocolMaker - TelegramDialer telegram.Telegram } func (p *Proxy) Serve(listener net.Listener) { @@ -77,8 +75,8 @@ func (p *Proxy) accept(conn net.Conn) { } defer wrappedConn.Close() - stats.S.ClientConnected(clientProtocol.GetConnectionType(), wrappedConn.RemoteAddr()) - defer stats.S.ClientDisconnected(clientProtocol.GetConnectionType(), wrappedConn.RemoteAddr()) + stats.S.ClientConnected(clientProtocol.ConnectionType(), wrappedConn.RemoteAddr()) + defer stats.S.ClientDisconnected(clientProtocol.ConnectionType(), wrappedConn.RemoteAddr()) logger.Infow("Client connected", "addr", conn.RemoteAddr()) req := &protocol.TelegramRequest{ @@ -100,7 +98,7 @@ func (p *Proxy) accept(conn net.Conn) { } func (p *Proxy) acceptDirectConnection(request *protocol.TelegramRequest) error { - telegramProtocol := p.TelegramProtocolMaker(p.TelegramDialer) + telegramProtocol := p.TelegramProtocolMaker() telegramConnRaw, err := telegramProtocol.Handshake(request) if err != nil { return err diff --git a/telegram/direct.go b/telegram/direct.go index 775a7d8..153143f 100644 --- a/telegram/direct.go +++ b/telegram/direct.go @@ -8,6 +8,8 @@ import ( "github.com/9seconds/mtg/wrappers" ) +var Direct = newDirectTelegram() + const ( directV4DefaultIdx conntypes.DC = 1 directV6DefaultIdx conntypes.DC = 1 @@ -48,7 +50,7 @@ func (d *directTelegram) Dial(ctx context.Context, return d.baseTelegram.dial(ctx, cancel, dc-1, protocol) } -func NewDirectTelegram() Telegram { +func newDirectTelegram() Telegram { return &directTelegram{ baseTelegram: baseTelegram{ dialer: net.Dialer{Timeout: telegramDialTimeout}, diff --git a/telegram/middle.go b/telegram/middle.go index fdc06e0..817b199 100644 --- a/telegram/middle.go +++ b/telegram/middle.go @@ -16,6 +16,8 @@ import ( const middleTelegramBackgroundUpdateEvery = time.Hour +var Middle = NewMiddleTelegram() + type middleTelegram struct { baseTelegram From 1617866c24868b1876b3796881c9f10cc919fa70 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Tue, 10 Sep 2019 13:54:14 +0300 Subject: [PATCH 13/62] Add required things for middlehandshake --- cli/proxy.go | 5 +- obfuscated2/telegram_protocol.go | 8 +-- protocol/interfaces.go | 6 +- proxy/proxy.go | 11 ++- wrappers/blockcipher.go | 2 +- wrappers/mtproto_cipher.go | 111 +++++++++++++++++++++++++++++++ 6 files changed, 121 insertions(+), 22 deletions(-) create mode 100644 wrappers/mtproto_cipher.go diff --git a/cli/proxy.go b/cli/proxy.go index 5a85a2e..ec8df0b 100644 --- a/cli/proxy.go +++ b/cli/proxy.go @@ -76,10 +76,9 @@ func Proxy() error { }() app := &proxy.Proxy{ - Logger: zap.S().Named("proxy"), - Context: ctx, + Logger: zap.S().Named("proxy"), + Context: ctx, ClientProtocolMaker: obfuscated2.MakeClientProtocol, - TelegramProtocolMaker: obfuscated2.MakeTelegramProtocol, } // if len(config.C.AdTag) == 0 { // app.TelegramProtocolMaker = obfuscated2.MakeTelegramProtocol diff --git a/obfuscated2/telegram_protocol.go b/obfuscated2/telegram_protocol.go index b3b1a45..eda4460 100644 --- a/obfuscated2/telegram_protocol.go +++ b/obfuscated2/telegram_protocol.go @@ -10,9 +10,7 @@ import ( "github.com/9seconds/mtg/wrappers" ) -type TelegramProtocol struct{} - -func (t *TelegramProtocol) Handshake(req *protocol.TelegramRequest) (wrappers.Wrap, error) { +func TelegramProtocol(req *protocol.TelegramRequest) (wrappers.Wrap, error) { socket, err := telegram.Direct.Dial(req.Ctx, req.Cancel, req.ClientProtocol.DC(), @@ -39,10 +37,6 @@ func (t *TelegramProtocol) Handshake(req *protocol.TelegramRequest) (wrappers.Wr return wrappers.NewObfuscated2(socket, encryptor, decryptor), nil } -func MakeTelegramProtocol() protocol.TelegramProtocol { - return &TelegramProtocol{} -} - func generateFrame(cp protocol.ClientProtocol) (fm Frame) { data := fm.Bytes() diff --git a/protocol/interfaces.go b/protocol/interfaces.go index 09f2c59..a392de1 100644 --- a/protocol/interfaces.go +++ b/protocol/interfaces.go @@ -12,9 +12,5 @@ type ClientProtocol interface { DC() conntypes.DC } -type TelegramProtocol interface { - Handshake(*TelegramRequest) (wrappers.Wrap, error) -} - +type TelegramProtocol func(*TelegramRequest) (wrappers.Wrap, error) type ClientProtocolMaker func() ClientProtocol -type TelegramProtocolMaker func() TelegramProtocol diff --git a/proxy/proxy.go b/proxy/proxy.go index e11e001..b42cd60 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -10,6 +10,7 @@ import ( "github.com/9seconds/mtg/config" "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/obfuscated2" "github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/stats" "github.com/9seconds/mtg/utils" @@ -19,10 +20,9 @@ import ( const directPipeBufferSize = 1024 * 1024 type Proxy struct { - Logger *zap.SugaredLogger - Context context.Context - ClientProtocolMaker protocol.ClientProtocolMaker - TelegramProtocolMaker protocol.TelegramProtocolMaker + Logger *zap.SugaredLogger + Context context.Context + ClientProtocolMaker protocol.ClientProtocolMaker } func (p *Proxy) Serve(listener net.Listener) { @@ -98,8 +98,7 @@ func (p *Proxy) accept(conn net.Conn) { } func (p *Proxy) acceptDirectConnection(request *protocol.TelegramRequest) error { - telegramProtocol := p.TelegramProtocolMaker() - telegramConnRaw, err := telegramProtocol.Handshake(request) + telegramConnRaw, err := obfuscated2.TelegramProtocol(request) if err != nil { return err } diff --git a/wrappers/blockcipher.go b/wrappers/blockcipher.go index c6a00b5..4554515 100644 --- a/wrappers/blockcipher.go +++ b/wrappers/blockcipher.go @@ -148,7 +148,7 @@ func (w *wrapperBlockCipher) RemoteAddr() *net.TCPAddr { return w.parent.RemoteAddr() } -func NewBlockCipher(parent StreamReadWriteCloser, encryptor, decryptor cipher.BlockMode) StreamReadWriteCloser { +func newBlockCipher(parent StreamReadWriteCloser, encryptor, decryptor cipher.BlockMode) StreamReadWriteCloser { return &wrapperBlockCipher{ parent: parent, encryptor: encryptor, diff --git a/wrappers/mtproto_cipher.go b/wrappers/mtproto_cipher.go new file mode 100644 index 0000000..e297d31 --- /dev/null +++ b/wrappers/mtproto_cipher.go @@ -0,0 +1,111 @@ +package wrappers + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/md5" + "crypto/sha1" + "encoding/binary" + "net" + + "github.com/9seconds/mtg/mtproto/rpc" + "github.com/9seconds/mtg/utils" +) + +type mtprotoCipherPurpose uint8 + +const ( + mtprotoCipherPurposeClient mtprotoCipherPurpose = iota + mtprotoCipherPurposeServer +) + +var mtprotoEmptyIP = [4]byte{0x00, 0x00, 0x00, 0x00} + +func NewMiddleProxyCipher(parent StreamReadWriteCloser, + req *rpc.NonceRequest, + resp *rpc.NonceResponse, + secret []byte) StreamReadWriteCloser { + localAddr := parent.LocalAddr() + remoteAddr := parent.RemoteAddr() + + encKey, encIV := mtprotoDeriveKeys(mtprotoCipherPurposeClient, + req, + resp, + localAddr, + remoteAddr, + secret) + decKey, decIV := mtprotoDeriveKeys(mtprotoCipherPurposeServer, + req, + resp, + localAddr, + remoteAddr, + secret) + + enc, _ := mtprotoMakeEncrypterDecrypter(encKey, encIV) + _, dec := mtprotoMakeEncrypterDecrypter(decKey, decIV) + + return newBlockCipher(parent, enc, dec) +} + +func mtprotoDeriveKeys(purpose mtprotoCipherPurpose, + req *rpc.NonceRequest, + resp *rpc.NonceResponse, + client, remote *net.TCPAddr, + secret []byte) ([]byte, []byte) { + message := bytes.Buffer{} + message.Write(resp.Nonce) // nolint: gosec + message.Write(req.Nonce) // nolint: gosec + message.Write(req.CryptoTS) // nolint: gosec + + clientIPv4 := mtprotoEmptyIP[:] + serverIPv4 := mtprotoEmptyIP[:] + if client.IP.To4() != nil { + clientIPv4 = utils.ReverseBytes(client.IP.To4()) + serverIPv4 = utils.ReverseBytes(remote.IP.To4()) + } + message.Write(serverIPv4) // nolint: gosec + + var port [2]byte + binary.LittleEndian.PutUint16(port[:], uint16(client.Port)) + message.Write(port[:]) // nolint: gosec + + switch purpose { + case mtprotoCipherPurposeClient: + message.WriteString("CLIENT") // nolint: gosec + case mtprotoCipherPurposeServer: + message.WriteString("SERVER") // nolint: gosec + default: + panic("Unexpected cipher purpose") + } + + message.Write(clientIPv4) // nolint: gosec + binary.LittleEndian.PutUint16(port[:], uint16(remote.Port)) + message.Write(port[:]) // nolint: gosec + message.Write(secret) // nolint: gosec + message.Write(resp.Nonce) // nolint: gosec + + if client.IP.To4() == nil { + message.Write(client.IP.To16()) // nolint: gosec + message.Write(remote.IP.To16()) // nolint: gosec + } + message.Write(req.Nonce) // nolint: gosec + + data := message.Bytes() + md5sum := md5.Sum(data[1:]) // nolint: gas + sha1sum := sha1.Sum(data) // nolint: gosec + + key := append(md5sum[:12], sha1sum[:]...) + iv := md5.Sum(data[2:]) // nolint: gas + + return key, iv[:] +} + +func mtprotoMakeEncrypterDecrypter(key, iv []byte) (cipher.BlockMode, cipher.BlockMode) { + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + + return cipher.NewCBCEncrypter(block, iv), cipher.NewCBCDecrypter(block, iv) +} From b280f47731914c15976fc887d69ab5906ec66a40 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Tue, 10 Sep 2019 16:44:00 +0300 Subject: [PATCH 14/62] Add mtproto handshake --- mtproto/protocol.go | 96 ++++++++++++++++++++++++++++++++++++++++++ telegram/base.go | 5 +++ telegram/interfaces.go | 1 + telegram/middle.go | 1 - 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 mtproto/protocol.go diff --git a/mtproto/protocol.go b/mtproto/protocol.go new file mode 100644 index 0000000..bca7366 --- /dev/null +++ b/mtproto/protocol.go @@ -0,0 +1,96 @@ +package mtproto + +import ( + "fmt" + + "github.com/9seconds/mtg/mtproto/rpc" + "github.com/9seconds/mtg/protocol" + "github.com/9seconds/mtg/telegram" + "github.com/9seconds/mtg/wrappers" +) + +func TelegramProtocol(req *protocol.TelegramRequest) (wrappers.Wrap, error) { + conn, err := telegram.Middle.Dial(req.Ctx, + req.Cancel, + req.ClientProtocol.DC(), + req.ClientProtocol.ConnectionProtocol()) + if err != nil { + return nil, fmt.Errorf("cannot connect to telegram: %w", err) + } + + rpcNonceConn := wrappers.NewMtprotoFrame(conn, rpc.SeqNoNonce) + rpcNonceReq, err := doRPCNonceRequest(rpcNonceConn) + if err != nil { + return nil, fmt.Errorf("cannot do nonce request: %w", err) + } + + rpcNonceResp, err := getRPCNonceResponse(rpcNonceConn, rpcNonceReq) + if err != nil { + return nil, fmt.Errorf("cannot get nonce response: %w", err) + } + + secureConn := wrappers.NewMiddleProxyCipher(conn, rpcNonceReq, rpcNonceResp, telegram.Middle.Secret()) + frameConn := wrappers.NewMtprotoFrame(secureConn, rpc.SeqNoHandshake) + + if err := doRPCHandshakeRequest(frameConn); err != nil { + return nil, fmt.Errorf("cannot do handshake request: %w", err) + } + if err := getRPCHandshakeResponse(frameConn); err != nil { + return nil, fmt.Errorf("cannot get handshake response: %w", err) + } + + return frameConn, nil +} + +func doRPCNonceRequest(conn wrappers.PacketWriter) (*rpc.NonceRequest, error) { + rpcNonceReq, err := rpc.NewNonceRequest(telegram.Middle.Secret()) + if err != nil { + panic(err) + } + if err := conn.Write(rpcNonceReq.Bytes()); err != nil { + return nil, err + } + + return rpcNonceReq, nil +} + +func getRPCNonceResponse(conn wrappers.PacketReader, req *rpc.NonceRequest) (*rpc.NonceResponse, error) { + packet, err := conn.Read() + if err != nil { + return nil, fmt.Errorf("cannot read from connection: %w", err) + } + + resp, err := rpc.NewNonceResponse(packet) + if err != nil { + return nil, fmt.Errorf("cannot build rpc nonce responce: %w", err) + } + if err = resp.Valid(req); err != nil { + return nil, fmt.Errorf("invalid nonce response: %w", err) + } + + return resp, nil +} + +func doRPCHandshakeRequest(conn wrappers.PacketWriter) error { + if err := conn.Write(rpc.HandshakeRequest); err != nil { + return fmt.Errorf("cannot make a request: %w", err) + } + return nil +} + +func getRPCHandshakeResponse(conn wrappers.PacketReader) error { + packet, err := conn.Read() + if err != nil { + return fmt.Errorf("cannot read a response: %w", err) + } + + resp, err := rpc.NewHandshakeResponse(packet) + if err != nil { + return fmt.Errorf("cannot build a handshake response: %w", err) + } + if err := resp.Valid(); err != nil { + return fmt.Errorf("invalid handshake response: %w", err) + } + + return nil +} diff --git a/telegram/base.go b/telegram/base.go index b4d7717..bc3319e 100644 --- a/telegram/base.go +++ b/telegram/base.go @@ -17,12 +17,17 @@ const telegramDialTimeout = 10 * time.Second type baseTelegram struct { dialer net.Dialer + secret []byte v4DefaultDC conntypes.DC V6DefaultDC conntypes.DC v4Addresses map[conntypes.DC][]string v6Addresses map[conntypes.DC][]string } +func (b *baseTelegram) Secret() []byte { + return b.secret +} + func (b *baseTelegram) dialToAddress(ctx context.Context, cancel context.CancelFunc, addr string) (wrappers.StreamReadWriteCloser, error) { diff --git a/telegram/interfaces.go b/telegram/interfaces.go index b75f057..9dc1bb8 100644 --- a/telegram/interfaces.go +++ b/telegram/interfaces.go @@ -12,4 +12,5 @@ type Telegram interface { context.CancelFunc, conntypes.DC, conntypes.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) + Secret() []byte } diff --git a/telegram/middle.go b/telegram/middle.go index 817b199..c465556 100644 --- a/telegram/middle.go +++ b/telegram/middle.go @@ -21,7 +21,6 @@ var Middle = NewMiddleTelegram() type middleTelegram struct { baseTelegram - secret []byte mutex sync.RWMutex } From 029245cd169ff484944661845921970d5d355004 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Fri, 13 Sep 2019 10:14:48 +0300 Subject: [PATCH 15/62] Correct initialization of antireplay cache --- antireplay/cache.go | 11 ++++------- cli/proxy.go | 4 +--- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/antireplay/cache.go b/antireplay/cache.go index 359cb39..219d2d2 100644 --- a/antireplay/cache.go +++ b/antireplay/cache.go @@ -1,9 +1,6 @@ package antireplay import ( - "errors" - "fmt" - "github.com/allegro/bigcache" "github.com/9seconds/mtg/config" @@ -20,15 +17,15 @@ func Has(data []byte) bool { return err == nil } -func Init() error { +func Init() { c, err := bigcache.NewBigCache(bigcache.Config{ Shards: 1024, LifeWindow: config.C.AntiReplay.EvictionTime, Hasher: hasher{}, HardMaxCacheSize: config.C.AntiReplay.MaxSize, }) + if err != nil { + panic(err) + } cache = c - err = fmt.Errorf("qqq: %w", errors.New("tt")) - - return err } diff --git a/cli/proxy.go b/cli/proxy.go index ec8df0b..bd78832 100644 --- a/cli/proxy.go +++ b/cli/proxy.go @@ -60,9 +60,7 @@ func Proxy() error { PrintJSONStdout(config.GetURLs()) - if err := antireplay.Init(); err != nil { - Fatal(err) - } + antireplay.Init() if err := stats.Init(ctx); err != nil { Fatal(err) } From 386522ba22b83537ab547a1dcb8f76a98a28293f Mon Sep 17 00:00:00 2001 From: 9seconds Date: Fri, 13 Sep 2019 10:15:00 +0300 Subject: [PATCH 16/62] Correct printable configuration --- cli/proxy.go | 2 +- config/config.go | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cli/proxy.go b/cli/proxy.go index bd78832..e8add55 100644 --- a/cli/proxy.go +++ b/cli/proxy.go @@ -42,7 +42,7 @@ func Proxy() error { if err := config.InitPublicAddress(ctx); err != nil { Fatal(err) } - zap.S().Debugw("Configuration", "config", config.C) + zap.S().Debugw("Configuration", "config", config.C.Printable()) if len(config.C.AdTag) > 0 { zap.S().Infow("Use middle proxy connection to Telegram") diff --git a/config/config.go b/config/config.go index d729e8f..f272660 100644 --- a/config/config.go +++ b/config/config.go @@ -137,6 +137,20 @@ type Config struct { AdTag []byte `json:"adtag"` } +func (c Config) Printable() interface{} { + data, err := json.Marshal(c) + if err != nil { + panic(err) + } + + rv := map[string]interface{}{} + if err := json.Unmarshal(data, &rv); err != nil { + panic(err) + } + + return rv +} + func (c Config) String() string { data, _ := json.Marshal(c) return string(data) From 072bce2922f3f1b3c0780ad4db6414f2b1a6c45d Mon Sep 17 00:00:00 2001 From: 9seconds Date: Fri, 13 Sep 2019 10:49:22 +0300 Subject: [PATCH 17/62] Small corrections for middle proxy connections --- cli/proxy.go | 3 +++ telegram/middle.go | 15 +++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/cli/proxy.go b/cli/proxy.go index e8add55..f181bc6 100644 --- a/cli/proxy.go +++ b/cli/proxy.go @@ -14,6 +14,7 @@ import ( "github.com/9seconds/mtg/obfuscated2" "github.com/9seconds/mtg/proxy" "github.com/9seconds/mtg/stats" + "github.com/9seconds/mtg/telegram" "github.com/9seconds/mtg/utils" ) @@ -64,6 +65,8 @@ func Proxy() error { if err := stats.Init(ctx); err != nil { Fatal(err) } + telegram.MiddleInit() + proxyListener, err := net.Listen("tcp", config.C.ListenAddr.String()) if err != nil { Fatal(err) diff --git a/telegram/middle.go b/telegram/middle.go index c465556..f6c6b2f 100644 --- a/telegram/middle.go +++ b/telegram/middle.go @@ -16,12 +16,19 @@ import ( const middleTelegramBackgroundUpdateEvery = time.Hour -var Middle = NewMiddleTelegram() +var Middle Telegram type middleTelegram struct { baseTelegram - mutex sync.RWMutex + mutex sync.RWMutex +} + +func (m *middleTelegram) Secret() []byte { + m.mutex.RLock() + defer m.mutex.RUnlock() + + return m.baseTelegram.Secret() } func (m *middleTelegram) update() error { @@ -74,7 +81,7 @@ func (m *middleTelegram) Dial(ctx context.Context, return m.baseTelegram.dial(ctx, cancel, dc, protocol) } -func NewMiddleTelegram() Telegram { +func MiddleInit() { tg := &middleTelegram{ baseTelegram: baseTelegram{ dialer: net.Dialer{Timeout: telegramDialTimeout}, @@ -85,5 +92,5 @@ func NewMiddleTelegram() Telegram { } go tg.backgroundUpdate() - return tg + Middle = tg } From c9743b5675df007ed13fa7b89acd4b4cbafc960f Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 7 Oct 2019 12:13:07 +0300 Subject: [PATCH 18/62] wip --- conntypes/packet.go | 3 + .../interfaces.go => conntypes/wrappers.go | 4 +- go.mod | 2 + go.sum | 4 + hub/closeable_channel.go | 59 +++++++++++ hub/connection.go | 100 ++++++++++++++++++ hub/connection_hub.go | 84 +++++++++++++++ hub/connection_hub_request.go | 8 ++ hub/hub.go | 48 +++++++++ hub/registry.go | 53 ++++++++++ mtproto/protocol.go | 15 ++- obfuscated2/client_protocol.go | 6 +- obfuscated2/telegram_protocol.go | 13 +-- protocol/interfaces.go | 8 +- protocol/request.go | 3 +- proxy/proxy.go | 19 ++-- telegram/base.go | 38 +++---- telegram/direct.go | 30 ++---- telegram/interfaces.go | 12 +-- telegram/middle.go | 10 +- wrappers/blockcipher.go | 16 +-- wrappers/conn.go | 90 +++++----------- wrappers/ctx.go | 89 ++++++++++++++++ wrappers/mtproto_cipher.go | 5 +- wrappers/mtproto_frame.go | 10 +- wrappers/obfuscated2.go | 7 +- wrappers/stats.go | 5 +- wrappers/timeout.go | 61 +++++++++++ 28 files changed, 630 insertions(+), 172 deletions(-) create mode 100644 conntypes/packet.go rename wrappers/interfaces.go => conntypes/wrappers.go (97%) create mode 100644 hub/closeable_channel.go create mode 100644 hub/connection.go create mode 100644 hub/connection_hub.go create mode 100644 hub/connection_hub_request.go create mode 100644 hub/hub.go create mode 100644 hub/registry.go create mode 100644 wrappers/ctx.go create mode 100644 wrappers/timeout.go diff --git a/conntypes/packet.go b/conntypes/packet.go new file mode 100644 index 0000000..fa72022 --- /dev/null +++ b/conntypes/packet.go @@ -0,0 +1,3 @@ +package conntypes + +type Packet []byte diff --git a/wrappers/interfaces.go b/conntypes/wrappers.go similarity index 97% rename from wrappers/interfaces.go rename to conntypes/wrappers.go index fd06f59..b26e245 100644 --- a/wrappers/interfaces.go +++ b/conntypes/wrappers.go @@ -1,4 +1,4 @@ -package wrappers +package conntypes import ( "io" @@ -8,8 +8,6 @@ import ( "go.uber.org/zap" ) -type Packet []byte - // Wrap is a base interface for all wrappers in this package. type Wrap interface { Conn() net.Conn diff --git a/go.mod b/go.mod index abc3da9..2179870 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,8 @@ require ( github.com/allegro/bigcache v1.2.1 github.com/beevik/ntp v0.2.0 github.com/cespare/xxhash v1.1.0 + github.com/dustin/go-humanize v1.0.0 + github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622 github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d github.com/kr/pretty v0.1.0 // indirect github.com/pkg/errors v0.8.1 diff --git a/go.sum b/go.sum index 9b41aba..94907f7 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,10 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622 h1:lxbhOGZ9pU3Kf8P6lFluUcE82yVZn2EqEf4+mWRNPV0= +github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622/go.mod h1:D90+MBHVc9Sk1lJAbEVgws0eYEurY4mv2TDso3Nxh3w= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= diff --git a/hub/closeable_channel.go b/hub/closeable_channel.go new file mode 100644 index 0000000..5042ef7 --- /dev/null +++ b/hub/closeable_channel.go @@ -0,0 +1,59 @@ +package hub + +import ( + "context" + "errors" + "time" + + "github.com/9seconds/mtg/conntypes" +) + +const closeableChannelReadTimeout = 2 * time.Minute + +type ChannelReadCloser interface { + Read() (conntypes.Packet, error) + Close() +} + +type closeableChannel struct { + channel chan conntypes.Packet + ctx context.Context + cancel context.CancelFunc +} + +func (c *closeableChannel) Read() (conntypes.Packet, error) { + timer := time.NewTimer(closeableChannelReadTimeout) + defer timer.Stop() + + select { + case <-timer.C: + return nil, errors.New("timeout") + case <-c.ctx.Done(): + return nil, errors.New("channel was closed") + case packet := <-c.channel: + return packet, nil + } +} + +func (c *closeableChannel) write(packet conntypes.Packet) error { + select { + case <-c.ctx.Done(): + return errors.New("channel was closed") + case c.channel <- packet: + return nil + } +} + +func (c *closeableChannel) Close() { + c.cancel() + c.channel = nil +} + +func newCloseableChannel(ctx context.Context) *closeableChannel { + ctx, cancel := context.WithCancel(ctx) + return &closeableChannel{ + channel: make(chan conntypes.Packet), + ctx: ctx, + cancel: cancel, + } +} diff --git a/hub/connection.go b/hub/connection.go new file mode 100644 index 0000000..8879f40 --- /dev/null +++ b/hub/connection.go @@ -0,0 +1,100 @@ +package hub + +import ( + "fmt" + "math/rand" + "sync" + + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/mtproto" + "github.com/9seconds/mtg/protocol" +) + +type connectionID int + +type connection struct { + conn conntypes.PacketReadWriteCloser + mutex sync.RWMutex + id connectionID + hub *connectionHub + pending uint + closing bool +} + +func (c *connection) Write(packet conntypes.Packet) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + err := c.conn.Write(packet) + if err != nil { + // if we tried to write into a socket and it was broken, it is + // a time to reconsider the prescence of this socket at all. + // + // probably we need to remove it completely because it seems + // that connection is broken. + c.pending = 0 + } + return err +} + +func (c *connection) Read() (conntypes.Packet, error) { + packet, err := c.conn.Read() + + c.mutex.Lock() + if err != nil { + c.pending-- + } else { + c.pending = 0 + } + c.mutex.Unlock() + + return packet, err +} + +func (c *connection) Stats() (bool, uint) { + c.mutex.RLock() + defer c.mutex.RUnlock() + + return c.closing, c.pending +} + +func (c *connection) Close() error { + c.mutex.Lock() + defer c.mutex.Unlock() + + c.closing = true + return c.conn.Close() +} + +func (c *connection) run() { + for { + packet, err := c.conn.Read() + if err != nil { + c.Close() + c.hub.brokenSocketsChan <- c.id + c.hub = nil + return + } + + // TODO + if channel, ok := Registry.getChannel(conntypes.ConnID{}); ok { + go channel.write(packet) // nolint: errcheck + } + } +} + +func newConnection(hub *connectionHub, req *protocol.TelegramRequest) (*connection, error) { + conn, err := mtproto.TelegramProtocol(req) + if err != nil { + return nil, fmt.Errorf("cannot create a new connection: %w", err) + } + + rv := &connection{ + conn: conn, + hub: hub, + id: connectionID(rand.Int()), + } + go rv.run() + + return rv, nil +} diff --git a/hub/connection_hub.go b/hub/connection_hub.go new file mode 100644 index 0000000..44bda44 --- /dev/null +++ b/hub/connection_hub.go @@ -0,0 +1,84 @@ +package hub + +import "time" + +const hubGCEvery = time.Minute + +type connectionHub struct { + sockets map[connectionID]*connection + + brokenSocketsChan chan connectionID + connectionRequestsChan chan *connectionHubRequest + returnConnectionsChan chan *connection +} + +func (h *connectionHub) run() { + gcTicker := time.NewTicker(hubGCEvery) + defer gcTicker.Stop() + + for { + select { + case <-gcTicker.C: + h.runGC() + case id := <-h.brokenSocketsChan: + h.runBrokenConnection(id) + case request := <-h.connectionRequestsChan: + h.runConnectionRequest(request) + case conn := <-h.returnConnectionsChan: + h.runReturnConnection(conn) + } + } +} + +func (h *connectionHub) runBrokenConnection(id connectionID) { + delete(h.sockets, id) +} + +func (h *connectionHub) runGC() { + for key, conn := range h.sockets { + closing, pending := conn.Stats() + switch { + case closing: + delete(h.sockets, key) + case pending == 0: + conn.Close() + delete(h.sockets, key) + return + } + + } +} + +func (h *connectionHub) runConnectionRequest(req *connectionHubRequest) { + for key, conn := range h.sockets { + closing, _ := conn.Stats() + delete(h.sockets, key) + + if !closing { + req.responseChan <- conn + return + } + } + + newConn, err := newConnection(h, req.req) + if err != nil { + close(req.responseChan) + return + } + + req.responseChan <- newConn +} + +func (h *connectionHub) runReturnConnection(conn *connection) { + h.sockets[conn.id] = conn +} + +func newConnectionHub() *connectionHub { + return &connectionHub{ + sockets: map[connectionID]*connection{}, + + brokenSocketsChan: make(chan connectionID, 1), + connectionRequestsChan: make(chan *connectionHubRequest), + returnConnectionsChan: make(chan *connection, 1), + } +} diff --git a/hub/connection_hub_request.go b/hub/connection_hub_request.go new file mode 100644 index 0000000..eef2c7c --- /dev/null +++ b/hub/connection_hub_request.go @@ -0,0 +1,8 @@ +package hub + +import "github.com/9seconds/mtg/protocol" + +type connectionHubRequest struct { + req *protocol.TelegramRequest + responseChan chan<- *connection +} diff --git a/hub/hub.go b/hub/hub.go new file mode 100644 index 0000000..ef160d0 --- /dev/null +++ b/hub/hub.go @@ -0,0 +1,48 @@ +package hub + +import ( + "errors" + "sync" + + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/protocol" +) + +type Concentrator struct { + hubs sync.Map +} + +func (c *Concentrator) Write(packet conntypes.Packet, req *protocol.TelegramRequest) error { + hub := c.getHub(req) + connectionChan := make(chan *connection) + hub.connectionRequestsChan <- &connectionHubRequest{ + req: req, + responseChan: connectionChan, + } + + conn, ok := <-connectionChan + if !ok { + return errors.New("cannot establish connection to telegram") + } +} + +func (c *Concentrator) getHub(req *protocol.TelegramRequest) *connectionHub { + dcMapRaw, ok := c.hubs.Load(req.ClientProtocol.DC()) + if !ok { + dcMapRaw, _ = c.hubs.LoadOrStore(req.ClientProtocol.DC(), &sync.Map{}) + } + dcMap := dcMapRaw.(*sync.Map) + + loaded := true + hubRaw, ok := dcMap.Load(req.ClientProtocol.ConnectionProtocol()) + if !ok { + hubRaw, loaded = dcMap.LoadOrStore(req.ClientProtocol.ConnectionProtocol(), + newConnectionHub()) + } + hub := hubRaw.(*connectionHub) + if !loaded { + go hub.run() + } + + return hub +} diff --git a/hub/registry.go b/hub/registry.go new file mode 100644 index 0000000..0891e45 --- /dev/null +++ b/hub/registry.go @@ -0,0 +1,53 @@ +package hub + +import ( + "context" + "sync" + + "github.com/9seconds/mtg/conntypes" +) + +var Registry *RegistryStruct + +type RegistryStruct struct { + conns map[string]*closeableChannel + ctx context.Context + mutex sync.RWMutex +} + +func (r *RegistryStruct) Register(id conntypes.ConnID) ChannelReadCloser { + channel := newCloseableChannel(r.ctx) + + r.mutex.Lock() + r.conns[string(id[:])] = channel + r.mutex.Unlock() + + return channel +} + +func (r *RegistryStruct) Unregister(id conntypes.ConnID) { + r.mutex.Lock() + defer r.mutex.Unlock() + + if channel, ok := r.conns[string(id[:])]; ok { + channel.Close() + delete(r.conns, string(id[:])) + } +} + +func (r *RegistryStruct) getChannel(id conntypes.ConnID) (*closeableChannel, bool) { + r.mutex.RLock() + defer r.mutex.RUnlock() + + if value, ok := r.conns[string(id[:])]; ok { + return value, true + } + return nil, false +} + +func InitRegistry(ctx context.Context) { + Registry = &RegistryStruct{ + ctx: ctx, + conns: map[string]*closeableChannel{}, + } +} diff --git a/mtproto/protocol.go b/mtproto/protocol.go index bca7366..d6a188d 100644 --- a/mtproto/protocol.go +++ b/mtproto/protocol.go @@ -3,16 +3,15 @@ package mtproto import ( "fmt" + "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/mtproto/rpc" "github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/telegram" "github.com/9seconds/mtg/wrappers" ) -func TelegramProtocol(req *protocol.TelegramRequest) (wrappers.Wrap, error) { - conn, err := telegram.Middle.Dial(req.Ctx, - req.Cancel, - req.ClientProtocol.DC(), +func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.PacketReadWriteCloser, error) { + conn, err := telegram.Middle.Dial(req.ClientProtocol.DC(), req.ClientProtocol.ConnectionProtocol()) if err != nil { return nil, fmt.Errorf("cannot connect to telegram: %w", err) @@ -42,7 +41,7 @@ func TelegramProtocol(req *protocol.TelegramRequest) (wrappers.Wrap, error) { return frameConn, nil } -func doRPCNonceRequest(conn wrappers.PacketWriter) (*rpc.NonceRequest, error) { +func doRPCNonceRequest(conn conntypes.PacketWriter) (*rpc.NonceRequest, error) { rpcNonceReq, err := rpc.NewNonceRequest(telegram.Middle.Secret()) if err != nil { panic(err) @@ -54,7 +53,7 @@ func doRPCNonceRequest(conn wrappers.PacketWriter) (*rpc.NonceRequest, error) { return rpcNonceReq, nil } -func getRPCNonceResponse(conn wrappers.PacketReader, req *rpc.NonceRequest) (*rpc.NonceResponse, error) { +func getRPCNonceResponse(conn conntypes.PacketReader, req *rpc.NonceRequest) (*rpc.NonceResponse, error) { packet, err := conn.Read() if err != nil { return nil, fmt.Errorf("cannot read from connection: %w", err) @@ -71,14 +70,14 @@ func getRPCNonceResponse(conn wrappers.PacketReader, req *rpc.NonceRequest) (*rp return resp, nil } -func doRPCHandshakeRequest(conn wrappers.PacketWriter) error { +func doRPCHandshakeRequest(conn conntypes.PacketWriter) error { if err := conn.Write(rpc.HandshakeRequest); err != nil { return fmt.Errorf("cannot make a request: %w", err) } return nil } -func getRPCHandshakeResponse(conn wrappers.PacketReader) error { +func getRPCHandshakeResponse(conn conntypes.PacketReader) error { packet, err := conn.Read() if err != nil { return fmt.Errorf("cannot read a response: %w", err) diff --git a/obfuscated2/client_protocol.go b/obfuscated2/client_protocol.go index 37d9800..028f868 100644 --- a/obfuscated2/client_protocol.go +++ b/obfuscated2/client_protocol.go @@ -37,7 +37,7 @@ func (c *ClientProtocol) DC() conntypes.DC { return c.dc } -func (c *ClientProtocol) Handshake(socket wrappers.StreamReadWriteCloser) (wrappers.StreamReadWriteCloser, error) { +func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conntypes.StreamReadWriteCloser, error) { fm, err := c.ReadFrame(socket) if err != nil { return nil, fmt.Errorf("cannot make a client handshake: %w", err) @@ -88,7 +88,7 @@ func (c *ClientProtocol) Handshake(socket wrappers.StreamReadWriteCloser) (wrapp return wrappers.NewObfuscated2(socket, encryptor, decryptor), nil } -func (c *ClientProtocol) ReadFrame(socket wrappers.StreamReader) (fm Frame, err error) { +func (c *ClientProtocol) ReadFrame(socket conntypes.StreamReader) (fm Frame, err error) { if _, err = io.ReadFull(handshakeReader{socket}, fm.Bytes()); err != nil { err = fmt.Errorf("cannot extract obfuscated2 frame: %w", err) } @@ -96,7 +96,7 @@ func (c *ClientProtocol) ReadFrame(socket wrappers.StreamReader) (fm Frame, err } type handshakeReader struct { - parent wrappers.StreamReader + parent conntypes.StreamReader } func (h handshakeReader) Read(p []byte) (int, error) { diff --git a/obfuscated2/telegram_protocol.go b/obfuscated2/telegram_protocol.go index eda4460..441ecd9 100644 --- a/obfuscated2/telegram_protocol.go +++ b/obfuscated2/telegram_protocol.go @@ -4,20 +4,21 @@ import ( "crypto/rand" "fmt" + "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/telegram" "github.com/9seconds/mtg/utils" "github.com/9seconds/mtg/wrappers" ) -func TelegramProtocol(req *protocol.TelegramRequest) (wrappers.Wrap, error) { - socket, err := telegram.Direct.Dial(req.Ctx, - req.Cancel, - req.ClientProtocol.DC(), +func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.StreamReadWriteCloser, error) { + conn, err := telegram.Direct.Dial(req.ClientProtocol.DC(), req.ClientProtocol.ConnectionProtocol()) if err != nil { return nil, fmt.Errorf("cannot dial to telegram: %w", err) } + conn = wrappers.NewTimeout(conn) + conn = wrappers.NewCtx(req.Ctx, req.Cancel, conn) fm := generateFrame(req.ClientProtocol) data := fm.Bytes() @@ -30,11 +31,11 @@ func TelegramProtocol(req *protocol.TelegramRequest) (wrappers.Wrap, error) { encryptor.XORKeyStream(data, data) copy(data[:frameOffsetIV], copyFrame[:frameOffsetIV]) - if _, err := socket.Write(data); err != nil { + if _, err := conn.Write(data); err != nil { return nil, fmt.Errorf("cannot write handshake frame to telegram: %w", err) } - return wrappers.NewObfuscated2(socket, encryptor, decryptor), nil + return wrappers.NewObfuscated2(conn, encryptor, decryptor), nil } func generateFrame(cp protocol.ClientProtocol) (fm Frame) { diff --git a/protocol/interfaces.go b/protocol/interfaces.go index a392de1..2e27fc3 100644 --- a/protocol/interfaces.go +++ b/protocol/interfaces.go @@ -1,16 +1,12 @@ package protocol -import ( - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/wrappers" -) +import "github.com/9seconds/mtg/conntypes" type ClientProtocol interface { - Handshake(wrappers.StreamReadWriteCloser) (wrappers.StreamReadWriteCloser, error) + Handshake(conntypes.StreamReadWriteCloser) (conntypes.StreamReadWriteCloser, error) ConnectionType() conntypes.ConnectionType ConnectionProtocol() conntypes.ConnectionProtocol DC() conntypes.DC } -type TelegramProtocol func(*TelegramRequest) (wrappers.Wrap, error) type ClientProtocolMaker func() ClientProtocol diff --git a/protocol/request.go b/protocol/request.go index e73489d..00a33f8 100644 --- a/protocol/request.go +++ b/protocol/request.go @@ -6,12 +6,11 @@ import ( "go.uber.org/zap" "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/wrappers" ) type TelegramRequest struct { Logger *zap.SugaredLogger - ClientConn wrappers.StreamReadWriteCloser + ClientConn conntypes.StreamReadWriteCloser ConnID conntypes.ConnID Ctx context.Context Cancel context.CancelFunc diff --git a/proxy/proxy.go b/proxy/proxy.go index b42cd60..501b721 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -63,25 +63,26 @@ func (p *Proxy) accept(conn net.Conn) { ctx, cancel := context.WithCancel(p.Context) defer cancel() - wrappedConn := wrappers.NewClientConn(ctx, cancel, conn, connID) - wrappedConn = wrappers.NewTraffic(wrappedConn) - defer wrappedConn.Close() + clientConn := wrappers.NewClientConn(conn, connID) + clientConn = wrappers.NewCtx(ctx, cancel, clientConn) + clientConn = wrappers.NewTimeout(clientConn) + clientConn = wrappers.NewTraffic(clientConn) + defer clientConn.Close() clientProtocol := p.ClientProtocolMaker() - wrappedConn, err := clientProtocol.Handshake(wrappedConn) + clientConn, err := clientProtocol.Handshake(clientConn) if err != nil { logger.Warnw("Cannot perform client handshake", "error", err) return } - defer wrappedConn.Close() - stats.S.ClientConnected(clientProtocol.ConnectionType(), wrappedConn.RemoteAddr()) - defer stats.S.ClientDisconnected(clientProtocol.ConnectionType(), wrappedConn.RemoteAddr()) + stats.S.ClientConnected(clientProtocol.ConnectionType(), clientConn.RemoteAddr()) + defer stats.S.ClientDisconnected(clientProtocol.ConnectionType(), clientConn.RemoteAddr()) logger.Infow("Client connected", "addr", conn.RemoteAddr()) req := &protocol.TelegramRequest{ Logger: logger, - ClientConn: wrappedConn, + ClientConn: clientConn, ConnID: connID, Ctx: ctx, Cancel: cancel, @@ -102,7 +103,7 @@ func (p *Proxy) acceptDirectConnection(request *protocol.TelegramRequest) error if err != nil { return err } - telegramConn := telegramConnRaw.(wrappers.StreamReadWriteCloser) + telegramConn := telegramConnRaw.(conntypes.StreamReadWriteCloser) defer telegramConn.Close() wg := &sync.WaitGroup{} diff --git a/telegram/base.go b/telegram/base.go index bc3319e..bee996a 100644 --- a/telegram/base.go +++ b/telegram/base.go @@ -1,7 +1,6 @@ package telegram import ( - "context" "fmt" "math/rand" "net" @@ -28,25 +27,8 @@ func (b *baseTelegram) Secret() []byte { return b.secret } -func (b *baseTelegram) dialToAddress(ctx context.Context, - cancel context.CancelFunc, - addr string) (wrappers.StreamReadWriteCloser, error) { - conn, err := b.dialer.Dial("tcp", addr) - if err != nil { - return nil, fmt.Errorf("dial has failed: %w", err) - } - - if err := utils.InitTCP(conn); err != nil { - return nil, fmt.Errorf("cannot initialize tcp socket: %w", err) - } - - return wrappers.NewTelegramConn(ctx, cancel, conn), nil -} - -func (b *baseTelegram) dial(ctx context.Context, - cancel context.CancelFunc, - dc conntypes.DC, - protocol conntypes.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) { +func (b *baseTelegram) dial(dc conntypes.DC, + protocol conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error) { addr := "" switch protocol { @@ -56,7 +38,16 @@ func (b *baseTelegram) dial(ctx context.Context, addr = b.chooseAddress(b.v6Addresses, dc, b.V6DefaultDC) } - return b.dialToAddress(ctx, cancel, addr) + conn, err := b.dialer.Dial("tcp", addr) + if err != nil { + return nil, fmt.Errorf("dial has failed: %w", err) + } + + if err := utils.InitTCP(conn); err != nil { + return nil, fmt.Errorf("cannot initialize tcp socket: %w", err) + } + + return wrappers.NewTelegramConn(conn), nil } func (b *baseTelegram) chooseAddress(addresses map[conntypes.DC][]string, @@ -66,7 +57,10 @@ func (b *baseTelegram) chooseAddress(addresses map[conntypes.DC][]string, addrs, _ = addresses[defaultDC] } - if len(addrs) > 0 { + switch { + case len(addrs) == 1: + return addrs[0] + case len(addrs) > 1: return addrs[rand.Intn(len(addrs))] } diff --git a/telegram/direct.go b/telegram/direct.go index 153143f..b69075e 100644 --- a/telegram/direct.go +++ b/telegram/direct.go @@ -1,15 +1,11 @@ package telegram import ( - "context" "net" "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/wrappers" ) -var Direct = newDirectTelegram() - const ( directV4DefaultIdx conntypes.DC = 1 directV6DefaultIdx conntypes.DC = 1 @@ -36,10 +32,8 @@ type directTelegram struct { baseTelegram } -func (d *directTelegram) Dial(ctx context.Context, - cancel context.CancelFunc, - dc conntypes.DC, - protocol conntypes.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) { +func (d *directTelegram) Dial(dc conntypes.DC, + protocol conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error) { switch { case dc < 0: dc = -dc @@ -47,17 +41,15 @@ func (d *directTelegram) Dial(ctx context.Context, dc = conntypes.DCDefaultIdx } - return d.baseTelegram.dial(ctx, cancel, dc-1, protocol) + return d.baseTelegram.dial(dc-1, protocol) } -func newDirectTelegram() Telegram { - return &directTelegram{ - baseTelegram: baseTelegram{ - dialer: net.Dialer{Timeout: telegramDialTimeout}, - v4DefaultDC: directV4DefaultIdx, - V6DefaultDC: directV6DefaultIdx, - v4Addresses: directV4Addresses, - v6Addresses: directV6Addresses, - }, - } +var Direct = &directTelegram{ + baseTelegram: baseTelegram{ + dialer: net.Dialer{Timeout: telegramDialTimeout}, + v4DefaultDC: directV4DefaultIdx, + V6DefaultDC: directV6DefaultIdx, + v4Addresses: directV4Addresses, + v6Addresses: directV6Addresses, + }, } diff --git a/telegram/interfaces.go b/telegram/interfaces.go index 9dc1bb8..c780a62 100644 --- a/telegram/interfaces.go +++ b/telegram/interfaces.go @@ -1,16 +1,8 @@ package telegram -import ( - "context" - - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/wrappers" -) +import "github.com/9seconds/mtg/conntypes" type Telegram interface { - Dial(context.Context, - context.CancelFunc, - conntypes.DC, - conntypes.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) + Dial(conntypes.DC, conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error) Secret() []byte } diff --git a/telegram/middle.go b/telegram/middle.go index f6c6b2f..6dc0279 100644 --- a/telegram/middle.go +++ b/telegram/middle.go @@ -1,7 +1,6 @@ package telegram import ( - "context" "fmt" "net" "sync" @@ -11,7 +10,6 @@ import ( "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/telegram/api" - "github.com/9seconds/mtg/wrappers" ) const middleTelegramBackgroundUpdateEvery = time.Hour @@ -67,10 +65,8 @@ func (m *middleTelegram) backgroundUpdate() { } } -func (m *middleTelegram) Dial(ctx context.Context, - cancel context.CancelFunc, - dc conntypes.DC, - protocol conntypes.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) { +func (m *middleTelegram) Dial(dc conntypes.DC, + protocol conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error) { if dc == 0 { dc = conntypes.DCDefaultIdx } @@ -78,7 +74,7 @@ func (m *middleTelegram) Dial(ctx context.Context, m.mutex.RLock() defer m.mutex.RUnlock() - return m.baseTelegram.dial(ctx, cancel, dc, protocol) + return m.baseTelegram.dial(dc, protocol) } func MiddleInit() { diff --git a/wrappers/blockcipher.go b/wrappers/blockcipher.go index 4554515..12c015f 100644 --- a/wrappers/blockcipher.go +++ b/wrappers/blockcipher.go @@ -10,6 +10,8 @@ import ( "time" "go.uber.org/zap" + + "github.com/9seconds/mtg/conntypes" ) const blockCipherReadCurrentDataBufferSize = 1024 + 1 // +1 because telegram operates with blocks mod 4 @@ -17,7 +19,7 @@ const blockCipherReadCurrentDataBufferSize = 1024 + 1 // +1 because telegram ope type wrapperBlockCipher struct { buf bytes.Buffer - parent StreamReadWriteCloser + parent conntypes.StreamReadWriteCloser encryptor cipher.BlockMode decryptor cipher.BlockMode } @@ -47,7 +49,8 @@ func (w *wrapperBlockCipher) ReadTimeout(p []byte, timeout time.Duration) (int, return w.read(p, readAllTimeout(timeout)) } -func (w *wrapperBlockCipher) read(p []byte, reader func(StreamReadWriteCloser) ([]byte, error)) (int, error) { +func (w *wrapperBlockCipher) read(p []byte, + reader func(conntypes.StreamReadWriteCloser) ([]byte, error)) (int, error) { if w.buf.Len() > 0 { return w.flush(p) } @@ -90,7 +93,7 @@ func (w *wrapperBlockCipher) encrypt(p []byte) ([]byte, error) { return encrypted, nil } -func readAll(src StreamReadWriteCloser) (rv []byte, err error) { +func readAll(src conntypes.StreamReadWriteCloser) (rv []byte, err error) { buf := make([]byte, blockCipherReadCurrentDataBufferSize) n := blockCipherReadCurrentDataBufferSize @@ -105,8 +108,8 @@ func readAll(src StreamReadWriteCloser) (rv []byte, err error) { return rv, nil } -func readAllTimeout(timeout time.Duration) func(StreamReadWriteCloser) ([]byte, error) { - return func(src StreamReadWriteCloser) (rv []byte, err error) { +func readAllTimeout(timeout time.Duration) func(conntypes.StreamReadWriteCloser) ([]byte, error) { + return func(src conntypes.StreamReadWriteCloser) (rv []byte, err error) { tmo := timeout buf := make([]byte, blockCipherReadCurrentDataBufferSize) n := blockCipherReadCurrentDataBufferSize @@ -148,7 +151,8 @@ func (w *wrapperBlockCipher) RemoteAddr() *net.TCPAddr { return w.parent.RemoteAddr() } -func newBlockCipher(parent StreamReadWriteCloser, encryptor, decryptor cipher.BlockMode) StreamReadWriteCloser { +func newBlockCipher(parent conntypes.StreamReadWriteCloser, + encryptor, decryptor cipher.BlockMode) conntypes.StreamReadWriteCloser { return &wrapperBlockCipher{ parent: parent, encryptor: encryptor, diff --git a/wrappers/conn.go b/wrappers/conn.go index 1649cd1..2d2f982 100644 --- a/wrappers/conn.go +++ b/wrappers/conn.go @@ -1,7 +1,6 @@ package wrappers import ( - "context" "fmt" "net" "time" @@ -19,15 +18,8 @@ const ( connPurposeTelegram ) -const ( - connTimeoutRead = 2 * time.Minute - connTimeoutWrite = 2 * time.Minute -) - type wrapperConn struct { parent net.Conn - ctx context.Context - cancel context.CancelFunc connID conntypes.ConnID logger *zap.SugaredLogger localAddr *net.TCPAddr @@ -35,61 +27,45 @@ type wrapperConn struct { } func (w *wrapperConn) WriteTimeout(p []byte, timeout time.Duration) (int, error) { - select { - case <-w.ctx.Done(): + if err := w.parent.SetWriteDeadline(time.Now().Add(timeout)); err != nil { w.Close() - return 0, fmt.Errorf("cannot write because context was closed: %w", w.ctx.Err()) - - default: - if err := w.parent.SetWriteDeadline(time.Now().Add(timeout)); err != nil { - w.Close() // nolint: gosec - return 0, fmt.Errorf("cannot set write deadline to the socket: %w", err) - } - - n, err := w.parent.Write(p) - w.logger.Debugw("Write to stream", "bytes", n, "error", err) - if err != nil { - w.Close() // nolint: gosec - } - - return n, err + return 0, fmt.Errorf("cannot set write deadline to the socket: %w", err) } + + return w.Write(p) } func (w *wrapperConn) Write(p []byte) (int, error) { - return w.WriteTimeout(p, connTimeoutWrite) + n, err := w.parent.Write(p) + w.logger.Debugw("write to stream", "bytes", n, "error", err) + if err != nil { + w.Close() // nolint: gosec + } + + return n, err } func (w *wrapperConn) ReadTimeout(p []byte, timeout time.Duration) (int, error) { - select { - case <-w.ctx.Done(): + if err := w.parent.SetReadDeadline(time.Now().Add(timeout)); err != nil { w.Close() - return 0, fmt.Errorf("cannot read because context was closed: %w", w.ctx.Err()) - - default: - if err := w.parent.SetReadDeadline(time.Now().Add(timeout)); err != nil { - w.Close() - return 0, fmt.Errorf("cannot set read deadline to the socket: %w", err) - } - - n, err := w.parent.Read(p) - w.logger.Debugw("Read from stream", "bytes", n, "error", err) - if err != nil { - w.Close() - } - - return n, err + return 0, fmt.Errorf("cannot set read deadline to the socket: %w", err) } + + return w.Read(p) } func (w *wrapperConn) Read(p []byte) (int, error) { - return w.ReadTimeout(p, connTimeoutRead) + n, err := w.parent.Read(p) + w.logger.Debugw("Read from stream", "bytes", n, "error", err) + if err != nil { + w.Close() + } + + return n, err } func (w *wrapperConn) Close() error { w.logger.Debugw("Close connection") - w.cancel() - return w.parent.Close() } @@ -109,11 +85,9 @@ func (w *wrapperConn) RemoteAddr() *net.TCPAddr { return w.remoteAddr } -func newConn(ctx context.Context, - cancel context.CancelFunc, - parent net.Conn, +func newConn(parent net.Conn, connID conntypes.ConnID, - purpose connPurpose) StreamReadWriteCloser { + purpose connPurpose) conntypes.StreamReadWriteCloser { localAddr := *parent.LocalAddr().(*net.TCPAddr) if parent.RemoteAddr().(*net.TCPAddr).IP.To4() != nil { @@ -135,8 +109,6 @@ func newConn(ctx context.Context, return &wrapperConn{ parent: parent, - ctx: ctx, - cancel: cancel, connID: connID, logger: logger, remoteAddr: parent.RemoteAddr().(*net.TCPAddr), @@ -144,15 +116,11 @@ func newConn(ctx context.Context, } } -func NewClientConn(ctx context.Context, - cancel context.CancelFunc, - parent net.Conn, - connID conntypes.ConnID) StreamReadWriteCloser { - return newConn(ctx, cancel, parent, connID, connPurposeClient) +func NewClientConn(parent net.Conn, + connID conntypes.ConnID) conntypes.StreamReadWriteCloser { + return newConn(parent, connID, connPurposeClient) } -func NewTelegramConn(ctx context.Context, - cancel context.CancelFunc, - parent net.Conn) StreamReadWriteCloser { - return newConn(ctx, cancel, parent, conntypes.ConnID{}, connPurposeTelegram) +func NewTelegramConn(parent net.Conn) conntypes.StreamReadWriteCloser { + return newConn(parent, conntypes.ConnID{}, connPurposeTelegram) } diff --git a/wrappers/ctx.go b/wrappers/ctx.go new file mode 100644 index 0000000..46f4242 --- /dev/null +++ b/wrappers/ctx.go @@ -0,0 +1,89 @@ +package wrappers + +import ( + "context" + "fmt" + "net" + "time" + + "go.uber.org/zap" + + "github.com/9seconds/mtg/conntypes" +) + +type wrapperCtx struct { + parent conntypes.StreamReadWriteCloser + ctx context.Context + cancel context.CancelFunc +} + +func (w *wrapperCtx) WriteTimeout(p []byte, timeout time.Duration) (int, error) { + select { + case <-w.ctx.Done(): + w.Close() + return 0, fmt.Errorf("cannot write because context was closed: %w", w.ctx.Err()) + default: + return w.parent.WriteTimeout(p, timeout) + } +} + +func (w *wrapperCtx) Write(p []byte) (int, error) { + select { + case <-w.ctx.Done(): + w.Close() + return 0, fmt.Errorf("cannot write because context was closed: %w", w.ctx.Err()) + default: + return w.parent.Write(p) + } +} + +func (w *wrapperCtx) ReadTimeout(p []byte, timeout time.Duration) (int, error) { + select { + case <-w.ctx.Done(): + w.Close() + return 0, fmt.Errorf("cannot write because context was closed: %w", w.ctx.Err()) + default: + return w.parent.ReadTimeout(p, timeout) + } +} + +func (w *wrapperCtx) Read(p []byte) (int, error) { + select { + case <-w.ctx.Done(): + w.Close() + return 0, fmt.Errorf("cannot write because context was closed: %w", w.ctx.Err()) + default: + return w.parent.Read(p) + } +} + +func (w *wrapperCtx) Close() error { + w.cancel() + return w.parent.Close() +} + +func (w *wrapperCtx) Conn() net.Conn { + return w.parent.Conn() +} + +func (w *wrapperCtx) Logger() *zap.SugaredLogger { + return w.parent.Logger().Named("ctx") +} + +func (w *wrapperCtx) LocalAddr() *net.TCPAddr { + return w.parent.LocalAddr() +} + +func (w *wrapperCtx) RemoteAddr() *net.TCPAddr { + return w.parent.RemoteAddr() +} + +func NewCtx(ctx context.Context, + cancel context.CancelFunc, + parent conntypes.StreamReadWriteCloser) conntypes.StreamReadWriteCloser { + return &wrapperCtx{ + parent: parent, + ctx: ctx, + cancel: cancel, + } +} diff --git a/wrappers/mtproto_cipher.go b/wrappers/mtproto_cipher.go index e297d31..01ec5c5 100644 --- a/wrappers/mtproto_cipher.go +++ b/wrappers/mtproto_cipher.go @@ -9,6 +9,7 @@ import ( "encoding/binary" "net" + "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/mtproto/rpc" "github.com/9seconds/mtg/utils" ) @@ -22,10 +23,10 @@ const ( var mtprotoEmptyIP = [4]byte{0x00, 0x00, 0x00, 0x00} -func NewMiddleProxyCipher(parent StreamReadWriteCloser, +func NewMiddleProxyCipher(parent conntypes.StreamReadWriteCloser, req *rpc.NonceRequest, resp *rpc.NonceResponse, - secret []byte) StreamReadWriteCloser { + secret []byte) conntypes.StreamReadWriteCloser { localAddr := parent.LocalAddr() remoteAddr := parent.RemoteAddr() diff --git a/wrappers/mtproto_frame.go b/wrappers/mtproto_frame.go index 3fecc56..071aec6 100644 --- a/wrappers/mtproto_frame.go +++ b/wrappers/mtproto_frame.go @@ -11,6 +11,8 @@ import ( "net" "go.uber.org/zap" + + "github.com/9seconds/mtg/conntypes" ) const ( @@ -33,13 +35,13 @@ var mtprotoFramePadding = []byte{0x04, 0x00, 0x00, 0x00} // PADDING is custom padding schema to complete frame length to such that // len(frame) % 16 == 0 type wrapperMtprotoFrame struct { - parent StreamReadWriteCloser + parent conntypes.StreamReadWriteCloser logger *zap.SugaredLogger readSeqNo int32 writeSeqNo int32 } -func (w *wrapperMtprotoFrame) Read() (Packet, error) { +func (w *wrapperMtprotoFrame) Read() (conntypes.Packet, error) { buf := &bytes.Buffer{} sum := crc32.NewIEEE() writer := io.MultiWriter(buf, sum) @@ -101,7 +103,7 @@ func (w *wrapperMtprotoFrame) Read() (Packet, error) { return data, nil } -func (w *wrapperMtprotoFrame) Write(p Packet) error { +func (w *wrapperMtprotoFrame) Write(p conntypes.Packet) error { messageLength := 4 + 4 + len(p) + 4 paddingLength := (aes.BlockSize - messageLength%aes.BlockSize) % aes.BlockSize @@ -149,7 +151,7 @@ func (w *wrapperMtprotoFrame) RemoteAddr() *net.TCPAddr { return w.parent.RemoteAddr() } -func NewMtprotoFrame(parent StreamReadWriteCloser, seqNo int32) PacketReadWriteCloser { +func NewMtprotoFrame(parent conntypes.StreamReadWriteCloser, seqNo int32) conntypes.PacketReadWriteCloser { return &wrapperMtprotoFrame{ parent: parent, logger: parent.Logger().Named("mtproto-frame"), diff --git a/wrappers/obfuscated2.go b/wrappers/obfuscated2.go index 4ef601a..d6f7a69 100644 --- a/wrappers/obfuscated2.go +++ b/wrappers/obfuscated2.go @@ -7,12 +7,14 @@ import ( "time" "go.uber.org/zap" + + "github.com/9seconds/mtg/conntypes" ) type wrapperObfuscated2 struct { encryptor cipher.Stream decryptor cipher.Stream - parent StreamReadWriteCloser + parent conntypes.StreamReadWriteCloser } func (w *wrapperObfuscated2) ReadTimeout(p []byte, timeout time.Duration) (int, error) { @@ -71,7 +73,8 @@ func (w *wrapperObfuscated2) Close() error { return w.parent.Close() } -func NewObfuscated2(socket StreamReadWriteCloser, encryptor, decryptor cipher.Stream) StreamReadWriteCloser { +func NewObfuscated2(socket conntypes.StreamReadWriteCloser, + encryptor, decryptor cipher.Stream) conntypes.StreamReadWriteCloser { return &wrapperObfuscated2{ parent: socket, encryptor: encryptor, diff --git a/wrappers/stats.go b/wrappers/stats.go index 4f33eee..33f57be 100644 --- a/wrappers/stats.go +++ b/wrappers/stats.go @@ -6,11 +6,12 @@ import ( "go.uber.org/zap" + "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/stats" ) type wrapperStats struct { - parent StreamReadWriteCloser + parent conntypes.StreamReadWriteCloser } func (w *wrapperStats) Write(p []byte) (int, error) { @@ -61,6 +62,6 @@ func (w *wrapperStats) Close() error { return w.parent.Close() } -func NewTraffic(parent StreamReadWriteCloser) StreamReadWriteCloser { +func NewTraffic(parent conntypes.StreamReadWriteCloser) conntypes.StreamReadWriteCloser { return &wrapperStats{parent} } diff --git a/wrappers/timeout.go b/wrappers/timeout.go new file mode 100644 index 0000000..f9c0302 --- /dev/null +++ b/wrappers/timeout.go @@ -0,0 +1,61 @@ +package wrappers + +import ( + "net" + "time" + + "go.uber.org/zap" + + "github.com/9seconds/mtg/conntypes" +) + +const ( + timeoutRead = 2 * time.Minute + timeoutWrite = 2 * time.Minute +) + +type wrapperTimeout struct { + parent conntypes.StreamReadWriteCloser +} + +func (w *wrapperTimeout) WriteTimeout(p []byte, timeout time.Duration) (int, error) { + return w.parent.WriteTimeout(p, timeout) +} + +func (w *wrapperTimeout) Write(p []byte) (int, error) { + return w.parent.WriteTimeout(p, timeoutWrite) +} + +func (w *wrapperTimeout) ReadTimeout(p []byte, timeout time.Duration) (int, error) { + return w.parent.ReadTimeout(p, timeout) +} + +func (w *wrapperTimeout) Read(p []byte) (int, error) { + return w.parent.ReadTimeout(p, timeoutRead) +} + +func (w *wrapperTimeout) Close() error { + return w.parent.Close() +} + +func (w *wrapperTimeout) Conn() net.Conn { + return w.parent.Conn() +} + +func (w *wrapperTimeout) Logger() *zap.SugaredLogger { + return w.parent.Logger().Named("timeout") +} + +func (w *wrapperTimeout) LocalAddr() *net.TCPAddr { + return w.parent.LocalAddr() +} + +func (w *wrapperTimeout) RemoteAddr() *net.TCPAddr { + return w.parent.RemoteAddr() +} + +func NewTimeout(parent conntypes.StreamReadWriteCloser) conntypes.StreamReadWriteCloser { + return &wrapperTimeout{ + parent: parent, + } +} From 413cafeeb6d2d651482d41e9231650989225e97a Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 7 Oct 2019 17:26:03 +0300 Subject: [PATCH 19/62] reworked hub --- hub/connection.go | 87 ++++++++------- hub/connection_hub.go | 109 ++++++++++--------- hub/connection_hub_request.go | 8 -- hub/{closeable_channel.go => ctx_channel.go} | 21 ++-- hub/hub.go | 64 ++++++----- hub/init.go | 30 +++++ hub/registry.go | 18 ++- 7 files changed, 189 insertions(+), 148 deletions(-) delete mode 100644 hub/connection_hub_request.go rename hub/{closeable_channel.go => ctx_channel.go} (62%) create mode 100644 hub/init.go diff --git a/hub/connection.go b/hub/connection.go index 8879f40..17ebbfc 100644 --- a/hub/connection.go +++ b/hub/connection.go @@ -10,34 +10,17 @@ import ( "github.com/9seconds/mtg/protocol" ) -type connectionID int - type connection struct { - conn conntypes.PacketReadWriteCloser - mutex sync.RWMutex - id connectionID - hub *connectionHub - pending uint - closing bool + conn conntypes.PacketReadWriteCloser + mutex sync.RWMutex + shutdownOnce sync.Once + hub *connectionHub + id int + pending uint + done chan struct{} } -func (c *connection) Write(packet conntypes.Packet) error { - c.mutex.Lock() - defer c.mutex.Unlock() - - err := c.conn.Write(packet) - if err != nil { - // if we tried to write into a socket and it was broken, it is - // a time to reconsider the prescence of this socket at all. - // - // probably we need to remove it completely because it seems - // that connection is broken. - c.pending = 0 - } - return err -} - -func (c *connection) Read() (conntypes.Packet, error) { +func (c *connection) read() (conntypes.Packet, error) { packet, err := c.conn.Read() c.mutex.Lock() @@ -51,39 +34,59 @@ func (c *connection) Read() (conntypes.Packet, error) { return packet, err } -func (c *connection) Stats() (bool, uint) { +func (c *connection) write(packet conntypes.Packet) error { + err := c.conn.Write(packet) + if err != nil { + // if we tried to write into a socket and it was broken, it is + // a time to reconsider the prescence of this socket at all. + // + // probably we need to remove it completely because it seems + // that connection is broken. + c.mutex.Lock() + c.pending = 0 + c.mutex.Unlock() + } + return err +} + +func (c *connection) shutdown() { + c.shutdownOnce.Do(func() { + close(c.done) + c.hub.channelBrokenSockets <- c.id + }) +} + +func (c *connection) closed() bool { + select { + case <-c.done: + return true + default: + return false + } +} + +func (c *connection) idle() bool { c.mutex.RLock() defer c.mutex.RUnlock() - return c.closing, c.pending -} - -func (c *connection) Close() error { - c.mutex.Lock() - defer c.mutex.Unlock() - - c.closing = true - return c.conn.Close() + return c.pending == 0 } func (c *connection) run() { for { - packet, err := c.conn.Read() + packet, err := c.read() if err != nil { - c.Close() - c.hub.brokenSocketsChan <- c.id - c.hub = nil + c.shutdown() return } - // TODO if channel, ok := Registry.getChannel(conntypes.ConnID{}); ok { go channel.write(packet) // nolint: errcheck } } } -func newConnection(hub *connectionHub, req *protocol.TelegramRequest) (*connection, error) { +func newConnection(req *protocol.TelegramRequest, hub *connectionHub) (*connection, error) { conn, err := mtproto.TelegramProtocol(req) if err != nil { return nil, fmt.Errorf("cannot create a new connection: %w", err) @@ -92,7 +95,7 @@ func newConnection(hub *connectionHub, req *protocol.TelegramRequest) (*connecti rv := &connection{ conn: conn, hub: hub, - id: connectionID(rand.Int()), + id: rand.Int(), } go rv.run() diff --git a/hub/connection_hub.go b/hub/connection_hub.go index 44bda44..e4aa037 100644 --- a/hub/connection_hub.go +++ b/hub/connection_hub.go @@ -1,84 +1,89 @@ package hub -import "time" +import ( + "time" + + "github.com/9seconds/mtg/protocol" +) const hubGCEvery = time.Minute -type connectionHub struct { - sockets map[connectionID]*connection - - brokenSocketsChan chan connectionID - connectionRequestsChan chan *connectionHubRequest - returnConnectionsChan chan *connection +type connectionHubRequest struct { + request *protocol.TelegramRequest + response chan<- *connection } -func (h *connectionHub) run() { - gcTicker := time.NewTicker(hubGCEvery) - defer gcTicker.Stop() +type connectionHub struct { + sockets map[int]*connection + + channelBrokenSockets chan int + channelConnectionRequests chan *connectionHubRequest + channelReturnConnections chan *connection +} + +func (c *connectionHub) run() { + ticker := time.NewTicker(hubGCEvery) + defer ticker.Stop() for { select { - case <-gcTicker.C: - h.runGC() - case id := <-h.brokenSocketsChan: - h.runBrokenConnection(id) - case request := <-h.connectionRequestsChan: - h.runConnectionRequest(request) - case conn := <-h.returnConnectionsChan: - h.runReturnConnection(conn) + case <-ticker.C: + c.runGC() + case request := <-c.channelConnectionRequests: + c.runConnectionRequest(request) + case id := <-c.channelBrokenSockets: + c.runBrokenSocket(id) + case conn := <-c.channelReturnConnections: + c.runReturnConnection(conn) } } } -func (h *connectionHub) runBrokenConnection(id connectionID) { - delete(h.sockets, id) -} - -func (h *connectionHub) runGC() { - for key, conn := range h.sockets { - closing, pending := conn.Stats() +func (c *connectionHub) runGC() { + for key, conn := range c.sockets { switch { - case closing: - delete(h.sockets, key) - case pending == 0: - conn.Close() - delete(h.sockets, key) + case conn.closed(): + delete(c.sockets, key) + case conn.idle(): + conn.shutdown() + delete(c.sockets, key) return } - } } -func (h *connectionHub) runConnectionRequest(req *connectionHubRequest) { - for key, conn := range h.sockets { - closing, _ := conn.Stats() - delete(h.sockets, key) - - if !closing { - req.responseChan <- conn +func (c *connectionHub) runConnectionRequest(req *connectionHubRequest) { + for key, conn := range c.sockets { + delete(c.sockets, key) + if !conn.closed() { + req.response <- conn + close(req.response) return } } - newConn, err := newConnection(h, req.req) - if err != nil { - close(req.responseChan) - return + if conn, err := newConnection(req.request, c); err == nil { + req.response <- conn } - - req.responseChan <- newConn + close(req.response) } -func (h *connectionHub) runReturnConnection(conn *connection) { - h.sockets[conn.id] = conn +func (c *connectionHub) runBrokenSocket(id int) { + delete(c.sockets, id) +} + +func (c *connectionHub) runReturnConnection(conn *connection) { + c.sockets[conn.id] = conn } func newConnectionHub() *connectionHub { - return &connectionHub{ - sockets: map[connectionID]*connection{}, - - brokenSocketsChan: make(chan connectionID, 1), - connectionRequestsChan: make(chan *connectionHubRequest), - returnConnectionsChan: make(chan *connection, 1), + rv := &connectionHub{ + sockets: map[int]*connection{}, + channelBrokenSockets: make(chan int, 1), + channelConnectionRequests: make(chan *connectionHubRequest), + channelReturnConnections: make(chan *connection, 1), } + go rv.run() + + return rv } diff --git a/hub/connection_hub_request.go b/hub/connection_hub_request.go deleted file mode 100644 index eef2c7c..0000000 --- a/hub/connection_hub_request.go +++ /dev/null @@ -1,8 +0,0 @@ -package hub - -import "github.com/9seconds/mtg/protocol" - -type connectionHubRequest struct { - req *protocol.TelegramRequest - responseChan chan<- *connection -} diff --git a/hub/closeable_channel.go b/hub/ctx_channel.go similarity index 62% rename from hub/closeable_channel.go rename to hub/ctx_channel.go index 5042ef7..9394ba3 100644 --- a/hub/closeable_channel.go +++ b/hub/ctx_channel.go @@ -12,46 +12,47 @@ const closeableChannelReadTimeout = 2 * time.Minute type ChannelReadCloser interface { Read() (conntypes.Packet, error) - Close() + Close() error } -type closeableChannel struct { +type ctxChannel struct { channel chan conntypes.Packet ctx context.Context cancel context.CancelFunc } -func (c *closeableChannel) Read() (conntypes.Packet, error) { +func (c *ctxChannel) Read() (conntypes.Packet, error) { timer := time.NewTimer(closeableChannelReadTimeout) defer timer.Stop() select { case <-timer.C: - return nil, errors.New("timeout") + return nil, ErrTimeout case <-c.ctx.Done(): - return nil, errors.New("channel was closed") + return nil, ErrClosed case packet := <-c.channel: return packet, nil } } -func (c *closeableChannel) write(packet conntypes.Packet) error { +func (c *ctxChannel) write(packet conntypes.Packet) error { select { case <-c.ctx.Done(): - return errors.New("channel was closed") + return ErrClosed case c.channel <- packet: return nil } } -func (c *closeableChannel) Close() { +func (c *ctxChannel) Close() error { c.cancel() c.channel = nil + return nil } -func newCloseableChannel(ctx context.Context) *closeableChannel { +func newCtxChannel(ctx context.Context) *ctxChannel { ctx, cancel := context.WithCancel(ctx) - return &closeableChannel{ + return &ctxChannel{ channel: make(chan conntypes.Packet), ctx: ctx, cancel: cancel, diff --git a/hub/hub.go b/hub/hub.go index ef160d0..1e6cd5d 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -1,48 +1,60 @@ package hub import ( - "errors" + "encoding/binary" + "fmt" + "strings" "sync" "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/protocol" ) -type Concentrator struct { - hubs sync.Map +type hub struct { + subs map[string]*connectionHub + mutex sync.RWMutex } -func (c *Concentrator) Write(packet conntypes.Packet, req *protocol.TelegramRequest) error { - hub := c.getHub(req) - connectionChan := make(chan *connection) - hub.connectionRequestsChan <- &connectionHubRequest{ - req: req, - responseChan: connectionChan, +func (h *hub) Write(packet conntypes.Packet, req *protocol.TelegramRequest) error { + sub := h.getHub(req) + connections := make(chan *connection) + sub.channelConnectionRequests <- &connectionHubRequest{ + request: req, + response: connections, } - conn, ok := <-connectionChan + conn, ok := <-connections if !ok { - return errors.New("cannot establish connection to telegram") + return ErrCannotCreateConnection } + + if err := conn.write(packet); err != nil { + return fmt.Errorf("cannot send packet: %w", err) + } + return nil } -func (c *Concentrator) getHub(req *protocol.TelegramRequest) *connectionHub { - dcMapRaw, ok := c.hubs.Load(req.ClientProtocol.DC()) - if !ok { - dcMapRaw, _ = c.hubs.LoadOrStore(req.ClientProtocol.DC(), &sync.Map{}) - } - dcMap := dcMapRaw.(*sync.Map) +func (h *hub) getHub(req *protocol.TelegramRequest) *connectionHub { + keyBuilder := strings.Builder{} + binary.Write(&keyBuilder, binary.LittleEndian, int16(req.ClientProtocol.DC())) + keyBuilder.WriteRune('_') + binary.Write(&keyBuilder, binary.LittleEndian, uint8(req.ClientProtocol.ConnectionProtocol())) + key := keyBuilder.String() + + h.mutex.RLock() + rv, ok := h.subs[key] + h.mutex.RUnlock() - loaded := true - hubRaw, ok := dcMap.Load(req.ClientProtocol.ConnectionProtocol()) if !ok { - hubRaw, loaded = dcMap.LoadOrStore(req.ClientProtocol.ConnectionProtocol(), - newConnectionHub()) - } - hub := hubRaw.(*connectionHub) - if !loaded { - go hub.run() + h.mutex.Lock() + defer h.mutex.Unlock() + + rv, ok = h.subs[key] + if !ok { + rv = newConnectionHub() + h.subs[key] = rv + } } - return hub + return rv } diff --git a/hub/init.go b/hub/init.go new file mode 100644 index 0000000..38d1a82 --- /dev/null +++ b/hub/init.go @@ -0,0 +1,30 @@ +package hub + +import ( + "context" + "errors" + "sync" +) + +var ( + Registry *registry + Hub *hub + + ErrTimeout = errors.New("timeout") + ErrClosed = errors.New("channel was closed") + ErrCannotCreateConnection = errors.New("cannot create connection") + + initOnce sync.Once +) + +func Init(ctx context.Context) { + initOnce.Do(func() { + Registry = ®istry{ + conns: map[string]*ctxChannel{}, + ctx: ctx, + } + Hub = &hub{ + subs: map[string]*connectionHub{}, + } + }) +} diff --git a/hub/registry.go b/hub/registry.go index 0891e45..4beebe8 100644 --- a/hub/registry.go +++ b/hub/registry.go @@ -7,16 +7,14 @@ import ( "github.com/9seconds/mtg/conntypes" ) -var Registry *RegistryStruct - -type RegistryStruct struct { - conns map[string]*closeableChannel +type registry struct { + conns map[string]*ctxChannel ctx context.Context mutex sync.RWMutex } -func (r *RegistryStruct) Register(id conntypes.ConnID) ChannelReadCloser { - channel := newCloseableChannel(r.ctx) +func (r *registry) Register(id conntypes.ConnID) ChannelReadCloser { + channel := newCtxChannel(r.ctx) r.mutex.Lock() r.conns[string(id[:])] = channel @@ -25,7 +23,7 @@ func (r *RegistryStruct) Register(id conntypes.ConnID) ChannelReadCloser { return channel } -func (r *RegistryStruct) Unregister(id conntypes.ConnID) { +func (r *registry) Unregister(id conntypes.ConnID) { r.mutex.Lock() defer r.mutex.Unlock() @@ -35,7 +33,7 @@ func (r *RegistryStruct) Unregister(id conntypes.ConnID) { } } -func (r *RegistryStruct) getChannel(id conntypes.ConnID) (*closeableChannel, bool) { +func (r *registry) getChannel(id conntypes.ConnID) (*ctxChannel, bool) { r.mutex.RLock() defer r.mutex.RUnlock() @@ -46,8 +44,8 @@ func (r *RegistryStruct) getChannel(id conntypes.ConnID) (*closeableChannel, boo } func InitRegistry(ctx context.Context) { - Registry = &RegistryStruct{ + Registry = ®istry{ ctx: ctx, - conns: map[string]*closeableChannel{}, + conns: map[string]*ctxChannel{}, } } From d44474012a0532373d4dd1b85dee6d6697669c11 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 7 Oct 2019 17:51:21 +0300 Subject: [PATCH 20/62] Rework antireplay --- antireplay/cache.go | 33 +++++++++------------------------ antireplay/init.go | 31 +++++++++++++++++++++++++++++++ obfuscated2/client_protocol.go | 4 ++-- 3 files changed, 42 insertions(+), 26 deletions(-) create mode 100644 antireplay/init.go diff --git a/antireplay/cache.go b/antireplay/cache.go index 219d2d2..0e634f2 100644 --- a/antireplay/cache.go +++ b/antireplay/cache.go @@ -1,31 +1,16 @@ package antireplay -import ( - "github.com/allegro/bigcache" +import "github.com/allegro/bigcache" - "github.com/9seconds/mtg/config" -) - -var cache *bigcache.BigCache - -func Add(data []byte) { - cache.Set(string(data), nil) // nolint: errcheck +type cache struct { + cache *bigcache.BigCache } -func Has(data []byte) bool { - _, err := cache.Get(string(data)) +func (c *cache) Add(data []byte) { + c.cache.Set(string(data), nil) // nolint: errcheck +} + +func (c *cache) Has(data []byte) bool { + _, err := c.cache.Get(string(data)) return err == nil } - -func Init() { - c, err := bigcache.NewBigCache(bigcache.Config{ - Shards: 1024, - LifeWindow: config.C.AntiReplay.EvictionTime, - Hasher: hasher{}, - HardMaxCacheSize: config.C.AntiReplay.MaxSize, - }) - if err != nil { - panic(err) - } - cache = c -} diff --git a/antireplay/init.go b/antireplay/init.go new file mode 100644 index 0000000..4211c77 --- /dev/null +++ b/antireplay/init.go @@ -0,0 +1,31 @@ +package antireplay + +import ( + "sync" + + "github.com/9seconds/mtg/config" + "github.com/allegro/bigcache" +) + +var ( + Cache *cache + initOnce sync.Once +) + +func Init() { + initOnce.Do(func() { + c, err := bigcache.NewBigCache(bigcache.Config{ + Shards: 1024, + LifeWindow: config.C.AntiReplay.EvictionTime, + Hasher: hasher{}, + HardMaxCacheSize: config.C.AntiReplay.MaxSize, + }) + if err != nil { + panic(err) + } + + Cache = &cache{ + cache: c, + } + }) +} diff --git a/obfuscated2/client_protocol.go b/obfuscated2/client_protocol.go index 028f868..38a27f3 100644 --- a/obfuscated2/client_protocol.go +++ b/obfuscated2/client_protocol.go @@ -80,10 +80,10 @@ func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conn } antiReplayKey := decryptedFrame.Unique() - if antireplay.Has(antiReplayKey) { + if antireplay.Cache.Has(antiReplayKey) { return nil, errors.New("Replay attack is detected") } - antireplay.Add(antiReplayKey) + antireplay.Cache.Add(antiReplayKey) return wrappers.NewObfuscated2(socket, encryptor, decryptor), nil } From 6c7edfb7db1028c73cb5990866dafcfdef998824 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Tue, 8 Oct 2019 11:55:57 +0300 Subject: [PATCH 21/62] Add new stats metric --- antireplay/init.go | 4 +- cli/proxy.go | 4 +- config/config.go | 207 ++++++++++++++----------------------- config/urls.go | 7 +- hub/connection.go | 2 +- hub/ctx_channel.go | 1 - main.go | 98 ++++++------------ proxy/proxy.go | 7 +- stats/interfaces.go | 50 +++++++++ stats/multi_stats.go | 57 ++++++++++ stats/stats.go | 59 +---------- stats/stats_json.go | 131 ----------------------- stats/stats_prometheus.go | 63 ++++++++--- stats/stats_statsd.go | 49 ++++++--- telegram/base.go | 2 +- utils/init_tcp.go | 4 +- wrappers/common.go | 21 ++++ wrappers/conn.go | 17 +-- wrappers/stats.go | 67 ------------ wrappers/stats_telegram.go | 70 +++++++++++++ wrappers/stats_traffic.go | 67 ++++++++++++ 21 files changed, 477 insertions(+), 510 deletions(-) create mode 100644 stats/interfaces.go create mode 100644 stats/multi_stats.go delete mode 100644 stats/stats_json.go create mode 100644 wrappers/common.go delete mode 100644 wrappers/stats.go create mode 100644 wrappers/stats_telegram.go create mode 100644 wrappers/stats_traffic.go diff --git a/antireplay/init.go b/antireplay/init.go index 4211c77..745e702 100644 --- a/antireplay/init.go +++ b/antireplay/init.go @@ -16,9 +16,9 @@ func Init() { initOnce.Do(func() { c, err := bigcache.NewBigCache(bigcache.Config{ Shards: 1024, - LifeWindow: config.C.AntiReplay.EvictionTime, + LifeWindow: config.C.AntiReplayEvictionTime, Hasher: hasher{}, - HardMaxCacheSize: config.C.AntiReplay.MaxSize, + HardMaxCacheSize: config.C.AntiReplayMaxSize, }) if err != nil { panic(err) diff --git a/cli/proxy.go b/cli/proxy.go index f181bc6..2017e8f 100644 --- a/cli/proxy.go +++ b/cli/proxy.go @@ -43,7 +43,7 @@ func Proxy() error { if err := config.InitPublicAddress(ctx); err != nil { Fatal(err) } - zap.S().Debugw("Configuration", "config", config.C.Printable()) + zap.S().Debugw("Configuration", "config", config.Printable()) if len(config.C.AdTag) > 0 { zap.S().Infow("Use middle proxy connection to Telegram") @@ -67,7 +67,7 @@ func Proxy() error { } telegram.MiddleInit() - proxyListener, err := net.Listen("tcp", config.C.ListenAddr.String()) + proxyListener, err := net.Listen("tcp", config.C.Bind.String()) if err != nil { Fatal(err) } diff --git a/config/config.go b/config/config.go index f272660..543a7a8 100644 --- a/config/config.go +++ b/config/config.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "net" - "strconv" "time" "go.uber.org/zap" @@ -40,22 +39,16 @@ const ( OptionTypeDebug OptionType = iota OptionTypeVerbose - OptionTypeBindIP - OptionTypeBindPort + OptionTypeBind OptionTypePublicIPv4 - OptionTypePublicIPv4Port OptionTypePublicIPv6 - OptionTypePublicIPv6Port - OptionTypeStatsIP - OptionTypeStatsPort - OptionTypeStatsdIP - OptionTypeStatsdPort + OptionTypeStatsBind + OptionTypeStatsNamespace + OptionTypeStatsdAddress OptionTypeStatsdNetwork - OptionTypeStatsdPrefix OptionTypeStatsdTagsFormat OptionTypeStatsdTags - OptionTypePrometheusPrefix OptionTypeWriteBufferSize OptionTypeReadBufferSize @@ -67,93 +60,30 @@ const ( OptionTypeAdtag ) -type BufferSize struct { - Read int `json:"read"` - Write int `json:"write"` -} - -type AntiReplay struct { - MaxSize int `json:"max_size"` - EvictionTime time.Duration `json:"duration"` -} - -type Stats struct { - Prefix string `json:"prefix"` -} - -type StatsdStats struct { - Stats - - Addr Addr `json:"addr"` - Tags map[string]string `json:"tags"` - TagsFormat statsd.TagFormat `json:"format"` -} - -type PrometheusStats struct { - Stats -} - -type Addr struct { - IP net.IP `json:"ip"` - Port int `json:"port"` - net string -} - -func (a Addr) Network() string { - if a.net == "" { - return "tcp" - } - return a.net -} - -func (a Addr) String() string { - return net.JoinHostPort(a.IP.String(), strconv.Itoa(a.Port)) -} - -func (a Addr) MarshalJSON() ([]byte, error) { - data := map[string]string{ - "network": a.Network(), - "addr": a.String(), - } - return json.Marshal(data) -} - type Config struct { - BufferSize BufferSize `json:"buffer_size"` - AntiReplay AntiReplay `json:"anti_replay"` + Bind *net.TCPAddr `json:"bind"` + PublicIPv4 *net.TCPAddr `json:"public_ipv4"` + PublicIPv6 *net.TCPAddr `json:"public_ipv6"` + StatsBind *net.TCPAddr `json:"stats_bind"` + StatsdAddr *net.TCPAddr `json:"stats_addr"` - ListenAddr Addr `json:"listen_addr"` - PublicIPv4Addr Addr `json:"public_ipv4_addr"` - PublicIPv6Addr Addr `json:"public_ipv6_addr"` - StatsAddr Addr `json:"stats_addr"` + StatsNamespace string `json:"stats_namespace"` + StatsdNetwork string `json:"statsd_network"` + StatsdTags map[string]string `json:"statsd_tags"` - StatsdStats StatsdStats `json:"stats_statsd"` - PrometheusStats PrometheusStats `json:"stats_prometheus"` + WriteBuffer int `json:"write_buffer"` + ReadBuffer int `json:"read_buffer"` - Debug bool `json:"debug"` - Verbose bool `json:"verbose"` - SecretMode SecretMode `json:"secret_mode"` - Secret []byte `json:"secret"` - AdTag []byte `json:"adtag"` -} + AntiReplayMaxSize int `json:"anti_replay_max_size"` + AntiReplayEvictionTime time.Duration `json:"anti_replay_eviction_time"` -func (c Config) Printable() interface{} { - data, err := json.Marshal(c) - if err != nil { - panic(err) - } + Debug bool `json:"debug"` + Verbose bool `json:"verbose"` + StatsdTagsFormat statsd.TagFormat `json:"statsd_tags_format"` + SecretMode SecretMode `json:"secret_mode"` - rv := map[string]interface{}{} - if err := json.Unmarshal(data, &rv); err != nil { - panic(err) - } - - return rv -} - -func (c Config) String() string { - data, _ := json.Marshal(c) - return string(data) + Secret []byte `json:"secret"` + AdTag []byte `json:"adtag"` } type Opt struct { @@ -163,59 +93,53 @@ type Opt struct { var C = Config{} -func Init(options ...Opt) error { // nolint: gocyclo +func Init(options ...Opt) error { // nolint: gocyclo, funlen for _, opt := range options { switch opt.Option { case OptionTypeDebug: C.Debug = opt.Value.(bool) case OptionTypeVerbose: C.Verbose = opt.Value.(bool) - case OptionTypeBindIP: - C.ListenAddr.IP = opt.Value.(net.IP) - case OptionTypeBindPort: - C.ListenAddr.Port = int(opt.Value.(uint16)) + case OptionTypeBind: + C.Bind = opt.Value.(*net.TCPAddr) case OptionTypePublicIPv4: - C.PublicIPv4Addr.IP = opt.Value.(net.IP) - case OptionTypePublicIPv4Port: - C.PublicIPv4Addr.Port = int(opt.Value.(uint16)) + C.PublicIPv4 = opt.Value.(*net.TCPAddr) case OptionTypePublicIPv6: - C.PublicIPv6Addr.IP = opt.Value.(net.IP) - case OptionTypePublicIPv6Port: - C.PublicIPv6Addr.Port = int(opt.Value.(uint16)) - case OptionTypeStatsIP: - C.StatsAddr.IP = opt.Value.(net.IP) - case OptionTypeStatsPort: - C.StatsAddr.Port = int(opt.Value.(uint16)) - case OptionTypeStatsdIP: - C.StatsdStats.Addr.IP = opt.Value.(net.IP) - case OptionTypeStatsdPort: - C.StatsdStats.Addr.Port = int(opt.Value.(uint16)) + C.PublicIPv6 = opt.Value.(*net.TCPAddr) + case OptionTypeStatsBind: + C.StatsBind = opt.Value.(*net.TCPAddr) + case OptionTypeStatsNamespace: + C.StatsNamespace = opt.Value.(string) + case OptionTypeStatsdAddress: + C.StatsdAddr = opt.Value.(*net.TCPAddr) case OptionTypeStatsdNetwork: - C.StatsdStats.Addr.net = opt.Value.(string) - case OptionTypeStatsdPrefix: - C.StatsdStats.Prefix = opt.Value.(string) + value := opt.Value.(string) + switch value { + case "udp", "tcp": + C.StatsdNetwork = value + default: + return fmt.Errorf("unknown statsd network %v", value) + } case OptionTypeStatsdTagsFormat: value := opt.Value.(string) switch value { case "datadog": - C.StatsdStats.TagsFormat = statsd.Datadog + C.StatsdTagsFormat = statsd.Datadog case "influxdb": - C.StatsdStats.TagsFormat = statsd.InfluxDB + C.StatsdTagsFormat = statsd.InfluxDB default: return fmt.Errorf("Incorrect statsd tag %s", value) } case OptionTypeStatsdTags: - C.StatsdStats.Tags = opt.Value.(map[string]string) - case OptionTypePrometheusPrefix: - C.PrometheusStats.Prefix = opt.Value.(string) + C.StatsdTags = opt.Value.(map[string]string) case OptionTypeWriteBufferSize: - C.BufferSize.Write = int(opt.Value.(uint32)) + C.WriteBuffer = int(opt.Value.(uint32)) case OptionTypeReadBufferSize: - C.BufferSize.Read = int(opt.Value.(uint32)) + C.ReadBuffer = int(opt.Value.(uint32)) case OptionTypeAntiReplayMaxSize: - C.AntiReplay.MaxSize = opt.Value.(int) + C.AntiReplayMaxSize = opt.Value.(int) case OptionTypeAntiReplayEvictionTime: - C.AntiReplay.EvictionTime = opt.Value.(time.Duration) + C.AntiReplayEvictionTime = opt.Value.(time.Duration) case OptionTypeSecret: C.Secret = opt.Value.([]byte) case OptionTypeAdtag: @@ -239,29 +163,29 @@ func Init(options ...Opt) error { // nolint: gocyclo } func InitPublicAddress(ctx context.Context) error { - if C.PublicIPv4Addr.Port == 0 { - C.PublicIPv4Addr.Port = C.ListenAddr.Port + if C.PublicIPv4.Port == 0 { + C.PublicIPv4.Port = C.Bind.Port } - if C.PublicIPv6Addr.Port == 0 { - C.PublicIPv6Addr.Port = C.ListenAddr.Port + if C.PublicIPv6.Port == 0 { + C.PublicIPv6.Port = C.Bind.Port } - foundAddress := C.PublicIPv4Addr.IP != nil || C.PublicIPv6Addr.IP != nil - if C.PublicIPv4Addr.IP == nil { + foundAddress := C.PublicIPv4.IP != nil || C.PublicIPv6.IP != nil + if C.PublicIPv4.IP == nil { ip, err := getGlobalIPv4(ctx) if err != nil { zap.S().Warnw("Cannot resolve public address", "error", err) } else { - C.PublicIPv4Addr.IP = ip + C.PublicIPv4.IP = ip foundAddress = true } } - if C.PublicIPv6Addr.IP == nil { + if C.PublicIPv6.IP == nil { ip, err := getGlobalIPv6(ctx) if err != nil { zap.S().Warnw("Cannot resolve public address", "error", err) } else { - C.PublicIPv6Addr.IP = ip + C.PublicIPv6.IP = ip foundAddress = true } } @@ -272,3 +196,22 @@ func InitPublicAddress(ctx context.Context) error { return nil } + +func Printable() interface{} { + data, err := json.Marshal(C) + if err != nil { + panic(err) + } + + rv := map[string]interface{}{} + if err := json.Unmarshal(data, &rv); err != nil { + panic(err) + } + + rrv, err := json.Marshal(rv) + if err != nil { + panic(err) + } + + return rrv +} diff --git a/config/urls.go b/config/urls.go index 2d95486..9d50f08 100644 --- a/config/urls.go +++ b/config/urls.go @@ -2,6 +2,7 @@ package config import ( "encoding/hex" + "fmt" "net/url" ) @@ -27,14 +28,14 @@ func GetURLs() (urls IPURLs) { secret = "dd" + hex.EncodeToString(C.Secret) } - urls.IPv4 = makeURLs(&C.PublicIPv4Addr, secret) - urls.IPv6 = makeURLs(&C.PublicIPv6Addr, secret) + urls.IPv4 = makeURLs(C.PublicIPv4, secret) + urls.IPv6 = makeURLs(C.PublicIPv6, secret) urls.BotSecret = secret return urls } -func makeURLs(addr *Addr, secret string) (urls URLs) { +func makeURLs(addr fmt.Stringer, secret string) (urls URLs) { values := url.Values{} values.Set("address", addr.String()) values.Set("secret", secret) diff --git a/hub/connection.go b/hub/connection.go index 17ebbfc..751fc3c 100644 --- a/hub/connection.go +++ b/hub/connection.go @@ -52,7 +52,7 @@ func (c *connection) write(packet conntypes.Packet) error { func (c *connection) shutdown() { c.shutdownOnce.Do(func() { close(c.done) - c.hub.channelBrokenSockets <- c.id + c.hub.channelBrokenSockets <- c.id }) } diff --git a/hub/ctx_channel.go b/hub/ctx_channel.go index 9394ba3..bc28cb2 100644 --- a/hub/ctx_channel.go +++ b/hub/ctx_channel.go @@ -2,7 +2,6 @@ package hub import ( "context" - "errors" "time" "github.com/9seconds/mtg/conntypes" diff --git a/main.go b/main.go index 774a3fc..f47a1fd 100644 --- a/main.go +++ b/main.go @@ -36,67 +36,42 @@ var ( Short('v'). Envar("MTG_VERBOSE"). Bool() - proxyBindIP = proxyCommand.Flag("bind-ip", - "Which IP to bind to."). + proxyBind = proxyCommand.Flag("bind", + "Host:Port to bind proxy to."). Short('b'). - Envar("MTG_IP"). - Default("127.0.0.1"). - IP() - proxyBindPort = proxyCommand.Flag("bind-port", - "Which port to bind to."). - Short('p'). - Envar("MTG_PORT"). - Default("3128"). - Uint16() + Envar("MTG_BIND"). + Default("0.0.0.0:3128"). + TCP() proxyPublicIPv4 = proxyCommand.Flag("public-ipv4", - "Which IPv4 address is public."). + "Which IPv4 host:port to use."). Short('4'). Envar("MTG_IPV4"). - IP() - proxyPublicIPv4Port = proxyCommand.Flag("public-ipv4-port", - "Which IPv4 port is public. Default is 'bind-port' value."). - Envar("MTG_IPV4_PORT"). - Uint16() + TCP() proxyPublicIPv6 = proxyCommand.Flag("public-ipv6", - "Which IPv6 address is public."). + "Which IPv6 host:port to use."). Short('6'). Envar("MTG_IPV6"). - IP() - proxyPublicIPv6Port = proxyCommand.Flag("public-ipv6-port", - "Which IPv6 port is public. Default is 'bind-port' value."). - Envar("MTG_IPV6_PORT"). - Uint16() - proxyStatsIP = proxyCommand.Flag("stats-ip", - "Which IP bind stats server to."). + TCP() + proxyStatsBind = proxyCommand.Flag("stats-bind", + "Which Host:Port to bind stats server to."). Short('t'). - Envar("MTG_STATS_IP"). - Default("127.0.0.1"). - IP() - proxyStatsPort = proxyCommand.Flag("stats-port", - "Which port bind stats to."). - Short('q'). - Envar("MTG_STATS_PORT"). - Default("3129"). - Uint16() - proxyStatsdIP = proxyCommand.Flag("statsd-ip", - "Which IP should we use for working with statsd."). - Envar("MTG_STATSD_IP"). - IP() - proxyStatsdPort = proxyCommand.Flag("statsd-port", - "Which port should we use for working with statsd."). - Envar("MTG_STATSD_PORT"). - Default("8125"). - Uint16() + Envar("MTG_STATS_BIND"). + Default("127.0.0.1:3129"). + TCP() + proxyStatsNamespace = proxyCommand.Flag("prometheus-namespace", + "Which namespace to use for Prometheus."). + Envar("MTG_STATS_NAMESPACE"). + Default("mtg"). + String() + proxyStatsdAddress = proxyCommand.Flag("statsd-addr", + "Host:port of statsd server"). + Envar("MTG_STATSD_ADDR"). + TCP() proxyStatsdNetwork = proxyCommand.Flag("statsd-network", "Which network is used to work with statsd. Only 'tcp' and 'udp' are supported."). Envar("MTG_STATSD_NETWORK"). Default("udp"). Enum("udp", "tcp") - proxyStatsdPrefix = proxyCommand.Flag("statsd-prefix", - "Which bucket prefix should we use for sending stats to statsd."). - Envar("MTG_STATSD_PREFIX"). - Default("mtg"). - String() proxyStatsdTagsFormat = proxyCommand.Flag("statsd-tags-format", "Which tag format should we use to send stats metrics. Valid options are 'datadog' and 'influxdb'."). Envar("MTG_STATSD_TAGS_FORMAT"). @@ -106,23 +81,18 @@ var ( "Tags to use for working with statsd (specified as 'key=value')."). Envar("MTG_STATSD_TAGS"). StringMap() - proxyPrometheusPrefix = proxyCommand.Flag("prometheus-prefix", - "Which namespace to use to send stats to Prometheus."). - Envar("MTG_PROMETHEUS_PREFIX"). - Default("mtg"). - String() proxyWriteBufferSize = proxyCommand.Flag("write-buffer", "Write buffer size in bytes. You can think about it as a buffer from client to Telegram."). Short('w'). Envar("MTG_BUFFER_WRITE"). - Default("65536"). - Uint32() + Default("65536KB"). + Bytes() proxyReadBufferSize = proxyCommand.Flag("read-buffer", "Read buffer size in bytes. You can think about it as a buffer from Telegram to client."). Short('r'). Envar("MTG_BUFFER_READ"). - Default("131072"). - Uint32() + Default("131072KB"). + Bytes() proxyAntiReplayMaxSize = proxyCommand.Flag("anti-replay-max-size", "Max size of antireplay cache in megabytes."). Envar("MTG_ANTIREPLAY_MAXSIZE"). @@ -154,21 +124,15 @@ func main() { err := config.Init( config.Opt{Option: config.OptionTypeDebug, Value: *proxyDebug}, config.Opt{Option: config.OptionTypeVerbose, Value: *proxyVerbose}, - config.Opt{Option: config.OptionTypeBindIP, Value: *proxyBindIP}, - config.Opt{Option: config.OptionTypeBindPort, Value: *proxyBindPort}, + config.Opt{Option: config.OptionTypeBind, Value: *proxyBind}, config.Opt{Option: config.OptionTypePublicIPv4, Value: *proxyPublicIPv4}, - config.Opt{Option: config.OptionTypePublicIPv4Port, Value: *proxyPublicIPv4Port}, config.Opt{Option: config.OptionTypePublicIPv6, Value: *proxyPublicIPv6}, - config.Opt{Option: config.OptionTypePublicIPv6Port, Value: *proxyPublicIPv6Port}, - config.Opt{Option: config.OptionTypeStatsIP, Value: *proxyStatsIP}, - config.Opt{Option: config.OptionTypeStatsPort, Value: *proxyStatsPort}, - config.Opt{Option: config.OptionTypeStatsdIP, Value: *proxyStatsdIP}, - config.Opt{Option: config.OptionTypeStatsdPort, Value: *proxyStatsdPort}, + config.Opt{Option: config.OptionTypeStatsBind, Value: *proxyStatsBind}, + config.Opt{Option: config.OptionTypeStatsNamespace, Value: *proxyStatsNamespace}, + config.Opt{Option: config.OptionTypeStatsdAddress, Value: *proxyStatsdAddress}, config.Opt{Option: config.OptionTypeStatsdNetwork, Value: *proxyStatsdNetwork}, - config.Opt{Option: config.OptionTypeStatsdPrefix, Value: *proxyStatsdPrefix}, config.Opt{Option: config.OptionTypeStatsdTagsFormat, Value: *proxyStatsdTagsFormat}, config.Opt{Option: config.OptionTypeStatsdTags, Value: *proxyStatsdTags}, - config.Opt{Option: config.OptionTypePrometheusPrefix, Value: *proxyPrometheusPrefix}, config.Opt{Option: config.OptionTypeWriteBufferSize, Value: *proxyWriteBufferSize}, config.Opt{Option: config.OptionTypeReadBufferSize, Value: *proxyReadBufferSize}, config.Opt{Option: config.OptionTypeAntiReplayMaxSize, Value: *proxyAntiReplayMaxSize}, diff --git a/proxy/proxy.go b/proxy/proxy.go index 501b721..1bca64f 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -47,7 +47,7 @@ func (p *Proxy) accept(conn net.Conn) { defer func() { conn.Close() if err := recover(); err != nil { - stats.S.Crash() + stats.Stats.Crash() p.Logger.Errorw("Crash of accept handler", "error", err) } }() @@ -66,7 +66,6 @@ func (p *Proxy) accept(conn net.Conn) { clientConn := wrappers.NewClientConn(conn, connID) clientConn = wrappers.NewCtx(ctx, cancel, clientConn) clientConn = wrappers.NewTimeout(clientConn) - clientConn = wrappers.NewTraffic(clientConn) defer clientConn.Close() clientProtocol := p.ClientProtocolMaker() @@ -76,8 +75,8 @@ func (p *Proxy) accept(conn net.Conn) { return } - stats.S.ClientConnected(clientProtocol.ConnectionType(), clientConn.RemoteAddr()) - defer stats.S.ClientDisconnected(clientProtocol.ConnectionType(), clientConn.RemoteAddr()) + stats.Stats.ClientConnected(clientProtocol.ConnectionType(), clientConn.RemoteAddr()) + defer stats.Stats.ClientDisconnected(clientProtocol.ConnectionType(), clientConn.RemoteAddr()) logger.Infow("Client connected", "addr", conn.RemoteAddr()) req := &protocol.TelegramRequest{ diff --git a/stats/interfaces.go b/stats/interfaces.go new file mode 100644 index 0000000..9a27580 --- /dev/null +++ b/stats/interfaces.go @@ -0,0 +1,50 @@ +package stats + +import ( + "net" + + "github.com/9seconds/mtg/conntypes" +) + +type IngressTrafficInterface interface { + IngressTraffic(int) +} + +type EgressTrafficInterface interface { + EgressTraffic(int) +} + +type ClientConnectedInterface interface { + ClientConnected(conntypes.ConnectionType, *net.TCPAddr) +} + +type ClientDisconnectedInterface interface { + ClientDisconnected(conntypes.ConnectionType, *net.TCPAddr) +} + +type TelegramConnectedInterface interface { + TelegramConnected(conntypes.DC, *net.TCPAddr) +} + +type TelegramDisconnectedInterface interface { + TelegramDisconnected(conntypes.DC, *net.TCPAddr) +} + +type CrashInterface interface { + Crash() +} + +type AntiReplayDetectedInterface interface { + AntiReplayDetected() +} + +type Interface interface { + IngressTrafficInterface + EgressTrafficInterface + ClientConnectedInterface + ClientDisconnectedInterface + TelegramConnectedInterface + TelegramDisconnectedInterface + CrashInterface + AntiReplayDetectedInterface +} diff --git a/stats/multi_stats.go b/stats/multi_stats.go new file mode 100644 index 0000000..c67a4ef --- /dev/null +++ b/stats/multi_stats.go @@ -0,0 +1,57 @@ +package stats + +import ( + "net" + + "github.com/9seconds/mtg/conntypes" +) + +type multiStats []Interface + +func (m multiStats) IngressTraffic(traffic int) { + for i := range m { + go m[i].IngressTraffic(traffic) + } +} + +func (m multiStats) EgressTraffic(traffic int) { + for i := range m { + go m[i].EgressTraffic(traffic) + } +} + +func (m multiStats) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) { + for i := range m { + go m[i].ClientConnected(connectionType, addr) + } +} + +func (m multiStats) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) { + for i := range m { + go m[i].ClientDisconnected(connectionType, addr) + } +} + +func (m multiStats) TelegramConnected(dc conntypes.DC, addr *net.TCPAddr) { + for i := range m { + go m[i].TelegramConnected(dc, addr) + } +} + +func (m multiStats) TelegramDisconnected(dc conntypes.DC, addr *net.TCPAddr) { + for i := range m { + go m[i].TelegramDisconnected(dc, addr) + } +} + +func (m multiStats) Crash() { + for i := range m { + go m[i].Crash() + } +} + +func (m multiStats) AntiReplayDetected() { + for i := range m { + go m[i].AntiReplayDetected() + } +} diff --git a/stats/stats.go b/stats/stats.go index b3742ec..535b5f3 100644 --- a/stats/stats.go +++ b/stats/stats.go @@ -7,69 +7,20 @@ import ( "net/http" "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/conntypes" ) -type Stats interface { - IngressTraffic(int) - EgressTraffic(int) - ClientConnected(conntypes.ConnectionType, *net.TCPAddr) - ClientDisconnected(conntypes.ConnectionType, *net.TCPAddr) - Crash() - AntiReplayDetected() -} - -type multiStats []Stats - -func (m multiStats) IngressTraffic(traffic int) { - for i := range m { - go m[i].IngressTraffic(traffic) - } -} - -func (m multiStats) EgressTraffic(traffic int) { - for i := range m { - go m[i].EgressTraffic(traffic) - } -} - -func (m multiStats) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) { - for i := range m { - go m[i].ClientConnected(connectionType, addr) - } -} - -func (m multiStats) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) { - for i := range m { - go m[i].ClientDisconnected(connectionType, addr) - } -} - -func (m multiStats) Crash() { - for i := range m { - go m[i].Crash() - } -} - -func (m multiStats) AntiReplayDetected() { - for i := range m { - go m[i].AntiReplayDetected() - } -} - -var S Stats +var Stats Interface func Init(ctx context.Context) error { mux := http.NewServeMux() - instanceJSON := newStatsJSON(mux) instancePrometheus, err := newStatsPrometheus(mux) if err != nil { return fmt.Errorf("cannot initialize prometheus: %w", err) } - stats := []Stats{instanceJSON, instancePrometheus} - if config.C.StatsdStats.Addr.IP != nil { + stats := []Interface{instancePrometheus} + if config.C.StatsdAddr != nil { instanceStatsd, err := newStatsStatsd() if err != nil { return fmt.Errorf("cannot inialize statsd: %w", err) @@ -77,7 +28,7 @@ func Init(ctx context.Context) error { stats = append(stats, instanceStatsd) } - listener, err := net.Listen("tcp", config.C.StatsAddr.String()) + listener, err := net.Listen("tcp", config.C.StatsBind.String()) if err != nil { return fmt.Errorf("cannot initialize stats server: %w", err) } @@ -91,7 +42,7 @@ func Init(ctx context.Context) error { srv.Shutdown(context.Background()) // nolint: errcheck }() - S = multiStats(stats) + Stats = multiStats(stats) return nil } diff --git a/stats/stats_json.go b/stats/stats_json.go deleted file mode 100644 index b180779..0000000 --- a/stats/stats_json.go +++ /dev/null @@ -1,131 +0,0 @@ -package stats - -import ( - "encoding/json" - "net" - "net/http" - "strconv" - "sync/atomic" - "time" - - "go.uber.org/zap" - - "github.com/9seconds/mtg/conntypes" -) - -type statsJSON struct { - Connections statsJSONConnections `json:"connections"` - Traffic statsJSONTraffic `json:"traffic"` - Uptime statsJSONUptime `json:"uptime"` - Crashes uint32 `json:"crashes"` - AntiReplays uint32 `json:"anti_replay_detected"` -} - -type statsBaseJSONConnections struct { - All statsJSONConnectionType `json:"all"` - Abridged statsJSONConnectionType `json:"abridged"` - Intermediate statsJSONConnectionType `json:"intermediate"` - Secured statsJSONConnectionType `json:"secured"` -} - -type statsJSONConnections struct { - statsBaseJSONConnections -} - -type statsJSONConnectionType struct { - IPv4 uint32 `json:"ipv4"` - IPv6 uint32 `json:"ipv6"` -} - -func (c statsJSONConnections) MarshalJSON() ([]byte, error) { - c.All.IPv4 = c.Abridged.IPv4 + c.Intermediate.IPv4 + c.Secured.IPv4 - c.All.IPv6 = c.Abridged.IPv6 + c.Intermediate.IPv6 + c.Secured.IPv6 - - return json.Marshal(c.statsBaseJSONConnections) -} - -type statsJSONTraffic struct { - Ingress uint64 `json:"ingress"` - Egress uint64 `json:"egress"` -} - -type statsJSONUptime time.Time - -func (s statsJSONUptime) MarshalJSON() ([]byte, error) { - seconds := strconv.Itoa(int(time.Since(time.Time(s)).Seconds())) - return []byte(seconds), nil -} - -func (s *statsJSON) IngressTraffic(traffic int) { - atomic.AddUint64(&s.Traffic.Ingress, uint64(traffic)) -} - -func (s *statsJSON) EgressTraffic(traffic int) { - atomic.AddUint64(&s.Traffic.Egress, uint64(traffic)) -} - -func (s *statsJSON) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) { - s.changeConnections(connectionType, addr, 1) -} - -func (s *statsJSON) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) { - s.changeConnections(connectionType, addr, ^uint32(0)) -} - -func (s *statsJSON) changeConnections(connectionType conntypes.ConnectionType, addr *net.TCPAddr, value uint32) { - var connections *statsJSONConnectionType - - switch connectionType { - case conntypes.ConnectionTypeAbridged: - connections = &s.Connections.Abridged - case conntypes.ConnectionTypeSecure: - connections = &s.Connections.Secured - default: - connections = &s.Connections.Intermediate - } - - if addr.IP.To4() != nil { - atomic.AddUint32(&connections.IPv4, value) - } else { - atomic.AddUint32(&connections.IPv6, value) - } -} - -func (s *statsJSON) Crash() { - atomic.AddUint32(&s.Crashes, 1) -} - -func (s *statsJSON) AntiReplayDetected() { - atomic.AddUint32(&s.AntiReplays, 1) -} - -func newStatsJSON(mux *http.ServeMux) Stats { - instance := &statsJSON{ - Uptime: statsJSONUptime(time.Now()), - } - logger := zap.S().Named("stats") - - mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "application/json") - first, err := json.Marshal(instance) - if err != nil { - logger.Errorw("Cannot encode json", "error", err) - http.Error(w, "Internal server error", http.StatusServiceUnavailable) - return - } - - interim := map[string]interface{}{} - if err := json.Unmarshal(first, &interim); err != nil { - panic(err) - } - - encoder := json.NewEncoder(w) - encoder.SetEscapeHTML(false) - encoder.SetIndent("", " ") - if err := encoder.Encode(interim); err != nil { - logger.Errorw("Cannot encode json", "error", err) - } - }) - - return instance -} diff --git a/stats/stats_prometheus.go b/stats/stats_prometheus.go index 80c512a..a075940 100644 --- a/stats/stats_prometheus.go +++ b/stats/stats_prometheus.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "net/http" + "strconv" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -13,10 +14,11 @@ import ( ) type statsPrometheus struct { - connections *prometheus.GaugeVec - traffic *prometheus.GaugeVec - crashes prometheus.Gauge - antiReplays prometheus.Gauge + connections *prometheus.GaugeVec + telegramConnections *prometheus.GaugeVec + traffic *prometheus.GaugeVec + crashes prometheus.Gauge + antiReplays prometheus.Counter } func (s *statsPrometheus) IngressTraffic(traffic int) { @@ -38,18 +40,39 @@ func (s *statsPrometheus) ClientDisconnected(connectionType conntypes.Connection func (s *statsPrometheus) changeConnections(connectionType conntypes.ConnectionType, addr *net.TCPAddr, increment float64) { - var labels [2]string + labels := [...]string{ + "intermediate", + "ipv4", + } switch connectionType { case conntypes.ConnectionTypeAbridged: labels[0] = "abridged" case conntypes.ConnectionTypeSecure: labels[0] = "secured" - default: - labels[0] = "intermediate" } - labels[1] = "ipv4" + if addr.IP.To4() == nil { + labels[1] = "ipv6" // nolint: goconst + } + + s.connections.WithLabelValues(labels[:]...).Add(increment) +} + +func (s *statsPrometheus) TelegramConnected(dc conntypes.DC, addr *net.TCPAddr) { + s.changeTelegramConnections(dc, addr, 1.0) +} + +func (s *statsPrometheus) TelegramDisconnected(dc conntypes.DC, addr *net.TCPAddr) { + s.changeTelegramConnections(dc, addr, -1.0) +} + +func (s *statsPrometheus) changeTelegramConnections(dc conntypes.DC, addr *net.TCPAddr, increment float64) { + labels := [...]string{ + strconv.Itoa(int(dc)), + "ipv4", + } + if addr.IP.To4() == nil { labels[1] = "ipv6" } @@ -65,26 +88,32 @@ func (s *statsPrometheus) AntiReplayDetected() { s.antiReplays.Inc() } -func newStatsPrometheus(mux *http.ServeMux) (Stats, error) { - registry := prometheus.NewRegistry() +func newStatsPrometheus(mux *http.ServeMux) (Interface, error) { + registry := prometheus.NewPedanticRegistry() + instance := &statsPrometheus{ connections: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: config.C.PrometheusStats.Prefix, + Namespace: config.C.StatsNamespace, Name: "connections", - Help: "Current number of connections to the proxy.", + Help: "Current number of client connections to the proxy.", }, []string{"type", "protocol"}), + telegramConnections: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: config.C.StatsNamespace, + Name: "telegram_connections", + Help: "Current number of telegram connections established by this proxy.", + }, []string{"dc", "protocol"}), traffic: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: config.C.PrometheusStats.Prefix, + Namespace: config.C.StatsNamespace, Name: "traffic", Help: "Traffic passed through the proxy in bytes.", }, []string{"direction"}), crashes: prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: config.C.PrometheusStats.Prefix, + Namespace: config.C.StatsNamespace, Name: "crashes", Help: "How many crashes happened.", }), - antiReplays: prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: config.C.PrometheusStats.Prefix, + antiReplays: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: config.C.StatsNamespace, Name: "anti_replays", Help: "How many anti replay attacks were prevented.", }), @@ -104,7 +133,7 @@ func newStatsPrometheus(mux *http.ServeMux) (Stats, error) { } handler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) - mux.Handle("/prometheus", handler) + mux.Handle("/", handler) return instance, nil } diff --git a/stats/stats_statsd.go b/stats/stats_statsd.go index a975b10..d5e5d95 100644 --- a/stats/stats_statsd.go +++ b/stats/stats_statsd.go @@ -3,6 +3,7 @@ package stats import ( "fmt" "net" + "strconv" "strings" "gopkg.in/alexcesaro/statsd.v2" @@ -32,19 +33,41 @@ func (s *statsStatsd) ClientDisconnected(connectionType conntypes.ConnectionType } func (s *statsStatsd) changeConnections(connectionType conntypes.ConnectionType, addr *net.TCPAddr, value int) { - var labels [3]string + labels := [...]string{ + "connections", + "intermediate", + "ipv4", + } - labels[0] = "connections" switch connectionType { case conntypes.ConnectionTypeAbridged: labels[1] = "abridged" case conntypes.ConnectionTypeSecure: labels[1] = "secured" - default: - labels[1] = "intermediate" } - labels[2] = "ipv4" + if addr.IP.To4() == nil { + labels[2] = "ipv6" + } + + s.client.Count(strings.Join(labels[:], "."), value) +} + +func (s *statsStatsd) TelegramConnected(dc conntypes.DC, addr *net.TCPAddr) { + s.changeTelegramConnections(dc, addr, 1) +} + +func (s *statsStatsd) TelegramDisconnected(dc conntypes.DC, addr *net.TCPAddr) { + s.changeTelegramConnections(dc, addr, -1) +} + +func (s *statsStatsd) changeTelegramConnections(dc conntypes.DC, addr *net.TCPAddr, value int) { + labels := [...]string{ + "telegram", + strconv.Itoa(int(dc)), + "ipv4", + } + if addr.IP.To4() == nil { labels[2] = "ipv6" } @@ -60,17 +83,17 @@ func (s *statsStatsd) AntiReplayDetected() { s.client.Increment("anti_replays") } -func newStatsStatsd() (Stats, error) { +func newStatsStatsd() (Interface, error) { options := []statsd.Option{ - statsd.Prefix(config.C.StatsdStats.Prefix), - statsd.Network(config.C.StatsdStats.Addr.Network()), - statsd.Address(config.C.StatsdStats.Addr.String()), - statsd.TagsFormat(config.C.StatsdStats.TagsFormat), + statsd.Prefix(config.C.StatsNamespace), + statsd.Network(config.C.StatsdNetwork), + statsd.Address(config.C.StatsBind.String()), + statsd.TagsFormat(config.C.StatsdTagsFormat), } - if len(config.C.StatsdStats.Tags) > 0 { - tags := make([]string, len(config.C.StatsdStats.Tags)*2) - for k, v := range config.C.StatsdStats.Tags { + if len(config.C.StatsdTags) > 0 { + tags := make([]string, len(config.C.StatsdTags)*2) + for k, v := range config.C.StatsdTags { tags = append(tags, k, v) } options = append(options, statsd.Tags(tags...)) diff --git a/telegram/base.go b/telegram/base.go index bee996a..66ae08c 100644 --- a/telegram/base.go +++ b/telegram/base.go @@ -47,7 +47,7 @@ func (b *baseTelegram) dial(dc conntypes.DC, return nil, fmt.Errorf("cannot initialize tcp socket: %w", err) } - return wrappers.NewTelegramConn(conn), nil + return wrappers.NewTelegramConn(dc, conn), nil } func (b *baseTelegram) chooseAddress(addresses map[conntypes.DC][]string, diff --git a/utils/init_tcp.go b/utils/init_tcp.go index 3fe7055..6f0ea2d 100644 --- a/utils/init_tcp.go +++ b/utils/init_tcp.go @@ -13,10 +13,10 @@ func InitTCP(conn net.Conn) error { if err := tcpConn.SetNoDelay(true); err != nil { return fmt.Errorf("cannot set TCP_NO_DELAY: %w", err) } - if err := tcpConn.SetReadBuffer(config.C.BufferSize.Read); err != nil { + if err := tcpConn.SetReadBuffer(config.C.ReadBuffer); err != nil { return fmt.Errorf("cannot set read buffer size: %w", err) } - if err := tcpConn.SetWriteBuffer(config.C.BufferSize.Write); err != nil { + if err := tcpConn.SetWriteBuffer(config.C.WriteBuffer); err != nil { return fmt.Errorf("cannot set write buffer size: %w", err) } diff --git a/wrappers/common.go b/wrappers/common.go new file mode 100644 index 0000000..edf9ebe --- /dev/null +++ b/wrappers/common.go @@ -0,0 +1,21 @@ +package wrappers + +import ( + "net" + + "github.com/9seconds/mtg/conntypes" +) + +func NewClientConn(parent net.Conn, connID conntypes.ConnID) conntypes.StreamReadWriteCloser { + conn := newConn(parent, connID, connPurposeClient) + conn = NewTrafficStats(conn) + + return conn +} + +func NewTelegramConn(dc conntypes.DC, parent net.Conn) conntypes.StreamReadWriteCloser { + conn := newConn(parent, conntypes.ConnID{}, connPurposeTelegram) + conn = NewTelegramStats(dc, conn) + + return conn +} diff --git a/wrappers/conn.go b/wrappers/conn.go index 2d2f982..5af5784 100644 --- a/wrappers/conn.go +++ b/wrappers/conn.go @@ -91,11 +91,11 @@ func newConn(parent net.Conn, localAddr := *parent.LocalAddr().(*net.TCPAddr) if parent.RemoteAddr().(*net.TCPAddr).IP.To4() != nil { - if config.C.PublicIPv4Addr.IP != nil { - localAddr.IP = config.C.PublicIPv4Addr.IP + if config.C.PublicIPv4.IP != nil { + localAddr.IP = config.C.PublicIPv4.IP } - } else if config.C.PublicIPv6Addr.IP != nil { - localAddr.IP = config.C.PublicIPv6Addr.IP + } else if config.C.PublicIPv6.IP != nil { + localAddr.IP = config.C.PublicIPv6.IP } logger := zap.S().With( @@ -115,12 +115,3 @@ func newConn(parent net.Conn, localAddr: &localAddr, } } - -func NewClientConn(parent net.Conn, - connID conntypes.ConnID) conntypes.StreamReadWriteCloser { - return newConn(parent, connID, connPurposeClient) -} - -func NewTelegramConn(parent net.Conn) conntypes.StreamReadWriteCloser { - return newConn(parent, conntypes.ConnID{}, connPurposeTelegram) -} diff --git a/wrappers/stats.go b/wrappers/stats.go deleted file mode 100644 index 33f57be..0000000 --- a/wrappers/stats.go +++ /dev/null @@ -1,67 +0,0 @@ -package wrappers - -import ( - "net" - "time" - - "go.uber.org/zap" - - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/stats" -) - -type wrapperStats struct { - parent conntypes.StreamReadWriteCloser -} - -func (w *wrapperStats) Write(p []byte) (int, error) { - n, err := w.parent.Write(p) - stats.S.EgressTraffic(n) - - return n, err -} - -func (w *wrapperStats) WriteTimeout(p []byte, timeout time.Duration) (int, error) { - n, err := w.parent.WriteTimeout(p, timeout) - stats.S.EgressTraffic(n) - - return n, err -} - -func (w *wrapperStats) Read(p []byte) (int, error) { - n, err := w.parent.Read(p) - stats.S.IngressTraffic(n) - - return n, err -} - -func (w *wrapperStats) ReadTimeout(p []byte, timeout time.Duration) (int, error) { - n, err := w.parent.ReadTimeout(p, timeout) - stats.S.IngressTraffic(n) - - return n, err -} - -func (w *wrapperStats) Conn() net.Conn { - return w.parent.Conn() -} - -func (w *wrapperStats) Logger() *zap.SugaredLogger { - return w.parent.Logger().Named("traffic") -} - -func (w *wrapperStats) LocalAddr() *net.TCPAddr { - return w.parent.LocalAddr() -} - -func (w *wrapperStats) RemoteAddr() *net.TCPAddr { - return w.parent.RemoteAddr() -} - -func (w *wrapperStats) Close() error { - return w.parent.Close() -} - -func NewTraffic(parent conntypes.StreamReadWriteCloser) conntypes.StreamReadWriteCloser { - return &wrapperStats{parent} -} diff --git a/wrappers/stats_telegram.go b/wrappers/stats_telegram.go new file mode 100644 index 0000000..b613b01 --- /dev/null +++ b/wrappers/stats_telegram.go @@ -0,0 +1,70 @@ +package wrappers + +import ( + "net" + "sync" + "time" + + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/stats" + "go.uber.org/zap" +) + +type wrapperTelegramStats struct { + parent conntypes.StreamReadWriteCloser + dc conntypes.DC + once sync.Once +} + +func (w *wrapperTelegramStats) Write(p []byte) (int, error) { + return w.parent.Write(p) +} + +func (w *wrapperTelegramStats) WriteTimeout(p []byte, timeout time.Duration) (int, error) { + return w.parent.WriteTimeout(p, timeout) +} + +func (w *wrapperTelegramStats) Read(p []byte) (int, error) { + return w.parent.Read(p) +} + +func (w *wrapperTelegramStats) ReadTimeout(p []byte, timeout time.Duration) (int, error) { + return w.parent.ReadTimeout(p, timeout) +} + +func (w *wrapperTelegramStats) Conn() net.Conn { + return w.parent.Conn() +} + +func (w *wrapperTelegramStats) Logger() *zap.SugaredLogger { + return w.parent.Logger().Named("stats-telegram") +} + +func (w *wrapperTelegramStats) LocalAddr() *net.TCPAddr { + return w.parent.LocalAddr() +} + +func (w *wrapperTelegramStats) RemoteAddr() *net.TCPAddr { + return w.parent.RemoteAddr() +} + +func (w *wrapperTelegramStats) Close() error { + var err error + + w.once.Do(func() { + err = w.parent.Close() + stats.Stats.TelegramDisconnected(w.dc, w.RemoteAddr()) + }) + + return err +} + +func NewTelegramStats(dc conntypes.DC, parent conntypes.StreamReadWriteCloser) conntypes.StreamReadWriteCloser { + conn := &wrapperTelegramStats{ + parent: parent, + dc: dc, + } + stats.Stats.TelegramConnected(dc, parent.RemoteAddr()) + + return conn +} diff --git a/wrappers/stats_traffic.go b/wrappers/stats_traffic.go new file mode 100644 index 0000000..c66f9fa --- /dev/null +++ b/wrappers/stats_traffic.go @@ -0,0 +1,67 @@ +package wrappers + +import ( + "net" + "time" + + "go.uber.org/zap" + + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/stats" +) + +type wrapperTrafficStats struct { + parent conntypes.StreamReadWriteCloser +} + +func (w *wrapperTrafficStats) Write(p []byte) (int, error) { + n, err := w.parent.Write(p) + stats.Stats.EgressTraffic(n) + + return n, err +} + +func (w *wrapperTrafficStats) WriteTimeout(p []byte, timeout time.Duration) (int, error) { + n, err := w.parent.WriteTimeout(p, timeout) + stats.Stats.EgressTraffic(n) + + return n, err +} + +func (w *wrapperTrafficStats) Read(p []byte) (int, error) { + n, err := w.parent.Read(p) + stats.Stats.IngressTraffic(n) + + return n, err +} + +func (w *wrapperTrafficStats) ReadTimeout(p []byte, timeout time.Duration) (int, error) { + n, err := w.parent.ReadTimeout(p, timeout) + stats.Stats.IngressTraffic(n) + + return n, err +} + +func (w *wrapperTrafficStats) Conn() net.Conn { + return w.parent.Conn() +} + +func (w *wrapperTrafficStats) Logger() *zap.SugaredLogger { + return w.parent.Logger().Named("stats-traffic") +} + +func (w *wrapperTrafficStats) LocalAddr() *net.TCPAddr { + return w.parent.LocalAddr() +} + +func (w *wrapperTrafficStats) RemoteAddr() *net.TCPAddr { + return w.parent.RemoteAddr() +} + +func (w *wrapperTrafficStats) Close() error { + return w.parent.Close() +} + +func NewTrafficStats(parent conntypes.StreamReadWriteCloser) conntypes.StreamReadWriteCloser { + return &wrapperTrafficStats{parent} +} From 3ae69183aa823a65f32e6e0dc7640caa4bb1662d Mon Sep 17 00:00:00 2001 From: 9seconds Date: Tue, 8 Oct 2019 14:08:34 +0300 Subject: [PATCH 22/62] Some refactorings for telegram package --- cli/proxy.go | 2 +- telegram/base.go | 3 --- telegram/direct.go | 16 +--------------- telegram/init.go | 42 ++++++++++++++++++++++++++++++++++++++++++ telegram/interfaces.go | 2 +- telegram/middle.go | 17 ----------------- 6 files changed, 45 insertions(+), 37 deletions(-) create mode 100644 telegram/init.go diff --git a/cli/proxy.go b/cli/proxy.go index 2017e8f..54a0d62 100644 --- a/cli/proxy.go +++ b/cli/proxy.go @@ -65,7 +65,7 @@ func Proxy() error { if err := stats.Init(ctx); err != nil { Fatal(err) } - telegram.MiddleInit() + telegram.Init() proxyListener, err := net.Listen("tcp", config.C.Bind.String()) if err != nil { diff --git a/telegram/base.go b/telegram/base.go index 66ae08c..276b36b 100644 --- a/telegram/base.go +++ b/telegram/base.go @@ -4,15 +4,12 @@ import ( "fmt" "math/rand" "net" - "time" "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/utils" "github.com/9seconds/mtg/wrappers" ) -const telegramDialTimeout = 10 * time.Second - type baseTelegram struct { dialer net.Dialer diff --git a/telegram/direct.go b/telegram/direct.go index b69075e..3810c9a 100644 --- a/telegram/direct.go +++ b/telegram/direct.go @@ -1,10 +1,6 @@ package telegram -import ( - "net" - - "github.com/9seconds/mtg/conntypes" -) +import "github.com/9seconds/mtg/conntypes" const ( directV4DefaultIdx conntypes.DC = 1 @@ -43,13 +39,3 @@ func (d *directTelegram) Dial(dc conntypes.DC, return d.baseTelegram.dial(dc-1, protocol) } - -var Direct = &directTelegram{ - baseTelegram: baseTelegram{ - dialer: net.Dialer{Timeout: telegramDialTimeout}, - v4DefaultDC: directV4DefaultIdx, - V6DefaultDC: directV6DefaultIdx, - v4Addresses: directV4Addresses, - v6Addresses: directV6Addresses, - }, -} diff --git a/telegram/init.go b/telegram/init.go new file mode 100644 index 0000000..b4a0c19 --- /dev/null +++ b/telegram/init.go @@ -0,0 +1,42 @@ +package telegram + +import ( + "net" + "sync" + "time" +) + +const telegramDialTimeout = 10 * time.Second + +var ( + Direct Telegram + Middle Telegram + + initOnce sync.Once +) + +func Init() { + initOnce.Do(func() { + Direct = &directTelegram{ + baseTelegram: baseTelegram{ + dialer: net.Dialer{Timeout: telegramDialTimeout}, + v4DefaultDC: directV4DefaultIdx, + V6DefaultDC: directV6DefaultIdx, + v4Addresses: directV4Addresses, + v6Addresses: directV6Addresses, + }, + } + + tg := &middleTelegram{ + baseTelegram: baseTelegram{ + dialer: net.Dialer{Timeout: telegramDialTimeout}, + }, + } + if err := tg.update(); err != nil { + panic(err) + } + go tg.backgroundUpdate() + + Middle = tg + }) +} diff --git a/telegram/interfaces.go b/telegram/interfaces.go index c780a62..e419b8b 100644 --- a/telegram/interfaces.go +++ b/telegram/interfaces.go @@ -1,6 +1,6 @@ package telegram -import "github.com/9seconds/mtg/conntypes" +import "github.com/9seconds/mtg/conntypes" type Telegram interface { Dial(conntypes.DC, conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error) diff --git a/telegram/middle.go b/telegram/middle.go index 6dc0279..88278b1 100644 --- a/telegram/middle.go +++ b/telegram/middle.go @@ -2,7 +2,6 @@ package telegram import ( "fmt" - "net" "sync" "time" @@ -14,8 +13,6 @@ import ( const middleTelegramBackgroundUpdateEvery = time.Hour -var Middle Telegram - type middleTelegram struct { baseTelegram @@ -76,17 +73,3 @@ func (m *middleTelegram) Dial(dc conntypes.DC, return m.baseTelegram.dial(dc, protocol) } - -func MiddleInit() { - tg := &middleTelegram{ - baseTelegram: baseTelegram{ - dialer: net.Dialer{Timeout: telegramDialTimeout}, - }, - } - if err := tg.update(); err != nil { - panic(err) - } - go tg.backgroundUpdate() - - Middle = tg -} From d9bd07b027f46a8d8e87611f8fd7ed70fb26811c Mon Sep 17 00:00:00 2001 From: 9seconds Date: Tue, 8 Oct 2019 14:09:48 +0300 Subject: [PATCH 23/62] disable gosec in hub connection --- hub/connection.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hub/connection.go b/hub/connection.go index 751fc3c..ba317fb 100644 --- a/hub/connection.go +++ b/hub/connection.go @@ -95,7 +95,7 @@ func newConnection(req *protocol.TelegramRequest, hub *connectionHub) (*connecti rv := &connection{ conn: conn, hub: hub, - id: rand.Int(), + id: rand.Int(), // nolint: gosec } go rv.run() From 1a7eee444e7fe5ca6ddf0efd9f30c57e97003bbc Mon Sep 17 00:00:00 2001 From: 9seconds Date: Wed, 9 Oct 2019 11:08:34 +0300 Subject: [PATCH 24/62] Use ReadFull in blockcipher --- utils/read_full.go | 20 ++++++++++++ wrappers/blockcipher.go | 70 +++++++---------------------------------- 2 files changed, 32 insertions(+), 58 deletions(-) create mode 100644 utils/read_full.go diff --git a/utils/read_full.go b/utils/read_full.go new file mode 100644 index 0000000..afb2554 --- /dev/null +++ b/utils/read_full.go @@ -0,0 +1,20 @@ +package utils + +import "io" + +const readFullBufferSize = 1024 + 1 // +1 because telegram opreates with blocks mod 4 + +func ReadFull(src io.Reader) (rv []byte, err error) { + buf := make([]byte, readFullBufferSize) + n := readFullBufferSize + + for n == len(buf) { + n, err = src.Read(buf) + if err != nil { + return nil, err + } + rv = append(rv, buf[:n]...) + } + + return rv, nil +} diff --git a/wrappers/blockcipher.go b/wrappers/blockcipher.go index 12c015f..0ae2cb3 100644 --- a/wrappers/blockcipher.go +++ b/wrappers/blockcipher.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/aes" "crypto/cipher" - "errors" "fmt" "net" "time" @@ -12,10 +11,9 @@ import ( "go.uber.org/zap" "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/utils" ) -const blockCipherReadCurrentDataBufferSize = 1024 + 1 // +1 because telegram operates with blocks mod 4 - type wrapperBlockCipher struct { buf bytes.Buffer @@ -41,35 +39,29 @@ func (w *wrapperBlockCipher) WriteTimeout(p []byte, timeout time.Duration) (int, } func (w *wrapperBlockCipher) Read(p []byte) (int, error) { - return w.read(p, readAll) - -} - -func (w *wrapperBlockCipher) ReadTimeout(p []byte, timeout time.Duration) (int, error) { - return w.read(p, readAllTimeout(timeout)) -} - -func (w *wrapperBlockCipher) read(p []byte, - reader func(conntypes.StreamReadWriteCloser) ([]byte, error)) (int, error) { if w.buf.Len() > 0 { return w.flush(p) } - var buf []byte - for len(buf) == 0 || len(buf)%aes.BlockSize != 0 { - rv, err := reader(w.parent) + var currentBuffer []byte + for len(currentBuffer) == 0 || len(currentBuffer)%aes.BlockSize != 0 { + rv, err := utils.ReadFull(w.parent) if err != nil { - return 0, fmt.Errorf("cannot read from socket: %w", err) + return 0, fmt.Errorf("cannot read data: %w", err) } - buf = append(buf, rv...) + currentBuffer = append(currentBuffer, rv...) } - w.decryptor.CryptBlocks(buf, buf) - w.buf.Write(buf) + w.decryptor.CryptBlocks(currentBuffer, currentBuffer) + w.buf.Write(currentBuffer) return w.flush(p) } +func (w *wrapperBlockCipher) ReadTimeout(p []byte, timeout time.Duration) (int, error) { + return w.Read(p) +} + func (w *wrapperBlockCipher) flush(p []byte) (int, error) { if w.buf.Len() > len(p) { return w.buf.Read(p) @@ -93,44 +85,6 @@ func (w *wrapperBlockCipher) encrypt(p []byte) ([]byte, error) { return encrypted, nil } -func readAll(src conntypes.StreamReadWriteCloser) (rv []byte, err error) { - buf := make([]byte, blockCipherReadCurrentDataBufferSize) - n := blockCipherReadCurrentDataBufferSize - - for n == len(buf) { - n, err = src.Read(buf) - if err != nil { - return nil, err - } - rv = append(rv, buf[:n]...) - } - - return rv, nil -} - -func readAllTimeout(timeout time.Duration) func(conntypes.StreamReadWriteCloser) ([]byte, error) { - return func(src conntypes.StreamReadWriteCloser) (rv []byte, err error) { - tmo := timeout - buf := make([]byte, blockCipherReadCurrentDataBufferSize) - n := blockCipherReadCurrentDataBufferSize - - for n == len(buf) { - if tmo <= 0 { - return nil, errors.New("timeout") - } - startTime := time.Now() - n, err = src.ReadTimeout(buf, tmo) - if err != nil { - return nil, err - } - rv = append(rv, buf[:n]...) - tmo -= time.Since(startTime) - } - - return rv, nil - } -} - func (w *wrapperBlockCipher) Close() error { return w.parent.Close() } From e81c5970d41aea502043a03065ae1ed89c84ace0 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 10 Oct 2019 07:22:08 +0300 Subject: [PATCH 25/62] Add client abridged protocol --- conntypes/acks.go | 6 ++ conntypes/wrap_interfaces.go | 14 +++ conntypes/wrap_packet_ack_interfaces.go | 41 ++++++++ conntypes/wrap_packet_interfaces.go | 51 ++++++++++ conntypes/wrap_stream_interfaces.go | 56 +++++++++++ conntypes/wrappers.go | 113 ---------------------- go.mod | 4 + go.sum | 8 ++ hub/connection.go | 17 +++- hub/connection_hub.go | 6 +- hub/ctx_channel.go | 14 +-- hub/hub.go | 12 ++- hub/init.go | 5 +- mtproto/protocol.go | 9 +- mtproto/rpc/proxy_flags.go | 40 ++++---- mtproto/rpc/proxy_response.go | 51 ++++++++++ obfuscated2/client_protocol.go | 4 +- obfuscated2/telegram_protocol.go | 8 +- proxy/proxy.go | 8 +- telegram/base.go | 4 +- wrappers/{ => packet}/mtproto_frame.go | 2 +- wrappers/packetack/client_abridged.go | 119 ++++++++++++++++++++++++ wrappers/packetack/proxy.go | 99 ++++++++++++++++++++ wrappers/{common.go => stream/base.go} | 2 +- wrappers/{ => stream}/blockcipher.go | 2 +- wrappers/{ => stream}/conn.go | 2 +- wrappers/{ => stream}/ctx.go | 2 +- wrappers/{ => stream}/mtproto_cipher.go | 2 +- wrappers/{ => stream}/obfuscated2.go | 2 +- wrappers/{ => stream}/stats_telegram.go | 5 +- wrappers/{ => stream}/stats_traffic.go | 2 +- wrappers/{ => stream}/timeout.go | 2 +- 32 files changed, 538 insertions(+), 174 deletions(-) create mode 100644 conntypes/acks.go create mode 100644 conntypes/wrap_interfaces.go create mode 100644 conntypes/wrap_packet_ack_interfaces.go create mode 100644 conntypes/wrap_packet_interfaces.go create mode 100644 conntypes/wrap_stream_interfaces.go create mode 100644 mtproto/rpc/proxy_response.go rename wrappers/{ => packet}/mtproto_frame.go (99%) create mode 100644 wrappers/packetack/client_abridged.go create mode 100644 wrappers/packetack/proxy.go rename wrappers/{common.go => stream/base.go} (96%) rename wrappers/{ => stream}/blockcipher.go (99%) rename wrappers/{ => stream}/conn.go (99%) rename wrappers/{ => stream}/ctx.go (99%) rename wrappers/{ => stream}/mtproto_cipher.go (99%) rename wrappers/{ => stream}/obfuscated2.go (99%) rename wrappers/{ => stream}/stats_telegram.go (98%) rename wrappers/{ => stream}/stats_traffic.go (98%) rename wrappers/{ => stream}/timeout.go (98%) diff --git a/conntypes/acks.go b/conntypes/acks.go new file mode 100644 index 0000000..d3dbd8d --- /dev/null +++ b/conntypes/acks.go @@ -0,0 +1,6 @@ +package conntypes + +type ConnectionAcks struct { + Simple bool + Quick bool +} diff --git a/conntypes/wrap_interfaces.go b/conntypes/wrap_interfaces.go new file mode 100644 index 0000000..f21eb91 --- /dev/null +++ b/conntypes/wrap_interfaces.go @@ -0,0 +1,14 @@ +package conntypes + +import ( + "net" + + "go.uber.org/zap" +) + +type Wrap interface { + Conn() net.Conn + Logger() *zap.SugaredLogger + LocalAddr() *net.TCPAddr + RemoteAddr() *net.TCPAddr +} diff --git a/conntypes/wrap_packet_ack_interfaces.go b/conntypes/wrap_packet_ack_interfaces.go new file mode 100644 index 0000000..6388f0c --- /dev/null +++ b/conntypes/wrap_packet_ack_interfaces.go @@ -0,0 +1,41 @@ +package conntypes + +import "io" + +type PacketAckReader interface { + Read(*ConnectionAcks) (Packet, error) +} + +type PacketAckWriter interface { + Write(Packet, *ConnectionAcks) error +} + +type PacketAckCloser interface { + io.Closer +} + +type PacketAckReadCloser interface { + PacketAckReader + PacketAckCloser +} + +type PacketAckWriteCloser interface { + PacketAckWriter + PacketAckCloser +} + +type PacketAckReadWriter interface { + PacketAckReader + PacketAckWriter +} + +type PacketAckReadWriteCloser interface { + PacketAckReader + PacketAckWriter + PacketAckCloser +} + +type PacketAckFullReadWriteCloser interface { + Wrap + PacketAckReadWriteCloser +} diff --git a/conntypes/wrap_packet_interfaces.go b/conntypes/wrap_packet_interfaces.go new file mode 100644 index 0000000..6c573d4 --- /dev/null +++ b/conntypes/wrap_packet_interfaces.go @@ -0,0 +1,51 @@ +package conntypes + +import "io" + +type BasePacketReader interface { + Read() (Packet, error) +} + +type BasePacketWriter interface { + Write(Packet) error +} + +type PacketReader interface { + Wrap + BasePacketReader +} + +type PacketWriter interface { + Wrap + BasePacketWriter +} + +type PacketCloser interface { + Wrap + io.Closer +} + +type PacketReadCloser interface { + Wrap + BasePacketReader + io.Closer +} + +type PacketWriteCloser interface { + Wrap + BasePacketWriter + io.Closer +} + +type PacketReadWriter interface { + Wrap + BasePacketWriter + BasePacketReader +} + +type PacketReadWriteCloser interface { + Wrap + BasePacketWriter + BasePacketReader + io.Closer +} diff --git a/conntypes/wrap_stream_interfaces.go b/conntypes/wrap_stream_interfaces.go new file mode 100644 index 0000000..5924d66 --- /dev/null +++ b/conntypes/wrap_stream_interfaces.go @@ -0,0 +1,56 @@ +package conntypes + +import ( + "io" + "time" +) + +type BaseStreamReaderWithTimeout interface { + ReadTimeout([]byte, time.Duration) (int, error) +} + +type BaseStreamWriterWithTimeout interface { + WriteTimeout([]byte, time.Duration) (int, error) +} + +type StreamReader interface { + Wrap + io.Reader + BaseStreamReaderWithTimeout +} + +type StreamWriter interface { + Wrap + io.Writer + BaseStreamWriterWithTimeout +} + +type StreamCloser interface { + Wrap + io.Closer +} + +type StreamReadCloser interface { + Wrap + io.ReadCloser + BaseStreamReaderWithTimeout +} + +type StreamWriteCloser interface { + Wrap + io.WriteCloser + BaseStreamWriterWithTimeout +} + +type StreamReadWriter interface { + Wrap + io.ReadWriter + BaseStreamReaderWithTimeout +} + +type StreamReadWriteCloser interface { + Wrap + io.ReadWriteCloser + BaseStreamReaderWithTimeout + BaseStreamWriterWithTimeout +} diff --git a/conntypes/wrappers.go b/conntypes/wrappers.go index b26e245..a086871 100644 --- a/conntypes/wrappers.go +++ b/conntypes/wrappers.go @@ -1,115 +1,2 @@ package conntypes -import ( - "io" - "net" - "time" - - "go.uber.org/zap" -) - -// Wrap is a base interface for all wrappers in this package. -type Wrap interface { - Conn() net.Conn - Logger() *zap.SugaredLogger - LocalAddr() *net.TCPAddr - RemoteAddr() *net.TCPAddr -} - -type BaseReaderWithTimeout interface { - ReadTimeout([]byte, time.Duration) (int, error) -} - -type BaseWriterWithTimeout interface { - WriteTimeout([]byte, time.Duration) (int, error) -} - -type BasePacketReader interface { - Read() (Packet, error) -} - -type BasePacketWriter interface { - Write(Packet) error -} - -type StreamReader interface { - Wrap - io.Reader - BaseReaderWithTimeout -} - -type StreamWriter interface { - Wrap - io.Writer - BaseWriterWithTimeout -} - -type StreamCloser interface { - Wrap - io.Closer -} - -type StreamReadCloser interface { - Wrap - io.ReadCloser - BaseReaderWithTimeout -} - -type StreamWriteCloser interface { - Wrap - io.WriteCloser - BaseWriterWithTimeout -} - -type StreamReadWriter interface { - Wrap - io.ReadWriter - BaseReaderWithTimeout -} - -type StreamReadWriteCloser interface { - Wrap - io.ReadWriteCloser - BaseReaderWithTimeout - BaseWriterWithTimeout -} - -type PacketReader interface { - Wrap - BasePacketReader -} - -type PacketWriter interface { - Wrap - BasePacketWriter -} - -type PacketCloser interface { - Wrap - io.Closer -} - -type PacketReadCloser interface { - Wrap - BasePacketReader - io.Closer -} - -type PacketWriteCloser interface { - Wrap - BasePacketWriter - io.Closer -} - -type PacketReadWriter interface { - Wrap - BasePacketWriter - BasePacketReader -} - -type PacketReadWriteCloser interface { - Wrap - BasePacketWriter - BasePacketReader - io.Closer -} diff --git a/go.mod b/go.mod index 2179870..b7f80f5 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,10 @@ require ( github.com/cespare/xxhash v1.1.0 github.com/dustin/go-humanize v1.0.0 github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622 + github.com/gofrs/uuid v3.2.0+incompatible github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d + github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect + github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b // indirect github.com/kr/pretty v0.1.0 // indirect github.com/pkg/errors v0.8.1 github.com/prometheus/client_golang v1.1.0 @@ -26,6 +29,7 @@ require ( gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/alexcesaro/statsd.v2 v2.0.0 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect ) go 1.13 diff --git a/go.sum b/go.sum index 94907f7..2e2a856 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= @@ -46,6 +48,10 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc= github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI= +github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b h1:Rrp0ByJXEjhREMPGTt3aWYjoIsUGCbt21ekbeJcTWv0= +github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -128,6 +134,8 @@ gopkg.in/alexcesaro/statsd.v2 v2.0.0/go.mod h1:i0ubccKGzBVNBpdGV5MocxyA/XlLUJzA7 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/hub/connection.go b/hub/connection.go index ba317fb..4d2ba5f 100644 --- a/hub/connection.go +++ b/hub/connection.go @@ -7,6 +7,7 @@ import ( "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/mtproto" + "github.com/9seconds/mtg/mtproto/rpc" "github.com/9seconds/mtg/protocol" ) @@ -73,6 +74,8 @@ func (c *connection) idle() bool { } func (c *connection) run() { + logger := c.hub.logger.Named("connection").With("id", c.id) + for { packet, err := c.read() if err != nil { @@ -80,8 +83,18 @@ func (c *connection) run() { return } - if channel, ok := Registry.getChannel(conntypes.ConnID{}); ok { - go channel.write(packet) // nolint: errcheck + response, err := rpc.ParseProxyResponse(packet) + if err != nil { + logger.Debugw("Failed response", "error", err) + continue + } + if response.Type == rpc.ProxyResponseTypeCloseExt { + logger.Debugw("Proxy has closed connection") + return + } + + if channel, ok := Registry.getChannel(response.ConnID); ok { + go channel.sendBack(response) // nolint: errcheck } } } diff --git a/hub/connection_hub.go b/hub/connection_hub.go index e4aa037..f39508d 100644 --- a/hub/connection_hub.go +++ b/hub/connection_hub.go @@ -3,6 +3,8 @@ package hub import ( "time" + "go.uber.org/zap" + "github.com/9seconds/mtg/protocol" ) @@ -15,6 +17,7 @@ type connectionHubRequest struct { type connectionHub struct { sockets map[int]*connection + logger *zap.SugaredLogger channelBrokenSockets chan int channelConnectionRequests chan *connectionHubRequest @@ -76,8 +79,9 @@ func (c *connectionHub) runReturnConnection(conn *connection) { c.sockets[conn.id] = conn } -func newConnectionHub() *connectionHub { +func newConnectionHub(logger *zap.SugaredLogger) *connectionHub { rv := &connectionHub{ + logger: logger.Named("connection-hub"), sockets: map[int]*connection{}, channelBrokenSockets: make(chan int, 1), channelConnectionRequests: make(chan *connectionHubRequest), diff --git a/hub/ctx_channel.go b/hub/ctx_channel.go index bc28cb2..0f599e4 100644 --- a/hub/ctx_channel.go +++ b/hub/ctx_channel.go @@ -4,23 +4,23 @@ import ( "context" "time" - "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/mtproto/rpc" ) const closeableChannelReadTimeout = 2 * time.Minute type ChannelReadCloser interface { - Read() (conntypes.Packet, error) + Read() (*rpc.ProxyResponse, error) Close() error } type ctxChannel struct { - channel chan conntypes.Packet + channel chan *rpc.ProxyResponse ctx context.Context cancel context.CancelFunc } -func (c *ctxChannel) Read() (conntypes.Packet, error) { +func (c *ctxChannel) Read() (*rpc.ProxyResponse, error) { timer := time.NewTimer(closeableChannelReadTimeout) defer timer.Stop() @@ -34,11 +34,11 @@ func (c *ctxChannel) Read() (conntypes.Packet, error) { } } -func (c *ctxChannel) write(packet conntypes.Packet) error { +func (c *ctxChannel) sendBack(response *rpc.ProxyResponse) error { select { case <-c.ctx.Done(): return ErrClosed - case c.channel <- packet: + case c.channel <- response: return nil } } @@ -52,7 +52,7 @@ func (c *ctxChannel) Close() error { func newCtxChannel(ctx context.Context) *ctxChannel { ctx, cancel := context.WithCancel(ctx) return &ctxChannel{ - channel: make(chan conntypes.Packet), + channel: make(chan *rpc.ProxyResponse), ctx: ctx, cancel: cancel, } diff --git a/hub/hub.go b/hub/hub.go index 1e6cd5d..6e76b4d 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -6,13 +6,16 @@ import ( "strings" "sync" + "go.uber.org/zap" + "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/protocol" ) type hub struct { - subs map[string]*connectionHub - mutex sync.RWMutex + logger *zap.SugaredLogger + subs map[string]*connectionHub + mutex sync.RWMutex } func (h *hub) Write(packet conntypes.Packet, req *protocol.TelegramRequest) error { @@ -51,7 +54,10 @@ func (h *hub) getHub(req *protocol.TelegramRequest) *connectionHub { rv, ok = h.subs[key] if !ok { - rv = newConnectionHub() + rv = newConnectionHub(h.logger.With( + "dc", req.ClientProtocol.DC(), + "protocol", req.ClientProtocol.ConnectionProtocol(), + )) h.subs[key] = rv } } diff --git a/hub/init.go b/hub/init.go index 38d1a82..35a0fee 100644 --- a/hub/init.go +++ b/hub/init.go @@ -4,6 +4,8 @@ import ( "context" "errors" "sync" + + "go.uber.org/zap" ) var ( @@ -24,7 +26,8 @@ func Init(ctx context.Context) { ctx: ctx, } Hub = &hub{ - subs: map[string]*connectionHub{}, + subs: map[string]*connectionHub{}, + logger: zap.S().Named("hub"), } }) } diff --git a/mtproto/protocol.go b/mtproto/protocol.go index d6a188d..f90d784 100644 --- a/mtproto/protocol.go +++ b/mtproto/protocol.go @@ -7,7 +7,8 @@ import ( "github.com/9seconds/mtg/mtproto/rpc" "github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/telegram" - "github.com/9seconds/mtg/wrappers" + "github.com/9seconds/mtg/wrappers/packet" + "github.com/9seconds/mtg/wrappers/stream" ) func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.PacketReadWriteCloser, error) { @@ -17,7 +18,7 @@ func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.PacketReadWriteC return nil, fmt.Errorf("cannot connect to telegram: %w", err) } - rpcNonceConn := wrappers.NewMtprotoFrame(conn, rpc.SeqNoNonce) + rpcNonceConn := packet.NewMtprotoFrame(conn, rpc.SeqNoNonce) rpcNonceReq, err := doRPCNonceRequest(rpcNonceConn) if err != nil { return nil, fmt.Errorf("cannot do nonce request: %w", err) @@ -28,8 +29,8 @@ func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.PacketReadWriteC return nil, fmt.Errorf("cannot get nonce response: %w", err) } - secureConn := wrappers.NewMiddleProxyCipher(conn, rpcNonceReq, rpcNonceResp, telegram.Middle.Secret()) - frameConn := wrappers.NewMtprotoFrame(secureConn, rpc.SeqNoHandshake) + secureConn := stream.NewMiddleProxyCipher(conn, rpcNonceReq, rpcNonceResp, telegram.Middle.Secret()) + frameConn := packet.NewMtprotoFrame(secureConn, rpc.SeqNoHandshake) if err := doRPCHandshakeRequest(frameConn); err != nil { return nil, fmt.Errorf("cannot do handshake request: %w", err) diff --git a/mtproto/rpc/proxy_flags.go b/mtproto/rpc/proxy_flags.go index c59d33f..c0e87fa 100644 --- a/mtproto/rpc/proxy_flags.go +++ b/mtproto/rpc/proxy_flags.go @@ -5,53 +5,53 @@ import ( "strings" ) -type proxyRequestFlags uint32 +type ProxyRequestFlags uint32 const ( - proxyRequestFlagsHasAdTag proxyRequestFlags = 0x8 - proxyRequestFlagsEncrypted proxyRequestFlags = 0x2 - proxyRequestFlagsMagic proxyRequestFlags = 0x1000 - proxyRequestFlagsExtMode2 proxyRequestFlags = 0x20000 - proxyRequestFlagsIntermediate proxyRequestFlags = 0x20000000 - proxyRequestFlagsAbdridged proxyRequestFlags = 0x40000000 - proxyRequestFlagsQuickAck proxyRequestFlags = 0x80000000 - proxyRequestFlagsPad proxyRequestFlags = 0x8000000 + ProxyRequestFlagsHasAdTag ProxyRequestFlags = 0x8 + ProxyRequestFlagsEncrypted ProxyRequestFlags = 0x2 + ProxyRequestFlagsMagic ProxyRequestFlags = 0x1000 + ProxyRequestFlagsExtMode2 ProxyRequestFlags = 0x20000 + ProxyRequestFlagsIntermediate ProxyRequestFlags = 0x20000000 + ProxyRequestFlagsAbdridged ProxyRequestFlags = 0x40000000 + ProxyRequestFlagsQuickAck ProxyRequestFlags = 0x80000000 + ProxyRequestFlagsPad ProxyRequestFlags = 0x8000000 ) -var proxyRequestFlagsEncryptedPrefix [8]byte +var ProxyRequestFlagsEncryptedPrefix [8]byte -func (r proxyRequestFlags) Bytes() []byte { +func (r ProxyRequestFlags) Bytes() []byte { converted := make([]byte, 4) binary.LittleEndian.PutUint32(converted, uint32(r)) return converted } -func (r proxyRequestFlags) String() string { +func (r ProxyRequestFlags) String() string { flags := make([]string, 0, 7) - if r&proxyRequestFlagsHasAdTag != 0 { + if r&ProxyRequestFlagsHasAdTag != 0 { flags = append(flags, "HAS_AD_TAG") } - if r&proxyRequestFlagsEncrypted != 0 { + if r&ProxyRequestFlagsEncrypted != 0 { flags = append(flags, "ENCRYPTED") } - if r&proxyRequestFlagsMagic != 0 { + if r&ProxyRequestFlagsMagic != 0 { flags = append(flags, "MAGIC") } - if r&proxyRequestFlagsExtMode2 != 0 { + if r&ProxyRequestFlagsExtMode2 != 0 { flags = append(flags, "EXT_MODE_2") } - if r&proxyRequestFlagsIntermediate != 0 { + if r&ProxyRequestFlagsIntermediate != 0 { flags = append(flags, "INTERMEDIATE") } - if r&proxyRequestFlagsAbdridged != 0 { + if r&ProxyRequestFlagsAbdridged != 0 { flags = append(flags, "ABRIDGED") } - if r&proxyRequestFlagsQuickAck != 0 { + if r&ProxyRequestFlagsQuickAck != 0 { flags = append(flags, "QUICK_ACK") } - if r&proxyRequestFlagsPad != 0 { + if r&ProxyRequestFlagsPad != 0 { flags = append(flags, "PAD") } diff --git a/mtproto/rpc/proxy_response.go b/mtproto/rpc/proxy_response.go new file mode 100644 index 0000000..2b91810 --- /dev/null +++ b/mtproto/rpc/proxy_response.go @@ -0,0 +1,51 @@ +package rpc + +import ( + "bytes" + "fmt" + + "github.com/9seconds/mtg/conntypes" +) + +type ProxyResponseType uint8 + +const ( + ProxyResponseTypeAns ProxyResponseType = iota + ProxyResponseTypeSimpleAck + ProxyResponseTypeCloseExt +) + +type ProxyResponse struct { + Type ProxyResponseType + ConnID conntypes.ConnID + Payload conntypes.Packet +} + +func ParseProxyResponse(packet conntypes.Packet) (*ProxyResponse, error) { + var response ProxyResponse + + if len(packet) < 4 { + return nil, fmt.Errorf("incorrect packet length: %d", len(packet)) + } + + tag := packet[:4] + switch { + case bytes.Equal(tag, TagProxyAns): + response.Type = ProxyResponseTypeAns + copy(response.ConnID[:], packet[8:16]) + response.Payload = packet[16:] + return &response, nil + + case bytes.Equal(tag, TagSimpleAck): + response.Type = ProxyResponseTypeSimpleAck + copy(response.ConnID[:], packet[4:12]) + response.Payload = packet[12:] + return &response, nil + + case bytes.Equal(tag, TagCloseExt): + response.Type = ProxyResponseTypeCloseExt + return &response, nil + } + + return nil, fmt.Errorf("unknown response type %x", tag) +} diff --git a/obfuscated2/client_protocol.go b/obfuscated2/client_protocol.go index 38a27f3..3dd00ec 100644 --- a/obfuscated2/client_protocol.go +++ b/obfuscated2/client_protocol.go @@ -14,7 +14,7 @@ import ( "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/utils" - "github.com/9seconds/mtg/wrappers" + "github.com/9seconds/mtg/wrappers/stream" ) const clientProtocolHandshakeTimeout = 10 * time.Second @@ -85,7 +85,7 @@ func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conn } antireplay.Cache.Add(antiReplayKey) - return wrappers.NewObfuscated2(socket, encryptor, decryptor), nil + return stream.NewObfuscated2(socket, encryptor, decryptor), nil } func (c *ClientProtocol) ReadFrame(socket conntypes.StreamReader) (fm Frame, err error) { diff --git a/obfuscated2/telegram_protocol.go b/obfuscated2/telegram_protocol.go index 441ecd9..9a92acb 100644 --- a/obfuscated2/telegram_protocol.go +++ b/obfuscated2/telegram_protocol.go @@ -8,7 +8,7 @@ import ( "github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/telegram" "github.com/9seconds/mtg/utils" - "github.com/9seconds/mtg/wrappers" + "github.com/9seconds/mtg/wrappers/stream" ) func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.StreamReadWriteCloser, error) { @@ -17,8 +17,8 @@ func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.StreamReadWriteC if err != nil { return nil, fmt.Errorf("cannot dial to telegram: %w", err) } - conn = wrappers.NewTimeout(conn) - conn = wrappers.NewCtx(req.Ctx, req.Cancel, conn) + conn = stream.NewTimeout(conn) + conn = stream.NewCtx(req.Ctx, req.Cancel, conn) fm := generateFrame(req.ClientProtocol) data := fm.Bytes() @@ -35,7 +35,7 @@ func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.StreamReadWriteC return nil, fmt.Errorf("cannot write handshake frame to telegram: %w", err) } - return wrappers.NewObfuscated2(conn, encryptor, decryptor), nil + return stream.NewObfuscated2(conn, encryptor, decryptor), nil } func generateFrame(cp protocol.ClientProtocol) (fm Frame) { diff --git a/proxy/proxy.go b/proxy/proxy.go index 1bca64f..b7d04a3 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -14,7 +14,7 @@ import ( "github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/stats" "github.com/9seconds/mtg/utils" - "github.com/9seconds/mtg/wrappers" + "github.com/9seconds/mtg/wrappers/stream" ) const directPipeBufferSize = 1024 * 1024 @@ -63,9 +63,9 @@ func (p *Proxy) accept(conn net.Conn) { ctx, cancel := context.WithCancel(p.Context) defer cancel() - clientConn := wrappers.NewClientConn(conn, connID) - clientConn = wrappers.NewCtx(ctx, cancel, clientConn) - clientConn = wrappers.NewTimeout(clientConn) + clientConn := stream.NewClientConn(conn, connID) + clientConn = stream.NewCtx(ctx, cancel, clientConn) + clientConn = stream.NewTimeout(clientConn) defer clientConn.Close() clientProtocol := p.ClientProtocolMaker() diff --git a/telegram/base.go b/telegram/base.go index 276b36b..eef2175 100644 --- a/telegram/base.go +++ b/telegram/base.go @@ -7,7 +7,7 @@ import ( "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/utils" - "github.com/9seconds/mtg/wrappers" + "github.com/9seconds/mtg/wrappers/stream" ) type baseTelegram struct { @@ -44,7 +44,7 @@ func (b *baseTelegram) dial(dc conntypes.DC, return nil, fmt.Errorf("cannot initialize tcp socket: %w", err) } - return wrappers.NewTelegramConn(dc, conn), nil + return stream.NewTelegramConn(dc, conn), nil } func (b *baseTelegram) chooseAddress(addresses map[conntypes.DC][]string, diff --git a/wrappers/mtproto_frame.go b/wrappers/packet/mtproto_frame.go similarity index 99% rename from wrappers/mtproto_frame.go rename to wrappers/packet/mtproto_frame.go index 071aec6..a43f4eb 100644 --- a/wrappers/mtproto_frame.go +++ b/wrappers/packet/mtproto_frame.go @@ -1,4 +1,4 @@ -package wrappers +package packet import ( "bytes" diff --git a/wrappers/packetack/client_abridged.go b/wrappers/packetack/client_abridged.go new file mode 100644 index 0000000..c4e68d2 --- /dev/null +++ b/wrappers/packetack/client_abridged.go @@ -0,0 +1,119 @@ +package packetack + +import ( + "bytes" + "fmt" + "io" + "net" + + "go.uber.org/zap" + + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/utils" +) + +const ( + clientAbridgedSmallPacketLength = 0x7f + clientAbridgedQuickAckLength = 0x80 + clientAbridgedLargePacketLength = 16777216 // 256 ^ 3 +) + +type wrapperClientAbridged struct { + parent conntypes.StreamReadWriteCloser +} + +func (w *wrapperClientAbridged) Read(acks *conntypes.ConnectionAcks) (conntypes.Packet, error) { + buf := bytes.Buffer{} + + if _, err := io.CopyN(&buf, w.parent, 1); err != nil { + return nil, fmt.Errorf("cannot read message length: %w", err) + } + msgLength := uint32(buf.Bytes()[0]) + buf.Reset() + + if msgLength >= clientAbridgedQuickAckLength { + acks.Quick = true + msgLength -= clientAbridgedQuickAckLength + } + + if msgLength == clientAbridgedSmallPacketLength { + if _, err := io.CopyN(&buf, w.parent, 3); err != nil { + return nil, fmt.Errorf("cannot read correct message length: %w", err) + } + number := utils.Uint24{} + copy(number[:], buf.Bytes()) + msgLength = utils.FromUint24(number) + } + msgLength *= 4 + + buf.Reset() + if _, err := io.CopyN(&buf, w.parent, int64(msgLength)); err != nil { + return nil, fmt.Errorf("cannot read message: %w", err) + } + + return conntypes.Packet(buf.Bytes()), nil +} + +func (w *wrapperClientAbridged) Write(packet conntypes.Packet, acks *conntypes.ConnectionAcks) error { + if len(packet)%4 != 0 { + return fmt.Errorf("incorrect packet length %d", len(packet)) + } + + if acks.Simple { + if _, err := w.parent.Write(utils.ReverseBytes(packet)); err != nil { + return fmt.Errorf("cannot send a simpleacked packet: %w", err) + } + return nil + } + + packetLength := len(packet) / 4 + switch { + case packetLength < clientAbridgedSmallPacketLength: + data := append([]byte{byte(packetLength)}, packet...) + if _, err := w.parent.Write(data); err != nil { + return fmt.Errorf("cannot send small packet: %w", err) + } + return nil + + case packetLength < clientAbridgedLargePacketLength: + length24 := utils.ToUint24(uint32(packetLength)) + buf := bytes.Buffer{} + + buf.WriteByte(byte(clientAbridgedSmallPacketLength)) + buf.Write(length24[:]) + buf.Write(packet) + + if _, err := w.parent.Write(buf.Bytes()); err != nil { + return fmt.Errorf("cannot send large packet: %w", err) + } + return nil + } + + return fmt.Errorf("packet is too big: %d", len(packet)) +} + +func (w *wrapperClientAbridged) Close() error { + return w.parent.Close() +} + +func (w *wrapperClientAbridged) Conn() net.Conn { + return w.parent.Conn() +} + +func (w *wrapperClientAbridged) LocalAddr() *net.TCPAddr { + return w.parent.LocalAddr() +} + +func (w *wrapperClientAbridged) RemoteAddr() *net.TCPAddr { + return w.parent.RemoteAddr() +} + +func (w *wrapperClientAbridged) Logger() *zap.SugaredLogger { + return w.parent.Logger().Named("client-abridged") +} + +func NewClientAbridged(parent conntypes.StreamReadWriteCloser) conntypes.PacketAckFullReadWriteCloser { + return &wrapperClientAbridged{ + parent: parent, + } +} diff --git a/wrappers/packetack/proxy.go b/wrappers/packetack/proxy.go new file mode 100644 index 0000000..bce700f --- /dev/null +++ b/wrappers/packetack/proxy.go @@ -0,0 +1,99 @@ +package packetack + +import ( + "bytes" + "encoding/binary" + "fmt" + "net" + + "github.com/9seconds/mtg/config" + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/hub" + "github.com/9seconds/mtg/mtproto/rpc" + "github.com/9seconds/mtg/protocol" +) + +type wrapperProxy struct { + flags rpc.ProxyRequestFlags + request *protocol.TelegramRequest + clientIPPort []byte + ourIPPort []byte + channelRead hub.ChannelReadCloser +} + +func (w *wrapperProxy) Write(packet conntypes.Packet, acks *conntypes.ConnectionAcks) error { + buf := bytes.Buffer{} + + flags := w.flags + if acks.Quick { + flags |= rpc.ProxyRequestFlagsQuickAck + } + if bytes.HasPrefix(packet, rpc.ProxyRequestFlagsEncryptedPrefix[:]) { + flags |= rpc.ProxyRequestFlagsEncrypted + } + + buf.Write(rpc.TagProxyRequest) + buf.Write(flags.Bytes()) + buf.Write(w.request.ConnID[:]) + buf.Write(w.clientIPPort) + buf.Write(w.ourIPPort) + buf.Write(rpc.ProxyRequestExtraSize) + buf.Write(rpc.ProxyRequestProxyTag) + buf.WriteByte(byte(len(config.C.AdTag))) + buf.Write(config.C.AdTag) + buf.Write(make([]byte, (4-buf.Len()%4)%4)) + buf.Write(packet) + + return hub.Hub.Write(buf.Bytes(), w.request) +} + +func (w *wrapperProxy) Read(acks *conntypes.ConnectionAcks) (conntypes.Packet, error) { + resp, err := w.channelRead.Read() + if err != nil { + return nil, fmt.Errorf("cannot read a response: %w", err) + } + + if resp.Type == rpc.ProxyResponseTypeSimpleAck { + acks.Simple = true + } + + return resp.Payload, nil +} + +func (w *wrapperProxy) Close() error { + return w.channelRead.Close() +} + +func NewProxy(request *protocol.TelegramRequest) conntypes.PacketAckReadWriteCloser { + flags := rpc.ProxyRequestFlagsHasAdTag | rpc.ProxyRequestFlagsMagic | rpc.ProxyRequestFlagsExtMode2 + + switch request.ClientProtocol.ConnectionType() { + case conntypes.ConnectionTypeAbridged: + flags |= rpc.ProxyRequestFlagsAbdridged + case conntypes.ConnectionTypeIntermediate: + flags |= rpc.ProxyRequestFlagsIntermediate + case conntypes.ConnectionTypeSecure: + flags |= rpc.ProxyRequestFlagsIntermediate | rpc.ProxyRequestFlagsPad + default: + panic("unknown connection type") + } + + return &wrapperProxy{ + flags: flags, + request: request, + channelRead: hub.Registry.Register(request.ConnID), + clientIPPort: proxyGetIPPort(request.ClientConn.RemoteAddr()), + ourIPPort: proxyGetIPPort(request.ClientConn.LocalAddr()), + } +} + +func proxyGetIPPort(addr *net.TCPAddr) []byte { + rv := [16 + 4]byte{} + port := [4]byte{} + + copy(rv[:16], addr.IP.To16()) + binary.LittleEndian.PutUint32(port[:], uint32(addr.Port)) + copy(rv[16:], port[:]) + + return rv[:] +} diff --git a/wrappers/common.go b/wrappers/stream/base.go similarity index 96% rename from wrappers/common.go rename to wrappers/stream/base.go index edf9ebe..0fd50ca 100644 --- a/wrappers/common.go +++ b/wrappers/stream/base.go @@ -1,4 +1,4 @@ -package wrappers +package stream import ( "net" diff --git a/wrappers/blockcipher.go b/wrappers/stream/blockcipher.go similarity index 99% rename from wrappers/blockcipher.go rename to wrappers/stream/blockcipher.go index 0ae2cb3..5755ddb 100644 --- a/wrappers/blockcipher.go +++ b/wrappers/stream/blockcipher.go @@ -1,4 +1,4 @@ -package wrappers +package stream import ( "bytes" diff --git a/wrappers/conn.go b/wrappers/stream/conn.go similarity index 99% rename from wrappers/conn.go rename to wrappers/stream/conn.go index 5af5784..d534ec3 100644 --- a/wrappers/conn.go +++ b/wrappers/stream/conn.go @@ -1,4 +1,4 @@ -package wrappers +package stream import ( "fmt" diff --git a/wrappers/ctx.go b/wrappers/stream/ctx.go similarity index 99% rename from wrappers/ctx.go rename to wrappers/stream/ctx.go index 46f4242..c744062 100644 --- a/wrappers/ctx.go +++ b/wrappers/stream/ctx.go @@ -1,4 +1,4 @@ -package wrappers +package stream import ( "context" diff --git a/wrappers/mtproto_cipher.go b/wrappers/stream/mtproto_cipher.go similarity index 99% rename from wrappers/mtproto_cipher.go rename to wrappers/stream/mtproto_cipher.go index 01ec5c5..85f8ce7 100644 --- a/wrappers/mtproto_cipher.go +++ b/wrappers/stream/mtproto_cipher.go @@ -1,4 +1,4 @@ -package wrappers +package stream import ( "bytes" diff --git a/wrappers/obfuscated2.go b/wrappers/stream/obfuscated2.go similarity index 99% rename from wrappers/obfuscated2.go rename to wrappers/stream/obfuscated2.go index d6f7a69..9ad4ae2 100644 --- a/wrappers/obfuscated2.go +++ b/wrappers/stream/obfuscated2.go @@ -1,4 +1,4 @@ -package wrappers +package stream import ( "crypto/cipher" diff --git a/wrappers/stats_telegram.go b/wrappers/stream/stats_telegram.go similarity index 98% rename from wrappers/stats_telegram.go rename to wrappers/stream/stats_telegram.go index b613b01..c45b769 100644 --- a/wrappers/stats_telegram.go +++ b/wrappers/stream/stats_telegram.go @@ -1,13 +1,14 @@ -package wrappers +package stream import ( "net" "sync" "time" + "go.uber.org/zap" + "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/stats" - "go.uber.org/zap" ) type wrapperTelegramStats struct { diff --git a/wrappers/stats_traffic.go b/wrappers/stream/stats_traffic.go similarity index 98% rename from wrappers/stats_traffic.go rename to wrappers/stream/stats_traffic.go index c66f9fa..911daa2 100644 --- a/wrappers/stats_traffic.go +++ b/wrappers/stream/stats_traffic.go @@ -1,4 +1,4 @@ -package wrappers +package stream import ( "net" diff --git a/wrappers/timeout.go b/wrappers/stream/timeout.go similarity index 98% rename from wrappers/timeout.go rename to wrappers/stream/timeout.go index f9c0302..6dee072 100644 --- a/wrappers/timeout.go +++ b/wrappers/stream/timeout.go @@ -1,4 +1,4 @@ -package wrappers +package stream import ( "net" From c68f2cd50f8f56453cc2d67cb8f5b99f4d462150 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 10 Oct 2019 09:32:50 +0300 Subject: [PATCH 26/62] Add client wrappers --- wrappers/packetack/client_abridged.go | 3 + wrappers/packetack/client_intermediate.go | 85 +++++++++++++++++++ .../packetack/client_intermediate_secure.go | 54 ++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 wrappers/packetack/client_intermediate.go create mode 100644 wrappers/packetack/client_intermediate_secure.go diff --git a/wrappers/packetack/client_abridged.go b/wrappers/packetack/client_abridged.go index c4e68d2..c9b26e1 100644 --- a/wrappers/packetack/client_abridged.go +++ b/wrappers/packetack/client_abridged.go @@ -25,6 +25,7 @@ type wrapperClientAbridged struct { func (w *wrapperClientAbridged) Read(acks *conntypes.ConnectionAcks) (conntypes.Packet, error) { buf := bytes.Buffer{} + buf.Grow(1) if _, err := io.CopyN(&buf, w.parent, 1); err != nil { return nil, fmt.Errorf("cannot read message length: %w", err) } @@ -37,6 +38,7 @@ func (w *wrapperClientAbridged) Read(acks *conntypes.ConnectionAcks) (conntypes. } if msgLength == clientAbridgedSmallPacketLength { + buf.Grow(3) if _, err := io.CopyN(&buf, w.parent, 3); err != nil { return nil, fmt.Errorf("cannot read correct message length: %w", err) } @@ -47,6 +49,7 @@ func (w *wrapperClientAbridged) Read(acks *conntypes.ConnectionAcks) (conntypes. msgLength *= 4 buf.Reset() + buf.Grow(int(msgLength)) if _, err := io.CopyN(&buf, w.parent, int64(msgLength)); err != nil { return nil, fmt.Errorf("cannot read message: %w", err) } diff --git a/wrappers/packetack/client_intermediate.go b/wrappers/packetack/client_intermediate.go new file mode 100644 index 0000000..0415c8c --- /dev/null +++ b/wrappers/packetack/client_intermediate.go @@ -0,0 +1,85 @@ +package packetack + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "net" + + "go.uber.org/zap" + + "github.com/9seconds/mtg/conntypes" +) + +const clientIntermediateQuickAckLength = 0x80000000 + +type wrapperClientIntermediate struct { + parent conntypes.StreamReadWriteCloser +} + +func (w *wrapperClientIntermediate) Read(acks *conntypes.ConnectionAcks) (conntypes.Packet, error) { + buf := bytes.Buffer{} + + buf.Grow(4) + if _, err := io.CopyN(&buf, w.parent, 4); err != nil { + return nil, fmt.Errorf("cannot read message length: %w", err) + } + length := binary.LittleEndian.Uint32(buf.Bytes()) + + if length > clientIntermediateQuickAckLength { + acks.Quick = true + length -= clientIntermediateQuickAckLength + } + + buf.Reset() + buf.Grow(int(length)) + if _, err := io.CopyN(&buf, w.parent, int64(length)); err != nil { + return nil, fmt.Errorf("cannot read the message: %w", err) + } + + return buf.Bytes(), nil +} + +func (w *wrapperClientIntermediate) Write(packet conntypes.Packet, acks *conntypes.ConnectionAcks) error { + if acks.Simple { + if _, err := w.parent.Write(packet); err != nil { + return fmt.Errorf("cannot send simpleacked packet: %w", err) + } + return nil + } + + length := [4]byte{} + binary.LittleEndian.PutUint32(length[:], uint32(len(packet))) + + if _, err := w.parent.Write(append(length[:], packet...)); err != nil { + return fmt.Errorf("cannot send packet: %w", err) + } + return nil +} + +func (w *wrapperClientIntermediate) Close() error { + return w.parent.Close() +} + +func (w *wrapperClientIntermediate) Conn() net.Conn { + return w.parent.Conn() +} + +func (w *wrapperClientIntermediate) LocalAddr() *net.TCPAddr { + return w.parent.LocalAddr() +} + +func (w *wrapperClientIntermediate) RemoteAddr() *net.TCPAddr { + return w.parent.RemoteAddr() +} + +func (w *wrapperClientIntermediate) Logger() *zap.SugaredLogger { + return w.parent.Logger().Named("client-intermediate") +} + +func NewClientIntermediate(parent conntypes.StreamReadWriteCloser) conntypes.PacketAckFullReadWriteCloser { + return &wrapperClientIntermediate{ + parent: parent, + } +} diff --git a/wrappers/packetack/client_intermediate_secure.go b/wrappers/packetack/client_intermediate_secure.go new file mode 100644 index 0000000..f4993e4 --- /dev/null +++ b/wrappers/packetack/client_intermediate_secure.go @@ -0,0 +1,54 @@ +package packetack + +import ( + "bytes" + "encoding/binary" + "fmt" + "math/rand" + + "github.com/9seconds/mtg/conntypes" +) + +type wrapperClientIntermediateSecure struct { + wrapperClientIntermediate +} + +func (w *wrapperClientIntermediateSecure) Read(acks *conntypes.ConnectionAcks) (conntypes.Packet, error) { + data, err := w.wrapperClientIntermediate.Read(acks) + if err != nil { + return nil, err + } + length := len(data) - (len(data) % 4) + + return data[:length], nil +} + +func (w *wrapperClientIntermediateSecure) Write(packet conntypes.Packet, acks *conntypes.ConnectionAcks) error { + if acks.Simple { + if _, err := w.parent.Write(packet); err != nil { + return fmt.Errorf("cannot send simpleacked packet: %w", err) + } + return nil + } + + buf := bytes.Buffer{} + paddingLength := rand.Intn(4) + buf.Grow(4 + len(packet) + paddingLength) + + binary.Write(&buf, binary.LittleEndian, uint32(len(packet)+paddingLength)) + buf.Write(packet) + buf.Write(make([]byte, paddingLength)) + + if _, err := w.parent.Write(buf.Bytes()); err != nil { + return fmt.Errorf("cannot send packet: %w", err) + } + return nil +} + +func NewClientIntermediateSecure(parent conntypes.StreamReadWriteCloser) conntypes.PacketAckFullReadWriteCloser { + return &wrapperClientIntermediateSecure{ + wrapperClientIntermediate: wrapperClientIntermediate{ + parent: parent, + }, + } +} From 828627b0d8616b43908c843c69454860acda50a5 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 10 Oct 2019 14:29:33 +0300 Subject: [PATCH 27/62] Wire things altogether --- proxy/direct.go | 43 +++++++++++++++++++++++++++++++++++ proxy/middle.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ proxy/proxy.go | 45 ++----------------------------------- 3 files changed, 105 insertions(+), 43 deletions(-) create mode 100644 proxy/direct.go create mode 100644 proxy/middle.go diff --git a/proxy/direct.go b/proxy/direct.go new file mode 100644 index 0000000..19e7f69 --- /dev/null +++ b/proxy/direct.go @@ -0,0 +1,43 @@ +package proxy + +import ( + "io" + "sync" + + "go.uber.org/zap" + + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/obfuscated2" + "github.com/9seconds/mtg/protocol" +) + +const directPipeBufferSize = 1024 * 1024 + +func directConnection(request *protocol.TelegramRequest) error { + telegramConnRaw, err := obfuscated2.TelegramProtocol(request) + if err != nil { + return err + } + telegramConn := telegramConnRaw.(conntypes.StreamReadWriteCloser) + defer telegramConn.Close() + + wg := &sync.WaitGroup{} + wg.Add(2) + + go directPipe(telegramConn, request.ClientConn, wg, request.Logger) + go directPipe(request.ClientConn, telegramConn, wg, request.Logger) + + <-request.Ctx.Done() + wg.Wait() + + return request.Ctx.Err() +} + +func directPipe(dst io.Writer, src io.Reader, wg *sync.WaitGroup, logger *zap.SugaredLogger) { + defer wg.Done() + + buf := make([]byte, directPipeBufferSize) + if _, err := io.CopyBuffer(dst, src, buf); err != nil { + logger.Debugw("Cannot pump sockets", "error", err) + } +} diff --git a/proxy/middle.go b/proxy/middle.go new file mode 100644 index 0000000..d806885 --- /dev/null +++ b/proxy/middle.go @@ -0,0 +1,60 @@ +package proxy + +import ( + "sync" + + "go.uber.org/zap" + + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/protocol" + "github.com/9seconds/mtg/wrappers/packetack" +) + +func middleConnection(request *protocol.TelegramRequest) error { + telegramConn := packetack.NewProxy(request) + defer telegramConn.Close() + + var clientConn conntypes.PacketAckFullReadWriteCloser + switch request.ClientProtocol.ConnectionType() { + case conntypes.ConnectionTypeAbridged: + clientConn = packetack.NewClientAbridged(request.ClientConn) + case conntypes.ConnectionTypeIntermediate: + clientConn = packetack.NewClientIntermediate(request.ClientConn) + case conntypes.ConnectionTypeSecure: + clientConn = packetack.NewClientIntermediateSecure(request.ClientConn) + default: + panic("unknown connection type") + } + + wg := &sync.WaitGroup{} + wg.Add(2) + + go middlePipe(telegramConn, clientConn, wg, request.Logger) + go middlePipe(clientConn, telegramConn, wg, request.Logger) + + <-request.Ctx.Done() + wg.Wait() + + return request.Ctx.Err() +} + +func middlePipe(dst conntypes.PacketAckWriter, + src conntypes.PacketAckReader, + wg *sync.WaitGroup, + logger *zap.SugaredLogger) { + defer wg.Done() + + for { + acks := conntypes.ConnectionAcks{} + packet, err := src.Read(&acks) + if err != nil { + logger.Debugw("Cannot read packet", "error", err) + return + } + + if err = dst.Write(packet, &acks); err != nil { + logger.Debugw("Cannot send packet", "error", err) + return + } + } +} diff --git a/proxy/proxy.go b/proxy/proxy.go index b7d04a3..46bc62f 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -2,23 +2,18 @@ package proxy import ( "context" - "io" "net" - "sync" "go.uber.org/zap" "github.com/9seconds/mtg/config" "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/obfuscated2" "github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/stats" "github.com/9seconds/mtg/utils" "github.com/9seconds/mtg/wrappers/stream" ) -const directPipeBufferSize = 1024 * 1024 - type Proxy struct { Logger *zap.SugaredLogger Context context.Context @@ -89,46 +84,10 @@ func (p *Proxy) accept(conn net.Conn) { } if len(config.C.AdTag) > 0 { - err = p.acceptMiddleProxyConnection(req) + err = middleConnection(req) } else { - err = p.acceptDirectConnection(req) + err = directConnection(req) } logger.Infow("Client disconnected", "error", err, "addr", conn.RemoteAddr()) } - -func (p *Proxy) acceptDirectConnection(request *protocol.TelegramRequest) error { - telegramConnRaw, err := obfuscated2.TelegramProtocol(request) - if err != nil { - return err - } - telegramConn := telegramConnRaw.(conntypes.StreamReadWriteCloser) - defer telegramConn.Close() - - wg := &sync.WaitGroup{} - wg.Add(2) - - go p.directPipe(telegramConn, request.ClientConn, wg, request.Logger) - go p.directPipe(request.ClientConn, telegramConn, wg, request.Logger) - - <-request.Ctx.Done() - wg.Wait() - - return request.Ctx.Err() -} - -func (p *Proxy) directPipe(dst io.Writer, - src io.Reader, - wg *sync.WaitGroup, - logger *zap.SugaredLogger) { - defer wg.Done() - - buf := make([]byte, directPipeBufferSize) - if _, err := io.CopyBuffer(dst, src, buf); err != nil { - logger.Debugw("Cannot pump sockets", "error", err) - } -} - -func (p *Proxy) acceptMiddleProxyConnection(request *protocol.TelegramRequest) error { - return nil -} From c62afbdfd81a34e11654209d4574a241636ed45f Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 10 Oct 2019 16:06:50 +0300 Subject: [PATCH 28/62] Rename prometheus namespace to stats-namespace --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index f47a1fd..e795f33 100644 --- a/main.go +++ b/main.go @@ -58,7 +58,7 @@ var ( Envar("MTG_STATS_BIND"). Default("127.0.0.1:3129"). TCP() - proxyStatsNamespace = proxyCommand.Flag("prometheus-namespace", + proxyStatsNamespace = proxyCommand.Flag("stats-namespace", "Which namespace to use for Prometheus."). Envar("MTG_STATS_NAMESPACE"). Default("mtg"). From 61450ea3dba0470d306d88faf8ed95fce0f8b79b Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 10 Oct 2019 16:11:22 +0300 Subject: [PATCH 29/62] Small logger fix --- wrappers/packetack/client_intermediate_secure.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wrappers/packetack/client_intermediate_secure.go b/wrappers/packetack/client_intermediate_secure.go index f4993e4..a42afec 100644 --- a/wrappers/packetack/client_intermediate_secure.go +++ b/wrappers/packetack/client_intermediate_secure.go @@ -6,6 +6,8 @@ import ( "fmt" "math/rand" + "go.uber.org/zap" + "github.com/9seconds/mtg/conntypes" ) @@ -45,6 +47,10 @@ func (w *wrapperClientIntermediateSecure) Write(packet conntypes.Packet, acks *c return nil } +func (w *wrapperClientIntermediateSecure) Logger() *zap.SugaredLogger { + return w.parent.Logger().Named("client-intermediate-secure") +} + func NewClientIntermediateSecure(parent conntypes.StreamReadWriteCloser) conntypes.PacketAckFullReadWriteCloser { return &wrapperClientIntermediateSecure{ wrapperClientIntermediate: wrapperClientIntermediate{ From 2eba78b0db9f5b2a5af75bd9223f427665655bf1 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 10 Oct 2019 16:42:29 +0300 Subject: [PATCH 30/62] Add unregistering on telegram conn close --- hub/hub.go | 3 +++ wrappers/packetack/proxy.go | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/hub/hub.go b/hub/hub.go index 6e76b4d..09c76d1 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -54,6 +54,9 @@ func (h *hub) getHub(req *protocol.TelegramRequest) *connectionHub { rv, ok = h.subs[key] if !ok { + h.logger.Debugw("Create new connection hub", + "dc", req.ClientProtocol.DC(), + "protocol", req.ClientProtocol.ConnectionProtocol()) rv = newConnectionHub(h.logger.With( "dc", req.ClientProtocol.DC(), "protocol", req.ClientProtocol.ConnectionProtocol(), diff --git a/wrappers/packetack/proxy.go b/wrappers/packetack/proxy.go index bce700f..301035c 100644 --- a/wrappers/packetack/proxy.go +++ b/wrappers/packetack/proxy.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "fmt" "net" + "sync" "github.com/9seconds/mtg/config" "github.com/9seconds/mtg/conntypes" @@ -19,6 +20,7 @@ type wrapperProxy struct { clientIPPort []byte ourIPPort []byte channelRead hub.ChannelReadCloser + closeOnce sync.Once } func (w *wrapperProxy) Write(packet conntypes.Packet, acks *conntypes.ConnectionAcks) error { @@ -61,7 +63,11 @@ func (w *wrapperProxy) Read(acks *conntypes.ConnectionAcks) (conntypes.Packet, e } func (w *wrapperProxy) Close() error { - return w.channelRead.Close() + w.closeOnce.Do(func() { + w.channelRead.Close() + hub.Registry.Unregister(w.request.ConnID) + }) + return nil } func NewProxy(request *protocol.TelegramRequest) conntypes.PacketAckReadWriteCloser { From d459efcf9cc12f3ae1902b4efe3fae71d6b8c225 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 10 Oct 2019 17:48:30 +0300 Subject: [PATCH 31/62] bug fixes --- cli/proxy.go | 2 ++ config/config.go | 11 +++++++++-- go.mod | 2 +- hub/connection.go | 1 + hub/connection_hub.go | 17 +++++++++++++++++ hub/hub.go | 3 +++ hub/registry.go | 7 ------- stats/stats_prometheus.go | 5 ++++- stats/stats_statsd.go | 2 +- 9 files changed, 38 insertions(+), 12 deletions(-) diff --git a/cli/proxy.go b/cli/proxy.go index 54a0d62..e2d8b20 100644 --- a/cli/proxy.go +++ b/cli/proxy.go @@ -10,6 +10,7 @@ import ( "github.com/9seconds/mtg/antireplay" "github.com/9seconds/mtg/config" + "github.com/9seconds/mtg/hub" "github.com/9seconds/mtg/ntp" "github.com/9seconds/mtg/obfuscated2" "github.com/9seconds/mtg/proxy" @@ -66,6 +67,7 @@ func Proxy() error { Fatal(err) } telegram.Init() + hub.Init(ctx) proxyListener, err := net.Listen("tcp", config.C.Bind.String()) if err != nil { diff --git a/config/config.go b/config/config.go index 543a7a8..a8bf8fd 100644 --- a/config/config.go +++ b/config/config.go @@ -9,6 +9,7 @@ import ( "net" "time" + "github.com/alecthomas/units" "go.uber.org/zap" statsd "gopkg.in/alexcesaro/statsd.v2" ) @@ -104,8 +105,14 @@ func Init(options ...Opt) error { // nolint: gocyclo, funlen C.Bind = opt.Value.(*net.TCPAddr) case OptionTypePublicIPv4: C.PublicIPv4 = opt.Value.(*net.TCPAddr) + if C.PublicIPv4 == nil { + C.PublicIPv4 = &net.TCPAddr{} + } case OptionTypePublicIPv6: C.PublicIPv6 = opt.Value.(*net.TCPAddr) + if C.PublicIPv6 == nil { + C.PublicIPv6 = &net.TCPAddr{} + } case OptionTypeStatsBind: C.StatsBind = opt.Value.(*net.TCPAddr) case OptionTypeStatsNamespace: @@ -133,9 +140,9 @@ func Init(options ...Opt) error { // nolint: gocyclo, funlen case OptionTypeStatsdTags: C.StatsdTags = opt.Value.(map[string]string) case OptionTypeWriteBufferSize: - C.WriteBuffer = int(opt.Value.(uint32)) + C.WriteBuffer = int(opt.Value.(units.Base2Bytes)) case OptionTypeReadBufferSize: - C.ReadBuffer = int(opt.Value.(uint32)) + C.ReadBuffer = int(opt.Value.(units.Base2Bytes)) case OptionTypeAntiReplayMaxSize: C.AntiReplayMaxSize = opt.Value.(int) case OptionTypeAntiReplayEvictionTime: diff --git a/go.mod b/go.mod index b7f80f5..7e21076 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ replace github.com/golang/lint => github.com/golang/lint v0.0.0-20190227174305-8 require ( github.com/OneOfOne/xxhash v1.2.5 // indirect github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect - github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 // indirect + github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 github.com/allegro/bigcache v1.2.1 github.com/beevik/ntp v0.2.0 github.com/cespare/xxhash v1.1.0 diff --git a/hub/connection.go b/hub/connection.go index 4d2ba5f..ee6f4ec 100644 --- a/hub/connection.go +++ b/hub/connection.go @@ -109,6 +109,7 @@ func newConnection(req *protocol.TelegramRequest, hub *connectionHub) (*connecti conn: conn, hub: hub, id: rand.Int(), // nolint: gosec + done: make(chan struct{}), } go rv.run() diff --git a/hub/connection_hub.go b/hub/connection_hub.go index f39508d..a1101b0 100644 --- a/hub/connection_hub.go +++ b/hub/connection_hub.go @@ -43,11 +43,16 @@ func (c *connectionHub) run() { } func (c *connectionHub) runGC() { + logger := c.logger.Named("gc") + for key, conn := range c.sockets { switch { case conn.closed(): + logger.Debugw("Delete closed socket", "key", key) delete(c.sockets, key) + case conn.idle(): + logger.Debugw("Delete idle socket", "key", key) conn.shutdown() delete(c.sockets, key) return @@ -56,9 +61,14 @@ func (c *connectionHub) runGC() { } func (c *connectionHub) runConnectionRequest(req *connectionHubRequest) { + logger := c.logger.Named("request").With("connection-id", req.request.ConnID) + for key, conn := range c.sockets { delete(c.sockets, key) if !conn.closed() { + logger.Debugw("Choose connection", + "id", conn.id, + "remote_addr", conn.conn.RemoteAddr()) req.response <- conn close(req.response) return @@ -66,16 +76,23 @@ func (c *connectionHub) runConnectionRequest(req *connectionHubRequest) { } if conn, err := newConnection(req.request, c); err == nil { + logger.Debugw("New connection", + "id", conn.id, + "remote_addr", conn.conn.RemoteAddr()) req.response <- conn } close(req.response) } func (c *connectionHub) runBrokenSocket(id int) { + c.logger.Named("broken-socket").Debugw("Delete broken socket", "id", id) delete(c.sockets, id) } func (c *connectionHub) runReturnConnection(conn *connection) { + c.logger.Named("return-connection").Debugw("Return connection", + "id", conn.id, + "remote_addr", conn.conn.RemoteAddr()) c.sockets[conn.id] = conn } diff --git a/hub/hub.go b/hub/hub.go index 09c76d1..e0545e2 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -32,8 +32,11 @@ func (h *hub) Write(packet conntypes.Packet, req *protocol.TelegramRequest) erro } if err := conn.write(packet); err != nil { + conn.shutdown() return fmt.Errorf("cannot send packet: %w", err) } + sub.channelReturnConnections <- conn + return nil } diff --git a/hub/registry.go b/hub/registry.go index 4beebe8..5f6e33c 100644 --- a/hub/registry.go +++ b/hub/registry.go @@ -42,10 +42,3 @@ func (r *registry) getChannel(id conntypes.ConnID) (*ctxChannel, bool) { } return nil, false } - -func InitRegistry(ctx context.Context) { - Registry = ®istry{ - ctx: ctx, - conns: map[string]*ctxChannel{}, - } -} diff --git a/stats/stats_prometheus.go b/stats/stats_prometheus.go index a075940..bcb0c91 100644 --- a/stats/stats_prometheus.go +++ b/stats/stats_prometheus.go @@ -77,7 +77,7 @@ func (s *statsPrometheus) changeTelegramConnections(dc conntypes.DC, addr *net.T labels[1] = "ipv6" } - s.connections.WithLabelValues(labels[:]...).Add(increment) + s.telegramConnections.WithLabelValues(labels[:]...).Add(increment) } func (s *statsPrometheus) Crash() { @@ -122,6 +122,9 @@ func newStatsPrometheus(mux *http.ServeMux) (Interface, error) { if err := registry.Register(instance.connections); err != nil { return nil, fmt.Errorf("cannot register metrics for connections: %w", err) } + if err := registry.Register(instance.telegramConnections); err != nil { + return nil, fmt.Errorf("cannot register metrics for telegram connections: %w", err) + } if err := registry.Register(instance.traffic); err != nil { return nil, fmt.Errorf("cannot register metrics for traffic: %w", err) } diff --git a/stats/stats_statsd.go b/stats/stats_statsd.go index d5e5d95..9451ed7 100644 --- a/stats/stats_statsd.go +++ b/stats/stats_statsd.go @@ -63,7 +63,7 @@ func (s *statsStatsd) TelegramDisconnected(dc conntypes.DC, addr *net.TCPAddr) { func (s *statsStatsd) changeTelegramConnections(dc conntypes.DC, addr *net.TCPAddr, value int) { labels := [...]string{ - "telegram", + "telegram_connections", strconv.Itoa(int(dc)), "ipv4", } From d10729490d11b1065be31973fe540713fcd9103d Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 10 Oct 2019 17:49:16 +0300 Subject: [PATCH 32/62] Remove obsolete files --- _antireplay/cache.go | 37 ---- _antireplay/hasher.go | 9 - _client/client.go | 15 -- _client/direct.go | 63 ------- _client/middle.go | 42 ----- _config/config.go | 224 ----------------------- _config/global_ips.go | 52 ------ _config/urls.go | 58 ------ _mtproto/connection_options.go | 87 --------- _mtproto/rpc/handshake_request.go | 26 --- _mtproto/rpc/handshake_response.go | 55 ------ _mtproto/rpc/nonce_request.go | 52 ------ _mtproto/rpc/nonce_response.go | 60 ------ _mtproto/rpc/proxy_flags.go | 59 ------ _mtproto/rpc/proxy_request.go | 105 ----------- _mtproto/rpc/rpc.go | 28 --- _obfuscated2/frame.go | 121 ------------ _obfuscated2/frame_test.go | 106 ----------- _obfuscated2/obfuscated2.go | 81 -------- _obfuscated2/obfuscated2_test.go | 97 ---------- _proxy/proxy.go | 178 ------------------ _stats/channels.go | 76 -------- _stats/init.go | 28 --- _stats/prometheus.go | 91 --------- _stats/server.go | 40 ---- _stats/stats.go | 175 ------------------ _stats/statsd.go | 77 -------- _telegram/dialer.go | 52 ------ _telegram/direct.go | 79 -------- _telegram/middle.go | 139 -------------- _telegram/middle_caller.go | 191 ------------------- _telegram/telegram.go | 68 ------- _utils/read_current_data.go | 21 --- _utils/reverse_bytes.go | 15 -- _utils/uint24.go | 15 -- _wrappers/blockcipher.go | 99 ---------- _wrappers/conn.go | 154 ---------------- _wrappers/mtproto_abridged.go | 159 ---------------- _wrappers/mtproto_cipher.go | 98 ---------- _wrappers/mtproto_frame.go | 161 ---------------- _wrappers/mtproto_intermediate.go | 117 ------------ _wrappers/mtproto_intermediate_secure.go | 74 -------- _wrappers/mtproto_proxy.go | 165 ----------------- _wrappers/streamcipher.go | 73 -------- _wrappers/streamcipher_pool.go | 14 -- _wrappers/wrap.go | 111 ----------- 46 files changed, 3847 deletions(-) delete mode 100644 _antireplay/cache.go delete mode 100644 _antireplay/hasher.go delete mode 100644 _client/client.go delete mode 100644 _client/direct.go delete mode 100644 _client/middle.go delete mode 100644 _config/config.go delete mode 100644 _config/global_ips.go delete mode 100644 _config/urls.go delete mode 100644 _mtproto/connection_options.go delete mode 100644 _mtproto/rpc/handshake_request.go delete mode 100644 _mtproto/rpc/handshake_response.go delete mode 100644 _mtproto/rpc/nonce_request.go delete mode 100644 _mtproto/rpc/nonce_response.go delete mode 100644 _mtproto/rpc/proxy_flags.go delete mode 100644 _mtproto/rpc/proxy_request.go delete mode 100644 _mtproto/rpc/rpc.go delete mode 100644 _obfuscated2/frame.go delete mode 100644 _obfuscated2/frame_test.go delete mode 100644 _obfuscated2/obfuscated2.go delete mode 100644 _obfuscated2/obfuscated2_test.go delete mode 100644 _proxy/proxy.go delete mode 100644 _stats/channels.go delete mode 100644 _stats/init.go delete mode 100644 _stats/prometheus.go delete mode 100644 _stats/server.go delete mode 100644 _stats/stats.go delete mode 100644 _stats/statsd.go delete mode 100644 _telegram/dialer.go delete mode 100644 _telegram/direct.go delete mode 100644 _telegram/middle.go delete mode 100644 _telegram/middle_caller.go delete mode 100644 _telegram/telegram.go delete mode 100644 _utils/read_current_data.go delete mode 100644 _utils/reverse_bytes.go delete mode 100644 _utils/uint24.go delete mode 100644 _wrappers/blockcipher.go delete mode 100644 _wrappers/conn.go delete mode 100644 _wrappers/mtproto_abridged.go delete mode 100644 _wrappers/mtproto_cipher.go delete mode 100644 _wrappers/mtproto_frame.go delete mode 100644 _wrappers/mtproto_intermediate.go delete mode 100644 _wrappers/mtproto_intermediate_secure.go delete mode 100644 _wrappers/mtproto_proxy.go delete mode 100644 _wrappers/streamcipher.go delete mode 100644 _wrappers/streamcipher_pool.go delete mode 100644 _wrappers/wrap.go diff --git a/_antireplay/cache.go b/_antireplay/cache.go deleted file mode 100644 index 4923d52..0000000 --- a/_antireplay/cache.go +++ /dev/null @@ -1,37 +0,0 @@ -package antireplay - -import ( - "github.com/allegro/bigcache" - "github.com/juju/errors" - - "github.com/9seconds/mtg/config" -) - -// Cache defines storage for obfuscated2 handshake frames. -type Cache struct { - cache *bigcache.BigCache -} - -func (a Cache) Add(frame []byte) { - a.cache.Set(string(frame), nil) // nolint: errcheck -} - -func (a Cache) Has(frame []byte) bool { - _, err := a.cache.Get(string(frame)) - - return err == nil -} - -func NewCache(config *config.Config) (Cache, error) { - cache, err := bigcache.NewBigCache(bigcache.Config{ - Shards: 1024, - LifeWindow: config.AntiReplayEvictionTime, - Hasher: hasher{}, - HardMaxCacheSize: config.AntiReplayMaxSize, - }) - if err != nil { - return Cache{}, errors.Annotate(err, "Cannot make cache") - } - - return Cache{cache}, nil -} diff --git a/_antireplay/hasher.go b/_antireplay/hasher.go deleted file mode 100644 index acaf9b6..0000000 --- a/_antireplay/hasher.go +++ /dev/null @@ -1,9 +0,0 @@ -package antireplay - -import "github.com/cespare/xxhash" - -type hasher struct{} - -func (h hasher) Sum64(value string) uint64 { - return xxhash.Sum64String(value) -} diff --git a/_client/client.go b/_client/client.go deleted file mode 100644 index b8f1b93..0000000 --- a/_client/client.go +++ /dev/null @@ -1,15 +0,0 @@ -package client - -import ( - "context" - "net" - - "github.com/9seconds/mtg/antireplay" - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/mtproto" - "github.com/9seconds/mtg/wrappers" -) - -// Init defines common method for initializing client connections. -type Init func(context.Context, context.CancelFunc, net.Conn, string, - antireplay.Cache, *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) diff --git a/_client/direct.go b/_client/direct.go deleted file mode 100644 index 5ee0a39..0000000 --- a/_client/direct.go +++ /dev/null @@ -1,63 +0,0 @@ -package client - -import ( - "context" - "net" - "time" - - "github.com/juju/errors" - - "github.com/9seconds/mtg/antireplay" - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/mtproto" - "github.com/9seconds/mtg/obfuscated2" - "github.com/9seconds/mtg/wrappers" -) - -const handshakeTimeout = 10 * time.Second - -// DirectInit initializes client connection for proxy which connects to -// Telegram directly. -func DirectInit(ctx context.Context, cancel context.CancelFunc, socket net.Conn, - connID string, antiReplayCache antireplay.Cache, - conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) { - tcpSocket := socket.(*net.TCPConn) - if err := tcpSocket.SetNoDelay(false); err != nil { - return nil, nil, errors.Annotate(err, "Cannot disable NO_DELAY to client socket") - } - if err := tcpSocket.SetReadBuffer(conf.ReadBufferSize); err != nil { - return nil, nil, errors.Annotate(err, "Cannot set read buffer size of client socket") - } - if err := tcpSocket.SetWriteBuffer(conf.WriteBufferSize); err != nil { - return nil, nil, errors.Annotate(err, "Cannot set write buffer size of client socket") - } - - socket.SetReadDeadline(time.Now().Add(handshakeTimeout)) // nolint: errcheck, gosec - frame, err := obfuscated2.ExtractFrame(socket) - if err != nil { - return nil, nil, errors.Annotate(err, "Cannot extract frame") - } - socket.SetReadDeadline(time.Time{}) // nolint: errcheck, gosec - - conn := wrappers.NewConn(ctx, cancel, socket, connID, wrappers.ConnPurposeClient, conf.PublicIPv4, conf.PublicIPv6) - obfs2, connOpts, err := obfuscated2.ParseObfuscated2ClientFrame(conf.Secret, frame) - if err != nil { - return nil, nil, errors.Annotate(err, "Cannot parse obfuscated frame") - } - - var replayPart = []byte(frame) - - if antiReplayCache.Has(replayPart[4:60]) { - return nil, nil, errors.New("Replay attack is detected") - } - antiReplayCache.Add(replayPart[4:60]) - - connOpts.ConnectionProto = mtproto.ConnectionProtocolAny - connOpts.ClientAddr = conn.RemoteAddr() - - conn = wrappers.NewStreamCipher(conn, obfs2.Encryptor, obfs2.Decryptor) - - conn.Logger().Infow("Client connection initialized") - - return conn, connOpts, nil -} diff --git a/_client/middle.go b/_client/middle.go deleted file mode 100644 index ecacd8e..0000000 --- a/_client/middle.go +++ /dev/null @@ -1,42 +0,0 @@ -package client - -import ( - "context" - "net" - - "github.com/9seconds/mtg/antireplay" - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/mtproto" - "github.com/9seconds/mtg/wrappers" -) - -// MiddleInit initializes client connection for proxy which has to -// support promoted channels, connect to Telegram middle proxies etc. -func MiddleInit(ctx context.Context, cancel context.CancelFunc, socket net.Conn, - connID string, antiReplayCache antireplay.Cache, - conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) { - conn, opts, err := DirectInit(ctx, cancel, socket, connID, antiReplayCache, conf) - if err != nil { - return nil, nil, err - } - connStream := conn.(wrappers.StreamReadWriteCloser) - - var newConn wrappers.PacketReadWriteCloser - switch opts.ConnectionType { - case mtproto.ConnectionTypeAbridged: - newConn = wrappers.NewMTProtoAbridged(connStream, opts) - case mtproto.ConnectionTypeIntermediate: - newConn = wrappers.NewMTProtoIntermediate(connStream, opts) - case mtproto.ConnectionTypeSecure: - newConn = wrappers.NewMTProtoIntermediateSecure(connStream, opts) - default: - panic("Unknown connection type") - } - - opts.ConnectionProto = mtproto.ConnectionProtocolIPv4 - if socket.LocalAddr().(*net.TCPAddr).IP.To4() == nil { - opts.ConnectionProto = mtproto.ConnectionProtocolIPv6 - } - - return newConn, opts, err -} diff --git a/_config/config.go b/_config/config.go deleted file mode 100644 index dc43864..0000000 --- a/_config/config.go +++ /dev/null @@ -1,224 +0,0 @@ -package config - -import ( - "bytes" - "encoding/hex" - "fmt" - "net" - "strconv" - "time" - - "github.com/juju/errors" - statsd "gopkg.in/alexcesaro/statsd.v2" -) - -// Config represents common configuration of mtg. -type Config struct { - Debug bool - Verbose bool - SecureMode bool - SecureOnly bool - - ReadBufferSize int - WriteBufferSize int - - BindPort uint16 - PublicIPv4Port uint16 - PublicIPv6Port uint16 - StatsPort uint16 - - BindIP net.IP - PublicIPv4 net.IP - PublicIPv6 net.IP - StatsIP net.IP - - AntiReplayMaxSize int - AntiReplayEvictionTime time.Duration - - StatsD struct { - Addr net.Addr - Prefix string - Tags map[string]string - TagsFormat statsd.TagFormat - Enabled bool - } - Prometheus struct { - Prefix string - } - - Secret []byte - AdTag []byte -} - -// URLs contains links to the proxy (tg://, t.me) and their QR codes. -type URLs struct { - TG string `json:"tg_url"` - TMe string `json:"tme_url"` - TGQRCode string `json:"tg_qrcode"` - TMeQRCode string `json:"tme_qrcode"` -} - -// IPURLs contains links to both ipv4 and ipv6 of the proxy. -type IPURLs struct { - IPv4 URLs `json:"ipv4"` - IPv6 URLs `json:"ipv6"` - BotSecret string `json:"secret_for_mtproxybot"` -} - -// BindAddr returns connection for this server to bind to. -func (c *Config) BindAddr() string { - return getAddr(c.BindIP, c.BindPort) -} - -// StatAddr returns connection string to the stats API. -func (c *Config) StatAddr() string { - return getAddr(c.StatsIP, c.StatsPort) -} - -// UseMiddleProxy defines if this proxy has to connect middle proxies -// which supports promoted channels or directly access Telegram. -func (c *Config) UseMiddleProxy() bool { - return len(c.AdTag) > 0 -} - -// BotSecretString returns secret string which should work with MTProxybot. -func (c *Config) BotSecretString() string { - return hex.EncodeToString(c.Secret) -} - -// SecretString returns a secret in a form entered on the start of the -// application. -func (c *Config) SecretString() string { - secret := c.BotSecretString() - if c.SecureMode { - return "dd" + secret - } - return secret -} - -// GetURLs returns configured IPURLs instance with links to this server. -func (c *Config) GetURLs() IPURLs { - urls := IPURLs{} - secret := c.SecretString() - if c.PublicIPv4 != nil { - urls.IPv4 = getURLs(c.PublicIPv4, c.PublicIPv4Port, secret) - } - if c.PublicIPv6 != nil { - urls.IPv6 = getURLs(c.PublicIPv6, c.PublicIPv6Port, secret) - } - urls.BotSecret = c.BotSecretString() - - return urls -} - -func getAddr(host fmt.Stringer, port uint16) string { - return net.JoinHostPort(host.String(), strconv.Itoa(int(port))) -} - -// NewConfig returns new configuration. If required, it manages and -// fetches data from external sources. Parameters passed to this -// function, should come from command line arguments. -func NewConfig(debug, verbose bool, // nolint: gocyclo - writeBufferSize, readBufferSize uint32, - bindIP, publicIPv4, publicIPv6, statsIP net.IP, - bindPort, publicIPv4Port, publicIPv6Port, statsPort, statsdPort uint16, - statsdIP, statsdNetwork, statsdPrefix, statsdTagsFormat string, - statsdTags map[string]string, prometheusPrefix string, - secureOnly bool, - antiReplayMaxSize int, antiReplayEvictionTime time.Duration, - secret, adtag []byte) (*Config, error) { - secureMode := secureOnly - if bytes.HasPrefix(secret, []byte{0xdd}) && len(secret) == 17 { - secureMode = true - secret = bytes.TrimPrefix(secret, []byte{0xdd}) - } else if len(secret) != 16 { - return nil, errors.New("Telegram demands secret of length 32") - } - - var err error - if publicIPv4 == nil { - publicIPv4, err = getGlobalIPv4() - if err != nil { - publicIPv4 = nil - } else if publicIPv4.To4() == nil { - return nil, errors.Errorf("IP %s is not IPv4", publicIPv4.String()) - } - } - if publicIPv4Port == 0 { - publicIPv4Port = bindPort - } - - if publicIPv6 == nil { - publicIPv6, err = getGlobalIPv6() - if err != nil { - publicIPv6 = nil - } else if publicIPv6.To4() != nil { - return nil, errors.Errorf("IP %s is not IPv6", publicIPv6.String()) - } - } - if publicIPv6Port == 0 { - publicIPv6Port = bindPort - } - - if statsIP == nil { - statsIP = publicIPv4 - } - - conf := &Config{ - Debug: debug, - Verbose: verbose, - SecureOnly: secureOnly, - BindIP: bindIP, - BindPort: bindPort, - PublicIPv4: publicIPv4, - PublicIPv4Port: publicIPv4Port, - PublicIPv6: publicIPv6, - PublicIPv6Port: publicIPv6Port, - StatsIP: statsIP, - StatsPort: statsPort, - Secret: secret, - AdTag: adtag, - SecureMode: secureMode, - ReadBufferSize: int(readBufferSize), - WriteBufferSize: int(writeBufferSize), - AntiReplayMaxSize: antiReplayMaxSize, - AntiReplayEvictionTime: antiReplayEvictionTime, - } - conf.Prometheus.Prefix = prometheusPrefix - - if statsdIP != "" { - conf.StatsD.Enabled = true - conf.StatsD.Prefix = statsdPrefix - conf.StatsD.Tags = statsdTags - - var ( - addr net.Addr - err error - ) - hostPort := net.JoinHostPort(statsdIP, strconv.Itoa(int(statsdPort))) - switch statsdNetwork { - case "tcp": - addr, err = net.ResolveTCPAddr("tcp", hostPort) - case "udp": - addr, err = net.ResolveUDPAddr("udp", hostPort) - default: - err = errors.Errorf("Unknown network %s", statsdNetwork) - } - if err != nil { - return nil, errors.Annotate(err, "Cannot resolve statsd address") - } - conf.StatsD.Addr = addr - - switch statsdTagsFormat { - case "datadog": - conf.StatsD.TagsFormat = statsd.Datadog - case "influxdb": - conf.StatsD.TagsFormat = statsd.InfluxDB - case "": - default: - return nil, errors.Errorf("Unknown tags format %s", statsdTagsFormat) - } - } - - return conf, nil -} diff --git a/_config/global_ips.go b/_config/global_ips.go deleted file mode 100644 index 9a144a6..0000000 --- a/_config/global_ips.go +++ /dev/null @@ -1,52 +0,0 @@ -package config - -import ( - "context" - "io/ioutil" - "net" - "net/http" - "strings" - - "github.com/juju/errors" -) - -const ifconfigAddress = "https://ifconfig.co/ip" - -func getGlobalIPv4() (net.IP, error) { - return fetchIP("tcp4") -} - -func getGlobalIPv6() (net.IP, error) { - return fetchIP("tcp6") -} - -func fetchIP(network string) (net.IP, error) { - dialer := &net.Dialer{FallbackDelay: -1} - client := &http.Client{ - Jar: nil, - Transport: &http.Transport{ - DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) { - return dialer.DialContext(ctx, network, addr) - }, - }, - } - - resp, err := client.Get(ifconfigAddress) - if err != nil { - return nil, err - } - defer resp.Body.Close() // nolint: errcheck - - respDataBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - respData := strings.TrimSpace(string(respDataBytes)) - - ip := net.ParseIP(respData) - if ip == nil { - return nil, errors.Errorf("ifconfig.co returns incorrect IP %s", respData) - } - - return ip, nil -} diff --git a/_config/urls.go b/_config/urls.go deleted file mode 100644 index 5f4e6d8..0000000 --- a/_config/urls.go +++ /dev/null @@ -1,58 +0,0 @@ -package config - -import ( - "net" - "net/url" - "strconv" -) - -func getURLs(addr net.IP, port uint16, secret string) (urls URLs) { - values := url.Values{} - values.Set("server", addr.String()) - values.Set("port", strconv.Itoa(int(port))) - values.Set("secret", secret) - - urls.TG = makeTGURL(values) - urls.TMe = makeTMeURL(values) - urls.TGQRCode = makeQRCodeURL(urls.TG) - urls.TMeQRCode = makeQRCodeURL(urls.TG) - - return -} - -func makeTGURL(values url.Values) string { - tgURL := url.URL{ - Scheme: "tg", - Host: "proxy", - RawQuery: values.Encode(), - } - - return tgURL.String() -} - -func makeTMeURL(values url.Values) string { - tMeURL := url.URL{ - Scheme: "https", - Host: "t.me", - Path: "proxy", - RawQuery: values.Encode(), - } - - return tMeURL.String() -} - -func makeQRCodeURL(data string) string { - qr := url.URL{ - Scheme: "https", - Host: "api.qrserver.com", - Path: "v1/create-qr-code", - } - - values := url.Values{} - values.Set("qzone", "4") - values.Set("format", "svg") - values.Set("data", data) - qr.RawQuery = values.Encode() - - return qr.String() -} diff --git a/_mtproto/connection_options.go b/_mtproto/connection_options.go deleted file mode 100644 index 0230f5f..0000000 --- a/_mtproto/connection_options.go +++ /dev/null @@ -1,87 +0,0 @@ -package mtproto - -import ( - "bytes" - "net" - - "github.com/juju/errors" -) - -// ConnectionType is a type of obfuscated2/mtproto connection requested -// by the user. -type ConnectionType uint8 - -// ConnectionProtocol is a type of IP protocol to use. -type ConnectionProtocol uint8 - -// Hacks is a simple structure to store flags for packet transmission. -type Hacks struct { - SimpleAck bool - QuickAck bool -} - -// ConnectionOpts presents an options, metadata on connection requested -// by the user on handshake. -type ConnectionOpts struct { - DC int16 - ConnectionType ConnectionType - ConnectionProto ConnectionProtocol - // Read and Write means direction related to the client. - // ReadHacks are meant to be flushed on client read - // WriteHacks are meant to be flushed on client write. - ReadHacks Hacks - WriteHacks Hacks - ClientAddr *net.TCPAddr -} - -// Different connection types which user requests from Telegram. -const ( - ConnectionTypeUnknown ConnectionType = iota - ConnectionTypeAbridged - ConnectionTypeIntermediate - ConnectionTypeSecure -) - -// ConnectionProtocol* define which connection protocols to use. -// ConnectionProtocolAny means that any is suitable. -const ( - ConnectionProtocolIPv4 ConnectionProtocol = 1 - ConnectionProtocolIPv6 = ConnectionProtocolIPv4 << 1 - ConnectionProtocolAny = ConnectionProtocolIPv4 | ConnectionProtocolIPv6 -) - -// Connection tags for mtproto handshakes. -var ( - ConnectionTagAbridged = []byte{0xef, 0xef, 0xef, 0xef} - ConnectionTagIntermediate = []byte{0xee, 0xee, 0xee, 0xee} - ConnectionTagSecure = []byte{0xdd, 0xdd, 0xdd, 0xdd} -) - -// Tag maps connection type to the corresponding handshake tag. -func (t ConnectionType) Tag() ([]byte, error) { - switch t { - case ConnectionTypeAbridged: - return ConnectionTagAbridged, nil - case ConnectionTypeIntermediate: - return ConnectionTagIntermediate, nil - case ConnectionTypeSecure: - return ConnectionTagSecure, nil - default: - return nil, errors.Errorf("Unknown connection type %d", t) - } -} - -// ConnectionTagFromHandshake maps magic bytes to the connection type. -func ConnectionTagFromHandshake(magic []byte) (ConnectionType, error) { - if bytes.Equal(magic, ConnectionTagIntermediate) { - return ConnectionTypeIntermediate, nil - } - if bytes.Equal(magic, ConnectionTagAbridged) { - return ConnectionTypeAbridged, nil - } - if bytes.Equal(magic, ConnectionTagSecure) { - return ConnectionTypeSecure, nil - } - - return ConnectionTypeUnknown, errors.New("Unknown handshake protocol") -} diff --git a/_mtproto/rpc/handshake_request.go b/_mtproto/rpc/handshake_request.go deleted file mode 100644 index dcd2290..0000000 --- a/_mtproto/rpc/handshake_request.go +++ /dev/null @@ -1,26 +0,0 @@ -package rpc - -import "bytes" - -// HandshakeRequest is the data type which is responsible for -// constructing of correct handshake request. -type HandshakeRequest struct { -} - -// Bytes returns serialized handshake request. -func (r *HandshakeRequest) Bytes() []byte { - buf := &bytes.Buffer{} - buf.Grow(len(TagHandshake) + len(HandshakeFlags) + len(HandshakeSenderPID) + len(HandshakePeerPID)) - - buf.Write(TagHandshake) // nolint: gosec - buf.Write(HandshakeFlags) // nolint: gosec - buf.Write(HandshakeSenderPID) // nolint: gosec - buf.Write(HandshakePeerPID) // nolint: gosec - - return buf.Bytes() -} - -// NewHandshakeRequest creates new HandshakeRequest instance. -func NewHandshakeRequest() *HandshakeRequest { - return &HandshakeRequest{} -} diff --git a/_mtproto/rpc/handshake_response.go b/_mtproto/rpc/handshake_response.go deleted file mode 100644 index 962e949..0000000 --- a/_mtproto/rpc/handshake_response.go +++ /dev/null @@ -1,55 +0,0 @@ -package rpc - -import ( - "bytes" - - "github.com/juju/errors" -) - -// HandshakeResponse defines data structure which is used for storage of -// handshake response. -type HandshakeResponse struct { - Type []byte - Flags []byte - SenderPID []byte - PeerPID []byte -} - -// Bytes returns a serialized handshake response. -func (r *HandshakeResponse) Bytes() []byte { - buf := &bytes.Buffer{} - - buf.Write(r.Type) // nolint: gosec - buf.Write(r.Flags) // nolint: gosec - buf.Write(r.SenderPID) // nolint: gosec - buf.Write(r.PeerPID) // nolint: gosec - - return buf.Bytes() -} - -// Valid checks that handshake response compliments request. -func (r *HandshakeResponse) Valid(req *HandshakeRequest) error { - if !bytes.Equal(r.Type, TagHandshake) { - return errors.New("Unexpected handshake tag") - } - if !bytes.Equal(r.PeerPID, HandshakeSenderPID) { - return errors.New("Incorrect sender PID") - } - - return nil -} - -// NewHandshakeResponse constructs new handshake response from the given -// data. -func NewHandshakeResponse(data []byte) (*HandshakeResponse, error) { - if len(data) != 32 { - return nil, errors.New("Incorrect handshake response length") - } - - return &HandshakeResponse{ - Type: data[:4], - Flags: data[4:8], - SenderPID: data[8:20], - PeerPID: data[20:], - }, nil -} diff --git a/_mtproto/rpc/nonce_request.go b/_mtproto/rpc/nonce_request.go deleted file mode 100644 index 60cf218..0000000 --- a/_mtproto/rpc/nonce_request.go +++ /dev/null @@ -1,52 +0,0 @@ -package rpc - -import ( - "bytes" - "crypto/rand" - "encoding/binary" - "time" - - "github.com/juju/errors" -) - -// NonceRequest is the data type which contains all the data for correct -// nonce request. -type NonceRequest struct { - KeySelector []byte - CryptoTS []byte - Nonce []byte -} - -// Bytes returns serialized nonce request. -func (r *NonceRequest) Bytes() []byte { - buf := &bytes.Buffer{} - - buf.Write(TagNonce) // nolint: gosec - buf.Write(r.KeySelector) // nolint: gosec - buf.Write(NonceCryptoAES) // nolint: gosec - buf.Write(r.CryptoTS) // nolint: gosec - buf.Write(r.Nonce) // nolint: gosec - - return buf.Bytes() -} - -// NewNonceRequest builds new none request based on proxy secret. -func NewNonceRequest(proxySecret []byte) (*NonceRequest, error) { - nonce := make([]byte, 16) - keySelector := make([]byte, 4) - cryptoTS := make([]byte, 4) - - if _, err := rand.Read(nonce); err != nil { - return nil, errors.Annotate(err, "Cannot generate nonce") - } - copy(keySelector, proxySecret) - - timestamp := time.Now().Truncate(time.Second).Unix() % 4294967296 // 256 ^ 4 - do not know how to name - binary.LittleEndian.PutUint32(cryptoTS, uint32(timestamp)) - - return &NonceRequest{ - KeySelector: keySelector, - CryptoTS: cryptoTS, - Nonce: nonce, - }, nil -} diff --git a/_mtproto/rpc/nonce_response.go b/_mtproto/rpc/nonce_response.go deleted file mode 100644 index f7c592e..0000000 --- a/_mtproto/rpc/nonce_response.go +++ /dev/null @@ -1,60 +0,0 @@ -package rpc - -import ( - "bytes" - - "github.com/juju/errors" -) - -// NonceResponse is the data type which contains data of nonce response. -type NonceResponse struct { - NonceRequest - - Type []byte - Crypto []byte -} - -// Bytes returns serialized form of the nonce response. -func (r *NonceResponse) Bytes() []byte { - buf := &bytes.Buffer{} - - buf.Write(r.Type) // nolint: gosec - buf.Write(r.KeySelector) // nolint: gosec - buf.Write(r.Crypto) // nolint: gosec - buf.Write(r.CryptoTS) // nolint: gosec - buf.Write(r.Nonce) // nolint: gosec - - return buf.Bytes() -} - -// Valid checks that nonce response compliments nonce request. -func (r *NonceResponse) Valid(req *NonceRequest) error { - if !bytes.Equal(r.Type, TagNonce) { - return errors.New("Unexpected RPC type") - } - if !bytes.Equal(r.Crypto, NonceCryptoAES) { - return errors.New("Unexpected crypto type") - } - if !bytes.Equal(r.KeySelector, req.KeySelector) { - return errors.New("Unexpected key selector") - } - - return nil -} - -// NewNonceResponse build new nonce response based on the given data. -func NewNonceResponse(data []byte) (*NonceResponse, error) { - if len(data) != 32 { - return nil, errors.New("Unexpected message length") - } - - return &NonceResponse{ - NonceRequest: NonceRequest{ - KeySelector: data[4:8], - CryptoTS: data[12:16], - Nonce: data[16:], - }, - Type: data[:4], - Crypto: data[8:12], - }, nil -} diff --git a/_mtproto/rpc/proxy_flags.go b/_mtproto/rpc/proxy_flags.go deleted file mode 100644 index c59d33f..0000000 --- a/_mtproto/rpc/proxy_flags.go +++ /dev/null @@ -1,59 +0,0 @@ -package rpc - -import ( - "encoding/binary" - "strings" -) - -type proxyRequestFlags uint32 - -const ( - proxyRequestFlagsHasAdTag proxyRequestFlags = 0x8 - proxyRequestFlagsEncrypted proxyRequestFlags = 0x2 - proxyRequestFlagsMagic proxyRequestFlags = 0x1000 - proxyRequestFlagsExtMode2 proxyRequestFlags = 0x20000 - proxyRequestFlagsIntermediate proxyRequestFlags = 0x20000000 - proxyRequestFlagsAbdridged proxyRequestFlags = 0x40000000 - proxyRequestFlagsQuickAck proxyRequestFlags = 0x80000000 - proxyRequestFlagsPad proxyRequestFlags = 0x8000000 -) - -var proxyRequestFlagsEncryptedPrefix [8]byte - -func (r proxyRequestFlags) Bytes() []byte { - converted := make([]byte, 4) - binary.LittleEndian.PutUint32(converted, uint32(r)) - - return converted -} - -func (r proxyRequestFlags) String() string { - flags := make([]string, 0, 7) - - if r&proxyRequestFlagsHasAdTag != 0 { - flags = append(flags, "HAS_AD_TAG") - } - if r&proxyRequestFlagsEncrypted != 0 { - flags = append(flags, "ENCRYPTED") - } - if r&proxyRequestFlagsMagic != 0 { - flags = append(flags, "MAGIC") - } - if r&proxyRequestFlagsExtMode2 != 0 { - flags = append(flags, "EXT_MODE_2") - } - if r&proxyRequestFlagsIntermediate != 0 { - flags = append(flags, "INTERMEDIATE") - } - if r&proxyRequestFlagsAbdridged != 0 { - flags = append(flags, "ABRIDGED") - } - if r&proxyRequestFlagsQuickAck != 0 { - flags = append(flags, "QUICK_ACK") - } - if r&proxyRequestFlagsPad != 0 { - flags = append(flags, "PAD") - } - - return strings.Join(flags, " | ") -} diff --git a/_mtproto/rpc/proxy_request.go b/_mtproto/rpc/proxy_request.go deleted file mode 100644 index bb9e68f..0000000 --- a/_mtproto/rpc/proxy_request.go +++ /dev/null @@ -1,105 +0,0 @@ -package rpc - -import ( - "bytes" - "crypto/rand" - "encoding/binary" - "fmt" - "net" - - "github.com/juju/errors" - - "github.com/9seconds/mtg/mtproto" -) - -// ProxyRequest is the data type for storing data required to compose -// RPC_PROXY_REQ request. -type ProxyRequest struct { - Flags proxyRequestFlags - ConnectionID []byte - OurIPPort []byte - ClientIPPort []byte - ADTag []byte - Options *mtproto.ConnectionOpts -} - -// MakeHeader makes RPC_PROXY_REQ header. We need only to append the -// data for it. -func (r *ProxyRequest) MakeHeader(message []byte) (*bytes.Buffer, fmt.Stringer) { - bufferLength := len(TagProxyRequest) + - 4 + // len(flags) - len(r.ConnectionID) + - len(r.ClientIPPort) + - len(r.OurIPPort) + - len(ProxyRequestExtraSize) + - len(ProxyRequestProxyTag) + - 1 + // len(AdTag) - len(r.ADTag) - bufferLength += bufferLength % 4 - - buf := &bytes.Buffer{} - buf.Grow(bufferLength + len(message)) - - flags := r.Flags - if r.Options.ReadHacks.QuickAck { - flags |= proxyRequestFlagsQuickAck - } - - if bytes.HasPrefix(message, proxyRequestFlagsEncryptedPrefix[:]) { - flags |= proxyRequestFlagsEncrypted - } - - buf.Write(TagProxyRequest) // nolint: gosec - buf.Write(flags.Bytes()) // nolint: gosec - buf.Write(r.ConnectionID) // nolint: gosec - buf.Write(r.ClientIPPort) // nolint: gosec - buf.Write(r.OurIPPort) // nolint: gosec - buf.Write(ProxyRequestExtraSize) // nolint: gosec - buf.Write(ProxyRequestProxyTag) // nolint: gosec - buf.WriteByte(byte(len(r.ADTag))) // nolint: gosec - buf.Write(r.ADTag) // nolint: gosec - buf.Write(make([]byte, (4-buf.Len()%4)%4)) // nolint: gosec - - return buf, flags -} - -// NewProxyRequest build new ProxyRequest data structure. -func NewProxyRequest(clientAddr, ownAddr *net.TCPAddr, - opts *mtproto.ConnectionOpts, adTag []byte) (*ProxyRequest, error) { - flags := proxyRequestFlagsHasAdTag | proxyRequestFlagsMagic | proxyRequestFlagsExtMode2 - - switch opts.ConnectionType { - case mtproto.ConnectionTypeAbridged: - flags |= proxyRequestFlagsAbdridged - case mtproto.ConnectionTypeIntermediate: - flags |= proxyRequestFlagsIntermediate - case mtproto.ConnectionTypeSecure: - flags |= proxyRequestFlagsIntermediate | proxyRequestFlagsPad - default: - panic("Unknown connection type") - } - - request := &ProxyRequest{ - Flags: flags, - ADTag: adTag, - Options: opts, - ConnectionID: make([]byte, 8), - ClientIPPort: make([]byte, 16+4), - OurIPPort: make([]byte, 16+4), - } - - if _, err := rand.Read(request.ConnectionID); err != nil { - return nil, errors.Annotate(err, "Cannot generate connection ID") - } - - port := [4]byte{} - copy(request.ClientIPPort[:16], clientAddr.IP.To16()) - binary.LittleEndian.PutUint32(port[:], uint32(clientAddr.Port)) - copy(request.ClientIPPort[16:], port[:]) - - copy(request.OurIPPort[:16], ownAddr.IP.To16()) - binary.LittleEndian.PutUint32(port[:], uint32(ownAddr.Port)) - copy(request.OurIPPort[16:], port[:]) - - return request, nil -} diff --git a/_mtproto/rpc/rpc.go b/_mtproto/rpc/rpc.go deleted file mode 100644 index 59512a3..0000000 --- a/_mtproto/rpc/rpc.go +++ /dev/null @@ -1,28 +0,0 @@ -package rpc - -// SeqNo* is the number of the sequence which have special meaning for -// the Telegram. -const ( - SeqNoNonce = -2 - SeqNoHandshake = -1 -) - -// Different constants for RPC protocol -var ( - TagCloseExt = []byte{0xa2, 0x34, 0xb6, 0x5e} - TagProxyAns = []byte{0x0d, 0xda, 0x03, 0x44} - TagSimpleAck = []byte{0x9b, 0x40, 0xac, 0x3b} - TagHandshake = []byte{0xf5, 0xee, 0x82, 0x76} - TagNonce = []byte{0xaa, 0x87, 0xcb, 0x7a} - TagProxyRequest = []byte{0xee, 0xf1, 0xce, 0x36} - - NonceCryptoAES = []byte{0x01, 0x00, 0x00, 0x00} - - HandshakeFlags = []byte{0x00, 0x00, 0x00, 0x00} - - ProxyRequestExtraSize = []byte{0x18, 0x00, 0x00, 0x00} - ProxyRequestProxyTag = []byte{0xae, 0x26, 0x1e, 0xdb} - - HandshakeSenderPID = []byte("IPIPPRPDTIME") - HandshakePeerPID = []byte("IPIPPRPDTIME") -) diff --git a/_obfuscated2/frame.go b/_obfuscated2/frame.go deleted file mode 100644 index 33a8dd4..0000000 --- a/_obfuscated2/frame.go +++ /dev/null @@ -1,121 +0,0 @@ -package obfuscated2 - -import ( - "bytes" - "crypto/rand" - "encoding/binary" - "io" - - "github.com/juju/errors" - - "github.com/9seconds/mtg/mtproto" -) - -// [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd] -const ( - frameLenKey = 32 - frameLenIV = 16 - frameLenMagic = 4 - frameLenDC = 2 - - frameOffsetFirst = 8 - frameOffsetKey = frameOffsetFirst + frameLenKey - frameOffsetIV = frameOffsetKey + frameLenIV - frameOffsetMagic = frameOffsetIV + frameLenMagic - frameOffsetDC = frameOffsetMagic + frameLenDC - - FrameLen = 64 -) - -// Frame represents handshake frame. Telegram sends 64 bytes of obfuscated2 -// initialization data first. -// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/ -type Frame []byte - -// Key returns AES encryption key. -func (f Frame) Key() []byte { - return f[frameOffsetFirst:frameOffsetKey] -} - -// IV returns AES encryption initialization vector -func (f Frame) IV() []byte { - return f[frameOffsetKey:frameOffsetIV] -} - -// Magic returns magic bytes from last 8 bytes of frame. Telegram checks -// for values there. If after decryption magic is not as expected, -// connection considered as failed. -func (f Frame) Magic() []byte { - return f[frameOffsetIV:frameOffsetMagic] -} - -// DC returns number of datacenter IP client wants to use. -func (f Frame) DC() (n int16) { - buf := bytes.NewReader(f[frameOffsetMagic:frameOffsetDC]) - if err := binary.Read(buf, binary.LittleEndian, &n); err != nil { - n = 1 - } - - return -} - -// ConnectionType identifies connection type of the handshake frame. -func (f Frame) ConnectionType() (mtproto.ConnectionType, error) { - return mtproto.ConnectionTagFromHandshake(f.Magic()) -} - -// Invert inverts frame for extracting encryption keys. Pkease check that link: -// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/ -func (f Frame) Invert() Frame { - reversed := make(Frame, FrameLen) - copy(reversed, f) - - for i := 0; i < frameLenKey+frameLenIV; i++ { - reversed[frameOffsetFirst+i] = f[frameOffsetIV-1-i] - } - - return reversed -} - -// ExtractFrame extracts exact obfuscated2 handshake frame from given reader. -func ExtractFrame(conn io.Reader) (Frame, error) { - frame := make(Frame, FrameLen) - buf := bytes.NewBuffer(frame) - buf.Reset() - - if _, err := io.CopyN(buf, conn, FrameLen); err != nil { - return nil, errors.Annotate(err, "Cannot extract obfuscated header") - } - copy(frame, buf.Bytes()) - - return frame, nil -} - -func generateFrame(connectionType mtproto.ConnectionType) Frame { - frame := make(Frame, FrameLen) - - for { - if _, err := rand.Read(frame); err != nil { - continue - } - if frame[0] == 0xef { - continue - } - - val := (uint32(frame[3]) << 24) | (uint32(frame[2]) << 16) | (uint32(frame[1]) << 8) | uint32(frame[0]) - if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee { - continue - } - - val = (uint32(frame[7]) << 24) | (uint32(frame[6]) << 16) | (uint32(frame[5]) << 8) | uint32(frame[4]) - if val == 0x00000000 { - continue - } - - // error has to be checked before calling this function - tag, _ := connectionType.Tag() // nolint: errcheck, gosec - copy(frame.Magic(), tag) - - return frame - } -} diff --git a/_obfuscated2/frame_test.go b/_obfuscated2/frame_test.go deleted file mode 100644 index 0983958..0000000 --- a/_obfuscated2/frame_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package obfuscated2 - -import ( - "bytes" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/9seconds/mtg/mtproto" -) - -func TestFrameKey(t *testing.T) { - toCompare := make([]byte, 32) - for i := 0; i < 32; i++ { - toCompare[i] = byte(1) - } - - assert.Equal(t, toCompare, makeFrame().Key()) -} - -func TestFrameIV(t *testing.T) { - toCompare := make([]byte, 16) - for i := 0; i < 16; i++ { - toCompare[i] = byte(2) - } - - assert.Equal(t, toCompare, makeFrame().IV()) -} - -func TestFrameMagic(t *testing.T) { - toCompare := make([]byte, 4) - for i := 0; i < 4; i++ { - toCompare[i] = 0xee - } - - assert.Equal(t, toCompare, makeFrame().Magic()) -} - -func TestFrameDC(t *testing.T) { - assert.Equal(t, int16(771), makeFrame().DC()) -} - -func TestFrameValid(t *testing.T) { - frame := makeFrame() - connType, err := frame.ConnectionType() - assert.Nil(t, err) - assert.Equal(t, connType, mtproto.ConnectionTypeIntermediate) - - frame[8+32+16+2] = byte(3) - _, err = frame.ConnectionType() - assert.NotNil(t, err) -} - -func TestFrameDoubleInvert(t *testing.T) { - frame := makeFrame() - assert.True(t, bytes.Equal(frame, frame.Invert().Invert())) -} - -func TestFrameInvert(t *testing.T) { - frame := makeFrame() - reversed := frame.Invert() - - assert.Exactly(t, frame[:8], reversed[:8]) - assert.Exactly(t, frame[56:], reversed[56:]) - - toCompare := make([]byte, 48) - for i := 0; i < 48; i++ { - toCompare[i] = frame[55-i] - } - assert.Equal(t, []byte(reversed[8:56]), toCompare) -} - -func TestFrameGenerateValid(t *testing.T) { - validTests := []mtproto.ConnectionType{ - mtproto.ConnectionTypeIntermediate, - mtproto.ConnectionTypeAbridged, - } - for _, test := range validTests { - t.Run(strconv.Itoa(int(test)), func(tt *testing.T) { - frame := generateFrame(test) // nolint: scopelint - conType, err := frame.ConnectionType() - assert.Nil(tt, err) - assert.Equal(tt, conType, test) // nolint: scopelint - }) - } -} - -func makeFrame() Frame { - f := make(Frame, FrameLen) - - for i := 8; i < (8 + 32); i++ { - f[i] = byte(1) - } - for i := (8 + 32); i < (8 + 32 + 16); i++ { - f[i] = byte(2) - } - for i := (8 + 32 + 16); i < (8 + 32 + 16 + 4); i++ { - f[i] = 0xee - } - for i := (8 + 32 + 16 + 4); i < (8 + 32 + 16 + 4 + 2); i++ { - f[i] = byte(3) - } - - return f -} diff --git a/_obfuscated2/obfuscated2.go b/_obfuscated2/obfuscated2.go deleted file mode 100644 index 5fce5dc..0000000 --- a/_obfuscated2/obfuscated2.go +++ /dev/null @@ -1,81 +0,0 @@ -package obfuscated2 - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/sha256" - - "github.com/juju/errors" - - "github.com/9seconds/mtg/mtproto" -) - -// Obfuscated2 contains AES CTR encryption and decryption streams -// for telegram connection. -type Obfuscated2 struct { - Decryptor cipher.Stream - Encryptor cipher.Stream -} - -// ParseObfuscated2ClientFrame parses client frame. Please check this link for -// details: http://telegra.ph/telegram-blocks-wtf-05-26 -// -// Beware, link above is in russian. -func ParseObfuscated2ClientFrame(secret []byte, frame Frame) (*Obfuscated2, *mtproto.ConnectionOpts, error) { - decHasher := sha256.New() - decHasher.Write(frame.Key()) // nolint: errcheck, gosec - decHasher.Write(secret) // nolint: errcheck, gosec - decryptor := makeStreamCipher(decHasher.Sum(nil), frame.IV()) - - invertedFrame := frame.Invert() - encHasher := sha256.New() - encHasher.Write(invertedFrame.Key()) // nolint: errcheck, gosec - encHasher.Write(secret) // nolint: errcheck, gosec - encryptor := makeStreamCipher(encHasher.Sum(nil), invertedFrame.IV()) - - decryptedFrame := make(Frame, FrameLen) - decryptor.XORKeyStream(decryptedFrame, frame) - connType, err := decryptedFrame.ConnectionType() - if err != nil { - return nil, nil, errors.Annotate(err, "Unknown protocol") - } - - obfs := &Obfuscated2{ - Decryptor: decryptor, - Encryptor: encryptor, - } - connOpts := &mtproto.ConnectionOpts{ - DC: decryptedFrame.DC(), - ConnectionType: connType, - } - - return obfs, connOpts, nil -} - -// MakeTelegramObfuscated2Frame creates new handshake frame to send to -// Telegram. -// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/ -func MakeTelegramObfuscated2Frame(opts *mtproto.ConnectionOpts) (*Obfuscated2, Frame) { - frame := generateFrame(opts.ConnectionType) - - encryptor := makeStreamCipher(frame.Key(), frame.IV()) - decryptorFrame := frame.Invert() - decryptor := makeStreamCipher(decryptorFrame.Key(), decryptorFrame.IV()) - - copyFrame := make(Frame, FrameLen) - copy(copyFrame[:frameOffsetIV], frame[:frameOffsetIV]) - encryptor.XORKeyStream(frame, frame) - copy(frame[:frameOffsetIV], copyFrame[:frameOffsetIV]) - - obfs := &Obfuscated2{ - Decryptor: decryptor, - Encryptor: encryptor, - } - - return obfs, frame -} - -func makeStreamCipher(key, iv []byte) cipher.Stream { - block, _ := aes.NewCipher(key) // nolint: gosec - return cipher.NewCTR(block, iv) -} diff --git a/_obfuscated2/obfuscated2_test.go b/_obfuscated2/obfuscated2_test.go deleted file mode 100644 index 7ebe92f..0000000 --- a/_obfuscated2/obfuscated2_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package obfuscated2 - -import ( - "crypto/sha256" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/9seconds/mtg/mtproto" -) - -func TestObfs2TelegramFrameDecrypt(t *testing.T) { - connOpts := &mtproto.ConnectionOpts{ - DC: 1, - ConnectionType: mtproto.ConnectionTypeIntermediate, - } - _, frame := MakeTelegramObfuscated2Frame(connOpts) - decryptor := makeStreamCipher(frame.Key(), frame.IV()) - - decrypted := make(Frame, FrameLen) - decryptor.XORKeyStream(decrypted, frame) - - _, err := decrypted.ConnectionType() - assert.Nil(t, err) -} - -func TestObfs2TelegramDecryptEncryptDecrypt(t *testing.T) { - connOpts := &mtproto.ConnectionOpts{ - DC: 1, - ConnectionType: mtproto.ConnectionTypeIntermediate, - } - obfs2, frame := MakeTelegramObfuscated2Frame(connOpts) - inverted := frame.Invert() - encryptor := makeStreamCipher(inverted.Key(), inverted.IV()) - - data := []byte{1, 2, 3} - encrypted := make([]byte, 3) - encryptor.XORKeyStream(encrypted, data) - decrypted := make([]byte, 3) - obfs2.Decryptor.XORKeyStream(decrypted, encrypted) - - assert.Equal(t, data, decrypted) -} - -func TestObfs2Full(t *testing.T) { - secret := []byte{1, 2, 3, 4, 5} - - clientFrame := generateFrame(mtproto.ConnectionTypeIntermediate) - clientHasher := sha256.New() - clientHasher.Write(clientFrame.Key()) // nolint: errcheck, gosec - clientHasher.Write(secret) // nolint: errcheck, gosec - clientKey := clientHasher.Sum(nil) - - encryptor := makeStreamCipher(clientKey, clientFrame.IV()) - encrypted := make(Frame, FrameLen) - encryptor.XORKeyStream(encrypted, clientFrame) - copy(encrypted[:56], clientFrame[:56]) - - invertedClientFrame := clientFrame.Invert() - clientHasher = sha256.New() - clientHasher.Write(invertedClientFrame.Key()) // nolint: errcheck, gosec - clientHasher.Write(secret) // nolint: errcheck, gosec - invertedClientKey := clientHasher.Sum(nil) - clientDecryptor := makeStreamCipher(invertedClientKey, invertedClientFrame.IV()) - - clientObfs, _, err := ParseObfuscated2ClientFrame(secret, encrypted) - assert.Nil(t, err) - - connOpts := &mtproto.ConnectionOpts{ - DC: 1, - ConnectionType: mtproto.ConnectionTypeIntermediate, - } - tgObfs, tgFrame := MakeTelegramObfuscated2Frame(connOpts) - tgDecryptor := makeStreamCipher(tgFrame.Key(), tgFrame.IV()) - decrypted := make(Frame, FrameLen) - tgDecryptor.XORKeyStream(decrypted, tgFrame) - _, err = decrypted.ConnectionType() - assert.Nil(t, err) - - tgInvertedFrame := tgFrame.Invert() - tgEncryptor := makeStreamCipher(tgInvertedFrame.Key(), tgInvertedFrame.IV()) - - message := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9} - tgEncryptedMessage := make([]byte, len(message)) - tgEncryptor.XORKeyStream(tgEncryptedMessage, message) - - tgEncDecryptedMessage := make([]byte, len(tgEncryptedMessage)) - tgObfs.Decryptor.XORKeyStream(tgEncDecryptedMessage, tgEncryptedMessage) - assert.Equal(t, message, tgEncDecryptedMessage) - - clientEncryptedMessage := make([]byte, len(tgEncDecryptedMessage)) - clientObfs.Encryptor.XORKeyStream(clientEncryptedMessage, tgEncDecryptedMessage) - finalMessage := make([]byte, len(clientEncryptedMessage)) - clientDecryptor.XORKeyStream(finalMessage, clientEncryptedMessage) - - assert.Equal(t, finalMessage, message) -} diff --git a/_proxy/proxy.go b/_proxy/proxy.go deleted file mode 100644 index ecda26a..0000000 --- a/_proxy/proxy.go +++ /dev/null @@ -1,178 +0,0 @@ -package proxy - -import ( - "context" - "io" - "net" - "sync" - - "github.com/gofrs/uuid" - "github.com/juju/errors" - "go.uber.org/zap" - - "github.com/9seconds/mtg/antireplay" - "github.com/9seconds/mtg/client" - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/mtproto" - "github.com/9seconds/mtg/stats" - "github.com/9seconds/mtg/telegram" - "github.com/9seconds/mtg/wrappers" -) - -// Proxy is a core of this program. -type Proxy struct { - antiReplayCache antireplay.Cache - clientInit client.Init - tg telegram.Telegram - conf *config.Config -} - -// Serve runs TCP proxy server. -func (p *Proxy) Serve() error { - lsock, err := net.Listen("tcp", p.conf.BindAddr()) - if err != nil { - return errors.Annotate(err, "Cannot create listen socket") - } - - for { - if conn, err := lsock.Accept(); err != nil { - zap.S().Errorw("Cannot allocate incoming connection", "error", err) - } else { - go p.accept(conn) - } - } -} - -func (p *Proxy) accept(conn net.Conn) { - connID := uuid.Must(uuid.NewV4()).String() - log := zap.S().With("connection_id", connID).Named("main") - ctx, cancel := context.WithCancel(context.Background()) - - defer func() { - cancel() - conn.Close() // nolint: errcheck, gosec - - if err := recover(); err != nil { - stats.NewCrash() - log.Errorw("Crash of accept handler", "error", err) - } - }() - - log.Infow("Client connected", "addr", conn.RemoteAddr()) - - clientConn, opts, err := p.clientInit(ctx, cancel, conn, connID, p.antiReplayCache, p.conf) - if err != nil { - log.Errorw("Cannot initialize client connection", "error", err) - return - } - defer clientConn.(io.Closer).Close() // nolint: errcheck - - if p.conf.SecureOnly && opts.ConnectionType != mtproto.ConnectionTypeSecure { - log.Errorw("Proxy supports only secure connections", "connection_type", opts.ConnectionType) - return - } - - stats.ClientConnected(opts.ConnectionType, clientConn.RemoteAddr()) - defer stats.ClientDisconnected(opts.ConnectionType, clientConn.RemoteAddr()) - - serverConn, err := p.getTelegramConn(ctx, cancel, opts, connID) - if err != nil { - log.Errorw("Cannot initialize server connection", "error", err) - return - } - defer serverConn.(io.Closer).Close() // nolint: errcheck - - go func() { - <-ctx.Done() - serverConn.(io.Closer).Close() // nolint: gosec - clientConn.(io.Closer).Close() // nolint: gosec - }() - - wait := &sync.WaitGroup{} - wait.Add(2) - - if p.conf.UseMiddleProxy() { - clientPacket := clientConn.(wrappers.PacketReadWriteCloser) - serverPacket := serverConn.(wrappers.PacketReadWriteCloser) - go p.middlePipe(clientPacket, serverPacket, wait, &opts.ReadHacks) - p.middlePipe(serverPacket, clientPacket, wait, &opts.WriteHacks) - } else { - clientStream := clientConn.(wrappers.StreamReadWriteCloser) - serverStream := serverConn.(wrappers.StreamReadWriteCloser) - go p.directPipe(clientStream, serverStream, wait, p.conf.ReadBufferSize) - p.directPipe(serverStream, clientStream, wait, p.conf.WriteBufferSize) - } - - wait.Wait() - - log.Infow("Client disconnected", "addr", conn.RemoteAddr()) -} - -func (p *Proxy) getTelegramConn(ctx context.Context, cancel context.CancelFunc, - opts *mtproto.ConnectionOpts, connID string) (wrappers.Wrap, error) { - streamConn, err := p.tg.Dial(ctx, cancel, connID, opts) - if err != nil { - return nil, errors.Annotate(err, "Cannot dial to Telegram") - } - - packetConn, err := p.tg.Init(opts, streamConn) - if err != nil { - return nil, errors.Annotate(err, "Cannot handshake telegram") - } - - return packetConn, nil -} - -func (p *Proxy) middlePipe(src wrappers.PacketReadCloser, dst io.Writer, wait *sync.WaitGroup, hacks *mtproto.Hacks) { - defer wait.Done() - - for { - hacks.SimpleAck = false - hacks.QuickAck = false - - packet, err := src.Read() - if err != nil { - src.Logger().Warnw("Cannot read packet", "error", err) - return - } - if _, err = dst.Write(packet); err != nil { - src.Logger().Warnw("Cannot write packet", "error", err) - return - } - } -} - -func (p *Proxy) directPipe(src wrappers.StreamReadCloser, dst io.Writer, wait *sync.WaitGroup, bufferSize int) { - defer wait.Done() - - buffer := make([]byte, bufferSize) - if _, err := io.CopyBuffer(dst, src, buffer); err != nil { - src.Logger().Warnw("Cannot pump sockets", "error", err) - } -} - -// NewProxy returns new proxy instance. -func NewProxy(conf *config.Config) (*Proxy, error) { - var clientInit client.Init - var tg telegram.Telegram - - cache, err := antireplay.NewCache(conf) - if err != nil { - return nil, errors.Annotate(err, "Cannot make proxy") - } - - if conf.UseMiddleProxy() { - clientInit = client.MiddleInit - tg = telegram.NewMiddleTelegram(conf) - } else { - clientInit = client.DirectInit - tg = telegram.NewDirectTelegram(conf) - } - - return &Proxy{ - antiReplayCache: cache, - conf: conf, - clientInit: clientInit, - tg: tg, - }, nil -} diff --git a/_stats/channels.go b/_stats/channels.go deleted file mode 100644 index f7317db..0000000 --- a/_stats/channels.go +++ /dev/null @@ -1,76 +0,0 @@ -package stats - -import ( - "net" - - "github.com/9seconds/mtg/mtproto" -) - -const ( - connectionsChanLength = 10 - trafficChanLength = 10 -) - -var ( - crashesChan = make(chan struct{}) - statsChan = make(chan chan<- Stats) - connectionsChan = make(chan connectionData, connectionsChanLength) - trafficChan = make(chan trafficData, trafficChanLength) -) - -type connectionData struct { - connectionType mtproto.ConnectionType - connected bool - addr *net.TCPAddr -} - -type trafficData struct { - traffic int - ingress bool -} - -// NewCrash indicates new crash. -func NewCrash() { - crashesChan <- struct{}{} -} - -// ClientConnected indicates that new client was connected. -func ClientConnected(connectionType mtproto.ConnectionType, addr *net.TCPAddr) { - connectionsChan <- connectionData{ - connectionType: connectionType, - addr: addr, - connected: true, - } -} - -// ClientDisconnected indicates that client was disconnected. -func ClientDisconnected(connectionType mtproto.ConnectionType, addr *net.TCPAddr) { - connectionsChan <- connectionData{ - connectionType: connectionType, - addr: addr, - connected: false, - } -} - -// IngressTraffic accounts new ingress traffic. -func IngressTraffic(traffic int) { - trafficChan <- trafficData{ - traffic: traffic, - ingress: true, - } -} - -// EgressTraffic accounts new ingress traffic. -func EgressTraffic(traffic int) { - trafficChan <- trafficData{ - traffic: traffic, - ingress: false, - } -} - -// GetStats returns a snapshot of Stats instance. -func GetStats() Stats { - rpcChan := make(chan Stats) - statsChan <- rpcChan - return <-rpcChan -} diff --git a/_stats/init.go b/_stats/init.go deleted file mode 100644 index ba3640f..0000000 --- a/_stats/init.go +++ /dev/null @@ -1,28 +0,0 @@ -package stats - -import ( - "github.com/juju/errors" - - "github.com/9seconds/mtg/config" -) - -// Init initializes stats subsystem. -func Init(conf *config.Config) error { - if conf.StatsD.Enabled { - client, err := newStatsd(conf) - if err != nil { - return errors.Annotate(err, "Cannot initialize statsd client") - } - go client.run() - } - prometheus, err := newPrometheus(conf) - if err != nil { - return errors.Annotate(err, "Cannot initialize prometheus client") - } - go prometheus.run() - - go NewStats(conf).start() - go startServer(conf, prometheus.getHTTPHandler()) - - return nil -} diff --git a/_stats/prometheus.go b/_stats/prometheus.go deleted file mode 100644 index 00257fa..0000000 --- a/_stats/prometheus.go +++ /dev/null @@ -1,91 +0,0 @@ -package stats - -import ( - "net/http" - "time" - - "github.com/juju/errors" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" - - "github.com/9seconds/mtg/config" -) - -const prometheusPollTime = time.Second - -type prometheusExporter struct { - registry prometheus.Gatherer - - connections *prometheus.GaugeVec - traffic *prometheus.GaugeVec - speed *prometheus.GaugeVec - crashes prometheus.Gauge -} - -func (p *prometheusExporter) run() { - for range time.Tick(prometheusPollTime) { - instance := GetStats() - - p.connections.WithLabelValues("abridged", "v4").Set(float64(instance.Connections.Abridged.IPv4)) - p.connections.WithLabelValues("abridged", "v6").Set(float64(instance.Connections.Abridged.IPv6)) - p.connections.WithLabelValues("intermediate", "v4").Set(float64(instance.Connections.Intermediate.IPv4)) - p.connections.WithLabelValues("intermediate", "v6").Set(float64(instance.Connections.Intermediate.IPv6)) - p.connections.WithLabelValues("secure", "v4").Set(float64(instance.Connections.Secure.IPv4)) - p.connections.WithLabelValues("secure", "v6").Set(float64(instance.Connections.Secure.IPv6)) - p.traffic.WithLabelValues("ingress").Set(float64(instance.Traffic.ingress)) - p.traffic.WithLabelValues("egress").Set(float64(instance.Traffic.egress)) - p.speed.WithLabelValues("ingress").Set(float64(instance.Speed.ingress)) - p.speed.WithLabelValues("egress").Set(float64(instance.Speed.egress)) - p.crashes.Set(float64(instance.Crashes)) - } -} - -func (p *prometheusExporter) getHTTPHandler() http.Handler { - return promhttp.HandlerFor(p.registry, promhttp.HandlerOpts{}) -} - -func newPrometheus(conf *config.Config) (*prometheusExporter, error) { - registry := prometheus.NewRegistry() - - connections := prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: conf.Prometheus.Prefix, - Name: "connections", - Help: "Current number of connections to the proxy.", - }, []string{"type", "protocol"}) - traffic := prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: conf.Prometheus.Prefix, - Name: "traffic", - Help: "Traffic passed through the proxy in bytes.", - }, []string{"direction"}) - speed := prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: conf.Prometheus.Prefix, - Name: "speed", - Help: "Current throughput in bytes per second.", - }, []string{"direction"}) - crashes := prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: conf.Prometheus.Prefix, - Name: "crashes", - Help: "How many crashes happened.", - }) - - if err := registry.Register(connections); err != nil { - return nil, errors.Annotate(err, "Cannot register connections collector") - } - if err := registry.Register(traffic); err != nil { - return nil, errors.Annotate(err, "cannot register traffic collector") - } - if err := registry.Register(speed); err != nil { - return nil, errors.Annotate(err, "cannot register speed collector") - } - if err := registry.Register(crashes); err != nil { - return nil, errors.Annotate(err, "cannot register crashes collector") - } - - return &prometheusExporter{ - registry: registry, - connections: connections, - traffic: traffic, - speed: speed, - crashes: crashes, - }, nil -} diff --git a/_stats/server.go b/_stats/server.go deleted file mode 100644 index cc5f659..0000000 --- a/_stats/server.go +++ /dev/null @@ -1,40 +0,0 @@ -package stats - -import ( - "encoding/json" - "net/http" - - "go.uber.org/zap" - - "github.com/9seconds/mtg/config" -) - -func startServer(conf *config.Config, prometheusHandler http.Handler) { - log := zap.S().Named("stats") - - http.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "application/json") - - first, err := json.Marshal(GetStats()) - if err != nil { - log.Errorw("Cannot encode json", "error", err) - http.Error(w, "Internal server error", 500) - return - } - - interim := map[string]interface{}{} - json.Unmarshal(first, &interim) // nolint: errcheck, gosec - - encoder := json.NewEncoder(w) - encoder.SetEscapeHTML(false) - encoder.SetIndent("", " ") - if err = encoder.Encode(interim); err != nil { - log.Errorw("Cannot encode json", "error", err) - } - }) - http.Handle("/prometheus/", prometheusHandler) - - if err := http.ListenAndServe(conf.StatAddr(), nil); err != nil { - log.Fatalw("Stats server has been stopped", "error", err) - } -} diff --git a/_stats/stats.go b/_stats/stats.go deleted file mode 100644 index 686163d..0000000 --- a/_stats/stats.go +++ /dev/null @@ -1,175 +0,0 @@ -package stats - -import ( - "encoding/json" - "fmt" - "strconv" - "time" - - humanize "github.com/dustin/go-humanize" - - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/mtproto" -) - -type uptime time.Time - -func (u uptime) MarshalJSON() ([]byte, error) { - duration := time.Since(time.Time(u)) - value := map[string]string{ - "seconds": strconv.Itoa(int(duration.Seconds())), - "human": humanize.Time(time.Time(u)), - } - - return json.Marshal(value) -} - -type connectionType struct { - IPv6 uint32 `json:"ipv6"` - IPv4 uint32 `json:"ipv4"` -} - -type baseConnections struct { - All connectionType `json:"all"` - Abridged connectionType `json:"abridged"` - Intermediate connectionType `json:"intermediate"` - Secure connectionType `json:"secure"` -} - -type connections struct { - baseConnections -} - -func (c connections) MarshalJSON() ([]byte, error) { - c.All.IPv4 = c.Abridged.IPv4 + c.Intermediate.IPv4 + c.Secure.IPv4 - c.All.IPv6 = c.Abridged.IPv6 + c.Intermediate.IPv6 + c.Secure.IPv6 - - return json.Marshal(c.baseConnections) -} - -type traffic struct { - ingress uint64 - egress uint64 -} - -func (t *traffic) dumpValue(value uint64) map[string]interface{} { - return map[string]interface{}{ - "bytes": value, - "human": humanize.Bytes(value), - } -} - -func (t traffic) MarshalJSON() ([]byte, error) { - value := map[string]map[string]interface{}{ - "ingress": t.dumpValue(t.ingress), - "egress": t.dumpValue(t.egress), - } - - return json.Marshal(value) -} - -type speed struct { - ingress uint64 - egress uint64 -} - -func (s *speed) dumpValue(value uint64) map[string]interface{} { - return map[string]interface{}{ - "bytes/s": value, - "human": fmt.Sprintf("%s/s", humanize.Bytes(value)), - } -} - -func (s speed) MarshalJSON() ([]byte, error) { - value := map[string]map[string]interface{}{ - "ingress": s.dumpValue(s.ingress), - "egress": s.dumpValue(s.egress), - } - - return json.Marshal(value) -} - -// Stats represents a statistics of the proxy. -type Stats struct { - URLs config.IPURLs `json:"urls"` - Connections connections `json:"connections"` - Traffic traffic `json:"traffic"` - Speed speed `json:"speed"` - Uptime uptime `json:"uptime"` - Crashes uint32 `json:"crashes"` - - previousTraffic traffic -} - -func (s *Stats) start() { - speedChan := time.Tick(time.Second) - - for { - select { - case <-speedChan: - s.handleSpeed() - case event := <-trafficChan: - s.handleTraffic(event) - case event := <-connectionsChan: - s.handleConnection(event) - case getStatsChan := <-statsChan: - s.handleGetStats(getStatsChan) - case <-crashesChan: - s.handleCrash() - } - } -} - -func (s *Stats) handleTraffic(evt trafficData) { - if evt.ingress { - s.Traffic.ingress += uint64(evt.traffic) - } else { - s.Traffic.egress += uint64(evt.traffic) - } -} - -func (s *Stats) handleSpeed() { - s.Speed.ingress = s.Traffic.ingress - s.previousTraffic.ingress - s.Speed.egress = s.Traffic.egress - s.previousTraffic.egress - s.previousTraffic.ingress = s.Traffic.ingress - s.previousTraffic.egress = s.Traffic.egress -} - -func (s *Stats) handleConnection(evt connectionData) { - var inc uint32 = 1 - if !evt.connected { - inc = ^uint32(0) - } - - var conn *connectionType - switch evt.connectionType { - case mtproto.ConnectionTypeAbridged: - conn = &s.Connections.Abridged - case mtproto.ConnectionTypeSecure: - conn = &s.Connections.Secure - default: - conn = &s.Connections.Intermediate - } - - if evt.addr.IP.To4() != nil { - conn.IPv4 += inc - } else { - conn.IPv6 += inc - } -} - -func (s *Stats) handleGetStats(getStatsChan chan<- Stats) { - getStatsChan <- *s -} - -func (s *Stats) handleCrash() { - s.Crashes++ -} - -// NewStats creates a new instance of Stats structure. -func NewStats(conf *config.Config) *Stats { - return &Stats{ - URLs: conf.GetURLs(), - Uptime: uptime(time.Now()), - } -} diff --git a/_stats/statsd.go b/_stats/statsd.go deleted file mode 100644 index 5745889..0000000 --- a/_stats/statsd.go +++ /dev/null @@ -1,77 +0,0 @@ -package stats - -import ( - "time" - - "github.com/juju/errors" - statsd "gopkg.in/alexcesaro/statsd.v2" - - "github.com/9seconds/mtg/config" -) - -const ( - statsdConnectionsAbridgedV4 = "connections.abridged.ipv4" - statsdConnectionsAbridgedV6 = "connections.abridged.ipv6" - - statsdConnectionsIntermediateV4 = "connections.intermediate.ipv4" - statsdConnectionsIntermediateV6 = "connections.intermediate.ipv6" - - statsdConnectionsSecureV4 = "connections.secure.ipv4" - statsdConnectionsSecureV6 = "connections.secure.ipv6" - - statsdTrafficIngress = "traffic.ingress" - statsdTrafficEgress = "traffic.egress" - - statsdSpeedIngress = "speed.ingress" - statsdSpeedEgress = "speed.egress" - - statsdCrashes = "crashes" -) - -const statsdPollTime = time.Second - -type statsdExporter struct { - client *statsd.Client -} - -func (s *statsdExporter) run() { - for range time.Tick(statsdPollTime) { - instance := GetStats() - - s.client.Gauge(statsdConnectionsAbridgedV4, instance.Connections.Abridged.IPv4) - s.client.Gauge(statsdConnectionsAbridgedV6, instance.Connections.Abridged.IPv6) - s.client.Gauge(statsdConnectionsIntermediateV4, instance.Connections.Intermediate.IPv4) - s.client.Gauge(statsdConnectionsIntermediateV6, instance.Connections.Intermediate.IPv6) - s.client.Gauge(statsdConnectionsSecureV4, instance.Connections.Secure.IPv4) - s.client.Gauge(statsdConnectionsSecureV6, instance.Connections.Secure.IPv6) - s.client.Gauge(statsdTrafficIngress, instance.Traffic.ingress) - s.client.Gauge(statsdTrafficEgress, instance.Traffic.egress) - s.client.Gauge(statsdSpeedIngress, instance.Speed.ingress) - s.client.Gauge(statsdSpeedEgress, instance.Speed.egress) - s.client.Gauge(statsdCrashes, instance.Crashes) - } -} - -func newStatsd(conf *config.Config) (*statsdExporter, error) { - options := []statsd.Option{ - statsd.Network(conf.StatsD.Addr.Network()), - statsd.Address(conf.StatsD.Addr.String()), - statsd.Prefix(conf.StatsD.Prefix), - } - - if conf.StatsD.TagsFormat > 0 { - options = append(options, statsd.TagsFormat(conf.StatsD.TagsFormat)) - tags := make([]string, len(conf.StatsD.Tags)*2) - for k, v := range conf.StatsD.Tags { - tags = append(tags, k, v) - } - options = append(options, statsd.Tags(tags...)) - } - - client, err := statsd.New(options...) - if err != nil { - return nil, errors.Annotate(err, "Cannot create statsd client") - } - - return &statsdExporter{client: client}, nil -} diff --git a/_telegram/dialer.go b/_telegram/dialer.go deleted file mode 100644 index 8d192d6..0000000 --- a/_telegram/dialer.go +++ /dev/null @@ -1,52 +0,0 @@ -package telegram - -import ( - "context" - "net" - "time" - - "github.com/juju/errors" - - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/wrappers" -) - -const telegramDialTimeout = 10 * time.Second - -type tgDialer struct { - net.Dialer - - conf *config.Config -} - -func (t *tgDialer) dial(addr string) (net.Conn, error) { - conn, err := t.Dialer.Dial("tcp", addr) - if err != nil { - return nil, errors.Annotate(err, "Cannot connect to Telegram") - } - - tcpSocket := conn.(*net.TCPConn) - if err = tcpSocket.SetNoDelay(true); err != nil { - return nil, errors.Annotate(err, "Cannot set NO_DELAY to Telegram") - } - if err = tcpSocket.SetReadBuffer(t.conf.WriteBufferSize); err != nil { - return nil, errors.Annotate(err, "Cannot set read buffer size on telegram socket") - } - if err = tcpSocket.SetWriteBuffer(t.conf.ReadBufferSize); err != nil { - return nil, errors.Annotate(err, "Cannot set write buffer size on telegram socket") - } - - return conn, nil -} - -func (t *tgDialer) dialRWC(ctx context.Context, cancel context.CancelFunc, - addr, connID string) (wrappers.StreamReadWriteCloser, error) { - conn, err := t.dial(addr) - if err != nil { - return nil, err - } - tgConn := wrappers.NewConn(ctx, cancel, conn, connID, - wrappers.ConnPurposeTelegram, t.conf.PublicIPv4, t.conf.PublicIPv6) - - return tgConn, nil -} diff --git a/_telegram/direct.go b/_telegram/direct.go deleted file mode 100644 index f8a083a..0000000 --- a/_telegram/direct.go +++ /dev/null @@ -1,79 +0,0 @@ -package telegram - -import ( - "context" - "net" - - "github.com/juju/errors" - - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/mtproto" - "github.com/9seconds/mtg/obfuscated2" - "github.com/9seconds/mtg/wrappers" -) - -const ( - directV4DefaultIdx = 1 - directV6DefaultIdx = 1 -) - -var ( - directV4Addresses = map[int16][]string{ - 0: {"149.154.175.50:443"}, - 1: {"149.154.167.51:443"}, - 2: {"149.154.175.100:443"}, - 3: {"149.154.167.91:443"}, - 4: {"149.154.171.5:443"}, - } - directV6Addresses = map[int16][]string{ - 0: {"[2001:b28:f23d:f001::a]:443"}, - 1: {"[2001:67c:04e8:f002::a]:443"}, - 2: {"[2001:b28:f23d:f003::a]:443"}, - 3: {"[2001:67c:04e8:f004::a]:443"}, - 4: {"[2001:b28:f23f:f005::a]:443"}, - } -) - -type directTelegram struct { - baseTelegram -} - -func (t *directTelegram) Dial(ctx context.Context, cancel context.CancelFunc, - connID string, connOpts *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error) { - dc := connOpts.DC - if dc < 0 { - dc = -dc - } else if dc == 0 { - dc = 1 - } - - return t.baseTelegram.dial(ctx, cancel, dc-1, connID, connOpts.ConnectionProto) -} - -func (t *directTelegram) Init(connOpts *mtproto.ConnectionOpts, - conn wrappers.StreamReadWriteCloser) (wrappers.Wrap, error) { - obfs2, frame := obfuscated2.MakeTelegramObfuscated2Frame(connOpts) - - if _, err := conn.Write(frame); err != nil { - return nil, errors.Annotate(err, "Cannot write hadnshake frame") - } - - return wrappers.NewStreamCipher(conn, obfs2.Encryptor, obfs2.Decryptor), nil -} - -// NewDirectTelegram returns Telegram instance which connects directly -// to Telegram bypassing middleproxies. -func NewDirectTelegram(conf *config.Config) Telegram { - return &directTelegram{ - baseTelegram: baseTelegram{ - dialer: tgDialer{ - Dialer: net.Dialer{Timeout: telegramDialTimeout}, - conf: conf, - }, - v4DefaultIdx: directV4DefaultIdx, - v6DefaultIdx: directV6DefaultIdx, - v4Addresses: directV4Addresses, - v6Addresses: directV6Addresses, - }, - } -} diff --git a/_telegram/middle.go b/_telegram/middle.go deleted file mode 100644 index 62ff4ba..0000000 --- a/_telegram/middle.go +++ /dev/null @@ -1,139 +0,0 @@ -package telegram - -import ( - "io" - "net" - "net/http" - "sync" - - "github.com/juju/errors" - - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/mtproto" - "github.com/9seconds/mtg/mtproto/rpc" - "github.com/9seconds/mtg/wrappers" -) - -type middleTelegram struct { - middleTelegramCaller - - conf *config.Config -} - -func (t *middleTelegram) Init(connOpts *mtproto.ConnectionOpts, - conn wrappers.StreamReadWriteCloser) (wrappers.Wrap, error) { - rpcNonceConn := wrappers.NewMTProtoFrame(conn, rpc.SeqNoNonce) - - rpcNonceReq, err := t.sendRPCNonceRequest(rpcNonceConn) - if err != nil { - return nil, err - } - rpcNonceResp, err := t.receiveRPCNonceResponse(rpcNonceConn, rpcNonceReq) - if err != nil { - return nil, err - } - - secureConn := wrappers.NewMiddleProxyCipher(conn, rpcNonceReq, rpcNonceResp, t.proxySecret) - frameConn := wrappers.NewMTProtoFrame(secureConn, rpc.SeqNoHandshake) - - rpcHandshakeReq, err := t.sendRPCHandshakeRequest(frameConn) - if err != nil { - return nil, err - } - _, err = t.receiveRPCHandshakeResponse(frameConn, rpcHandshakeReq) - if err != nil { - return nil, err - } - - proxyConn, err := wrappers.NewMTProtoProxy(frameConn, connOpts, t.conf.AdTag) - if err != nil { - return nil, err - } - proxyConn.Logger().Infow("Telegram connection initialized") - - return proxyConn, nil -} - -func (t *middleTelegram) sendRPCNonceRequest(conn io.Writer) (*rpc.NonceRequest, error) { - rpcNonceReq, err := rpc.NewNonceRequest(t.proxySecret) - if err != nil { - return nil, errors.Annotate(err, "Cannot create RPC nonce request") - } - if _, err = conn.Write(rpcNonceReq.Bytes()); err != nil { - return nil, errors.Annotate(err, "Cannot send RPC nonce request") - } - - return rpcNonceReq, nil -} - -func (t *middleTelegram) receiveRPCNonceResponse(conn wrappers.PacketReader, - req *rpc.NonceRequest) (*rpc.NonceResponse, error) { - packet, err := conn.Read() - if err != nil { - return nil, errors.Annotate(err, "Cannot read RPC nonce response") - } - - rpcNonceResp, err := rpc.NewNonceResponse(packet) - if err != nil { - return nil, errors.Annotate(err, "Cannot initialize RPC nonce response") - } - if err = rpcNonceResp.Valid(req); err != nil { - return nil, errors.Annotate(err, "Invalid RPC nonce response") - } - - return rpcNonceResp, nil -} - -func (t *middleTelegram) sendRPCHandshakeRequest(conn io.Writer) (*rpc.HandshakeRequest, error) { - req := rpc.NewHandshakeRequest() - if _, err := conn.Write(req.Bytes()); err != nil { - return nil, errors.Annotate(err, "Cannot send RPC handshake request") - } - - return req, nil -} - -func (t *middleTelegram) receiveRPCHandshakeResponse(conn wrappers.PacketReader, - req *rpc.HandshakeRequest) (*rpc.HandshakeResponse, error) { - packet, err := conn.Read() - if err != nil { - return nil, errors.Annotate(err, "Cannot read RPC handshake response") - } - - rpcHandshakeResp, err := rpc.NewHandshakeResponse(packet) - if err != nil { - return nil, errors.Annotate(err, "Cannot initialize RPC handshake response") - } - if err = rpcHandshakeResp.Valid(req); err != nil { - return nil, errors.Annotate(err, "Invalid RPC handshake response") - } - - return rpcHandshakeResp, nil -} - -// NewMiddleTelegram creates new instance of Telegram which works with -// middle proxies. -func NewMiddleTelegram(conf *config.Config) Telegram { - tg := &middleTelegram{ - middleTelegramCaller: middleTelegramCaller{ - baseTelegram: baseTelegram{ - dialer: tgDialer{ - Dialer: net.Dialer{Timeout: telegramDialTimeout}, - conf: conf, - }, - }, - httpClient: &http.Client{ - Timeout: middleTelegramHTTPClientTimeout, - }, - dialerMutex: &sync.RWMutex{}, - }, - conf: conf, - } - - if err := tg.update(); err != nil { - panic(err) - } - go tg.autoUpdate() - - return tg -} diff --git a/_telegram/middle_caller.go b/_telegram/middle_caller.go deleted file mode 100644 index 47f0780..0000000 --- a/_telegram/middle_caller.go +++ /dev/null @@ -1,191 +0,0 @@ -package telegram - -import ( - "bufio" - "context" - "io/ioutil" - "net" - "net/http" - "regexp" - "strconv" - "strings" - "sync" - "time" - - "github.com/juju/errors" - "go.uber.org/zap" - - "github.com/9seconds/mtg/mtproto" - "github.com/9seconds/mtg/wrappers" -) - -const ( - middleTelegramAutoUpdateInterval = 6 * time.Hour - middleTelegramHTTPClientTimeout = 30 * time.Second - - tgAddrProxySecret = "https://core.telegram.org/getProxySecret" // nolint: gas - tgAddrProxyV4 = "https://core.telegram.org/getProxyConfig" // nolint: gas - tgAddrProxyV6 = "https://core.telegram.org/getProxyConfigV6" // nolint: gas - tgUserAgent = "mtg" -) - -var middleTelegramProxyConfigSplitter = regexp.MustCompile(`\s+`) - -type middleTelegramCaller struct { - baseTelegram - - proxySecret []byte - dialerMutex *sync.RWMutex - httpClient *http.Client -} - -func (t *middleTelegramCaller) Dial(ctx context.Context, cancel context.CancelFunc, connID string, - connOpts *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error) { - dc := connOpts.DC - if dc == 0 { - dc = 1 - } - t.dialerMutex.RLock() - defer t.dialerMutex.RUnlock() - - return t.baseTelegram.dial(ctx, cancel, dc, connID, connOpts.ConnectionProto) -} - -func (t *middleTelegramCaller) autoUpdate() { - for range time.Tick(middleTelegramAutoUpdateInterval) { - if err := t.update(); err != nil { - zap.S().Warnw("Cannot update from Telegram", "error", err) - } - } -} - -func (t *middleTelegramCaller) update() error { - secret, err := t.getTelegramProxySecret() - if err != nil { - return errors.Annotate(err, "Cannot get proxy secret") - } - - v4Addresses, v4DefaultIdx, err := t.getTelegramAddresses(tgAddrProxyV4) - if err != nil { - return errors.Annotate(err, "Cannot get ipv4 addresses") - } - - v6Addresses, v6DefaultIdx, err := t.getTelegramAddresses(tgAddrProxyV6) - if err != nil { - return errors.Annotate(err, "Cannot get ipv6 addresses") - } - - t.dialerMutex.Lock() - t.proxySecret = secret - t.v4DefaultIdx = v4DefaultIdx - t.v6DefaultIdx = v6DefaultIdx - t.v4Addresses = v4Addresses - t.v6Addresses = v6Addresses - t.dialerMutex.Unlock() - - zap.S().Infow("Telegram middle proxy data has been updated") - - return nil -} - -func (t *middleTelegramCaller) getTelegramProxySecret() ([]byte, error) { - resp, err := t.call(tgAddrProxySecret) - if err != nil { - return nil, errors.Annotate(err, "Cannot access telegram server") - } - defer resp.Body.Close() // nolint: errcheck - - secret, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, errors.Annotate(err, "Cannot read response") - } - - return secret, nil -} - -func (t *middleTelegramCaller) getTelegramAddresses(url string) (map[int16][]string, int16, error) { // nolint: gocyclo - resp, err := t.call(url) - if err != nil { - return nil, 0, errors.Annotate(err, "Cannot access telegram server") - } - defer resp.Body.Close() // nolint: errcheck - - scanner := bufio.NewScanner(resp.Body) - data := map[int16][]string{} - - var defaultIdx int16 = 1 - for scanner.Scan() { - text := strings.TrimSpace(scanner.Text()) - switch { - case strings.HasPrefix(text, "#"): - continue - case strings.HasPrefix(text, "proxy_for"): - addr, idx, err2 := t.parseProxyFor(text) - if err2 != nil { - return nil, 0, errors.Annotate(err2, "Cannot parse 'proxy_for' section") - } - if addresses, ok := data[idx]; ok { - data[idx] = append(addresses, addr) - } else { - data[idx] = []string{addr} - } - case strings.HasPrefix(text, "default"): - idx, err2 := t.parseDefault(text) - if err2 != nil { - return nil, 0, errors.Annotate(err2, "Cannot parse 'default' section") - } - defaultIdx = idx - default: - return nil, 0, errors.Errorf("Unknown config string '%s'", text) - } - } - - err = scanner.Err() - if err != nil { - return nil, 0, errors.Annotate(err, "Cannot read response from the telegram") - } - - return data, defaultIdx, nil -} - -func (t *middleTelegramCaller) parseProxyFor(text string) (string, int16, error) { - chunks := middleTelegramProxyConfigSplitter.Split(text, 3) - if len(chunks) != 3 || chunks[0] != "proxy_for" { - return "", 0, errors.Errorf("Incorrect config '%s'", text) - } - - dcIdx, err := strconv.ParseInt(chunks[1], 10, 16) - if err != nil { - return "", 0, errors.Annotatef(err, "Incorrect config '%s'", text) - } - - addr := strings.TrimRight(chunks[2], ";") - if _, _, err = net.SplitHostPort(addr); err != nil { - return "", 0, errors.Annotatef(err, "Incorrect config '%s'", text) - } - - return addr, int16(dcIdx), nil -} - -func (t *middleTelegramCaller) parseDefault(text string) (int16, error) { - chunks := middleTelegramProxyConfigSplitter.Split(text, 2) - if len(chunks) != 2 || chunks[0] != "default" { - return 0, errors.Errorf("Incorrect config '%s'", text) - } - - dcIdxString := strings.TrimRight(chunks[1], ";") - dcIdx, err := strconv.ParseInt(dcIdxString, 10, 16) - if err != nil { - return 0, errors.Annotatef(err, "Incorrect config '%s'", text) - } - - return int16(dcIdx), nil -} - -func (t *middleTelegramCaller) call(url string) (*http.Response, error) { - req, _ := http.NewRequest("GET", url, nil) // nolint: gosec - req.Header.Set("Accept", "text/plain") - req.Header.Set("User-Agent", tgUserAgent) - - return t.httpClient.Do(req) -} diff --git a/_telegram/telegram.go b/_telegram/telegram.go deleted file mode 100644 index c7fd41e..0000000 --- a/_telegram/telegram.go +++ /dev/null @@ -1,68 +0,0 @@ -package telegram - -import ( - "context" - "math/rand" - - "github.com/juju/errors" - - "github.com/9seconds/mtg/mtproto" - "github.com/9seconds/mtg/wrappers" -) - -// Telegram is an interface for different Telegram work modes. -type Telegram interface { - Dial(context.Context, context.CancelFunc, string, *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error) - Init(*mtproto.ConnectionOpts, wrappers.StreamReadWriteCloser) (wrappers.Wrap, error) -} - -type baseTelegram struct { - dialer tgDialer - - v4DefaultIdx int16 - v6DefaultIdx int16 - v4Addresses map[int16][]string - v6Addresses map[int16][]string -} - -func (b *baseTelegram) dial(ctx context.Context, cancel context.CancelFunc, dcIdx int16, connID string, - proto mtproto.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) { - addrs := make([]string, 2) - - if proto&mtproto.ConnectionProtocolIPv6 != 0 { - if addr := b.chooseAddress(b.v6Addresses, dcIdx, b.v6DefaultIdx); addr != "" { - addrs = append(addrs, addr) - } - } - if proto&mtproto.ConnectionProtocolIPv4 != 0 { - if addr := b.chooseAddress(b.v4Addresses, dcIdx, b.v4DefaultIdx); addr != "" { - addrs = append(addrs, addr) - } - } - - for _, addr := range addrs { - if conn, err := b.dialer.dialRWC(ctx, cancel, addr, connID); err == nil { - return conn, err - } - } - - return nil, errors.New("Cannot connect to Telegram") -} - -func (b *baseTelegram) chooseAddress(addresses map[int16][]string, idx, defaultIdx int16) string { - if addr, ok := addresses[idx]; ok { - return b.chooseRandomAddress(addr) - } else if addr, ok := addresses[defaultIdx]; ok { - return b.chooseRandomAddress(addr) - } - - return "" -} - -func (b *baseTelegram) chooseRandomAddress(addresses []string) string { - if len(addresses) > 0 { - return addresses[rand.Intn(len(addresses))] - } - - return "" -} diff --git a/_utils/read_current_data.go b/_utils/read_current_data.go deleted file mode 100644 index 284369c..0000000 --- a/_utils/read_current_data.go +++ /dev/null @@ -1,21 +0,0 @@ -package utils - -import "io" - -const readCurrentDataBufferSize = 1024 + 1 // + 1 because telegram operates with blocks mod 4 - -// ReadCurrentData reads all data from io.Reader which is ready to be read. -func ReadCurrentData(src io.Reader) (rv []byte, err error) { - buf := make([]byte, readCurrentDataBufferSize) - n := readCurrentDataBufferSize - - for n == len(buf) { - n, err = src.Read(buf) - if err != nil { - return nil, err - } - rv = append(rv, buf[:n]...) - } - - return rv, nil -} diff --git a/_utils/reverse_bytes.go b/_utils/reverse_bytes.go deleted file mode 100644 index ab7cd5b..0000000 --- a/_utils/reverse_bytes.go +++ /dev/null @@ -1,15 +0,0 @@ -package utils - -// ReverseBytes is a common slice reverser. -func ReverseBytes(data []byte) []byte { - dataLen := len(data) - rv := make([]byte, dataLen) - - rv[dataLen/2] = data[dataLen/2] - for i := dataLen/2 - 1; i >= 0; i-- { - opp := dataLen - i - 1 - rv[i], rv[opp] = data[opp], data[i] - } - - return rv -} diff --git a/_utils/uint24.go b/_utils/uint24.go deleted file mode 100644 index 66c5f1b..0000000 --- a/_utils/uint24.go +++ /dev/null @@ -1,15 +0,0 @@ -package utils - -// Uint24 is a replacement for the absent Go uint24 data type. -// This data type is little endian. -type Uint24 [3]byte - -// ToUint24 converts number to Uint24. -func ToUint24(number uint32) Uint24 { - return Uint24{byte(number), byte(number >> 8), byte(number >> 16)} -} - -// FromUint24 converts Uint24 to number. -func FromUint24(number Uint24) uint32 { - return uint32(number[0]) + (uint32(number[1]) << 8) + (uint32(number[2]) << 16) -} diff --git a/_wrappers/blockcipher.go b/_wrappers/blockcipher.go deleted file mode 100644 index 745efca..0000000 --- a/_wrappers/blockcipher.go +++ /dev/null @@ -1,99 +0,0 @@ -package wrappers - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "net" - - "go.uber.org/zap" - - "github.com/9seconds/mtg/utils" - "github.com/juju/errors" -) - -// BlockCipher is a stream writer which encrypts/decrypts blocks of data -// with AES CBC. This also is buffered reader. It means, that block -// reading is transparent for it, you can assume you are working with -// good old io.Reader. -type BlockCipher struct { - buf *bytes.Buffer - - logger *zap.SugaredLogger - conn StreamReadWriteCloser - encryptor cipher.BlockMode - decryptor cipher.BlockMode -} - -func (b *BlockCipher) Read(p []byte) (int, error) { - if b.buf.Len() > 0 { - return b.flush(p) - } - - buf := []byte{} - for len(buf) == 0 || len(buf)%aes.BlockSize != 0 { - rv, err := utils.ReadCurrentData(b.conn) - if err != nil { - return 0, errors.Annotate(err, "Cannot read from socket") - } - buf = append(buf, rv...) - } - - b.decryptor.CryptBlocks(buf, buf) - b.buf.Write(buf) // nolint: gosec - - return b.flush(p) -} - -func (b *BlockCipher) flush(p []byte) (int, error) { - if b.buf.Len() <= len(p) { - sizeToReturn := b.buf.Len() - copy(p, b.buf.Bytes()) - b.buf.Reset() - return sizeToReturn, nil - } - - return b.buf.Read(p) -} - -func (b *BlockCipher) Write(p []byte) (int, error) { - if len(p)%aes.BlockSize > 0 { - return 0, errors.Errorf("Incorrect block size %d", len(p)) - } - - encrypted := make([]byte, len(p)) - b.encryptor.CryptBlocks(encrypted, p) - - return b.conn.Write(encrypted) -} - -// Logger returns an instance of the logger for this wrapper. -func (b *BlockCipher) Logger() *zap.SugaredLogger { - return b.logger -} - -// LocalAddr returns local address of the underlying net.Conn. -func (b *BlockCipher) LocalAddr() *net.TCPAddr { - return b.conn.LocalAddr() -} - -// RemoteAddr returns remote address of the underlying net.Conn. -func (b *BlockCipher) RemoteAddr() *net.TCPAddr { - return b.conn.RemoteAddr() -} - -// Close closes underlying net.Conn. -func (b *BlockCipher) Close() error { - return b.conn.Close() -} - -// NewBlockCipher creates new instance of BlockCipher based on given data. -func NewBlockCipher(conn StreamReadWriteCloser, encryptor, decryptor cipher.BlockMode) StreamReadWriteCloser { - return &BlockCipher{ - buf: &bytes.Buffer{}, - conn: conn, - logger: conn.Logger().Named("block-cipher"), - encryptor: encryptor, - decryptor: decryptor, - } -} diff --git a/_wrappers/conn.go b/_wrappers/conn.go deleted file mode 100644 index 239af53..0000000 --- a/_wrappers/conn.go +++ /dev/null @@ -1,154 +0,0 @@ -package wrappers - -import ( - "context" - "net" - "time" - - "go.uber.org/zap" - - "github.com/9seconds/mtg/stats" - "github.com/juju/errors" -) - -// ConnPurpose is intended to be identifier of connection purpose. We -// sometimes want to treat client/telegram connection differently (for -// logging for example). -type ConnPurpose uint8 - -func (c ConnPurpose) String() string { - switch c { - case ConnPurposeClient: - return "client" - case ConnPurposeTelegram: - return "telegram" - } - - return "" -} - -// ConnPurpose* define different connection types. -const ( - ConnPurposeClient = iota - ConnPurposeTelegram -) - -const ( - connTimeoutRead = 2 * time.Minute - connTimeoutWrite = 2 * time.Minute -) - -// Conn is a basic wrapper for net.Conn providing the most low-level -// logic and management as possible. -type Conn struct { - conn net.Conn - ctx context.Context - cancel context.CancelFunc - connID string - logger *zap.SugaredLogger - - publicIPv4 net.IP - publicIPv6 net.IP -} - -func (c *Conn) Write(p []byte) (int, error) { - select { - case <-c.ctx.Done(): - c.Close() // nolint: gosec - return 0, errors.Annotate(c.ctx.Err(), "Cannot write because context was closed") - default: - if err := c.conn.SetWriteDeadline(time.Now().Add(connTimeoutWrite)); err != nil { - c.Close() // nolint: gosec - return 0, errors.Annotate(err, "Cannot set write deadline to the socket") - } - - n, err := c.conn.Write(p) - c.logger.Debugw("Write to stream", "bytes", n, "error", err) - stats.EgressTraffic(n) - if err != nil { - c.Close() // nolint: gosec - } - - return n, err - } -} - -func (c *Conn) Read(p []byte) (int, error) { - select { - case <-c.ctx.Done(): - c.Close() // nolint: gosec - return 0, errors.Annotate(c.ctx.Err(), "Cannot read because context was closed") - default: - if err := c.conn.SetReadDeadline(time.Now().Add(connTimeoutRead)); err != nil { - c.Close() // nolint: gosec - return 0, errors.Annotate(err, "Cannot set read deadline to the socket") - } - - n, err := c.conn.Read(p) - c.logger.Debugw("Read from stream", "bytes", n, "error", err) - stats.IngressTraffic(n) - if err != nil { - c.Close() // nolint: gosec - } - - return n, err - } -} - -// Close closes underlying net.Conn instance. -func (c *Conn) Close() error { - c.logger.Debugw("Close connection") - c.cancel() - - return c.conn.Close() -} - -// Logger returns an instance of the logger for this wrapper. -func (c *Conn) Logger() *zap.SugaredLogger { - return c.logger -} - -// LocalAddr returns local address of the underlying net.Conn. -func (c *Conn) LocalAddr() *net.TCPAddr { - addr := c.conn.LocalAddr().(*net.TCPAddr) - newAddr := *addr - - if c.RemoteAddr().IP.To4() != nil { - if c.publicIPv4 != nil { - newAddr.IP = c.publicIPv4 - } - } else if c.publicIPv6 != nil { - newAddr.IP = c.publicIPv6 - } - - return &newAddr -} - -// RemoteAddr returns remote address of the underlying net.Conn. -func (c *Conn) RemoteAddr() *net.TCPAddr { - return c.conn.RemoteAddr().(*net.TCPAddr) -} - -// NewConn initializes Conn wrapper for net.Conn. -func NewConn(ctx context.Context, cancel context.CancelFunc, conn net.Conn, - connID string, purpose ConnPurpose, publicIPv4, publicIPv6 net.IP) StreamReadWriteCloser { - logger := zap.S().With( - "connection_id", connID, - "local_address", conn.LocalAddr(), - "remote_address", conn.RemoteAddr(), - "purpose", purpose, - ).Named("conn") - - wrapper := Conn{ - conn: conn, - ctx: ctx, - cancel: cancel, - connID: connID, - logger: logger, - publicIPv4: publicIPv4, - publicIPv6: publicIPv6, - } - wrapper.logger = logger.With("faked_local_addr", wrapper.LocalAddr()) - - return &wrapper -} diff --git a/_wrappers/mtproto_abridged.go b/_wrappers/mtproto_abridged.go deleted file mode 100644 index 6332af6..0000000 --- a/_wrappers/mtproto_abridged.go +++ /dev/null @@ -1,159 +0,0 @@ -package wrappers - -import ( - "bytes" - "io" - "net" - - "github.com/juju/errors" - "go.uber.org/zap" - - "github.com/9seconds/mtg/mtproto" - "github.com/9seconds/mtg/utils" -) - -const ( - mtprotoAbridgedSmallPacketLength = 0x7f - mtprotoAbridgedQuickAckLength = 0x80 - mtprotoAbridgedLargePacketLength = 16777216 // 256 ^ 3 -) - -// MTProtoAbridged presents abridged connection between client and -// middle proxy. -type MTProtoAbridged struct { - conn StreamReadWriteCloser - opts *mtproto.ConnectionOpts - logger *zap.SugaredLogger - - readCounter uint32 - writeCounter uint32 -} - -func (m *MTProtoAbridged) Read() ([]byte, error) { - defer func() { - m.readCounter++ - }() - - m.logger.Debugw("Read packet", - "simple_ack", m.opts.ReadHacks.SimpleAck, - "quick_ack", m.opts.ReadHacks.QuickAck, - "counter", m.readCounter, - ) - - buf := &bytes.Buffer{} - buf.Grow(3) - - if _, err := io.CopyN(buf, m.conn, 1); err != nil { - return nil, errors.Annotate(err, "Cannot read message length") - } - msgLength := uint32(buf.Bytes()[0]) - buf.Reset() - - m.logger.Debugw("Packet first byte", - "byte", msgLength, - "counter", m.readCounter, - "simple_ack", m.opts.ReadHacks.SimpleAck, - "quick_ack", m.opts.ReadHacks.QuickAck, - ) - - if msgLength >= mtprotoAbridgedQuickAckLength { - m.opts.ReadHacks.QuickAck = true - msgLength -= mtprotoAbridgedQuickAckLength - } - - if msgLength == mtprotoAbridgedSmallPacketLength { - if _, err := io.CopyN(buf, m.conn, 3); err != nil { - return nil, errors.Annotate(err, "Cannot read the correct message length") - } - number := utils.Uint24{} - copy(number[:], buf.Bytes()) - msgLength = utils.FromUint24(number) - } - msgLength *= 4 - - m.logger.Debugw("Packet length", - "length", msgLength, - "simple_ack", m.opts.ReadHacks.SimpleAck, - "quick_ack", m.opts.ReadHacks.QuickAck, - "counter", m.readCounter, - ) - - buf.Reset() - buf.Grow(int(msgLength)) - if _, err := io.CopyN(buf, m.conn, int64(msgLength)); err != nil { - return nil, errors.Annotate(err, "Cannot read message") - } - - return buf.Bytes(), nil -} - -func (m *MTProtoAbridged) Write(p []byte) (int, error) { - defer func() { - m.writeCounter++ - }() - - m.logger.Debugw("Write packet", - "length", len(p), - "simple_ack", m.opts.WriteHacks.SimpleAck, - "quick_ack", m.opts.WriteHacks.QuickAck, - "counter", m.writeCounter, - ) - - if len(p)%4 != 0 { - return 0, errors.Errorf("Incorrect packet length %d", len(p)) - } - - if m.opts.WriteHacks.SimpleAck { - return m.conn.Write(utils.ReverseBytes(p)) - } - - packetLength := len(p) / 4 - switch { - case packetLength < mtprotoAbridgedSmallPacketLength: - newData := append([]byte{byte(packetLength)}, p...) - return m.conn.Write(newData) - - case packetLength < mtprotoAbridgedLargePacketLength: - length24 := utils.ToUint24(uint32(packetLength)) - - buf := &bytes.Buffer{} - buf.Grow(1 + 3 + len(p)) - - buf.WriteByte(byte(mtprotoAbridgedSmallPacketLength)) // nolint: gosec - buf.Write(length24[:]) // nolint: gosec - buf.Write(p) // nolint: gosec - - return m.conn.Write(buf.Bytes()) - } - - return 0, errors.Errorf("Packet is too big %d", len(p)) -} - -// Logger returns an instance of the logger for this wrapper. -func (m *MTProtoAbridged) Logger() *zap.SugaredLogger { - return m.logger -} - -// LocalAddr returns local address of the underlying net.Conn. -func (m *MTProtoAbridged) LocalAddr() *net.TCPAddr { - return m.conn.LocalAddr() -} - -// RemoteAddr returns remote address of the underlying net.Conn. -func (m *MTProtoAbridged) RemoteAddr() *net.TCPAddr { - return m.conn.RemoteAddr() -} - -// Close closes underlying net.Conn instance. -func (m *MTProtoAbridged) Close() error { - return m.conn.Close() -} - -// NewMTProtoAbridged creates new wrapper for abridged client connection. -func NewMTProtoAbridged(conn StreamReadWriteCloser, opts *mtproto.ConnectionOpts) PacketReadWriteCloser { - return &MTProtoAbridged{ - conn: conn, - opts: opts, - logger: conn.Logger().Named("mtproto-abridged"), - } -} diff --git a/_wrappers/mtproto_cipher.go b/_wrappers/mtproto_cipher.go deleted file mode 100644 index 2f65b0b..0000000 --- a/_wrappers/mtproto_cipher.go +++ /dev/null @@ -1,98 +0,0 @@ -package wrappers - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/md5" // nolint: gas - "crypto/sha1" // nolint: gosec - "encoding/binary" - "net" - - "github.com/9seconds/mtg/mtproto/rpc" - "github.com/9seconds/mtg/utils" -) - -type cipherPurpose uint8 - -const ( - cipherPurposeClient cipherPurpose = iota - cipherPurposeServer -) - -var emptyIP = [4]byte{0x00, 0x00, 0x00, 0x00} - -// NewMiddleProxyCipher creates new block cipher to proxy<->telegram -// connection. -func NewMiddleProxyCipher(conn StreamReadWriteCloser, - req *rpc.NonceRequest, resp *rpc.NonceResponse, secret []byte) StreamReadWriteCloser { - localAddr := conn.LocalAddr() - remoteAddr := conn.RemoteAddr() - - encKey, encIV := deriveKeys(cipherPurposeClient, req, resp, localAddr, remoteAddr, secret) - decKey, decIV := deriveKeys(cipherPurposeServer, req, resp, localAddr, remoteAddr, secret) - - enc, _ := makeEncrypterDecrypter(encKey, encIV) - _, dec := makeEncrypterDecrypter(decKey, decIV) - - return NewBlockCipher(conn, enc, dec) -} - -func deriveKeys(purpose cipherPurpose, req *rpc.NonceRequest, resp *rpc.NonceResponse, - client, remote *net.TCPAddr, secret []byte) ([]byte, []byte) { - message := bytes.Buffer{} - message.Write(resp.Nonce) // nolint: gosec - message.Write(req.Nonce) // nolint: gosec - message.Write(req.CryptoTS) // nolint: gosec - - clientIPv4 := emptyIP[:] - serverIPv4 := emptyIP[:] - if client.IP.To4() != nil { - clientIPv4 = utils.ReverseBytes(client.IP.To4()) - serverIPv4 = utils.ReverseBytes(remote.IP.To4()) - } - message.Write(serverIPv4) // nolint: gosec - - var port [2]byte - binary.LittleEndian.PutUint16(port[:], uint16(client.Port)) - message.Write(port[:]) // nolint: gosec - - switch purpose { - case cipherPurposeClient: - message.WriteString("CLIENT") // nolint: gosec - case cipherPurposeServer: - message.WriteString("SERVER") // nolint: gosec - default: - panic("Unexpected cipher purpose") - } - - message.Write(clientIPv4) // nolint: gosec - binary.LittleEndian.PutUint16(port[:], uint16(remote.Port)) - message.Write(port[:]) // nolint: gosec - message.Write(secret) // nolint: gosec - message.Write(resp.Nonce) // nolint: gosec - - if client.IP.To4() == nil { - message.Write(client.IP.To16()) // nolint: gosec - message.Write(remote.IP.To16()) // nolint: gosec - } - message.Write(req.Nonce) // nolint: gosec - - data := message.Bytes() - md5sum := md5.Sum(data[1:]) // nolint: gas - sha1sum := sha1.Sum(data) // nolint: gosec - - key := append(md5sum[:12], sha1sum[:]...) - iv := md5.Sum(data[2:]) // nolint: gas - - return key, iv[:] -} - -func makeEncrypterDecrypter(key, iv []byte) (cipher.BlockMode, cipher.BlockMode) { - block, err := aes.NewCipher(key) - if err != nil { - panic(err) - } - - return cipher.NewCBCEncrypter(block, iv), cipher.NewCBCDecrypter(block, iv) -} diff --git a/_wrappers/mtproto_frame.go b/_wrappers/mtproto_frame.go deleted file mode 100644 index 2e39d6b..0000000 --- a/_wrappers/mtproto_frame.go +++ /dev/null @@ -1,161 +0,0 @@ -package wrappers - -import ( - "bytes" - "crypto/aes" - "encoding/binary" - "hash/crc32" - "io" - "io/ioutil" - "net" - - "github.com/juju/errors" - "go.uber.org/zap" -) - -const ( - mtprotoFrameMinMessageLength = 12 - mtprotoFrameMaxMessageLength = 16777216 -) - -var mtprotoFramePadding = []byte{0x04, 0x00, 0x00, 0x00} - -// MTProtoFrame is a wrapper which converts written data to the MTProtoFrame. -// The format of the frame: -// -// [ MSGLEN(4) | SEQNO(4) | MSG(...) | CRC32(4) | PADDING(4*x) ] -// -// MSGLEN is the length of the message + len of seqno and msglen. -// SEQNO is the number of frame in the receive/send sequence. If client -// sends a message with SeqNo 18, it has to receive message with SeqNo 18. -// MSG is the data which has to be written -// CRC32 is the CRC32 checksum of MSGLEN + SEQNO + MSG -// PADDING is custom padding schema to complete frame length to such that -// len(frame) % 16 == 0 -type MTProtoFrame struct { - conn StreamReadWriteCloser - logger *zap.SugaredLogger - - readSeqNo int32 - writeSeqNo int32 -} - -func (m *MTProtoFrame) Read() ([]byte, error) { // nolint: gocyclo - buf := &bytes.Buffer{} - sum := crc32.NewIEEE() - writer := io.MultiWriter(buf, sum) - - for { - buf.Reset() - sum.Reset() - if _, err := io.CopyN(writer, m.conn, 4); err != nil { - return nil, errors.Annotate(err, "Cannot read frame padding") - } - if !bytes.Equal(buf.Bytes(), mtprotoFramePadding) { - break - } - } - - messageLength := binary.LittleEndian.Uint32(buf.Bytes()) - m.logger.Debugw("Read MTProto frame", - "messageLength", messageLength, - "sequence_number", m.readSeqNo, - ) - if messageLength%4 != 0 || messageLength < mtprotoFrameMinMessageLength || - messageLength > mtprotoFrameMaxMessageLength { - return nil, errors.Errorf("Incorrect frame message length %d", messageLength) - } - - buf.Reset() - buf.Grow(int(messageLength) - 4 - 4) - if _, err := io.CopyN(writer, m.conn, int64(messageLength)-4-4); err != nil { - return nil, errors.Annotate(err, "Cannot read the message frame") - } - - var seqNo int32 - binary.Read(buf, binary.LittleEndian, &seqNo) // nolint: errcheck, gosec - if seqNo != m.readSeqNo { - return nil, errors.Errorf("Unexpected sequence number %d (wait for %d)", seqNo, m.readSeqNo) - } - - data, _ := ioutil.ReadAll(buf) // nolint: gosec - buf.Reset() - // write to buf, not to writer. This is because we are going to fetch - // crc32 checksum. - if _, err := io.CopyN(buf, m.conn, 4); err != nil { - return nil, errors.Annotate(err, "Cannot read checksum") - } - - checksum := binary.LittleEndian.Uint32(buf.Bytes()) - if checksum != sum.Sum32() { - return nil, errors.Errorf("CRC32 checksum mismatch. Wait for %d, got %d", sum.Sum32(), checksum) - } - - m.logger.Debugw("Read MTProto frame", - "messageLength", messageLength, - "sequence_number", m.readSeqNo, - "dataLength", len(data), - "checksum", checksum, - ) - m.readSeqNo++ - - return data, nil -} - -func (m *MTProtoFrame) Write(p []byte) (int, error) { - messageLength := 4 + 4 + len(p) + 4 - paddingLength := (aes.BlockSize - messageLength%aes.BlockSize) % aes.BlockSize - - buf := &bytes.Buffer{} - buf.Grow(messageLength + paddingLength) - - binary.Write(buf, binary.LittleEndian, uint32(messageLength)) // nolint: errcheck, gosec - binary.Write(buf, binary.LittleEndian, m.writeSeqNo) // nolint: errcheck, gosec - buf.Write(p) // nolint: gosec - - checksum := crc32.ChecksumIEEE(buf.Bytes()) - binary.Write(buf, binary.LittleEndian, checksum) // nolint: errcheck, gosec - buf.Write(bytes.Repeat(mtprotoFramePadding, paddingLength/4)) // nolint: gosec - - m.logger.Debugw("Write MTProto frame", - "length", len(p), - "sequence_number", m.writeSeqNo, - "crc32", checksum, - "frame_length", buf.Len(), - ) - m.writeSeqNo++ - - _, err := m.conn.Write(buf.Bytes()) - - return len(p), err -} - -// Logger returns an instance of the logger for this wrapper. -func (m *MTProtoFrame) Logger() *zap.SugaredLogger { - return m.logger -} - -// LocalAddr returns local address of the underlying net.Conn. -func (m *MTProtoFrame) LocalAddr() *net.TCPAddr { - return m.conn.LocalAddr() -} - -// RemoteAddr returns remote address of the underlying net.Conn. -func (m *MTProtoFrame) RemoteAddr() *net.TCPAddr { - return m.conn.RemoteAddr() -} - -// Close closes underlying net.Conn instance. -func (m *MTProtoFrame) Close() error { - return m.conn.Close() -} - -// NewMTProtoFrame creates new PacketWrapper for underlying connection. -func NewMTProtoFrame(conn StreamReadWriteCloser, seqNo int32) PacketReadWriteCloser { - return &MTProtoFrame{ - conn: conn, - logger: conn.Logger().Named("mtproto-frame"), - readSeqNo: seqNo, - writeSeqNo: seqNo, - } -} diff --git a/_wrappers/mtproto_intermediate.go b/_wrappers/mtproto_intermediate.go deleted file mode 100644 index 5b88d74..0000000 --- a/_wrappers/mtproto_intermediate.go +++ /dev/null @@ -1,117 +0,0 @@ -package wrappers - -import ( - "bytes" - "encoding/binary" - "io" - "net" - - "github.com/juju/errors" - "go.uber.org/zap" - - "github.com/9seconds/mtg/mtproto" -) - -const mtprotoIntermediateQuickAckLength = 0x80000000 - -// MTProtoIntermediate presents intermediate connection between client -// and Telegram. -type MTProtoIntermediate struct { - conn StreamReadWriteCloser - opts *mtproto.ConnectionOpts - logger *zap.SugaredLogger - - readCounter uint32 - writeCounter uint32 -} - -func (m *MTProtoIntermediate) Read() ([]byte, error) { - defer func() { - m.readCounter++ - }() - - m.logger.Debugw("Read packet", - "simple_ack", m.opts.ReadHacks.SimpleAck, - "quick_ack", m.opts.ReadHacks.QuickAck, - "counter", m.readCounter, - ) - - buf := &bytes.Buffer{} - buf.Grow(4) - - if _, err := io.CopyN(buf, m.conn, 4); err != nil { - return nil, errors.Annotate(err, "Cannot read message length") - } - length := binary.LittleEndian.Uint32(buf.Bytes()) - - m.logger.Debugw("Packet message length", - "simple_ack", m.opts.ReadHacks.SimpleAck, - "quick_ack", m.opts.ReadHacks.QuickAck, - "counter", m.readCounter, - "length", length, - ) - - if length > mtprotoIntermediateQuickAckLength { - m.opts.ReadHacks.QuickAck = true - length -= mtprotoIntermediateQuickAckLength - } - - buf.Reset() - buf.Grow(int(length)) - if _, err := io.CopyN(buf, m.conn, int64(length)); err != nil { - return nil, errors.Annotate(err, "Cannot read the message") - } - - return buf.Bytes()[:length], nil -} - -func (m *MTProtoIntermediate) Write(p []byte) (int, error) { - defer func() { - m.writeCounter++ - }() - - m.logger.Debugw("Write packet", - "simple_ack", m.opts.WriteHacks.SimpleAck, - "quick_ack", m.opts.WriteHacks.QuickAck, - "counter", m.writeCounter, - ) - - if m.opts.WriteHacks.SimpleAck { - return m.conn.Write(p) - } - - var length [4]byte - binary.LittleEndian.PutUint32(length[:], uint32(len(p))) - - return m.conn.Write(append(length[:], p...)) -} - -// Logger returns an instance of the logger for this wrapper. -func (m *MTProtoIntermediate) Logger() *zap.SugaredLogger { - return m.logger -} - -// LocalAddr returns local address of the underlying net.Conn. -func (m *MTProtoIntermediate) LocalAddr() *net.TCPAddr { - return m.conn.LocalAddr() -} - -// RemoteAddr returns remote address of the underlying net.Conn. -func (m *MTProtoIntermediate) RemoteAddr() *net.TCPAddr { - return m.conn.RemoteAddr() -} - -// Close closes underlying net.Conn instance. -func (m *MTProtoIntermediate) Close() error { - return m.conn.Close() -} - -// NewMTProtoIntermediate creates new PacketWrapper for intermediate -// client connection. -func NewMTProtoIntermediate(conn StreamReadWriteCloser, opts *mtproto.ConnectionOpts) PacketReadWriteCloser { - return &MTProtoIntermediate{ - conn: conn, - logger: conn.Logger().Named("mtproto-intermediate"), - opts: opts, - } -} diff --git a/_wrappers/mtproto_intermediate_secure.go b/_wrappers/mtproto_intermediate_secure.go deleted file mode 100644 index f26612b..0000000 --- a/_wrappers/mtproto_intermediate_secure.go +++ /dev/null @@ -1,74 +0,0 @@ -package wrappers - -import ( - "bytes" - "encoding/binary" - "math/rand" - - "github.com/9seconds/mtg/mtproto" -) - -// MTProtoIntermediateSecure is an extension of MTProtoIntermediate -// mode which supports random paddings (socalled 'secure mode' or -// 'dd-secrets'). -type MTProtoIntermediateSecure struct { - MTProtoIntermediate -} - -func (m *MTProtoIntermediateSecure) Read() ([]byte, error) { - data, err := m.MTProtoIntermediate.Read() - if err != nil { - return nil, err - } - length := len(data) - (len(data) % 4) - - return data[:length], nil -} - -func (m *MTProtoIntermediateSecure) Write(p []byte) (int, error) { - defer func() { - m.writeCounter++ - }() - - m.logger.Debugw("Write packet", - "simple_ack", m.opts.WriteHacks.SimpleAck, - "quick_ack", m.opts.WriteHacks.QuickAck, - "counter", m.writeCounter, - ) - - if m.opts.WriteHacks.SimpleAck { - return m.conn.Write(p) - } - - buf := &bytes.Buffer{} - paddingLength := rand.Intn(4) - buf.Grow(4 + len(p) + paddingLength) - - binary.Write(buf, binary.LittleEndian, uint32(len(p)+paddingLength)) // nolint: errcheck, gosec - buf.Write(p) // nolint: gosec - buf.Write(make([]byte, paddingLength)) // nolint: gosec - - m.logger.Debugw("Write packet with padding", - "simple_ack", m.opts.WriteHacks.SimpleAck, - "quick_ack", m.opts.WriteHacks.QuickAck, - "counter", m.writeCounter, - "padding_length", paddingLength, - "length", len(p), - ) - - _, err := m.conn.Write(buf.Bytes()) - - return len(p), err -} - -// NewMTProtoIntermediateSecure create new instance of -// MTProtoIntermediateSecure instance. -func NewMTProtoIntermediateSecure(conn StreamReadWriteCloser, opts *mtproto.ConnectionOpts) PacketReadWriteCloser { - return &MTProtoIntermediateSecure{ - MTProtoIntermediate: MTProtoIntermediate{ - conn: conn, - logger: conn.Logger().Named("mtproto-intermediate-secure"), - opts: opts, - }, - } -} diff --git a/_wrappers/mtproto_proxy.go b/_wrappers/mtproto_proxy.go deleted file mode 100644 index e3ac897..0000000 --- a/_wrappers/mtproto_proxy.go +++ /dev/null @@ -1,165 +0,0 @@ -package wrappers - -import ( - "bytes" - "fmt" - "net" - - "github.com/juju/errors" - "go.uber.org/zap" - - "github.com/9seconds/mtg/mtproto" - "github.com/9seconds/mtg/mtproto/rpc" -) - -// MTProtoProxy is a wrapper which creates/reads RPC responses from Telegram. -type MTProtoProxy struct { - conn PacketReadWriteCloser - req *rpc.ProxyRequest - logger *zap.SugaredLogger - - readCounter uint32 - writeCounter uint32 -} - -func (m *MTProtoProxy) Read() ([]byte, error) { - defer func() { - m.readCounter++ - }() - - m.logger.Debugw("Read packet", - "counter", m.readCounter, - "simple_ack", m.req.Options.WriteHacks.SimpleAck, - "quick_ack", m.req.Options.WriteHacks.QuickAck, - ) - - packet, err := m.conn.Read() - if err != nil { - return nil, errors.Annotate(err, "Cannot read packet") - } - - m.logger.Debugw("Read packet length", - "counter", m.readCounter, - "simple_ack", m.req.Options.WriteHacks.SimpleAck, - "quick_ack", m.req.Options.WriteHacks.QuickAck, - "length", len(packet), - ) - - if len(packet) < 4 { - return nil, errors.Annotate(err, "Incorrect packet length") - } - - tag, packet := packet[:4], packet[4:] - switch { - case bytes.Equal(tag, rpc.TagProxyAns): - return m.readProxyAns(packet) - case bytes.Equal(tag, rpc.TagSimpleAck): - return m.readSimpleAck(packet) - case bytes.Equal(tag, rpc.TagCloseExt): - return m.readCloseExt() - } - - return nil, errors.Errorf("Unknown RPC answer %v", tag) -} - -func (m *MTProtoProxy) readProxyAns(data []byte) ([]byte, error) { - if len(data) < 12 { - return nil, errors.Errorf("Incorrect data of proxy answer: %d", len(data)) - } - data = data[12:] - - m.logger.Debugw("Read RPC_PROXY_ANS", - "counter", m.readCounter, - "length", len(data), - ) - - return data, nil -} - -func (m *MTProtoProxy) readSimpleAck(data []byte) ([]byte, error) { - if len(data) != 12 { - return nil, errors.Errorf("Incorrect data of simple ack: %d", len(data)) - } - data = data[8:12] - m.req.Options.WriteHacks.SimpleAck = true - - m.logger.Debugw("Read RPC_SIMPLE_ACK", - "counter", m.readCounter, - "length", len(data), - ) - - return data, nil -} - -func (m *MTProtoProxy) readCloseExt() ([]byte, error) { - m.logger.Debugw("Read RPC_CLOSE_EXT", "counter", m.readCounter) - - return nil, errors.New("Connection has been closed remotely by RPC call") -} - -func (m *MTProtoProxy) Write(p []byte) (int, error) { - defer func() { - m.writeCounter++ - }() - - m.logger.Debugw("Write packet", - "length", len(p), - "counter", m.writeCounter, - "simple_ack", m.req.Options.ReadHacks.SimpleAck, - "quick_ack", m.req.Options.ReadHacks.QuickAck, - ) - - header, flags := m.req.MakeHeader(p) - if ce := m.logger.Desugar().Check(zap.DebugLevel, "RPC_PROXY_REQ header"); ce != nil { - ce.Write( - zap.Int("length", len(p)), - zap.Uint32("counter", m.writeCounter), - zap.Bool("simple_ack", m.req.Options.ReadHacks.QuickAck), - zap.Bool("quick_ack", m.req.Options.ReadHacks.SimpleAck), - zap.String("header", fmt.Sprintf("%v", header.Bytes())), - zap.Stringer("flags", flags), - ) - } - header.Write(p) // nolint: gosec - - if _, err := m.conn.Write(header.Bytes()); err != nil { - return 0, err - } - - return len(p), nil -} - -// Logger returns an instance of the logger for this wrapper. -func (m *MTProtoProxy) Logger() *zap.SugaredLogger { - return m.logger -} - -// LocalAddr returns local address of the underlying net.Conn. -func (m *MTProtoProxy) LocalAddr() *net.TCPAddr { - return m.conn.LocalAddr() -} - -// RemoteAddr returns remote address of the underlying net.Conn. -func (m *MTProtoProxy) RemoteAddr() *net.TCPAddr { - return m.conn.RemoteAddr() -} - -// Close closes underlying net.Conn instance. -func (m *MTProtoProxy) Close() error { - return m.conn.Close() -} - -// NewMTProtoProxy creates new RPC wrapper. -func NewMTProtoProxy(conn PacketReadWriteCloser, connOpts *mtproto.ConnectionOpts, - adTag []byte) (PacketReadWriteCloser, error) { - req, err := rpc.NewProxyRequest(connOpts.ClientAddr, conn.LocalAddr(), connOpts, adTag) - if err != nil { - return nil, errors.Annotate(err, "Cannot create new RPC proxy request") - } - - return &MTProtoProxy{ - conn: conn, - logger: conn.Logger().Named("mtproto-proxy"), - req: req, - }, nil -} diff --git a/_wrappers/streamcipher.go b/_wrappers/streamcipher.go deleted file mode 100644 index 5c0b137..0000000 --- a/_wrappers/streamcipher.go +++ /dev/null @@ -1,73 +0,0 @@ -package wrappers - -import ( - "bytes" - "crypto/cipher" - "net" - - "github.com/juju/errors" - "go.uber.org/zap" -) - -// StreamCipher is a wrapper which encrypts/decrypts stream with AES-CTR -// (as a part of obfuscated2 protocol). -type StreamCipher struct { - encryptor cipher.Stream - decryptor cipher.Stream - conn StreamReadWriteCloser - logger *zap.SugaredLogger -} - -func (s *StreamCipher) Read(p []byte) (int, error) { - n, err := s.conn.Read(p) - if err != nil { - return 0, errors.Annotate(err, "Cannot read stream ciphered data") - } - s.decryptor.XORKeyStream(p, p[:n]) - - return n, nil -} - -func (s *StreamCipher) Write(p []byte) (int, error) { - buf := streamCipherBufferPool.Get().(*bytes.Buffer) - defer streamCipherBufferPool.Put(buf) - - buf.Reset() - buf.Grow(len(p)) - buf.Write(p) // nolint: gosec - - data := buf.Bytes() - s.encryptor.XORKeyStream(data, data) - - return s.conn.Write(data) -} - -// Logger returns an instance of the logger for this wrapper. -func (s *StreamCipher) Logger() *zap.SugaredLogger { - return s.logger -} - -// LocalAddr returns local address of the underlying net.Conn. -func (s *StreamCipher) LocalAddr() *net.TCPAddr { - return s.conn.LocalAddr() -} - -// RemoteAddr returns remote address of the underlying net.Conn. -func (s *StreamCipher) RemoteAddr() *net.TCPAddr { - return s.conn.RemoteAddr() -} - -// Close closes underlying net.Conn instance. -func (s *StreamCipher) Close() error { - return s.conn.Close() -} - -// NewStreamCipher creates new stream cipher wrapper. -func NewStreamCipher(conn StreamReadWriteCloser, encryptor, decryptor cipher.Stream) StreamReadWriteCloser { - return &StreamCipher{ - conn: conn, - logger: conn.Logger().Named("stream-cipher"), - encryptor: encryptor, - decryptor: decryptor, - } -} diff --git a/_wrappers/streamcipher_pool.go b/_wrappers/streamcipher_pool.go deleted file mode 100644 index 45253c6..0000000 --- a/_wrappers/streamcipher_pool.go +++ /dev/null @@ -1,14 +0,0 @@ -package wrappers - -import ( - "bytes" - "sync" -) - -var ( - streamCipherBufferPool = sync.Pool{ - New: func() interface{} { - return &bytes.Buffer{} - }, - } -) diff --git a/_wrappers/wrap.go b/_wrappers/wrap.go deleted file mode 100644 index 5e9581b..0000000 --- a/_wrappers/wrap.go +++ /dev/null @@ -1,111 +0,0 @@ -package wrappers - -import ( - "io" - "net" - - "go.uber.org/zap" -) - -// Wrap is a base interface for all wrappers in this package. -type Wrap interface { - Logger() *zap.SugaredLogger - LocalAddr() *net.TCPAddr - RemoteAddr() *net.TCPAddr -} - -// Writer is a base interface for writers of this package. -type Writer interface { - io.Writer - Wrap -} - -// Closer is a base interface for wrappers of this package which can -// close connections. -type Closer interface { - io.Closer - Wrap -} - -// WriteCloser is a base interface for wrappers of this package which -// can write to and close connections. -type WriteCloser interface { - io.Closer - Writer -} - -// StreamReader is a base interface for wrappers which can read from the -// stream. -type StreamReader interface { - io.Reader - Wrap -} - -// StreamReadCloser is a base interface for wrappers which can read from -// and close the connections. -type StreamReadCloser interface { - io.Closer - StreamReader -} - -// StreamReadWriter is a base interface for wrappers which can read from -// and write to the connections. -type StreamReadWriter interface { - io.Writer - StreamReader -} - -// StreamWriteCloser is a base interface for wrappers which can write to -// and close the connections. -type StreamWriteCloser interface { - io.WriteCloser - Wrap -} - -// StreamReadWriteCloser is a base interface for stream processors. -type StreamReadWriteCloser interface { - io.Closer - StreamReadWriter -} - -// PacketReader is a base interface for wrappers which reads 'packets'. -// packets are atoms so you either get a packet or you get an error You -// cannot resume reading from packet. -type PacketReader interface { - Read() ([]byte, error) - Wrap -} - -// PacketWriter is a base interface for wrappers which can write packets. -type PacketWriter interface { - io.Writer - Wrap -} - -// PacketReadWriter is a base interface for wrappers which can read from -// and write packets. -type PacketReadWriter interface { - io.Writer - PacketReader -} - -// PacketReadCloser is a base interface for wrappers which can read -// packets and close the connection. -type PacketReadCloser interface { - io.Closer - PacketReader -} - -// PacketWriteCloser is a base interface for wrappers which can write -// packets and close the connection. -type PacketWriteCloser interface { - io.Writer - io.Closer - Wrap -} - -// PacketReadWriteCloser is a base interface for packet processors. -type PacketReadWriteCloser interface { - io.Closer - PacketReadWriter -} From 56cf90b13dc88852e1938ab60107eadfa36cdf4d Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 10 Oct 2019 21:07:55 +0300 Subject: [PATCH 33/62] Correct closing of connections --- hub/connection.go | 1 + proxy/direct.go | 11 +++++++---- proxy/middle.go | 13 ++++++++----- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/hub/connection.go b/hub/connection.go index ee6f4ec..4420a97 100644 --- a/hub/connection.go +++ b/hub/connection.go @@ -52,6 +52,7 @@ func (c *connection) write(packet conntypes.Packet) error { func (c *connection) shutdown() { c.shutdownOnce.Do(func() { + c.conn.Close() close(c.done) c.hub.channelBrokenSockets <- c.id }) diff --git a/proxy/direct.go b/proxy/direct.go index 19e7f69..23266ae 100644 --- a/proxy/direct.go +++ b/proxy/direct.go @@ -27,14 +27,17 @@ func directConnection(request *protocol.TelegramRequest) error { go directPipe(telegramConn, request.ClientConn, wg, request.Logger) go directPipe(request.ClientConn, telegramConn, wg, request.Logger) - <-request.Ctx.Done() wg.Wait() - return request.Ctx.Err() + return nil } -func directPipe(dst io.Writer, src io.Reader, wg *sync.WaitGroup, logger *zap.SugaredLogger) { - defer wg.Done() +func directPipe(dst io.WriteCloser, src io.ReadCloser, wg *sync.WaitGroup, logger *zap.SugaredLogger) { + defer func() { + dst.Close() + src.Close() + wg.Done() + }() buf := make([]byte, directPipeBufferSize) if _, err := io.CopyBuffer(dst, src, buf); err != nil { diff --git a/proxy/middle.go b/proxy/middle.go index d806885..c8c79e3 100644 --- a/proxy/middle.go +++ b/proxy/middle.go @@ -32,17 +32,20 @@ func middleConnection(request *protocol.TelegramRequest) error { go middlePipe(telegramConn, clientConn, wg, request.Logger) go middlePipe(clientConn, telegramConn, wg, request.Logger) - <-request.Ctx.Done() wg.Wait() - return request.Ctx.Err() + return nil } -func middlePipe(dst conntypes.PacketAckWriter, - src conntypes.PacketAckReader, +func middlePipe(dst conntypes.PacketAckWriteCloser, + src conntypes.PacketAckReadCloser, wg *sync.WaitGroup, logger *zap.SugaredLogger) { - defer wg.Done() + defer func() { + dst.Close() + src.Close() + wg.Done() + }() for { acks := conntypes.ConnectionAcks{} From 6e154b3f44ac6fc74243ca284623f266ef2daddd Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 10 Oct 2019 21:11:30 +0300 Subject: [PATCH 34/62] Fix for urls --- config/urls.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/config/urls.go b/config/urls.go index 9d50f08..b4471d6 100644 --- a/config/urls.go +++ b/config/urls.go @@ -2,8 +2,9 @@ package config import ( "encoding/hex" - "fmt" + "net" "net/url" + "strconv" ) type URLs struct { @@ -35,9 +36,10 @@ func GetURLs() (urls IPURLs) { return urls } -func makeURLs(addr fmt.Stringer, secret string) (urls URLs) { +func makeURLs(addr *net.TCPAddr, secret string) (urls URLs) { values := url.Values{} - values.Set("address", addr.String()) + values.Set("server", addr.IP.String()) + values.Set("port", strconv.Itoa(addr.Port)) values.Set("secret", secret) urls.TG = makeTGURL(values) From 5009859a1e3ed7131177ddddba41a3cf26ee3c03 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Fri, 11 Oct 2019 09:41:18 +0300 Subject: [PATCH 35/62] linting the code --- Makefile | 6 +----- cli/generate.go | 1 + cli/proxy.go | 12 ++++++++++-- cli/utils.go | 1 + config/config.go | 12 ++++++++---- config/global_ips.go | 5 +++++ config/urls.go | 1 + conntypes/protocol.go | 1 + conntypes/wrappers.go | 2 -- hub/connection.go | 2 ++ hub/connection_hub.go | 6 +++++- hub/ctx_channel.go | 2 ++ hub/hub.go | 5 +++-- hub/registry.go | 1 + main.go | 1 - mtproto/protocol.go | 16 +++++++++++----- mtproto/rpc/handshake_response.go | 7 ++++--- mtproto/rpc/nonce_request.go | 1 + mtproto/rpc/nonce_response.go | 10 ++++++---- mtproto/rpc/proxy_flags.go | 7 +++++++ mtproto/rpc/proxy_response.go | 6 ++++-- ntp/ntp.go | 4 +++- obfuscated2/client_protocol.go | 7 +++++-- obfuscated2/telegram_protocol.go | 2 ++ proxy/direct.go | 3 +++ proxy/middle.go | 7 ++++--- proxy/proxy.go | 7 ++++++- stats/stats.go | 4 ++++ stats/stats_prometheus.go | 4 ++++ stats/stats_statsd.go | 1 + telegram/api/addresses.go | 9 ++++++--- telegram/api/api.go | 4 +++- telegram/api/secret.go | 1 + telegram/base.go | 2 +- telegram/middle.go | 1 + utils/init_tcp.go | 2 ++ utils/read_full.go | 1 + utils/reverse_bytes.go | 2 +- utils/rlimit.go | 1 + utils/signal_context.go | 1 + wrappers/packet/mtproto_frame.go | 18 ++++++++++++------ wrappers/packetack/client_abridged.go | 11 ++++++++++- wrappers/packetack/client_intermediate.go | 5 +++++ .../packetack/client_intermediate_secure.go | 5 ++++- wrappers/packetack/proxy.go | 4 +++- wrappers/stream/blockcipher.go | 3 +++ wrappers/stream/conn.go | 2 ++ wrappers/stream/mtproto_cipher.go | 8 ++++++-- wrappers/stream/obfuscated2.go | 2 ++ wrappers/stream/stats_telegram.go | 1 + 50 files changed, 172 insertions(+), 55 deletions(-) delete mode 100644 conntypes/wrappers.go diff --git a/Makefile b/Makefile index dfed95d..e90b35d 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ APP_NAME := $(IMAGE_NAME) CC_BINARIES := $(shell bash -c "echo -n $(APP_NAME)-{linux,freebsd,openbsd}-{386,amd64} $(APP_NAME)-linux-{arm,arm64}") -GOLANGCI_LINT_VERSION := v1.15.0 +GOLANGCI_LINT_VERSION := v1.20.0 VERSION_GO := $(shell go version) VERSION_DATE := $(shell date -Ru) @@ -51,10 +51,6 @@ crosscompile: $(CC_BINARIES) crosscompile-dir: @rm -rf "$(CC_DIR)" && mkdir -p "$(CC_DIR)" -.PHONY: test -test: vendor - @$(MOD_ON) go test -v ./... - .PHONY: lint lint: vendor @$(MOD_OFF) golangci-lint run diff --git a/cli/generate.go b/cli/generate.go index 8010280..96c8311 100644 --- a/cli/generate.go +++ b/cli/generate.go @@ -12,6 +12,7 @@ func Generate(secretType string) { if _, err := rand.Read(data); err != nil { panic(err) } + secret := hex.EncodeToString(data) switch secretType { diff --git a/cli/proxy.go b/cli/proxy.go index e2d8b20..ef94430 100644 --- a/cli/proxy.go +++ b/cli/proxy.go @@ -19,10 +19,11 @@ import ( "github.com/9seconds/mtg/utils" ) -func Proxy() error { +func Proxy() error { // nolint: funlen ctx := utils.GetSignalContext() atom := zap.NewAtomicLevel() + switch { case config.C.Debug: atom.SetLevel(zapcore.DebugLevel) @@ -38,23 +39,28 @@ func Proxy() error { zapcore.Lock(os.Stderr), atom, )) + zap.ReplaceGlobals(logger) defer logger.Sync() // nolint: errcheck if err := config.InitPublicAddress(ctx); err != nil { Fatal(err) } + zap.S().Debugw("Configuration", "config", config.Printable()) if len(config.C.AdTag) > 0 { zap.S().Infow("Use middle proxy connection to Telegram") + diff, err := ntp.Fetch() if err != nil { Fatal("Cannot fetch time data from NTP") } + if diff > time.Second { Fatal("Your local time is skewed and drift is bigger than a second. Please sync your time.") } + go ntp.AutoUpdate() } else { zap.S().Infow("Use direct connection to Telegram") @@ -62,10 +68,11 @@ func Proxy() error { PrintJSONStdout(config.GetURLs()) - antireplay.Init() if err := stats.Init(ctx); err != nil { Fatal(err) } + + antireplay.Init() telegram.Init() hub.Init(ctx) @@ -73,6 +80,7 @@ func Proxy() error { if err != nil { Fatal(err) } + go func() { <-ctx.Done() proxyListener.Close() diff --git a/cli/utils.go b/cli/utils.go index 36f1128..f26c1b6 100644 --- a/cli/utils.go +++ b/cli/utils.go @@ -11,6 +11,7 @@ func Fatal(arg interface{}) { if value, ok := arg.(error); ok { arg = fmt.Errorf("fatal error: %+v", value) } + PrintStderr(arg) os.Exit(1) } diff --git a/config/config.go b/config/config.go index a8bf8fd..7bb12d6 100644 --- a/config/config.go +++ b/config/config.go @@ -23,6 +23,7 @@ func (s SecretMode) String() string { case SecretModeSecured: return "secured" } + return "tls" } @@ -135,7 +136,7 @@ func Init(options ...Opt) error { // nolint: gocyclo, funlen case "influxdb": C.StatsdTagsFormat = statsd.InfluxDB default: - return fmt.Errorf("Incorrect statsd tag %s", value) + return fmt.Errorf("incorrect statsd tag %s", value) } case OptionTypeStatsdTags: C.StatsdTags = opt.Value.(map[string]string) @@ -152,7 +153,7 @@ func Init(options ...Opt) error { // nolint: gocyclo, funlen case OptionTypeAdtag: C.AdTag = opt.Value.([]byte) default: - return fmt.Errorf("Unknown tag %v", opt.Option) + return fmt.Errorf("unknown tag %v", opt.Option) } } @@ -163,7 +164,7 @@ func Init(options ...Opt) error { // nolint: gocyclo, funlen case len(C.Secret) == SimpleSecretLength: C.SecretMode = SecretModeSimple default: - return errors.New("Incorrect secret") + return errors.New("incorrect secret") } return nil @@ -173,11 +174,13 @@ func InitPublicAddress(ctx context.Context) error { if C.PublicIPv4.Port == 0 { C.PublicIPv4.Port = C.Bind.Port } + if C.PublicIPv6.Port == 0 { C.PublicIPv6.Port = C.Bind.Port } foundAddress := C.PublicIPv4.IP != nil || C.PublicIPv6.IP != nil + if C.PublicIPv4.IP == nil { ip, err := getGlobalIPv4(ctx) if err != nil { @@ -187,6 +190,7 @@ func InitPublicAddress(ctx context.Context) error { foundAddress = true } } + if C.PublicIPv6.IP == nil { ip, err := getGlobalIPv6(ctx) if err != nil { @@ -198,7 +202,7 @@ func InitPublicAddress(ctx context.Context) error { } if !foundAddress { - return errors.New("Cannot resolve any public address") + return errors.New("cannot resolve any public address") } return nil diff --git a/config/global_ips.go b/config/global_ips.go index be5fe5c..e27b969 100644 --- a/config/global_ips.go +++ b/config/global_ips.go @@ -21,6 +21,7 @@ func getGlobalIPv4(ctx context.Context) (net.IP, error) { if err != nil || ip.To4() == nil { return nil, fmt.Errorf("cannot find public ipv4 address: %w", err) } + return ip, nil } @@ -29,6 +30,7 @@ func getGlobalIPv6(ctx context.Context) (net.IP, error) { if err != nil || ip.To4() != nil { return nil, fmt.Errorf("cannot find public ipv6 address: %w", err) } + return ip, nil } @@ -54,14 +56,17 @@ func fetchIP(ctx context.Context, network string) (net.IP, error) { if resp != nil { io.Copy(ioutil.Discard, resp.Body) // nolint: errcheck } + return nil, fmt.Errorf("cannot perform a request: %w", err) } + defer resp.Body.Close() // nolint: errcheck respDataBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("cannot read response body: %w", err) } + respData := strings.TrimSpace(string(respDataBytes)) ip := net.ParseIP(respData) diff --git a/config/urls.go b/config/urls.go index b4471d6..28a6b7b 100644 --- a/config/urls.go +++ b/config/urls.go @@ -22,6 +22,7 @@ type IPURLs struct { func GetURLs() (urls IPURLs) { secret := "" + switch C.SecretMode { case SecretModeSimple: secret = hex.EncodeToString(C.Secret) diff --git a/conntypes/protocol.go b/conntypes/protocol.go index 21da2c0..15a1c69 100644 --- a/conntypes/protocol.go +++ b/conntypes/protocol.go @@ -9,6 +9,7 @@ func (c ConnectionProtocol) String() string { case ConnectionProtocolIPv4: return "ipv4" } + return "ipv6" } diff --git a/conntypes/wrappers.go b/conntypes/wrappers.go deleted file mode 100644 index a086871..0000000 --- a/conntypes/wrappers.go +++ /dev/null @@ -1,2 +0,0 @@ -package conntypes - diff --git a/hub/connection.go b/hub/connection.go index 4420a97..c3edeb8 100644 --- a/hub/connection.go +++ b/hub/connection.go @@ -47,6 +47,7 @@ func (c *connection) write(packet conntypes.Packet) error { c.pending = 0 c.mutex.Unlock() } + return err } @@ -89,6 +90,7 @@ func (c *connection) run() { logger.Debugw("Failed response", "error", err) continue } + if response.Type == rpc.ProxyResponseTypeCloseExt { logger.Debugw("Proxy has closed connection") return diff --git a/hub/connection_hub.go b/hub/connection_hub.go index a1101b0..8ba9027 100644 --- a/hub/connection_hub.go +++ b/hub/connection_hub.go @@ -50,11 +50,11 @@ func (c *connectionHub) runGC() { case conn.closed(): logger.Debugw("Delete closed socket", "key", key) delete(c.sockets, key) - case conn.idle(): logger.Debugw("Delete idle socket", "key", key) conn.shutdown() delete(c.sockets, key) + return } } @@ -65,12 +65,14 @@ func (c *connectionHub) runConnectionRequest(req *connectionHubRequest) { for key, conn := range c.sockets { delete(c.sockets, key) + if !conn.closed() { logger.Debugw("Choose connection", "id", conn.id, "remote_addr", conn.conn.RemoteAddr()) req.response <- conn close(req.response) + return } } @@ -81,6 +83,7 @@ func (c *connectionHub) runConnectionRequest(req *connectionHubRequest) { "remote_addr", conn.conn.RemoteAddr()) req.response <- conn } + close(req.response) } @@ -93,6 +96,7 @@ func (c *connectionHub) runReturnConnection(conn *connection) { c.logger.Named("return-connection").Debugw("Return connection", "id", conn.id, "remote_addr", conn.conn.RemoteAddr()) + c.sockets[conn.id] = conn } diff --git a/hub/ctx_channel.go b/hub/ctx_channel.go index 0f599e4..e9e8b86 100644 --- a/hub/ctx_channel.go +++ b/hub/ctx_channel.go @@ -46,11 +46,13 @@ func (c *ctxChannel) sendBack(response *rpc.ProxyResponse) error { func (c *ctxChannel) Close() error { c.cancel() c.channel = nil + return nil } func newCtxChannel(ctx context.Context) *ctxChannel { ctx, cancel := context.WithCancel(ctx) + return &ctxChannel{ channel: make(chan *rpc.ProxyResponse), ctx: ctx, diff --git a/hub/hub.go b/hub/hub.go index e0545e2..57c2875 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -42,9 +42,9 @@ func (h *hub) Write(packet conntypes.Packet, req *protocol.TelegramRequest) erro func (h *hub) getHub(req *protocol.TelegramRequest) *connectionHub { keyBuilder := strings.Builder{} - binary.Write(&keyBuilder, binary.LittleEndian, int16(req.ClientProtocol.DC())) + binary.Write(&keyBuilder, binary.LittleEndian, int16(req.ClientProtocol.DC())) // nolint: errcheck keyBuilder.WriteRune('_') - binary.Write(&keyBuilder, binary.LittleEndian, uint8(req.ClientProtocol.ConnectionProtocol())) + binary.Write(&keyBuilder, binary.LittleEndian, uint8(req.ClientProtocol.ConnectionProtocol())) // nolint: errcheck key := keyBuilder.String() h.mutex.RLock() @@ -60,6 +60,7 @@ func (h *hub) getHub(req *protocol.TelegramRequest) *connectionHub { h.logger.Debugw("Create new connection hub", "dc", req.ClientProtocol.DC(), "protocol", req.ClientProtocol.ConnectionProtocol()) + rv = newConnectionHub(h.logger.With( "dc", req.ClientProtocol.DC(), "protocol", req.ClientProtocol.ConnectionProtocol(), diff --git a/hub/registry.go b/hub/registry.go index 5f6e33c..c8652a1 100644 --- a/hub/registry.go +++ b/hub/registry.go @@ -40,5 +40,6 @@ func (r *registry) getChannel(id conntypes.ConnID) (*ctxChannel, bool) { if value, ok := r.conns[string(id[:])]; ok { return value, true } + return nil, false } diff --git a/main.go b/main.go index e795f33..7d0fb31 100644 --- a/main.go +++ b/main.go @@ -119,7 +119,6 @@ func main() { switch kingpin.MustParse(app.Parse(os.Args[1:])) { case generateSecretCommand.FullCommand(): cli.Generate(*generateSecretType) - case proxyCommand.FullCommand(): err := config.Init( config.Opt{Option: config.OptionTypeDebug, Value: *proxyDebug}, diff --git a/mtproto/protocol.go b/mtproto/protocol.go index f90d784..b7dae2e 100644 --- a/mtproto/protocol.go +++ b/mtproto/protocol.go @@ -20,6 +20,7 @@ func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.PacketReadWriteC rpcNonceConn := packet.NewMtprotoFrame(conn, rpc.SeqNoNonce) rpcNonceReq, err := doRPCNonceRequest(rpcNonceConn) + if err != nil { return nil, fmt.Errorf("cannot do nonce request: %w", err) } @@ -35,6 +36,7 @@ func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.PacketReadWriteC if err := doRPCHandshakeRequest(frameConn); err != nil { return nil, fmt.Errorf("cannot do handshake request: %w", err) } + if err := getRPCHandshakeResponse(frameConn); err != nil { return nil, fmt.Errorf("cannot get handshake response: %w", err) } @@ -42,11 +44,12 @@ func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.PacketReadWriteC return frameConn, nil } -func doRPCNonceRequest(conn conntypes.PacketWriter) (*rpc.NonceRequest, error) { +func doRPCNonceRequest(conn conntypes.BasePacketWriter) (*rpc.NonceRequest, error) { rpcNonceReq, err := rpc.NewNonceRequest(telegram.Middle.Secret()) if err != nil { panic(err) } + if err := conn.Write(rpcNonceReq.Bytes()); err != nil { return nil, err } @@ -54,7 +57,7 @@ func doRPCNonceRequest(conn conntypes.PacketWriter) (*rpc.NonceRequest, error) { return rpcNonceReq, nil } -func getRPCNonceResponse(conn conntypes.PacketReader, req *rpc.NonceRequest) (*rpc.NonceResponse, error) { +func getRPCNonceResponse(conn conntypes.BasePacketReader, req *rpc.NonceRequest) (*rpc.NonceResponse, error) { packet, err := conn.Read() if err != nil { return nil, fmt.Errorf("cannot read from connection: %w", err) @@ -62,8 +65,9 @@ func getRPCNonceResponse(conn conntypes.PacketReader, req *rpc.NonceRequest) (*r resp, err := rpc.NewNonceResponse(packet) if err != nil { - return nil, fmt.Errorf("cannot build rpc nonce responce: %w", err) + return nil, fmt.Errorf("cannot build rpc nonce response: %w", err) } + if err = resp.Valid(req); err != nil { return nil, fmt.Errorf("invalid nonce response: %w", err) } @@ -71,14 +75,15 @@ func getRPCNonceResponse(conn conntypes.PacketReader, req *rpc.NonceRequest) (*r return resp, nil } -func doRPCHandshakeRequest(conn conntypes.PacketWriter) error { +func doRPCHandshakeRequest(conn conntypes.BasePacketWriter) error { if err := conn.Write(rpc.HandshakeRequest); err != nil { return fmt.Errorf("cannot make a request: %w", err) } + return nil } -func getRPCHandshakeResponse(conn conntypes.PacketReader) error { +func getRPCHandshakeResponse(conn conntypes.BasePacketReader) error { packet, err := conn.Read() if err != nil { return fmt.Errorf("cannot read a response: %w", err) @@ -88,6 +93,7 @@ func getRPCHandshakeResponse(conn conntypes.PacketReader) error { if err != nil { return fmt.Errorf("cannot build a handshake response: %w", err) } + if err := resp.Valid(); err != nil { return fmt.Errorf("invalid handshake response: %w", err) } diff --git a/mtproto/rpc/handshake_response.go b/mtproto/rpc/handshake_response.go index 659ce7c..1f1d509 100644 --- a/mtproto/rpc/handshake_response.go +++ b/mtproto/rpc/handshake_response.go @@ -28,10 +28,11 @@ func (r *HandshakeResponse) Bytes() []byte { // Valid checks that handshake response compliments request. func (r *HandshakeResponse) Valid() error { if !bytes.Equal(r.Type, TagHandshake) { - return errors.New("Unexpected handshake tag") + return errors.New("unexpected handshake tag") } + if !bytes.Equal(r.PeerPID, HandshakeSenderPID) { - return errors.New("Incorrect sender PID") + return errors.New("incorrect sender PID") } return nil @@ -41,7 +42,7 @@ func (r *HandshakeResponse) Valid() error { // data. func NewHandshakeResponse(data []byte) (*HandshakeResponse, error) { if len(data) != 32 { - return nil, fmt.Errorf("Incorrect handshake response length %d", len(data)) + return nil, fmt.Errorf("incorrect handshake response length %d", len(data)) } return &HandshakeResponse{ diff --git a/mtproto/rpc/nonce_request.go b/mtproto/rpc/nonce_request.go index 2915dad..8a06c26 100644 --- a/mtproto/rpc/nonce_request.go +++ b/mtproto/rpc/nonce_request.go @@ -36,6 +36,7 @@ func NewNonceRequest(proxySecret []byte) (*NonceRequest, error) { if _, err := rand.Read(nonce); err != nil { return nil, fmt.Errorf("cannot generate nonce: %w", err) } + copy(keySelector, proxySecret) timestamp := time.Now().Truncate(time.Second).Unix() % 4294967296 // 256 ^ 4 - do not know how to name diff --git a/mtproto/rpc/nonce_response.go b/mtproto/rpc/nonce_response.go index 08b9a4b..b214eb5 100644 --- a/mtproto/rpc/nonce_response.go +++ b/mtproto/rpc/nonce_response.go @@ -28,13 +28,15 @@ func (r *NonceResponse) Bytes() []byte { func (r *NonceResponse) Valid(req *NonceRequest) error { if !bytes.Equal(r.Type, TagNonce) { - return errors.New("Unexpected RPC type") + return errors.New("unexpected RPC type") } + if !bytes.Equal(r.Crypto, NonceCryptoAES) { - return errors.New("Unexpected crypto type") + return errors.New("unexpected crypto type") } + if !bytes.Equal(r.KeySelector, req.KeySelector) { - return errors.New("Unexpected key selector") + return errors.New("unexpected key selector") } return nil @@ -43,7 +45,7 @@ func (r *NonceResponse) Valid(req *NonceRequest) error { // NewNonceResponse build new nonce response based on the given data. func NewNonceResponse(data []byte) (*NonceResponse, error) { if len(data) != 32 { - return nil, fmt.Errorf("Unexpected message length %d", len(data)) + return nil, fmt.Errorf("unexpected message length %d", len(data)) } return &NonceResponse{ diff --git a/mtproto/rpc/proxy_flags.go b/mtproto/rpc/proxy_flags.go index c0e87fa..2179c66 100644 --- a/mtproto/rpc/proxy_flags.go +++ b/mtproto/rpc/proxy_flags.go @@ -33,24 +33,31 @@ func (r ProxyRequestFlags) String() string { if r&ProxyRequestFlagsHasAdTag != 0 { flags = append(flags, "HAS_AD_TAG") } + if r&ProxyRequestFlagsEncrypted != 0 { flags = append(flags, "ENCRYPTED") } + if r&ProxyRequestFlagsMagic != 0 { flags = append(flags, "MAGIC") } + if r&ProxyRequestFlagsExtMode2 != 0 { flags = append(flags, "EXT_MODE_2") } + if r&ProxyRequestFlagsIntermediate != 0 { flags = append(flags, "INTERMEDIATE") } + if r&ProxyRequestFlagsAbdridged != 0 { flags = append(flags, "ABRIDGED") } + if r&ProxyRequestFlagsQuickAck != 0 { flags = append(flags, "QUICK_ACK") } + if r&ProxyRequestFlagsPad != 0 { flags = append(flags, "PAD") } diff --git a/mtproto/rpc/proxy_response.go b/mtproto/rpc/proxy_response.go index 2b91810..5366f34 100644 --- a/mtproto/rpc/proxy_response.go +++ b/mtproto/rpc/proxy_response.go @@ -29,21 +29,23 @@ func ParseProxyResponse(packet conntypes.Packet) (*ProxyResponse, error) { } tag := packet[:4] + switch { case bytes.Equal(tag, TagProxyAns): response.Type = ProxyResponseTypeAns copy(response.ConnID[:], packet[8:16]) response.Payload = packet[16:] - return &response, nil + return &response, nil case bytes.Equal(tag, TagSimpleAck): response.Type = ProxyResponseTypeSimpleAck copy(response.ConnID[:], packet[4:12]) response.Payload = packet[12:] - return &response, nil + return &response, nil case bytes.Equal(tag, TagCloseExt): response.Type = ProxyResponseTypeCloseExt + return &response, nil } diff --git a/ntp/ntp.go b/ntp/ntp.go index ee5d515..0b4fa1f 100644 --- a/ntp/ntp.go +++ b/ntp/ntp.go @@ -21,15 +21,17 @@ var ntpEndpoints = [...]string{ // Fetch fetches the data on time drift. func Fetch() (time.Duration, error) { url := ntpEndpoints[rand.Intn(len(ntpEndpoints))] + resp, err := ntp.Query(url) if err != nil { - return 0, fmt.Errorf("Cannot fetch NTP server %s: %w", url, err) + return 0, fmt.Errorf("cannot fetch NTP server %s: %w", url, err) } offsetInt := int64(resp.ClockOffset) if offsetInt < 0 { offsetInt = -offsetInt } + offset := time.Duration(offsetInt) return offset, nil diff --git a/obfuscated2/client_protocol.go b/obfuscated2/client_protocol.go index 3dd00ec..3ccc7c4 100644 --- a/obfuscated2/client_protocol.go +++ b/obfuscated2/client_protocol.go @@ -58,6 +58,7 @@ func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conn decryptor.XORKeyStream(decryptedFrame.Bytes(), fm.Bytes()) magic := decryptedFrame.Magic() + switch { case bytes.Equal(magic, conntypes.ConnectionTagAbridged): c.connectionType = conntypes.ConnectionTypeAbridged @@ -66,7 +67,7 @@ func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conn case bytes.Equal(magic, conntypes.ConnectionTagSecure): c.connectionType = conntypes.ConnectionTypeSecure default: - return nil, errors.New("Unknown connection type") + return nil, errors.New("unknown connection type") } c.connectionProtocol = conntypes.ConnectionProtocolIPv4 @@ -81,8 +82,9 @@ func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conn antiReplayKey := decryptedFrame.Unique() if antireplay.Cache.Has(antiReplayKey) { - return nil, errors.New("Replay attack is detected") + return nil, errors.New("replay attack is detected") } + antireplay.Cache.Add(antiReplayKey) return stream.NewObfuscated2(socket, encryptor, decryptor), nil @@ -92,6 +94,7 @@ func (c *ClientProtocol) ReadFrame(socket conntypes.StreamReader) (fm Frame, err if _, err = io.ReadFull(handshakeReader{socket}, fm.Bytes()); err != nil { err = fmt.Errorf("cannot extract obfuscated2 frame: %w", err) } + return } diff --git a/obfuscated2/telegram_protocol.go b/obfuscated2/telegram_protocol.go index 9a92acb..cf17099 100644 --- a/obfuscated2/telegram_protocol.go +++ b/obfuscated2/telegram_protocol.go @@ -17,6 +17,7 @@ func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.StreamReadWriteC if err != nil { return nil, fmt.Errorf("cannot dial to telegram: %w", err) } + conn = stream.NewTimeout(conn) conn = stream.NewCtx(req.Ctx, req.Cancel, conn) fm := generateFrame(req.ClientProtocol) @@ -45,6 +46,7 @@ func generateFrame(cp protocol.ClientProtocol) (fm Frame) { if _, err := rand.Read(data); err != nil { continue } + if data[0] == 0xef { continue } diff --git a/proxy/direct.go b/proxy/direct.go index 23266ae..7067cbb 100644 --- a/proxy/direct.go +++ b/proxy/direct.go @@ -18,13 +18,16 @@ func directConnection(request *protocol.TelegramRequest) error { if err != nil { return err } + telegramConn := telegramConnRaw.(conntypes.StreamReadWriteCloser) + defer telegramConn.Close() wg := &sync.WaitGroup{} wg.Add(2) go directPipe(telegramConn, request.ClientConn, wg, request.Logger) + go directPipe(request.ClientConn, telegramConn, wg, request.Logger) wg.Wait() diff --git a/proxy/middle.go b/proxy/middle.go index c8c79e3..d7d3d42 100644 --- a/proxy/middle.go +++ b/proxy/middle.go @@ -10,11 +10,12 @@ import ( "github.com/9seconds/mtg/wrappers/packetack" ) -func middleConnection(request *protocol.TelegramRequest) error { +func middleConnection(request *protocol.TelegramRequest) { telegramConn := packetack.NewProxy(request) defer telegramConn.Close() var clientConn conntypes.PacketAckFullReadWriteCloser + switch request.ClientProtocol.ConnectionType() { case conntypes.ConnectionTypeAbridged: clientConn = packetack.NewClientAbridged(request.ClientConn) @@ -30,11 +31,10 @@ func middleConnection(request *protocol.TelegramRequest) error { wg.Add(2) go middlePipe(telegramConn, clientConn, wg, request.Logger) + go middlePipe(clientConn, telegramConn, wg, request.Logger) wg.Wait() - - return nil } func middlePipe(dst conntypes.PacketAckWriteCloser, @@ -50,6 +50,7 @@ func middlePipe(dst conntypes.PacketAckWriteCloser, for { acks := conntypes.ConnectionAcks{} packet, err := src.Read(&acks) + if err != nil { logger.Debugw("Cannot read packet", "error", err) return diff --git a/proxy/proxy.go b/proxy/proxy.go index 46bc62f..809482e 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -34,6 +34,7 @@ func (p *Proxy) Serve(listener net.Listener) { continue } } + go p.accept(conn) } } @@ -61,10 +62,12 @@ func (p *Proxy) accept(conn net.Conn) { clientConn := stream.NewClientConn(conn, connID) clientConn = stream.NewCtx(ctx, cancel, clientConn) clientConn = stream.NewTimeout(clientConn) + defer clientConn.Close() clientProtocol := p.ClientProtocolMaker() clientConn, err := clientProtocol.Handshake(clientConn) + if err != nil { logger.Warnw("Cannot perform client handshake", "error", err) return @@ -83,8 +86,10 @@ func (p *Proxy) accept(conn net.Conn) { ClientProtocol: clientProtocol, } + err = nil + if len(config.C.AdTag) > 0 { - err = middleConnection(req) + middleConnection(req) } else { err = directConnection(req) } diff --git a/stats/stats.go b/stats/stats.go index 535b5f3..044e704 100644 --- a/stats/stats.go +++ b/stats/stats.go @@ -20,11 +20,13 @@ func Init(ctx context.Context) error { } stats := []Interface{instancePrometheus} + if config.C.StatsdAddr != nil { instanceStatsd, err := newStatsStatsd() if err != nil { return fmt.Errorf("cannot inialize statsd: %w", err) } + stats = append(stats, instanceStatsd) } @@ -36,7 +38,9 @@ func Init(ctx context.Context) error { srv := http.Server{ Handler: mux, } + go srv.Serve(listener) // nolint: errcheck + go func() { <-ctx.Done() srv.Shutdown(context.Background()) // nolint: errcheck diff --git a/stats/stats_prometheus.go b/stats/stats_prometheus.go index bcb0c91..69a3f6c 100644 --- a/stats/stats_prometheus.go +++ b/stats/stats_prometheus.go @@ -122,15 +122,19 @@ func newStatsPrometheus(mux *http.ServeMux) (Interface, error) { if err := registry.Register(instance.connections); err != nil { return nil, fmt.Errorf("cannot register metrics for connections: %w", err) } + if err := registry.Register(instance.telegramConnections); err != nil { return nil, fmt.Errorf("cannot register metrics for telegram connections: %w", err) } + if err := registry.Register(instance.traffic); err != nil { return nil, fmt.Errorf("cannot register metrics for traffic: %w", err) } + if err := registry.Register(instance.crashes); err != nil { return nil, fmt.Errorf("cannot register metrics for crashes: %w", err) } + if err := registry.Register(instance.antiReplays); err != nil { return nil, fmt.Errorf("cannot register metrics for anti replays: %w", err) } diff --git a/stats/stats_statsd.go b/stats/stats_statsd.go index 9451ed7..e68ede7 100644 --- a/stats/stats_statsd.go +++ b/stats/stats_statsd.go @@ -96,6 +96,7 @@ func newStatsStatsd() (Interface, error) { for k, v := range config.C.StatsdTags { tags = append(tags, k, v) } + options = append(options, statsd.Tags(tags...)) } diff --git a/telegram/api/addresses.go b/telegram/api/addresses.go index f65fcac..c7573e4 100644 --- a/telegram/api/addresses.go +++ b/telegram/api/addresses.go @@ -31,34 +31,36 @@ func getAddresses(url string) (map[conntypes.DC][]string, conntypes.DC, error) { if err != nil { return nil, 0, fmt.Errorf("cannot get http response: %w", err) } + defer resp.Close() scanner := bufio.NewScanner(resp) data := map[conntypes.DC][]string{} + defaultDC := conntypes.DCDefaultIdx - var defaultDC = conntypes.DCDefaultIdx for scanner.Scan() { text := strings.TrimSpace(scanner.Text()) + switch { case strings.HasPrefix(text, "#"): continue - case strings.HasPrefix(text, "proxy_for"): addr, idx, err := addressesParseProxyFor(text) if err != nil { return nil, 0, fmt.Errorf("cannot parse 'proxy_for' section: %w", err) } + if addresses, ok := data[idx]; ok { data[idx] = append(addresses, addr) } else { data[idx] = []string{addr} } - case strings.HasPrefix(text, "default"): idx, err := addressesParseDefault(text) if err != nil { return nil, 0, fmt.Errorf("cannot parse 'default' section: %w", err) } + defaultDC = idx } } @@ -97,6 +99,7 @@ func addressesParseDefault(text string) (conntypes.DC, error) { } dcString := strings.TrimRight(chunks[1], ";") + dc, err := strconv.ParseInt(dcString, 10, 16) if err != nil { return 0, fmt.Errorf("incorrect config '%s': %w", text, err) diff --git a/telegram/api/api.go b/telegram/api/api.go index 8b36d83..91a21c1 100644 --- a/telegram/api/api.go +++ b/telegram/api/api.go @@ -22,15 +22,17 @@ func request(url string) (io.ReadCloser, error) { if err != nil { panic(err) } + req.Header.Set("Accept", "text/plan") req.Header.Set("User-Agent", apiUserAgent) resp, err := httpClient.Do(req) if err != nil { if resp != nil { - io.Copy(ioutil.Discard, resp.Body) + io.Copy(ioutil.Discard, resp.Body) // nolint: errcheck resp.Body.Close() } + return nil, fmt.Errorf("cannot perform a request: %w", err) } diff --git a/telegram/api/secret.go b/telegram/api/secret.go index c2b2e43..c5a1137 100644 --- a/telegram/api/secret.go +++ b/telegram/api/secret.go @@ -12,6 +12,7 @@ func Secret() ([]byte, error) { if err != nil { return nil, fmt.Errorf("cannot access telegram server: %w", err) } + defer resp.Close() secret, err := ioutil.ReadAll(resp) diff --git a/telegram/base.go b/telegram/base.go index eef2175..51cfd42 100644 --- a/telegram/base.go +++ b/telegram/base.go @@ -51,7 +51,7 @@ func (b *baseTelegram) chooseAddress(addresses map[conntypes.DC][]string, dc, defaultDC conntypes.DC) string { addrs, ok := addresses[dc] if !ok { - addrs, _ = addresses[defaultDC] + addrs = addresses[defaultDC] } switch { diff --git a/telegram/middle.go b/telegram/middle.go index 88278b1..519332e 100644 --- a/telegram/middle.go +++ b/telegram/middle.go @@ -55,6 +55,7 @@ func (m *middleTelegram) update() error { func (m *middleTelegram) backgroundUpdate() { logger := zap.S().Named("telegram") + for range time.Tick(middleTelegramBackgroundUpdateEvery) { if err := m.update(); err != nil { logger.Warnw("Cannot update Telegram proxies", "error", err) diff --git a/utils/init_tcp.go b/utils/init_tcp.go index 6f0ea2d..3bf90ca 100644 --- a/utils/init_tcp.go +++ b/utils/init_tcp.go @@ -13,9 +13,11 @@ func InitTCP(conn net.Conn) error { if err := tcpConn.SetNoDelay(true); err != nil { return fmt.Errorf("cannot set TCP_NO_DELAY: %w", err) } + if err := tcpConn.SetReadBuffer(config.C.ReadBuffer); err != nil { return fmt.Errorf("cannot set read buffer size: %w", err) } + if err := tcpConn.SetWriteBuffer(config.C.WriteBuffer); err != nil { return fmt.Errorf("cannot set write buffer size: %w", err) } diff --git a/utils/read_full.go b/utils/read_full.go index afb2554..912aa19 100644 --- a/utils/read_full.go +++ b/utils/read_full.go @@ -13,6 +13,7 @@ func ReadFull(src io.Reader) (rv []byte, err error) { if err != nil { return nil, err } + rv = append(rv, buf[:n]...) } diff --git a/utils/reverse_bytes.go b/utils/reverse_bytes.go index ab7cd5b..87ad8f6 100644 --- a/utils/reverse_bytes.go +++ b/utils/reverse_bytes.go @@ -4,8 +4,8 @@ package utils func ReverseBytes(data []byte) []byte { dataLen := len(data) rv := make([]byte, dataLen) - rv[dataLen/2] = data[dataLen/2] + for i := dataLen/2 - 1; i >= 0; i-- { opp := dataLen - i - 1 rv[i], rv[opp] = data[opp], data[i] diff --git a/utils/rlimit.go b/utils/rlimit.go index d62d3fa..b474076 100644 --- a/utils/rlimit.go +++ b/utils/rlimit.go @@ -13,6 +13,7 @@ func SetLimits() error { if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rLimit); err != nil { return fmt.Errorf("cannot get rlimit: %w", err) } + rLimit.Cur = rLimit.Max if err := unix.Setrlimit(unix.RLIMIT_NOFILE, &rLimit); err != nil { diff --git a/utils/signal_context.go b/utils/signal_context.go index 40e2b6f..6d3951f 100644 --- a/utils/signal_context.go +++ b/utils/signal_context.go @@ -14,6 +14,7 @@ func GetSignalContext() context.Context { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + go func() { for range sigChan { cancel() diff --git a/wrappers/packet/mtproto_frame.go b/wrappers/packet/mtproto_frame.go index a43f4eb..6a1cd1a 100644 --- a/wrappers/packet/mtproto_frame.go +++ b/wrappers/packet/mtproto_frame.go @@ -41,7 +41,7 @@ type wrapperMtprotoFrame struct { writeSeqNo int32 } -func (w *wrapperMtprotoFrame) Read() (conntypes.Packet, error) { +func (w *wrapperMtprotoFrame) Read() (conntypes.Packet, error) { // nolint: funlen buf := &bytes.Buffer{} sum := crc32.NewIEEE() writer := io.MultiWriter(buf, sum) @@ -49,9 +49,11 @@ func (w *wrapperMtprotoFrame) Read() (conntypes.Packet, error) { for { buf.Reset() sum.Reset() + if _, err := io.CopyN(writer, w.parent, 4); err != nil { return nil, fmt.Errorf("cannot read frame padding: %w", err) } + if !bytes.Equal(buf.Bytes(), mtprotoFramePadding) { break } @@ -62,19 +64,23 @@ func (w *wrapperMtprotoFrame) Read() (conntypes.Packet, error) { "messageLength", messageLength, "sequence_number", w.readSeqNo, ) + if messageLength%4 != 0 || messageLength < mtprotoFrameMinMessageLength || messageLength > mtprotoFrameMaxMessageLength { - return nil, fmt.Errorf("Incorrect frame message length %d", messageLength) + return nil, fmt.Errorf("incorrect frame message length %d", messageLength) } buf.Reset() buf.Grow(int(messageLength) - 4 - 4) + if _, err := io.CopyN(writer, w.parent, int64(messageLength)-4-4); err != nil { return nil, fmt.Errorf("cannot read the message frame: %w", err) } var seqNo int32 - binary.Read(buf, binary.LittleEndian, &seqNo) // nolint: errcheck, gosec + + binary.Read(buf, binary.LittleEndian, &seqNo) // nolint: errcheck + if seqNo != w.readSeqNo { return nil, fmt.Errorf("unexpected sequence number %d (wait for %d)", seqNo, w.readSeqNo) } @@ -110,12 +116,12 @@ func (w *wrapperMtprotoFrame) Write(p conntypes.Packet) error { buf := &bytes.Buffer{} buf.Grow(messageLength + paddingLength) - binary.Write(buf, binary.LittleEndian, uint32(messageLength)) - binary.Write(buf, binary.LittleEndian, w.writeSeqNo) + binary.Write(buf, binary.LittleEndian, uint32(messageLength)) // nolint: errcheck + binary.Write(buf, binary.LittleEndian, w.writeSeqNo) // nolint: errcheck buf.Write(p) checksum := crc32.ChecksumIEEE(buf.Bytes()) - binary.Write(buf, binary.LittleEndian, checksum) + binary.Write(buf, binary.LittleEndian, checksum) // nolint: errcheck buf.Write(bytes.Repeat(mtprotoFramePadding, paddingLength/4)) w.logger.Debugw("Write MTProto frame", diff --git a/wrappers/packetack/client_abridged.go b/wrappers/packetack/client_abridged.go index c9b26e1..1b8aa9a 100644 --- a/wrappers/packetack/client_abridged.go +++ b/wrappers/packetack/client_abridged.go @@ -26,9 +26,11 @@ func (w *wrapperClientAbridged) Read(acks *conntypes.ConnectionAcks) (conntypes. buf := bytes.Buffer{} buf.Grow(1) + if _, err := io.CopyN(&buf, w.parent, 1); err != nil { return nil, fmt.Errorf("cannot read message length: %w", err) } + msgLength := uint32(buf.Bytes()[0]) buf.Reset() @@ -39,17 +41,21 @@ func (w *wrapperClientAbridged) Read(acks *conntypes.ConnectionAcks) (conntypes. if msgLength == clientAbridgedSmallPacketLength { buf.Grow(3) + if _, err := io.CopyN(&buf, w.parent, 3); err != nil { return nil, fmt.Errorf("cannot read correct message length: %w", err) } + number := utils.Uint24{} copy(number[:], buf.Bytes()) msgLength = utils.FromUint24(number) } + msgLength *= 4 buf.Reset() buf.Grow(int(msgLength)) + if _, err := io.CopyN(&buf, w.parent, int64(msgLength)); err != nil { return nil, fmt.Errorf("cannot read message: %w", err) } @@ -66,18 +72,20 @@ func (w *wrapperClientAbridged) Write(packet conntypes.Packet, acks *conntypes.C if _, err := w.parent.Write(utils.ReverseBytes(packet)); err != nil { return fmt.Errorf("cannot send a simpleacked packet: %w", err) } + return nil } packetLength := len(packet) / 4 + switch { case packetLength < clientAbridgedSmallPacketLength: data := append([]byte{byte(packetLength)}, packet...) if _, err := w.parent.Write(data); err != nil { return fmt.Errorf("cannot send small packet: %w", err) } - return nil + return nil case packetLength < clientAbridgedLargePacketLength: length24 := utils.ToUint24(uint32(packetLength)) buf := bytes.Buffer{} @@ -89,6 +97,7 @@ func (w *wrapperClientAbridged) Write(packet conntypes.Packet, acks *conntypes.C if _, err := w.parent.Write(buf.Bytes()); err != nil { return fmt.Errorf("cannot send large packet: %w", err) } + return nil } diff --git a/wrappers/packetack/client_intermediate.go b/wrappers/packetack/client_intermediate.go index 0415c8c..71ca5f5 100644 --- a/wrappers/packetack/client_intermediate.go +++ b/wrappers/packetack/client_intermediate.go @@ -22,9 +22,11 @@ func (w *wrapperClientIntermediate) Read(acks *conntypes.ConnectionAcks) (connty buf := bytes.Buffer{} buf.Grow(4) + if _, err := io.CopyN(&buf, w.parent, 4); err != nil { return nil, fmt.Errorf("cannot read message length: %w", err) } + length := binary.LittleEndian.Uint32(buf.Bytes()) if length > clientIntermediateQuickAckLength { @@ -34,6 +36,7 @@ func (w *wrapperClientIntermediate) Read(acks *conntypes.ConnectionAcks) (connty buf.Reset() buf.Grow(int(length)) + if _, err := io.CopyN(&buf, w.parent, int64(length)); err != nil { return nil, fmt.Errorf("cannot read the message: %w", err) } @@ -46,6 +49,7 @@ func (w *wrapperClientIntermediate) Write(packet conntypes.Packet, acks *conntyp if _, err := w.parent.Write(packet); err != nil { return fmt.Errorf("cannot send simpleacked packet: %w", err) } + return nil } @@ -55,6 +59,7 @@ func (w *wrapperClientIntermediate) Write(packet conntypes.Packet, acks *conntyp if _, err := w.parent.Write(append(length[:], packet...)); err != nil { return fmt.Errorf("cannot send packet: %w", err) } + return nil } diff --git a/wrappers/packetack/client_intermediate_secure.go b/wrappers/packetack/client_intermediate_secure.go index a42afec..153e779 100644 --- a/wrappers/packetack/client_intermediate_secure.go +++ b/wrappers/packetack/client_intermediate_secure.go @@ -20,6 +20,7 @@ func (w *wrapperClientIntermediateSecure) Read(acks *conntypes.ConnectionAcks) ( if err != nil { return nil, err } + length := len(data) - (len(data) % 4) return data[:length], nil @@ -30,6 +31,7 @@ func (w *wrapperClientIntermediateSecure) Write(packet conntypes.Packet, acks *c if _, err := w.parent.Write(packet); err != nil { return fmt.Errorf("cannot send simpleacked packet: %w", err) } + return nil } @@ -37,13 +39,14 @@ func (w *wrapperClientIntermediateSecure) Write(packet conntypes.Packet, acks *c paddingLength := rand.Intn(4) buf.Grow(4 + len(packet) + paddingLength) - binary.Write(&buf, binary.LittleEndian, uint32(len(packet)+paddingLength)) + binary.Write(&buf, binary.LittleEndian, uint32(len(packet)+paddingLength)) // nolint: errcheck buf.Write(packet) buf.Write(make([]byte, paddingLength)) if _, err := w.parent.Write(buf.Bytes()); err != nil { return fmt.Errorf("cannot send packet: %w", err) } + return nil } diff --git a/wrappers/packetack/proxy.go b/wrappers/packetack/proxy.go index 301035c..768eddb 100644 --- a/wrappers/packetack/proxy.go +++ b/wrappers/packetack/proxy.go @@ -15,12 +15,12 @@ import ( ) type wrapperProxy struct { - flags rpc.ProxyRequestFlags request *protocol.TelegramRequest clientIPPort []byte ourIPPort []byte channelRead hub.ChannelReadCloser closeOnce sync.Once + flags rpc.ProxyRequestFlags } func (w *wrapperProxy) Write(packet conntypes.Packet, acks *conntypes.ConnectionAcks) error { @@ -30,6 +30,7 @@ func (w *wrapperProxy) Write(packet conntypes.Packet, acks *conntypes.Connection if acks.Quick { flags |= rpc.ProxyRequestFlagsQuickAck } + if bytes.HasPrefix(packet, rpc.ProxyRequestFlagsEncryptedPrefix[:]) { flags |= rpc.ProxyRequestFlagsEncrypted } @@ -67,6 +68,7 @@ func (w *wrapperProxy) Close() error { w.channelRead.Close() hub.Registry.Unregister(w.request.ConnID) }) + return nil } diff --git a/wrappers/stream/blockcipher.go b/wrappers/stream/blockcipher.go index 5755ddb..34ff7d8 100644 --- a/wrappers/stream/blockcipher.go +++ b/wrappers/stream/blockcipher.go @@ -27,6 +27,7 @@ func (w *wrapperBlockCipher) Write(p []byte) (int, error) { if err != nil { return 0, err } + return w.parent.Write(encrypted) } @@ -35,6 +36,7 @@ func (w *wrapperBlockCipher) WriteTimeout(p []byte, timeout time.Duration) (int, if err != nil { return 0, err } + return w.parent.WriteTimeout(encrypted, timeout) } @@ -49,6 +51,7 @@ func (w *wrapperBlockCipher) Read(p []byte) (int, error) { if err != nil { return 0, fmt.Errorf("cannot read data: %w", err) } + currentBuffer = append(currentBuffer, rv...) } diff --git a/wrappers/stream/conn.go b/wrappers/stream/conn.go index d534ec3..8b0bf80 100644 --- a/wrappers/stream/conn.go +++ b/wrappers/stream/conn.go @@ -38,6 +38,7 @@ func (w *wrapperConn) WriteTimeout(p []byte, timeout time.Duration) (int, error) func (w *wrapperConn) Write(p []byte) (int, error) { n, err := w.parent.Write(p) w.logger.Debugw("write to stream", "bytes", n, "error", err) + if err != nil { w.Close() // nolint: gosec } @@ -57,6 +58,7 @@ func (w *wrapperConn) ReadTimeout(p []byte, timeout time.Duration) (int, error) func (w *wrapperConn) Read(p []byte) (int, error) { n, err := w.parent.Read(p) w.logger.Debugw("Read from stream", "bytes", n, "error", err) + if err != nil { w.Close() } diff --git a/wrappers/stream/mtproto_cipher.go b/wrappers/stream/mtproto_cipher.go index 85f8ce7..a46b528 100644 --- a/wrappers/stream/mtproto_cipher.go +++ b/wrappers/stream/mtproto_cipher.go @@ -4,8 +4,8 @@ import ( "bytes" "crypto/aes" "crypto/cipher" - "crypto/md5" - "crypto/sha1" + "crypto/md5" // nolint: gosec + "crypto/sha1" // nolint: gosec "encoding/binary" "net" @@ -61,13 +61,16 @@ func mtprotoDeriveKeys(purpose mtprotoCipherPurpose, clientIPv4 := mtprotoEmptyIP[:] serverIPv4 := mtprotoEmptyIP[:] + if client.IP.To4() != nil { clientIPv4 = utils.ReverseBytes(client.IP.To4()) serverIPv4 = utils.ReverseBytes(remote.IP.To4()) } + message.Write(serverIPv4) // nolint: gosec var port [2]byte + binary.LittleEndian.PutUint16(port[:], uint16(client.Port)) message.Write(port[:]) // nolint: gosec @@ -90,6 +93,7 @@ func mtprotoDeriveKeys(purpose mtprotoCipherPurpose, message.Write(client.IP.To16()) // nolint: gosec message.Write(remote.IP.To16()) // nolint: gosec } + message.Write(req.Nonce) // nolint: gosec data := message.Bytes() diff --git a/wrappers/stream/obfuscated2.go b/wrappers/stream/obfuscated2.go index 9ad4ae2..d94b245 100644 --- a/wrappers/stream/obfuscated2.go +++ b/wrappers/stream/obfuscated2.go @@ -22,6 +22,7 @@ func (w *wrapperObfuscated2) ReadTimeout(p []byte, timeout time.Duration) (int, if err != nil { return 0, fmt.Errorf("cannot read stream ciphered data: %w", err) } + w.decryptor.XORKeyStream(p, p[:n]) return n, nil @@ -32,6 +33,7 @@ func (w *wrapperObfuscated2) Read(p []byte) (int, error) { if err != nil { return n, err } + w.decryptor.XORKeyStream(p, p[:n]) return n, nil diff --git a/wrappers/stream/stats_telegram.go b/wrappers/stream/stats_telegram.go index c45b769..064790c 100644 --- a/wrappers/stream/stats_telegram.go +++ b/wrappers/stream/stats_telegram.go @@ -65,6 +65,7 @@ func NewTelegramStats(dc conntypes.DC, parent conntypes.StreamReadWriteCloser) c parent: parent, dc: dc, } + stats.Stats.TelegramConnected(dc, parent.RemoteAddr()) return conn From 5f168d1ee4f852e4cd547b59ee40cb4855c75527 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Fri, 11 Oct 2019 09:42:04 +0300 Subject: [PATCH 36/62] Update dependencies --- go.mod | 20 ++++++-------------- go.sum | 36 ++++++++++++++++-------------------- 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index 7e21076..f206fda 100644 --- a/go.mod +++ b/go.mod @@ -4,32 +4,24 @@ replace github.com/golang/lint => github.com/golang/lint v0.0.0-20190227174305-8 require ( github.com/OneOfOne/xxhash v1.2.5 // indirect - github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect - github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 + github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d github.com/allegro/bigcache v1.2.1 github.com/beevik/ntp v0.2.0 github.com/cespare/xxhash v1.1.0 - github.com/dustin/go-humanize v1.0.0 - github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622 - github.com/gofrs/uuid v3.2.0+incompatible - github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d - github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect - github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b // indirect github.com/kr/pretty v0.1.0 // indirect - github.com/pkg/errors v0.8.1 github.com/prometheus/client_golang v1.1.0 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect + github.com/prometheus/common v0.7.0 // indirect + github.com/prometheus/procfs v0.0.5 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/stretchr/testify v1.4.0 // indirect go.uber.org/atomic v1.4.0 // indirect - go.uber.org/multierr v1.1.0 // indirect + go.uber.org/multierr v1.2.0 // indirect go.uber.org/zap v1.10.0 - golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect - golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a + golang.org/x/net v0.0.0-20191009170851-d66e71096ffb // indirect + golang.org/x/sys v0.0.0-20191010194322-b09406accb47 gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/alexcesaro/statsd.v2 v2.0.0 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect - gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect ) go 1.13 diff --git a/go.sum b/go.sum index 2e2a856..f071844 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZq github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw= @@ -24,16 +26,11 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622 h1:lxbhOGZ9pU3Kf8P6lFluUcE82yVZn2EqEf4+mWRNPV0= -github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622/go.mod h1:D90+MBHVc9Sk1lJAbEVgws0eYEurY4mv2TDso3Nxh3w= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= @@ -42,16 +39,11 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc= -github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= -github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI= -github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= -github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b h1:Rrp0ByJXEjhREMPGTt3aWYjoIsUGCbt21ekbeJcTWv0= -github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -87,13 +79,18 @@ github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d h1:GoAlyOgbOEIFdaDqxJVlbOQ1DtGmZWs/Qau0hIlk+WQ= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -107,25 +104,26 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.2.0 h1:6I+W7f5VwC5SV9dNrZ3qXrDB9mD0dyGOi/ZJmYw03T4= +go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191009170851-d66e71096ffb h1:TR699M2v0qoKTOHxeLgp6zPqaQNs74f01a/ob9W0qko= +golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -134,8 +132,6 @@ gopkg.in/alexcesaro/statsd.v2 v2.0.0/go.mod h1:i0ubccKGzBVNBpdGV5MocxyA/XlLUJzA7 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= -gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= From f531d90fc4acfb47d4b4ca8dbe94c1231d628bda Mon Sep 17 00:00:00 2001 From: 9seconds Date: Fri, 11 Oct 2019 10:16:17 +0300 Subject: [PATCH 37/62] Use golang 1.13 in travis --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7dd05a5..23230cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,7 @@ sudo: false dist: trusty go: - - "1.11.x" - - 1.12.x + - 1.13.x - master before_script: make prepare @@ -14,7 +13,6 @@ before_script: make prepare script: - make all - make lint - - make test matrix: allow_failures: From 1e428e13fc89e04b473123b9ce8fe0ada94b1cb6 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 4 Nov 2019 16:07:20 +0300 Subject: [PATCH 38/62] Add base for faketls --- faketls/client_protocol.go | 35 +++++++ faketls/consts.go | 21 +++++ faketls/telegram_protocol.go | 10 ++ wrappers/stream/blockcipher.go | 58 ++++-------- wrappers/stream/buffered_reader.go | 43 +++++++++ wrappers/stream/faketls.go | 145 +++++++++++++++++++++++++++++ wrappers/stream/rewind.go | 98 +++++++++++++++++++ 7 files changed, 370 insertions(+), 40 deletions(-) create mode 100644 faketls/client_protocol.go create mode 100644 faketls/consts.go create mode 100644 faketls/telegram_protocol.go create mode 100644 wrappers/stream/buffered_reader.go create mode 100644 wrappers/stream/faketls.go create mode 100644 wrappers/stream/rewind.go diff --git a/faketls/client_protocol.go b/faketls/client_protocol.go new file mode 100644 index 0000000..620a89d --- /dev/null +++ b/faketls/client_protocol.go @@ -0,0 +1,35 @@ +package faketls + +import ( + "bufio" + + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/obfuscated2" + "github.com/9seconds/mtg/wrappers/stream" +) + +type ClientProtocol struct { + obfuscated2.ClientProtocol +} + +func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conntypes.StreamReadWriteCloser, error) { + rewinded := stream.NewRewind(socket) + bufferedReader := bufio.NewReader(rewinded) + + for _, expected := range faketlsStartBytes { + if actual, err := bufferedReader.ReadByte(); err != nil || actual != expected { + return nil, c.simulateWebsite(rewinded) + } + } + + if err := c.tlsHandshake(rewinded); err != nil { + return nil, c.simulateWebsite(rewinded) + } + + conn, err := c.ClientProtocol.Handshake(socket) + if err != nil { + return nil, err + } + + return conn, err +} diff --git a/faketls/consts.go b/faketls/consts.go new file mode 100644 index 0000000..66061d0 --- /dev/null +++ b/faketls/consts.go @@ -0,0 +1,21 @@ +package faketls + +const ( + TLSHandshakeLength = 1 + 2 + 2 + 512 +) + +var ( +faketlsStartBytes = [...]byte{ + 0x16, + 0x03, + 0x01, + 0x02, + 0x00, + 0x01, + 0x00, + 0x01, + 0xfc, + 0x03, + 0x03, +} +) diff --git a/faketls/telegram_protocol.go b/faketls/telegram_protocol.go new file mode 100644 index 0000000..9c1cd85 --- /dev/null +++ b/faketls/telegram_protocol.go @@ -0,0 +1,10 @@ +package faketls + +import ( + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/protocol" +) + +func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.StreamReadWriteCloser, error) { + return nil, nil +} diff --git a/wrappers/stream/blockcipher.go b/wrappers/stream/blockcipher.go index 34ff7d8..1a40076 100644 --- a/wrappers/stream/blockcipher.go +++ b/wrappers/stream/blockcipher.go @@ -1,7 +1,6 @@ package stream import ( - "bytes" "crypto/aes" "crypto/cipher" "fmt" @@ -15,7 +14,7 @@ import ( ) type wrapperBlockCipher struct { - buf bytes.Buffer + bufferedReader parent conntypes.StreamReadWriteCloser encryptor cipher.BlockMode @@ -40,43 +39,6 @@ func (w *wrapperBlockCipher) WriteTimeout(p []byte, timeout time.Duration) (int, return w.parent.WriteTimeout(encrypted, timeout) } -func (w *wrapperBlockCipher) Read(p []byte) (int, error) { - if w.buf.Len() > 0 { - return w.flush(p) - } - - var currentBuffer []byte - for len(currentBuffer) == 0 || len(currentBuffer)%aes.BlockSize != 0 { - rv, err := utils.ReadFull(w.parent) - if err != nil { - return 0, fmt.Errorf("cannot read data: %w", err) - } - - currentBuffer = append(currentBuffer, rv...) - } - - w.decryptor.CryptBlocks(currentBuffer, currentBuffer) - w.buf.Write(currentBuffer) - - return w.flush(p) -} - -func (w *wrapperBlockCipher) ReadTimeout(p []byte, timeout time.Duration) (int, error) { - return w.Read(p) -} - -func (w *wrapperBlockCipher) flush(p []byte) (int, error) { - if w.buf.Len() > len(p) { - return w.buf.Read(p) - } - - sizeToReturn := w.buf.Len() - copy(p, w.buf.Bytes()) - w.buf.Reset() - - return sizeToReturn, nil -} - func (w *wrapperBlockCipher) encrypt(p []byte) ([]byte, error) { if len(p)%aes.BlockSize > 0 { return nil, fmt.Errorf("incorrect block size %d", len(p)) @@ -110,9 +72,25 @@ func (w *wrapperBlockCipher) RemoteAddr() *net.TCPAddr { func newBlockCipher(parent conntypes.StreamReadWriteCloser, encryptor, decryptor cipher.BlockMode) conntypes.StreamReadWriteCloser { - return &wrapperBlockCipher{ + cipher := &wrapperBlockCipher{ parent: parent, encryptor: encryptor, decryptor: decryptor, } + + cipher.readFunc = func() ([]byte, error) { + var currentBuffer []byte + for len(currentBuffer) == 0 || len(currentBuffer)%aes.BlockSize != 0 { + rv, err := utils.ReadFull(cipher.parent) + if err != nil { + return nil, fmt.Errorf("cannot read data: %w", err) + } + currentBuffer = append(currentBuffer, rv...) + } + cipher.decryptor.CryptBlocks(currentBuffer, currentBuffer) + + return currentBuffer, nil + } + + return cipher } diff --git a/wrappers/stream/buffered_reader.go b/wrappers/stream/buffered_reader.go new file mode 100644 index 0000000..8b23627 --- /dev/null +++ b/wrappers/stream/buffered_reader.go @@ -0,0 +1,43 @@ +package stream + +import ( + "bytes" + "time" +) + +type bufferedReaderReadFunc func() ([]byte, error) + +type bufferedReader struct { + buf bytes.Buffer + readFunc bufferedReaderReadFunc +} + +func (b *bufferedReader) Read(p []byte) (int, error) { + if b.buf.Len() > 0 { + return b.flush(p) + } + + res, err := b.readFunc() + if err != nil { + return 0, err + } + b.buf.Write(res) + + return b.flush(p) +} + +func (b *bufferedReader) ReadTimeout(p []byte, _ time.Duration) (int, error) { + return b.Read(p) +} + +func (b *bufferedReader) flush(p []byte) (int, error) { + if b.buf.Len() > len(p) { + return b.buf.Read(p) + } + + sizeToReturn := b.buf.Len() + copy(p, b.buf.Bytes()) + b.buf.Reset() + + return sizeToReturn, nil +} diff --git a/wrappers/stream/faketls.go b/wrappers/stream/faketls.go new file mode 100644 index 0000000..7c60a8f --- /dev/null +++ b/wrappers/stream/faketls.go @@ -0,0 +1,145 @@ +package stream + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "time" + + "go.uber.org/zap" + + "github.com/9seconds/mtg/conntypes" +) + +var ( + errFakeTLSTimeout = errors.New("timeout") + fakeTLSWritePrefix = []byte{0x17, 0x03, 0x03} +) + +const faketlsMaxChunkSize = 16384 + 24 + +type wrapperFakeTLS struct { + bufferedReader + + parent conntypes.StreamReadWriteCloser +} + +func (w *wrapperFakeTLS) Write(p []byte) (int, error) { + return w.write(p, func(b []byte) (int, error) { + return w.parent.Write(b) + }) +} + +func (w *wrapperFakeTLS) WriteTimeout(p []byte, timeout time.Duration) (int, error) { + startTime := time.Now() + + return w.write(p, func(b []byte) (int, error) { + elapsed := time.Since(startTime) + if elapsed > timeout { + return w.parent.WriteTimeout(b, timeout-elapsed) + } + return 0, errFakeTLSTimeout + }) +} + +func (w *wrapperFakeTLS) write(p []byte, writeFunc func([]byte) (int, error)) (int, error) { + sum := 0 + size := [2]byte{} + + for len(p) > 0 { + chunkSize := faketlsMaxChunkSize + if chunkSize > len(p) { + chunkSize = len(p) + } + + if _, err := writeFunc(fakeTLSWritePrefix); err != nil { + return sum, err + } + + binary.BigEndian.PutUint16(size[:], uint16(chunkSize)) + + if _, err := writeFunc(size[:]); err != nil { + return sum, err + } + + n, err := writeFunc(p[:chunkSize]) + sum += n + + if err != nil { + return sum, err + } + + p = p[chunkSize:] + } + + return sum, nil +} + +func (w *wrapperFakeTLS) Conn() net.Conn { + return w.parent.Conn() +} + +func (w *wrapperFakeTLS) Logger() *zap.SugaredLogger { + return w.parent.Logger().Named("faketls") +} + +func (w *wrapperFakeTLS) LocalAddr() *net.TCPAddr { + return w.parent.LocalAddr() +} + +func (w *wrapperFakeTLS) RemoteAddr() *net.TCPAddr { + return w.parent.RemoteAddr() +} + +func (w *wrapperFakeTLS) Close() error { + return w.parent.Close() +} + +func NewFakeTLS(socket conntypes.StreamReadWriteCloser) conntypes.StreamReadWriteCloser { + faketls := &wrapperFakeTLS{ + parent: socket, + } + + faketls.readFunc = func() ([]byte, error) { + data := &bytes.Buffer{} + buf := [2]byte{} + recordType := byte(0x14) + + for recordType == 0x14 { + if _, err := io.ReadFull(faketls.parent, buf[:1]); err != nil { + return nil, fmt.Errorf("cannot read record type: %w", err) + } + + switch buf[0] { + case 0x14, 0x17: + recordType = buf[0] + default: + return nil, fmt.Errorf("incorrect record type %v", buf[0]) + } + + if _, err := io.ReadFull(faketls.parent, buf[:]); err != nil { + return nil, fmt.Errorf("cannot read version: %w", err) + } + + if !bytes.Equal(buf[:], []byte{0x03, 0x03}) { + return nil, fmt.Errorf("unknown tls version %v", buf) + } + + if _, err := io.ReadFull(faketls.parent, buf[:]); err != nil { + return nil, fmt.Errorf("cannot read data length: %w", err) + } + + dataLength := binary.BigEndian.Uint16(buf[:]) + if _, err := io.CopyN(data, faketls.parent, int64(dataLength)); err != nil { + return nil, fmt.Errorf("cannot copy frame data: %w", err) + } + } + + return data.Bytes(), nil + } + + return faketls +} diff --git a/wrappers/stream/rewind.go b/wrappers/stream/rewind.go new file mode 100644 index 0000000..b2f4d52 --- /dev/null +++ b/wrappers/stream/rewind.go @@ -0,0 +1,98 @@ +package stream + +import ( + "bytes" + "io" + "net" + "sync" + "time" + + "go.uber.org/zap" + + "github.com/9seconds/mtg/conntypes" +) + +type ReadWriteCloseRewinder interface { + conntypes.StreamReadWriteCloser + Rewind() +} + +type wrapperRewind struct { + parent conntypes.StreamReadWriteCloser + buf bytes.Buffer + mutex sync.Mutex + rewinded bool +} + +func (w *wrapperRewind) Write(p []byte) (int, error) { + return w.parent.Write(p) +} + +func (w *wrapperRewind) WriteTimeout(p []byte, timeout time.Duration) (int, error) { + return w.parent.WriteTimeout(p, timeout) +} + +func (w *wrapperRewind) Read(p []byte) (int, error) { + w.mutex.Lock() + defer w.mutex.Unlock() + + if w.rewinded { + if n, err := w.buf.Read(p); err != io.EOF { + return n, err + } + } + + n, err := w.parent.Read(p) + w.buf.Write(p[:n]) + + return n, err +} + +func (w *wrapperRewind) ReadTimeout(p []byte, timeout time.Duration) (int, error) { + w.mutex.Lock() + defer w.mutex.Unlock() + + if w.rewinded { + if n, err := w.buf.Read(p); err != io.EOF { + return n, err + } + } + + n, err := w.parent.ReadTimeout(p, timeout) + w.buf.Write(p[:n]) + + return n, err +} + +func (w *wrapperRewind) Conn() net.Conn { + return w.parent.Conn() +} + +func (w *wrapperRewind) Logger() *zap.SugaredLogger { + return w.parent.Logger().Named("rewinded") +} + +func (w *wrapperRewind) LocalAddr() *net.TCPAddr { + return w.parent.LocalAddr() +} + +func (w *wrapperRewind) RemoteAddr() *net.TCPAddr { + return w.parent.RemoteAddr() +} + +func (w *wrapperRewind) Close() error { + w.buf.Reset() + return w.parent.Close() +} + +func (w *wrapperRewind) Rewind() { + w.mutex.Lock() + w.rewinded = true + w.mutex.Unlock() +} + +func NewRewind(parent conntypes.StreamReadWriteCloser) ReadWriteCloseRewinder { + return &wrapperRewind{ + parent: parent, + } +} From fbbf9b1634ad8fad24f79090066fbb58bc47b237 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 4 Nov 2019 21:36:59 +0300 Subject: [PATCH 39/62] Get rid of magic constants --- wrappers/stream/faketls.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/wrappers/stream/faketls.go b/wrappers/stream/faketls.go index 7c60a8f..ee1e5f3 100644 --- a/wrappers/stream/faketls.go +++ b/wrappers/stream/faketls.go @@ -19,7 +19,11 @@ var ( fakeTLSWritePrefix = []byte{0x17, 0x03, 0x03} ) -const faketlsMaxChunkSize = 16384 + 24 +const ( + faketlsMaxChunkSize = 16384 + 24 + faketlsRecordTypeApplicationData = 0x17 + faketlsRecordTypeCCS = 0x14 +) type wrapperFakeTLS struct { bufferedReader @@ -106,15 +110,15 @@ func NewFakeTLS(socket conntypes.StreamReadWriteCloser) conntypes.StreamReadWrit faketls.readFunc = func() ([]byte, error) { data := &bytes.Buffer{} buf := [2]byte{} - recordType := byte(0x14) + recordType := byte(faketlsRecordTypeCCS) - for recordType == 0x14 { + for recordType == faketlsRecordTypeCCS { if _, err := io.ReadFull(faketls.parent, buf[:1]); err != nil { return nil, fmt.Errorf("cannot read record type: %w", err) } switch buf[0] { - case 0x14, 0x17: + case faketlsRecordTypeCCS, faketlsRecordTypeApplicationData: recordType = buf[0] default: return nil, fmt.Errorf("incorrect record type %v", buf[0]) From 4c5f1a5636901e4d1e9b8d58252bea2af889a875 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Tue, 5 Nov 2019 10:44:23 +0300 Subject: [PATCH 40/62] Propagate cloak_port setting --- config/config.go | 6 ++++++ main.go | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/config/config.go b/config/config.go index 7bb12d6..bea6989 100644 --- a/config/config.go +++ b/config/config.go @@ -55,6 +55,8 @@ const ( OptionTypeWriteBufferSize OptionTypeReadBufferSize + OptionTypeCloakPort + OptionTypeAntiReplayMaxSize OptionTypeAntiReplayEvictionTime @@ -71,10 +73,12 @@ type Config struct { StatsNamespace string `json:"stats_namespace"` StatsdNetwork string `json:"statsd_network"` + CloakHost string `json:"cloak_host"` StatsdTags map[string]string `json:"statsd_tags"` WriteBuffer int `json:"write_buffer"` ReadBuffer int `json:"read_buffer"` + CloakPort int `json:"cloak_port"` AntiReplayMaxSize int `json:"anti_replay_max_size"` AntiReplayEvictionTime time.Duration `json:"anti_replay_eviction_time"` @@ -144,6 +148,8 @@ func Init(options ...Opt) error { // nolint: gocyclo, funlen C.WriteBuffer = int(opt.Value.(units.Base2Bytes)) case OptionTypeReadBufferSize: C.ReadBuffer = int(opt.Value.(units.Base2Bytes)) + case OptionTypeCloakPort: + C.CloakPort = int(opt.Value.(uint16)) case OptionTypeAntiReplayMaxSize: C.AntiReplayMaxSize = opt.Value.(int) case OptionTypeAntiReplayEvictionTime: diff --git a/main.go b/main.go index 7d0fb31..194545b 100644 --- a/main.go +++ b/main.go @@ -93,6 +93,11 @@ var ( Envar("MTG_BUFFER_READ"). Default("131072KB"). Bytes() + proxyTLSCloakPort = proxyCommand.Flag("cloak-port", + "Port which should be used for host cloaking."). + Envar("MTG_CLOAK_PORT"). + Default("443"). + Uint16() proxyAntiReplayMaxSize = proxyCommand.Flag("anti-replay-max-size", "Max size of antireplay cache in megabytes."). Envar("MTG_ANTIREPLAY_MAXSIZE"). @@ -134,6 +139,7 @@ func main() { config.Opt{Option: config.OptionTypeStatsdTags, Value: *proxyStatsdTags}, config.Opt{Option: config.OptionTypeWriteBufferSize, Value: *proxyWriteBufferSize}, config.Opt{Option: config.OptionTypeReadBufferSize, Value: *proxyReadBufferSize}, + config.Opt{Option: config.OptionTypeCloakPort, Value: *proxyTLSCloakPort}, config.Opt{Option: config.OptionTypeAntiReplayMaxSize, Value: *proxyAntiReplayMaxSize}, config.Opt{Option: config.OptionTypeAntiReplayEvictionTime, Value: *proxyAntiReplayEvictionTime}, config.Opt{Option: config.OptionTypeSecret, Value: *proxySecret}, From 559a1a666b0ae82aad0ecfa1565e4296160d4a1a Mon Sep 17 00:00:00 2001 From: 9seconds Date: Tue, 5 Nov 2019 11:01:29 +0300 Subject: [PATCH 41/62] Generate tls secrets --- cli/generate.go | 4 ++-- config/config.go | 12 ++++++------ main.go | 7 ++++++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/cli/generate.go b/cli/generate.go index 96c8311..5811798 100644 --- a/cli/generate.go +++ b/cli/generate.go @@ -7,7 +7,7 @@ import ( "github.com/9seconds/mtg/config" ) -func Generate(secretType string) { +func Generate(secretType, hostname string) { data := make([]byte, config.SimpleSecretLength) if _, err := rand.Read(data); err != nil { panic(err) @@ -21,6 +21,6 @@ func Generate(secretType string) { case "secured": PrintStdout("dd" + secret) default: - Fatal("Unknown secret type " + secret) + PrintStdout("ee" + secret + hex.EncodeToString([]byte(hostname))) } } diff --git a/config/config.go b/config/config.go index bea6989..588737f 100644 --- a/config/config.go +++ b/config/config.go @@ -169,6 +169,11 @@ func Init(options ...Opt) error { // nolint: gocyclo, funlen C.Secret = bytes.TrimPrefix(C.Secret, []byte{0xdd}) case len(C.Secret) == SimpleSecretLength: C.SecretMode = SecretModeSimple + case bytes.HasPrefix(C.Secret, []byte{0xee}): + C.SecretMode = SecretModeTLS + secret := bytes.TrimPrefix(C.Secret, []byte{0xee}) + C.Secret = secret[:SimpleSecretLength] + C.CloakHost = string(secret[SimpleSecretLength:]) default: return errors.New("incorrect secret") } @@ -225,10 +230,5 @@ func Printable() interface{} { panic(err) } - rrv, err := json.Marshal(rv) - if err != nil { - panic(err) - } - - return rrv + return rv } diff --git a/main.go b/main.go index 194545b..62336ac 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,11 @@ var ( generateSecretCommand = app.Command("generate-secret", "Generate new secret") + generateCloakHost = generateSecretCommand.Flag("cloak-host", + "A host to use for TLS cloaking."). + Short('c'). + Default("storage.googleapis.com"). + String() generateSecretType = generateSecretCommand.Arg("type", "A type of secret to generate. Valid options are 'simple', 'secured' and 'tls'"). Required(). @@ -123,7 +128,7 @@ func main() { switch kingpin.MustParse(app.Parse(os.Args[1:])) { case generateSecretCommand.FullCommand(): - cli.Generate(*generateSecretType) + cli.Generate(*generateSecretType, *generateCloakHost) case proxyCommand.FullCommand(): err := config.Init( config.Opt{Option: config.OptionTypeDebug, Value: *proxyDebug}, From fd8506c82a0f262f56304f6a2c011d6e6803d7e9 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Tue, 5 Nov 2019 11:13:18 +0300 Subject: [PATCH 42/62] Propagate hostname from the secret --- config/config.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/config/config.go b/config/config.go index 588737f..dc4db73 100644 --- a/config/config.go +++ b/config/config.go @@ -167,17 +167,28 @@ func Init(options ...Opt) error { // nolint: gocyclo, funlen case len(C.Secret) == 1+SimpleSecretLength && bytes.HasPrefix(C.Secret, []byte{0xdd}): C.SecretMode = SecretModeSecured C.Secret = bytes.TrimPrefix(C.Secret, []byte{0xdd}) - case len(C.Secret) == SimpleSecretLength: - C.SecretMode = SecretModeSimple - case bytes.HasPrefix(C.Secret, []byte{0xee}): + case len(C.Secret) > SimpleSecretLength && bytes.HasPrefix(C.Secret, []byte{0xee}): C.SecretMode = SecretModeTLS secret := bytes.TrimPrefix(C.Secret, []byte{0xee}) C.Secret = secret[:SimpleSecretLength] C.CloakHost = string(secret[SimpleSecretLength:]) + case len(C.Secret) == SimpleSecretLength: + C.SecretMode = SecretModeSimple default: return errors.New("incorrect secret") } + if C.CloakHost != "" { + addrs, err := net.LookupHost(C.CloakHost) + if err != nil { + return fmt.Errorf("cannot resolve address of %s host: %w", C.CloakHost, err) + } + + if len(addrs) == 0 { + return fmt.Errorf("no known ip addresses for the host %s", C.CloakHost) + } + } + return nil } From 038b2b200db55d01e6eaf8e893b1a0e8445583b1 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 7 Nov 2019 13:04:38 +0300 Subject: [PATCH 43/62] Success path for fake tls is implemented --- antireplay/cache.go | 20 ++++++-- antireplay/init.go | 15 +++++- cli/proxy.go | 11 ++-- config/urls.go | 4 +- faketls/certificate_server.go | 91 +++++++++++++++++++++++++++++++++ faketls/client_protocol.go | 74 +++++++++++++++++++++++++-- faketls/consts.go | 37 ++++++++------ faketls/init.go | 50 ++++++++++++++++++ faketls/telegram_protocol.go | 10 ---- go.mod | 1 + go.sum | 1 + obfuscated2/client_protocol.go | 6 ++- tlstypes/certificate_server.go | 80 +++++++++++++++++++++++++++++ tlstypes/client_hello.go | 86 +++++++++++++++++++++++++++++++ tlstypes/consts.go | 79 +++++++++++++++++++++++++++++ tlstypes/handshake.go | 37 ++++++++++++++ tlstypes/record.go | 85 +++++++++++++++++++++++++++++++ tlstypes/server_hello.go | 92 ++++++++++++++++++++++++++++++++++ utils/uint24.go | 19 +++++++ wrappers/stream/faketls.go | 80 ++++++----------------------- wrappers/stream/rewind.go | 10 +++- 21 files changed, 777 insertions(+), 111 deletions(-) create mode 100644 faketls/certificate_server.go create mode 100644 faketls/init.go delete mode 100644 faketls/telegram_protocol.go create mode 100644 tlstypes/certificate_server.go create mode 100644 tlstypes/client_hello.go create mode 100644 tlstypes/consts.go create mode 100644 tlstypes/handshake.go create mode 100644 tlstypes/record.go create mode 100644 tlstypes/server_hello.go diff --git a/antireplay/cache.go b/antireplay/cache.go index 0e634f2..a142bd9 100644 --- a/antireplay/cache.go +++ b/antireplay/cache.go @@ -3,14 +3,24 @@ package antireplay import "github.com/allegro/bigcache" type cache struct { - cache *bigcache.BigCache + obfuscated2 *bigcache.BigCache + tls *bigcache.BigCache } -func (c *cache) Add(data []byte) { - c.cache.Set(string(data), nil) // nolint: errcheck +func (c *cache) AddObfuscated2(data []byte) { + c.obfuscated2.Set(string(data), nil) // nolint: errcheck } -func (c *cache) Has(data []byte) bool { - _, err := c.cache.Get(string(data)) +func (c *cache) AddTLS(data []byte) { + c.tls.Set(string(data), nil) // nolint: errcheck +} + +func (c *cache) HasObfuscated2(data []byte) bool { + _, err := c.obfuscated2.Get(string(data)) + return err == nil +} + +func (c *cache) HasTLS(data []byte) bool { + _, err := c.tls.Get(string(data)) return err == nil } diff --git a/antireplay/init.go b/antireplay/init.go index 745e702..740fa6f 100644 --- a/antireplay/init.go +++ b/antireplay/init.go @@ -14,7 +14,17 @@ var ( func Init() { initOnce.Do(func() { - c, err := bigcache.NewBigCache(bigcache.Config{ + c1, err := bigcache.NewBigCache(bigcache.Config{ + Shards: 1024, + LifeWindow: config.C.AntiReplayEvictionTime, + Hasher: hasher{}, + HardMaxCacheSize: config.C.AntiReplayMaxSize, + }) + if err != nil { + panic(err) + } + + c2, err := bigcache.NewBigCache(bigcache.Config{ Shards: 1024, LifeWindow: config.C.AntiReplayEvictionTime, Hasher: hasher{}, @@ -25,7 +35,8 @@ func Init() { } Cache = &cache{ - cache: c, + obfuscated2: c1, + tls: c2, } }) } diff --git a/cli/proxy.go b/cli/proxy.go index ef94430..188dc68 100644 --- a/cli/proxy.go +++ b/cli/proxy.go @@ -10,6 +10,7 @@ import ( "github.com/9seconds/mtg/antireplay" "github.com/9seconds/mtg/config" + "github.com/9seconds/mtg/faketls" "github.com/9seconds/mtg/hub" "github.com/9seconds/mtg/ntp" "github.com/9seconds/mtg/obfuscated2" @@ -75,6 +76,7 @@ func Proxy() error { // nolint: funlen antireplay.Init() telegram.Init() hub.Init(ctx) + faketls.Init(ctx) proxyListener, err := net.Listen("tcp", config.C.Bind.String()) if err != nil { @@ -91,12 +93,9 @@ func Proxy() error { // nolint: funlen Context: ctx, ClientProtocolMaker: obfuscated2.MakeClientProtocol, } - // if len(config.C.AdTag) == 0 { - // app.TelegramProtocolMaker = obfuscated2.MakeTelegramProtocol - // } - // if config.C.SecretMode != config.SecretModeTLS { - // app.ClientProtocolMaker = obfuscated2.MakeClientProtocol - // } + if config.C.SecretMode == config.SecretModeTLS { + app.ClientProtocolMaker = faketls.MakeClientProtocol + } app.Serve(proxyListener) diff --git a/config/urls.go b/config/urls.go index 28a6b7b..1e90b1d 100644 --- a/config/urls.go +++ b/config/urls.go @@ -28,11 +28,13 @@ func GetURLs() (urls IPURLs) { secret = hex.EncodeToString(C.Secret) case SecretModeSecured: secret = "dd" + hex.EncodeToString(C.Secret) + case SecretModeTLS: + secret = "ee" + hex.EncodeToString(C.Secret) + hex.EncodeToString([]byte(C.CloakHost)) } urls.IPv4 = makeURLs(C.PublicIPv4, secret) urls.IPv6 = makeURLs(C.PublicIPv6, secret) - urls.BotSecret = secret + urls.BotSecret = hex.EncodeToString(C.Secret) return urls } diff --git a/faketls/certificate_server.go b/faketls/certificate_server.go new file mode 100644 index 0000000..e11e105 --- /dev/null +++ b/faketls/certificate_server.go @@ -0,0 +1,91 @@ +package faketls + +import ( + "bytes" + "container/ring" + "context" + "crypto/tls" + "errors" + "fmt" + "net" + "strconv" + "time" + + "go.uber.org/zap" + + "github.com/9seconds/mtg/config" +) + +type connectionServer struct { + nextWriteItem *ring.Ring + nextReadItem *ring.Ring + + ctx context.Context + channelGet chan chan<- []byte +} + +func (c *connectionServer) get() ([]byte, error) { + resp := make(chan []byte) + select { + case <-c.ctx.Done(): + return nil, errors.New("context closed") + case c.channelGet <- resp: + return <-resp, nil + } +} + +func (c *connectionServer) fetch() ([]byte, error) { + addr := net.JoinHostPort(config.C.CloakHost, strconv.Itoa(config.C.CloakPort)) + conn, err := tls.Dial("tcp", addr, &tls.Config{InsecureSkipVerify: true}) // nolint: gosec + + if err != nil { + return nil, fmt.Errorf("cannot connect to the masked host: %w", err) + } + + defer conn.Close() + + if err = conn.Handshake(); err != nil { + return nil, fmt.Errorf("cannot perform tls handshake: %w", err) + } + + certificates := conn.ConnectionState().PeerCertificates + if len(certificates) == 0 { + return nil, errors.New("no certificates is found") + } + + var buf bytes.Buffer + + for _, v := range certificates { + buf.Write(v.Raw) + } + + return buf.Bytes(), nil +} + +func (c *connectionServer) run(tickEvery time.Duration) { + logger := zap.S().Named("tls-connection-server") + + ticker := time.NewTicker(tickEvery) + defer ticker.Stop() + + for { + select { + case <-c.ctx.Done(): + return + case resp := <-c.channelGet: + resp <- c.nextReadItem.Value.([]byte) + close(resp) + + c.nextReadItem = c.nextReadItem.Next() + case <-ticker.C: + cert, err := c.fetch() + switch err { + case nil: + c.nextWriteItem.Value = cert + c.nextWriteItem = c.nextWriteItem.Next() + default: + logger.Warnw("cannot fetch certificates", "error", err) + } + } + } +} diff --git a/faketls/client_protocol.go b/faketls/client_protocol.go index 620a89d..6102b88 100644 --- a/faketls/client_protocol.go +++ b/faketls/client_protocol.go @@ -2,9 +2,18 @@ package faketls import ( "bufio" + "encoding/binary" + "errors" + "fmt" + "io" + "time" + "github.com/9seconds/mtg/antireplay" "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/obfuscated2" + "github.com/9seconds/mtg/protocol" + "github.com/9seconds/mtg/stats" + "github.com/9seconds/mtg/tlstypes" "github.com/9seconds/mtg/wrappers/stream" ) @@ -18,18 +27,77 @@ func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conn for _, expected := range faketlsStartBytes { if actual, err := bufferedReader.ReadByte(); err != nil || actual != expected { - return nil, c.simulateWebsite(rewinded) + fmt.Println("!!!!!!!!!!!! ERROR !!!!!!!!!!!!", err) + return nil, errors.New("qqq") } } + rewinded.Rewind() + rewinded = stream.NewRewind(rewinded) + if err := c.tlsHandshake(rewinded); err != nil { - return nil, c.simulateWebsite(rewinded) + fmt.Println("!!!!!!!!!!!! ERROR !!!!!!!!!!!!", err) + return nil, errors.New("qqq") } - conn, err := c.ClientProtocol.Handshake(socket) + conn := stream.NewFakeTLS(socket) + conn, err := c.ClientProtocol.Handshake(conn) + if err != nil { return nil, err } return conn, err } + +func (c *ClientProtocol) tlsHandshake(conn io.ReadWriter) error { + helloRecord, err := tlstypes.ReadRecord(conn) + if err != nil { + return fmt.Errorf("cannot read initial record: %w", err) + } + + clientHello, err := tlstypes.ParseClientHello(helloRecord.Data.Bytes()) + if err != nil { + return fmt.Errorf("cannot parse client hello: %w", err) + } + + digest := clientHello.Digest() + for i := 0; i < len(digest)-4; i++ { + if digest[i] != 0 { + return errBadDigest + } + } + + timestamp := int64(binary.LittleEndian.Uint32(digest[len(digest)-4:])) + createdAt := time.Unix(timestamp, 0) + timeDiff := time.Since(createdAt) + + if (timeDiff > TimeSkew || timeDiff < -TimeSkew) && timestamp > TimeFromBoot { + return errBadTime + } + + if antireplay.Cache.HasTLS(clientHello.Random[:]) { + stats.Stats.AntiReplayDetected() + return errors.New("antireplay detected") + } + + antireplay.Cache.AddTLS(clientHello.Random[:]) + + hostCert, err := connectionServerInstance.get() + if err != nil { + return fmt.Errorf("cannot get host certificate: %w", err) + } + + serverHello := tlstypes.NewServerHello(clientHello) + serverHelloPacket := serverHello.WelcomePacket(hostCert) + + if _, err := conn.Write(serverHelloPacket); err != nil { + return fmt.Errorf("cannot send welcome packet: %w", err) + } + + return nil +} + +func MakeClientProtocol() protocol.ClientProtocol { + return &ClientProtocol{} +} diff --git a/faketls/consts.go b/faketls/consts.go index 66061d0..9bd2353 100644 --- a/faketls/consts.go +++ b/faketls/consts.go @@ -1,21 +1,30 @@ package faketls +import ( + "errors" + "time" +) + const ( - TLSHandshakeLength = 1 + 2 + 2 + 512 + TimeSkew = 5 * time.Second + TimeFromBoot = 24 * 60 * 60 ) var ( -faketlsStartBytes = [...]byte{ - 0x16, - 0x03, - 0x01, - 0x02, - 0x00, - 0x01, - 0x00, - 0x01, - 0xfc, - 0x03, - 0x03, -} + errBadDigest = errors.New("bad digest") + errBadTime = errors.New("bad time") + + faketlsStartBytes = [...]byte{ + 0x16, + 0x03, + 0x01, + 0x02, + 0x00, + 0x01, + 0x00, + 0x01, + 0xfc, + 0x03, + 0x03, + } ) diff --git a/faketls/init.go b/faketls/init.go new file mode 100644 index 0000000..b3b6500 --- /dev/null +++ b/faketls/init.go @@ -0,0 +1,50 @@ +package faketls + +import ( + "container/ring" + "context" + "sync" + "time" + + "github.com/9seconds/mtg/config" +) + +var ( + connectionServerInstance connectionServer + connectionServerInitOnce sync.Once +) + +const ( + connectionServerKeepCertificates = 5 + connectionServerUpdateEvery = 10 * time.Minute +) + +func Init(ctx context.Context) { + connectionServerInitOnce.Do(func() { + if config.C.CloakHost == "" { + return + } + + connectionServerInstance = connectionServer{ + channelGet: make(chan chan<- []byte), + ctx: ctx, + } + + cert, err := connectionServerInstance.fetch() + if err != nil { + panic(err) + } + + r := ring.New(connectionServerKeepCertificates) + + for i := 0; i < connectionServerKeepCertificates; i++ { + r.Value = cert + r = r.Next() + } + + connectionServerInstance.nextWriteItem = r + connectionServerInstance.nextReadItem = r + + go connectionServerInstance.run(connectionServerUpdateEvery) + }) +} diff --git a/faketls/telegram_protocol.go b/faketls/telegram_protocol.go deleted file mode 100644 index 9c1cd85..0000000 --- a/faketls/telegram_protocol.go +++ /dev/null @@ -1,10 +0,0 @@ -package faketls - -import ( - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/protocol" -) - -func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.StreamReadWriteCloser, error) { - return nil, nil -} diff --git a/go.mod b/go.mod index f206fda..68e37aa 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( go.uber.org/atomic v1.4.0 // indirect go.uber.org/multierr v1.2.0 // indirect go.uber.org/zap v1.10.0 + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 golang.org/x/net v0.0.0-20191009170851-d66e71096ffb // indirect golang.org/x/sys v0.0.0-20191010194322-b09406accb47 gopkg.in/alecthomas/kingpin.v2 v2.2.6 diff --git a/go.sum b/go.sum index f071844..5ac33a5 100644 --- a/go.sum +++ b/go.sum @@ -109,6 +109,7 @@ go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= diff --git a/obfuscated2/client_protocol.go b/obfuscated2/client_protocol.go index 3ccc7c4..9f940af 100644 --- a/obfuscated2/client_protocol.go +++ b/obfuscated2/client_protocol.go @@ -13,6 +13,7 @@ import ( "github.com/9seconds/mtg/config" "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/protocol" + "github.com/9seconds/mtg/stats" "github.com/9seconds/mtg/utils" "github.com/9seconds/mtg/wrappers/stream" ) @@ -81,11 +82,12 @@ func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conn } antiReplayKey := decryptedFrame.Unique() - if antireplay.Cache.Has(antiReplayKey) { + if antireplay.Cache.HasObfuscated2(antiReplayKey) { + stats.Stats.AntiReplayDetected() return nil, errors.New("replay attack is detected") } - antireplay.Cache.Add(antiReplayKey) + antireplay.Cache.AddObfuscated2(antiReplayKey) return stream.NewObfuscated2(socket, encryptor, decryptor), nil } diff --git a/tlstypes/certificate_server.go b/tlstypes/certificate_server.go new file mode 100644 index 0000000..341d51d --- /dev/null +++ b/tlstypes/certificate_server.go @@ -0,0 +1,80 @@ +package tlstypes + +import ( + "container/ring" + "context" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "net" + "strconv" + "time" + + "go.uber.org/zap" + + "github.com/9seconds/mtg/config" +) + +const ( + connectionServerKeepCertificates = 5 + connectionServerUpdateEvery = 10 * time.Minute +) + +type connectionServer struct { + nextWriteItem *ring.Ring + nextReadItem *ring.Ring + + ctx context.Context + channelGet chan chan<- *x509.Certificate +} + +func (c *connectionServer) fetch() (*x509.Certificate, error) { + addr := net.JoinHostPort(config.C.CloakHost, strconv.Itoa(config.C.CloakPort)) + conn, err := tls.Dial("tcp", addr, &tls.Config{InsecureSkipVerify: true}) // nolint: gosec + + if err != nil { + return nil, fmt.Errorf("cannot connect to the masked host: %w", err) + } + + defer conn.Close() + + if err = conn.Handshake(); err != nil { + return nil, fmt.Errorf("cannot perform tls handshake: %w", err) + } + + certificates := conn.ConnectionState().PeerCertificates + if len(certificates) == 0 { + return nil, errors.New("no certificates is found") + } + + return certificates[0], nil +} + +func (c *connectionServer) run() { + logger := zap.S().Named("tls-connection-server") + + ticker := time.NewTicker(connectionServerUpdateEvery) + defer ticker.Stop() + + for { + select { + case <-c.ctx.Done(): + return + case resp := <-c.channelGet: + resp <- c.nextReadItem.Value.(*x509.Certificate) + close(resp) + + c.nextReadItem = c.nextReadItem.Next() + case <-ticker.C: + cert, err := c.fetch() + switch err { + case nil: + c.nextWriteItem.Value = cert + c.nextWriteItem = c.nextWriteItem.Next() + default: + logger.Warnw("cannot fetch certificates", "error", err) + } + } + } +} diff --git a/tlstypes/client_hello.go b/tlstypes/client_hello.go new file mode 100644 index 0000000..dbdcde4 --- /dev/null +++ b/tlstypes/client_hello.go @@ -0,0 +1,86 @@ +package tlstypes + +import ( + "bytes" + "crypto/hmac" + "crypto/sha256" + "fmt" + + "github.com/9seconds/mtg/config" + "github.com/9seconds/mtg/utils" +) + +type ClientHello struct { + Handshake +} + +func (c ClientHello) Digest() []byte { + dirtyDigest := c.Random + c.Random = [32]byte{} + + rec := Record{ + Type: RecordTypeHandshake, + Version: Version10, + Data: &c, + } + + mac := hmac.New(sha256.New, config.C.Secret) + mac.Write(rec.Bytes()) // nolint: errcheck + computedDigest := mac.Sum(nil) + + for i := range computedDigest { + computedDigest[i] ^= dirtyDigest[i] + } + + return computedDigest +} + +func ParseClientHello(raw []byte) (*ClientHello, error) { + rv := &ClientHello{} + + rv.Type = HandshakeType(raw[0]) + if rv.Type != HandshakeTypeClient { + return nil, fmt.Errorf("incorrect handshake type %v", rv.Type) + } + + raw = raw[1:] + sizeUint24 := utils.Uint24{} + copy(sizeUint24[:], utils.ReverseBytes(raw[:3])) + size := int(utils.FromUint24(sizeUint24)) + + raw = raw[3:] + if len(raw) != size { + return nil, fmt.Errorf("payload size mismatch (%d != %d)", len(raw), size) + } + + versionRaw := raw[:2] + + switch { + case bytes.Equal(versionRaw, Version13Bytes): + rv.Version = Version13 + case bytes.Equal(versionRaw, Version12Bytes): + rv.Version = Version12 + case bytes.Equal(versionRaw, Version11Bytes): + rv.Version = Version11 + case bytes.Equal(versionRaw, Version10Bytes): + rv.Version = Version10 + default: + return nil, fmt.Errorf("unknown protocol version %v", versionRaw) + } + + raw = raw[2:] + copy(rv.Random[:], raw[:32]) + raw = raw[32:] + + sessionIDLength := int(raw[0]) + raw = raw[1:] + rv.SessionID = make([]byte, sessionIDLength) + copy(rv.SessionID, raw) + raw = raw[sessionIDLength:] + + tail := make([]byte, len(raw)) + copy(tail, raw) + rv.Tail = RawBytes(tail) + + return rv, nil +} diff --git a/tlstypes/consts.go b/tlstypes/consts.go new file mode 100644 index 0000000..72e6935 --- /dev/null +++ b/tlstypes/consts.go @@ -0,0 +1,79 @@ +package tlstypes + +type RecordType uint8 + +const ( + RecordTypeHandshake RecordType = 0x16 + RecordTypeApplicationData RecordType = 0x17 + RecordTypeChangeCipherSpec RecordType = 0x14 +) + +type HandshakeType uint8 + +const ( + HandshakeTypeClient HandshakeType = 0x01 + HandshakeTypeServer HandshakeType = 0x02 +) + +type CipherSuiteType uint8 + +const ( + CipherSuiteType_TLS_AES_128_GCM_SHA256 CipherSuiteType = iota // nolint: stylecheck, golint + CipherSuiteType_TLS_AES_256_GCM_SHA384 // nolint: stylecheck, golint + CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256 // nolint: stylecheck, golint +) + +func (c CipherSuiteType) Bytes() []byte { + switch c { + case CipherSuiteType_TLS_AES_128_GCM_SHA256: + return CipherSuiteType_TLS_AES_128_GCM_SHA256_Bytes + case CipherSuiteType_TLS_AES_256_GCM_SHA384: + return CipherSuiteType_TLS_AES_256_GCM_SHA384_Bytes + } + + return CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256_Bytes +} + +type Version uint8 + +func (v Version) Bytes() []byte { + switch v { + case Version13: + return Version13Bytes + case Version12: + return Version12Bytes + case Version11: + return Version11Bytes + } + + return Version10Bytes +} + +const ( + VersionUnknown Version = iota + Version10 + Version11 + Version12 + Version13 +) + +var ( + Version10Bytes = []byte{0x03, 0x01} + Version11Bytes = []byte{0x03, 0x02} + Version12Bytes = []byte{0x03, 0x03} + Version13Bytes = []byte{0x03, 0x04} + + CipherSuiteType_TLS_AES_128_GCM_SHA256_Bytes = []byte{0x13, 0x01} // nolint: stylecheck, golint + CipherSuiteType_TLS_AES_256_GCM_SHA384_Bytes = []byte{0x13, 0x02} // nolint: stylecheck, golint + CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256_Bytes = []byte{0x13, 0x03} // nolint; stylecheck, golint +) + +type Byter interface { + Bytes() []byte +} + +type RawBytes []byte + +func (r RawBytes) Bytes() []byte { + return []byte(r) +} diff --git a/tlstypes/handshake.go b/tlstypes/handshake.go new file mode 100644 index 0000000..ec0accf --- /dev/null +++ b/tlstypes/handshake.go @@ -0,0 +1,37 @@ +package tlstypes + +import ( + "bytes" + + "github.com/9seconds/mtg/utils" +) + +type Handshake struct { + Type HandshakeType + Version Version + Random [32]byte + SessionID []byte + Tail Byter +} + +func (h *Handshake) Bytes() []byte { + buf := bytes.Buffer{} + packetBuf := bytes.Buffer{} + + buf.WriteByte(byte(h.Type)) + + packetBuf.Write(h.Version.Bytes()) + packetBuf.Write(h.Random[:]) + packetBuf.WriteByte(byte(len(h.SessionID))) + packetBuf.Write(h.SessionID) + packetBuf.Write(h.Tail.Bytes()) + + sizeUint24 := utils.ToUint24(uint32(packetBuf.Len())) + sizeUint24Bytes := sizeUint24[:] + sizeUint24Bytes[0], sizeUint24Bytes[2] = sizeUint24Bytes[2], sizeUint24Bytes[0] + + buf.Write(sizeUint24Bytes) + packetBuf.WriteTo(&buf) // nolint: errcheck + + return buf.Bytes() +} diff --git a/tlstypes/record.go b/tlstypes/record.go new file mode 100644 index 0000000..d6a71dd --- /dev/null +++ b/tlstypes/record.go @@ -0,0 +1,85 @@ +package tlstypes + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" +) + +const recordMaxChunkSize = 16384 + 24 + +type Record struct { + Type RecordType + Version Version + Data Byter +} + +func (r Record) Bytes() []byte { + buf := bytes.Buffer{} + data := r.Data.Bytes() + + buf.WriteByte(byte(r.Type)) + buf.Write(r.Version.Bytes()) + binary.Write(&buf, binary.BigEndian, uint16(len(data))) // nolint: errcheck + buf.Write(data) + + return buf.Bytes() +} + +func ReadRecord(reader io.Reader) (Record, error) { + buf := [2]byte{} + rec := Record{} + + if _, err := io.ReadFull(reader, buf[:1]); err != nil { + return rec, fmt.Errorf("cannot read record type: %w", err) + } + + rec.Type = RecordType(buf[0]) + + if _, err := io.ReadFull(reader, buf[:]); err != nil { + return rec, fmt.Errorf("cannot read version: %w", err) + } + + switch { + case bytes.Equal(buf[:], Version13Bytes): + rec.Version = Version13 + case bytes.Equal(buf[:], Version12Bytes): + rec.Version = Version12 + case bytes.Equal(buf[:], Version11Bytes): + rec.Version = Version11 + case bytes.Equal(buf[:], Version10Bytes): + rec.Version = Version10 + } + + if _, err := io.ReadFull(reader, buf[:]); err != nil { + return rec, fmt.Errorf("cannot read data length: %w", err) + } + + data := make([]byte, binary.BigEndian.Uint16(buf[:])) + if _, err := io.ReadFull(reader, data); err != nil { + return rec, fmt.Errorf("cannot read data: %w", err) + } + + rec.Data = RawBytes(data) + + return rec, nil +} + +func MakeRecords(raw []byte) (arr []Record) { + for len(raw) > 0 { + chunkSize := recordMaxChunkSize + if chunkSize > len(raw) { + chunkSize = len(raw) + } + + arr = append(arr, Record{ + Type: RecordTypeApplicationData, + Version: Version12, + Data: RawBytes(raw[:chunkSize]), + }) + raw = raw[chunkSize:] + } + + return +} diff --git a/tlstypes/server_hello.go b/tlstypes/server_hello.go new file mode 100644 index 0000000..b233d29 --- /dev/null +++ b/tlstypes/server_hello.go @@ -0,0 +1,92 @@ +package tlstypes + +import ( + "bytes" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "io" + + "golang.org/x/crypto/curve25519" + + "github.com/9seconds/mtg/config" +) + +type ServerHello struct { + Handshake + + clientHello *ClientHello +} + +func (s ServerHello) WelcomePacket(hostCert []byte) []byte { + s.Random = [32]byte{} + rec := Record{ + Type: RecordTypeHandshake, + Version: Version12, + Data: &s, + } + buf := bytes.NewBuffer(rec.Bytes()) + + recChangeCipher := Record{ + Type: RecordTypeChangeCipherSpec, + Version: Version12, + Data: RawBytes([]byte{0x01}), + } + buf.Write(recChangeCipher.Bytes()) + + recData := Record{ + Type: RecordTypeApplicationData, + Version: Version12, + Data: RawBytes(hostCert), + } + buf.Write(recData.Bytes()) + packet := buf.Bytes() + + mac := hmac.New(sha256.New, config.C.Secret) + mac.Write(s.clientHello.Random[:]) // nolint: errcheck + mac.Write(packet) // nolint: errcheck + copy(packet[11:], mac.Sum(nil)) + + return packet +} + +func NewServerHello(clientHello *ClientHello) *ServerHello { + rv := &ServerHello{ + clientHello: clientHello, + } + + rv.Type = HandshakeTypeServer + rv.Version = Version12 + rv.SessionID = make([]byte, len(clientHello.SessionID)) + copy(rv.SessionID, clientHello.SessionID) + + tail := bytes.NewBuffer(CipherSuiteType_TLS_AES_128_GCM_SHA256_Bytes) + tail.WriteByte(0x00) // no compression + makeTLSExtensions(tail) + rv.Tail = RawBytes(tail.Bytes()) + + return rv +} + +func makeTLSExtensions(buf io.Writer) { + buf.Write([]byte{ // nolint: errcheck + 0x00, 0x2e, // 46 bytes of data + 0x00, 0x33, // Extension - Key Share + 0x00, 0x24, // 36 bytes + 0x00, 0x1d, // x25519 curve + 0x00, 0x20, // 32 bytes of key + }) + + var dst, in, base [32]byte + + rand.Read(in[:]) // nolint: errcheck + rand.Read(base[:]) // nolint: errcheck + curve25519.ScalarMult(&dst, &in, &base) + buf.Write(dst[:]) // nolint: errcheck + + buf.Write([]byte{ // nolint: errcheck + 0x00, 0x2b, // Extension - Supported Versions + 0x00, 0x02, // 2 bytes are following + 0x03, 0x04, // TLS 1.3 + }) +} diff --git a/utils/uint24.go b/utils/uint24.go index 350f3d5..be4ac1e 100644 --- a/utils/uint24.go +++ b/utils/uint24.go @@ -1,5 +1,10 @@ package utils +import ( + "fmt" + "strings" +) + type Uint24 [3]byte func ToUint24(number uint32) Uint24 { @@ -9,3 +14,17 @@ func ToUint24(number uint32) Uint24 { func FromUint24(number Uint24) uint32 { return uint32(number[0]) + (uint32(number[1]) << 8) + (uint32(number[2]) << 16) } + +func Hexify(data []byte) string { + s := []string{} + + for _, v := range data { + if v < 0x10 { + s = append(s, fmt.Sprintf("0x0%x", v)) + } else { + s = append(s, fmt.Sprintf("0x%x", v)) + } + } + + return strings.Join(s, " ") +} diff --git a/wrappers/stream/faketls.go b/wrappers/stream/faketls.go index ee1e5f3..3db845b 100644 --- a/wrappers/stream/faketls.go +++ b/wrappers/stream/faketls.go @@ -1,28 +1,15 @@ package stream import ( - "bytes" - "encoding/binary" "errors" "fmt" - "io" "net" "time" "go.uber.org/zap" "github.com/9seconds/mtg/conntypes" -) - -var ( - errFakeTLSTimeout = errors.New("timeout") - fakeTLSWritePrefix = []byte{0x17, 0x03, 0x03} -) - -const ( - faketlsMaxChunkSize = 16384 + 24 - faketlsRecordTypeApplicationData = 0x17 - faketlsRecordTypeCCS = 0x14 + "github.com/9seconds/mtg/tlstypes" ) type wrapperFakeTLS struct { @@ -45,38 +32,20 @@ func (w *wrapperFakeTLS) WriteTimeout(p []byte, timeout time.Duration) (int, err if elapsed > timeout { return w.parent.WriteTimeout(b, timeout-elapsed) } - return 0, errFakeTLSTimeout + return 0, errors.New("timeout") }) } func (w *wrapperFakeTLS) write(p []byte, writeFunc func([]byte) (int, error)) (int, error) { sum := 0 - size := [2]byte{} - - for len(p) > 0 { - chunkSize := faketlsMaxChunkSize - if chunkSize > len(p) { - chunkSize = len(p) - } - - if _, err := writeFunc(fakeTLSWritePrefix); err != nil { - return sum, err - } - - binary.BigEndian.PutUint16(size[:], uint16(chunkSize)) - - if _, err := writeFunc(size[:]); err != nil { - return sum, err - } - - n, err := writeFunc(p[:chunkSize]) - sum += n + for _, v := range tlstypes.MakeRecords(p) { + _, err := writeFunc(v.Bytes()) if err != nil { return sum, err } - p = p[chunkSize:] + sum += len(v.Data.Bytes()) } return sum, nil @@ -108,41 +77,20 @@ func NewFakeTLS(socket conntypes.StreamReadWriteCloser) conntypes.StreamReadWrit } faketls.readFunc = func() ([]byte, error) { - data := &bytes.Buffer{} - buf := [2]byte{} - recordType := byte(faketlsRecordTypeCCS) - - for recordType == faketlsRecordTypeCCS { - if _, err := io.ReadFull(faketls.parent, buf[:1]); err != nil { - return nil, fmt.Errorf("cannot read record type: %w", err) + for { + rec, err := tlstypes.ReadRecord(faketls.parent) + if err != nil { + return nil, err } - switch buf[0] { - case faketlsRecordTypeCCS, faketlsRecordTypeApplicationData: - recordType = buf[0] + switch rec.Type { + case tlstypes.RecordTypeChangeCipherSpec: + case tlstypes.RecordTypeApplicationData: + return rec.Data.Bytes(), nil default: - return nil, fmt.Errorf("incorrect record type %v", buf[0]) - } - - if _, err := io.ReadFull(faketls.parent, buf[:]); err != nil { - return nil, fmt.Errorf("cannot read version: %w", err) - } - - if !bytes.Equal(buf[:], []byte{0x03, 0x03}) { - return nil, fmt.Errorf("unknown tls version %v", buf) - } - - if _, err := io.ReadFull(faketls.parent, buf[:]); err != nil { - return nil, fmt.Errorf("cannot read data length: %w", err) - } - - dataLength := binary.BigEndian.Uint16(buf[:]) - if _, err := io.CopyN(data, faketls.parent, int64(dataLength)); err != nil { - return nil, fmt.Errorf("cannot copy frame data: %w", err) + return nil, fmt.Errorf("unsupported record type %v", rec.Type) } } - - return data.Bytes(), nil } return faketls diff --git a/wrappers/stream/rewind.go b/wrappers/stream/rewind.go index b2f4d52..1170d9d 100644 --- a/wrappers/stream/rewind.go +++ b/wrappers/stream/rewind.go @@ -43,7 +43,10 @@ func (w *wrapperRewind) Read(p []byte) (int, error) { } n, err := w.parent.Read(p) - w.buf.Write(p[:n]) + + if !w.rewinded { + w.buf.Write(p[:n]) + } return n, err } @@ -59,7 +62,10 @@ func (w *wrapperRewind) ReadTimeout(p []byte, timeout time.Duration) (int, error } n, err := w.parent.ReadTimeout(p, timeout) - w.buf.Write(p[:n]) + + if !w.rewinded { + w.buf.Write(p[:n]) + } return n, err } From d8216f776d02533f8a08189793a11247215a8039 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 7 Nov 2019 13:55:41 +0300 Subject: [PATCH 44/62] First version of website cloaking --- faketls/client_protocol.go | 45 ++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/faketls/client_protocol.go b/faketls/client_protocol.go index 6102b88..9acdbd3 100644 --- a/faketls/client_protocol.go +++ b/faketls/client_protocol.go @@ -6,9 +6,13 @@ import ( "errors" "fmt" "io" + "net" + "strconv" + "sync" "time" "github.com/9seconds/mtg/antireplay" + "github.com/9seconds/mtg/config" "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/obfuscated2" "github.com/9seconds/mtg/protocol" @@ -27,8 +31,10 @@ func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conn for _, expected := range faketlsStartBytes { if actual, err := bufferedReader.ReadByte(); err != nil || actual != expected { - fmt.Println("!!!!!!!!!!!! ERROR !!!!!!!!!!!!", err) - return nil, errors.New("qqq") + rewinded.Rewind() + c.cloakHost(rewinded) + + return nil, errors.New("failed first bytes of tls handshake") } } @@ -36,8 +42,10 @@ func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conn rewinded = stream.NewRewind(rewinded) if err := c.tlsHandshake(rewinded); err != nil { - fmt.Println("!!!!!!!!!!!! ERROR !!!!!!!!!!!!", err) - return nil, errors.New("qqq") + rewinded.Rewind() + c.cloakHost(rewinded) + + return nil, fmt.Errorf("failed tls handshake: %w", err) } conn := stream.NewFakeTLS(socket) @@ -98,6 +106,35 @@ func (c *ClientProtocol) tlsHandshake(conn io.ReadWriter) error { return nil } +func (c *ClientProtocol) cloakHost(clientConn io.ReadWriteCloser) { + addr := net.JoinHostPort(config.C.CloakHost, strconv.Itoa(config.C.CloakPort)) + hostConn, err := net.Dial("tcp", addr) + + if err != nil { + return + } + + defer hostConn.Close() + + wg := &sync.WaitGroup{} + wg.Add(2) + + go c.pipe(hostConn, clientConn, wg) + + go c.pipe(clientConn, hostConn, wg) + + wg.Wait() +} + +func (c *ClientProtocol) pipe(dst io.WriteCloser, src io.Reader, wg *sync.WaitGroup) { + defer func() { + wg.Done() + dst.Close() + }() + + io.Copy(dst, src) // nolint: errcheck +} + func MakeClientProtocol() protocol.ClientProtocol { return &ClientProtocol{} } From 9e3683ce082de156f973cfd122d57a59d38a3b3c Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 7 Nov 2019 13:56:19 +0300 Subject: [PATCH 45/62] Update golangci-lint --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e90b35d..e56ab75 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ APP_NAME := $(IMAGE_NAME) CC_BINARIES := $(shell bash -c "echo -n $(APP_NAME)-{linux,freebsd,openbsd}-{386,amd64} $(APP_NAME)-linux-{arm,arm64}") -GOLANGCI_LINT_VERSION := v1.20.0 +GOLANGCI_LINT_VERSION := v1.21.0 VERSION_GO := $(shell go version) VERSION_DATE := $(shell date -Ru) From d4cd779c42c7520e06ddee6752c4b406c902923c Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 7 Nov 2019 13:58:19 +0300 Subject: [PATCH 46/62] Fix lint issues --- tlstypes/certificate_server.go | 80 ------------------------------ wrappers/stream/blockcipher.go | 1 + wrappers/stream/buffered_reader.go | 1 + 3 files changed, 2 insertions(+), 80 deletions(-) delete mode 100644 tlstypes/certificate_server.go diff --git a/tlstypes/certificate_server.go b/tlstypes/certificate_server.go deleted file mode 100644 index 341d51d..0000000 --- a/tlstypes/certificate_server.go +++ /dev/null @@ -1,80 +0,0 @@ -package tlstypes - -import ( - "container/ring" - "context" - "crypto/tls" - "crypto/x509" - "errors" - "fmt" - "net" - "strconv" - "time" - - "go.uber.org/zap" - - "github.com/9seconds/mtg/config" -) - -const ( - connectionServerKeepCertificates = 5 - connectionServerUpdateEvery = 10 * time.Minute -) - -type connectionServer struct { - nextWriteItem *ring.Ring - nextReadItem *ring.Ring - - ctx context.Context - channelGet chan chan<- *x509.Certificate -} - -func (c *connectionServer) fetch() (*x509.Certificate, error) { - addr := net.JoinHostPort(config.C.CloakHost, strconv.Itoa(config.C.CloakPort)) - conn, err := tls.Dial("tcp", addr, &tls.Config{InsecureSkipVerify: true}) // nolint: gosec - - if err != nil { - return nil, fmt.Errorf("cannot connect to the masked host: %w", err) - } - - defer conn.Close() - - if err = conn.Handshake(); err != nil { - return nil, fmt.Errorf("cannot perform tls handshake: %w", err) - } - - certificates := conn.ConnectionState().PeerCertificates - if len(certificates) == 0 { - return nil, errors.New("no certificates is found") - } - - return certificates[0], nil -} - -func (c *connectionServer) run() { - logger := zap.S().Named("tls-connection-server") - - ticker := time.NewTicker(connectionServerUpdateEvery) - defer ticker.Stop() - - for { - select { - case <-c.ctx.Done(): - return - case resp := <-c.channelGet: - resp <- c.nextReadItem.Value.(*x509.Certificate) - close(resp) - - c.nextReadItem = c.nextReadItem.Next() - case <-ticker.C: - cert, err := c.fetch() - switch err { - case nil: - c.nextWriteItem.Value = cert - c.nextWriteItem = c.nextWriteItem.Next() - default: - logger.Warnw("cannot fetch certificates", "error", err) - } - } - } -} diff --git a/wrappers/stream/blockcipher.go b/wrappers/stream/blockcipher.go index 1a40076..3fc7a40 100644 --- a/wrappers/stream/blockcipher.go +++ b/wrappers/stream/blockcipher.go @@ -85,6 +85,7 @@ func newBlockCipher(parent conntypes.StreamReadWriteCloser, if err != nil { return nil, fmt.Errorf("cannot read data: %w", err) } + currentBuffer = append(currentBuffer, rv...) } cipher.decryptor.CryptBlocks(currentBuffer, currentBuffer) diff --git a/wrappers/stream/buffered_reader.go b/wrappers/stream/buffered_reader.go index 8b23627..6a551f7 100644 --- a/wrappers/stream/buffered_reader.go +++ b/wrappers/stream/buffered_reader.go @@ -21,6 +21,7 @@ func (b *bufferedReader) Read(p []byte) (int, error) { if err != nil { return 0, err } + b.buf.Write(res) return b.flush(p) From f005592872b07725beda68976c85c4edce46b80a Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 7 Nov 2019 13:59:20 +0300 Subject: [PATCH 47/62] Update dependencies --- go.mod | 19 +++++++------------ go.sum | 59 +++++++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 51 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 68e37aa..8c7a5d2 100644 --- a/go.mod +++ b/go.mod @@ -8,21 +8,16 @@ require ( github.com/allegro/bigcache v1.2.1 github.com/beevik/ntp v0.2.0 github.com/cespare/xxhash v1.1.0 - github.com/kr/pretty v0.1.0 // indirect - github.com/prometheus/client_golang v1.1.0 - github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect - github.com/prometheus/common v0.7.0 // indirect - github.com/prometheus/procfs v0.0.5 // indirect + github.com/prometheus/client_golang v1.2.1 github.com/spaolacci/murmur3 v1.1.0 // indirect - go.uber.org/atomic v1.4.0 // indirect - go.uber.org/multierr v1.2.0 // indirect - go.uber.org/zap v1.10.0 - golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 - golang.org/x/net v0.0.0-20191009170851-d66e71096ffb // indirect - golang.org/x/sys v0.0.0-20191010194322-b09406accb47 + go.uber.org/multierr v1.4.0 // indirect + go.uber.org/zap v1.12.0 + golang.org/x/crypto v0.0.0-20191106202628-ed6320f186d4 + golang.org/x/net v0.0.0-20191105084925-a882066a44e0 // indirect + golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd + golang.org/x/tools v0.0.0-20191107010934-f79515f33823 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/alexcesaro/statsd.v2 v2.0.0 - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect ) go 1.13 diff --git a/go.sum b/go.sum index 5ac33a5..741a398 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= @@ -23,6 +25,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= +github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -42,9 +46,11 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/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= @@ -67,8 +73,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= +github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= @@ -77,18 +83,15 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCb github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d h1:GoAlyOgbOEIFdaDqxJVlbOQ1DtGmZWs/Qau0hIlk+WQ= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -102,30 +105,53 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.2.0 h1:6I+W7f5VwC5SV9dNrZ3qXrDB9mD0dyGOi/ZJmYw03T4= -go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E= +go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.12.0 h1:dySoUQPFBGj6xwjmBzageVL8jGi8uxc6bEmJQjA06bw= +go.uber.org/zap v1.12.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191106202628-ed6320f186d4 h1:PDpCLFAH/YIX0QpHPf2eO7L4rC2OOirBrKtXTLLiNTY= +golang.org/x/crypto v0.0.0-20191106202628-ed6320f186d4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191009170851-d66e71096ffb h1:TR699M2v0qoKTOHxeLgp6zPqaQNs74f01a/ob9W0qko= -golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191105084925-a882066a44e0 h1:QPlSTtPE2k6PZPasQUbzuK3p9JbS+vMXYVto8g/yrsg= +golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd h1:3x5uuvBgE6oaXJjCOvpCC1IpgJogqQ+PqGGU3ZxAgII= +golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191107010934-f79515f33823 h1:akkRBeitX2EZP59KdtKw310CI4WGPCNPyrLbE7WZA8Y= +golang.org/x/tools v0.0.0-20191107010934-f79515f33823/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/statsd.v2 v2.0.0 h1:FXkZSCZIH17vLCO5sO2UucTHsH9pc+17F6pl3JVCwMc= @@ -133,7 +159,10 @@ gopkg.in/alexcesaro/statsd.v2 v2.0.0/go.mod h1:i0ubccKGzBVNBpdGV5MocxyA/XlLUJzA7 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= From ed180ecb3dee68b13425bbe49a95e2ee147dfe03 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 7 Nov 2019 14:01:04 +0300 Subject: [PATCH 48/62] Remove unnecessary hexify function --- utils/uint24.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/utils/uint24.go b/utils/uint24.go index be4ac1e..350f3d5 100644 --- a/utils/uint24.go +++ b/utils/uint24.go @@ -1,10 +1,5 @@ package utils -import ( - "fmt" - "strings" -) - type Uint24 [3]byte func ToUint24(number uint32) Uint24 { @@ -14,17 +9,3 @@ func ToUint24(number uint32) Uint24 { func FromUint24(number Uint24) uint32 { return uint32(number[0]) + (uint32(number[1]) << 8) + (uint32(number[2]) << 16) } - -func Hexify(data []byte) string { - s := []string{} - - for _, v := range data { - if v < 0x10 { - s = append(s, fmt.Sprintf("0x0%x", v)) - } else { - s = append(s, fmt.Sprintf("0x%x", v)) - } - } - - return strings.Join(s, " ") -} From 0ff9a587800e26f67f0a5bac57fe6fdf6c9f85db Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 7 Nov 2019 14:01:08 +0300 Subject: [PATCH 49/62] Update dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 88f81fc..fa0cb12 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ############################################################################### # BUILD STAGE -FROM golang:1.12-alpine +FROM golang:1.13-alpine RUN set -x \ && apk --no-cache --update add \ From 22905b2a25c3cad48f1a97a67b33986b1dc61697 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 11 Nov 2019 14:04:15 +0300 Subject: [PATCH 50/62] Correct multiplexing --- faketls/client_protocol.go | 9 -- hub/connection.go | 191 ++++++++++++++++++++------------- hub/connection_hub.go | 114 -------------------- hub/connection_list.go | 70 ++++++++++++ hub/ctx_channel.go | 61 ----------- hub/hub.go | 63 +++-------- hub/init.go | 19 +--- hub/interface.go | 7 ++ hub/mux.go | 80 ++++++++++++++ hub/proxy_conn.go | 77 +++++++++++++ hub/registry.go | 45 -------- main.go | 76 ++++++------- obfuscated2/client_protocol.go | 8 +- proxy/middle.go | 6 +- stats/interfaces.go | 6 +- stats/multi_stats.go | 4 +- stats/stats_prometheus.go | 16 +-- stats/stats_statsd.go | 4 +- wrappers/packetack/proxy.go | 25 +++-- 19 files changed, 445 insertions(+), 436 deletions(-) delete mode 100644 hub/connection_hub.go create mode 100644 hub/connection_list.go delete mode 100644 hub/ctx_channel.go create mode 100644 hub/interface.go create mode 100644 hub/mux.go create mode 100644 hub/proxy_conn.go delete mode 100644 hub/registry.go diff --git a/faketls/client_protocol.go b/faketls/client_protocol.go index 9acdbd3..f0b82b2 100644 --- a/faketls/client_protocol.go +++ b/faketls/client_protocol.go @@ -11,12 +11,10 @@ import ( "sync" "time" - "github.com/9seconds/mtg/antireplay" "github.com/9seconds/mtg/config" "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/obfuscated2" "github.com/9seconds/mtg/protocol" - "github.com/9seconds/mtg/stats" "github.com/9seconds/mtg/tlstypes" "github.com/9seconds/mtg/wrappers/stream" ) @@ -84,13 +82,6 @@ func (c *ClientProtocol) tlsHandshake(conn io.ReadWriter) error { return errBadTime } - if antireplay.Cache.HasTLS(clientHello.Random[:]) { - stats.Stats.AntiReplayDetected() - return errors.New("antireplay detected") - } - - antireplay.Cache.AddTLS(clientHello.Random[:]) - hostCert, err := connectionServerInstance.get() if err != nil { return fmt.Errorf("cannot get host certificate: %w", err) diff --git a/hub/connection.go b/hub/connection.go index c3edeb8..60ee738 100644 --- a/hub/connection.go +++ b/hub/connection.go @@ -5,6 +5,8 @@ import ( "math/rand" "sync" + "go.uber.org/zap" + "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/mtproto" "github.com/9seconds/mtg/mtproto/rpc" @@ -12,108 +14,149 @@ import ( ) type connection struct { - conn conntypes.PacketReadWriteCloser - mutex sync.RWMutex - shutdownOnce sync.Once - hub *connectionHub - id int - pending uint - done chan struct{} -} + conn conntypes.PacketReadWriteCloser + proxyConns map[string]*ProxyConn + closeOnce sync.Once + proxyConnsMutex sync.RWMutex + id int + logger *zap.SugaredLogger -func (c *connection) read() (conntypes.Packet, error) { - packet, err := c.conn.Read() - - c.mutex.Lock() - if err != nil { - c.pending-- - } else { - c.pending = 0 - } - c.mutex.Unlock() - - return packet, err -} - -func (c *connection) write(packet conntypes.Packet) error { - err := c.conn.Write(packet) - if err != nil { - // if we tried to write into a socket and it was broken, it is - // a time to reconsider the prescence of this socket at all. - // - // probably we need to remove it completely because it seems - // that connection is broken. - c.mutex.Lock() - c.pending = 0 - c.mutex.Unlock() - } - - return err -} - -func (c *connection) shutdown() { - c.shutdownOnce.Do(func() { - c.conn.Close() - close(c.done) - c.hub.channelBrokenSockets <- c.id - }) -} - -func (c *connection) closed() bool { - select { - case <-c.done: - return true - default: - return false - } -} - -func (c *connection) idle() bool { - c.mutex.RLock() - defer c.mutex.RUnlock() - - return c.pending == 0 + channelDone chan struct{} + channelWrite chan conntypes.Packet + channelRead chan *rpc.ProxyResponse + channelConnAttach chan *ProxyConn + channelConnDetach chan conntypes.ConnID } func (c *connection) run() { - logger := c.hub.logger.Named("connection").With("id", c.id) + defer c.Close() for { - packet, err := c.read() + select { + case <-c.channelDone: + for _, v := range c.proxyConns { + v.Close() + } + + return + case resp := <-c.channelRead: + if channel, ok := c.proxyConns[string(resp.ConnID[:])]; ok { + if resp.Type == rpc.ProxyResponseTypeCloseExt { + channel.Close() + } else { + channel.put(resp) + } + } + case packet := <-c.channelWrite: + if err := c.conn.Write(packet); err != nil { + c.logger.Debugw("Cannot write packet", "error", err) + c.Close() + } + case conn := <-c.channelConnAttach: + c.proxyConnsMutex.Lock() + c.proxyConns[string(conn.req.ConnID[:])] = conn + c.proxyConnsMutex.Unlock() + conn.channelWrite = c.channelWrite + case connID := <-c.channelConnDetach: + if conn, ok := c.proxyConns[string(connID[:])]; ok { + c.proxyConnsMutex.Lock() + delete(c.proxyConns, string(connID[:])) + c.proxyConnsMutex.Unlock() + conn.Close() + } + } + } +} + +func (c *connection) readLoop() { + for { + packet, err := c.conn.Read() if err != nil { - c.shutdown() + c.logger.Debugw("Cannot read packet", "error", err) + c.Close() + return } response, err := rpc.ParseProxyResponse(packet) if err != nil { - logger.Debugw("Failed response", "error", err) + c.logger.Debugw("Failed response", "error", err) continue } - if response.Type == rpc.ProxyResponseTypeCloseExt { - logger.Debugw("Proxy has closed connection") + select { + case <-c.channelDone: return - } - - if channel, ok := Registry.getChannel(response.ConnID); ok { - go channel.sendBack(response) // nolint: errcheck + case c.channelRead <- response: } } } -func newConnection(req *protocol.TelegramRequest, hub *connectionHub) (*connection, error) { +func (c *connection) Close() { + c.closeOnce.Do(func() { + c.logger.Debugw("Closing connection") + + close(c.channelDone) + c.conn.Close() + }) +} + +func (c *connection) Done() bool { + select { + case <-c.channelDone: + return true + default: + return c.Len() == 0 + } +} + +func (c *connection) Len() int { + c.proxyConnsMutex.RLock() + defer c.proxyConnsMutex.RUnlock() + + return len(c.proxyConns) +} + +func (c *connection) Attach(conn *ProxyConn) error { + select { + case <-c.channelDone: + return ErrClosed + case c.channelConnAttach <- conn: + return nil + } +} + +func (c *connection) Detach(connID conntypes.ConnID) { + select { + case <-c.channelDone: + case c.channelConnDetach <- connID: + } +} + +func newConnection(req *protocol.TelegramRequest) (*connection, error) { conn, err := mtproto.TelegramProtocol(req) if err != nil { return nil, fmt.Errorf("cannot create a new connection: %w", err) } + id := rand.Int() // nolint: gosec rv := &connection{ conn: conn, - hub: hub, - id: rand.Int(), // nolint: gosec - done: make(chan struct{}), + id: id, + logger: zap.S().Named("hub-connection").With("id", id, + "dc", req.ClientProtocol.DC(), + "protocol", req.ClientProtocol.ConnectionProtocol()), + proxyConns: make(map[string]*ProxyConn), + + channelRead: make(chan *rpc.ProxyResponse, 1), + channelDone: make(chan struct{}), + channelWrite: make(chan conntypes.Packet), + channelConnAttach: make(chan *ProxyConn), + channelConnDetach: make(chan conntypes.ConnID), } + + go rv.readLoop() + go rv.run() return rv, nil diff --git a/hub/connection_hub.go b/hub/connection_hub.go deleted file mode 100644 index 8ba9027..0000000 --- a/hub/connection_hub.go +++ /dev/null @@ -1,114 +0,0 @@ -package hub - -import ( - "time" - - "go.uber.org/zap" - - "github.com/9seconds/mtg/protocol" -) - -const hubGCEvery = time.Minute - -type connectionHubRequest struct { - request *protocol.TelegramRequest - response chan<- *connection -} - -type connectionHub struct { - sockets map[int]*connection - logger *zap.SugaredLogger - - channelBrokenSockets chan int - channelConnectionRequests chan *connectionHubRequest - channelReturnConnections chan *connection -} - -func (c *connectionHub) run() { - ticker := time.NewTicker(hubGCEvery) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - c.runGC() - case request := <-c.channelConnectionRequests: - c.runConnectionRequest(request) - case id := <-c.channelBrokenSockets: - c.runBrokenSocket(id) - case conn := <-c.channelReturnConnections: - c.runReturnConnection(conn) - } - } -} - -func (c *connectionHub) runGC() { - logger := c.logger.Named("gc") - - for key, conn := range c.sockets { - switch { - case conn.closed(): - logger.Debugw("Delete closed socket", "key", key) - delete(c.sockets, key) - case conn.idle(): - logger.Debugw("Delete idle socket", "key", key) - conn.shutdown() - delete(c.sockets, key) - - return - } - } -} - -func (c *connectionHub) runConnectionRequest(req *connectionHubRequest) { - logger := c.logger.Named("request").With("connection-id", req.request.ConnID) - - for key, conn := range c.sockets { - delete(c.sockets, key) - - if !conn.closed() { - logger.Debugw("Choose connection", - "id", conn.id, - "remote_addr", conn.conn.RemoteAddr()) - req.response <- conn - close(req.response) - - return - } - } - - if conn, err := newConnection(req.request, c); err == nil { - logger.Debugw("New connection", - "id", conn.id, - "remote_addr", conn.conn.RemoteAddr()) - req.response <- conn - } - - close(req.response) -} - -func (c *connectionHub) runBrokenSocket(id int) { - c.logger.Named("broken-socket").Debugw("Delete broken socket", "id", id) - delete(c.sockets, id) -} - -func (c *connectionHub) runReturnConnection(conn *connection) { - c.logger.Named("return-connection").Debugw("Return connection", - "id", conn.id, - "remote_addr", conn.conn.RemoteAddr()) - - c.sockets[conn.id] = conn -} - -func newConnectionHub(logger *zap.SugaredLogger) *connectionHub { - rv := &connectionHub{ - logger: logger.Named("connection-hub"), - sockets: map[int]*connection{}, - channelBrokenSockets: make(chan int, 1), - channelConnectionRequests: make(chan *connectionHubRequest), - channelReturnConnections: make(chan *connection, 1), - } - go rv.run() - - return rv -} diff --git a/hub/connection_list.go b/hub/connection_list.go new file mode 100644 index 0000000..7bb4c48 --- /dev/null +++ b/hub/connection_list.go @@ -0,0 +1,70 @@ +package hub + +import ( + "fmt" + "sort" +) + +const connectionListMaxClientsPerConnection = 2 + +type connectionList struct { + connections []*connection +} + +func (c *connectionList) Get(conn *ProxyConn) (*connection, error) { + if len(c.connections) > 0 { + c.gc() + } + + if len(c.connections) > 0 && c.connections[0].Len() < connectionListMaxClientsPerConnection { + if err := c.connections[0].Attach(conn); err == nil { + return c.connections[0], nil + } + } + + newConn, err := newConnection(conn.req) + if err != nil { + return nil, fmt.Errorf("cannot allocate a new connection: %w", err) + } + + if err = newConn.Attach(conn); err != nil { + newConn.Close() + return nil, fmt.Errorf("cannot attach to the newly created connection: %w", err) + } + + c.connections = append(c.connections, newConn) + lastIndex := len(c.connections) - 1 + c.connections[0], c.connections[lastIndex] = c.connections[lastIndex], c.connections[0] + + return newConn, nil +} + +func (c *connectionList) gc() { + prevLen := len(c.connections) + + for i := len(c.connections) - 1; i >= 0; i-- { + lastIndex := len(c.connections) - 1 + + if c.connections[i].Done() { + c.connections[i].Close() + + if len(c.connections)-1 == i { + c.connections = c.connections[:lastIndex] + } else { + c.connections[i], c.connections[lastIndex] = c.connections[lastIndex], c.connections[i] + } + } + } + + if prevLen != len(c.connections) { + c.sort() + } +} + +func (c *connectionList) sort() { + if len(c.connections) > 1 { + sort.Slice(c.connections, func(i, j int) bool { + return c.connections[i].Len() < c.connections[j].Len() + }) + } +} diff --git a/hub/ctx_channel.go b/hub/ctx_channel.go deleted file mode 100644 index e9e8b86..0000000 --- a/hub/ctx_channel.go +++ /dev/null @@ -1,61 +0,0 @@ -package hub - -import ( - "context" - "time" - - "github.com/9seconds/mtg/mtproto/rpc" -) - -const closeableChannelReadTimeout = 2 * time.Minute - -type ChannelReadCloser interface { - Read() (*rpc.ProxyResponse, error) - Close() error -} - -type ctxChannel struct { - channel chan *rpc.ProxyResponse - ctx context.Context - cancel context.CancelFunc -} - -func (c *ctxChannel) Read() (*rpc.ProxyResponse, error) { - timer := time.NewTimer(closeableChannelReadTimeout) - defer timer.Stop() - - select { - case <-timer.C: - return nil, ErrTimeout - case <-c.ctx.Done(): - return nil, ErrClosed - case packet := <-c.channel: - return packet, nil - } -} - -func (c *ctxChannel) sendBack(response *rpc.ProxyResponse) error { - select { - case <-c.ctx.Done(): - return ErrClosed - case c.channel <- response: - return nil - } -} - -func (c *ctxChannel) Close() error { - c.cancel() - c.channel = nil - - return nil -} - -func newCtxChannel(ctx context.Context) *ctxChannel { - ctx, cancel := context.WithCancel(ctx) - - return &ctxChannel{ - channel: make(chan *rpc.ProxyResponse), - ctx: ctx, - cancel: cancel, - } -} diff --git a/hub/hub.go b/hub/hub.go index 57c2875..edfc442 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -1,73 +1,40 @@ package hub import ( - "encoding/binary" - "fmt" - "strings" + "context" "sync" - "go.uber.org/zap" - - "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/protocol" ) type hub struct { - logger *zap.SugaredLogger - subs map[string]*connectionHub - mutex sync.RWMutex + muxes map[int32]*mux + mutex sync.RWMutex + ctx context.Context } -func (h *hub) Write(packet conntypes.Packet, req *protocol.TelegramRequest) error { - sub := h.getHub(req) - connections := make(chan *connection) - sub.channelConnectionRequests <- &connectionHubRequest{ - request: req, - response: connections, - } - - conn, ok := <-connections - if !ok { - return ErrCannotCreateConnection - } - - if err := conn.write(packet); err != nil { - conn.shutdown() - return fmt.Errorf("cannot send packet: %w", err) - } - sub.channelReturnConnections <- conn - - return nil +func (h *hub) Register(req *protocol.TelegramRequest) (*ProxyConn, error) { + return h.getMux(req).Get(req) } -func (h *hub) getHub(req *protocol.TelegramRequest) *connectionHub { - keyBuilder := strings.Builder{} - binary.Write(&keyBuilder, binary.LittleEndian, int16(req.ClientProtocol.DC())) // nolint: errcheck - keyBuilder.WriteRune('_') - binary.Write(&keyBuilder, binary.LittleEndian, uint8(req.ClientProtocol.ConnectionProtocol())) // nolint: errcheck - key := keyBuilder.String() +func (h *hub) getMux(req *protocol.TelegramRequest) *mux { + var key int32 = 32767 + int32(req.ClientProtocol.DC()) + 100000*int32(req.ClientProtocol.ConnectionProtocol()) h.mutex.RLock() - rv, ok := h.subs[key] + m, ok := h.muxes[key] h.mutex.RUnlock() if !ok { h.mutex.Lock() - defer h.mutex.Unlock() + m, ok = h.muxes[key] - rv, ok = h.subs[key] if !ok { - h.logger.Debugw("Create new connection hub", - "dc", req.ClientProtocol.DC(), - "protocol", req.ClientProtocol.ConnectionProtocol()) - - rv = newConnectionHub(h.logger.With( - "dc", req.ClientProtocol.DC(), - "protocol", req.ClientProtocol.ConnectionProtocol(), - )) - h.subs[key] = rv + m = newMux(h.ctx) + h.muxes[key] = m } + + h.mutex.Unlock() } - return rv + return m } diff --git a/hub/init.go b/hub/init.go index 35a0fee..19f5289 100644 --- a/hub/init.go +++ b/hub/init.go @@ -4,30 +4,21 @@ import ( "context" "errors" "sync" - - "go.uber.org/zap" ) var ( - Registry *registry - Hub *hub - - ErrTimeout = errors.New("timeout") - ErrClosed = errors.New("channel was closed") - ErrCannotCreateConnection = errors.New("cannot create connection") + ErrTimeout = errors.New("timeout") + ErrClosed = errors.New("context is closed") + Hub Interface initOnce sync.Once ) func Init(ctx context.Context) { initOnce.Do(func() { - Registry = ®istry{ - conns: map[string]*ctxChannel{}, - ctx: ctx, - } Hub = &hub{ - subs: map[string]*connectionHub{}, - logger: zap.S().Named("hub"), + muxes: make(map[int32]*mux), + ctx: ctx, } }) } diff --git a/hub/interface.go b/hub/interface.go new file mode 100644 index 0000000..a1efcdc --- /dev/null +++ b/hub/interface.go @@ -0,0 +1,7 @@ +package hub + +import "github.com/9seconds/mtg/protocol" + +type Interface interface { + Register(*protocol.TelegramRequest) (*ProxyConn, error) +} diff --git a/hub/mux.go b/hub/mux.go new file mode 100644 index 0000000..7aa82f6 --- /dev/null +++ b/hub/mux.go @@ -0,0 +1,80 @@ +package hub + +import ( + "context" + + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/protocol" +) + +type muxNewRequest struct { + req *protocol.TelegramRequest + resp chan<- muxNewResponse +} + +type muxNewResponse struct { + conn *ProxyConn + err error +} + +type mux struct { + connections connectionList + clients map[string]*connection + ctx context.Context + channelClosed chan conntypes.ConnID + channelNew chan muxNewRequest +} + +func (m *mux) run() { + for { + select { + case <-m.ctx.Done(): + for _, v := range m.clients { + v.Close() + } + + return + case req := <-m.channelNew: + proxyConn := newProxyConn(req.req, m.channelClosed) + conn, err := m.connections.Get(proxyConn) + + if err == nil { + m.clients[string(req.req.ConnID[:])] = conn + } + + req.resp <- muxNewResponse{ + conn: proxyConn, + err: err, + } + close(req.resp) + case connID := <-m.channelClosed: + if conn, ok := m.clients[string(connID[:])]; ok { + conn.Detach(connID) + delete(m.clients, string(connID[:])) + } + } + } +} + +func (m *mux) Get(req *protocol.TelegramRequest) (*ProxyConn, error) { + resp := make(chan muxNewResponse) + m.channelNew <- muxNewRequest{ + req: req, + resp: resp, + } + rv := <-resp + + return rv.conn, rv.err +} + +func newMux(ctx context.Context) *mux { + m := &mux{ + ctx: ctx, + clients: make(map[string]*connection), + channelClosed: make(chan conntypes.ConnID, 1), + channelNew: make(chan muxNewRequest), + } + go m.run() + + return m +} diff --git a/hub/proxy_conn.go b/hub/proxy_conn.go new file mode 100644 index 0000000..a3e4686 --- /dev/null +++ b/hub/proxy_conn.go @@ -0,0 +1,77 @@ +package hub + +import ( + "sync" + "time" + + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/mtproto/rpc" + "github.com/9seconds/mtg/protocol" +) + +const ( + proxyConnWriteTimeout = 2 * time.Minute + proxyConnReadTimeout = 2 * time.Minute +) + +type ProxyConn struct { + closeOnce sync.Once + req *protocol.TelegramRequest + channelResponse chan *rpc.ProxyResponse + channelClosed chan<- conntypes.ConnID + channelWrite chan<- conntypes.Packet + channelDone chan struct{} +} + +func (p *ProxyConn) Read() (*rpc.ProxyResponse, error) { + timer := time.NewTimer(proxyConnReadTimeout) + defer timer.Stop() + + select { + case <-timer.C: + return nil, ErrTimeout + case <-p.channelDone: + return nil, ErrClosed + case packet := <-p.channelResponse: + return packet, nil + } +} + +func (p *ProxyConn) Write(packet conntypes.Packet) error { + timer := time.NewTimer(proxyConnWriteTimeout) + defer timer.Stop() + + select { + case <-timer.C: + return ErrTimeout + case <-p.channelDone: + return ErrClosed + case p.channelWrite <- packet: + return nil + } +} + +func (p *ProxyConn) put(response *rpc.ProxyResponse) { + select { + case <-p.channelDone: + case p.channelResponse <- response: + } +} + +func (p *ProxyConn) Close() { + p.closeOnce.Do(func() { + close(p.channelDone) + go func() { + p.channelClosed <- p.req.ConnID + }() + }) +} + +func newProxyConn(req *protocol.TelegramRequest, channelClosed chan<- conntypes.ConnID) *ProxyConn { + return &ProxyConn{ + channelResponse: make(chan *rpc.ProxyResponse), + channelDone: make(chan struct{}), + channelClosed: channelClosed, + req: req, + } +} diff --git a/hub/registry.go b/hub/registry.go deleted file mode 100644 index c8652a1..0000000 --- a/hub/registry.go +++ /dev/null @@ -1,45 +0,0 @@ -package hub - -import ( - "context" - "sync" - - "github.com/9seconds/mtg/conntypes" -) - -type registry struct { - conns map[string]*ctxChannel - ctx context.Context - mutex sync.RWMutex -} - -func (r *registry) Register(id conntypes.ConnID) ChannelReadCloser { - channel := newCtxChannel(r.ctx) - - r.mutex.Lock() - r.conns[string(id[:])] = channel - r.mutex.Unlock() - - return channel -} - -func (r *registry) Unregister(id conntypes.ConnID) { - r.mutex.Lock() - defer r.mutex.Unlock() - - if channel, ok := r.conns[string(id[:])]; ok { - channel.Close() - delete(r.conns, string(id[:])) - } -} - -func (r *registry) getChannel(id conntypes.ConnID) (*ctxChannel, bool) { - r.mutex.RLock() - defer r.mutex.RUnlock() - - if value, ok := r.conns[string(id[:])]; ok { - return value, true - } - - return nil, false -} diff --git a/main.go b/main.go index 62336ac..169f834 100644 --- a/main.go +++ b/main.go @@ -29,92 +29,92 @@ var ( Required(). Enum("simple", "secured", "tls") - proxyCommand = app.Command("proxy", + runCommand = app.Command("run", "Run new proxy instance") - proxyDebug = proxyCommand.Flag("debug", + runDebug = runCommand.Flag("debug", "Run in debug mode."). Short('d'). Envar("MTG_DEBUG"). Bool() - proxyVerbose = proxyCommand.Flag("verbose", + runVerbose = runCommand.Flag("verbose", "Run in verbose mode."). Short('v'). Envar("MTG_VERBOSE"). Bool() - proxyBind = proxyCommand.Flag("bind", + runBind = runCommand.Flag("bind", "Host:Port to bind proxy to."). Short('b'). Envar("MTG_BIND"). Default("0.0.0.0:3128"). TCP() - proxyPublicIPv4 = proxyCommand.Flag("public-ipv4", + runPublicIPv4 = runCommand.Flag("public-ipv4", "Which IPv4 host:port to use."). Short('4'). Envar("MTG_IPV4"). TCP() - proxyPublicIPv6 = proxyCommand.Flag("public-ipv6", + runPublicIPv6 = runCommand.Flag("public-ipv6", "Which IPv6 host:port to use."). Short('6'). Envar("MTG_IPV6"). TCP() - proxyStatsBind = proxyCommand.Flag("stats-bind", + runStatsBind = runCommand.Flag("stats-bind", "Which Host:Port to bind stats server to."). Short('t'). Envar("MTG_STATS_BIND"). Default("127.0.0.1:3129"). TCP() - proxyStatsNamespace = proxyCommand.Flag("stats-namespace", + runStatsNamespace = runCommand.Flag("stats-namespace", "Which namespace to use for Prometheus."). Envar("MTG_STATS_NAMESPACE"). Default("mtg"). String() - proxyStatsdAddress = proxyCommand.Flag("statsd-addr", + runStatsdAddress = runCommand.Flag("statsd-addr", "Host:port of statsd server"). Envar("MTG_STATSD_ADDR"). TCP() - proxyStatsdNetwork = proxyCommand.Flag("statsd-network", + runStatsdNetwork = runCommand.Flag("statsd-network", "Which network is used to work with statsd. Only 'tcp' and 'udp' are supported."). Envar("MTG_STATSD_NETWORK"). Default("udp"). Enum("udp", "tcp") - proxyStatsdTagsFormat = proxyCommand.Flag("statsd-tags-format", + runStatsdTagsFormat = runCommand.Flag("statsd-tags-format", "Which tag format should we use to send stats metrics. Valid options are 'datadog' and 'influxdb'."). Envar("MTG_STATSD_TAGS_FORMAT"). Default("influxdb"). Enum("datadog", "influxdb") - proxyStatsdTags = proxyCommand.Flag("statsd-tags", + runStatsdTags = runCommand.Flag("statsd-tags", "Tags to use for working with statsd (specified as 'key=value')."). Envar("MTG_STATSD_TAGS"). StringMap() - proxyWriteBufferSize = proxyCommand.Flag("write-buffer", + runWriteBufferSize = runCommand.Flag("write-buffer", "Write buffer size in bytes. You can think about it as a buffer from client to Telegram."). Short('w'). Envar("MTG_BUFFER_WRITE"). Default("65536KB"). Bytes() - proxyReadBufferSize = proxyCommand.Flag("read-buffer", + runReadBufferSize = runCommand.Flag("read-buffer", "Read buffer size in bytes. You can think about it as a buffer from Telegram to client."). Short('r'). Envar("MTG_BUFFER_READ"). Default("131072KB"). Bytes() - proxyTLSCloakPort = proxyCommand.Flag("cloak-port", + runTLSCloakPort = runCommand.Flag("cloak-port", "Port which should be used for host cloaking."). Envar("MTG_CLOAK_PORT"). Default("443"). Uint16() - proxyAntiReplayMaxSize = proxyCommand.Flag("anti-replay-max-size", + runAntiReplayMaxSize = runCommand.Flag("anti-replay-max-size", "Max size of antireplay cache in megabytes."). Envar("MTG_ANTIREPLAY_MAXSIZE"). Default("128"). Int() - proxyAntiReplayEvictionTime = proxyCommand.Flag("anti-replay-eviction-time", + runAntiReplayEvictionTime = runCommand.Flag("anti-replay-eviction-time", "Eviction time period for obfuscated2 handshakes"). Envar("MTG_ANTIREPLAY_EVICTIONTIME"). Default("168h"). Duration() - proxySecret = proxyCommand.Arg("secret", "Secret of this proxy.").Required().HexBytes() - proxyAdtag = proxyCommand.Arg("adtag", "ADTag of the proxy.").HexBytes() + runSecret = runCommand.Arg("secret", "Secret of this proxy.").Required().HexBytes() + runAdtag = runCommand.Arg("adtag", "ADTag of the proxy.").HexBytes() ) func main() { @@ -129,26 +129,26 @@ func main() { switch kingpin.MustParse(app.Parse(os.Args[1:])) { case generateSecretCommand.FullCommand(): cli.Generate(*generateSecretType, *generateCloakHost) - case proxyCommand.FullCommand(): + case runCommand.FullCommand(): err := config.Init( - config.Opt{Option: config.OptionTypeDebug, Value: *proxyDebug}, - config.Opt{Option: config.OptionTypeVerbose, Value: *proxyVerbose}, - config.Opt{Option: config.OptionTypeBind, Value: *proxyBind}, - config.Opt{Option: config.OptionTypePublicIPv4, Value: *proxyPublicIPv4}, - config.Opt{Option: config.OptionTypePublicIPv6, Value: *proxyPublicIPv6}, - config.Opt{Option: config.OptionTypeStatsBind, Value: *proxyStatsBind}, - config.Opt{Option: config.OptionTypeStatsNamespace, Value: *proxyStatsNamespace}, - config.Opt{Option: config.OptionTypeStatsdAddress, Value: *proxyStatsdAddress}, - config.Opt{Option: config.OptionTypeStatsdNetwork, Value: *proxyStatsdNetwork}, - config.Opt{Option: config.OptionTypeStatsdTagsFormat, Value: *proxyStatsdTagsFormat}, - config.Opt{Option: config.OptionTypeStatsdTags, Value: *proxyStatsdTags}, - config.Opt{Option: config.OptionTypeWriteBufferSize, Value: *proxyWriteBufferSize}, - config.Opt{Option: config.OptionTypeReadBufferSize, Value: *proxyReadBufferSize}, - config.Opt{Option: config.OptionTypeCloakPort, Value: *proxyTLSCloakPort}, - config.Opt{Option: config.OptionTypeAntiReplayMaxSize, Value: *proxyAntiReplayMaxSize}, - config.Opt{Option: config.OptionTypeAntiReplayEvictionTime, Value: *proxyAntiReplayEvictionTime}, - config.Opt{Option: config.OptionTypeSecret, Value: *proxySecret}, - config.Opt{Option: config.OptionTypeAdtag, Value: *proxyAdtag}, + config.Opt{Option: config.OptionTypeDebug, Value: *runDebug}, + config.Opt{Option: config.OptionTypeVerbose, Value: *runVerbose}, + config.Opt{Option: config.OptionTypeBind, Value: *runBind}, + config.Opt{Option: config.OptionTypePublicIPv4, Value: *runPublicIPv4}, + config.Opt{Option: config.OptionTypePublicIPv6, Value: *runPublicIPv6}, + config.Opt{Option: config.OptionTypeStatsBind, Value: *runStatsBind}, + config.Opt{Option: config.OptionTypeStatsNamespace, Value: *runStatsNamespace}, + config.Opt{Option: config.OptionTypeStatsdAddress, Value: *runStatsdAddress}, + config.Opt{Option: config.OptionTypeStatsdNetwork, Value: *runStatsdNetwork}, + config.Opt{Option: config.OptionTypeStatsdTagsFormat, Value: *runStatsdTagsFormat}, + config.Opt{Option: config.OptionTypeStatsdTags, Value: *runStatsdTags}, + config.Opt{Option: config.OptionTypeWriteBufferSize, Value: *runWriteBufferSize}, + config.Opt{Option: config.OptionTypeReadBufferSize, Value: *runReadBufferSize}, + config.Opt{Option: config.OptionTypeCloakPort, Value: *runTLSCloakPort}, + config.Opt{Option: config.OptionTypeAntiReplayMaxSize, Value: *runAntiReplayMaxSize}, + config.Opt{Option: config.OptionTypeAntiReplayEvictionTime, Value: *runAntiReplayEvictionTime}, + config.Opt{Option: config.OptionTypeSecret, Value: *runSecret}, + config.Opt{Option: config.OptionTypeAdtag, Value: *runAdtag}, ) if err != nil { cli.Fatal(err) diff --git a/obfuscated2/client_protocol.go b/obfuscated2/client_protocol.go index 9f940af..ec96807 100644 --- a/obfuscated2/client_protocol.go +++ b/obfuscated2/client_protocol.go @@ -81,13 +81,13 @@ func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conn c.dc = conntypes.DCDefaultIdx } - antiReplayKey := decryptedFrame.Unique() - if antireplay.Cache.HasObfuscated2(antiReplayKey) { - stats.Stats.AntiReplayDetected() + replayKeys := decryptedFrame.Unique() + if antireplay.Cache.HasObfuscated2(replayKeys) { + stats.Stats.ReplayDetected() return nil, errors.New("replay attack is detected") } - antireplay.Cache.AddObfuscated2(antiReplayKey) + antireplay.Cache.AddObfuscated2(replayKeys) return stream.NewObfuscated2(socket, encryptor, decryptor), nil } diff --git a/proxy/middle.go b/proxy/middle.go index d7d3d42..c0995dc 100644 --- a/proxy/middle.go +++ b/proxy/middle.go @@ -11,7 +11,11 @@ import ( ) func middleConnection(request *protocol.TelegramRequest) { - telegramConn := packetack.NewProxy(request) + telegramConn, err := packetack.NewProxy(request) + if err != nil { + request.Logger.Debugw("Cannot dial to Telegram", "error", err) + return + } defer telegramConn.Close() var clientConn conntypes.PacketAckFullReadWriteCloser diff --git a/stats/interfaces.go b/stats/interfaces.go index 9a27580..0672982 100644 --- a/stats/interfaces.go +++ b/stats/interfaces.go @@ -34,8 +34,8 @@ type CrashInterface interface { Crash() } -type AntiReplayDetectedInterface interface { - AntiReplayDetected() +type ReplayDetectedInterface interface { + ReplayDetected() } type Interface interface { @@ -46,5 +46,5 @@ type Interface interface { TelegramConnectedInterface TelegramDisconnectedInterface CrashInterface - AntiReplayDetectedInterface + ReplayDetectedInterface } diff --git a/stats/multi_stats.go b/stats/multi_stats.go index c67a4ef..f87ee75 100644 --- a/stats/multi_stats.go +++ b/stats/multi_stats.go @@ -50,8 +50,8 @@ func (m multiStats) Crash() { } } -func (m multiStats) AntiReplayDetected() { +func (m multiStats) ReplayDetected() { for i := range m { - go m[i].AntiReplayDetected() + go m[i].ReplayDetected() } } diff --git a/stats/stats_prometheus.go b/stats/stats_prometheus.go index 69a3f6c..e2cefb2 100644 --- a/stats/stats_prometheus.go +++ b/stats/stats_prometheus.go @@ -18,7 +18,7 @@ type statsPrometheus struct { telegramConnections *prometheus.GaugeVec traffic *prometheus.GaugeVec crashes prometheus.Gauge - antiReplays prometheus.Counter + replayAttacks prometheus.Counter } func (s *statsPrometheus) IngressTraffic(traffic int) { @@ -84,8 +84,8 @@ func (s *statsPrometheus) Crash() { s.crashes.Inc() } -func (s *statsPrometheus) AntiReplayDetected() { - s.antiReplays.Inc() +func (s *statsPrometheus) ReplayDetected() { + s.replayAttacks.Inc() } func newStatsPrometheus(mux *http.ServeMux) (Interface, error) { @@ -112,10 +112,10 @@ func newStatsPrometheus(mux *http.ServeMux) (Interface, error) { Name: "crashes", Help: "How many crashes happened.", }), - antiReplays: prometheus.NewCounter(prometheus.CounterOpts{ + replayAttacks: prometheus.NewCounter(prometheus.CounterOpts{ Namespace: config.C.StatsNamespace, - Name: "anti_replays", - Help: "How many anti replay attacks were prevented.", + Name: "replay_attacks", + Help: "How many replay attacks were prevented.", }), } @@ -135,8 +135,8 @@ func newStatsPrometheus(mux *http.ServeMux) (Interface, error) { return nil, fmt.Errorf("cannot register metrics for crashes: %w", err) } - if err := registry.Register(instance.antiReplays); err != nil { - return nil, fmt.Errorf("cannot register metrics for anti replays: %w", err) + if err := registry.Register(instance.replayAttacks); err != nil { + return nil, fmt.Errorf("cannot register metrics for replays: %w", err) } handler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) diff --git a/stats/stats_statsd.go b/stats/stats_statsd.go index e68ede7..e6baa5d 100644 --- a/stats/stats_statsd.go +++ b/stats/stats_statsd.go @@ -79,8 +79,8 @@ func (s *statsStatsd) Crash() { s.client.Increment("crashes") } -func (s *statsStatsd) AntiReplayDetected() { - s.client.Increment("anti_replays") +func (s *statsStatsd) ReplayDetected() { + s.client.Increment("replay_attacks") } func newStatsStatsd() (Interface, error) { diff --git a/wrappers/packetack/proxy.go b/wrappers/packetack/proxy.go index 768eddb..62ac7d2 100644 --- a/wrappers/packetack/proxy.go +++ b/wrappers/packetack/proxy.go @@ -5,7 +5,6 @@ import ( "encoding/binary" "fmt" "net" - "sync" "github.com/9seconds/mtg/config" "github.com/9seconds/mtg/conntypes" @@ -16,10 +15,9 @@ import ( type wrapperProxy struct { request *protocol.TelegramRequest + proxy *hub.ProxyConn clientIPPort []byte ourIPPort []byte - channelRead hub.ChannelReadCloser - closeOnce sync.Once flags rpc.ProxyRequestFlags } @@ -47,11 +45,11 @@ func (w *wrapperProxy) Write(packet conntypes.Packet, acks *conntypes.Connection buf.Write(make([]byte, (4-buf.Len()%4)%4)) buf.Write(packet) - return hub.Hub.Write(buf.Bytes(), w.request) + return w.proxy.Write(buf.Bytes()) } func (w *wrapperProxy) Read(acks *conntypes.ConnectionAcks) (conntypes.Packet, error) { - resp, err := w.channelRead.Read() + resp, err := w.proxy.Read() if err != nil { return nil, fmt.Errorf("cannot read a response: %w", err) } @@ -64,15 +62,11 @@ func (w *wrapperProxy) Read(acks *conntypes.ConnectionAcks) (conntypes.Packet, e } func (w *wrapperProxy) Close() error { - w.closeOnce.Do(func() { - w.channelRead.Close() - hub.Registry.Unregister(w.request.ConnID) - }) - + w.proxy.Close() return nil } -func NewProxy(request *protocol.TelegramRequest) conntypes.PacketAckReadWriteCloser { +func NewProxy(request *protocol.TelegramRequest) (conntypes.PacketAckReadWriteCloser, error) { flags := rpc.ProxyRequestFlagsHasAdTag | rpc.ProxyRequestFlagsMagic | rpc.ProxyRequestFlagsExtMode2 switch request.ClientProtocol.ConnectionType() { @@ -86,13 +80,18 @@ func NewProxy(request *protocol.TelegramRequest) conntypes.PacketAckReadWriteClo panic("unknown connection type") } + proxy, err := hub.Hub.Register(request) + if err != nil { + return nil, fmt.Errorf("cannot make a new proxy wrapper: %w", err) + } + return &wrapperProxy{ flags: flags, request: request, - channelRead: hub.Registry.Register(request.ConnID), + proxy: proxy, clientIPPort: proxyGetIPPort(request.ClientConn.RemoteAddr()), ourIPPort: proxyGetIPPort(request.ClientConn.LocalAddr()), - } + }, nil } func proxyGetIPPort(addr *net.TCPAddr) []byte { From 34ad883559c5491efc85706b2c78603a895a1826 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 11 Nov 2019 14:59:09 +0300 Subject: [PATCH 51/62] Use ristretto instead of bigcache --- antireplay/cache.go | 30 +++++++++++++++++++++--------- antireplay/hasher.go | 9 --------- antireplay/init.go | 33 ++++++++++++--------------------- config/config.go | 9 ++------- faketls/client_protocol.go | 9 +++++++++ go.mod | 5 +---- go.sum | 14 ++++---------- main.go | 12 +++--------- obfuscated2/client_protocol.go | 6 +++--- 9 files changed, 55 insertions(+), 72 deletions(-) delete mode 100644 antireplay/hasher.go diff --git a/antireplay/cache.go b/antireplay/cache.go index a142bd9..72ca30e 100644 --- a/antireplay/cache.go +++ b/antireplay/cache.go @@ -1,26 +1,38 @@ package antireplay -import "github.com/allegro/bigcache" +import "github.com/dgraph-io/ristretto" + +var ( + prefixObfuscated2 = []byte{0x00} + prefixTLS = []byte{0x01} +) type cache struct { - obfuscated2 *bigcache.BigCache - tls *bigcache.BigCache + data *ristretto.Cache } func (c *cache) AddObfuscated2(data []byte) { - c.obfuscated2.Set(string(data), nil) // nolint: errcheck + c.data.Set(keyObfuscated2(data), nil, int64(len(data))) } func (c *cache) AddTLS(data []byte) { - c.tls.Set(string(data), nil) // nolint: errcheck + c.data.Set(keyTLS(data), nil, int64(len(data))) } func (c *cache) HasObfuscated2(data []byte) bool { - _, err := c.obfuscated2.Get(string(data)) - return err == nil + _, ok := c.data.Get(keyObfuscated2(data)) + return ok } func (c *cache) HasTLS(data []byte) bool { - _, err := c.tls.Get(string(data)) - return err == nil + _, ok := c.data.Get(keyTLS(data)) + return ok +} + +func keyObfuscated2(data []byte) string { + return string(append(prefixObfuscated2, data...)) +} + +func keyTLS(data []byte) string { + return string(append(prefixTLS, data...)) } diff --git a/antireplay/hasher.go b/antireplay/hasher.go deleted file mode 100644 index acaf9b6..0000000 --- a/antireplay/hasher.go +++ /dev/null @@ -1,9 +0,0 @@ -package antireplay - -import "github.com/cespare/xxhash" - -type hasher struct{} - -func (h hasher) Sum64(value string) uint64 { - return xxhash.Sum64String(value) -} diff --git a/antireplay/init.go b/antireplay/init.go index 740fa6f..5659ab7 100644 --- a/antireplay/init.go +++ b/antireplay/init.go @@ -1,42 +1,33 @@ package antireplay import ( + "math" "sync" "github.com/9seconds/mtg/config" - "github.com/allegro/bigcache" + "github.com/dgraph-io/ristretto" ) var ( - Cache *cache + Cache cache initOnce sync.Once ) func Init() { initOnce.Do(func() { - c1, err := bigcache.NewBigCache(bigcache.Config{ - Shards: 1024, - LifeWindow: config.C.AntiReplayEvictionTime, - Hasher: hasher{}, - HardMaxCacheSize: config.C.AntiReplayMaxSize, + cost := float64(config.C.AntiReplayMaxSize) / 32.0 + cost = math.Ceil(cost) + + c, err := ristretto.NewCache(&ristretto.Config{ + NumCounters: int64(cost) * 10, + MaxCost: config.C.AntiReplayMaxSize, + BufferItems: 64, + Metrics: false, }) if err != nil { panic(err) } - c2, err := bigcache.NewBigCache(bigcache.Config{ - Shards: 1024, - LifeWindow: config.C.AntiReplayEvictionTime, - Hasher: hasher{}, - HardMaxCacheSize: config.C.AntiReplayMaxSize, - }) - if err != nil { - panic(err) - } - - Cache = &cache{ - obfuscated2: c1, - tls: c2, - } + Cache.data = c }) } diff --git a/config/config.go b/config/config.go index dc4db73..e79f07e 100644 --- a/config/config.go +++ b/config/config.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "net" - "time" "github.com/alecthomas/units" "go.uber.org/zap" @@ -58,7 +57,6 @@ const ( OptionTypeCloakPort OptionTypeAntiReplayMaxSize - OptionTypeAntiReplayEvictionTime OptionTypeSecret OptionTypeAdtag @@ -80,8 +78,7 @@ type Config struct { ReadBuffer int `json:"read_buffer"` CloakPort int `json:"cloak_port"` - AntiReplayMaxSize int `json:"anti_replay_max_size"` - AntiReplayEvictionTime time.Duration `json:"anti_replay_eviction_time"` + AntiReplayMaxSize int64 `json:"anti_replay_max_size"` Debug bool `json:"debug"` Verbose bool `json:"verbose"` @@ -151,9 +148,7 @@ func Init(options ...Opt) error { // nolint: gocyclo, funlen case OptionTypeCloakPort: C.CloakPort = int(opt.Value.(uint16)) case OptionTypeAntiReplayMaxSize: - C.AntiReplayMaxSize = opt.Value.(int) - case OptionTypeAntiReplayEvictionTime: - C.AntiReplayEvictionTime = opt.Value.(time.Duration) + C.AntiReplayMaxSize = int64(opt.Value.(units.Base2Bytes)) case OptionTypeSecret: C.Secret = opt.Value.([]byte) case OptionTypeAdtag: diff --git a/faketls/client_protocol.go b/faketls/client_protocol.go index f0b82b2..a0cb209 100644 --- a/faketls/client_protocol.go +++ b/faketls/client_protocol.go @@ -11,10 +11,12 @@ import ( "sync" "time" + "github.com/9seconds/mtg/antireplay" "github.com/9seconds/mtg/config" "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/obfuscated2" "github.com/9seconds/mtg/protocol" + "github.com/9seconds/mtg/stats" "github.com/9seconds/mtg/tlstypes" "github.com/9seconds/mtg/wrappers/stream" ) @@ -82,6 +84,13 @@ func (c *ClientProtocol) tlsHandshake(conn io.ReadWriter) error { return errBadTime } + if antireplay.Cache.HasTLS(clientHello.Random[:]) { + stats.Stats.ReplayDetected() + return errors.New("replay attack is detected") + } + + antireplay.Cache.AddTLS(clientHello.Random[:]) + hostCert, err := connectionServerInstance.get() if err != nil { return fmt.Errorf("cannot get host certificate: %w", err) diff --git a/go.mod b/go.mod index 8c7a5d2..7264d9f 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,10 @@ module github.com/9seconds/mtg replace github.com/golang/lint => github.com/golang/lint v0.0.0-20190227174305-8f45f776aaf1 require ( - github.com/OneOfOne/xxhash v1.2.5 // indirect github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d - github.com/allegro/bigcache v1.2.1 github.com/beevik/ntp v0.2.0 - github.com/cespare/xxhash v1.1.0 + github.com/dgraph-io/ristretto v0.0.0-20191108194154-8d6a8a75b4ca github.com/prometheus/client_golang v1.2.1 - github.com/spaolacci/murmur3 v1.1.0 // indirect go.uber.org/multierr v1.4.0 // indirect go.uber.org/zap v1.12.0 golang.org/x/crypto v0.0.0-20191106202628-ed6320f186d4 diff --git a/go.sum b/go.sum index 741a398..e7a8a98 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,5 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= -github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= @@ -13,8 +10,6 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1C github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= -github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw= github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= @@ -23,13 +18,15 @@ github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/ristretto v0.0.0-20191108194154-8d6a8a75b4ca h1:NgFAOh7RDM5WbdADm6VyLG1koLrzG0G24M/f5395PcI= +github.com/dgraph-io/ristretto v0.0.0-20191108194154-8d6a8a75b4ca/go.mod h1:edzKIzGvqUCMzhTVWbiTSe75zD9Xxq0GtSBtFmaUTZs= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -94,9 +91,6 @@ github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= diff --git a/main.go b/main.go index 169f834..bbded3d 100644 --- a/main.go +++ b/main.go @@ -104,15 +104,10 @@ var ( Default("443"). Uint16() runAntiReplayMaxSize = runCommand.Flag("anti-replay-max-size", - "Max size of antireplay cache in megabytes."). + "Max size of antireplay cache."). Envar("MTG_ANTIREPLAY_MAXSIZE"). - Default("128"). - Int() - runAntiReplayEvictionTime = runCommand.Flag("anti-replay-eviction-time", - "Eviction time period for obfuscated2 handshakes"). - Envar("MTG_ANTIREPLAY_EVICTIONTIME"). - Default("168h"). - Duration() + Default("128MB"). + Bytes() runSecret = runCommand.Arg("secret", "Secret of this proxy.").Required().HexBytes() runAdtag = runCommand.Arg("adtag", "ADTag of the proxy.").HexBytes() ) @@ -146,7 +141,6 @@ func main() { config.Opt{Option: config.OptionTypeReadBufferSize, Value: *runReadBufferSize}, config.Opt{Option: config.OptionTypeCloakPort, Value: *runTLSCloakPort}, config.Opt{Option: config.OptionTypeAntiReplayMaxSize, Value: *runAntiReplayMaxSize}, - config.Opt{Option: config.OptionTypeAntiReplayEvictionTime, Value: *runAntiReplayEvictionTime}, config.Opt{Option: config.OptionTypeSecret, Value: *runSecret}, config.Opt{Option: config.OptionTypeAdtag, Value: *runAdtag}, ) diff --git a/obfuscated2/client_protocol.go b/obfuscated2/client_protocol.go index ec96807..d85387c 100644 --- a/obfuscated2/client_protocol.go +++ b/obfuscated2/client_protocol.go @@ -81,13 +81,13 @@ func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conn c.dc = conntypes.DCDefaultIdx } - replayKeys := decryptedFrame.Unique() - if antireplay.Cache.HasObfuscated2(replayKeys) { + replayKey := decryptedFrame.Unique() + if antireplay.Cache.HasObfuscated2(replayKey) { stats.Stats.ReplayDetected() return nil, errors.New("replay attack is detected") } - antireplay.Cache.AddObfuscated2(replayKeys) + antireplay.Cache.AddObfuscated2(replayKey) return stream.NewObfuscated2(socket, encryptor, decryptor), nil } From a7a4da8ee570582adf7d5b4e7a90a1411b31135c Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 11 Nov 2019 15:03:59 +0300 Subject: [PATCH 52/62] Make connection multiplexing configurable --- config/config.go | 10 ++++++++++ hub/connection_list.go | 6 +++--- main.go | 6 ++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/config/config.go b/config/config.go index e79f07e..868b467 100644 --- a/config/config.go +++ b/config/config.go @@ -58,6 +58,8 @@ const ( OptionTypeAntiReplayMaxSize + OptionTypeMultiplexPerConnection + OptionTypeSecret OptionTypeAdtag ) @@ -80,6 +82,8 @@ type Config struct { AntiReplayMaxSize int64 `json:"anti_replay_max_size"` + MultiplexPerConnection int `json:"multiplex_per_connection"` + Debug bool `json:"debug"` Verbose bool `json:"verbose"` StatsdTagsFormat statsd.TagFormat `json:"statsd_tags_format"` @@ -149,6 +153,8 @@ func Init(options ...Opt) error { // nolint: gocyclo, funlen C.CloakPort = int(opt.Value.(uint16)) case OptionTypeAntiReplayMaxSize: C.AntiReplayMaxSize = int64(opt.Value.(units.Base2Bytes)) + case OptionTypeMultiplexPerConnection: + C.MultiplexPerConnection = int(opt.Value.(uint)) case OptionTypeSecret: C.Secret = opt.Value.([]byte) case OptionTypeAdtag: @@ -173,6 +179,10 @@ func Init(options ...Opt) error { // nolint: gocyclo, funlen return errors.New("incorrect secret") } + if C.MultiplexPerConnection == 0 { + return errors.New("cannot use 0 clients per connection for multiplexing") + } + if C.CloakHost != "" { addrs, err := net.LookupHost(C.CloakHost) if err != nil { diff --git a/hub/connection_list.go b/hub/connection_list.go index 7bb4c48..4a335bf 100644 --- a/hub/connection_list.go +++ b/hub/connection_list.go @@ -3,9 +3,9 @@ package hub import ( "fmt" "sort" -) -const connectionListMaxClientsPerConnection = 2 + "github.com/9seconds/mtg/config" +) type connectionList struct { connections []*connection @@ -16,7 +16,7 @@ func (c *connectionList) Get(conn *ProxyConn) (*connection, error) { c.gc() } - if len(c.connections) > 0 && c.connections[0].Len() < connectionListMaxClientsPerConnection { + if len(c.connections) > 0 && c.connections[0].Len() < config.C.MultiplexPerConnection { if err := c.connections[0].Attach(conn); err == nil { return c.connections[0], nil } diff --git a/main.go b/main.go index bbded3d..400c110 100644 --- a/main.go +++ b/main.go @@ -108,6 +108,11 @@ var ( Envar("MTG_ANTIREPLAY_MAXSIZE"). Default("128MB"). Bytes() + runMultiplexPerConnection = runCommand.Flag("multiplex-per-connection", + "How many clients can share a single connection to Telegram."). + Envar("MTG_MULTIPLEX_PERCONNECTION"). + Default("50"). + Uint() runSecret = runCommand.Arg("secret", "Secret of this proxy.").Required().HexBytes() runAdtag = runCommand.Arg("adtag", "ADTag of the proxy.").HexBytes() ) @@ -141,6 +146,7 @@ func main() { config.Opt{Option: config.OptionTypeReadBufferSize, Value: *runReadBufferSize}, config.Opt{Option: config.OptionTypeCloakPort, Value: *runTLSCloakPort}, config.Opt{Option: config.OptionTypeAntiReplayMaxSize, Value: *runAntiReplayMaxSize}, + config.Opt{Option: config.OptionTypeMultiplexPerConnection, Value: *runMultiplexPerConnection}, config.Opt{Option: config.OptionTypeSecret, Value: *runSecret}, config.Opt{Option: config.OptionTypeAdtag, Value: *runAdtag}, ) From b491e03504092c4d63c44d45c30c374527115782 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 11 Nov 2019 15:07:46 +0300 Subject: [PATCH 53/62] Update dependencies --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- hub/mux.go | 1 + 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 7264d9f..cf4c180 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,10 @@ require ( github.com/prometheus/client_golang v1.2.1 go.uber.org/multierr v1.4.0 // indirect go.uber.org/zap v1.12.0 - golang.org/x/crypto v0.0.0-20191106202628-ed6320f186d4 - golang.org/x/net v0.0.0-20191105084925-a882066a44e0 // indirect - golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd - golang.org/x/tools v0.0.0-20191107010934-f79515f33823 // indirect + golang.org/x/crypto v0.0.0-20191108234033-bd318be0434a + golang.org/x/net v0.0.0-20191109021931-daa7c04131f5 // indirect + golang.org/x/sys v0.0.0-20191110163157-d32e6e3b99c4 + golang.org/x/tools v0.0.0-20191109212701-97ad0ed33101 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/alexcesaro/statsd.v2 v2.0.0 ) diff --git a/go.sum b/go.sum index e7a8a98..c8c3fad 100644 --- a/go.sum +++ b/go.sum @@ -112,8 +112,8 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191106202628-ed6320f186d4 h1:PDpCLFAH/YIX0QpHPf2eO7L4rC2OOirBrKtXTLLiNTY= -golang.org/x/crypto v0.0.0-20191106202628-ed6320f186d4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191108234033-bd318be0434a h1:R/qVym5WAxsZWQqZCwDY/8sdVKV1m1WgU4/S5IRQAzc= +golang.org/x/crypto v0.0.0-20191108234033-bd318be0434a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -122,8 +122,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191105084925-a882066a44e0 h1:QPlSTtPE2k6PZPasQUbzuK3p9JbS+vMXYVto8g/yrsg= -golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191109021931-daa7c04131f5 h1:bHNaocaoJxYBo5cw41UyTMLjYlb8wPY7+WFrnklbHOM= +golang.org/x/net v0.0.0-20191109021931-daa7c04131f5/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -135,16 +135,16 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd h1:3x5uuvBgE6oaXJjCOvpCC1IpgJogqQ+PqGGU3ZxAgII= -golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191110163157-d32e6e3b99c4 h1:Hynbrlo6LbYI3H1IqXpkVDOcX/3HiPdhVEuyj5a59RM= +golang.org/x/sys v0.0.0-20191110163157-d32e6e3b99c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191107010934-f79515f33823 h1:akkRBeitX2EZP59KdtKw310CI4WGPCNPyrLbE7WZA8Y= -golang.org/x/tools v0.0.0-20191107010934-f79515f33823/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191109212701-97ad0ed33101 h1:LCmXVkvpQCDj724eX6irUTPCJP5GelFHxqGSWL2D1R0= +golang.org/x/tools v0.0.0-20191109212701-97ad0ed33101/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= diff --git a/hub/mux.go b/hub/mux.go index 7aa82f6..0b60154 100644 --- a/hub/mux.go +++ b/hub/mux.go @@ -62,6 +62,7 @@ func (m *mux) Get(req *protocol.TelegramRequest) (*ProxyConn, error) { req: req, resp: resp, } + rv := <-resp return rv.conn, rv.err From 273aa183670e31fb962078f343c339c455aa4083 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 11 Nov 2019 15:09:29 +0300 Subject: [PATCH 54/62] Rename full path in order to favor modules --- antireplay/init.go | 2 +- cli/generate.go | 2 +- cli/proxy.go | 20 +++++++++---------- faketls/certificate_server.go | 2 +- faketls/client_protocol.go | 16 +++++++-------- faketls/init.go | 2 +- go.mod | 6 ++---- hub/connection.go | 8 ++++---- hub/connection_list.go | 2 +- hub/hub.go | 2 +- hub/interface.go | 2 +- hub/mux.go | 4 ++-- hub/proxy_conn.go | 6 +++--- main.go | 6 +++--- mtproto/protocol.go | 12 +++++------ mtproto/rpc/proxy_response.go | 2 +- obfuscated2/client_protocol.go | 14 ++++++------- obfuscated2/telegram_protocol.go | 10 +++++----- protocol/interfaces.go | 2 +- protocol/request.go | 2 +- proxy/direct.go | 6 +++--- proxy/middle.go | 6 +++--- proxy/proxy.go | 12 +++++------ stats/interfaces.go | 2 +- stats/multi_stats.go | 2 +- stats/stats.go | 2 +- stats/stats_prometheus.go | 4 ++-- stats/stats_statsd.go | 4 ++-- telegram/api/addresses.go | 2 +- telegram/base.go | 6 +++--- telegram/direct.go | 2 +- telegram/interfaces.go | 2 +- telegram/middle.go | 4 ++-- tlstypes/client_hello.go | 4 ++-- tlstypes/handshake.go | 2 +- tlstypes/server_hello.go | 2 +- utils/init_tcp.go | 2 +- wrappers/packet/mtproto_frame.go | 2 +- wrappers/packetack/client_abridged.go | 4 ++-- wrappers/packetack/client_intermediate.go | 2 +- .../packetack/client_intermediate_secure.go | 2 +- wrappers/packetack/proxy.go | 10 +++++----- wrappers/stream/base.go | 2 +- wrappers/stream/blockcipher.go | 4 ++-- wrappers/stream/conn.go | 4 ++-- wrappers/stream/ctx.go | 2 +- wrappers/stream/faketls.go | 4 ++-- wrappers/stream/mtproto_cipher.go | 6 +++--- wrappers/stream/obfuscated2.go | 2 +- wrappers/stream/rewind.go | 2 +- wrappers/stream/stats_telegram.go | 4 ++-- wrappers/stream/stats_traffic.go | 4 ++-- wrappers/stream/timeout.go | 2 +- 53 files changed, 120 insertions(+), 122 deletions(-) diff --git a/antireplay/init.go b/antireplay/init.go index 5659ab7..9fab0d8 100644 --- a/antireplay/init.go +++ b/antireplay/init.go @@ -4,7 +4,7 @@ import ( "math" "sync" - "github.com/9seconds/mtg/config" + "mtg/config" "github.com/dgraph-io/ristretto" ) diff --git a/cli/generate.go b/cli/generate.go index 5811798..2d92321 100644 --- a/cli/generate.go +++ b/cli/generate.go @@ -4,7 +4,7 @@ import ( "crypto/rand" "encoding/hex" - "github.com/9seconds/mtg/config" + "mtg/config" ) func Generate(secretType, hostname string) { diff --git a/cli/proxy.go b/cli/proxy.go index 188dc68..1cda1a3 100644 --- a/cli/proxy.go +++ b/cli/proxy.go @@ -8,16 +8,16 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" - "github.com/9seconds/mtg/antireplay" - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/faketls" - "github.com/9seconds/mtg/hub" - "github.com/9seconds/mtg/ntp" - "github.com/9seconds/mtg/obfuscated2" - "github.com/9seconds/mtg/proxy" - "github.com/9seconds/mtg/stats" - "github.com/9seconds/mtg/telegram" - "github.com/9seconds/mtg/utils" + "mtg/antireplay" + "mtg/config" + "mtg/faketls" + "mtg/hub" + "mtg/ntp" + "mtg/obfuscated2" + "mtg/proxy" + "mtg/stats" + "mtg/telegram" + "mtg/utils" ) func Proxy() error { // nolint: funlen diff --git a/faketls/certificate_server.go b/faketls/certificate_server.go index e11e105..af0f100 100644 --- a/faketls/certificate_server.go +++ b/faketls/certificate_server.go @@ -13,7 +13,7 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/config" + "mtg/config" ) type connectionServer struct { diff --git a/faketls/client_protocol.go b/faketls/client_protocol.go index a0cb209..718350b 100644 --- a/faketls/client_protocol.go +++ b/faketls/client_protocol.go @@ -11,14 +11,14 @@ import ( "sync" "time" - "github.com/9seconds/mtg/antireplay" - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/obfuscated2" - "github.com/9seconds/mtg/protocol" - "github.com/9seconds/mtg/stats" - "github.com/9seconds/mtg/tlstypes" - "github.com/9seconds/mtg/wrappers/stream" + "mtg/antireplay" + "mtg/config" + "mtg/conntypes" + "mtg/obfuscated2" + "mtg/protocol" + "mtg/stats" + "mtg/tlstypes" + "mtg/wrappers/stream" ) type ClientProtocol struct { diff --git a/faketls/init.go b/faketls/init.go index b3b6500..d2c8369 100644 --- a/faketls/init.go +++ b/faketls/init.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/9seconds/mtg/config" + "mtg/config" ) var ( diff --git a/go.mod b/go.mod index cf4c180..a2f6aad 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ -module github.com/9seconds/mtg +module mtg -replace github.com/golang/lint => github.com/golang/lint v0.0.0-20190227174305-8f45f776aaf1 +go 1.13 require ( github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d @@ -16,5 +16,3 @@ require ( gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/alexcesaro/statsd.v2 v2.0.0 ) - -go 1.13 diff --git a/hub/connection.go b/hub/connection.go index 60ee738..1bb67c7 100644 --- a/hub/connection.go +++ b/hub/connection.go @@ -7,10 +7,10 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/mtproto" - "github.com/9seconds/mtg/mtproto/rpc" - "github.com/9seconds/mtg/protocol" + "mtg/conntypes" + "mtg/mtproto" + "mtg/mtproto/rpc" + "mtg/protocol" ) type connection struct { diff --git a/hub/connection_list.go b/hub/connection_list.go index 4a335bf..7acdde1 100644 --- a/hub/connection_list.go +++ b/hub/connection_list.go @@ -4,7 +4,7 @@ import ( "fmt" "sort" - "github.com/9seconds/mtg/config" + "mtg/config" ) type connectionList struct { diff --git a/hub/hub.go b/hub/hub.go index edfc442..fd291ec 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -4,7 +4,7 @@ import ( "context" "sync" - "github.com/9seconds/mtg/protocol" + "mtg/protocol" ) type hub struct { diff --git a/hub/interface.go b/hub/interface.go index a1efcdc..883c91e 100644 --- a/hub/interface.go +++ b/hub/interface.go @@ -1,6 +1,6 @@ package hub -import "github.com/9seconds/mtg/protocol" +import "mtg/protocol" type Interface interface { Register(*protocol.TelegramRequest) (*ProxyConn, error) diff --git a/hub/mux.go b/hub/mux.go index 0b60154..7263826 100644 --- a/hub/mux.go +++ b/hub/mux.go @@ -3,8 +3,8 @@ package hub import ( "context" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/protocol" + "mtg/conntypes" + "mtg/protocol" ) type muxNewRequest struct { diff --git a/hub/proxy_conn.go b/hub/proxy_conn.go index a3e4686..bbfa428 100644 --- a/hub/proxy_conn.go +++ b/hub/proxy_conn.go @@ -4,9 +4,9 @@ import ( "sync" "time" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/mtproto/rpc" - "github.com/9seconds/mtg/protocol" + "mtg/conntypes" + "mtg/mtproto/rpc" + "mtg/protocol" ) const ( diff --git a/main.go b/main.go index 400c110..6864153 100644 --- a/main.go +++ b/main.go @@ -7,9 +7,9 @@ import ( kingpin "gopkg.in/alecthomas/kingpin.v2" - "github.com/9seconds/mtg/cli" - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/utils" + "mtg/cli" + "mtg/config" + "mtg/utils" ) var version = "dev" // this has to be set by build ld flags diff --git a/mtproto/protocol.go b/mtproto/protocol.go index b7dae2e..3dfdda4 100644 --- a/mtproto/protocol.go +++ b/mtproto/protocol.go @@ -3,12 +3,12 @@ package mtproto import ( "fmt" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/mtproto/rpc" - "github.com/9seconds/mtg/protocol" - "github.com/9seconds/mtg/telegram" - "github.com/9seconds/mtg/wrappers/packet" - "github.com/9seconds/mtg/wrappers/stream" + "mtg/conntypes" + "mtg/mtproto/rpc" + "mtg/protocol" + "mtg/telegram" + "mtg/wrappers/packet" + "mtg/wrappers/stream" ) func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.PacketReadWriteCloser, error) { diff --git a/mtproto/rpc/proxy_response.go b/mtproto/rpc/proxy_response.go index 5366f34..8132d67 100644 --- a/mtproto/rpc/proxy_response.go +++ b/mtproto/rpc/proxy_response.go @@ -4,7 +4,7 @@ import ( "bytes" "fmt" - "github.com/9seconds/mtg/conntypes" + "mtg/conntypes" ) type ProxyResponseType uint8 diff --git a/obfuscated2/client_protocol.go b/obfuscated2/client_protocol.go index d85387c..3d8b6f9 100644 --- a/obfuscated2/client_protocol.go +++ b/obfuscated2/client_protocol.go @@ -9,13 +9,13 @@ import ( "io" "time" - "github.com/9seconds/mtg/antireplay" - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/protocol" - "github.com/9seconds/mtg/stats" - "github.com/9seconds/mtg/utils" - "github.com/9seconds/mtg/wrappers/stream" + "mtg/antireplay" + "mtg/config" + "mtg/conntypes" + "mtg/protocol" + "mtg/stats" + "mtg/utils" + "mtg/wrappers/stream" ) const clientProtocolHandshakeTimeout = 10 * time.Second diff --git a/obfuscated2/telegram_protocol.go b/obfuscated2/telegram_protocol.go index cf17099..34ec469 100644 --- a/obfuscated2/telegram_protocol.go +++ b/obfuscated2/telegram_protocol.go @@ -4,11 +4,11 @@ import ( "crypto/rand" "fmt" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/protocol" - "github.com/9seconds/mtg/telegram" - "github.com/9seconds/mtg/utils" - "github.com/9seconds/mtg/wrappers/stream" + "mtg/conntypes" + "mtg/protocol" + "mtg/telegram" + "mtg/utils" + "mtg/wrappers/stream" ) func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.StreamReadWriteCloser, error) { diff --git a/protocol/interfaces.go b/protocol/interfaces.go index 2e27fc3..bd44ce8 100644 --- a/protocol/interfaces.go +++ b/protocol/interfaces.go @@ -1,6 +1,6 @@ package protocol -import "github.com/9seconds/mtg/conntypes" +import "mtg/conntypes" type ClientProtocol interface { Handshake(conntypes.StreamReadWriteCloser) (conntypes.StreamReadWriteCloser, error) diff --git a/protocol/request.go b/protocol/request.go index 00a33f8..8db368a 100644 --- a/protocol/request.go +++ b/protocol/request.go @@ -5,7 +5,7 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" + "mtg/conntypes" ) type TelegramRequest struct { diff --git a/proxy/direct.go b/proxy/direct.go index 7067cbb..e4b5683 100644 --- a/proxy/direct.go +++ b/proxy/direct.go @@ -6,9 +6,9 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/obfuscated2" - "github.com/9seconds/mtg/protocol" + "mtg/conntypes" + "mtg/obfuscated2" + "mtg/protocol" ) const directPipeBufferSize = 1024 * 1024 diff --git a/proxy/middle.go b/proxy/middle.go index c0995dc..97262ec 100644 --- a/proxy/middle.go +++ b/proxy/middle.go @@ -5,9 +5,9 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/protocol" - "github.com/9seconds/mtg/wrappers/packetack" + "mtg/conntypes" + "mtg/protocol" + "mtg/wrappers/packetack" ) func middleConnection(request *protocol.TelegramRequest) { diff --git a/proxy/proxy.go b/proxy/proxy.go index 809482e..4c800dd 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -6,12 +6,12 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/protocol" - "github.com/9seconds/mtg/stats" - "github.com/9seconds/mtg/utils" - "github.com/9seconds/mtg/wrappers/stream" + "mtg/config" + "mtg/conntypes" + "mtg/protocol" + "mtg/stats" + "mtg/utils" + "mtg/wrappers/stream" ) type Proxy struct { diff --git a/stats/interfaces.go b/stats/interfaces.go index 0672982..345bdaa 100644 --- a/stats/interfaces.go +++ b/stats/interfaces.go @@ -3,7 +3,7 @@ package stats import ( "net" - "github.com/9seconds/mtg/conntypes" + "mtg/conntypes" ) type IngressTrafficInterface interface { diff --git a/stats/multi_stats.go b/stats/multi_stats.go index f87ee75..e7728d3 100644 --- a/stats/multi_stats.go +++ b/stats/multi_stats.go @@ -3,7 +3,7 @@ package stats import ( "net" - "github.com/9seconds/mtg/conntypes" + "mtg/conntypes" ) type multiStats []Interface diff --git a/stats/stats.go b/stats/stats.go index 044e704..b4ccad7 100644 --- a/stats/stats.go +++ b/stats/stats.go @@ -6,7 +6,7 @@ import ( "net" "net/http" - "github.com/9seconds/mtg/config" + "mtg/config" ) var Stats Interface diff --git a/stats/stats_prometheus.go b/stats/stats_prometheus.go index e2cefb2..509f864 100644 --- a/stats/stats_prometheus.go +++ b/stats/stats_prometheus.go @@ -9,8 +9,8 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/conntypes" + "mtg/config" + "mtg/conntypes" ) type statsPrometheus struct { diff --git a/stats/stats_statsd.go b/stats/stats_statsd.go index e6baa5d..0261f0b 100644 --- a/stats/stats_statsd.go +++ b/stats/stats_statsd.go @@ -8,8 +8,8 @@ import ( "gopkg.in/alexcesaro/statsd.v2" - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/conntypes" + "mtg/config" + "mtg/conntypes" ) type statsStatsd struct { diff --git a/telegram/api/addresses.go b/telegram/api/addresses.go index c7573e4..bbb2116 100644 --- a/telegram/api/addresses.go +++ b/telegram/api/addresses.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "github.com/9seconds/mtg/conntypes" + "mtg/conntypes" ) const ( diff --git a/telegram/base.go b/telegram/base.go index 51cfd42..f4cf3ff 100644 --- a/telegram/base.go +++ b/telegram/base.go @@ -5,9 +5,9 @@ import ( "math/rand" "net" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/utils" - "github.com/9seconds/mtg/wrappers/stream" + "mtg/conntypes" + "mtg/utils" + "mtg/wrappers/stream" ) type baseTelegram struct { diff --git a/telegram/direct.go b/telegram/direct.go index 3810c9a..8fc0b48 100644 --- a/telegram/direct.go +++ b/telegram/direct.go @@ -1,6 +1,6 @@ package telegram -import "github.com/9seconds/mtg/conntypes" +import "mtg/conntypes" const ( directV4DefaultIdx conntypes.DC = 1 diff --git a/telegram/interfaces.go b/telegram/interfaces.go index e419b8b..f6008c2 100644 --- a/telegram/interfaces.go +++ b/telegram/interfaces.go @@ -1,6 +1,6 @@ package telegram -import "github.com/9seconds/mtg/conntypes" +import "mtg/conntypes" type Telegram interface { Dial(conntypes.DC, conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error) diff --git a/telegram/middle.go b/telegram/middle.go index 519332e..3464489 100644 --- a/telegram/middle.go +++ b/telegram/middle.go @@ -7,8 +7,8 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/telegram/api" + "mtg/conntypes" + "mtg/telegram/api" ) const middleTelegramBackgroundUpdateEvery = time.Hour diff --git a/tlstypes/client_hello.go b/tlstypes/client_hello.go index dbdcde4..deef64a 100644 --- a/tlstypes/client_hello.go +++ b/tlstypes/client_hello.go @@ -6,8 +6,8 @@ import ( "crypto/sha256" "fmt" - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/utils" + "mtg/config" + "mtg/utils" ) type ClientHello struct { diff --git a/tlstypes/handshake.go b/tlstypes/handshake.go index ec0accf..6afe0ba 100644 --- a/tlstypes/handshake.go +++ b/tlstypes/handshake.go @@ -3,7 +3,7 @@ package tlstypes import ( "bytes" - "github.com/9seconds/mtg/utils" + "mtg/utils" ) type Handshake struct { diff --git a/tlstypes/server_hello.go b/tlstypes/server_hello.go index b233d29..e2dd739 100644 --- a/tlstypes/server_hello.go +++ b/tlstypes/server_hello.go @@ -9,7 +9,7 @@ import ( "golang.org/x/crypto/curve25519" - "github.com/9seconds/mtg/config" + "mtg/config" ) type ServerHello struct { diff --git a/utils/init_tcp.go b/utils/init_tcp.go index 3bf90ca..d59b281 100644 --- a/utils/init_tcp.go +++ b/utils/init_tcp.go @@ -4,7 +4,7 @@ import ( "fmt" "net" - "github.com/9seconds/mtg/config" + "mtg/config" ) func InitTCP(conn net.Conn) error { diff --git a/wrappers/packet/mtproto_frame.go b/wrappers/packet/mtproto_frame.go index 6a1cd1a..34553d3 100644 --- a/wrappers/packet/mtproto_frame.go +++ b/wrappers/packet/mtproto_frame.go @@ -12,7 +12,7 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" + "mtg/conntypes" ) const ( diff --git a/wrappers/packetack/client_abridged.go b/wrappers/packetack/client_abridged.go index 1b8aa9a..1c7a94c 100644 --- a/wrappers/packetack/client_abridged.go +++ b/wrappers/packetack/client_abridged.go @@ -8,8 +8,8 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/utils" + "mtg/conntypes" + "mtg/utils" ) const ( diff --git a/wrappers/packetack/client_intermediate.go b/wrappers/packetack/client_intermediate.go index 71ca5f5..d454147 100644 --- a/wrappers/packetack/client_intermediate.go +++ b/wrappers/packetack/client_intermediate.go @@ -9,7 +9,7 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" + "mtg/conntypes" ) const clientIntermediateQuickAckLength = 0x80000000 diff --git a/wrappers/packetack/client_intermediate_secure.go b/wrappers/packetack/client_intermediate_secure.go index 153e779..85a9888 100644 --- a/wrappers/packetack/client_intermediate_secure.go +++ b/wrappers/packetack/client_intermediate_secure.go @@ -8,7 +8,7 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" + "mtg/conntypes" ) type wrapperClientIntermediateSecure struct { diff --git a/wrappers/packetack/proxy.go b/wrappers/packetack/proxy.go index 62ac7d2..6129615 100644 --- a/wrappers/packetack/proxy.go +++ b/wrappers/packetack/proxy.go @@ -6,11 +6,11 @@ import ( "fmt" "net" - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/hub" - "github.com/9seconds/mtg/mtproto/rpc" - "github.com/9seconds/mtg/protocol" + "mtg/config" + "mtg/conntypes" + "mtg/hub" + "mtg/mtproto/rpc" + "mtg/protocol" ) type wrapperProxy struct { diff --git a/wrappers/stream/base.go b/wrappers/stream/base.go index 0fd50ca..3780ebd 100644 --- a/wrappers/stream/base.go +++ b/wrappers/stream/base.go @@ -3,7 +3,7 @@ package stream import ( "net" - "github.com/9seconds/mtg/conntypes" + "mtg/conntypes" ) func NewClientConn(parent net.Conn, connID conntypes.ConnID) conntypes.StreamReadWriteCloser { diff --git a/wrappers/stream/blockcipher.go b/wrappers/stream/blockcipher.go index 3fc7a40..a5463b9 100644 --- a/wrappers/stream/blockcipher.go +++ b/wrappers/stream/blockcipher.go @@ -9,8 +9,8 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/utils" + "mtg/conntypes" + "mtg/utils" ) type wrapperBlockCipher struct { diff --git a/wrappers/stream/conn.go b/wrappers/stream/conn.go index 8b0bf80..fc85e3f 100644 --- a/wrappers/stream/conn.go +++ b/wrappers/stream/conn.go @@ -7,8 +7,8 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/config" - "github.com/9seconds/mtg/conntypes" + "mtg/config" + "mtg/conntypes" ) type connPurpose uint8 diff --git a/wrappers/stream/ctx.go b/wrappers/stream/ctx.go index c744062..3a89677 100644 --- a/wrappers/stream/ctx.go +++ b/wrappers/stream/ctx.go @@ -8,7 +8,7 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" + "mtg/conntypes" ) type wrapperCtx struct { diff --git a/wrappers/stream/faketls.go b/wrappers/stream/faketls.go index 3db845b..10ef688 100644 --- a/wrappers/stream/faketls.go +++ b/wrappers/stream/faketls.go @@ -8,8 +8,8 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/tlstypes" + "mtg/conntypes" + "mtg/tlstypes" ) type wrapperFakeTLS struct { diff --git a/wrappers/stream/mtproto_cipher.go b/wrappers/stream/mtproto_cipher.go index a46b528..350dbbb 100644 --- a/wrappers/stream/mtproto_cipher.go +++ b/wrappers/stream/mtproto_cipher.go @@ -9,9 +9,9 @@ import ( "encoding/binary" "net" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/mtproto/rpc" - "github.com/9seconds/mtg/utils" + "mtg/conntypes" + "mtg/mtproto/rpc" + "mtg/utils" ) type mtprotoCipherPurpose uint8 diff --git a/wrappers/stream/obfuscated2.go b/wrappers/stream/obfuscated2.go index d94b245..0368a2b 100644 --- a/wrappers/stream/obfuscated2.go +++ b/wrappers/stream/obfuscated2.go @@ -8,7 +8,7 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" + "mtg/conntypes" ) type wrapperObfuscated2 struct { diff --git a/wrappers/stream/rewind.go b/wrappers/stream/rewind.go index 1170d9d..1472012 100644 --- a/wrappers/stream/rewind.go +++ b/wrappers/stream/rewind.go @@ -9,7 +9,7 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" + "mtg/conntypes" ) type ReadWriteCloseRewinder interface { diff --git a/wrappers/stream/stats_telegram.go b/wrappers/stream/stats_telegram.go index 064790c..3eceafa 100644 --- a/wrappers/stream/stats_telegram.go +++ b/wrappers/stream/stats_telegram.go @@ -7,8 +7,8 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/stats" + "mtg/conntypes" + "mtg/stats" ) type wrapperTelegramStats struct { diff --git a/wrappers/stream/stats_traffic.go b/wrappers/stream/stats_traffic.go index 911daa2..90ed926 100644 --- a/wrappers/stream/stats_traffic.go +++ b/wrappers/stream/stats_traffic.go @@ -6,8 +6,8 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" - "github.com/9seconds/mtg/stats" + "mtg/conntypes" + "mtg/stats" ) type wrapperTrafficStats struct { diff --git a/wrappers/stream/timeout.go b/wrappers/stream/timeout.go index 6dee072..ce35675 100644 --- a/wrappers/stream/timeout.go +++ b/wrappers/stream/timeout.go @@ -6,7 +6,7 @@ import ( "go.uber.org/zap" - "github.com/9seconds/mtg/conntypes" + "mtg/conntypes" ) const ( From 2bf291bab53a89aefcc581586ce01c836c8916ef Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 11 Nov 2019 15:16:24 +0300 Subject: [PATCH 55/62] Fix minor lint issue --- antireplay/init.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/antireplay/init.go b/antireplay/init.go index 9fab0d8..a89ea89 100644 --- a/antireplay/init.go +++ b/antireplay/init.go @@ -4,8 +4,9 @@ import ( "math" "sync" - "mtg/config" "github.com/dgraph-io/ristretto" + + "mtg/config" ) var ( From 6732e80d060ba5f663e311092cc4973cda7df75e Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 11 Nov 2019 15:22:28 +0300 Subject: [PATCH 56/62] Use recommended way of generating x25519 curve --- tlstypes/server_hello.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tlstypes/server_hello.go b/tlstypes/server_hello.go index e2dd739..4918926 100644 --- a/tlstypes/server_hello.go +++ b/tlstypes/server_hello.go @@ -77,12 +77,11 @@ func makeTLSExtensions(buf io.Writer) { 0x00, 0x20, // 32 bytes of key }) - var dst, in, base [32]byte + var scalar [32]byte - rand.Read(in[:]) // nolint: errcheck - rand.Read(base[:]) // nolint: errcheck - curve25519.ScalarMult(&dst, &in, &base) - buf.Write(dst[:]) // nolint: errcheck + rand.Read(scalar[:]) // nolint: errcheck + curve, _ := curve25519.X25519(scalar[:], curve25519.Basepoint) + buf.Write(curve) // nolint: errcheck buf.Write([]byte{ // nolint: errcheck 0x00, 0x2b, // Extension - Supported Versions From ae479adeaac0d305e3ae608389411dd7e7fff7a7 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 11 Nov 2019 16:05:19 +0300 Subject: [PATCH 57/62] Use fastcache instead of ristretto --- antireplay/cache.go | 22 ++++++++++------------ antireplay/init.go | 18 ++---------------- config/config.go | 4 ++-- go.mod | 2 +- go.sum | 17 +++++++++++++---- 5 files changed, 28 insertions(+), 35 deletions(-) diff --git a/antireplay/cache.go b/antireplay/cache.go index 72ca30e..9eb08ab 100644 --- a/antireplay/cache.go +++ b/antireplay/cache.go @@ -1,6 +1,6 @@ package antireplay -import "github.com/dgraph-io/ristretto" +import "github.com/VictoriaMetrics/fastcache" var ( prefixObfuscated2 = []byte{0x00} @@ -8,31 +8,29 @@ var ( ) type cache struct { - data *ristretto.Cache + data *fastcache.Cache } func (c *cache) AddObfuscated2(data []byte) { - c.data.Set(keyObfuscated2(data), nil, int64(len(data))) + c.data.Set(keyObfuscated2(data), nil) } func (c *cache) AddTLS(data []byte) { - c.data.Set(keyTLS(data), nil, int64(len(data))) + c.data.Set(keyTLS(data), nil) } func (c *cache) HasObfuscated2(data []byte) bool { - _, ok := c.data.Get(keyObfuscated2(data)) - return ok + return c.data.Has(keyObfuscated2(data)) } func (c *cache) HasTLS(data []byte) bool { - _, ok := c.data.Get(keyTLS(data)) - return ok + return c.data.Has(keyTLS(data)) } -func keyObfuscated2(data []byte) string { - return string(append(prefixObfuscated2, data...)) +func keyObfuscated2(data []byte) []byte { + return append(prefixObfuscated2, data...) } -func keyTLS(data []byte) string { - return string(append(prefixTLS, data...)) +func keyTLS(data []byte) []byte { + return append(prefixTLS, data...) } diff --git a/antireplay/init.go b/antireplay/init.go index a89ea89..ccde75b 100644 --- a/antireplay/init.go +++ b/antireplay/init.go @@ -1,10 +1,9 @@ package antireplay import ( - "math" "sync" - "github.com/dgraph-io/ristretto" + "github.com/VictoriaMetrics/fastcache" "mtg/config" ) @@ -16,19 +15,6 @@ var ( func Init() { initOnce.Do(func() { - cost := float64(config.C.AntiReplayMaxSize) / 32.0 - cost = math.Ceil(cost) - - c, err := ristretto.NewCache(&ristretto.Config{ - NumCounters: int64(cost) * 10, - MaxCost: config.C.AntiReplayMaxSize, - BufferItems: 64, - Metrics: false, - }) - if err != nil { - panic(err) - } - - Cache.data = c + Cache.data = fastcache.New(config.C.AntiReplayMaxSize) }) } diff --git a/config/config.go b/config/config.go index 868b467..7db0e6e 100644 --- a/config/config.go +++ b/config/config.go @@ -80,7 +80,7 @@ type Config struct { ReadBuffer int `json:"read_buffer"` CloakPort int `json:"cloak_port"` - AntiReplayMaxSize int64 `json:"anti_replay_max_size"` + AntiReplayMaxSize int `json:"anti_replay_max_size"` MultiplexPerConnection int `json:"multiplex_per_connection"` @@ -152,7 +152,7 @@ func Init(options ...Opt) error { // nolint: gocyclo, funlen case OptionTypeCloakPort: C.CloakPort = int(opt.Value.(uint16)) case OptionTypeAntiReplayMaxSize: - C.AntiReplayMaxSize = int64(opt.Value.(units.Base2Bytes)) + C.AntiReplayMaxSize = int(opt.Value.(units.Base2Bytes)) case OptionTypeMultiplexPerConnection: C.MultiplexPerConnection = int(opt.Value.(uint)) case OptionTypeSecret: diff --git a/go.mod b/go.mod index a2f6aad..901f250 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module mtg go 1.13 require ( + github.com/VictoriaMetrics/fastcache v1.5.2 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d github.com/beevik/ntp v0.2.0 - github.com/dgraph-io/ristretto v0.0.0-20191108194154-8d6a8a75b4ca github.com/prometheus/client_golang v1.2.1 go.uber.org/multierr v1.4.0 // indirect go.uber.org/zap v1.12.0 diff --git a/go.sum b/go.sum index c8c3fad..3b89cb0 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/VictoriaMetrics/fastcache v1.5.2 h1:Erd8iIuBAL9kke8JzM4+WxkKuFkHh3ktwLanJvDgR44= +github.com/VictoriaMetrics/fastcache v1.5.2/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= @@ -10,6 +14,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1C github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw= github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= @@ -18,15 +24,14 @@ github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.0.1-0.20190104013014-3767db7a7e18/go.mod h1:HD5P3vAIAh+Y2GAxg0PrPN1P8WkepXGpjbUPDHJqqKM= github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgraph-io/ristretto v0.0.0-20191108194154-8d6a8a75b4ca h1:NgFAOh7RDM5WbdADm6VyLG1koLrzG0G24M/f5395PcI= -github.com/dgraph-io/ristretto v0.0.0-20191108194154-8d6a8a75b4ca/go.mod h1:edzKIzGvqUCMzhTVWbiTSe75zD9Xxq0GtSBtFmaUTZs= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -40,6 +45,8 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -91,6 +98,8 @@ github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.0.1-0.20190317074736-539464a789e9/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= From 7d1d9007af26a003820278cb7267d572231d25d5 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 11 Nov 2019 17:12:54 +0300 Subject: [PATCH 58/62] Update environment variable table --- README.md | 211 ++++++++++++++++++++---------------------------------- 1 file changed, 77 insertions(+), 134 deletions(-) diff --git a/README.md b/README.md index 16a4603..cff9a5e 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ mtg is an implementation in golang which is intended to be: This proxy supports 2 modes of work: direct connection to Telegram and promoted channel mode. If you do not need promoted channels, I would -recommend you to go with direct mode: this is way more robust. +recommend you to go with direct mode: this way is more robust. To run proxy in direct mode, all you need to do is just provide a secret. If you do not provide ADTag as a second parameter, promoted @@ -102,88 +102,46 @@ Also, there is another project on Ansible Galaxy: https://galaxy.ansible.com/iva # Configuration -Basically, to run this tool you need to configure as less as possible. +Basically, to run this tool you need to configure as less as possible. Telegram +clients support 3 different secret types: + +* Simple - basically, it is just a flow of frames ciphered by AES-CTR stream + cipher. +* Secured - the same stream as simple but with some random noise to prevent + statistical analysis of traffic flow. +* FakeTLS - this mode envelops telegram stream in TLS so it looks (in theory) + the same as any TLS1.3 traffic from DPI point of view. + +If you do not have preferences, go with FakeTLS or at least secured. +Simple mode is a little bit naive and traffic flow can be easily +identified as Telegram one. + +Unlike the rest of implementation, mtg is quite strict about the +execution mode: if you run a proxy instance with FakeTLS secret, you +can't connect to it with simple or secured clients. You can't connect +to the proxy with secured secret with FakeTLS key. It forces one mode +of working. So, unfortunately, there is no way how to connect to the +deployed proxy with another secret (if you know how to construct and +convert them). But at the same time, old clients can't connect so they +won't expose the type of the service. First, you need to generate a secret: ```console -openssl rand -hex 16 +$ mtg generate-secret simple +52a493bdfb90eea55739eabff2d92a14 ``` -or - ```console -head -c 512 /dev/urandom | md5sum | cut -f 1 -d ' ' +$ mtg generate-secret secured +ddf05fb7acb549be047a7c585116581418 ``` -## Secure mode - -_tl;dr - use secret mode for all new installation of proxy; only clients -with dd-secrets will be able to connect. This mode abuses attempts to -DPI MTPROTO traffic._ - -Secure mode is not the best name and of course, it creates a lot of -confusion. To explain what it means, we need to tell you some bits on -dd-secrets. - -MTPROTO proxy protocol requires 16-byte secret. You usually -propagate it as a 32 characters hexadecimal string like -`282831900f371ca182feb0e4e1e1aeef` (if you decode this string -to bytes, you will get a real secret which is used in the -protocol). Everything went quite good until the moment when -developers found an evidence that [protocol is quite weak to -DPI](https://github.com/TelegramMessenger/MTProxy/issues/35) and some -enthusiasts even created simple proofs of concepts on [detecting MTPROTO -traffic](https://github.com/darkk/poormansmtproto). - -Telegram team has introduced a patch called dd-secrets. If you have -a secret `282831900f371ca182feb0e4e1e1aeef` then your dd-secret is -`dd282831900f371ca182feb0e4e1e1aeef`. That is, you just add dd prefix -to the secret, prepend it with dd. In that case, original secret -`282831900f371ca182feb0e4e1e1aeef` is used but client and server start -to act a little bit different: they start to add random noise to the -packets so they can't be detected by their length. In order to keep -backward compatibility, all proxies a quite liberal to the secrets to -use: if the client uses plain secret, without dd prefix, they fall back -to the normal behavior. If dd-secret is used (proxy can extract this -information on the handshake), then more secured, the hardened behavior -is used. - -Yes, it can look like a hack but it is as it is. - -Now going back to the secure mode: if you do not pass `-s` flag to the -mtg, then it checks what mode is requested by the client. If the client -uses plain secret, without dd prefix, then proxy falls back to the -original behavior and do not play with paddings. If dd-secret is used -and client demands this mode, then proxy start to add that random noise -to the packets. But if you pass `-s`, then only clients with dd-secrets -can connect. How to migrate existing clients then? If a client is new -enough, you can just prepend the secret with dd string in the settings. -If it is an old guy, then nothing to do, sorry. - -Why this mode matters? We do not have evidence but there is quite a big -suspicion that some ISPs start to filter MTPROTO traffic. If they detect -the IP address which acts as a proxy, they block it and no clients can -use this proxy. This is an attempt to prevent such a situation. - -General rule of thumb: with all new installation of proxies I would -advise to go with secure mode by default. But please do remember that it -means that clients, which do not pass dd-prefix to their secrets, will -not be able to connect. *Secure mode works only with dd-prefixes!* - -Oneliners to generate such secrets: - ```console -echo dd$(openssl rand -hex 16) +$ mtg generate-secret -c google.com tls +ee852380f362a09343efb4690c4e17862e676f6f676c652e636f6d ``` -or - -```console -echo dd$(head -c 512 /dev/urandom | md5sum | cut -f 1 -d ' ') -``` - - ## Antireplay cache In order to prevent replay attacks, we have internal storage of first @@ -193,36 +151,43 @@ randomly by design and we have negligible possibility of duplication prevent replays. +## FakeTLS + +If you run this a proxy in faketls mode, this proxy will try to hide +itself cloaking a host provided as a part of generated secret. It means +that if you cloak google.com then you can curl this proxy and you'll get +a google.com response back. + +mtg proxifies L3 traffic. In other words, only TCP, without interfering +in TLS, HTTP or any other high-level protocol. + + ## Environment variables It is possible to configure this tool using environment variables. You can configure any flag but not secret or adtag. Here is the list of supported environment variables: -| Environment variable | Corresponding flags | Default value | Description | -|-------------------------------|-----------------------------|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `MTG_DEBUG` | `-d`, `--debug` | `false` | Run in debug mode. Usually, you need to run in this mode only if you develop this tool or its maintainer is asking you to provide logs with such verbosity. | -| `MTG_VERBOSE` | `-v`, `--verbose` | `false` | Run in verbose mode. This is way less chatty than debug mode. | -| `MTG_IP` | `-b`, `--bind-ip` | `127.0.0.1` | Which IP should we bind to. As usual, `0.0.0.0` means that we want to listen on all interfaces. Also, 4 zeroes will bind to both IPv4 and IPv6. | -| `MTG_PORT` | `-p`, `--bind-port` | `3128` | Which port should we bind to (listen on). | -| `MTG_IPV4` | `-4`, `--public-ipv4` | [Autodetect](https://ifconfig.co) | IPv4 address of this proxy. This is required if you NAT your proxy or run it in a docker container. In that case, you absolutely need to specify public IPv4 address of the proxy, otherwise either URLs will be broken or proxy could not access Telegram middle proxies. | -| `MTG_IPV4_PORT` | `--public-ipv4-port` | Value of `--bind-port` | Which port should be public of IPv4 interface. This affects only generated links and should be changed only if you NAT your proxy or run it in a docker container. | -| `MTG_IPV6` | `-6`, `--public-ipv6` | [Autodetect](https://ifconfig.co) | IPv6 address of this proxy. This is required if you NAT your proxy or run it in a docker container. In that case, you absolutely need to specify public IPv6 address of the proxy, otherwise either URLs will be broken or proxy could not access Telegram middle proxies. | -| `MTG_IPV6_PORT` | `--public-ipv6-port` | Value of `--bind-port` | Which port should be public of IPv6 interface. This affects only generated links and should be changed only if you NAT your proxy or run it in a docker container. | -| `MTG_STATS_IP` | `-t`, `--stats-ip` | `127.0.0.1` | Which IP should we bind the internal statistics HTTP server. | -| `MTG_STATS_PORT` | `-q`, `--stats-port` | `3129` | Which port should we bind the internal statistics HTTP server. | -| `MTG_STATSD_IP` | `--statsd-ip` | | IP/host addresses of statsd service. No defaults, by defaults we do not send anything there. | -| `MTG_STATSD_PORT` | `--statsd-port` | `8125` | Which port should we use to work with statsd. | -| `MTG_STATSD_NETWORK` | `--statsd-network` | `udp` | Which protocol should we use to work with statsd. Possible options are `udp` and `tcp`. | -| `MTG_STATSD_PREFIX` | `--statsd-prefix` | `mtg` | Which bucket prefix we should use. For example, if you set `mtg`, then metric `traffic.ingress` would be send as `mtg.traffic.ingress`. | -| `MTG_STATSD_TAGS_FORMAT` | `--statsd-tags-format` | | Which tags format we should use. By default, we are using default vanilla statsd tags format but if you want to send directly to InfluxDB or Datadog, please specify it there. Possible options are `influxdb` and `datadog`. | -| `MTG_STATSD_TAGS` | `--statsd-tags` | | Which tags should we send to statsd with our metrics. Please specify them as `key=value` pairs. | -| `MTG_PROMETHEUS_PREFIX` | `--prometheus-prefix` | `mtg` | Which namespace should be used for prometheus metrics. | -| `MTG_BUFFER_WRITE` | `-w`, `--write-buffer` | `65536` | The size of TCP write buffer in bytes. Write buffer is the buffer for messages which are going from client to Telegram. | -| `MTG_BUFFER_READ` | `-r`, `--read-buffer` | `131072` | The size of TCP read buffer in bytes. Read buffer is the buffer for messages from Telegram to client. | -| `MTG_SECURE_ONLY` | `-s`, `--secure-only` | `false` | Support only clients with secure mode (i.e only clients with dd-secrets). | -| `MTG_ANTIREPLAY_MAXSIZE` | `anti-replay-max-size` | `128` | Max size of antireplay cache in megabytes. | -| `MTG_ANTIREPLAY_EVICTIONTIME` | `anti-replay-eviction-time` | `168h` | Eviction time for antireplay cache entries. | +| Environment variable | Corresponding flags | Default value | Description | +|-------------------------------|------------------------------|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `MTG_DEBUG` | `-d`, `--debug` | `false` | Run in debug mode. Usually, you need to run in this mode only if you develop this tool or its maintainer is asking you to provide logs with such verbosity. | +| `MTG_VERBOSE` | `-v`, `--verbose` | `false` | Run in verbose mode. This is way less chatty than debug mode. | +| `MTG_BIND` | `-b`, `--bind` | `0.0.0.0:3128` | Which host/port pair should we bind to (listen on). | +| `MTG_IPV4` | `-4`, `--public-ipv4` | [Autodetect](https://ifconfig.co) | IPv4 address:port of this proxy. This is required if you NAT your proxy or run it in a docker container. In that case, you absolutely need to specify public IPv4 address of the proxy, otherwise either URLs will be broken or proxy could not access Telegram middle proxies. | +| `MTG_IPV6` | `-6`, `--public-ipv6` | [Autodetect](https://ifconfig.co) | IPv6 address:port of this proxy. This is required if you NAT your proxy or run it in a docker container. In that case, you absolutely need to specify public IPv6 address of the proxy, otherwise either URLs will be broken or proxy could not access Telegram middle proxies. | +| `MTG_STATS_BIND` | `-t`, `--stats-bind` | `127.0.0.1:3129` | Which hist:port should we bind the internal statistics HTTP server (Prometheus). | +| `MTG_STATS_NAMESPACE` | `--stats-namespace` | `mtg` | Which namespace should be used for prometheus metrics. | +| `MTG_STATSD_ADDR` | `--statsd-addr` | | IP:host addresses of statsd service. No defaults, by defaults we do not send anything there. | +| `MTG_STATSD_PORT` | `--statsd-port` | `8125` | Which port should we use to work with statsd. | +| `MTG_STATSD_NETWORK` | `--statsd-network` | `udp` | Which protocol should we use to work with statsd. Possible options are `udp` and `tcp`. | +| `MTG_STATSD_PREFIX` | `--statsd-prefix` | `mtg` | Which bucket prefix we should use. For example, if you set `mtg`, then metric `traffic.ingress` would be send as `mtg.traffic.ingress`. | +| `MTG_STATSD_TAGS_FORMAT` | `--statsd-tags-format` | | Which tags format we should use. By default, we are using default vanilla statsd tags format but if you want to send directly to InfluxDB or Datadog, please specify it there. Possible options are `influxdb` and `datadog`. | +| `MTG_STATSD_TAGS` | `--statsd-tags` | | Which tags should we send to statsd with our metrics. Please specify them as `key=value` pairs. | +| `MTG_BUFFER_WRITE` | `-w`, `--write-buffer` | `65536` | The size of TCP write buffer in bytes. Write buffer is the buffer for messages which are going from client to Telegram. | +| `MTG_BUFFER_READ` | `-r`, `--read-buffer` | `131072` | The size of TCP read buffer in bytes. Read buffer is the buffer for messages from Telegram to client. | +| `MTG_ANTIREPLAY_MAXSIZE` | `--anti-replay-max-size` | `128MB` | Max size of antireplay cache. | +| `MTG_CLOAK_PORT` | `--cloak-port` | `443` | Which port we should use to connect to cloaked host in FakeTLS mode. | +| `MTG_MULTIPLEX_PERCONNECTION` | `--multiplex-per-connection` | `50` | How many client connections can share a single Telegram connection in adtag mode | Usually you want to modify only read/write buffer sizes. If you feel that proxy is slow, try to increase both sizes giving more priority to @@ -237,35 +202,17 @@ userspace. Now run the tool: ```console -mtg +$ mtg run ``` How to run the tool with ADTag: ```console -mtg +$ mtg run ``` This tool will listen on port 3128 by default with the given secret. -# One-line runner - -```console -docker run --name mtg --restart=unless-stopped -p 3128:3128 -p 3129:3129 -d nineseconds/mtg:stable $(openssl rand -hex 16) -``` - -or in secret mode: - -```console -docker run --name mtg --restart=unless-stopped -p 3128:3128 -p 3129:3129 -d nineseconds/mtg:stable dd$(openssl rand -hex 16) -``` - -You will have this tool up and running on port 3128. Now curl -`localhost:3129` to get `tg://` links or do `docker logs mtg`. Also, -port 3129 will show you some statistics if you are interested in. - -Also, you can use [run-mtg.sh](https://github.com/9seconds/mtg/blob/master/run-mtg.sh) script - # statsd integration @@ -278,19 +225,20 @@ and [Datadog](https://docs.datadoghq.com/developers/dogstatsd/). All metrics are gauges. Here is the list of metrics and their meaning: -| Metric name | Unit | Description | -|---------------------------------|---------|-----------------------------------------------------------| -| `connections.abridged.ipv4` | number | The number of active abridged IPv4 connections | -| `connections.abridged.ipv6` | number | The number of active abridged IPv6 connections | -| `connections.intermediate.ipv4` | number | The number of active intermediate IPv4 connections | -| `connections.intermediate.ipv6` | number | The number of active intermediate IPv6 connections | -| `connections.secure.ipv4` | number | The number of active secure intermediate IPv4 connections | -| `connections.secure.ipv6` | number | The number of active secure intermediate IPv6 connections | -| `crashes` | number | An amount of crashes in client handlers | -| `traffic.ingress` | bytes | Ingress traffic from the start of application (incoming) | -| `traffic.egress` | bytes | Egress traffic from the start of application (outgoing) | -| `speed.ingress` | bytes/s | Ingress bandwidth of the latest second (incoming traffic) | -| `speed.egress` | bytes/s | Egress bandwidth of the latest second (outgoing traffic) | +| Metric name | Unit | Description | +|----------------------------------|---------|-----------------------------------------------------------| +| `connections.abridged.ipv4` | number | The number of active abridged IPv4 connections | +| `connections.abridged.ipv6` | number | The number of active abridged IPv6 connections | +| `connections.intermediate.ipv4` | number | The number of active intermediate IPv4 connections | +| `connections.intermediate.ipv6` | number | The number of active intermediate IPv6 connections | +| `connections.secure.ipv4` | number | The number of active secure intermediate IPv4 connections | +| `connections.secure.ipv6` | number | The number of active secure intermediate IPv6 connections | +| `telegram_connections.[dc].ipv4` | number | The number of active abridged IPv4 connections | +| `telegram_connections.[dc].ipv6` | number | The number of active abridged IPv6 connections | +| `crashes` | number | An amount of crashes in client handlers | +| `traffic.ingress` | bytes | Ingress traffic from the start of application (incoming) | +| `traffic.egress` | bytes | Egress traffic from the start of application (outgoing) | +| `replay_attacks` | number | A number of prevented replay attacks. | All metrics are prefixed with given prefix. Default prefix is `mtg`. With such prefix metric name `traffic.ingress`, for example, would be @@ -300,9 +248,4 @@ With such prefix metric name `traffic.ingress`, for example, would be # Prometheus integration [Prometheus](https://prometheus.io) integration comes out of -the box, you do not need to setup anything special. Prometheus -scrape endpoint lives on the same IP/port where generic stats -service (`http://${MTG_STATS_IP}:${MTG_STATS_PORT}`) but on -`/prometheus` path. So, if you access http stats service as `curl -http://localhost:3129/`, then your prometheus endpoint is `curl -http://localhost:3129/prometheus/`. +the box, you do not need to setup anything special. From 24137b6ea278ec2146310f49dad7ec5870ba610c Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 11 Nov 2019 17:26:07 +0300 Subject: [PATCH 59/62] Some notes on upgrading to 1.0 --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cff9a5e..0cb3e9e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ Bullshit-free MTPROTO proxy for Telegram [![Go Report Card](https://goreportcard.com/badge/github.com/9seconds/mtg)](https://goreportcard.com/report/github.com/9seconds/mtg) [![Docker Build Status](https://img.shields.io/docker/build/nineseconds/mtg.svg)](https://hub.docker.com/r/nineseconds/mtg/) +**Please see a guide on upgrading to 1.0 at the end of this README.** + # Rationale There are several available proxies for Telegram MTPROTO available. Here @@ -238,7 +240,7 @@ All metrics are gauges. Here is the list of metrics and their meaning: | `crashes` | number | An amount of crashes in client handlers | | `traffic.ingress` | bytes | Ingress traffic from the start of application (incoming) | | `traffic.egress` | bytes | Egress traffic from the start of application (outgoing) | -| `replay_attacks` | number | A number of prevented replay attacks. | +| `replay_attacks` | number | The number of prevented replay attacks. | All metrics are prefixed with given prefix. Default prefix is `mtg`. With such prefix metric name `traffic.ingress`, for example, would be @@ -249,3 +251,18 @@ With such prefix metric name `traffic.ingress`, for example, would be [Prometheus](https://prometheus.io) integration comes out of the box, you do not need to setup anything special. + + +# Upgrade to 1.0 + +Version 1.0 breaks compatibility with previous versions so please read +this chapter carefully: + +1. mtg now uses subcommands. Please use `mtg run` instead of just `mtg` to run + a proxy. +2. Options which set host and port separately were removed in a favor of fused + host:port options. +3. Own stats server was removed. Prometheus endpoint is moved to default stats + endpoint. +4. It is possible to connect to this proxy only with a secret which was used to + run it. So, no backward compatibility of clients. From 1258f071977b83c6c89ae06886d8b2fe844b4632 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 11 Nov 2019 17:38:26 +0300 Subject: [PATCH 60/62] Update README --- README.md | 59 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 0cb3e9e..3630bc0 100644 --- a/README.md +++ b/README.md @@ -17,33 +17,33 @@ are the most notable: * [Python](https://github.com/alexbers/mtprotoproxy) * [Erlang](https://github.com/seriyps/mtproto_proxy) -Almost all of them follow the way how official proxy was build. This -includes support of multiple secrets, support of promoted channels etc. +Almost all of them follow the way how official proxy was built. This +includes support of multiple secrets, support of promoted channels, etc. mtg is an implementation in golang which is intended to be: * **Lightweight** - It has to consume as less resources as possible but not by losing + It has to consume as few resources as possible but not by losing maintainability. * **Easily deployable** I strongly believe that Telegram proxies should follow the way of ShadowSocks: promoted channels is a strange way of doing business I suppose. I think the only viable way is to have a proxy with minimum configuration which should work everywhere. -* **Single secret** - I think that multiple secrets solves no problems and just complexify - software. I also believe that in case of throwout proxies, this feature - is useless luxury. +* **A single secret** + I think that multiple secrets solve no problems and just complexify + software. I also believe that in the case of throwout proxies, this + feature is a useless luxury. * **Minimum docker image size** Official image is less than 3 megabytes. Literally. * **No management WebUI** - This is an implementation of simple lightweight proxy. I won't do that. + This is an implementation of a simple lightweight proxy. I won't do that. This proxy supports 2 modes of work: direct connection to Telegram and promoted channel mode. If you do not need promoted channels, I would recommend you to go with direct mode: this way is more robust. -To run proxy in direct mode, all you need to do is just provide a +To run a proxy in direct mode, all you need to do is just provide a secret. If you do not provide ADTag as a second parameter, promoted channels mode won't be activated. @@ -104,7 +104,7 @@ Also, there is another project on Ansible Galaxy: https://galaxy.ansible.com/iva # Configuration -Basically, to run this tool you need to configure as less as possible. Telegram +To run this tool you need to configure as less as possible. Telegram clients support 3 different secret types: * Simple - basically, it is just a flow of frames ciphered by AES-CTR stream @@ -146,22 +146,22 @@ ee852380f362a09343efb4690c4e17862e676f6f676c652e636f6d ## Antireplay cache -In order to prevent replay attacks, we have internal storage of first -frames messages for connected clients. These frames are generated -randomly by design and we have negligible possibility of duplication -(probability is 1/(2^64)) but it could be quite effective in order to -prevent replays. +To prevent replay attacks, we have internal storage of first frames +messages for connected clients. These frames are generated randomly +by design and we have the negligible possibility of duplication +(probability is 1/(2^64)) but it could be quite effective to prevent +replays. ## FakeTLS If you run this a proxy in faketls mode, this proxy will try to hide -itself cloaking a host provided as a part of generated secret. It means -that if you cloak google.com then you can curl this proxy and you'll get -a google.com response back. +itself cloaking a host provided as a part of the generated secret. It +means that if you cloak google.com then you can curl this proxy and +you'll get a google.com response back. -mtg proxifies L3 traffic. In other words, only TCP, without interfering -in TLS, HTTP or any other high-level protocol. +mtg proxies L3 traffic. In other words, only TCP, without interfering in +TLS, HTTP or any other high-level protocol. ## Environment variables @@ -258,11 +258,14 @@ the box, you do not need to setup anything special. Version 1.0 breaks compatibility with previous versions so please read this chapter carefully: -1. mtg now uses subcommands. Please use `mtg run` instead of just `mtg` to run - a proxy. -2. Options which set host and port separately were removed in a favor of fused - host:port options. -3. Own stats server was removed. Prometheus endpoint is moved to default stats - endpoint. -4. It is possible to connect to this proxy only with a secret which was used to - run it. So, no backward compatibility of clients. +1. mtg now uses subcommands. Please use `mtg run` instead of just + `mtg` to run a proxy. +2. Options which set host and port separately were removed in a + favor of fused `host:port` options. +3. Own stats server was removed. Prometheus endpoint is moved to + default stats endpoint. +4. It is possible to connect to this proxy only with a secret which + was used to run it. So, no backward compatibility of clients. +5. Multiplexing involves connectivity with middle proxies and involves + the most complex code path of this proxy. To avoid potential bugs, + we still recommend using direct mode. From 3c75fcca8002dfaa79b935dda1b2abc46735039c Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 11 Nov 2019 17:42:49 +0300 Subject: [PATCH 61/62] Remove run-mtg script --- run-mtg.sh | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100755 run-mtg.sh diff --git a/run-mtg.sh b/run-mtg.sh deleted file mode 100755 index 60459c0..0000000 --- a/run-mtg.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -set -eu -o pipefail - -IMAGE_NAME="nineseconds/mtg" -CONTAINER_NAME="mtg" -SECRET_PATH="$HOME/.mtg.secret" -PROXY_PORT=444 -STAT_PORT=3129 - -[[ -e "$SECRET_PATH" ]] || ( - openssl rand -hex 16 > "$SECRET_PATH" - chmod 0400 "$SECRET_PATH" -) - -docker pull "$IMAGE_NAME" -docker ps --filter "Name=$CONTAINER_NAME" -aq | xargs -r docker rm -fv -docker run \ - -d \ - --name "$CONTAINER_NAME" \ - --sysctl 'net.ipv4.ip_local_port_range=10000 65000' \ - --sysctl net.ipv4.tcp_congestion_control=bbr \ - --sysctl net.ipv4.tcp_fastopen=3 \ - --sysctl net.ipv4.tcp_fin_timeout=30 \ - --sysctl net.ipv4.tcp_max_syn_backlog=4096 \ - --sysctl net.ipv4.tcp_max_tw_buckets=5000 \ - --sysctl net.ipv4.tcp_mtu_probing=1 \ - --sysctl 'net.ipv4.tcp_rmem=4096 87380 67108864' \ - --sysctl net.ipv4.tcp_syncookies=1 \ - --sysctl net.ipv4.tcp_tw_reuse=1 \ - --sysctl 'net.ipv4.tcp_wmem=4096 65536 67108864' \ - --ulimit nofile=51200:51200 \ - --restart=unless-stopped \ - -p $PROXY_PORT:3128 \ - -p $STAT_PORT:3129 \ - "$IMAGE_NAME" "$(cat "$SECRET_PATH")" From 8b802819eecd128ec0b4439423cdcb3a272f47f7 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 11 Nov 2019 17:48:04 +0300 Subject: [PATCH 62/62] We become fat --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3630bc0..22a5e9f 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ mtg is an implementation in golang which is intended to be: software. I also believe that in the case of throwout proxies, this feature is a useless luxury. * **Minimum docker image size** - Official image is less than 3 megabytes. Literally. + Official image is less than 3.5 megabytes. Literally. * **No management WebUI** This is an implementation of a simple lightweight proxy. I won't do that.