Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go.sum
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ go build -o internetometer main.go
- `--save log.jsonl`: Сохранить результат в лог-файл.
- `--prometheus`: Вывод в формате метрик Prometheus.
- `--concurrency 4`: Количество параллельных потоков.
- `--interface tun0`: Привязать исходящие соединения к указанному сетевому интерфейсу (использует `SO_BINDTODEVICE`, только Linux).
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
)
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -34,6 +35,7 @@ func main() {
Timeout: *timeout,
Language: *lang,
Concurrency: *concurrency,
Interface: *iface,
})

if *useTUI {
Expand Down
12 changes: 8 additions & 4 deletions pkg/yandex/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
}
}

Expand Down
35 changes: 35 additions & 0 deletions pkg/yandex/client_linux.go
Original file line number Diff line number Diff line change
@@ -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,
}
}
9 changes: 9 additions & 0 deletions pkg/yandex/client_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//go:build !linux

package yandex

import "net/http"

func interfaceTransport(_ string) http.RoundTripper {
return nil
}