Skip to content
Closed
156 changes: 156 additions & 0 deletions cmd/turn-no-vps-probe/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package main

import (
"context"
"flag"
"fmt"
"net"
"os"
"strings"
"time"

core "wg-turn-client"
)

const defaultTelegramTargets = "149.154.175.50:443,149.154.167.51:443,95.161.76.100:443,149.154.175.100:443,149.154.167.91:443,149.154.171.5:443"

func main() {
hashInput := flag.String("hash", "", "VK call hash or full https://vk.com/call/join/... link")
stunTarget := flag.String("stun-target", "stun.cloudflare.com:3478", "public STUN control target")
telegramTargets := flag.String("telegram-targets", defaultTelegramTargets, "comma-separated Telegram DC UDP targets")
controlTimeout := flag.Duration("control-timeout", 45*time.Second, "UDP control probe timeout")
telegramTimeout := flag.Duration("telegram-timeout", 30*time.Second, "timeout per Telegram DC")
flag.Parse()

hash := normalizeVKHash(*hashInput)
if hash == "" {
fmt.Fprintln(os.Stderr, "error: -hash is required")
flag.Usage()
os.Exit(2)
}

targets, err := parseTargets(*telegramTargets)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(2)
}

fmt.Println("PWDTT no-VPS direct Telegram verdict probe")
fmt.Println("Stage 1/2: verify bidirectional external UDP through VK/OK TURN")

controlCtx, cancelControl := context.WithTimeout(context.Background(), *controlTimeout)
control, err := core.ProbeTurnUDPSTUN(controlCtx, core.TurnUDPSTUNProbeConfig{
Hash: hash,
Target: *stunTarget,
Timeout: *controlTimeout,
})
cancelControl()
if err != nil {
fmt.Fprintf(os.Stderr, "UDP CONTROL FAILED: %v\n", err)
fmt.Println()
fmt.Println("VERDICT")
fmt.Println("UDP egress: NOT CONFIRMED")
fmt.Println("Ordinary Telegram MTProto over TURN UDP: NOT TESTED")
fmt.Println("Direct no-VPS Telegram messaging: NOT CONFIRMED")
os.Exit(1)
}

fmt.Println("UDP CONTROL PASS")
fmt.Printf(" TURN endpoint: %s\n", control.TurnAddress)
fmt.Printf(" Relay: %s\n", control.RelayAddress)
fmt.Printf(" STUN target: %s\n", control.Target)
fmt.Printf(" Response source: %s\n", control.ResponseSource)
fmt.Printf(" Mapped address: %s\n", control.MappedAddress)
fmt.Println()
fmt.Println("Stage 2/2: probe ordinary unauthenticated MTProto over UDP")
fmt.Printf("Testing %d Telegram DC target(s) with all implemented framing candidates.\n", len(targets))

for i, target := range targets {
fmt.Printf("[%d/%d] %s ...\n", i+1, len(targets), target)

probeCtx, cancelProbe := context.WithTimeout(context.Background(), *telegramTimeout)
result, probeErr := core.ProbeTelegramMTProtoUDP(probeCtx, core.TelegramMTProtoUDPProbeConfig{
Hash: hash,
Target: target,
Timeout: *telegramTimeout,
})
cancelProbe()

if probeErr != nil {
fmt.Printf(" NO VALID resPQ: %v\n", probeErr)
continue
}

fmt.Println(" MTProto UDP PASS")
fmt.Printf(" TURN endpoint: %s\n", result.TurnAddress)
fmt.Printf(" Relay: %s\n", result.RelayAddress)
fmt.Printf(" Response source: %s\n", result.ResponseSource)
fmt.Printf(" Framing: %s\n", result.Mode)
fmt.Printf(" Response bytes: %d\n", result.ResponseBytes)
fmt.Println()
fmt.Println("VERDICT")
fmt.Println("UDP egress: PASS")
fmt.Println("Ordinary Telegram MTProto over TURN UDP: PASS")
fmt.Println("Direct no-VPS Telegram messaging transport: CONFIRMED AT PROTOCOL-PROBE LEVEL")
fmt.Println("Next step: implement a persistent client transport around the confirmed framing.")
return
}

