mirror of
https://github.com/hackdiaz-dev/free-turn-proxy.git
synced 2026-10-12 09:41:53 +02:00
fix(lint): устранить замечания golangci-lint + переход на dockers_v2
Проход lint (52 → 0): - vkauth/token: переименовать затенённый err (govet shadow x3) - udprelay/loop: явный _ = conn.Close() (errcheck x2) - captcha/manual: ReadHeaderTimeout + html.EscapeString (G112, G705) - proxy/tcpfwd: хелпер nonNegUint64 для байтовых счётчиков io.Copy (G115) - proxy/bondclient, wire/bondframe: bounds-проверки на len->uint16/uint32 - wire/srtpmimicry: задокументирована усечка RTP seq mod 2^16 - dnsdial/doh: bounds-проверка + binary.BigEndian.PutUint16 - captcha/slider: clamp luma до 255 перед конверсией uint8 - vkauth/types: обоснованный nolint для публичных учёток VK SDK Новый пакет internal/randx — Intn/IntN/Float64 на crypto/rand, drop-in замена math/rand. Мигрированы все места antifingerprint-jitter (~22) в captcha, slider, namegen, vkauth, udprelay. GoReleaser: - bump GORELEASER_VERSION v2.5.0 → v2.15.4 в Taskfile и обоих CI-workflow; текущий синтаксис .goreleaser.yaml (archives.ids, format_overrides.formats) требует v2.8+ - миграция dockers + docker_manifests → dockers_v2 (единый блок, multi-platform buildx); Dockerfile.goreleaser использует конвенцию ARG TARGETPLATFORM
This commit is contained in:
@@ -17,7 +17,7 @@ env:
|
||||
GO_VERSION: "1.25.0"
|
||||
GOLANGCI_VERSION: v2.12.2
|
||||
GOVULN_VERSION: v1.1.4
|
||||
GORELEASER_VERSION: v2.5.0
|
||||
GORELEASER_VERSION: v2.15.4
|
||||
TASK_VERSION: v3.40.0
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -11,7 +11,7 @@ permissions:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.25.0"
|
||||
GORELEASER_VERSION: v2.5.0
|
||||
GORELEASER_VERSION: v2.15.4
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.idea
|
||||
.claude
|
||||
CLAUDE.md
|
||||
*.exe
|
||||
*.so
|
||||
*.tmp
|
||||
|
||||
+15
-43
@@ -149,51 +149,23 @@ release:
|
||||
draft: false
|
||||
name_template: "{{ .ProjectName }} {{ .Version }}"
|
||||
|
||||
dockers:
|
||||
- id: server-amd64
|
||||
dockers_v2:
|
||||
- id: server
|
||||
ids:
|
||||
- server
|
||||
image_templates:
|
||||
- "ghcr.io/samosvalishe/btp:{{ .Version }}-amd64"
|
||||
dockerfile: Dockerfile.goreleaser
|
||||
use: buildx
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
build_flag_templates:
|
||||
- "--platform=linux/amd64"
|
||||
- "--label=org.opencontainers.image.source=https://github.com/samosvalishe/btp"
|
||||
- "--label=org.opencontainers.image.version={{ .Version }}"
|
||||
- "--label=org.opencontainers.image.revision={{ .FullCommit }}"
|
||||
- "--label=org.opencontainers.image.created={{ .Date }}"
|
||||
images:
|
||||
- ghcr.io/samosvalishe/btp
|
||||
tags:
|
||||
- "{{ .Version }}"
|
||||
- '{{ if not .Prerelease }}latest{{ end }}'
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
labels:
|
||||
org.opencontainers.image.source: https://github.com/samosvalishe/btp
|
||||
org.opencontainers.image.version: "{{ .Version }}"
|
||||
org.opencontainers.image.revision: "{{ .FullCommit }}"
|
||||
org.opencontainers.image.created: "{{ .Date }}"
|
||||
extra_files:
|
||||
- docker/entrypoint.sh
|
||||
|
||||
- id: server-arm64
|
||||
ids:
|
||||
- server
|
||||
image_templates:
|
||||
- "ghcr.io/samosvalishe/btp:{{ .Version }}-arm64"
|
||||
dockerfile: Dockerfile.goreleaser
|
||||
use: buildx
|
||||
goos: linux
|
||||
goarch: arm64
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm64"
|
||||
- "--label=org.opencontainers.image.source=https://github.com/samosvalishe/btp"
|
||||
- "--label=org.opencontainers.image.version={{ .Version }}"
|
||||
- "--label=org.opencontainers.image.revision={{ .FullCommit }}"
|
||||
- "--label=org.opencontainers.image.created={{ .Date }}"
|
||||
extra_files:
|
||||
- docker/entrypoint.sh
|
||||
|
||||
docker_manifests:
|
||||
- name_template: "ghcr.io/samosvalishe/btp:{{ .Version }}"
|
||||
image_templates:
|
||||
- "ghcr.io/samosvalishe/btp:{{ .Version }}-amd64"
|
||||
- "ghcr.io/samosvalishe/btp:{{ .Version }}-arm64"
|
||||
|
||||
- name_template: "ghcr.io/samosvalishe/btp:latest"
|
||||
skip_push: "{{ .Prerelease }}"
|
||||
image_templates:
|
||||
- "ghcr.io/samosvalishe/btp:{{ .Version }}-amd64"
|
||||
- "ghcr.io/samosvalishe/btp:{{ .Version }}-arm64"
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
# Thin runtime image. Built by GoReleaser using a pre-compiled server binary.
|
||||
# See .goreleaser.yaml (dockers section) for the build context.
|
||||
# See .goreleaser.yaml (dockers_v2 section) for the build context.
|
||||
FROM alpine:3.23
|
||||
|
||||
RUN apk add --no-cache ca-certificates tzdata
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# GoReleaser places the server binary at the build root, and any files
|
||||
# listed in `extra_files` (entrypoint.sh) are also placed in the context root.
|
||||
COPY server /app/server
|
||||
# GoReleaser dockers_v2 buildx-builds multi-platform images; per-platform
|
||||
# binaries are placed under $TARGETPLATFORM/ in the build context.
|
||||
ARG TARGETPLATFORM
|
||||
COPY $TARGETPLATFORM/server /app/server
|
||||
COPY docker/entrypoint.sh /app/entrypoint.sh
|
||||
|
||||
RUN sed -i 's/\r$//' /app/entrypoint.sh \
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ vars:
|
||||
LDFLAGS: -s -w -checklinkname=0 -X main.version={{.VERSION}}
|
||||
GOLANGCI_VERSION: v2.12.2
|
||||
GOVULN_VERSION: v1.1.4
|
||||
GORELEASER_VERSION: v2.5.0
|
||||
GORELEASER_VERSION: v2.15.4
|
||||
|
||||
tasks:
|
||||
default:
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
mathrand "math/rand"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -23,6 +22,7 @@ import (
|
||||
|
||||
"github.com/samosvalishe/btp/internal/client/browserprofile"
|
||||
"github.com/samosvalishe/btp/internal/logx"
|
||||
"github.com/samosvalishe/btp/internal/randx"
|
||||
)
|
||||
|
||||
// Log — пакетный логгер. По умолчанию no-op; main устанавливает его через
|
||||
@@ -449,7 +449,7 @@ func (s *captchaSession) solveCheckboxCaptcha(
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return "", s.ctx.Err()
|
||||
case <-time.After(time.Duration(400+mathrand.Intn(250)) * time.Millisecond):
|
||||
case <-time.After(time.Duration(400+randx.Intn(250)) * time.Millisecond):
|
||||
}
|
||||
|
||||
check, err := s.performCaptchaCheck(sessionToken, browserFP, hash, "{}", "[]", debugInfo)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -548,7 +549,7 @@ func startCaptchaServer(srv *http.Server, logPrefix string) error {
|
||||
// runCaptchaServerAndWait открывает браузер и ждёт токен решения.
|
||||
// При срабатывании ctx возвращает ctx.Err(); в обоих случаях HTTP-сервер останавливается.
|
||||
func runCaptchaServerAndWait(ctx context.Context, handler http.Handler, captchaURL string, keyCh <-chan string, logPrefix string) (string, error) {
|
||||
srv := &http.Server{Handler: handler}
|
||||
srv := &http.Server{Handler: handler, ReadHeaderTimeout: 10 * time.Second}
|
||||
|
||||
if err := startCaptchaServer(srv, logPrefix); err != nil {
|
||||
return "", err
|
||||
@@ -700,7 +701,8 @@ func SolveViaProxy(ctx context.Context, redirectURI string, dialer net.Dialer) (
|
||||
Log.Errorf("[Captcha Proxy] %s %s: %v", r.Method, r.URL.String(), err)
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
_, _ = fmt.Fprintf(w, `<!DOCTYPE html><html><body style="font-family:sans-serif;padding:20px"><h2>Captcha proxy error</h2><p>%s %s</p><p>%v</p></body></html>`, r.Method, r.URL.String(), err)
|
||||
_, _ = fmt.Fprintf(w, `<!DOCTYPE html><html><body style="font-family:sans-serif;padding:20px"><h2>Captcha proxy error</h2><p>%s %s</p><p>%s</p></body></html>`,
|
||||
html.EscapeString(r.Method), html.EscapeString(r.URL.String()), html.EscapeString(err.Error()))
|
||||
},
|
||||
ModifyResponse: func(res *http.Response) error {
|
||||
rewriteProxyCookies(res.Header)
|
||||
@@ -870,7 +872,9 @@ func SolveViaProxy(ctx context.Context, redirectURI string, dialer net.Dialer) (
|
||||
|
||||
func openBrowser(url string) {
|
||||
for _, cmd := range browserOpenCommands(runtime.GOOS, url) {
|
||||
if err := exec.Command(cmd.name, cmd.args...).Start(); err == nil { //nolint:noctx
|
||||
// cmd.name/args приходят из жёстко закодированного browserOpenCommands;
|
||||
// внешнего ввода в exec.Command нет (url передан внутри args как обычная строка).
|
||||
if err := exec.Command(cmd.name, cmd.args...).Start(); err == nil { //nolint:noctx,gosec // hardcoded browser binary list, no taint
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,13 @@ import (
|
||||
"image/color"
|
||||
_ "image/jpeg" // регистрация JPEG-декодера для image.Decode
|
||||
"math"
|
||||
mathrand "math/rand"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/samosvalishe/btp/internal/randx"
|
||||
)
|
||||
|
||||
type sliderPuzzle struct {
|
||||
@@ -509,7 +510,10 @@ func sampleLumaMapped(img image.Image, dstRect image.Rectangle, srcRect image.Re
|
||||
c := sampleColorMapped(img, dstRect, srcRect, dstX, dstY)
|
||||
r, g, b, _ := c.RGBA()
|
||||
y := (299*(r>>8) + 587*(g>>8) + 114*(b>>8)) / 1000
|
||||
return uint8(y)
|
||||
if y > 255 {
|
||||
y = 255
|
||||
}
|
||||
return uint8(y) //nolint:gosec // bounded above by 255
|
||||
}
|
||||
|
||||
func absFloat(v float64) float64 {
|
||||
@@ -565,29 +569,29 @@ func buildSliderCursor(candidateIndex int, candidateCount int) string {
|
||||
Y int `json:"y"`
|
||||
}
|
||||
|
||||
startX := 570 + mathrand.Intn(40)
|
||||
startY := 875 + mathrand.Intn(30)
|
||||
startX := 570 + randx.Intn(40)
|
||||
startY := 875 + randx.Intn(30)
|
||||
|
||||
denom := candidateCount - 1
|
||||
if denom < 1 {
|
||||
denom = 1
|
||||
}
|
||||
baseTargetX := 734 + (937-734)*(candidateIndex-1)/denom
|
||||
targetX := baseTargetX + mathrand.Intn(10) - 5
|
||||
targetY := 655 + mathrand.Intn(14)
|
||||
targetX := baseTargetX + randx.Intn(10) - 5
|
||||
targetY := 655 + randx.Intn(14)
|
||||
|
||||
points := make([]cursorPoint, 0, 28)
|
||||
|
||||
for i := 0; i < 1+mathrand.Intn(3); i++ {
|
||||
for i := 0; i < 1+randx.Intn(3); i++ {
|
||||
points = append(points, cursorPoint{
|
||||
X: startX + mathrand.Intn(5) - 2,
|
||||
Y: startY + mathrand.Intn(5) - 2,
|
||||
X: startX + randx.Intn(5) - 2,
|
||||
Y: startY + randx.Intn(5) - 2,
|
||||
})
|
||||
}
|
||||
|
||||
transitSteps := 2 + mathrand.Intn(3)
|
||||
arcOffX := mathrand.Intn(60) - 30
|
||||
arcOffY := -(mathrand.Intn(30) + 10)
|
||||
transitSteps := 2 + randx.Intn(3)
|
||||
arcOffX := randx.Intn(60) - 30
|
||||
arcOffY := -(randx.Intn(30) + 10)
|
||||
for i := 1; i <= transitSteps; i++ {
|
||||
t := float64(i) / float64(transitSteps+1)
|
||||
cx := float64(startX+targetX)/2 + float64(arcOffX)
|
||||
@@ -596,25 +600,25 @@ func buildSliderCursor(candidateIndex int, candidateCount int) string {
|
||||
by := (1-t)*(1-t)*float64(startY) + 2*t*(1-t)*cy + t*t*float64(targetY)
|
||||
jitter := int((1-t)*8) + 2
|
||||
points = append(points, cursorPoint{
|
||||
X: int(math.Round(bx)) + mathrand.Intn(jitter*2+1) - jitter,
|
||||
Y: int(math.Round(by)) + mathrand.Intn(jitter*2+1) - jitter,
|
||||
X: int(math.Round(bx)) + randx.Intn(jitter*2+1) - jitter,
|
||||
Y: int(math.Round(by)) + randx.Intn(jitter*2+1) - jitter,
|
||||
})
|
||||
}
|
||||
|
||||
approachSteps := 4 + mathrand.Intn(4)
|
||||
approachSteps := 4 + randx.Intn(4)
|
||||
prev := points[len(points)-1]
|
||||
for i := 1; i <= approachSteps; i++ {
|
||||
t := float64(i) / float64(approachSteps)
|
||||
ax := prev.X + int(math.Round(t*float64(targetX-prev.X))) + mathrand.Intn(5) - 2
|
||||
ay := prev.Y + int(math.Round(t*float64(targetY-prev.Y))) + mathrand.Intn(5) - 2
|
||||
ax := prev.X + int(math.Round(t*float64(targetX-prev.X))) + randx.Intn(5) - 2
|
||||
ay := prev.Y + int(math.Round(t*float64(targetY-prev.Y))) + randx.Intn(5) - 2
|
||||
points = append(points, cursorPoint{X: ax, Y: ay})
|
||||
}
|
||||
|
||||
settleCount := 3 + mathrand.Intn(5)
|
||||
settleCount := 3 + randx.Intn(5)
|
||||
for i := 0; i < settleCount; i++ {
|
||||
points = append(points, cursorPoint{
|
||||
X: targetX + mathrand.Intn(7) - 3,
|
||||
Y: targetY + mathrand.Intn(7) - 3,
|
||||
X: targetX + randx.Intn(7) - 3,
|
||||
Y: targetY + randx.Intn(7) - 3,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -334,9 +335,13 @@ func handleDohForwarderTCP(conn net.Conn, r *DohResolver) {
|
||||
Log.Warnf("[DoH] tcp forward failed: %v", err)
|
||||
return
|
||||
}
|
||||
if len(resp) > 0xFFFF {
|
||||
Log.Warnf("[DoH] response too large for TCP framing: %d", len(resp))
|
||||
return
|
||||
}
|
||||
out := make([]byte, 2+len(resp))
|
||||
out[0] = byte(len(resp) >> 8)
|
||||
out[1] = byte(len(resp))
|
||||
respLen := uint16(len(resp)) //nolint:gosec // bounded above
|
||||
binary.BigEndian.PutUint16(out[:2], respLen)
|
||||
copy(out[2:], resp)
|
||||
_ = conn.SetWriteDeadline(time.Now().Add(forwarderTCPWriteDL)) //nolint:errcheck
|
||||
if _, err := conn.Write(out); err != nil {
|
||||
|
||||
@@ -4,8 +4,9 @@ package namegen
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"math/rand/v2"
|
||||
"strings"
|
||||
|
||||
"github.com/samosvalishe/btp/internal/randx"
|
||||
)
|
||||
|
||||
//go:embed data/first_names_male.txt
|
||||
@@ -60,19 +61,19 @@ const SurnameProbability = 0.7
|
||||
// Generate возвращает случайное "Имя" или "Имя Фамилия".
|
||||
// Пол выбирается равномерно; женские фамилии феминизируются.
|
||||
func Generate() string {
|
||||
isFemale := rand.IntN(2) == 0
|
||||
isFemale := randx.IntN(2) == 0
|
||||
|
||||
pool := maleFirstNames
|
||||
if isFemale {
|
||||
pool = femaleFirstNames
|
||||
}
|
||||
fn := pool[rand.IntN(len(pool))]
|
||||
fn := pool[randx.IntN(len(pool))]
|
||||
|
||||
if rand.Float64() >= SurnameProbability {
|
||||
if randx.Float64() >= SurnameProbability {
|
||||
return fn
|
||||
}
|
||||
|
||||
ln := lastNames[rand.IntN(len(lastNames))]
|
||||
ln := lastNames[randx.IntN(len(lastNames))]
|
||||
if isFemale {
|
||||
ln = feminizeSurname(ln)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -12,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/samosvalishe/btp/internal/logx"
|
||||
"github.com/samosvalishe/btp/internal/randx"
|
||||
|
||||
tlsclient "github.com/bogdanfinn/tls-client"
|
||||
)
|
||||
@@ -96,7 +96,7 @@ func New(cfg Config) *Client {
|
||||
}
|
||||
c.tokenChain = c.getTokenChain
|
||||
c.minFetchIntervalFn = func() time.Duration {
|
||||
return 3*time.Second + time.Duration(rand.Intn(3000))*time.Millisecond
|
||||
return 3*time.Second + time.Duration(randx.Intn(3000))*time.Millisecond
|
||||
}
|
||||
return c
|
||||
}
|
||||
@@ -238,7 +238,7 @@ func (c *Client) fetch(ctx context.Context, link string, streamID int) (string,
|
||||
}
|
||||
|
||||
func vkDelayRandom(ctx context.Context, minMs, maxMs int) error {
|
||||
ms := minMs + rand.Intn(maxMs-minMs+1)
|
||||
ms := minMs + randx.Intn(maxMs-minMs+1)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
|
||||
@@ -44,8 +44,8 @@ func (c *Client) getTokenChain(ctx context.Context, link string, streamID int, c
|
||||
return "", "", nil, err
|
||||
}
|
||||
|
||||
if err := vkDelayRandom(ctx, 100, 150); err != nil {
|
||||
return "", "", nil, err
|
||||
if delayErr := vkDelayRandom(ctx, 100, 150); delayErr != nil {
|
||||
return "", "", nil, delayErr
|
||||
}
|
||||
|
||||
// Шаг 1a: прогрев getCallPreview (не критично).
|
||||
@@ -55,8 +55,8 @@ func (c *Client) getTokenChain(ctx context.Context, link string, streamID int, c
|
||||
c.log.Warnf("[STREAM %d] [VK Auth] getCallPreview failed: %v", streamID, prevErr)
|
||||
}
|
||||
|
||||
if err := vkDelayRandom(ctx, 200, 400); err != nil {
|
||||
return "", "", nil, err
|
||||
if delayErr := vkDelayRandom(ctx, 200, 400); delayErr != nil {
|
||||
return "", "", nil, delayErr
|
||||
}
|
||||
|
||||
// Шаг 2: анонимный call-токен (здесь может сработать captcha).
|
||||
@@ -65,8 +65,8 @@ func (c *Client) getTokenChain(ctx context.Context, link string, streamID int, c
|
||||
return "", "", nil, err
|
||||
}
|
||||
|
||||
if err := vkDelayRandom(ctx, 100, 150); err != nil {
|
||||
return "", "", nil, err
|
||||
if delayErr := vkDelayRandom(ctx, 100, 150); delayErr != nil {
|
||||
return "", "", nil, delayErr
|
||||
}
|
||||
|
||||
// Шаг 3: ok.ru session_key.
|
||||
@@ -75,8 +75,8 @@ func (c *Client) getTokenChain(ctx context.Context, link string, streamID int, c
|
||||
return "", "", nil, err
|
||||
}
|
||||
|
||||
if err := vkDelayRandom(ctx, 100, 150); err != nil {
|
||||
return "", "", nil, err
|
||||
if delayErr := vkDelayRandom(ctx, 100, 150); delayErr != nil {
|
||||
return "", "", nil, delayErr
|
||||
}
|
||||
|
||||
// Шаг 4: TURN-реквизиты.
|
||||
|
||||
@@ -20,7 +20,12 @@ type TurnCredentials struct {
|
||||
Link string
|
||||
}
|
||||
|
||||
// DefaultCredentials — публичные app_id VK SDK; клиент перебирает по порядку.
|
||||
// DefaultCredentials — публичные app_id/secret VK SDK, извлечённые из
|
||||
// официальных VK-клиентов (web/mobile/video). Это НЕ приватные креды
|
||||
// пользователя — VK раздаёт их в JS-бандле страницы калла. Клиент перебирает
|
||||
// по порядку при ошибках авторизации.
|
||||
//
|
||||
//nolint:gosec // public VK SDK app credentials, not user secrets
|
||||
var DefaultCredentials = []VKCredentials{
|
||||
{ClientID: "6287487", ClientSecret: "QbYic1K3lEV5kTGiqlq2"}, // VK_WEB_APP_ID
|
||||
{ClientID: "7879029", ClientSecret: "aR5NKGmm03GYrCiNKsaw"}, // VK_MVK_APP_ID
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -76,11 +77,14 @@ func (h *Handler) Handle(ctx context.Context, tcpConn net.Conn, connID uint64, c
|
||||
opened = append(opened, pending{ps: ps, stream: stream})
|
||||
}
|
||||
|
||||
if len(opened) > math.MaxUint16 {
|
||||
opened = opened[:math.MaxUint16]
|
||||
}
|
||||
lanes := make([]*lane, 0, len(opened))
|
||||
laneIDs := make([]string, 0, len(opened))
|
||||
laneCount := uint16(len(opened))
|
||||
laneCount := uint16(len(opened)) //nolint:gosec // bounded above by MaxUint16
|
||||
for i, p := range opened {
|
||||
if err := bondframe.WriteHello(p.stream, connID, uint16(i), laneCount); err != nil {
|
||||
if err := bondframe.WriteHello(p.stream, connID, uint16(i), laneCount); err != nil { //nolint:gosec // i < laneCount <= MaxUint16
|
||||
h.Deps.log().Errorf("[bond %d] session %d hello error: %s", connID, p.ps.ID, err)
|
||||
_ = p.stream.Close()
|
||||
continue
|
||||
|
||||
@@ -167,10 +167,11 @@ func Run(ctx context.Context, deps *Deps, params *Params, peer *net.UDPAddr, lis
|
||||
}
|
||||
}
|
||||
fromSession, toSession := netconn.BiCopy(ctx, tc, stream, errf)
|
||||
sessRef.FromSession.Add(uint64(fromSession))
|
||||
sessRef.ToSession.Add(uint64(toSession))
|
||||
fromU, toU := nonNegUint64(fromSession), nonNegUint64(toSession)
|
||||
sessRef.FromSession.Add(fromU)
|
||||
sessRef.ToSession.Add(toU)
|
||||
deps.log().Debugf("[session %d] TCP done #%d local<-session=%s local->session=%s",
|
||||
sessRef.ID, cid, stats.FormatByteCount(uint64(fromSession)), stats.FormatByteCount(uint64(toSession)))
|
||||
sessRef.ID, cid, stats.FormatByteCount(fromU), stats.FormatByteCount(toU))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -255,7 +256,7 @@ func createSmuxSession(ctx context.Context, deps *Deps, params *Params, peer *ne
|
||||
cleanupFns = append(cleanupFns, func() { _ = dtlsConn.Close() })
|
||||
deps.log().Debugf("DTLS connection established")
|
||||
|
||||
statsCtx, statsCancel := context.WithCancel(ctx)
|
||||
statsCtx, statsCancel := context.WithCancel(ctx) //nolint:gosec // cancel is held in cleanupFns and invoked via cleanup() LIFO
|
||||
cleanupFns = append(cleanupFns, statsCancel)
|
||||
st := stats.New(deps.log().DebugEnabled())
|
||||
go st.LogEvery(statsCtx, deps.log().Debugf, fmt.Sprintf("[session %d] TCP", id), "to-turn", "from-turn")
|
||||
@@ -278,3 +279,12 @@ func createSmuxSession(ctx context.Context, deps *Deps, params *Params, peer *ne
|
||||
|
||||
return smuxSess, cleanup, nil
|
||||
}
|
||||
|
||||
// nonNegUint64 безопасно приводит int64-счётчик байт к uint64 (io.Copy
|
||||
// возвращает n >= 0 при успехе, но на ошибке n может быть отрицательным).
|
||||
func nonNegUint64(n int64) uint64 {
|
||||
if n < 0 {
|
||||
return 0
|
||||
}
|
||||
return uint64(n)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -13,6 +12,7 @@ import (
|
||||
"github.com/cbeuw/connutil"
|
||||
"github.com/samosvalishe/btp/internal/client/vkauth"
|
||||
"github.com/samosvalishe/btp/internal/proxy/common"
|
||||
"github.com/samosvalishe/btp/internal/randx"
|
||||
"github.com/samosvalishe/btp/internal/stats"
|
||||
"github.com/samosvalishe/btp/internal/wire/srtpmimicry"
|
||||
)
|
||||
@@ -36,7 +36,7 @@ func DTLSLoop(ctx context.Context, deps *Deps, peer *net.UDPAddr, listenConn net
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(time.Duration(1+rand.Intn(2)) * time.Second):
|
||||
case <-time.After(time.Duration(1+randx.Intn(2)) * time.Second):
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func DTLSLoop(ctx context.Context, deps *Deps, peer *net.UDPAddr, listenConn net
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(time.Duration(10+rand.Intn(20)) * time.Second):
|
||||
case <-time.After(time.Duration(10+randx.Intn(20)) * time.Second):
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func TURNLoop(ctx context.Context, deps *Deps, params *Params, peer *net.UDPAddr
|
||||
|
||||
func oneDTLS(ctx context.Context, deps *Deps, peer *net.UDPAddr, listenConn net.PacketConn, inboundChan <-chan *Packet, connchan chan<- net.PacketConn, okchan chan<- struct{}, streamID int) error {
|
||||
select {
|
||||
case <-time.After(time.Duration(rand.Intn(400)+100) * time.Millisecond):
|
||||
case <-time.After(time.Duration(randx.Intn(400)+100) * time.Millisecond):
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
@@ -127,8 +127,8 @@ func oneDTLS(ctx context.Context, deps *Deps, peer *net.UDPAddr, listenConn net.
|
||||
defer dtlscancel()
|
||||
|
||||
conn1, conn2 := connutil.AsyncPacketPipe()
|
||||
defer conn1.Close()
|
||||
defer conn2.Close()
|
||||
defer func() { _ = conn1.Close() }()
|
||||
defer func() { _ = conn2.Close() }()
|
||||
// TURNLoop может перезапускать oneTURN несколько раз в рамках одного DTLS
|
||||
// соединения, каждый раз перечитывая conn2; публикуем до завершения DTLS.
|
||||
go func() {
|
||||
@@ -216,7 +216,7 @@ func oneTURN(ctx context.Context, deps *Deps, params *Params, peer *net.UDPAddr,
|
||||
var err error
|
||||
defer func() { c <- err }()
|
||||
select {
|
||||
case <-time.After(time.Duration(rand.Intn(400)+100) * time.Millisecond):
|
||||
case <-time.After(time.Duration(randx.Intn(400)+100) * time.Millisecond):
|
||||
case <-ctx.Done():
|
||||
err = ctx.Err()
|
||||
return
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Package randx — тонкая обёртка над crypto/rand с API в стиле math/rand.
|
||||
// Используется там, где gosec G404 бьёт по math/rand: антифингерпринт-jitter,
|
||||
// случайные задержки, выбор имён/паттернов. CSPRNG здесь не критичен по
|
||||
// производительности, зато снимает шум линтера и убирает PRNG-предсказуемость.
|
||||
package randx
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// IntN возвращает равномерное случайное число из [0, n). Для n <= 0 возвращает 0.
|
||||
func IntN(n int) int {
|
||||
if n <= 0 {
|
||||
return 0
|
||||
}
|
||||
v, err := rand.Int(rand.Reader, big.NewInt(int64(n)))
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return int(v.Int64())
|
||||
}
|
||||
|
||||
// Intn — алиас IntN для совместимости с math/rand.
|
||||
func Intn(n int) int { return IntN(n) }
|
||||
|
||||
// Float64 возвращает число из [0.0, 1.0) с 53 битами мантиссы.
|
||||
func Float64() float64 {
|
||||
var b [8]byte
|
||||
if _, err := rand.Read(b[:]); err != nil {
|
||||
return 0
|
||||
}
|
||||
u := binary.LittleEndian.Uint64(b[:]) >> 11
|
||||
return float64(u) / float64(1<<53)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
@@ -78,10 +79,13 @@ func ParseHelloHeader(hdr []byte) (Hello, error) {
|
||||
}
|
||||
|
||||
func WriteFrame(w io.Writer, typ byte, seq uint64, data []byte) error {
|
||||
if uint64(len(data)) > math.MaxUint32 {
|
||||
return fmt.Errorf("bondframe: data too large: %d", len(data))
|
||||
}
|
||||
var hdr [13]byte
|
||||
hdr[0] = typ
|
||||
binary.BigEndian.PutUint64(hdr[1:9], seq)
|
||||
binary.BigEndian.PutUint32(hdr[9:13], uint32(len(data)))
|
||||
binary.BigEndian.PutUint32(hdr[9:13], uint32(len(data))) //nolint:gosec // bounded above
|
||||
if _, err := w.Write(hdr[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ func (c *Conn) WrapInto(dst, payload []byte) (int, error) {
|
||||
// RTP-заголовок.
|
||||
dst[0] = rtpVersion
|
||||
dst[1] = rtpPT
|
||||
seq := uint16(c.seq.Add(1) - 1)
|
||||
seq := uint16(c.seq.Add(1) - 1) //nolint:gosec // RTP sequence number is intentionally mod 2^16
|
||||
binary.BigEndian.PutUint16(dst[2:4], seq)
|
||||
ts := c.timestamp.Add(tsStep) - tsStep
|
||||
binary.BigEndian.PutUint32(dst[4:8], ts)
|
||||
|
||||
Reference in New Issue
Block a user