Initial commit

This commit is contained in:
ilya
2026-06-18 04:01:27 +03:00
commit 61d7329926
13 changed files with 1317 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
.DS_Store
output*
build*
+15
View File
@@ -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
)
+12
View File
@@ -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=
+69
View File
@@ -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())
}
}
+92
View File
@@ -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)
}
Executable
BIN
View File
Binary file not shown.
+103
View File
@@ -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()
}
+143
View File
@@ -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
}
+375
View File
@@ -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))
}
+67
View File
@@ -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()) {}
+221
View File
@@ -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()) {}
+189
View File
@@ -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()
}
+28
View File
@@ -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
}