mirror of
https://github.com/p1neappleXpress/OpenFlux.git
synced 2026-10-02 05:04:39 +08:00
Initial commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
.DS_Store
|
||||
output*
|
||||
build*
|
||||
@@ -0,0 +1,15 @@
|
||||
module openflux
|
||||
|
||||
go 1.26.4
|
||||
|
||||
require github.com/gorilla/websocket v1.5.3
|
||||
|
||||
require gvisor.dev/gvisor v0.0.0-20260530041128-23ef90c42be7
|
||||
|
||||
require (
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc=
|
||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
gvisor.dev/gvisor v0.0.0-20260530041128-23ef90c42be7 h1:ZodhPVQzlCmuc1jt1ZIovMTwAJEArTPgq+UTu+mkVME=
|
||||
gvisor.dev/gvisor v0.0.0-20260530041128-23ef90c42be7/go.mod h1:8aLQqUBHDH8fY5y60lzmwDpMMbQCcT3EBfoSwhfaGCY=
|
||||
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"openflux/socks5"
|
||||
"openflux/transport"
|
||||
"openflux/transport/yandex"
|
||||
"openflux/tunnel"
|
||||
"openflux/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
globalDocUrl string
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Print("written by p1neappleXpress\n")
|
||||
|
||||
exitNode := flag.Bool("exit-node", false, "Run as exit node (needs root)")
|
||||
client := flag.Bool("client", false, "Run as client")
|
||||
debug := flag.Bool("debug", false, "Enable verbose debug logging")
|
||||
socksAddr := flag.String("socks5", ":1080", "SOCKS5 address")
|
||||
transportType := flag.String("transport", "yandex", "Transport type (yandex, google, custom)")
|
||||
flag.StringVar(&globalDocUrl, "url", "https://localhost", "Document URL")
|
||||
flag.Parse()
|
||||
|
||||
if !*exitNode && !*client {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *debug {
|
||||
utils.EnableDebug()
|
||||
}
|
||||
|
||||
log.Printf("=== Universal Bypass Tool ===")
|
||||
log.Printf("Mode: %s", map[bool]string{true: "EXIT NODE", false: "CLIENT"}[*exitNode])
|
||||
log.Printf("Transport: %s", *transportType)
|
||||
|
||||
config := transport.DefaultConfig()
|
||||
var trans transport.Transport
|
||||
|
||||
switch *transportType {
|
||||
case "yandex":
|
||||
trans = yandex.NewYandexDocsTransport(globalDocUrl, config)
|
||||
default:
|
||||
log.Fatalf("Unknown transport type: %s", *transportType)
|
||||
}
|
||||
|
||||
if err := trans.Start(); err != nil {
|
||||
log.Fatalf("Failed to start transport: %v", err)
|
||||
}
|
||||
|
||||
tun := tunnel.NewTCPTunnel(trans, *exitNode)
|
||||
|
||||
if *exitNode {
|
||||
log.Printf("Running as EXIT NODE (needs root for raw socket)")
|
||||
log.Printf("! Run: sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP")
|
||||
select {}
|
||||
} else {
|
||||
log.Printf("Running as CLIENT (SOCKS5 on %s)", *socksAddr)
|
||||
socks5Server := socks5.NewSOCKS5Server(*socksAddr, tun)
|
||||
log.Fatal(socks5Server.Start())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func TCPChecksum(tcpData []byte, srcIP, dstIP [4]byte) uint16 {
|
||||
pseudoHeader := []byte{
|
||||
srcIP[0], srcIP[1], srcIP[2], srcIP[3],
|
||||
dstIP[0], dstIP[1], dstIP[2], dstIP[3],
|
||||
0, 6,
|
||||
0, 0,
|
||||
}
|
||||
|
||||
tcpLen := len(tcpData)
|
||||
pseudoHeader[10] = byte(tcpLen >> 8)
|
||||
pseudoHeader[11] = byte(tcpLen & 0xff)
|
||||
|
||||
all := make([]byte, 0, len(pseudoHeader)+tcpLen)
|
||||
all = append(all, pseudoHeader...)
|
||||
all = append(all, tcpData...)
|
||||
|
||||
sum := uint32(0)
|
||||
for i := 0; i < len(all)-1; i += 2 {
|
||||
sum += uint32(all[i])<<8 | uint32(all[i+1])
|
||||
}
|
||||
if len(all)%2 == 1 {
|
||||
sum += uint32(all[len(all)-1]) << 8
|
||||
}
|
||||
|
||||
for sum>>16 > 0 {
|
||||
sum = (sum & 0xffff) + (sum >> 16)
|
||||
}
|
||||
|
||||
return uint16(^sum)
|
||||
}
|
||||
|
||||
func IPChecksum(b []byte) uint16 {
|
||||
sum := uint32(0)
|
||||
for i := 0; i < len(b)-1; i += 2 {
|
||||
sum += uint32(b[i])<<8 | uint32(b[i+1])
|
||||
}
|
||||
if len(b)%2 == 1 {
|
||||
sum += uint32(b[len(b)-1]) << 8
|
||||
}
|
||||
sum = (sum >> 16) + (sum & 0xFFFF)
|
||||
sum += sum >> 16
|
||||
return uint16(^sum)
|
||||
}
|
||||
|
||||
func ParsePacketInfo(data []byte) string {
|
||||
if len(data) < 20 {
|
||||
return fmt.Sprintf("short packet (%d bytes)", len(data))
|
||||
}
|
||||
srcIP := net.IP(data[12:16])
|
||||
dstIP := net.IP(data[16:20])
|
||||
protocol := data[9]
|
||||
ttl := data[8]
|
||||
totalLen := uint16(data[2])<<8 | uint16(data[3])
|
||||
|
||||
if protocol == 6 && len(data) >= 40 {
|
||||
srcPort := uint16(data[20])<<8 | uint16(data[21])
|
||||
dstPort := uint16(data[22])<<8 | uint16(data[23])
|
||||
flags := data[33]
|
||||
seq := uint32(data[24])<<24 | uint32(data[25])<<16 | uint32(data[26])<<8 | uint32(data[27])
|
||||
ack := uint32(data[28])<<24 | uint32(data[29])<<16 | uint32(data[30])<<8 | uint32(data[31])
|
||||
window := uint16(data[34])<<8 | uint16(data[35])
|
||||
|
||||
flagStr := ""
|
||||
if flags&0x02 != 0 {
|
||||
flagStr += "SYN "
|
||||
}
|
||||
if flags&0x10 != 0 {
|
||||
flagStr += "ACK "
|
||||
}
|
||||
if flags&0x01 != 0 {
|
||||
flagStr += "FIN "
|
||||
}
|
||||
if flags&0x04 != 0 {
|
||||
flagStr += "RST "
|
||||
}
|
||||
if flags&0x08 != 0 {
|
||||
flagStr += "PSH "
|
||||
}
|
||||
|
||||
return fmt.Sprintf("TCP %s:%d -> %s:%d [%s] seq=%d ack=%d win=%d len=%d ttl=%d",
|
||||
srcIP, srcPort, dstIP, dstPort, strings.TrimSpace(flagStr), seq, ack, window, totalLen, ttl)
|
||||
}
|
||||
return fmt.Sprintf("IP proto=%d %s -> %s len=%d ttl=%d", protocol, srcIP, dstIP, totalLen, ttl)
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"openflux/utils"
|
||||
)
|
||||
|
||||
type Dialer interface {
|
||||
DialTCP(address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
type SOCKS5Server struct {
|
||||
listenAddr string
|
||||
dialer Dialer
|
||||
}
|
||||
|
||||
func NewSOCKS5Server(addr string, dialer Dialer) *SOCKS5Server {
|
||||
return &SOCKS5Server{listenAddr: addr, dialer: dialer}
|
||||
}
|
||||
|
||||
func (s *SOCKS5Server) Start() error {
|
||||
listener, err := net.Listen("tcp", s.listenAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
utils.Debugf("[SOCKS5] Listening on %s", s.listenAddr)
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
utils.Debugf("[SOCKS5] Accept error: %v", err)
|
||||
continue
|
||||
}
|
||||
go s.handleConnection(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SOCKS5Server) handleConnection(clientConn net.Conn) {
|
||||
defer clientConn.Close()
|
||||
|
||||
buf := make([]byte, 256)
|
||||
n, err := clientConn.Read(buf)
|
||||
if err != nil || n < 2 || buf[0] != 0x05 {
|
||||
return
|
||||
}
|
||||
|
||||
clientConn.Write([]byte{0x05, 0x00})
|
||||
|
||||
n, err = clientConn.Read(buf)
|
||||
if err != nil || n < 10 || buf[1] != 0x01 {
|
||||
return
|
||||
}
|
||||
|
||||
var targetAddr string
|
||||
switch buf[3] {
|
||||
case 0x01:
|
||||
targetAddr = fmt.Sprintf("%d.%d.%d.%d:%d",
|
||||
buf[4], buf[5], buf[6], buf[7],
|
||||
uint16(buf[8])<<8|uint16(buf[9]))
|
||||
case 0x03:
|
||||
domainLen := int(buf[4])
|
||||
targetAddr = fmt.Sprintf("%s:%d",
|
||||
string(buf[5:5+domainLen]),
|
||||
uint16(buf[5+domainLen])<<8|uint16(buf[6+domainLen]))
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
utils.Debugf("[SOCKS5] CONNECT %s", targetAddr)
|
||||
|
||||
targetConn, err := s.dialer.DialTCP(targetAddr)
|
||||
if err != nil {
|
||||
utils.Debugf("[SOCKS5] Dial failed: %v", err)
|
||||
clientConn.Write([]byte{0x05, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
defer targetConn.Close()
|
||||
|
||||
clientConn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer targetConn.Close()
|
||||
io.Copy(targetConn, clientConn)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer clientConn.Close()
|
||||
io.Copy(clientConn, targetConn)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TransportConfig struct {
|
||||
MaxReconnectAttempts int
|
||||
ReconnectDelay time.Duration
|
||||
ReconnectMultiplier float64
|
||||
MaxQueueSize int
|
||||
KeepAliveInterval time.Duration
|
||||
}
|
||||
|
||||
type Transport interface {
|
||||
Start() error
|
||||
Stop() error
|
||||
Send(data []byte) error
|
||||
Receive(callback func([]byte))
|
||||
IsConnected() bool
|
||||
Stats() TransportStats
|
||||
}
|
||||
|
||||
type TransportStats struct {
|
||||
BytesSent uint64
|
||||
BytesReceived uint64
|
||||
PacketsSent uint64
|
||||
PacketsRecv uint64
|
||||
Reconnects uint64
|
||||
Connected bool
|
||||
Uptime time.Duration
|
||||
}
|
||||
|
||||
func DefaultConfig() TransportConfig {
|
||||
return TransportConfig{
|
||||
MaxReconnectAttempts: 999999,
|
||||
ReconnectDelay: 0,
|
||||
ReconnectMultiplier: 1.1,
|
||||
MaxQueueSize: 1024,
|
||||
KeepAliveInterval: 10 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
type BaseTransport struct {
|
||||
config TransportConfig
|
||||
running atomic.Int32
|
||||
connected atomic.Int32
|
||||
stats TransportStats
|
||||
startTime time.Time
|
||||
|
||||
receiveCallback func([]byte)
|
||||
Mu sync.RWMutex
|
||||
|
||||
reconnectAttempts atomic.Int32
|
||||
}
|
||||
|
||||
func NewBaseTransport(config TransportConfig) *BaseTransport {
|
||||
return &BaseTransport{
|
||||
config: config,
|
||||
startTime: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BaseTransport) Start() error {
|
||||
b.running.Store(1)
|
||||
b.startTime = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BaseTransport) Stop() error {
|
||||
b.running.Store(0)
|
||||
b.connected.Store(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BaseTransport) IsRunning() bool {
|
||||
return b.running.Load() == 1
|
||||
}
|
||||
|
||||
func (b *BaseTransport) IsConnected() bool {
|
||||
return b.connected.Load() == 1
|
||||
}
|
||||
|
||||
func (b *BaseTransport) SetConnected(connected bool) {
|
||||
if connected {
|
||||
b.connected.Store(1)
|
||||
} else {
|
||||
b.connected.Store(0)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BaseTransport) Receive(callback func([]byte)) {
|
||||
b.Mu.Lock()
|
||||
defer b.Mu.Unlock()
|
||||
b.receiveCallback = callback
|
||||
}
|
||||
|
||||
func (b *BaseTransport) CallReceive(data []byte) {
|
||||
b.Mu.RLock()
|
||||
cb := b.receiveCallback
|
||||
b.Mu.RUnlock()
|
||||
if cb != nil {
|
||||
cb(data)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BaseTransport) GetSession(accessor func(interface{})) {
|
||||
b.Mu.RLock()
|
||||
defer b.Mu.RUnlock()
|
||||
// This is a helper for subclasses
|
||||
}
|
||||
|
||||
func (b *BaseTransport) Stats() TransportStats {
|
||||
return TransportStats{
|
||||
BytesSent: atomic.LoadUint64(&b.stats.BytesSent),
|
||||
BytesReceived: atomic.LoadUint64(&b.stats.BytesReceived),
|
||||
PacketsSent: atomic.LoadUint64(&b.stats.PacketsSent),
|
||||
PacketsRecv: atomic.LoadUint64(&b.stats.PacketsRecv),
|
||||
Reconnects: uint64(b.reconnectAttempts.Load()),
|
||||
Connected: b.IsConnected(),
|
||||
Uptime: time.Since(b.startTime),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BaseTransport) RecordSend(bytes int) {
|
||||
atomic.AddUint64(&b.stats.BytesSent, uint64(bytes))
|
||||
atomic.AddUint64(&b.stats.PacketsSent, 1)
|
||||
}
|
||||
|
||||
func (b *BaseTransport) RecordReceive(bytes int) {
|
||||
atomic.AddUint64(&b.stats.BytesReceived, uint64(bytes))
|
||||
atomic.AddUint64(&b.stats.PacketsRecv, 1)
|
||||
}
|
||||
|
||||
func (b *BaseTransport) RecordReconnect() {
|
||||
b.reconnectAttempts.Add(1)
|
||||
}
|
||||
|
||||
func (b *BaseTransport) GetConfig() TransportConfig {
|
||||
return b.config
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
package yandex
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"openflux/transport"
|
||||
"openflux/utils"
|
||||
)
|
||||
|
||||
type YandexDocsInfo struct {
|
||||
CookieStr string
|
||||
Token string
|
||||
DocID string
|
||||
CallbackURL string
|
||||
UserID string
|
||||
Origin string
|
||||
Host string
|
||||
WsURL string
|
||||
Permissions map[string]interface{}
|
||||
OpenCmd map[string]interface{}
|
||||
}
|
||||
|
||||
type DocSession struct {
|
||||
Info YandexDocsInfo
|
||||
Conn *websocket.Conn
|
||||
WriteQueue chan []byte
|
||||
UserID string
|
||||
}
|
||||
|
||||
type YandexDocsTransport struct {
|
||||
*transport.BaseTransport
|
||||
|
||||
url string
|
||||
session *DocSession
|
||||
writeMu sync.Mutex
|
||||
|
||||
userCounter atomic.Int32
|
||||
baseUserID string
|
||||
}
|
||||
|
||||
func NewYandexDocsTransport(url string, config transport.TransportConfig) *YandexDocsTransport {
|
||||
t := &YandexDocsTransport{
|
||||
BaseTransport: transport.NewBaseTransport(config),
|
||||
url: url,
|
||||
}
|
||||
t.baseUserID = randUserID()
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *YandexDocsTransport) Start() error {
|
||||
if err := t.BaseTransport.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.baseUserID = randUserID()
|
||||
go t.keepAliveLoop()
|
||||
go t.connectLoop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *YandexDocsTransport) Send(data []byte) error {
|
||||
if !t.IsConnected() {
|
||||
return fmt.Errorf("transport not connected")
|
||||
}
|
||||
|
||||
t.Mu.RLock()
|
||||
session := t.session
|
||||
t.Mu.RUnlock()
|
||||
|
||||
if session == nil {
|
||||
return fmt.Errorf("no active session")
|
||||
}
|
||||
|
||||
select {
|
||||
case session.WriteQueue <- data:
|
||||
t.RecordSend(len(data))
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("write queue full")
|
||||
}
|
||||
}
|
||||
|
||||
func (t *YandexDocsTransport) connectLoop() {
|
||||
for t.IsRunning() {
|
||||
t.connectToDoc(0)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *YandexDocsTransport) connectToDoc(attempt int) {
|
||||
if !t.IsRunning() {
|
||||
return
|
||||
}
|
||||
|
||||
utils.Debugf("[YDOCS] connectToDoc attempt %d/%d", attempt+1, t.GetConfig().MaxReconnectAttempts)
|
||||
|
||||
go func() {
|
||||
t.Mu.Lock()
|
||||
existingSession := t.session
|
||||
t.Mu.Unlock()
|
||||
|
||||
var userID string
|
||||
if existingSession != nil {
|
||||
userID = existingSession.UserID
|
||||
} else {
|
||||
suffix := fmt.Sprintf("%03d", t.userCounter.Add(1)%1000)
|
||||
userID = t.baseUserID + suffix
|
||||
}
|
||||
|
||||
info, err := t.fetchDocInfo(t.url, userID)
|
||||
if err != nil {
|
||||
utils.Debugf("[YDOCS] fetchDocInfo failed: %v", err)
|
||||
t.scheduleReconnect(attempt)
|
||||
return
|
||||
}
|
||||
|
||||
dialer := websocket.Dialer{HandshakeTimeout: 10 * time.Second}
|
||||
headers := http.Header{}
|
||||
headers.Set("User-Agent", "Mozilla/5.0")
|
||||
headers.Set("Origin", info.Origin)
|
||||
headers.Set("Cookie", info.CookieStr)
|
||||
headers.Set("Host", info.Host)
|
||||
|
||||
conn, _, err := dialer.Dial(info.WsURL, headers)
|
||||
if err != nil {
|
||||
utils.Debugf("[YDOCS] WebSocket dial failed: %v", err)
|
||||
t.scheduleReconnect(attempt)
|
||||
return
|
||||
}
|
||||
|
||||
writeQueue := make(chan []byte, t.GetConfig().MaxQueueSize)
|
||||
if existingSession != nil {
|
||||
writeQueue = existingSession.WriteQueue
|
||||
}
|
||||
|
||||
t.Mu.Lock()
|
||||
t.session = &DocSession{Info: info, Conn: conn, WriteQueue: writeQueue, UserID: userID}
|
||||
t.SetConnected(true)
|
||||
t.Mu.Unlock()
|
||||
|
||||
if existingSession == nil {
|
||||
go t.writerLoop()
|
||||
}
|
||||
|
||||
// Authenticate
|
||||
auth1 := fmt.Sprintf(`40{"token":"%s"}`, info.Token)
|
||||
conn.WriteMessage(websocket.TextMessage, []byte(auth1))
|
||||
|
||||
authData := map[string]interface{}{
|
||||
"type": "auth", "docid": info.DocID, "token": "fghhfgsjdgfjs",
|
||||
"user": map[string]interface{}{"id": userID}, "editorType": 0,
|
||||
"lastOtherSaveTime": -1, "permissions": info.Permissions,
|
||||
"openCmd": info.OpenCmd, "coEditingMode": "fast", "jwtOpen": info.Token,
|
||||
}
|
||||
messagePart, _ := json.Marshal([]interface{}{"message", authData})
|
||||
conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("42%s", string(messagePart))))
|
||||
|
||||
// Read loop
|
||||
for t.IsRunning() {
|
||||
_, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
utils.Debugf("[YDOCS] Read error: %v", err)
|
||||
t.SetConnected(false)
|
||||
t.scheduleReconnect(attempt)
|
||||
return
|
||||
}
|
||||
t.handleMessage(message)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (t *YandexDocsTransport) writerLoop() {
|
||||
for t.IsRunning() {
|
||||
t.Mu.Lock()
|
||||
session := t.session
|
||||
t.Mu.Unlock()
|
||||
|
||||
if session == nil || session.Conn == nil {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case packet := <-session.WriteQueue:
|
||||
payload := base64.StdEncoding.EncodeToString(packet)
|
||||
msg := fmt.Sprintf(`42["message",{"type":"cursor","cursor":"18;%s"}]`, payload)
|
||||
|
||||
t.writeMu.Lock()
|
||||
err := session.Conn.WriteMessage(websocket.TextMessage, []byte(msg))
|
||||
t.writeMu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
utils.Debugf("[YDOCS] Write error: %v", err)
|
||||
}
|
||||
default:
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *YandexDocsTransport) keepAliveLoop() {
|
||||
ticker := time.NewTicker(t.GetConfig().KeepAliveInterval)
|
||||
defer ticker.Stop()
|
||||
keepAliveMsg := `42["message",{"type":"cursor","cursor":"18;---KA---"}]`
|
||||
|
||||
for t.IsRunning() {
|
||||
<-ticker.C
|
||||
t.Mu.Lock()
|
||||
session := t.session
|
||||
t.Mu.Unlock()
|
||||
|
||||
if session != nil && session.Conn != nil {
|
||||
t.writeMu.Lock()
|
||||
err := session.Conn.WriteMessage(websocket.TextMessage, []byte(keepAliveMsg))
|
||||
t.writeMu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
utils.Debugf("[YDOCS] Keep-alive failed: %v", err)
|
||||
t.SetConnected(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *YandexDocsTransport) handleMessage(data []byte) {
|
||||
text := string(data)
|
||||
|
||||
if strings.Contains(text, "---KA---") {
|
||||
return
|
||||
}
|
||||
|
||||
if text == "2" {
|
||||
t.Mu.Lock()
|
||||
session := t.session
|
||||
t.Mu.Unlock()
|
||||
if session != nil && session.Conn != nil {
|
||||
session.Conn.WriteMessage(websocket.TextMessage, []byte("3"))
|
||||
}
|
||||
return
|
||||
}
|
||||
if text == "3" {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(text, "saveChanges") || strings.Contains(text, "cursor") {
|
||||
base64Str := t.extractBase64String(text)
|
||||
if base64Str == "" {
|
||||
return
|
||||
}
|
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(base64Str)
|
||||
if err != nil {
|
||||
utils.Debugf("[YDOCS] Base64 decode error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.RecordReceive(len(decoded))
|
||||
t.CallReceive(decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *YandexDocsTransport) extractBase64String(response string) string {
|
||||
if strings.Contains(response, "saveChanges") {
|
||||
marker := `"excelAdditionalInfo":"`
|
||||
left := strings.Index(response, marker) + len(marker)
|
||||
if left < len(marker) {
|
||||
return ""
|
||||
}
|
||||
right := strings.Index(response[left:], `"`)
|
||||
if right == -1 {
|
||||
return ""
|
||||
}
|
||||
return response[left : left+right]
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`"cursor":"[^;]+;([^"]+)"`)
|
||||
matches := re.FindStringSubmatch(response)
|
||||
if len(matches) > 1 {
|
||||
return matches[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (t *YandexDocsTransport) scheduleReconnect(attempt int) {
|
||||
if !t.IsRunning() || attempt >= t.GetConfig().MaxReconnectAttempts {
|
||||
return
|
||||
}
|
||||
|
||||
t.RecordReconnect()
|
||||
delay := time.Duration(float64(t.GetConfig().ReconnectDelay) *
|
||||
math.Pow(t.GetConfig().ReconnectMultiplier, float64(attempt)))
|
||||
|
||||
utils.Debugf("[YDOCS] Reconnecting in %v...", delay)
|
||||
time.Sleep(delay)
|
||||
t.connectToDoc(attempt + 1)
|
||||
}
|
||||
|
||||
func (t *YandexDocsTransport) fetchDocInfo(url, userID string) (YandexDocsInfo, error) {
|
||||
client := &http.Client{
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error { return nil },
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return YandexDocsInfo{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
htmlBytes, _ := io.ReadAll(resp.Body)
|
||||
html := string(htmlBytes)
|
||||
|
||||
var cookies []string
|
||||
for _, c := range resp.Cookies() {
|
||||
cookies = append(cookies, fmt.Sprintf("%s=%s", c.Name, c.Value))
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`<script[^>]*id="client-config"[^>]*>(.*?)</script>`)
|
||||
matches := re.FindStringSubmatch(html)
|
||||
if len(matches) < 2 {
|
||||
return YandexDocsInfo{}, fmt.Errorf("config not found")
|
||||
}
|
||||
|
||||
var config map[string]interface{}
|
||||
json.Unmarshal([]byte(matches[1]), &config)
|
||||
officeAction := config["officeActionData"].(map[string]interface{})
|
||||
editorConfigRaw := officeAction["editor_config"].(map[string]interface{})
|
||||
balancerURL := officeAction["balancer_url"].(string)
|
||||
host := strings.TrimPrefix(balancerURL, "https://")
|
||||
document := editorConfigRaw["document"].(map[string]interface{})
|
||||
|
||||
perms, _ := document["permissions"].(map[string]interface{})
|
||||
if perms == nil {
|
||||
perms = make(map[string]interface{})
|
||||
}
|
||||
|
||||
return YandexDocsInfo{
|
||||
CookieStr: strings.Join(cookies, "; "),
|
||||
Token: editorConfigRaw["token"].(string),
|
||||
DocID: document["key"].(string),
|
||||
Origin: balancerURL,
|
||||
Host: host,
|
||||
WsURL: fmt.Sprintf("wss://%s/2024.1.1-375/doc/%s/c/?EIO=4&transport=websocket", host, document["key"].(string)),
|
||||
Permissions: perms,
|
||||
OpenCmd: map[string]interface{}{
|
||||
"c": "open",
|
||||
"id": document["key"].(string),
|
||||
"userid": userID,
|
||||
"format": document["fileType"],
|
||||
"url": document["url"],
|
||||
"title": document["title"],
|
||||
"lcid": 25,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func randUserID() string {
|
||||
return fmt.Sprintf("%010d", rand.New(rand.NewSource(time.Now().UnixNano())).Intn(1000000000))
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
|
||||
"openflux/network"
|
||||
"openflux/utils"
|
||||
)
|
||||
|
||||
type TunnelLinkEndpoint struct {
|
||||
dispatcher stack.NetworkDispatcher
|
||||
onOutgoingPacket func([]byte)
|
||||
packetIn atomic.Uint64
|
||||
packetOut atomic.Uint64
|
||||
}
|
||||
|
||||
func NewTunnelLinkEndpoint() *TunnelLinkEndpoint {
|
||||
return &TunnelLinkEndpoint{}
|
||||
}
|
||||
|
||||
func (e *TunnelLinkEndpoint) InjectInbound(data []byte) {
|
||||
e.packetIn.Add(1)
|
||||
utils.Debugf("[TUNL] Inject inbound: %s", network.ParsePacketInfo(data))
|
||||
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: buffer.MakeWithData(append([]byte{}, data...)),
|
||||
})
|
||||
e.dispatcher.DeliverNetworkPacket(ipv4.ProtocolNumber, pkt)
|
||||
}
|
||||
|
||||
func (e *TunnelLinkEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
|
||||
n := 0
|
||||
for _, pkt := range pkts.AsSlice() {
|
||||
data := pkt.ToView().ToSlice()
|
||||
e.packetOut.Add(1)
|
||||
fmt.Printf("-> %d bytes - %s\n", len(data), network.ParsePacketInfo(data))
|
||||
if e.onOutgoingPacket != nil {
|
||||
e.onOutgoingPacket(data)
|
||||
}
|
||||
n++
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (e *TunnelLinkEndpoint) MTU() uint32 { return 1500 }
|
||||
func (e *TunnelLinkEndpoint) MaxHeaderLength() uint16 { return 0 }
|
||||
func (e *TunnelLinkEndpoint) LinkAddress() tcpip.LinkAddress { return "\x02\x00\x00\x00\x00\x01" }
|
||||
func (e *TunnelLinkEndpoint) Capabilities() stack.LinkEndpointCapabilities { return stack.CapabilityNone }
|
||||
func (e *TunnelLinkEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
e.dispatcher = dispatcher
|
||||
}
|
||||
func (e *TunnelLinkEndpoint) IsAttached() bool { return e.dispatcher != nil }
|
||||
func (e *TunnelLinkEndpoint) Wait() {}
|
||||
func (e *TunnelLinkEndpoint) ARPHardwareType() header.ARPHardwareType { return header.ARPHardwareNone }
|
||||
func (e *TunnelLinkEndpoint) AddHeader(*stack.PacketBuffer) {}
|
||||
func (e *TunnelLinkEndpoint) Close() {}
|
||||
func (e *TunnelLinkEndpoint) SetMTU(uint32) {}
|
||||
func (e *TunnelLinkEndpoint) SetLinkAddress(tcpip.LinkAddress) {}
|
||||
func (e *TunnelLinkEndpoint) ParseHeader(*stack.PacketBuffer) bool { return true }
|
||||
func (e *TunnelLinkEndpoint) SetOnCloseAction(func()) {}
|
||||
@@ -0,0 +1,221 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
|
||||
"openflux/network"
|
||||
"openflux/utils"
|
||||
)
|
||||
|
||||
type RawSocketEndpoint struct {
|
||||
dispatcher stack.NetworkDispatcher
|
||||
sendFd int
|
||||
recvFd int
|
||||
nicID tcpip.NICID
|
||||
packetIn atomic.Uint64
|
||||
packetOut atomic.Uint64
|
||||
outgoingSYNs sync.Map
|
||||
activePorts sync.Map
|
||||
sendToTransport func([]byte)
|
||||
}
|
||||
|
||||
func NewRawSocketEndpoint(nicID tcpip.NICID) (*RawSocketEndpoint, error) {
|
||||
sendFd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("send socket failed: %v (need root)", err)
|
||||
}
|
||||
|
||||
if err := syscall.SetsockoptInt(sendFd, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1); err != nil {
|
||||
syscall.Close(sendFd)
|
||||
return nil, fmt.Errorf("IP_HDRINCL: %v", err)
|
||||
}
|
||||
|
||||
recvFd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_TCP)
|
||||
if err != nil {
|
||||
syscall.Close(sendFd)
|
||||
return nil, fmt.Errorf("recv socket failed: %v (need root)", err)
|
||||
}
|
||||
|
||||
addr := &syscall.SockaddrInet4{
|
||||
Addr: [4]byte{0, 0, 0, 0},
|
||||
Port: 0,
|
||||
}
|
||||
if err := syscall.Bind(recvFd, addr); err != nil {
|
||||
syscall.Close(sendFd)
|
||||
syscall.Close(recvFd)
|
||||
return nil, fmt.Errorf("bind failed: %v", err)
|
||||
}
|
||||
|
||||
ep := &RawSocketEndpoint{
|
||||
sendFd: sendFd,
|
||||
recvFd: recvFd,
|
||||
nicID: nicID,
|
||||
}
|
||||
|
||||
go ep.readLoop()
|
||||
return ep, nil
|
||||
}
|
||||
|
||||
func (e *RawSocketEndpoint) SetTransportSender(sendFunc func([]byte)) {
|
||||
e.sendToTransport = sendFunc
|
||||
}
|
||||
|
||||
func (e *RawSocketEndpoint) readLoop() {
|
||||
buf := make([]byte, 65535)
|
||||
|
||||
for {
|
||||
n, _, err := syscall.Recvfrom(e.recvFd, buf, 0)
|
||||
if err != nil {
|
||||
if err == syscall.EAGAIN || err == syscall.EWOULDBLOCK {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
utils.Debugf("[RAW-NIC%d] Read error: %v", e.nicID, err)
|
||||
return
|
||||
}
|
||||
if n < 40 {
|
||||
continue
|
||||
}
|
||||
|
||||
protocol := buf[9]
|
||||
flags := buf[33]
|
||||
dstIP := net.IP(buf[16:20])
|
||||
localIP := getLocalIP()
|
||||
|
||||
if protocol == 6 && dstIP.String() == localIP {
|
||||
dstPort := uint16(buf[22])<<8 | uint16(buf[23])
|
||||
|
||||
if _, active := e.activePorts.Load(dstPort); !active {
|
||||
continue
|
||||
}
|
||||
|
||||
if flags == 0x12 {
|
||||
ackNum := uint32(buf[28])<<24 | uint32(buf[29])<<16 | uint32(buf[30])<<8 | uint32(buf[31])
|
||||
synSeq := ackNum - 1
|
||||
|
||||
if _, ok := e.outgoingSYNs.Load(synSeq); !ok {
|
||||
continue
|
||||
}
|
||||
e.outgoingSYNs.Delete(synSeq)
|
||||
}
|
||||
|
||||
pktCopy := make([]byte, n)
|
||||
copy(pktCopy, buf[:n])
|
||||
|
||||
copy(pktCopy[16:20], []byte{10, 10, 10, 2})
|
||||
|
||||
pktCopy[10] = 0
|
||||
pktCopy[11] = 0
|
||||
ipChecksumVal := network.IPChecksum(pktCopy[:20])
|
||||
pktCopy[10] = byte(ipChecksumVal >> 8)
|
||||
pktCopy[11] = byte(ipChecksumVal & 0xFF)
|
||||
|
||||
ipHeaderLen := int(pktCopy[0]&0x0F) * 4
|
||||
tcpHeader := pktCopy[ipHeaderLen:]
|
||||
srcIPBytes := [4]byte{pktCopy[12], pktCopy[13], pktCopy[14], pktCopy[15]}
|
||||
dstIPBytes := [4]byte{pktCopy[16], pktCopy[17], pktCopy[18], pktCopy[19]}
|
||||
tcpHeader[16] = 0
|
||||
tcpHeader[17] = 0
|
||||
tcpChecksumVal := network.TCPChecksum(tcpHeader, srcIPBytes, dstIPBytes)
|
||||
tcpHeader[16] = byte(tcpChecksumVal >> 8)
|
||||
tcpHeader[17] = byte(tcpChecksumVal & 0xFF)
|
||||
|
||||
if e.sendToTransport != nil {
|
||||
e.sendToTransport(pktCopy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *RawSocketEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
|
||||
n := 0
|
||||
for _, pkt := range pkts.AsSlice() {
|
||||
ipPacket := pkt.ToView().ToSlice()
|
||||
if len(ipPacket) < 40 {
|
||||
continue
|
||||
}
|
||||
|
||||
pktCopy := make([]byte, len(ipPacket))
|
||||
copy(pktCopy, ipPacket)
|
||||
|
||||
localIP := getLocalIP()
|
||||
var localIPBytes [4]byte
|
||||
fmt.Sscanf(localIP, "%d.%d.%d.%d", &localIPBytes[0], &localIPBytes[1], &localIPBytes[2], &localIPBytes[3])
|
||||
copy(pktCopy[12:16], localIPBytes[:])
|
||||
|
||||
pktCopy[10] = 0
|
||||
pktCopy[11] = 0
|
||||
ipChecksumVal := network.IPChecksum(pktCopy[:20])
|
||||
pktCopy[10] = byte(ipChecksumVal >> 8)
|
||||
pktCopy[11] = byte(ipChecksumVal & 0xFF)
|
||||
|
||||
ipHeaderLen := int(pktCopy[0]&0x0F) * 4
|
||||
tcpHeader := pktCopy[ipHeaderLen:]
|
||||
srcIPBytes := [4]byte{pktCopy[12], pktCopy[13], pktCopy[14], pktCopy[15]}
|
||||
dstIPBytes := [4]byte{pktCopy[16], pktCopy[17], pktCopy[18], pktCopy[19]}
|
||||
tcpHeader[16] = 0
|
||||
tcpHeader[17] = 0
|
||||
tcpChecksumVal := network.TCPChecksum(tcpHeader, srcIPBytes, dstIPBytes)
|
||||
tcpHeader[16] = byte(tcpChecksumVal >> 8)
|
||||
tcpHeader[17] = byte(tcpChecksumVal & 0xFF)
|
||||
|
||||
srcPort := uint16(tcpHeader[0])<<8 | uint16(tcpHeader[1])
|
||||
|
||||
if tcpHeader[13]&0x02 != 0 {
|
||||
seqNum := uint32(tcpHeader[4])<<24 | uint32(tcpHeader[5])<<16 | uint32(tcpHeader[6])<<8 | uint32(tcpHeader[7])
|
||||
e.outgoingSYNs.Store(seqNum, true)
|
||||
e.activePorts.Store(srcPort, true)
|
||||
}
|
||||
|
||||
if tcpHeader[13]&0x01 != 0 || tcpHeader[13]&0x04 != 0 {
|
||||
dstPort := uint16(tcpHeader[2])<<8 | uint16(tcpHeader[3])
|
||||
e.activePorts.Delete(dstPort)
|
||||
}
|
||||
|
||||
var dst [4]byte
|
||||
copy(dst[:], pktCopy[16:20])
|
||||
|
||||
addr := &syscall.SockaddrInet4{
|
||||
Addr: dst,
|
||||
Port: 0,
|
||||
}
|
||||
|
||||
if err := syscall.Sendto(e.sendFd, pktCopy, 0, addr); err != nil {
|
||||
utils.Debugf("[RAW-NIC%d] Sendto failed: %v", e.nicID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
e.packetOut.Add(1)
|
||||
n++
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (e *RawSocketEndpoint) MTU() uint32 { return 1500 }
|
||||
func (e *RawSocketEndpoint) MaxHeaderLength() uint16 { return 0 }
|
||||
func (e *RawSocketEndpoint) LinkAddress() tcpip.LinkAddress { return "" }
|
||||
func (e *RawSocketEndpoint) Capabilities() stack.LinkEndpointCapabilities { return stack.CapabilityNone }
|
||||
func (e *RawSocketEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
e.dispatcher = dispatcher
|
||||
}
|
||||
func (e *RawSocketEndpoint) IsAttached() bool { return e.dispatcher != nil }
|
||||
func (e *RawSocketEndpoint) Wait() {}
|
||||
func (e *RawSocketEndpoint) ARPHardwareType() header.ARPHardwareType { return header.ARPHardwareNone }
|
||||
func (e *RawSocketEndpoint) AddHeader(*stack.PacketBuffer) {}
|
||||
func (e *RawSocketEndpoint) Close() {
|
||||
syscall.Close(e.sendFd)
|
||||
syscall.Close(e.recvFd)
|
||||
}
|
||||
func (e *RawSocketEndpoint) SetMTU(uint32) {}
|
||||
func (e *RawSocketEndpoint) SetLinkAddress(tcpip.LinkAddress) {}
|
||||
func (e *RawSocketEndpoint) ParseHeader(*stack.PacketBuffer) bool { return true }
|
||||
func (e *RawSocketEndpoint) SetOnCloseAction(func()) {}
|
||||
@@ -0,0 +1,189 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||
|
||||
"openflux/transport"
|
||||
"openflux/utils"
|
||||
)
|
||||
|
||||
type TCPTunnel struct {
|
||||
gvisorStack *stack.Stack
|
||||
tunnelEP *TunnelLinkEndpoint
|
||||
transport transport.Transport
|
||||
isExitNode bool
|
||||
rawEP *RawSocketEndpoint
|
||||
startTime time.Time
|
||||
packetCount atomic.Uint64
|
||||
}
|
||||
|
||||
func NewTCPTunnel(trans transport.Transport, isExitNode bool) *TCPTunnel {
|
||||
t := &TCPTunnel{
|
||||
transport: trans,
|
||||
isExitNode: isExitNode,
|
||||
startTime: time.Now(),
|
||||
}
|
||||
|
||||
utils.Debugf("[TUNNEL] Net stack init...")
|
||||
t.gvisorStack = stack.New(stack.Options{
|
||||
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
|
||||
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol},
|
||||
})
|
||||
|
||||
tunnelEP := NewTunnelLinkEndpoint()
|
||||
tunnelEP.onOutgoingPacket = func(data []byte) {
|
||||
utils.Debugf("[TUNNEL] Outgoing via transport: %d bytes", len(data))
|
||||
trans.Send(data)
|
||||
}
|
||||
t.tunnelEP = tunnelEP
|
||||
|
||||
tunnelNIC := tcpip.NICID(1)
|
||||
if err := t.gvisorStack.CreateNIC(tunnelNIC, tunnelEP); err != nil {
|
||||
utils.Debugf("[TUNNEL] CreateNIC tunnel error: %v", err)
|
||||
}
|
||||
|
||||
if isExitNode {
|
||||
t.setupExitNode(tunnelNIC)
|
||||
} else {
|
||||
t.setupClient(tunnelNIC)
|
||||
}
|
||||
|
||||
trans.Receive(func(data []byte) {
|
||||
tunnelEP.InjectInbound(data)
|
||||
})
|
||||
|
||||
go t.printStats()
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *TCPTunnel) setupExitNode(tunnelNIC tcpip.NICID) {
|
||||
localIP := getLocalIP()
|
||||
utils.Debugf("[TUNNEL] EXIT NODE - Local IP: %s", localIP)
|
||||
|
||||
rawEP, err := NewRawSocketEndpoint(tcpip.NICID(2))
|
||||
if err != nil {
|
||||
utils.Debugf("[TUNNEL] Raw socket error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.rawEP = rawEP
|
||||
rawEP.SetTransportSender(func(data []byte) {
|
||||
t.transport.Send(data)
|
||||
})
|
||||
|
||||
internetNIC := tcpip.NICID(2)
|
||||
if err := t.gvisorStack.CreateNIC(internetNIC, rawEP); err != nil {
|
||||
utils.Debugf("[TUNNEL] CreateNIC internet error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var ipBytes [4]byte
|
||||
fmt.Sscanf(localIP, "%d.%d.%d.%d", &ipBytes[0], &ipBytes[1], &ipBytes[2], &ipBytes[3])
|
||||
internetAddr := tcpip.AddrFrom4(ipBytes)
|
||||
t.gvisorStack.AddProtocolAddress(internetNIC, tcpip.ProtocolAddress{
|
||||
Protocol: ipv4.ProtocolNumber,
|
||||
AddressWithPrefix: tcpip.AddressWithPrefix{
|
||||
Address: internetAddr,
|
||||
PrefixLen: 24,
|
||||
},
|
||||
}, stack.AddressProperties{})
|
||||
|
||||
t.gvisorStack.SetForwardingDefaultAndAllNICs(ipv4.ProtocolNumber, true)
|
||||
t.gvisorStack.AddRoute(tcpip.Route{
|
||||
Destination: header.IPv4EmptySubnet,
|
||||
NIC: internetNIC,
|
||||
})
|
||||
|
||||
tunnelSubnet := tcpip.AddressWithPrefix{
|
||||
Address: tcpip.AddrFrom4([4]byte{10, 10, 10, 0}),
|
||||
PrefixLen: 24,
|
||||
}.Subnet()
|
||||
t.gvisorStack.AddRoute(tcpip.Route{
|
||||
Destination: tunnelSubnet,
|
||||
NIC: tunnelNIC,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *TCPTunnel) setupClient(tunnelNIC tcpip.NICID) {
|
||||
clientAddr := tcpip.AddrFrom4([4]byte{10, 10, 10, 2})
|
||||
t.gvisorStack.AddProtocolAddress(tunnelNIC, tcpip.ProtocolAddress{
|
||||
Protocol: ipv4.ProtocolNumber,
|
||||
AddressWithPrefix: tcpip.AddressWithPrefix{
|
||||
Address: clientAddr,
|
||||
PrefixLen: 24,
|
||||
},
|
||||
}, stack.AddressProperties{})
|
||||
|
||||
t.gvisorStack.AddRoute(tcpip.Route{
|
||||
Destination: header.IPv4EmptySubnet,
|
||||
NIC: tunnelNIC,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *TCPTunnel) DialTCP(address string) (net.Conn, error) {
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve: %w", err)
|
||||
}
|
||||
|
||||
ip := tcpAddr.IP.To4()
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("IPv6 not supported")
|
||||
}
|
||||
|
||||
nic := tcpip.NICID(1)
|
||||
if t.isExitNode {
|
||||
nic = tcpip.NICID(2)
|
||||
}
|
||||
|
||||
conn, err := gonet.DialTCP(t.gvisorStack, tcpip.FullAddress{
|
||||
NIC: nic,
|
||||
Addr: tcpip.AddrFrom4([4]byte{ip[0], ip[1], ip[2], ip[3]}),
|
||||
Port: uint16(tcpAddr.Port),
|
||||
}, ipv4.ProtocolNumber)
|
||||
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func (t *TCPTunnel) ListenTCP(port uint16) (net.Listener, error) {
|
||||
return gonet.ListenTCP(t.gvisorStack, tcpip.FullAddress{
|
||||
NIC: 1,
|
||||
Port: port,
|
||||
}, ipv4.ProtocolNumber)
|
||||
}
|
||||
|
||||
func (t *TCPTunnel) printStats() {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
stats := t.gvisorStack.Stats()
|
||||
utils.Debugf("[STATS] uptime=%v packets=%d connected=%d established=%d retrans=%d",
|
||||
time.Since(t.startTime).Round(time.Second),
|
||||
t.packetCount.Load(),
|
||||
stats.TCP.CurrentConnected.Value(),
|
||||
stats.TCP.CurrentEstablished.Value(),
|
||||
stats.TCP.Retransmits.Value(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func getLocalIP() string {
|
||||
conn, err := net.Dial("udp", "8.8.8.8:80")
|
||||
if err != nil {
|
||||
return "192.168.1.100"
|
||||
}
|
||||
defer conn.Close()
|
||||
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
||||
return localAddr.IP.String()
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
debugLog *log.Logger
|
||||
verbose bool
|
||||
)
|
||||
|
||||
func EnableDebug() {
|
||||
verbose = true
|
||||
debugLog = log.New(os.Stderr, "", log.LstdFlags|log.Lmicroseconds)
|
||||
log.SetFlags(log.LstdFlags | log.Lmicroseconds | log.Lshortfile)
|
||||
}
|
||||
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
if verbose {
|
||||
debugLog.Output(2, fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func IsVerbose() bool {
|
||||
return verbose
|
||||
}
|
||||
Reference in New Issue
Block a user