fmt.Println()
fmt.Println("VERDICT")
fmt.Println("UDP egress: PASS")
fmt.Println("Ordinary Telegram MTProto over tested UDP framings/DCs: NO VALID RESPONSE")
fmt.Println("Direct no-VPS Telegram messaging: NOT CONFIRMED")
fmt.Println("The result does not prove impossibility; it shows that the tested direct MTProto-over-UDP variants did not work.")
os.Exit(3)
}

func normalizeVKHash(input string) string {
value := strings.TrimSpace(input)
if value == "" {
return ""
}

const marker = "/call/join/"
if index := strings.Index(value, marker); index >= 0 {
value = value[index+len(marker):]
}
if index := strings.IndexAny(value, "?#/"); index >= 0 {
value = value[:index]
}
value = strings.TrimSpace(value)
if strings.ContainsAny(value, "<>") {
return ""
}
return value
}

func parseTargets(input string) ([]string, error) {
parts := strings.Split(input, ",")
result := make([]string, 0, len(parts))
seen := make(map[string]struct{}, len(parts))

for _, part := range parts {
target := strings.TrimSpace(part)
if target == "" {
continue
}
host, port, err := net.SplitHostPort(target)
if err != nil {
return nil, fmt.Errorf("invalid Telegram target %q: %w", target, err)
}
if strings.Trim(host, "[]") == "" || port == "" {
return nil, fmt.Errorf("invalid Telegram target %q", target)
}
if _, ok := seen[target]; ok {
continue
}
seen[target] = struct{}{}
result = append(result, target)
}

if len(result) == 0 {
return nil, fmt.Errorf("at least one Telegram target is required")
}
return result, nil
}
46 changes: 46 additions & 0 deletions cmd/turn-no-vps-probe/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package main

import (
"reflect"
"testing"
)

func TestNormalizeVKHash(t *testing.T) {
cases := map[string]string{
"abc123": "abc123",
" https://vk.com/call/join/abc123 ": "abc123",
"https://vk.com/call/join/abc123?foo=bar": "abc123",
"<VK_HASH>": "",
"": "",
}

for input, want := range cases {
if got := normalizeVKHash(input); got != want {
t.Fatalf("normalizeVKHash(%q)=%q, want %q", input, got, want)
}
}
}

func TestParseTargets(t *testing.T) {
got, err := parseTargets("149.154.175.50:443, 149.154.167.51:443,149.154.175.50:443")
if err != nil {
t.Fatalf("parseTargets: %v", err)
}

want := []string{
"149.154.175.50:443",
"149.154.167.51:443",
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("parseTargets=%v, want %v", got, want)
}
}

func TestParseTargetsRejectsInvalid(t *testing.T) {
if _, err := parseTargets("149.154.175.50"); err == nil {
t.Fatal("expected invalid target error")
}
if _, err := parseTargets(" , "); err == nil {
t.Fatal("expected empty target list error")
}
}
90 changes: 90 additions & 0 deletions cmd/turn-tcp-probe/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package main

import (
"context"
"flag"
"fmt"
"net"
"os"
"strings"
"time"

core "wg-turn-client"
)

func main() {
hash := flag.String("hash", "", "VK call hash (the part after /call/join/)")
target := flag.String("target", "example.com:80", "TCP target in host:port form")
mode := flag.String("mode", "connect", "probe mode: connect or http")
timeout := flag.Duration("timeout", 45*time.Second, "overall probe timeout")
flag.Parse()

if strings.TrimSpace(*hash) == "" {
fmt.Fprintln(os.Stderr, "error: -hash is required")
flag.Usage()
os.Exit(2)
}

payload, err := probePayload(*mode, *target)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(2)
}

fmt.Println("TURN TCP RFC 6062 probe")
fmt.Printf("Target: %s\n", *target)
fmt.Printf("Mode: %s\n", strings.ToLower(strings.TrimSpace(*mode)))
fmt.Println("Obtaining VK TURN credentials and trying TCP allocation...")

