From 251659145364f628a0dd915a522ad322f5ef10b2 Mon Sep 17 00:00:00 2001 From: Mike Kolganov Date: Thu, 2 Apr 2026 02:11:39 +0600 Subject: [PATCH] feat: add --interface flag to bind connections to a specific network interface Adds -interface flag that uses SO_BINDTODEVICE to bind all outgoing HTTP connections to a specified network interface (e.g. tun0, eth1). Linux only; on other platforms the flag is accepted but has no effect. --- .gitignore | 1 + README.md | 1 + go.mod | 2 +- main.go | 2 ++ pkg/yandex/client.go | 12 ++++++++---- pkg/yandex/client_linux.go | 35 +++++++++++++++++++++++++++++++++++ pkg/yandex/client_other.go | 9 +++++++++ 7 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 .gitignore create mode 100644 pkg/yandex/client_linux.go create mode 100644 pkg/yandex/client_other.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08cb523 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +go.sum diff --git a/README.md b/README.md index e0b1b8d..bf55648 100644 --- a/README.md +++ b/README.md @@ -39,3 +39,4 @@ go build -o internetometer main.go - `--save log.jsonl`: Сохранить результат в лог-файл. - `--prometheus`: Вывод в формате метрик Prometheus. - `--concurrency 4`: Количество параллельных потоков. +- `--interface tun0`: Привязать исходящие соединения к указанному сетевому интерфейсу (использует `SO_BINDTODEVICE`, только Linux). diff --git a/go.mod b/go.mod index b1f38dd..b8b744f 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/charmbracelet/bubbles v1.0.0 github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/lipgloss v1.1.0 + golang.org/x/sys v0.38.0 ) require ( @@ -28,6 +29,5 @@ require ( github.com/muesli/termenv v0.16.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.3.8 // indirect ) diff --git a/main.go b/main.go index df30843..0e3e231 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,7 @@ func main() { prometheus := flag.Bool("prometheus", false, "Output results in Prometheus metrics format") useTUI := flag.Bool("tui", false, "Use interactive TUI for progress") timeout := flag.Duration("timeout", 60*time.Second, "Timeout for the entire operation") + iface := flag.String("interface", "", "Bind outgoing connections to this network interface, e.g. tun0 (uses SO_BINDTODEVICE)") flag.Parse() @@ -34,6 +35,7 @@ func main() { Timeout: *timeout, Language: *lang, Concurrency: *concurrency, + Interface: *iface, }) if *useTUI { diff --git a/pkg/yandex/client.go b/pkg/yandex/client.go index 4b1a4a9..5fb7aeb 100644 --- a/pkg/yandex/client.go +++ b/pkg/yandex/client.go @@ -14,6 +14,7 @@ type Config struct { Timeout time.Duration Language string // "ru" or "en" Concurrency int + Interface string // bind outgoing connections to this network interface (SO_BINDTODEVICE, Linux only) } type Client struct { @@ -37,11 +38,14 @@ func NewClient(cfg *Config) *Client { cfg.Concurrency = 4 } + httpClient := &http.Client{Timeout: cfg.Timeout} + if t := interfaceTransport(cfg.Interface); t != nil { + httpClient.Transport = t + } + return &Client{ - httpClient: &http.Client{ - Timeout: cfg.Timeout, - }, - config: cfg, + httpClient: httpClient, + config: cfg, } } diff --git a/pkg/yandex/client_linux.go b/pkg/yandex/client_linux.go new file mode 100644 index 0000000..7d489e3 --- /dev/null +++ b/pkg/yandex/client_linux.go @@ -0,0 +1,35 @@ +//go:build linux + +package yandex + +import ( + "net" + "net/http" + "syscall" + "time" + + "golang.org/x/sys/unix" +) + +func interfaceTransport(iface string) http.RoundTripper { + if iface == "" { + return nil + } + dialer := &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + Control: func(network, address string, c syscall.RawConn) error { + var sockErr error + err := c.Control(func(fd uintptr) { + sockErr = unix.SetsockoptString(int(fd), unix.SOL_SOCKET, unix.SO_BINDTODEVICE, iface) + }) + if err != nil { + return err + } + return sockErr + }, + } + return &http.Transport{ + DialContext: dialer.DialContext, + } +} diff --git a/pkg/yandex/client_other.go b/pkg/yandex/client_other.go new file mode 100644 index 0000000..187a1db --- /dev/null +++ b/pkg/yandex/client_other.go @@ -0,0 +1,9 @@ +//go:build !linux + +package yandex + +import "net/http" + +func interfaceTransport(_ string) http.RoundTripper { + return nil +}