REPOSITORY / ScuroNeko/mtg

Compare commits

DIFF REPOSITORY

Compare commits

...
19 Commits
Author SHA1 Message Date
9seconds 61a1024264 Merge remote-tracking branch 'origin/master' into stable 2018-10-17 14:20:36 +03:00
Sergey ArkhipovandGitHub 4695a0c433 Merge pull request #47 from 9seconds/prometheus
Prometheus integration
2018-10-17 13:14:31 +03:00
9seconds a9d0ca1e90 Describe and document prometheus integration 2018-10-17 12:54:14 +03:00
9seconds 9346c11f37 Add support for prometheus 2018-10-17 12:06:46 +03:00
9seconds 5f00363da5 Add goreportcard 2018-10-17 10:52:30 +03:00
9seconds 3aec9657d7 Add comments for exported functions 2018-10-17 10:51:16 +03:00
9seconds 358d42ec61 Fix spelling issue 2018-10-17 10:49:27 +03:00
Sergey ArkhipovandGitHub ac2625209b Merge pull request #45 from 9seconds/stats
Rework stats server
2018-10-16 22:24:01 +03:00
9seconds c74e0ee359 Rework stats server 2018-10-16 22:17:46 +03:00
Sergey ArkhipovandGitHub 7c463851b2 Okay, 3 megabytes. Image grows :( 2018-10-13 11:18:36 +03:00
Sergey ArkhipovandGitHub c9f6b922c3 Merge pull request #44 from 9seconds/goroutines-number
Decrease the number of used goroutines
2018-10-12 10:56:46 +03:00
9seconds 7128e70e50 Minor changes in dockerfile 2018-10-12 10:50:52 +03:00
9seconds cf9bf56d8e Update dependencies 2018-10-12 10:40:54 +03:00
9seconds 76cfbb009a Decrease an amount of goroutines 2018-10-12 10:38:35 +03:00
9seconds cb78169d6a Update dependencies 2018-10-10 13:49:52 +03:00
9seconds d1703873c1 Merge remote-tracking branch 'origin/master' into stable 2018-09-24 18:29:24 +03:00
Sergey ArkhipovandGitHub ac33abbbb1 Merge pull request #40 from 9seconds/secure-only
"Secure only" mode
2018-09-24 18:15:36 +03:00
9seconds a6893c8df7 Update README 2018-09-24 18:08:06 +03:00
9seconds 7182c7bf65 Add new secure-only mode 2018-09-24 18:04:00 +03:00
14 changed files with 374 additions and 251 deletions
+3 -4
View File
@@ -10,8 +10,7 @@ RUN set -x \
curl \ curl \
git \ git \
make \ make \
upx \ upx
&& update-ca-certificates
COPY . /go/src/github.com/9seconds/mtg/ COPY . /go/src/github.com/9seconds/mtg/
@@ -26,7 +25,7 @@ RUN set -x \
FROM scratch FROM scratch
ENTRYPOINT ["/usr/local/bin/mtg"] ENTRYPOINT ["/mtg"]
ENV MTG_IP=0.0.0.0 \ ENV MTG_IP=0.0.0.0 \
MTG_PORT=3128 \ MTG_PORT=3128 \
MTG_STATS_IP=0.0.0.0 \ MTG_STATS_IP=0.0.0.0 \
@@ -34,4 +33,4 @@ ENV MTG_IP=0.0.0.0 \
EXPOSE 3128 3129 EXPOSE 3128 3129
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=0 /go/src/github.com/9seconds/mtg/mtg /usr/local/bin/mtg COPY --from=0 /go/src/github.com/9seconds/mtg/mtg /mtg
+20 -1
View File
@@ -3,6 +3,7 @@
Bullshit-free MTPROTO proxy for Telegram Bullshit-free MTPROTO proxy for Telegram
[![Build Status](https://travis-ci.org/9seconds/mtg.svg?branch=master)](https://travis-ci.org/9seconds/mtg) [![Build Status](https://travis-ci.org/9seconds/mtg.svg?branch=master)](https://travis-ci.org/9seconds/mtg)
[![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/) [![Docker Build Status](https://img.shields.io/docker/build/nineseconds/mtg.svg)](https://hub.docker.com/r/nineseconds/mtg/)
# Rationale # Rationale
@@ -32,7 +33,7 @@ mtg is an implementation in golang which is intended to be:
software. I also believe that in case of throwout proxies, this feature software. I also believe that in case of throwout proxies, this feature
is useless luxury. is useless luxury.
* **Minimum docker image size** * **Minimum docker image size**
Official image is less than 2.5 megabytes. Literally. Official image is less than 3 megabytes. Literally.
* **No management WebUI** * **No management WebUI**
This is an implementation of simple lightweight proxy. I won't do that. This is an implementation of simple lightweight proxy. I won't do that.
@@ -130,6 +131,11 @@ or
echo dd$(head -c 512 /dev/urandom | md5sum | cut -f 1 -d ' ') echo dd$(head -c 512 /dev/urandom | md5sum | cut -f 1 -d ' ')
``` ```
If you want to enforce the usage of secure mode, please pass `-s` or
`--secure-only` flags. In that case, clients which do not use dd-secrets
are going to be disconnected from the proxy.
## Environment variables ## Environment variables
It is possible to configure this tool using environment variables. You It is possible to configure this tool using environment variables. You
@@ -154,8 +160,10 @@ supported environment variables:
| `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_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_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_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_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_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). |
Usually you want to modify only read/write buffer sizes. If you feel 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 that proxy is slow, try to increase both sizes giving more priority to
@@ -228,3 +236,14 @@ All metrics are gauges. Here is the list of metrics and their meaning:
All metrics are prefixed with given prefix. Default prefix is `mtg`. All metrics are prefixed with given prefix. Default prefix is `mtg`.
With such prefix metric name `traffic.ingress`, for example, would be With such prefix metric name `traffic.ingress`, for example, would be
`mtg.traffic.ingress`. `mtg.traffic.ingress`.
# 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/`.
+9 -2
View File
@@ -16,6 +16,7 @@ type Config struct {
Debug bool Debug bool
Verbose bool Verbose bool
SecureMode bool SecureMode bool
SecureOnly bool
ReadBufferSize int ReadBufferSize int
WriteBufferSize int WriteBufferSize int
@@ -37,6 +38,9 @@ type Config struct {
TagsFormat statsd.TagFormat TagsFormat statsd.TagFormat
Enabled bool Enabled bool
} }
Prometheus struct {
Prefix string
}
Secret []byte Secret []byte
AdTag []byte AdTag []byte
@@ -115,9 +119,10 @@ func NewConfig(debug, verbose bool, // nolint: gocyclo
bindIP, publicIPv4, publicIPv6, statsIP net.IP, bindIP, publicIPv4, publicIPv6, statsIP net.IP,
bindPort, publicIPv4Port, publicIPv6Port, statsPort, statsdPort uint16, bindPort, publicIPv4Port, publicIPv6Port, statsPort, statsdPort uint16,
statsdIP, statsdNetwork, statsdPrefix, statsdTagsFormat string, statsdIP, statsdNetwork, statsdPrefix, statsdTagsFormat string,
statsdTags map[string]string, statsdTags map[string]string, prometheusPrefix string,
secureOnly bool,
secret, adtag []byte) (*Config, error) { secret, adtag []byte) (*Config, error) {
secureMode := false secureMode := secureOnly
if bytes.HasPrefix(secret, []byte{0xdd}) && len(secret) == 17 { if bytes.HasPrefix(secret, []byte{0xdd}) && len(secret) == 17 {
secureMode = true secureMode = true
secret = bytes.TrimPrefix(secret, []byte{0xdd}) secret = bytes.TrimPrefix(secret, []byte{0xdd})
@@ -157,6 +162,7 @@ func NewConfig(debug, verbose bool, // nolint: gocyclo
conf := &Config{ conf := &Config{
Debug: debug, Debug: debug,
Verbose: verbose, Verbose: verbose,
SecureOnly: secureOnly,
BindIP: bindIP, BindIP: bindIP,
BindPort: bindPort, BindPort: bindPort,
PublicIPv4: publicIPv4, PublicIPv4: publicIPv4,
@@ -171,6 +177,7 @@ func NewConfig(debug, verbose bool, // nolint: gocyclo
ReadBufferSize: int(readBufferSize), ReadBufferSize: int(readBufferSize),
WriteBufferSize: int(writeBufferSize), WriteBufferSize: int(writeBufferSize),
} }
conf.Prometheus.Prefix = prometheusPrefix
if statsdIP != "" { if statsdIP != "" {
conf.StatsD.Enabled = true conf.StatsD.Enabled = true
+12 -3
View File
@@ -4,20 +4,29 @@ require (
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
github.com/beevik/ntp v0.2.0 github.com/beevik/ntp v0.2.0
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d github.com/dustin/go-humanize v1.0.0
github.com/gofrs/uuid v3.1.0+incompatible github.com/gofrs/uuid v3.1.0+incompatible
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1 github.com/gogo/protobuf v1.1.1 // indirect
github.com/golang/protobuf v1.2.0 // indirect
github.com/juju/errors v0.0.0-20181012004132-a4583d0a56ea
github.com/juju/loggo v0.0.0-20180524022052-584905176618 // indirect github.com/juju/loggo v0.0.0-20180524022052-584905176618 // indirect
github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 // indirect github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 // indirect
github.com/kr/pretty v0.1.0 // indirect github.com/kr/pretty v0.1.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/pkg/errors v0.8.0 // indirect github.com/pkg/errors v0.8.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v0.9.0
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect
github.com/prometheus/common v0.0.0-20181015124227-bcb74de08d37 // indirect
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d // indirect
github.com/stretchr/testify v1.2.2 github.com/stretchr/testify v1.2.2
go.uber.org/atomic v1.3.2 // indirect go.uber.org/atomic v1.3.2 // indirect
go.uber.org/multierr v1.1.0 // indirect go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.9.1 go.uber.org/zap v1.9.1
golang.org/x/net v0.0.0-20180921000356-2f5d2388922f // indirect golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 // indirect
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/alecthomas/kingpin.v2 v2.2.6
gopkg.in/alexcesaro/statsd.v2 v2.0.0 gopkg.in/alexcesaro/statsd.v2 v2.0.0
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
+24 -6
View File
@@ -4,14 +4,20 @@ 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-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw= 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/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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d h1:lDrio3iIdNb0Gw9CgH7cQF+iuB5mOOjdJ9ERNJCBgb4= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA= github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA=
github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1 h1:wnhMXidtb70kDZCeLt/EfsVtkXS5c8zLnE9y/6DIRAU= github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
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/juju/errors v0.0.0-20181012004132-a4583d0a56ea h1:g2k+8WR7cHch4g0tBDhfiEvAp7fXxTNBiD1oC1Oxj3E=
github.com/juju/errors v0.0.0-20181012004132-a4583d0a56ea/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/loggo v0.0.0-20180524022052-584905176618 h1:MK144iBQF9hTSwBW/9eJm034bVoG30IshVm688T2hi8= github.com/juju/loggo v0.0.0-20180524022052-584905176618 h1:MK144iBQF9hTSwBW/9eJm034bVoG30IshVm688T2hi8=
github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 h1:WQM1NildKThwdP7qWrNAFGzp4ijNLw8RlgENkaI4MJs= github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 h1:WQM1NildKThwdP7qWrNAFGzp4ijNLw8RlgENkaI4MJs=
@@ -21,10 +27,20 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 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/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/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.0 h1:tXuTFVHC03mW0D+Ua1Q2d1EAVqLTuggX50V0VLICCzY=
github.com/prometheus/client_golang v0.9.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
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/common v0.0.0-20181015124227-bcb74de08d37 h1:Y7YdJ9Xb3MoQOzAWXnDunAJYpvhVwZdTirNfGUgPKaA=
github.com/prometheus/common v0.0.0-20181015124227-bcb74de08d37/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
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/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
@@ -33,8 +49,10 @@ 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.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/net v0.0.0-20180921000356-2f5d2388922f h1:QM2QVxvDoW9PFSPp/zy9FgxJLfaWTZlS61KEPtBwacM= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w=
golang.org/x/net v0.0.0-20180921000356-2f5d2388922f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= 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/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/statsd.v2 v2.0.0 h1:FXkZSCZIH17vLCO5sO2UucTHsH9pc+17F6pl3JVCwMc= gopkg.in/alexcesaro/statsd.v2 v2.0.0 h1:FXkZSCZIH17vLCO5sO2UucTHsH9pc+17F6pl3JVCwMc=
+13 -2
View File
@@ -110,6 +110,12 @@ var (
Envar("MTG_STATSD_TAGS"). Envar("MTG_STATSD_TAGS").
StringMap() StringMap()
prometheusPrefix = app.Flag("prometheus-prefix",
"Which namespace to use to send stats to Prometheus.").
Envar("MTG_PROMETHEUS_PREFIX").
Default("mtg").
String()
writeBufferSize = app.Flag("write-buffer", writeBufferSize = app.Flag("write-buffer",
"Write buffer size in bytes. You can think about it as a buffer from client to Telegram."). "Write buffer size in bytes. You can think about it as a buffer from client to Telegram.").
Short('w'). Short('w').
@@ -122,6 +128,11 @@ var (
Envar("MTG_BUFFER_READ"). Envar("MTG_BUFFER_READ").
Default("131072"). Default("131072").
Uint32() Uint32()
secureOnly = app.Flag("secure-only",
"Support clients with dd-secrets only.").
Short('s').
Envar("MTG_SECURE_ONLY").
Bool()
secret = app.Arg("secret", "Secret of this proxy.").Required().HexBytes() secret = app.Arg("secret", "Secret of this proxy.").Required().HexBytes()
adtag = app.Arg("adtag", "ADTag of the proxy.").HexBytes() adtag = app.Arg("adtag", "ADTag of the proxy.").HexBytes()
@@ -146,7 +157,7 @@ func main() { // nolint: gocyclo
*bindIP, *publicIPv4, *publicIPv6, *statsIP, *bindIP, *publicIPv4, *publicIPv6, *statsIP,
*bindPort, *publicIPv4Port, *publicIPv6Port, *statsPort, *statsdPort, *bindPort, *publicIPv4Port, *publicIPv6Port, *statsPort, *statsdPort,
*statsdIP, *statsdNetwork, *statsdPrefix, *statsdTagsFormat, *statsdIP, *statsdNetwork, *statsdPrefix, *statsdTagsFormat,
*statsdTags, *statsdTags, *prometheusPrefix, *secureOnly,
*secret, *adtag, *secret, *adtag,
) )
if err != nil { if err != nil {
@@ -188,7 +199,7 @@ func main() { // nolint: gocyclo
zap.S().Infow("Use direct connection to Telegram") zap.S().Infow("Use direct connection to Telegram")
} }
if err := stats.Start(conf); err != nil { if err := stats.Init(conf); err != nil {
panic(err) panic(err)
} }
+11 -16
View File
@@ -65,6 +65,11 @@ func (p *Proxy) accept(conn net.Conn) {
} }
defer clientConn.(io.Closer).Close() // nolint: errcheck 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()) stats.ClientConnected(opts.ConnectionType, clientConn.RemoteAddr())
defer stats.ClientDisconnected(opts.ConnectionType, clientConn.RemoteAddr()) defer stats.ClientDisconnected(opts.ConnectionType, clientConn.RemoteAddr())
@@ -88,12 +93,12 @@ func (p *Proxy) accept(conn net.Conn) {
clientPacket := clientConn.(wrappers.PacketReadWriteCloser) clientPacket := clientConn.(wrappers.PacketReadWriteCloser)
serverPacket := serverConn.(wrappers.PacketReadWriteCloser) serverPacket := serverConn.(wrappers.PacketReadWriteCloser)
go p.middlePipe(clientPacket, serverPacket, wait, &opts.ReadHacks) go p.middlePipe(clientPacket, serverPacket, wait, &opts.ReadHacks)
go p.middlePipe(serverPacket, clientPacket, wait, &opts.WriteHacks) p.middlePipe(serverPacket, clientPacket, wait, &opts.WriteHacks)
} else { } else {
clientStream := clientConn.(wrappers.StreamReadWriteCloser) clientStream := clientConn.(wrappers.StreamReadWriteCloser)
serverStream := serverConn.(wrappers.StreamReadWriteCloser) serverStream := serverConn.(wrappers.StreamReadWriteCloser)
go p.directPipe(clientStream, serverStream, wait, p.conf.ReadBufferSize) go p.directPipe(clientStream, serverStream, wait, p.conf.ReadBufferSize)
go p.directPipe(serverStream, clientStream, wait, p.conf.WriteBufferSize) p.directPipe(serverStream, clientStream, wait, p.conf.WriteBufferSize)
} }
wait.Wait() wait.Wait()
@@ -116,13 +121,8 @@ func (p *Proxy) getTelegramConn(ctx context.Context, cancel context.CancelFunc,
return packetConn, nil return packetConn, nil
} }
func (p *Proxy) middlePipe(src wrappers.PacketReadCloser, dst io.WriteCloser, func (p *Proxy) middlePipe(src wrappers.PacketReadCloser, dst io.Writer, wait *sync.WaitGroup, hacks *mtproto.Hacks) {
wait *sync.WaitGroup, hacks *mtproto.Hacks) { defer wait.Done()
defer func() {
src.Close() // nolint: errcheck, gosec
dst.Close() // nolint: errcheck, gosec
wait.Done()
}()
for { for {
hacks.SimpleAck = false hacks.SimpleAck = false
@@ -140,13 +140,8 @@ func (p *Proxy) middlePipe(src wrappers.PacketReadCloser, dst io.WriteCloser,
} }
} }
func (p *Proxy) directPipe(src wrappers.StreamReadCloser, dst io.WriteCloser, func (p *Proxy) directPipe(src wrappers.StreamReadCloser, dst io.Writer, wait *sync.WaitGroup, bufferSize int) {
wait *sync.WaitGroup, bufferSize int) { defer wait.Done()
defer func() {
src.Close() // nolint: errcheck, gosec
dst.Close() // nolint: errcheck, gosec
wait.Done()
}()
buffer := make([]byte, bufferSize) buffer := make([]byte, bufferSize)
if _, err := io.CopyBuffer(dst, src, buffer); err != nil { if _, err := io.CopyBuffer(dst, src, buffer); err != nil {
+17 -86
View File
@@ -2,21 +2,20 @@ package stats
import ( import (
"net" "net"
"time"
"github.com/9seconds/mtg/mtproto" "github.com/9seconds/mtg/mtproto"
) )
const ( const (
crashesChanLength = 1 connectionsChanLength = 10
connectionsChanLength = 20 trafficChanLength = 10
trafficChanLength = 5000
) )
var ( var (
crashesChan = make(chan struct{}, crashesChanLength) crashesChan = make(chan struct{})
connectionsChan = make(chan *connectionData, connectionsChanLength) statsChan = make(chan chan<- Stats)
trafficChan = make(chan *trafficData, trafficChanLength) connectionsChan = make(chan connectionData, connectionsChanLength)
trafficChan = make(chan trafficData, trafficChanLength)
) )
type connectionData struct { type connectionData struct {
@@ -30,81 +29,6 @@ type trafficData struct {
ingress bool ingress bool
} }
func crashManager() {
for range crashesChan {
instance.mutex.RLock()
instance.Crashes++
instance.mutex.RUnlock()
}
}
func connectionManager() {
for event := range connectionsChan {
instance.mutex.RLock()
isIPv4 := event.addr.IP.To4() != nil
var inc uint32 = 1
if !event.connected {
inc = ^uint32(0)
}
switch event.connectionType {
case mtproto.ConnectionTypeAbridged:
if isIPv4 {
instance.Connections.Abridged.IPv4 += inc
} else {
instance.Connections.Abridged.IPv6 += inc
}
case mtproto.ConnectionTypeSecure:
if isIPv4 {
instance.Connections.Secure.IPv4 += inc
} else {
instance.Connections.Secure.IPv6 += inc
}
default:
if isIPv4 {
instance.Connections.Intermediate.IPv4 += inc
} else {
instance.Connections.Intermediate.IPv6 += inc
}
}
instance.mutex.RUnlock()
}
}
func trafficManager() {
speedChan := time.Tick(time.Second)
for {
select {
case event := <-trafficChan:
instance.mutex.RLock()
if event.ingress {
instance.Traffic.Ingress += trafficValue(event.traffic)
instance.speedCurrent.Ingress += trafficSpeedValue(event.traffic)
} else {
instance.Traffic.Egress += trafficValue(event.traffic)
instance.speedCurrent.Egress += trafficSpeedValue(event.traffic)
}
instance.mutex.RUnlock()
case <-speedChan:
instance.mutex.RLock()
instance.Speed.Ingress = instance.speedCurrent.Ingress
instance.Speed.Egress = instance.speedCurrent.Egress
instance.speedCurrent.Ingress = trafficSpeedValue(0)
instance.speedCurrent.Egress = trafficSpeedValue(0)
instance.mutex.RUnlock()
}
}
}
// NewCrash indicates new crash. // NewCrash indicates new crash.
func NewCrash() { func NewCrash() {
crashesChan <- struct{}{} crashesChan <- struct{}{}
@@ -112,7 +36,7 @@ func NewCrash() {
// ClientConnected indicates that new client was connected. // ClientConnected indicates that new client was connected.
func ClientConnected(connectionType mtproto.ConnectionType, addr *net.TCPAddr) { func ClientConnected(connectionType mtproto.ConnectionType, addr *net.TCPAddr) {
connectionsChan <- &connectionData{ connectionsChan <- connectionData{
connectionType: connectionType, connectionType: connectionType,
addr: addr, addr: addr,
connected: true, connected: true,
@@ -121,7 +45,7 @@ func ClientConnected(connectionType mtproto.ConnectionType, addr *net.TCPAddr) {
// ClientDisconnected indicates that client was disconnected. // ClientDisconnected indicates that client was disconnected.
func ClientDisconnected(connectionType mtproto.ConnectionType, addr *net.TCPAddr) { func ClientDisconnected(connectionType mtproto.ConnectionType, addr *net.TCPAddr) {
connectionsChan <- &connectionData{ connectionsChan <- connectionData{
connectionType: connectionType, connectionType: connectionType,
addr: addr, addr: addr,
connected: false, connected: false,
@@ -130,7 +54,7 @@ func ClientDisconnected(connectionType mtproto.ConnectionType, addr *net.TCPAddr
// IngressTraffic accounts new ingress traffic. // IngressTraffic accounts new ingress traffic.
func IngressTraffic(traffic int) { func IngressTraffic(traffic int) {
trafficChan <- &trafficData{ trafficChan <- trafficData{
traffic: traffic, traffic: traffic,
ingress: true, ingress: true,
} }
@@ -138,8 +62,15 @@ func IngressTraffic(traffic int) {
// EgressTraffic accounts new ingress traffic. // EgressTraffic accounts new ingress traffic.
func EgressTraffic(traffic int) { func EgressTraffic(traffic int) {
trafficChan <- &trafficData{ trafficChan <- trafficData{
traffic: traffic, traffic: traffic,
ingress: false, ingress: false,
} }
} }
// GetStats returns a snapshot of Stats instance.
func GetStats() Stats {
rpcChan := make(chan Stats)
statsChan <- rpcChan
return <-rpcChan
}
+28
View File
@@ -0,0 +1,28 @@
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)
return nil
}
+80
View File
@@ -0,0 +1,80 @@
package stats
import (
"time"
"github.com/juju/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/9seconds/mtg/config"
)
const prometheusPollTime = time.Second
type prometheusExporter struct {
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 newPrometheus(conf *config.Config) (*prometheusExporter, error) {
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 := prometheus.Register(connections); err != nil {
return nil, errors.Annotate(err, "Cannot register connections collector")
}
if err := prometheus.Register(traffic); err != nil {
return nil, errors.Annotate(err, "cannot register traffic collector")
}
if err := prometheus.Register(speed); err != nil {
return nil, errors.Annotate(err, "cannot register speed collector")
}
if err := prometheus.Register(crashes); err != nil {
return nil, errors.Annotate(err, "cannot register crashes collector")
}
return &prometheusExporter{
connections: connections,
traffic: traffic,
speed: speed,
crashes: crashes,
}, nil
}
+10 -38
View File
@@ -3,67 +3,39 @@ package stats
import ( import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"sync"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/9seconds/mtg/config" "github.com/9seconds/mtg/config"
) )
var instance *stats func startServer(conf *config.Config) {
// Start starts new statistics server.
func Start(conf *config.Config) error {
log := zap.S().Named("stats") log := zap.S().Named("stats")
instance = &stats{
URLs: conf.GetURLs(),
Uptime: uptime(time.Now()),
mutex: &sync.RWMutex{},
}
if conf.StatsD.Enabled {
client, err := newStatsd(conf)
if err != nil {
return err
}
go client.run()
}
go crashManager()
go connectionManager()
go trafficManager()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
instance.mutex.Lock() first, err := json.Marshal(GetStats())
first, err := json.Marshal(instance)
instance.mutex.Unlock()
if err != nil { if err != nil {
log.Errorw("Cannot encode json", "error", err) log.Errorw("Cannot encode json", "error", err)
http.Error(w, "Internal server error", 500) http.Error(w, "Internal server error", 500)
return return
} }
interm := map[string]interface{}{} interim := map[string]interface{}{}
json.Unmarshal(first, &interm) // nolint: errcheck, gosec json.Unmarshal(first, &interim) // nolint: errcheck, gosec
encoder := json.NewEncoder(w) encoder := json.NewEncoder(w)
encoder.SetEscapeHTML(false) encoder.SetEscapeHTML(false)
encoder.SetIndent("", " ") encoder.SetIndent("", " ")
if err = encoder.Encode(interm); err != nil { if err = encoder.Encode(interim); err != nil {
log.Errorw("Cannot encode json", "error", err) log.Errorw("Cannot encode json", "error", err)
} }
}) })
http.Handle("/prometheus/", promhttp.Handler())
go func() { if err := http.ListenAndServe(conf.StatAddr(), nil); err != nil {
if err := http.ListenAndServe(conf.StatAddr(), nil); err != nil { log.Fatalw("Stats server has been stopped", "error", err)
log.Fatalw("Stats server has been stopped", "error", err) }
}
}()
return nil
} }
+122 -49
View File
@@ -4,12 +4,12 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv" "strconv"
"sync"
"time" "time"
humanize "github.com/dustin/go-humanize" humanize "github.com/dustin/go-humanize"
"github.com/9seconds/mtg/config" "github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/mtproto"
) )
type uptime time.Time type uptime time.Time
@@ -24,72 +24,73 @@ func (u uptime) MarshalJSON() ([]byte, error) {
return json.Marshal(value) return json.Marshal(value)
} }
type trafficValue uint64 type connectionType struct {
IPv6 uint32 `json:"ipv6"`
func (t trafficValue) MarshalJSON() ([]byte, error) { IPv4 uint32 `json:"ipv4"`
tv := uint64(t)
value := map[string]interface{}{
"bytes": tv,
"human": humanize.Bytes(tv),
}
return json.Marshal(value)
} }
type trafficSpeedValue uint64 type baseConnections struct {
func (t trafficSpeedValue) MarshalJSON() ([]byte, error) {
speed := uint64(t)
value := map[string]interface{}{
"bytes/s": speed,
"human": fmt.Sprintf("%s/S", humanize.Bytes(speed)),
}
return json.Marshal(value)
}
type connections struct {
All connectionType `json:"all"` All connectionType `json:"all"`
Abridged connectionType `json:"abridged"` Abridged connectionType `json:"abridged"`
Intermediate connectionType `json:"intermediate"` Intermediate connectionType `json:"intermediate"`
Secure connectionType `json:"secure"` Secure connectionType `json:"secure"`
} }
type connections struct {
baseConnections
}
func (c connections) MarshalJSON() ([]byte, error) { func (c connections) MarshalJSON() ([]byte, error) {
c.All.IPv4 = c.Abridged.IPv4 + c.Intermediate.IPv4 + c.Secure.IPv4 c.All.IPv4 = c.Abridged.IPv4 + c.Intermediate.IPv4 + c.Secure.IPv4
c.All.IPv6 = c.Abridged.IPv6 + c.Intermediate.IPv6 + c.Secure.IPv6 c.All.IPv6 = c.Abridged.IPv6 + c.Intermediate.IPv6 + c.Secure.IPv6
value := struct { return json.Marshal(c.baseConnections)
All connectionType `json:"all"` }
Abridged connectionType `json:"abridged"`
Intermediate connectionType `json:"intermediate"` type traffic struct {
Secure connectionType `json:"secure"` ingress uint64
}{ egress uint64
All: c.All, }
Abridged: c.Abridged,
Intermediate: c.Intermediate, func (t *traffic) dumpValue(value uint64) map[string]interface{} {
Secure: c.Secure, 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) return json.Marshal(value)
} }
type connectionType struct {
IPv6 uint32 `json:"ipv6"`
IPv4 uint32 `json:"ipv4"`
}
type traffic struct {
Ingress trafficValue `json:"ingress"`
Egress trafficValue `json:"egress"`
}
type speed struct { type speed struct {
Ingress trafficSpeedValue `json:"ingress"` ingress uint64
Egress trafficSpeedValue `json:"egress"` egress uint64
} }
type stats struct { 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"` URLs config.IPURLs `json:"urls"`
Connections connections `json:"connections"` Connections connections `json:"connections"`
Traffic traffic `json:"traffic"` Traffic traffic `json:"traffic"`
@@ -97,6 +98,78 @@ type stats struct {
Uptime uptime `json:"uptime"` Uptime uptime `json:"uptime"`
Crashes uint32 `json:"crashes"` Crashes uint32 `json:"crashes"`
speedCurrent speed previousTraffic traffic
mutex *sync.RWMutex }
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()),
}
} }
+5 -7
View File
@@ -36,7 +36,7 @@ type statsdExporter struct {
func (s *statsdExporter) run() { func (s *statsdExporter) run() {
for range time.Tick(statsdPollTime) { for range time.Tick(statsdPollTime) {
instance.mutex.Lock() instance := GetStats()
s.client.Gauge(statsdConnectionsAbridgedV4, instance.Connections.Abridged.IPv4) s.client.Gauge(statsdConnectionsAbridgedV4, instance.Connections.Abridged.IPv4)
s.client.Gauge(statsdConnectionsAbridgedV6, instance.Connections.Abridged.IPv6) s.client.Gauge(statsdConnectionsAbridgedV6, instance.Connections.Abridged.IPv6)
@@ -44,13 +44,11 @@ func (s *statsdExporter) run() {
s.client.Gauge(statsdConnectionsIntermediateV6, instance.Connections.Intermediate.IPv6) s.client.Gauge(statsdConnectionsIntermediateV6, instance.Connections.Intermediate.IPv6)
s.client.Gauge(statsdConnectionsSecureV4, instance.Connections.Secure.IPv4) s.client.Gauge(statsdConnectionsSecureV4, instance.Connections.Secure.IPv4)
s.client.Gauge(statsdConnectionsSecureV6, instance.Connections.Secure.IPv6) s.client.Gauge(statsdConnectionsSecureV6, instance.Connections.Secure.IPv6)
s.client.Gauge(statsdTrafficIngress, uint64(instance.Traffic.Ingress)) s.client.Gauge(statsdTrafficIngress, instance.Traffic.ingress)
s.client.Gauge(statsdTrafficEgress, uint64(instance.Traffic.Egress)) s.client.Gauge(statsdTrafficEgress, instance.Traffic.egress)
s.client.Gauge(statsdSpeedIngress, uint64(instance.Speed.Ingress)) s.client.Gauge(statsdSpeedIngress, instance.Speed.ingress)
s.client.Gauge(statsdSpeedEgress, uint64(instance.Speed.Egress)) s.client.Gauge(statsdSpeedEgress, instance.Speed.egress)
s.client.Gauge(statsdCrashes, instance.Crashes) s.client.Gauge(statsdCrashes, instance.Crashes)
instance.mutex.Unlock()
} }
} }
+20 -37
View File
@@ -38,13 +38,6 @@ const (
connTimeoutWrite = 2 * time.Minute connTimeoutWrite = 2 * time.Minute
) )
type ioResult struct {
n int
err error
}
type ioFunc func([]byte) (int, error)
// Conn is a basic wrapper for net.Conn providing the most low-level // Conn is a basic wrapper for net.Conn providing the most low-level
// logic and management as possible. // logic and management as possible.
type Conn struct { type Conn struct {
@@ -61,12 +54,20 @@ type Conn struct {
func (c *Conn) Write(p []byte) (int, error) { func (c *Conn) Write(p []byte) (int, error) {
select { select {
case <-c.ctx.Done(): case <-c.ctx.Done():
c.Close() // nolint: gosec
return 0, errors.Annotate(c.ctx.Err(), "Cannot write because context was closed") return 0, errors.Annotate(c.ctx.Err(), "Cannot write because context was closed")
default: default:
n, err := c.doIO(c.conn.Write, p, connTimeoutWrite) 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) c.logger.Debugw("Write to stream", "bytes", n, "error", err)
stats.EgressTraffic(n) stats.EgressTraffic(n)
if err != nil {
c.Close() // nolint: gosec
}
return n, err return n, err
} }
@@ -75,48 +76,30 @@ func (c *Conn) Write(p []byte) (int, error) {
func (c *Conn) Read(p []byte) (int, error) { func (c *Conn) Read(p []byte) (int, error) {
select { select {
case <-c.ctx.Done(): case <-c.ctx.Done():
c.Close() // nolint: gosec
return 0, errors.Annotate(c.ctx.Err(), "Cannot read because context was closed") return 0, errors.Annotate(c.ctx.Err(), "Cannot read because context was closed")
default: default:
n, err := c.doIO(c.conn.Read, p, connTimeoutRead) 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) c.logger.Debugw("Read from stream", "bytes", n, "error", err)
stats.IngressTraffic(n) stats.IngressTraffic(n)
if err != nil {
c.Close() // nolint: gosec
}
return n, err return n, err
} }
} }
func (c *Conn) doIO(callback ioFunc, p []byte, timeout time.Duration) (int, error) {
resChan := make(chan ioResult, 1)
timer := time.NewTimer(timeout)
go func() {
n, err := callback(p)
resChan <- ioResult{n: n, err: err}
}()
select {
case res := <-resChan:
timer.Stop()
if res.err != nil {
c.Close() // nolint: gosec
}
return res.n, res.err
case <-c.ctx.Done():
timer.Stop()
c.Close() // nolint: gosec
return 0, errors.Annotate(c.ctx.Err(), "Cannot do IO because context is closed")
case <-timer.C:
c.Close() // nolint: gosec
return 0, errors.Annotate(c.ctx.Err(), "Timeout on IO operation")
}
}
// Close closes underlying net.Conn instance. // Close closes underlying net.Conn instance.
func (c *Conn) Close() error { func (c *Conn) Close() error {
defer c.logger.Debugw("Close connection") c.logger.Debugw("Close connection")
c.cancel() c.cancel()
return c.conn.Close() return c.conn.Close()
} }