REPOSITORY / ScuroNeko/mtg

Compare commits

DIFF REPOSITORY

Compare commits

..
12 Commits
Author SHA1 Message Date
ScuroNeko 98aa31b5f5 removed unwanted log write
doctor logging per-ip
2026-04-20 13:45:03 +03:00
ScuroNeko f32ca9f568 gitignore 2026-04-20 13:06:03 +03:00
ScuroNeko c80d3452f0 support for test dc1-3
loading custom dc ips from config
2026-04-20 13:04:13 +03:00
Sergei ArkhipovandGitHub 9bf7222208 Merge pull request #467 from dolonet/docs/blocklist-lan-gotcha
docs: warn about firehol_level1 RFC1918 gotcha in default blocklist
2026-04-14 14:06:34 +02:00
Sergei ArkhipovandGitHub 5953f9320c Merge pull request #462 from dolonet/contrib/docker-sni-router
Add docker-compose example with HAProxy SNI router
2026-04-14 14:06:18 +02:00
dolonet 170346bb74 Pass real client IPs through with PROXY protocol v2
Without this, mtg and Caddy see HAProxy's container IP for every
connection, which breaks meaningful logging, abuse handling, and any
IP-based blocklist logic.  HAProxy sends a PROXY protocol v2 header on
its TCP backends; mtg enables proxy-protocol-listener, and Caddy wraps
:8443 with a proxy_protocol listener before tls.

The :80 path (ACME HTTP-01 passthrough) is unchanged — client IP there
is not useful and HAProxy's http mode already adds X-Forwarded-For if
anyone wants it.

