Enhance MTProxy checker by introducing a new -probe mode deep-strict for stricter response validation. Update README and Docker documentation to clarify probing modes, exit codes, and the new MTPROXY_TDLIB_HELPER and MTPROXY_TDLIB_TIMEOUT environment variables for external helper integration. Refactor connection handling and error reporting to improve robustness and clarity in response verification.
Publish mtproxy_checker Docker image / test (push) Successful in 7s
Publish mtproxy_checker Docker image / build-and-push (push) Successful in 53s

This commit is contained in:
Denozordec
2026-04-11 16:12:45 +07:00
parent 24a7315a16
commit 388784eb5e
17 changed files with 902 additions and 24 deletions
+50 -6
View File
@@ -39,6 +39,8 @@ type config struct {
dcIDs []int16
probe checker.ProbeMode
allowedPrefixes []netip.Prefix
tdlibHelper string
tdlibTimeout time.Duration
}
func loadConfig() (*config, error) {
@@ -90,6 +92,15 @@ func loadConfig() (*config, error) {
return nil, err
}
probe := checker.ParseProbe(os.Getenv("MTPROXY_PROBE"))
tdlibHelper := strings.TrimSpace(os.Getenv("MTPROXY_TDLIB_HELPER"))
tdlibTimeoutStr := strings.TrimSpace(os.Getenv("MTPROXY_TDLIB_TIMEOUT"))
if tdlibTimeoutStr == "" {
tdlibTimeoutStr = "45s"
}
tdlibTimeout, err := time.ParseDuration(tdlibTimeoutStr)
if err != nil || tdlibTimeout <= 0 {
return nil, fmt.Errorf("MTPROXY_TDLIB_TIMEOUT: invalid duration %q", tdlibTimeoutStr)
}
return &config{
listFile: listFile,
checkInterval: interval,
@@ -98,6 +109,8 @@ func loadConfig() (*config, error) {
dcIDs: dcIDs,
probe: probe,
allowedPrefixes: prefixes,
tdlibHelper: tdlibHelper,
tdlibTimeout: tdlibTimeout,
}, nil
}
@@ -108,14 +121,32 @@ type dcProbeResult struct {
Error string `json:"error,omitempty"`
}
type standardProbe struct {
OK bool `json:"ok"`
ExitCode int `json:"exit_code"`
Error string `json:"error,omitempty"`
ParseError string `json:"parse_error,omitempty"`
}
type tdlibProbe struct {
SkippedReason string `json:"skipped_reason,omitempty"`
Ran bool `json:"ran"`
OK bool `json:"ok"`
ExitCode int `json:"exit_code,omitempty"`
Error string `json:"error,omitempty"`
DurationMs int64 `json:"duration_ms,omitempty"`
}
type proxyEntry struct {
RawLine string `json:"raw_line"`
URL string `json:"url,omitempty"`
CheckedAt string `json:"checked_at,omitempty"`
Standard standardProbe `json:"standard"`
TDLib tdlibProbe `json:"tdlib"`
OK bool `json:"ok"`
ExitCode int `json:"exit_code"`
Error string `json:"error,omitempty"`
ParseError string `json:"parse_error,omitempty"`
CheckedAt string `json:"checked_at,omitempty"`
DCs []dcProbeResult `json:"dcs,omitempty"`
}
@@ -185,9 +216,13 @@ func aggregateExitFromDCs(dcs []dcProbeResult) int {
return 1
}
func checkOneLine(ctx context.Context, line string, dcIDs []int16, perDCTimeout time.Duration, probe checker.ProbeMode) proxyEntry {
func checkOneLine(ctx context.Context, line string, dcIDs []int16, perDCTimeout time.Duration, probe checker.ProbeMode, tdlibHelper string, tdlibTimeout time.Duration) (ent proxyEntry) {
now := time.Now().UTC().Format(time.RFC3339)
ent := proxyEntry{RawLine: line, CheckedAt: now}
ent = proxyEntry{RawLine: line, CheckedAt: now}
defer func() {
ent.Standard = standardProbe{OK: ent.OK, ExitCode: ent.ExitCode, Error: ent.Error, ParseError: ent.ParseError}
ent.TDLib = runTDLibBlock(line, tdlibHelper, tdlibTimeout, probe, ent)
}()
if len(dcIDs) == 0 {
ent.ExitCode = 2
ent.Error = "no DC ids configured"
@@ -210,8 +245,8 @@ func checkOneLine(ctx context.Context, line string, dcIDs []int16, perDCTimeout
}
if len(dcIDs) == 1 {
dcCtx, cancel := context.WithTimeout(ctx, perDCTimeout)
defer cancel()
err = checker.Check(dcCtx, t.Host, t.Port, parsed, dcIDs[0], &checker.Options{Probe: probe})
cancel()
code, msg := checkresult.Classify(err)
ent.ExitCode = code
ent.OK = err == nil
@@ -262,6 +297,8 @@ func runCycle(cfg *config, st *store) {
ParseError: err.Error(),
Error: err.Error(),
CheckedAt: finished.Format(time.RFC3339),
Standard: standardProbe{OK: false, ExitCode: 2, Error: err.Error(), ParseError: err.Error()},
TDLib: tdlibProbe{SkippedReason: "cycle aborted (list file error)", Ran: false},
}}, finished, time.Now().Add(cfg.checkInterval))
log.Printf("read list file: %v", err)
return
@@ -272,8 +309,11 @@ func runCycle(cfg *config, st *store) {
if len(cfg.dcIDs) > 1 {
total = cfg.checkTimeout * time.Duration(len(cfg.dcIDs))
}
if cfg.probe == checker.ProbeFast && strings.TrimSpace(cfg.tdlibHelper) != "" {
total += cfg.tdlibTimeout
}
ctx, cancel := context.WithTimeout(context.Background(), total)
ent := checkOneLine(ctx, line, cfg.dcIDs, cfg.checkTimeout, cfg.probe)
ent := checkOneLine(ctx, line, cfg.dcIDs, cfg.checkTimeout, cfg.probe, cfg.tdlibHelper, cfg.tdlibTimeout)
cancel()
entries = append(entries, ent)
}
@@ -353,7 +393,11 @@ func run() error {
errCh := make(chan error, 1)
go func() {
log.Printf("listening on %s, list=%s interval=%s dcs=%v", cfg.httpAddr, cfg.listFile, cfg.checkInterval, cfg.dcIDs)
if cfg.tdlibHelper != "" {
log.Printf("listening on %s, list=%s interval=%s dcs=%v tdlib_helper=%s tdlib_timeout=%s", cfg.httpAddr, cfg.listFile, cfg.checkInterval, cfg.dcIDs, cfg.tdlibHelper, cfg.tdlibTimeout)
} else {
log.Printf("listening on %s, list=%s interval=%s dcs=%v", cfg.httpAddr, cfg.listFile, cfg.checkInterval, cfg.dcIDs)
}
errCh <- srv.ListenAndServe()
}()
+33
View File
@@ -0,0 +1,33 @@
package main
import (
"context"
"errors"
"strings"
"time"
"mtproxy_checker/internal/checker"
"mtproxy_checker/internal/tdlibexec"
)
func runTDLibBlock(proxyLine, helper string, timeout time.Duration, probe checker.ProbeMode, ent proxyEntry) tdlibProbe {
if probe != checker.ProbeFast {
return tdlibProbe{SkippedReason: "tdlib runs only with probe fast (or empty MTPROXY_PROBE)", Ran: false}
}
if ent.ParseError != "" {
return tdlibProbe{SkippedReason: "skipped: standard parse error", Ran: false}
}
if strings.TrimSpace(helper) == "" {
return tdlibProbe{SkippedReason: "MTPROXY_TDLIB_HELPER not set", Ran: false}
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
res, err := tdlibexec.Run(ctx, helper, proxyLine)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
return tdlibProbe{Ran: true, OK: false, ExitCode: 4, Error: err.Error(), DurationMs: res.DurationMs}
}
return tdlibProbe{Ran: true, OK: false, ExitCode: 1, Error: err.Error(), DurationMs: res.DurationMs}
}
return tdlibProbe{Ran: true, OK: res.OK, ExitCode: res.ExitCode, Error: res.Error, DurationMs: res.DurationMs}
}