ctx, cancel := context.WithTimeout(context.Background(), *timeout)
defer cancel()

result, err := core.ProbeTurnTCP(ctx, core.TurnTCPProbeConfig{
Hash: *hash,
Target: *target,
Payload: payload,
Timeout: *timeout,
})
if err != nil {
fmt.Fprintf(os.Stderr, "FAILED: %v\n", err)
os.Exit(1)
}

fmt.Println("SUCCESS")
fmt.Printf("TURN endpoint: %s\n", result.TurnAddress)
fmt.Printf("Relayed address: %s\n", result.RelayAddress)
fmt.Printf("Target: %s\n", result.Target)

if result.ResponsePreview != "" {
fmt.Println("Response preview:")
fmt.Print(result.ResponsePreview)
if !strings.HasSuffix(result.ResponsePreview, "\n") {
fmt.Println()
}
}
}

func probePayload(mode, target string) ([]byte, error) {
switch strings.ToLower(strings.TrimSpace(mode)) {
case "connect":
return nil, nil
case "http":
host, port, err := net.SplitHostPort(strings.TrimSpace(target))
if err != nil {
return nil, fmt.Errorf("parse target: %w", err)
}
if port != "80" {
return nil, fmt.Errorf("http mode requires target port 80, got %s", port)
}
host = strings.Trim(host, "[]")
if host == "" {
return nil, fmt.Errorf("target host is empty")
}
return []byte(fmt.Sprintf(
"GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\nUser-Agent: PWDTT-TURN-TCP-Probe/1\r\n\r\n",
host,
)), nil
default:
return nil, fmt.Errorf("unsupported -mode %q; use connect or http", mode)
}
}
57 changes: 57 additions & 0 deletions cmd/turn-telegram-mtproto-udp-probe/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package main

import (
"context"
"flag"
"fmt"
"os"
"strings"
"time"

core "wg-turn-client"
)

func main() {
hash := flag.String("hash", "", "VK call hash (the part after /call/join/)")
target := flag.String("target", "149.154.167.51:443", "Telegram DC UDP target in ip:port form")
timeout := flag.Duration("timeout", 45*time.Second, "overall probe timeout")
flag.Parse()

if strings.TrimSpace(*hash) == "" {
fmt.Fprintln(os.Stderr, "error: -hash is required")
flag.Usage()
os.Exit(2)
}
if strings.Contains(*target, "<") || strings.Contains(*target, ">") {
fmt.Fprintln(os.Stderr, "error: replace the -target placeholder with a real Telegram DC ip:port")
os.Exit(2)
}

fmt.Println("Telegram MTProto via VK TURN UDP probe")
fmt.Printf("Telegram DC: %s\n", *target)
fmt.Println("Request: unauthenticated req_pq_multi")
fmt.Println("Framing candidates: raw, abridged, abridged-init, intermediate, intermediate-init, padded-intermediate-init, full")
fmt.Println("Obtaining VK TURN credentials and probing Telegram directly over UDP...")

ctx, cancel := context.WithTimeout(context.Background(), *timeout)
defer cancel()

result, err := core.ProbeTelegramMTProtoUDP(ctx, core.TelegramMTProtoUDPProbeConfig{
Hash: *hash,
Target: *target,
Timeout: *timeout,
})
if err != nil {
fmt.Fprintf(os.Stderr, "FAILED: %v\n", err)
os.Exit(1)
}

fmt.Println("SUCCESS")
fmt.Printf("TURN endpoint: %s\n", result.TurnAddress)
fmt.Printf("Relayed address: %s\n", result.RelayAddress)
fmt.Printf("Telegram DC: %s\n", result.Target)
fmt.Printf("Response source: %s\n", result.ResponseSource)
fmt.Printf("MTProto UDP framing: %s\n", result.Mode)
fmt.Printf("Response bytes: %d\n", result.ResponseBytes)
fmt.Println("Validated: resPQ with matching nonce")
}
Loading
Loading