// tunnel-client — 零依赖隧道客户端，Go 静态编译，全架构兼容
// 编译: GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o tuncli
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"sync"
	"time"
)

const (
	Magic        = 0xACE50713
	Version      = 1
	CmdAuth      = 0x01
	CmdAuthOk    = 0x02
	CmdAuthFail  = 0x03
	CmdConnect   = 0x10
	CmdConnectOk = 0x11
	CmdConnectFail = 0x12
	CmdData      = 0x20
	CmdClose     = 0x30
	CmdPing      = 0x40
	CmdPong      = 0x50
)

func makeFrame(cmd uint8, stream uint32, payload []byte) []byte {
	buf := make([]byte, 14+len(payload))
	binary.BigEndian.PutUint32(buf[0:4], Magic)
	buf[4] = Version
	buf[5] = cmd
	binary.BigEndian.PutUint32(buf[6:10], stream)
	binary.BigEndian.PutUint32(buf[10:14], uint32(len(payload)))
	copy(buf[14:], payload)
	return buf
}

func readFrame(r io.Reader) (cmd uint8, stream uint32, payload []byte, err error) {
	header := make([]byte, 14)
	if _, err = io.ReadFull(r, header); err != nil {
		return
	}
	magic := binary.BigEndian.Uint32(header[0:4])
	if magic != Magic {
		err = fmt.Errorf("bad magic: %08x", magic)
		return
	}
	cmd = header[5]
	stream = binary.BigEndian.Uint32(header[6:10])
	length := binary.BigEndian.Uint32(header[10:14])
	if length > 0 {
		payload = make([]byte, length)
		_, err = io.ReadFull(r, payload)
	}
	return
}

func makeTokenHash(token string) []byte {
	mac := hmac.New(sha256.New, []byte(token))
	mac.Write([]byte("tunnel-auth"))
	return mac.Sum(nil)
}

func writeFrame(conn net.Conn, cmd uint8, stream uint32, payload []byte) error {
	_, err := conn.Write(makeFrame(cmd, stream, payload))
	return err
}

func main() {
	host := "tunnel.ltzy.top"
	port := 9999
	token := "2f4354f1642c634a0f75db5d8037e840"

	if len(os.Args) > 1 {
		host = os.Args[1]
	}
	if len(os.Args) > 2 {
		fmt.Sscanf(os.Args[2], "%d", &port)
	}
	if len(os.Args) > 3 {
		token = os.Args[3]
	}

	log.SetFlags(log.Ltime)
	log.SetPrefix("[tuncli] ")

	for {
		addr := fmt.Sprintf("%s:%d", host, port)
		log.Printf("连接 %s ...", addr)

		conn, err := net.DialTimeout("tcp", addr, 15*time.Second)
		if err != nil {
			log.Printf("连接失败: %v，3秒后重连", err)
			time.Sleep(3 * time.Second)
			continue
		}

		log.Printf("已连接，认证...")
		writeFrame(conn, CmdAuth, 0, makeTokenHash(token))
		cmd, _, payload, err := readFrame(conn)
		if err != nil {
			log.Printf("认证失败: %v", err)
			conn.Close()
			time.Sleep(3 * time.Second)
			continue
		}
		if cmd == CmdAuthFail {
			log.Printf("认证失败: %s", string(payload))
			conn.Close()
			time.Sleep(3 * time.Second)
			continue
		}
		if cmd != CmdAuthOk {
			log.Printf("认证异常: cmd=%d", cmd)
			conn.Close()
			time.Sleep(3 * time.Second)
			continue
		}
		log.Printf("认证成功")

		streams := make(map[uint32]net.Conn)
		var mu sync.Mutex
		lastPing := time.Now()

		for {
			conn.SetReadDeadline(time.Now().Add(30 * time.Second))
			cmd, stream, payload, err := readFrame(conn)
			if err != nil {
				if ne, ok := err.(net.Error); ok && ne.Timeout() {
					if time.Since(lastPing) > 60*time.Second {
						writeFrame(conn, CmdPing, 0, nil)
						lastPing = time.Now()
					}
					continue
				}
				log.Printf("断开: %v", err)
				break
			}
			lastPing = time.Now()

			switch cmd {
			case CmdConnect:
				addr := string(payload)
				log.Printf("代理: %s", addr)
				tc, err := net.DialTimeout("tcp", addr, 15*time.Second)
				if err != nil {
					log.Printf("连接失败 %s: %v", addr, err)
					writeFrame(conn, CmdConnectFail, stream, []byte(err.Error()))
					continue
				}
				mu.Lock()
				streams[stream] = tc
				mu.Unlock()
				writeFrame(conn, CmdConnectOk, stream, nil)
				// 从目标读取数据，通过 CMD_DATA 发送回服务端
				go func(sid uint32, tc net.Conn) {
					buf := make([]byte, 65536)
					for {
						n, err := tc.Read(buf)
						if n > 0 {
							writeFrame(conn, CmdData, sid, buf[:n])
						}
						if err != nil {
							writeFrame(conn, CmdClose, sid, nil)
							mu.Lock()
							delete(streams, sid)
							mu.Unlock()
							tc.Close()
							return
						}
					}
				}(stream, tc)

			case CmdData:
				mu.Lock()
				tc := streams[stream]
				mu.Unlock()
				if tc != nil {
					_, err := tc.Write(payload)
					if err != nil {
						mu.Lock()
						delete(streams, stream)
						mu.Unlock()
						tc.Close()
						writeFrame(conn, CmdClose, stream, nil)
					}
				}

			case CmdClose:
				mu.Lock()
				tc := streams[stream]
				delete(streams, stream)
				mu.Unlock()
				if tc != nil {
					tc.Close()
				}

			case CmdPing:
				writeFrame(conn, CmdPong, 0, nil)
			}
		}

		mu.Lock()
		for _, tc := range streams {
			tc.Close()
		}
		streams = make(map[uint32]net.Conn)
		mu.Unlock()
		conn.Close()
		log.Printf("3秒后重连...")
		time.Sleep(3 * time.Second)
	}
}