Requested in https://github.com/9seconds/mtg/pull/462 review.
2026-04-13 07:58:16 +00:00
dolonet 68a4685ec6 Fix description of blocklist rejection behavior
The previous wording ("silently routed to the fronting domain")
is inaccurate. In mtglib/proxy.go the blocklist path calls
conn.Close() immediately with no further handshake or fronting;
domain fronting only happens on FakeTLS failures for non-blocked
IPs. Reword to "TCP connection is closed with no response" so
users searching the docs get the same symptom they actually see.
2026-04-13 07:46:52 +00:00
dolonet 602f85d24d Document firehol_level1 RFC1918 gotcha in blocklist defaults
The default [defense.blocklist] uses firehol_level1.netset, which
includes bogon networks and therefore all RFC1918 ranges. Clients
connecting from a LAN address (e.g. a phone on the home Wi-Fi when
mtg runs at home) are silently rejected with "ip was blacklisted"
and routed to the fronting domain. This is a recurring source of
confusion (see issue #466 for the latest example).

Add a warning next to the urls list in example.config.toml and a
Troubleshooting section in README.md covering the symptom, the
cause, and three resolution paths (disable blocklist, swap for a
narrower list, or use hairpin NAT).

Docs only, no code changes.
2026-04-13 07:08:08 +00:00
dolonet d0412b21f6 Fix ACME HTTP-01 passthrough in HAProxy config
Add an ACL that routes /.well-known/acme-challenge/ requests on :80
to Caddy instead of redirecting to HTTPS, so Let's Encrypt certificate
issuance works out of the box.

Also simplify Caddyfile to use Caddy's http_port/https_port directives.
2026-04-10 10:50:26 +00:00
dolonet 0c1d001949 Add docker-compose example with HAProxy SNI router
Turnkey deployment: HAProxy on :443 peeks at the TLS SNI and routes
Telegram clients to mtg while forwarding everything else (including DPI
probes) to a real Caddy web server with automatic HTTPS.

This is the setup recommended in BEST_PRACTICES.md, packaged so that
operators can clone and run it with minimal configuration.

Refs: #458
2026-04-10 10:12:39 +00:00
Sergei ArkhipovandGitHub d7249756e2 Merge pull request #459 from dolonet/fix/openbsd-keepalive
Fix TCP keepalive setup on OpenBSD
2026-04-10 11:47:21 +02:00
dolonet 2fa0e5ed94 Fix TCP keepalive setup on OpenBSD
Fixes #457.

OpenBSD has no user-settable per-socket TCP keepalive options:
TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT do not exist on OpenBSD,
keepalive timing is controlled system-wide via the sysctls
net.inet.tcp.keepidle and net.inet.tcp.keepintvl. Go reflects this in
src/net/tcpsockopt_openbsd.go: setKeepAliveIdle / Interval / Count
return ENOPROTOOPT for any non-negative value, and only short-circuit
to nil for negative values that explicitly mean "leave alone".

mtg builds a net.KeepAliveConfig with zero-valued Idle / Interval /
Count whenever the user does not override them in the config (which
is the default and the documented expectation). It then hands that
config to (*TCPConn).SetKeepAliveConfig in two places:

  - network/sockopts.go: applied to every connection accepted by
    internal/utils.Listener.Accept and to every server-side dial that
    goes through the v1 default network.
  - network/v2/sockopts.go: applied to every connection produced by
    the v2 network's DialContext.

On OpenBSD both calls fail with "set tcp ...: protocol not available".
The user-visible effect is that:

  - `mtg doctor` reports the error for every Telegram DC.
  - `mtg run` accepts incoming TCP connections at the kernel level but
    Listener.Accept then closes each one before the proxy server ever
    sees it, so the client appears to hang on a half-open socket and
    nothing is logged.
  - There is no configuration workaround. Setting [network]
    keep-alive.disabled = true only zeroes Enable; Go still calls
    setKeepAliveIdle / Interval / Count, which still fail.

This change extracts the keepalive setup behind an applyKeepAlive
helper that has a per-platform implementation, following the same
build-tag pattern already used for sockopts_lowat, sockopts_congestion,
sockopts_reuseaddr and sockopts_usertimeout. On every supported
platform except OpenBSD it still calls SetKeepAliveConfig and the
behaviour is unchanged. On OpenBSD it calls SetKeepAlive(cfg.Enable)
instead, which only flips SO_KEEPALIVE on or off and never touches
the missing per-socket options. OpenBSD users get the system-wide
sysctl-controlled keepalive timing, which is the only thing the
kernel exposes anyway.

Verified by cross-building (`GOOS=openbsd GOARCH=amd64 go build ./...`
and `GOARCH=arm64`) and by running `go test ./network/...` on linux.
2026-04-09 16:06:46 +00:00
20 changed files with 504 additions and 3 deletions
+2
View File
@@ -8,3 +8,5 @@
mtg mtg
coverage.txt coverage.txt
dist/ dist/
.idea/
.vscode/
+44
View File
@@ -514,6 +514,50 @@ This is not very necessary. Keep in mind these rules:
you can enable `drs` setting. you can enable `drs` setting.
9. **If you are not sure, touch nothing!** 9. **If you are not sure, touch nothing!**
## Troubleshooting
### `ip was blacklisted` for clients on the same LAN
If you run mtg at home and a client on the same LAN (for example, your
phone on the home Wi-Fi) cannot connect, check the proxy logs for a
message like:
```json
{"level":"info","ip":"10.0.1.1","logger":"proxy","message":"ip was blacklisted"}
```
The reason is that the default blocklist (`firehol_level1.netset`)
includes bogon networks, which covers all RFC1918 ranges
(`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`). Any client
connecting from such an address is rejected by the blocklist —
the TCP connection is closed immediately with no response, so
from the client's point of view nothing loads at all.
There are three ways to resolve it:
1. Disable the blocklist entirely in `config.toml`:
```toml
[defense.blocklist]
enabled = false
```
Simplest option if the proxy is used only by you and people you trust.
2. Keep the blocklist but swap `firehol_level1` for a narrower list that
does not include bogons, for example `firehol_abusers_1d`:
```toml
[defense.blocklist]
enabled = true
urls = ["https://iplists.firehol.org/files/firehol_abusers_1d.netset"]
```
3. Connect to the proxy through a public IP or domain name with hairpin
NAT (`MASQUERADE`) on your router. mtg will then see the client with
its public address and the blocklist will not match. This is more
work to set up but preserves full blocklist protection.
## Metrics ## Metrics
Out of the box, mtg works with Out of the box, mtg works with
+30
View File
@@ -0,0 +1,30 @@
{
# Caddy sits behind HAProxy which passes raw TLS through on :8443.
# ACME HTTP-01 challenges arrive on :80 via HAProxy's acl passthrough.
http_port 80
https_port 8443
# HAProxy forwards connections to :8443 with a PROXY protocol v2
# header (see haproxy.cfg `send-proxy-v2`). The proxy_protocol
# listener wrapper strips the header and exposes the real client IP
# to Caddy's access log. The `tls` wrapper must follow so that TLS
# is terminated on the unwrapped connection.
#
# `allow` lists the networks permitted to send PROXY headers. These
# ranges cover docker compose's default bridge networks; tighten
# them if you pin a specific subnet in docker-compose.yml.
servers :8443 {
listener_wrappers {
proxy_protocol {
timeout 5s
allow 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
}
tls
}
}
}
{$DOMAIN} {
root * /srv
file_server
}
+89
View File
@@ -0,0 +1,89 @@
# SNI-routing deployment for mtg
A turnkey `docker compose` setup that puts an SNI-aware TCP router
(HAProxy) in front of mtg **and** a real web server (Caddy with
automatic HTTPS).
## Why
Modern DPI systems actively probe suspected proxies. If the server
closes the connection or returns something unexpected, the IP gets
flagged. With this setup:
- **Telegram clients** connect to port 443, HAProxy sees the configured
SNI and routes them to mtg (FakeTLS).
- **Everything else** (browsers, DPI probes, scanners) is routed to
Caddy, which responds with a real Let's Encrypt certificate and serves
genuine web content.
Because your domain's DNS points to this server, the SNI/IP match is
natural and passive DPI has nothing to flag.
## Quick start
```bash
# 1. Point your domain's DNS A/AAAA record to this server's IP.
# 2. Generate an mtg secret:
docker run --rm nineseconds/mtg:2 generate-secret --hex YOUR_DOMAIN
# 3. Edit the config files:
# - mtg-config.toml → paste the secret
# - haproxy.cfg → replace "example.com" in the SNI ACL
# - .env or export → DOMAIN=your.domain
# 4. (Optional) put your site content into www/
# 5. Start:
docker compose up -d
# 6. Verify:
# - Open https://YOUR_DOMAIN in a browser → you should see the web page
# - Configure Telegram with the proxy link from:
docker compose exec mtg mtg access /config/config.toml
```
## Real client IPs (PROXY protocol)
HAProxy forwards TCP connections to mtg and Caddy with a PROXY protocol
v2 header so both backends see the real client IP instead of HAProxy's
container address. The three pieces must stay in sync:
- `haproxy.cfg``send-proxy-v2` on the `mtg` and `web` backend `server` lines
- `mtg-config.toml``proxy-protocol-listener = true`
- `Caddyfile``listener_wrappers { proxy_protocol { ... } tls }` on `:8443`
If you disable one, disable all three, otherwise the backend will fail
to parse the connection.
## ACME (Let's Encrypt) notes
HAProxy passes `/.well-known/acme-challenge/` requests on `:80` to
Caddy so that HTTP-01 validation works out of the box. Make sure your
domain's DNS A/AAAA record points to this server before starting.
## Architecture
```
┌──────────────────┐
:443 ──────>│ HAProxy │
│ (TCP, SNI peek) │
└──┬───────────┬───┘
SNI match │ │ default
v v
┌─────────┐ ┌─────────┐
│ mtg │ │ Caddy │
│ :3128 │ │ :8443 │
│ FakeTLS │ │ real TLS│
└─────────┘ └─────────┘
```
## Files
| File | Purpose |
|---|---|
| `docker-compose.yml` | Service definitions |
| `haproxy.cfg` | SNI routing rules — **edit the domain** |
| `mtg-config.toml` | mtg proxy config — **paste your secret** |
| `Caddyfile` | Web server config (auto-HTTPS) |
| `www/` | Static site content served by Caddy |
+54
View File
@@ -0,0 +1,54 @@
# SNI-routing deployment: HAProxy (443) -> mtg + real web backend
#
# This setup puts an SNI-aware TCP router in front of mtg so that:
# - Telegram clients (FakeTLS with the correct SNI) are routed to mtg
# - All other TLS traffic (including DPI probes) reaches the real web
# server, which responds with a genuine certificate
#
# The result: active probes see a real website; passive DPI sees matching
# SNI/IP because the domain resolves to this server's IP.
#
# Quick start:
# 1. Set YOUR_DOMAIN below (and in mtg-config.toml)
# 2. docker compose up -d
# 3. mtg generate-secret YOUR_DOMAIN -> put it in mtg-config.toml
# 4. docker compose restart mtg
#
# See BEST_PRACTICES.md and the project wiki for background.
services:
haproxy:
image: haproxy:lts-alpine
ports:
- "443:443"
- "80:80"
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
depends_on:
- mtg
- web
restart: unless-stopped
mtg:
image: nineseconds/mtg:2
volumes:
- ./mtg-config.toml:/config/config.toml:ro
expose:
- "3128"
restart: unless-stopped
web:
image: caddy:alpine
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- ./www:/srv:ro
expose:
- "80"
- "8443"
environment:
DOMAIN: ${DOMAIN:-example.com}
restart: unless-stopped
volumes:
caddy_data:
+62
View File
@@ -0,0 +1,62 @@
# HAProxy SNI router — Layer 4 (TCP mode)
#
# Inspects the SNI in the TLS ClientHello and routes traffic:
# - SNI matching the mtg secret domain -> mtg (FakeTLS / MTProto)
# - Everything else -> real web backend (Caddy)
#
# Because routing happens before TLS termination, each backend sees the
# raw ClientHello and handles TLS itself. The real web backend therefore
# presents a genuine certificate to any probe or browser.
global
log stdout format raw local0 info
maxconn 4096
defaults
log global
mode tcp
option tcplog
timeout connect 5s
timeout client 60s
timeout server 60s
# --- HTTP :80 — ACME challenges + redirect -----------------------------------
frontend http
bind *:80
mode http
# Let Caddy answer ACME HTTP-01 challenges for Let's Encrypt.
acl is_acme path_beg /.well-known/acme-challenge/
use_backend web_acme if is_acme
http-request redirect scheme https code 301
# --- TLS :443 — SNI-based routing -------------------------------------------
frontend tls
bind *:443
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
# Route Telegram clients to mtg.
# Replace "example.com" with the domain from your mtg secret.
use_backend mtg if { req_ssl_sni -i example.com }
default_backend web
backend mtg
# send-proxy-v2 prepends a PROXY protocol v2 header so mtg sees the
# real client IP instead of HAProxy's. mtg must have
# `proxy-protocol-listener = true` in its config.
server mtg mtg:3128 send-proxy-v2
backend web
# send-proxy-v2 prepends a PROXY protocol v2 header so Caddy logs the
# real client IP instead of HAProxy's. Caddy must enable the
# proxy_protocol listener wrapper on :8443 (see Caddyfile).
server web web:8443 send-proxy-v2
backend web_acme
mode http
server web web:80
+17
View File
@@ -0,0 +1,17 @@
# Minimal mtg configuration for the SNI-router setup.
#
# 1. Generate a secret: mtg generate-secret --hex example.com
# 2. Paste it below.
# 3. Replace example.com with your actual domain everywhere.
secret = "PASTE_YOUR_SECRET_HERE"
bind-to = "0.0.0.0:3128"
# HAProxy in front sends PROXY protocol v2 headers so mtg can see the
# real client IP. Keep this in sync with haproxy.cfg (`send-proxy-v2`).
proxy-protocol-listener = true
[defense.anti-replay]
enabled = true
max-size = "1mib"
error-rate = 0.001
+5
View File
@@ -0,0 +1,5 @@
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Welcome</title></head>
<body><h1>It works!</h1><p>Replace this with your own content.</p></body>
</html>
+13 -1
View File
@@ -25,6 +25,18 @@ var TelegramCoreAddresses = map[int][]string{
}, },
203: { 203: {
"91.105.192.100:443", "91.105.192.100:443",
"[2a0a:f280:0203:000a:5000:0000:0000:0100]:443", "[2a0a:f280:0203:a:5000::100]:443",
},
10001: {
"149.154.175.10:443",
"[2001:b28:f23d:f001::e]:443",
},
10002: {
"149.154.167.40:443",
"[2001:67c:4e8:f002::e]:443",
},
10003: {
"149.154.175.117:443",
"[2001:b28:f23d:f003::e]:443",
}, },
} }
+19
View File
@@ -316,6 +316,17 @@ download-concurrency = 2
# A list of URLs in FireHOL format (https://iplists.firehol.org/) # A list of URLs in FireHOL format (https://iplists.firehol.org/)
# You can provider links here (starts with https:// or http://) or # You can provider links here (starts with https:// or http://) or
# path to a local file, but in this case it should be absolute. # path to a local file, but in this case it should be absolute.
#
# NOTE: the default list below (firehol_level1.netset) includes bogon
# networks, and therefore RFC1918 ranges as well (10.0.0.0/8,
# 172.16.0.0/12, 192.168.0.0/16). If you run mtg on a home/LAN network
# and connect from a client on the same LAN, that client will be
# rejected with "ip was blacklisted" and the connection dropped (TCP
# close, no response). If you see this, you can either disable this section
# (enabled = false), replace firehol_level1 with a narrower list that
# does not include bogons (e.g. firehol_abusers_1d), or connect via
# a public IP/domain with hairpin NAT on your router. See README for
# details.
urls = [ urls = [
"https://iplists.firehol.org/files/firehol_level1.netset", "https://iplists.firehol.org/files/firehol_level1.netset",
# "/local.file" # "/local.file"
@@ -367,3 +378,11 @@ bind-to = "127.0.0.1:3129"
http-path = "/" http-path = "/"
# prefix for metrics for prometheus # prefix for metrics for prometheus
metric-prefix = "mtg" metric-prefix = "mtg"
[dc]
dc1 = [
"149.154.175.50:443",
"149.154.175.54:443",
"149.154.175.59:443",
"[2001:b28:f23d:f001::a]:443"
]
+13
View File
@@ -50,6 +50,12 @@ var (
tplEDCConnect = template.Must( tplEDCConnect = template.Must(
template.New("").Parse(" ❌ DC {{ .dc }}: {{ .error }}\n"), template.New("").Parse(" ❌ DC {{ .dc }}: {{ .error }}\n"),
) )
tplODCIPConnect = template.Must(
template.New("").Parse(" ✅ {{ .ip }}\n"),
)
tplEDCIPConnect = template.Must(
template.New("").Parse(" ❌ {{ .ip }}: {{ .error }}\n"),
)
tplODNSSNIMatch = template.Must( tplODNSSNIMatch = template.Must(
template.New("").Parse(" ✅ IP address {{ .ip }} matches secret hostname {{ .hostname }}\n"), template.New("").Parse(" ✅ IP address {{ .ip }} matches secret hostname {{ .hostname }}\n"),
@@ -285,8 +291,15 @@ func (d *Doctor) checkNetworkAddresses(ntw mtglib.Network, addresses []string) e
for _, addr := range checkAddresses { for _, addr := range checkAddresses {
conn, err = ntw.DialContext(ctx, "tcp", addr) conn, err = ntw.DialContext(ctx, "tcp", addr)
if err != nil { if err != nil {
tplEDCIPConnect.Execute(os.Stdout, map[string]any{
"error": err,
"ip": addr,
})
continue continue
} }
tplODCIPConnect.Execute(os.Stdout, map[string]any{
"ip": addr,
})
conn.Close() //nolint: errcheck conn.Close() //nolint: errcheck
+11
View File
@@ -91,6 +91,17 @@ type Config struct {
MetricPrefix TypeMetricPrefix `json:"metricPrefix"` MetricPrefix TypeMetricPrefix `json:"metricPrefix"`
} `json:"prometheus"` } `json:"prometheus"`
} `json:"stats"` } `json:"stats"`
DCs struct{
DC1 []TypeHostPort `json:"dc1"`
DC2 []TypeHostPort `json:"dc2"`
DC3 []TypeHostPort `json:"dc3"`
DC4 []TypeHostPort `json:"dc4"`
DC5 []TypeHostPort `json:"dc5"`
DC203 []TypeHostPort `json:"dc203"`
DC10001 []TypeHostPort `json:"dc10001"`
DC10002[]TypeHostPort `json:"dc10002"`
DC10003 []TypeHostPort `json:"dc10003"`
} `json:"dc"`
} }
func (c *Config) GetConcurrency(defaultValue uint) uint { func (c *Config) GetConcurrency(defaultValue uint) uint {
+11
View File
@@ -84,6 +84,17 @@ type tomlConfig struct {
MetricPrefix string `toml:"metric-prefix" json:"metricPrefix,omitempty"` MetricPrefix string `toml:"metric-prefix" json:"metricPrefix,omitempty"`
} `toml:"prometheus" json:"prometheus,omitempty"` } `toml:"prometheus" json:"prometheus,omitempty"`
} `toml:"stats" json:"stats,omitempty"` } `toml:"stats" json:"stats,omitempty"`
DCs struct{
DC1 []string `toml:"dc1" json:"dc1,omitempty"`
DC2 []string `toml:"dc2" json:"dc2,omitempty"`
DC3 []string `toml:"dc3" json:"dc3,omitempty"`
DC4 []string `toml:"dc4" json:"dc4,omitempty"`
DC5 []string `toml:"dc5" json:"dc5,omitempty"`
DC203 []string `toml:"dc203" json:"dc203,omitempty"`
DC10001 []string `toml:"dc10001" json:"dc10001,omitempty"`
DC10002 []string `toml:"dc10002" json:"dc10002,omitempty"`
DC10003 []string `toml:"dc10003" json:"dc10003,omitempty"`
} `toml:"dc" json:"dc,omitempty"`
} }
func Parse(rawData []byte) (*Config, error) { func Parse(rawData []byte) (*Config, error) {
+70
View File
@@ -4,9 +4,77 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/9seconds/mtg/v2/essentials"
"github.com/9seconds/mtg/v2/internal/config" "github.com/9seconds/mtg/v2/internal/config"
) )
func readCustomDC(cfg *config.Config) {
if len(cfg.DCs.DC1) > 0 {
var ips []string
for _, addr := range cfg.DCs.DC1 {
ips = append(ips, addr.Value)
}
essentials.TelegramCoreAddresses[1] = ips
}
if len(cfg.DCs.DC2) > 0 {
var ips []string
for _, addr := range cfg.DCs.DC2 {
ips = append(ips, addr.Value)
}
essentials.TelegramCoreAddresses[2] = ips
}
if len(cfg.DCs.DC3) > 0 {
var ips []string
for _, addr := range cfg.DCs.DC3 {
ips = append(ips, addr.Value)
}
essentials.TelegramCoreAddresses[3] = ips
}
if len(cfg.DCs.DC4) > 0 {
var ips []string
for _, addr := range cfg.DCs.DC4 {
ips = append(ips, addr.Value)
}
essentials.TelegramCoreAddresses[4] = ips
}
if len(cfg.DCs.DC5) > 0 {
var ips []string
for _, addr := range cfg.DCs.DC5 {
ips = append(ips, addr.Value)
}
essentials.TelegramCoreAddresses[5] = ips
}
if len(cfg.DCs.DC203) > 0 {
var ips []string
for _, addr := range cfg.DCs.DC203 {
ips = append(ips, addr.Value)
}
essentials.TelegramCoreAddresses[203] = ips
}
if len(cfg.DCs.DC10001) > 0 {
var ips []string
for _, addr := range cfg.DCs.DC10001 {
ips = append(ips, addr.Value)
}
essentials.TelegramCoreAddresses[10001] = ips
}
if len(cfg.DCs.DC10002) > 0 {
var ips []string
for _, addr := range cfg.DCs.DC10002 {
ips = append(ips, addr.Value)
}
essentials.TelegramCoreAddresses[10002] = ips
}
if len(cfg.DCs.DC10003) > 0 {
var ips []string
for _, addr := range cfg.DCs.DC10003 {
ips = append(ips, addr.Value)
}
essentials.TelegramCoreAddresses[10003] = ips
}
}
func ReadConfig(path string) (*config.Config, error) { func ReadConfig(path string) (*config.Config, error) {
content, err := os.ReadFile(path) content, err := os.ReadFile(path)
if err != nil { if err != nil {
@@ -22,5 +90,7 @@ func ReadConfig(path string) (*config.Config, error) {
return nil, fmt.Errorf("invalid config: %w", err) return nil, fmt.Errorf("invalid config: %w", err)
} }
readCustomDC(conf)
return conf, nil return conf, nil
} }
+1 -1
View File
@@ -20,7 +20,7 @@ func SetServerSocketOptions(conn net.Conn, bufferSize int) error {
} }
func setCommonSocketOptions(conn *net.TCPConn) error { func setCommonSocketOptions(conn *net.TCPConn) error {
if err := conn.SetKeepAliveConfig(net.KeepAliveConfig{ if err := applyKeepAlive(conn, net.KeepAliveConfig{
Enable: true, Enable: true,
Idle: DefaultKeepAliveIdle, Idle: DefaultKeepAliveIdle,
Interval: DefaultKeepAliveInterval, Interval: DefaultKeepAliveInterval,
+11
View File
@@ -0,0 +1,11 @@
//go:build !openbsd
package network
import "net"
// applyKeepAlive enables TCP keepalive on conn and applies the per-socket
// idle/interval/count tuning from cfg.
func applyKeepAlive(conn *net.TCPConn, cfg net.KeepAliveConfig) error {
return conn.SetKeepAliveConfig(cfg) //nolint: wrapcheck
}
+20
View File
@@ -0,0 +1,20 @@
package network
import "net"
// applyKeepAlive enables (or disables) TCP keepalive on conn.
//
// OpenBSD has no user-settable per-socket TCP keepalive options: TCP_KEEPIDLE,
// TCP_KEEPINTVL and TCP_KEEPCNT do not exist on OpenBSD, and Go's
// (*TCPConn).SetKeepAliveConfig therefore returns ENOPROTOOPT ("protocol not
// available") for any non-negative Idle/Interval/Count value (see
// src/net/tcpsockopt_openbsd.go in the Go source tree). Calling
// SetKeepAliveConfig with mtg's defaults (zero values) breaks every accepted
// listener connection and every outbound dial on OpenBSD.
//
// On OpenBSD we only flip SO_KEEPALIVE on or off; the keepalive timing is
// controlled system-wide via the sysctl knobs net.inet.tcp.keepidle and
// net.inet.tcp.keepintvl.
func applyKeepAlive(conn *net.TCPConn, cfg net.KeepAliveConfig) error {
return conn.SetKeepAlive(cfg.Enable) //nolint: wrapcheck
}
+1 -1
View File
@@ -6,7 +6,7 @@ import (
) )
func setCommonSocketOptions(conn *net.TCPConn, keepAliveConfig net.KeepAliveConfig) error { func setCommonSocketOptions(conn *net.TCPConn, keepAliveConfig net.KeepAliveConfig) error {
if err := conn.SetKeepAliveConfig(keepAliveConfig); err != nil { if err := applyKeepAlive(conn, keepAliveConfig); err != nil {
return fmt.Errorf("cannot configure TCP keepalive: %w", err) return fmt.Errorf("cannot configure TCP keepalive: %w", err)
} }
+11
View File
@@ -0,0 +1,11 @@
//go:build !openbsd
package network
import "net"
// applyKeepAlive enables TCP keepalive on conn and applies the per-socket
// idle/interval/count tuning from cfg.
func applyKeepAlive(conn *net.TCPConn, cfg net.KeepAliveConfig) error {
return conn.SetKeepAliveConfig(cfg) //nolint: wrapcheck
}
+20
View File
@@ -0,0 +1,20 @@
package network
import "net"
// applyKeepAlive enables (or disables) TCP keepalive on conn.
//
// OpenBSD has no user-settable per-socket TCP keepalive options: TCP_KEEPIDLE,
// TCP_KEEPINTVL and TCP_KEEPCNT do not exist on OpenBSD, and Go's
// (*TCPConn).SetKeepAliveConfig therefore returns ENOPROTOOPT ("protocol not
// available") for any non-negative Idle/Interval/Count value (see
// src/net/tcpsockopt_openbsd.go in the Go source tree). Calling
// SetKeepAliveConfig with mtg's defaults (zero values) breaks every accepted
// listener connection and every outbound dial on OpenBSD.
//
// On OpenBSD we only flip SO_KEEPALIVE on or off; the keepalive timing is
// controlled system-wide via the sysctl knobs net.inet.tcp.keepidle and
// net.inet.tcp.keepintvl.
func applyKeepAlive(conn *net.TCPConn, cfg net.KeepAliveConfig) error {
return conn.SetKeepAlive(cfg.Enable) //nolint: wrapcheck
}