refactor code

This commit is contained in:
ICKelin
2024-04-03 17:15:19 +08:00
parent f844fca7d1
commit 7600c87eeb
83 changed files with 793 additions and 1346 deletions

3
.gitignore vendored
View File

@@ -12,4 +12,5 @@ research
*.tar.gz
docker-build/gtun/gtun*
docker-build/gtund/gtund
docker-build/gtund/gtund
release

20
build.sh Executable file
View File

@@ -0,0 +1,20 @@
rm -r release
mkdir -p release
mkdir -p release/gtun/etc
mkdir -p release/gtund/etc
DIR=`pwd`
cd src/gtun
GOOS=linux go build -o gtun
mv gtun $DIR/release/gtun/
cd $DIR
cp scripts/install_gtun.sh release/gtun/install.sh
cp -r etc/gtun/* release/gtun/etc
cd src/gtund
GOOS=linux go build -o gtund
mv gtund $DIR/release/gtund/
cd $DIR
cp scripts/install_gtund.sh release/gtund/install.sh
cp -r etc/gtund/* release/gtund/etc

View File

@@ -1,14 +0,0 @@
#!/bin/bash
echo "building gtund...."
GOOS=linux go build -o bin/gtund/gtund cmd/gtund/*.go
echo "builded gtund...."
echo "building gtun...."
GOOS=linux go build -o bin/gtun/gtun-linux_amd64 cmd/gtun/*.go
GOARCH=arm GOOS=linux go build -o bin/gtun/gtun-linux_arm cmd/gtun/*.go
echo "builded gtun...."
cp -r etc/gtun.yaml bin/gtun/
cp -r etc/gtund.yaml bin/gtund/
cp install.sh bin/gtun/

View File

@@ -1,14 +0,0 @@
WORKSPACE=`pwd`
./build_exec.sh
echo "building gtund docker image"
cd docker-build/gtund
cp $WORKSPACE/bin/gtund/gtund .
docker build . -t gtund
echo "builded gtund docker image"
echo "building gtun docker image"
cd $WORKSPACE/docker-build/gtun
cp $WORKSPACE/bin/gtun/gtun-linux_amd64 .
docker build . -t gtun
echo "builded gtun docker image"

View File

@@ -1,42 +0,0 @@
/*
MIT License
Copyright (c) 2018 ICKelin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
DESCRIPTION:
This program is a gtun client for game/ip accelator.
Author: ICKelin
*/
package main
import (
"github.com/ICKelin/gtun/gtun"
)
func main() {
gtun.Main()
}

View File

@@ -1,40 +0,0 @@
/*
MIT License
Copyright (c) 2018 ICKelin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
DESCRIPTION:
This program is a gtun server for game/ip accelator.
Author: ICKelin
*/
package main
import "github.com/ICKelin/gtun/gtund"
func main() {
gtund.Main()
}

View File

@@ -1,65 +0,0 @@
version: '3'
services:
gtun:
build: ./gtun
container_name: gtun
restart: always
network_mode: host
privileged: true
volumes:
- /opt/apps/logs:/opt/logs
environment:
TIME_ZONE: Asia/Shanghai
settings: |
settings:
CN:
proxy_file: "https://www.ipdeny.com/ipblocks/data/countries/us.zone"
route:
- trace_addr: ${CN_SERVER_IP}:${CN_SERVER_TRACE_PORT}
scheme: "kcp"
addr: ${CN_SERVER_IP}:${CN_SERVER_PORT}
auth_key: ""
proxy:
"tproxy_tcp": |
{
"read_timeout": 30,
"write_timeout": 30,
"listen_addr": ":8524",
"rate_limit": 50,
"region": "CN"
}
"tproxy_udp": |
{
"read_timeout": 30,
"write_timeout": 30,
"session_timeout": 30,
"listen_addr": ":8524",
"rate_limit": 50,
"region": "CN"
}
log:
days: 5
level: Debug
path: gtun.log
http_server:
listen_addr: ":9001"
gtund:
build: ./gtund
container_name: gtund
restart: always
network_mode: host
volumes:
- /opt/apps/logs:/logs
environment:
TIME_ZONE: Asia/Shanghai
settings: |
server:
- listen: ":3002"
authKey: "rewrite with your auth key"
scheme: "kcp"
trace: ":3003"
log:
days: 5
level: "debug"
path: "gtund.log"

View File

@@ -1,6 +0,0 @@
FROM ubuntu:18.04
COPY gtun-linux_amd64 /gtun
COPY start.sh /
RUN chmod +x start.sh && chmod +x gtun
RUN mkdir /opt/logs
CMD /start.sh

View File

@@ -1,11 +0,0 @@
#!/usr/bin/env bash
if [ "$TIME_ZONE" != "" ]; then
ln -snf /usr/share/zoneinfo/$TIME_ZONE /etc/localtime && echo $TIME_ZONE > /etc/timezone
fi
#项目的配置文件
if [ "$settings" != "" ]; then
echo "$settings" > /gtun.yaml
fi
/gtun -c /gtun.yaml

View File

@@ -1,6 +0,0 @@
FROM ubuntu:18.04
COPY gtund /
COPY start.sh /
RUN chmod +x start.sh && chmod +x gtund
RUN mkdir /opt/logs
CMD /start.sh

View File

@@ -1,11 +0,0 @@
#!/usr/bin/env bash
if [ "$TIME_ZONE" != "" ]; then
ln -snf /usr/share/zoneinfo/$TIME_ZONE /etc/localtime && echo $TIME_ZONE > /etc/timezone
fi
#项目的配置文件
if [ "$settings" != "" ]; then
echo "$settings" > /gtund.yaml
fi
/gtund -c /gtund.yaml

View File

@@ -1,33 +0,0 @@
settings:
CN:
proxy_file: "https://www.ipdeny.com/ipblocks/data/countries/us.zone"
route:
- trace_addr: ${CN_SERVER_IP}:${CN_SERVER_TRACE_PORT}
scheme: "kcp"
addr: ${CN_SERVER_IP}:${CN_SERVER_PORT}
auth_key: ""
proxy:
"tproxy_tcp": |
{
"read_timeout": 30,
"write_timeout": 30,
"listen_addr": ":8524",
"rate_limit": 50,
"region": "CN"
}
"tproxy_udp": |
{
"read_timeout": 30,
"write_timeout": 30,
"session_timeout": 30,
"listen_addr": ":8524",
"rate_limit": 50,
"region": "CN"
}
log:
days: 5
level: Debug
path: gtun.log
http_server:
listen_addr: ":9001""

16
etc/gtun/gtun.service Normal file
View File

@@ -0,0 +1,16 @@
[Unit]
Description=gtun - ip accelerator base on tproxy
After=network.target auditd.service
[Service]
ExecStart=/opt/apps/gtun/gtun -c /opt/apps/gtun/etc/gtun.yaml
KillMode=process
Restart=always
RestartPreventExitStatus=255
Type=simple
LimitNOFILE=1000000
LimitNPROC=1000000
[Install]
WantedBy=multi-user.target
Alias=gtun.service

7
etc/gtun/gtun.yaml Normal file
View File

@@ -0,0 +1,7 @@
route_file: "/opt/apps/gtun/etc/route.json"
proxy_file: "/opt/apps/gtun/etc/proxy.yaml"
log:
days: 5
level: debug
path: /opt/apps/gtun/logs/gtun.log

28
etc/gtun/proxy.yaml Normal file
View File

@@ -0,0 +1,28 @@
"HK":
tproxy_tcp: |
{
"read_timeout": 30,
"write_timeout": 30,
"listen_addr": ":8524"
}
tproxy_udp: |
{
"read_timeout": 30,
"write_timeout": 30,
"session_timeout": 30,
"listen_addr": ":8524"
}
"US":
tproxy_tcp: |
{
"read_timeout": 30,
"write_timeout": 30,
"listen_addr": ":8525"
}
tproxy_udp: |
{
"read_timeout": 30,
"write_timeout": 30,
"session_timeout": 30,
"listen_addr": ":8525"
}

16
etc/gtund/gtund.service Normal file
View File

@@ -0,0 +1,16 @@
[Unit]
Description=gtund - ip accelerator base on tproxy
After=network.target auditd.service
[Service]
ExecStart=/opt/apps/gtund/gtund -c /opt/apps/gtund/etc/gtund.yaml
KillMode=process
Restart=always
RestartPreventExitStatus=255
Type=simple
LimitNOFILE=1000000
LimitNPROC=1000000
[Install]
WantedBy=multi-user.target
Alias=gtund.service

View File

@@ -11,4 +11,4 @@ server:
log:
days: 5
level: "debug"
path: "gtund.log"
path: "/opt/apps/gtund/logs/gtund.log"

3
go.mod
View File

@@ -7,9 +7,12 @@ require (
github.com/agiledragon/gomonkey/v2 v2.10.1
github.com/astaxie/beego v1.12.3
github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff
github.com/beyond-net/golib v0.0.0-20230917030559-01515040c020
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58
github.com/gogo/protobuf v1.3.2
github.com/radovskyb/watcher v1.0.7
github.com/smartystreets/goconvey v1.8.1
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)

5
go.sum
View File

@@ -64,6 +64,8 @@ github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff/go.mod h1:PhH1ZhyCzHK
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/beyond-net/golib v0.0.0-20230917030559-01515040c020 h1:oPvnpNkZiI7glSP5JZ/9UaGn1dx1jmDTRahh76+x0D0=
github.com/beyond-net/golib v0.0.0-20230917030559-01515040c020/go.mod h1:a6+2fw9h26TRFMwTV5UiEfu7ktXHMY/M7nlAqNUdmi8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
@@ -288,6 +290,8 @@ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB8
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -744,6 +748,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -1,61 +0,0 @@
package gtun
import (
"gopkg.in/yaml.v2"
"os"
)
var gConfig *Config
type Config struct {
Settings map[string]RegionConfig `yaml:"settings"`
HTTPServer HTTPConfig `yaml:"http_server"`
Log Log `yaml:"log"`
}
type HTTPConfig struct {
ListenAddr string `yaml:"listen_addr"`
}
type RegionConfig struct {
Route []RouteConfig `yaml:"route"`
ProxyFile string `yaml:"proxy_file"`
Proxy map[string]string `yaml:"proxy"`
}
type RouteConfig struct {
Region string `yaml:"region"`
TraceAddr string `yaml:"trace_addr"`
Scheme string `yaml:"scheme"`
Addr string `yaml:"addr"`
AuthKey string `yaml:"auth_key"`
}
type Log struct {
Days int64 `yaml:"days"`
Level string `yaml:"level"`
Path string `yaml:"path"`
}
func ParseConfig(path string) (*Config, error) {
content, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return ParseBuffer(content)
}
func ParseBuffer(content []byte) (*Config, error) {
conf := Config{}
err := yaml.Unmarshal(content, &conf)
if err != nil {
return nil, err
}
gConfig = &conf
return &conf, err
}
func GetConfig() *Config {
return gConfig
}

View File

@@ -1,115 +0,0 @@
package gtun
import (
"encoding/json"
"github.com/ICKelin/gtun/gtun/proxy"
"io"
"net/http"
)
type HTTPServer struct {
listenAddr string
}
func NewHTTPServer(listenAddr string) *HTTPServer {
return &HTTPServer{listenAddr: listenAddr}
}
func (s *HTTPServer) ListenAndServe() error {
http.HandleFunc("/meta", loadMeta)
http.HandleFunc("/ip/add", addIP)
http.HandleFunc("/ip/delete", delIP)
return http.ListenAndServe(s.listenAddr, nil)
}
type response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
func loadMeta(w http.ResponseWriter, r *http.Request) {
regionList := make([]string, 0)
regions := GetConfig().Settings
for region, _ := range regions {
regionList = append(regionList, region)
}
type replyBody struct {
Regions []string `json:"regions"`
Cfg *Config
}
body := &replyBody{
Regions: regionList,
Cfg: GetConfig(),
}
reply(w, body)
}
func addIP(w http.ResponseWriter, r *http.Request) {
type req struct {
Region string `json:"region"`
IP string `json:"ip"`
}
var form = req{}
err := bindForm(r, &form)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// add to ipset
err = proxy.AddIP(form.Region, form.IP)
if err != nil {
reply(w, &response{
Code: -1,
Message: err.Error(),
Data: nil,
})
return
}
reply(w, &response{Code: 0, Message: "success"})
}
func delIP(w http.ResponseWriter, r *http.Request) {
type req struct {
Region string `json:"region"`
IP string `json:"ip"`
}
var form = req{}
err := bindForm(r, &form)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// delete from ipset
err = proxy.DelIP(form.Region, form.IP)
if err != nil {
reply(w, &response{
Code: -1,
Message: err.Error(),
Data: nil,
})
return
}
reply(w, &response{Code: 0, Message: "success"})
}
func bindForm(r *http.Request, obj interface{}) error {
cnt, err := io.ReadAll(r.Body)
if err != nil {
return err
}
return json.Unmarshal(cnt, obj)
}
func reply(w http.ResponseWriter, obj interface{}) {
buf, _ := json.Marshal(obj)
_, _ = w.Write(buf)
}

View File

@@ -1,52 +0,0 @@
package gtun
import (
"flag"
"fmt"
"github.com/ICKelin/gtun/gtun/proxy"
"github.com/ICKelin/gtun/gtun/route"
"github.com/ICKelin/gtun/internal/logs"
)
func Main() {
flgConf := flag.String("c", "", "config file")
flag.Parse()
conf, err := ParseConfig(*flgConf)
if err != nil {
fmt.Printf("load config fail: %v\n", err)
return
}
logs.Init(conf.Log.Path, conf.Log.Level, conf.Log.Days)
// run proxy
for region, cfg := range conf.Settings {
// init plugins
err = proxy.Setup(region, cfg.ProxyFile, cfg.Proxy)
if err != nil {
fmt.Printf("set proxy fail: %v\n", err)
return
}
}
// run route and race
raceManager := route.GetTraceManager()
for region, cfg := range conf.Settings {
raceTargets := make([]string, 0)
for _, r := range cfg.Route {
raceTargets = append(raceTargets, r.TraceAddr)
hopConn, err := route.CreateConnection(region, r.Scheme, r.Addr, r.AuthKey)
if err != nil {
fmt.Printf("connect to %s://%s fail: %v\n", r.Scheme, r.Addr, err)
return
}
go hopConn.ConnectNextHop()
}
regionRace := route.NewTrace(region, raceTargets)
raceManager.AddRegionTrace(region, regionRace)
}
raceManager.RunRace()
panic(NewHTTPServer(conf.HTTPServer.ListenAddr).ListenAndServe())
}

View File

@@ -1,5 +0,0 @@
package gtun
import (
_ "github.com/ICKelin/gtun/gtun/proxy"
)

View File

@@ -1,83 +0,0 @@
package proxy
import (
"fmt"
"github.com/ICKelin/gtun/internal/logs"
"github.com/ICKelin/gtun/internal/utils"
"strings"
"sync/atomic"
)
var (
markID = int32(1)
routeTableID = int32(101)
)
func allocateMarkID() int32 {
return atomic.AddInt32(&markID, 1)
}
func allocateRouteTableID() int32 {
return atomic.AddInt32(&routeTableID, 1)
}
func initRedirect(proto, region, redirectPort string) {
setName := ipsetNamePrefix + region
out, err := utils.ExecCmd("ipset", []string{"create", setName, "hash:net"})
if err != nil {
logs.Warn("create ipset fail: %v %s", err, out)
}
out, err = utils.ExecCmd("ipset", []string{"-F", setName})
if err != nil {
logs.Warn("flush ipset fail: %v %s", err, out)
}
markID := allocateMarkID()
routeTable := allocateRouteTableID()
args := fmt.Sprintf("-t mangle -D PREROUTING -p %s -m set --match-set %s dst -j TPROXY --tproxy-mark %d/%d --on-port %s", proto, setName, markID, markID, redirectPort)
out, err = utils.ExecCmd("iptables", strings.Split(args, " "))
if err != nil {
logs.Warn("%s %s %s", args, err, out)
}
args = fmt.Sprintf("-t mangle -A PREROUTING -p %s -m set --match-set %s dst -j TPROXY --tproxy-mark %d/%d --on-port %s", proto, setName, markID, markID, redirectPort)
out, err = utils.ExecCmd("iptables", strings.Split(args, " "))
if err != nil {
logs.Warn("%s %s %s", args, err, out)
}
args = fmt.Sprintf("-t mangle -D OUTPUT -p %s -m set --match-set %s dst -j MARK --set-mark %d", proto, setName, markID)
out, err = utils.ExecCmd("iptables", strings.Split(args, " "))
if err != nil {
logs.Warn("%s %s %s", args, err, out)
}
args = fmt.Sprintf("-t mangle -A OUTPUT -p %s -m set --match-set %s dst -j MARK --set-mark %d", proto, setName, markID)
out, err = utils.ExecCmd("iptables", strings.Split(args, " "))
if err != nil {
logs.Warn("%s %s %s", args, err, out)
}
args = fmt.Sprintf("rule del fwmark %d lookup %d", markID, routeTable)
out, err = utils.ExecCmd("ip", strings.Split(args, " "))
if err != nil {
logs.Warn("%s %s %s", args, err, out)
}
args = fmt.Sprintf("rule add fwmark %d lookup %d", markID, routeTable)
out, err = utils.ExecCmd("ip", strings.Split(args, " "))
if err != nil {
logs.Warn("%s %s %s", args, err, out)
}
args = fmt.Sprintf("ro del local default dev lo table %d", routeTable)
out, err = utils.ExecCmd("ip", strings.Split(args, " "))
if err != nil {
logs.Warn("%s %s %s", args, err, out)
}
args = fmt.Sprintf("ro add local default dev lo table %d", routeTable)
out, err = utils.ExecCmd("ip", strings.Split(args, " "))
if err != nil {
logs.Warn("%s %s %s", args, err, out)
}
}

View File

@@ -1,61 +0,0 @@
package proxy
import (
"bytes"
"net"
"testing"
"time"
)
type dummyConn struct {
readBuf bytes.Buffer
writeBuf bytes.Buffer
}
func (d *dummyConn) Read(b []byte) (n int, err error) {
n = copy(b, d.readBuf.Bytes())
return n, nil
}
func (d *dummyConn) Write(b []byte) (n int, err error) {
return d.writeBuf.Write(b)
}
func (d *dummyConn) Close() error {
d.readBuf.Reset()
d.writeBuf.Reset()
return nil
}
func (d *dummyConn) LocalAddr() net.Addr {
return nil
}
func (d *dummyConn) RemoteAddr() net.Addr {
return nil
}
func (d *dummyConn) SetDeadline(t time.Time) error {
return nil
}
func (d *dummyConn) SetReadDeadline(t time.Time) error {
return nil
}
func (d *dummyConn) SetWriteDeadline(t time.Time) error {
return nil
}
func TestTProxyTCPDoProxy(t *testing.T) {
p := NewTProxyTCP()
cfg := `{}`
err := p.Setup([]byte(cfg))
if err != nil {
t.Error(err)
return
}
conn := &dummyConn{}
p.(*TProxyTCP).doProxy(conn)
}

View File

@@ -1,146 +0,0 @@
package proxy
import (
"encoding/binary"
"encoding/json"
"fmt"
"github.com/ICKelin/gtun/gtun/route"
"github.com/ICKelin/gtun/internal/logs"
"github.com/ICKelin/gtun/internal/proto"
"github.com/ICKelin/gtun/internal/utils"
"github.com/ICKelin/optw/transport"
"io"
"time"
)
func init() {
Register("tun_proxy", NewTunProxy)
}
type TunProxyConfig struct {
Region string `json:"region"`
MTU int `json:"mtu"`
WriteTimeout int `json:"write_timeout"`
ReadTimeout int `json:"read_timeout"`
}
type TunProxy struct {
config TunProxyConfig
dev *utils.Interface
}
func NewTunProxy() Proxy {
return &TunProxy{}
}
func (p *TunProxy) Name() string {
return "tun_proxy"
}
func (p *TunProxy) Setup(cfg json.RawMessage) error {
var config = TunProxyConfig{}
err := json.Unmarshal(cfg, &config)
if err != nil {
return err
}
if config.MTU <= 0 {
return fmt.Errorf("%s invalid mtu", p.Name())
}
dev, err := utils.NewInterface()
if err != nil {
return err
}
err = dev.SetMTU(config.MTU)
if err != nil {
return err
}
err = dev.Up()
if err != nil {
return err
}
p.config = config
p.dev = dev
return nil
}
func (p *TunProxy) ListenAndServe() error {
// tun proxy use only one stream for long live connection
var nextHopStream transport.Stream
var nextHopConn *route.HopInfo
for {
buf, err := p.dev.Read()
if err != nil {
return err
}
if nextHopConn == nil || nextHopConn.IsClosed() {
nextHopConn = route.GetRouteManager().Route(p.config.Region, "")
if nextHopConn == nil {
logs.Warn("route to next hop fail")
continue
}
nextHopStream, err = nextHopConn.OpenStream()
if err != nil {
logs.Warn("open stream fail: %v", err)
continue
}
// encode proxy protocol
bytes := proto.EncodeProxyProtocol("tun_proxy", "", "0", "", "0")
_ = nextHopStream.SetWriteDeadline(time.Now().Add(time.Duration(p.config.WriteTimeout)))
_, err = nextHopStream.Write(bytes)
_ = nextHopStream.SetWriteDeadline(time.Time{})
go p.readFromRemote(nextHopStream)
}
bytes := proto.EncodeData(buf)
nextHopStream.SetWriteDeadline(time.Now().Add(time.Duration(p.config.WriteTimeout)))
_, err = nextHopStream.Write(bytes)
nextHopStream.SetWriteDeadline(time.Time{})
if err != nil {
nextHopStream.Close()
nextHopConn.Close()
logs.Error("stream write fail: %v", err)
}
}
}
func (p *TunProxy) readFromRemote(stream transport.Stream) {
defer stream.Close()
hdr := make([]byte, 2)
for {
nr, err := stream.Read(hdr)
if err != nil {
if err != io.EOF {
logs.Error("read stream fail %v", err)
}
break
}
if nr != 2 {
logs.Error("invalid bodylen: %d", nr)
continue
}
nlen := binary.BigEndian.Uint16(hdr)
buf := make([]byte, nlen)
stream.SetReadDeadline(time.Now().Add(time.Duration(p.config.ReadTimeout)))
_, err = io.ReadFull(stream, buf)
stream.SetReadDeadline(time.Time{})
if err != nil {
logs.Error("read stream body fail: %v", err)
break
}
_, err = p.dev.Write(buf)
if err != nil {
logs.Warn("write to dev fail: %v", err)
return
}
}
}

View File

@@ -1,130 +0,0 @@
package proxy
import (
"bufio"
"encoding/json"
"fmt"
"github.com/ICKelin/gtun/internal/logs"
"github.com/ICKelin/gtun/internal/utils"
"net/http"
"os"
"strings"
"time"
)
var errRegistered = fmt.Errorf("already registered")
var errNotRegister = fmt.Errorf("proxy not register")
var ipsetNamePrefix = "GTUN-"
// Proxy defines Proxies, such as tproxy_tcp, tproxy_udp,ip_tun, ip_wireguard
type Proxy interface {
Name() string
Setup(cfg json.RawMessage) error
ListenAndServe() error
}
var registerProxy = make(map[string]func() Proxy)
func Register(name string, constructor func() Proxy) error {
if _, ok := registerProxy[name]; ok {
return errRegistered
}
registerProxy[name] = constructor
return nil
}
func Setup(region, ruleFile string, proxyConfigs map[string]string) error {
for name, config := range proxyConfigs {
constructor := registerProxy[name]
if constructor == nil {
return errNotRegister
}
p := constructor()
err := p.Setup([]byte(config))
if err != nil {
return err
}
AddFromFile(region, ruleFile)
go p.ListenAndServe()
}
return nil
}
func AddIP(region string, ip string) error {
out, err := utils.ExecCmd("ipset", []string{"add", ipsetNamePrefix + region, ip})
if err != nil {
return fmt.Errorf("add to ipset fail: %v %s", err, out)
}
return nil
}
func DelIP(region, ip string) error {
out, err := utils.ExecCmd("ipset", []string{"del", ipsetNamePrefix + region, ip})
if err != nil {
return fmt.Errorf("del from ipset fail: %v %s", err, out)
}
return nil
}
func AddApp(region, appName string) error {
AddFromFile(region, appName)
return nil
}
func AddFromFile(region, file string) {
ips := loadIPs(file)
for _, ip := range ips {
AddIP(region, ip)
}
}
func loadIPs(file string) []string {
if len(file) <= 0 {
return nil
}
ips := make([]string, 0)
var br *bufio.Reader
if strings.HasPrefix(file, "http://") || strings.HasPrefix(file, "https://") {
// load from url
req, err := http.NewRequest("GET", file, nil)
if err != nil {
logs.Warn("load file fail: %v", err)
return nil
}
cli := http.Client{
Timeout: time.Second * 120,
}
resp, err := cli.Do(req)
if err != nil {
logs.Warn("load file fail: %v", err)
return nil
}
defer resp.Body.Close()
br = bufio.NewReader(resp.Body)
} else {
// load from file
fp, err := os.Open(file)
if err != nil {
logs.Warn("open rule file fail: %v", err)
return nil
}
defer fp.Close()
br = bufio.NewReader(fp)
}
for {
line, _, err := br.ReadLine()
if err != nil {
break
}
ips = append(ips, string(line))
}
return ips
}

View File

@@ -1,105 +0,0 @@
package route
import (
"github.com/ICKelin/gtun/internal/logs"
"strings"
"sync"
"github.com/ICKelin/optw/transport"
)
var routeManager = &Manager{
regionHops: make(map[string][]*HopInfo),
raceManager: GetTraceManager(),
}
type Manager struct {
raceManager *TraceManager
regionHopsMu sync.RWMutex
regionHops map[string][]*HopInfo
}
type HopInfo struct {
transport.Conn
}
func GetRouteManager() *Manager {
return routeManager
}
func (routeManager *Manager) Route(region, dip string) *HopInfo {
routeManager.regionHopsMu.RLock()
defer routeManager.regionHopsMu.RUnlock()
regionHops, ok := routeManager.regionHops[region]
if !ok {
return nil
}
if len(regionHops) <= 0 {
return nil
}
bestNode := routeManager.raceManager.GetBestNode(region)
bestIP := strings.Split(bestNode, ":")[0]
for i := 0; i < len(regionHops); i++ {
hop := regionHops[i]
if hop.IsClosed() {
logs.Warn("%s %s is closed", region, hop.RemoteAddr())
continue
}
if len(bestIP) != 0 {
// use only ip address for the same node
// TODO: use scheme://ip:port
hopIP := strings.Split(hop.RemoteAddr().String(), ":")[0]
if bestIP == hopIP {
logs.Debug("best ip match %s", bestIP)
return hop
}
}
}
logs.Warn("use random hop")
hash := 0
for _, c := range dip {
hash += int(c)
}
hop := regionHops[hash%len(regionHops)]
if hop == nil || hop.IsClosed() {
return nil
}
return hop
}
func (routeManager *Manager) AddRoute(region string, hop *HopInfo) {
routeManager.regionHopsMu.Lock()
defer routeManager.regionHopsMu.Unlock()
regionHops := routeManager.regionHops[region]
if regionHops == nil {
regionHops = make([]*HopInfo, 0)
}
regionHops = append(regionHops, hop)
routeManager.regionHops[region] = regionHops
}
func (routeManager *Manager) DeleteRoute(region string, hop *HopInfo) {
routeManager.regionHopsMu.Lock()
defer routeManager.regionHopsMu.Unlock()
regionHops := routeManager.regionHops[region]
if regionHops == nil {
return
}
hops := make([]*HopInfo, 0, len(regionHops))
for _, s := range regionHops {
if s.RemoteAddr().String() == hop.RemoteAddr().String() {
continue
}
hops = append(hops, s)
}
routeManager.regionHops[region] = hops
}

View File

@@ -1,191 +0,0 @@
package route
import (
"github.com/ICKelin/optw/transport"
"github.com/agiledragon/gomonkey/v2"
. "github.com/smartystreets/goconvey/convey"
"net"
"testing"
)
func TestManager_AddRoute(t *testing.T) {
Convey("Test add route", t, func() {
Convey("add first hop for region", func() {
GetRouteManager().AddRoute("test_region1", &HopInfo{})
So(len(GetRouteManager().regionHops), ShouldEqual, 1)
So(len(GetRouteManager().regionHops["test_region1"]), ShouldEqual, 1)
})
Convey("add second hop", func() {
GetRouteManager().AddRoute("test_region1", &HopInfo{})
So(len(GetRouteManager().regionHops), ShouldEqual, 1)
So(len(GetRouteManager().regionHops["test_region1"]), ShouldEqual, 2)
})
})
}
func TestManager_DeleteRoute(t *testing.T) {
Convey("Test delete route", t, func() {
Convey("delete un exist region", func() {
GetRouteManager().DeleteRoute("not-found", nil)
})
Convey("delete exist region with empty hops", func() {
GetRouteManager().regionHops["region1"] = make([]*HopInfo, 0)
GetRouteManager().DeleteRoute("region1", nil)
})
Convey("delete exist region with hops", func() {
newHopInfo := &HopInfo{Conn: &mockConn{}}
gomonkey.ApplyMethod(newHopInfo.Conn, "RemoteAddr", func(hopInfo transport.Conn) net.Addr {
return &net.TCPAddr{
IP: net.IPv4(127, 0, 0, 1),
Port: 10000,
Zone: "",
}
})
GetRouteManager().AddRoute("region1", newHopInfo)
So(len(GetRouteManager().regionHops["region1"]), ShouldEqual, 1)
GetRouteManager().DeleteRoute("region1", newHopInfo)
So(len(GetRouteManager().regionHops["region1"]), ShouldEqual, 0)
})
Convey("delete not exist hops", func() {
newHopInfo := &HopInfo{Conn: &mockConn{}}
i := 0
gomonkey.ApplyMethod(newHopInfo.Conn, "RemoteAddr", func(hopInfo transport.Conn) net.Addr {
i += 1
return &net.TCPAddr{
IP: net.IPv4(127, 0, byte(i), 1),
Port: 10000,
Zone: "",
}
})
deleteHopInfo := &HopInfo{Conn: &mockConn{}}
gomonkey.ApplyMethod(deleteHopInfo.Conn, "RemoteAddr", func(hopInfo transport.Conn) net.Addr {
i += 1
return &net.TCPAddr{
IP: net.IPv4(127, 0, byte(i), 1),
Port: 10000,
Zone: "",
}
})
GetRouteManager().AddRoute("region1", newHopInfo)
So(len(GetRouteManager().regionHops["region1"]), ShouldEqual, 1)
GetRouteManager().DeleteRoute("region1", deleteHopInfo)
So(len(GetRouteManager().regionHops["region1"]), ShouldEqual, 1)
})
})
}
func TestManager_Route(t *testing.T) {
Convey("Test route", t, func() {
Convey("nil next hop region", func() {
hop := GetRouteManager().Route("not-found", "")
So(hop, ShouldBeNil)
GetRouteManager().regionHops["region1"] = make([]*HopInfo, 0)
hop = GetRouteManager().Route("region1", "")
So(hop, ShouldBeNil)
})
Convey("next hop is closed", func() {
hop := &HopInfo{Conn: &mockConn{}}
gomonkey.ApplyMethod(hop.Conn, "IsClosed", func(conn transport.Conn) bool {
return true
})
gomonkey.ApplyMethod(hop.Conn, "RemoteAddr", func(hopInfo transport.Conn) net.Addr {
return &net.TCPAddr{
IP: net.IPv4(127, 0, byte(0), 1),
Port: 10000,
Zone: "",
}
})
GetRouteManager().AddRoute("region1", hop)
routeHop := GetRouteManager().Route("region1", "")
So(routeHop, ShouldBeNil)
})
Convey("best ip not match", func() {
hop := &HopInfo{Conn: &mockConn{}}
gomonkey.ApplyMethod(hop.Conn, "IsClosed", func(conn transport.Conn) bool {
return false
})
gomonkey.ApplyMethod(hop.Conn, "RemoteAddr", func(hopInfo transport.Conn) net.Addr {
return &net.TCPAddr{
IP: net.IPv4(127, 0, byte(0), 1),
Port: 10000,
Zone: "",
}
})
gomonkey.ApplyMethod(GetRouteManager().raceManager, "GetBestNode", func(manager *TraceManager) string {
return "192.168.1.1:9000"
})
GetRouteManager().AddRoute("region2", hop)
routeHop := GetRouteManager().Route("region2", "")
// use random
So(routeHop, ShouldNotBeNil)
So(routeHop.RemoteAddr().String(), ShouldEqual, "127.0.0.1:10000")
})
Convey("best ip match", func() {
hop := &HopInfo{Conn: &mockConn{}}
gomonkey.ApplyMethod(hop.Conn, "IsClosed", func(conn transport.Conn) bool {
return false
})
gomonkey.ApplyMethod(hop.Conn, "RemoteAddr", func(hopInfo transport.Conn) net.Addr {
return &net.TCPAddr{
IP: net.IPv4(127, 0, byte(0), 1),
Port: 10000,
Zone: "",
}
})
gomonkey.ApplyMethod(GetRouteManager().raceManager, "GetBestNode", func(manager *TraceManager) string {
return "127.0.0.1:10000"
})
GetRouteManager().AddRoute("region3", hop)
routeHop := GetRouteManager().Route("region3", "")
So(routeHop, ShouldNotBeNil)
So(routeHop.RemoteAddr().String(), ShouldEqual, "127.0.0.1:10000")
})
Convey("random next hop", func() {})
})
}
type mockConn struct{}
func (m *mockConn) OpenStream() (transport.Stream, error) {
//TODO implement me
panic("implement me")
}
func (m *mockConn) AcceptStream() (transport.Stream, error) {
//TODO implement me
panic("implement me")
}
func (m *mockConn) Close() {
//TODO implement me
panic("implement me")
}
func (m *mockConn) IsClosed() bool {
//TODO implement me
panic("implement me")
}
func (m *mockConn) RemoteAddr() net.Addr {
//TODO implement me
panic("implement me")
}
var _ transport.Conn = &mockConn{}

View File

@@ -1,9 +0,0 @@
echo "add no proxy address"
ipset create GTUN-NOPROXY hash:net
iptables -t mangle -I PREROUTING -m set --match-set GTUN-NOPROXY dst -j ACCEPT
iptables -t mangle -I OUTPUT -m set --match-set GTUN-NOPROXY dst -j ACCEPT
echo "start gtun"
nohup ./gtun-linux-amd64 -c gtun.yaml &
echo "start success."

7
scripts/install_gtun.sh Executable file
View File

@@ -0,0 +1,7 @@
systemctl stop gtun
GTUN_DIR="/opt/apps/gtun"
mkdir -p $GTUN_DIR/logs
cp -r . $GTUN_DIR
cp etc/gtun.service /lib/systemd/system/
systemctl daemon-reload
systemctl start gtun

7
scripts/install_gtund.sh Executable file
View File

@@ -0,0 +1,7 @@
systemctl stop gtun
GTUND_DIR="/opt/apps/gtund"
mkdir -p $GTUND_DIR/logs
cp -r . $GTUND_DIR
cp etc/gtund.service /lib/systemd/system/
systemctl daemon-reload
systemctl start gtun

77
src/gtun/config/config.go Normal file
View File

@@ -0,0 +1,77 @@
package config
import (
"encoding/json"
"gopkg.in/yaml.v2"
"os"
)
var gConfig *Config
type Config struct {
RouteFile string `yaml:"route_file"`
ProxyFile string `yaml:"proxy_file"`
Log Log `yaml:"log"`
}
type RouteConfig struct {
Region string `yaml:"region" json:"region"`
Scheme string `yaml:"scheme" json:"scheme"`
Server string `yaml:"server" json:"server"`
Trace string `yaml:"trace" json:"trace"`
AuthKey string `yaml:"auth_key" json:"auth_key"`
}
type Log struct {
Days int64 `yaml:"days"`
Level string `yaml:"level"`
Path string `yaml:"path"`
}
func Parse(path string) (*Config, error) {
content, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return ParseBuffer(content)
}
func ParseBuffer(content []byte) (*Config, error) {
conf := Config{}
err := yaml.Unmarshal(content, &conf)
if err != nil {
return nil, err
}
gConfig = &conf
return &conf, err
}
func ParseProxy(proxyFile string) (map[string]map[string]string, error) {
content, err := os.ReadFile(proxyFile)
if err != nil {
return nil, err
}
proxies := make(map[string]map[string]string)
err = yaml.Unmarshal(content, &proxies)
if err != nil {
return nil, err
}
return proxies, nil
}
func ParseRoute(routeFile string) ([]*RouteConfig, error) {
content, err := os.ReadFile(routeFile)
if err != nil {
return nil, err
}
var routeConfig = make([]*RouteConfig, 0)
err = json.Unmarshal(content, &routeConfig)
if err != nil {
return nil, err
}
return routeConfig, nil
}

50
src/gtun/main.go Normal file
View File

@@ -0,0 +1,50 @@
package main
import (
"flag"
"fmt"
"github.com/ICKelin/gtun/src/gtun/config"
"github.com/ICKelin/gtun/src/gtun/proxy"
"github.com/ICKelin/gtun/src/gtun/route"
"github.com/ICKelin/gtun/src/internal/logs"
)
func main() {
flgConf := flag.String("c", "", "config file")
flag.Parse()
conf, err := config.Parse(*flgConf)
if err != nil {
fmt.Printf("load config fail: %v\n", err)
return
}
logs.Init(conf.Log.Path, conf.Log.Level, conf.Log.Days)
routeConfig, err := config.ParseRoute(conf.RouteFile)
if err != nil {
fmt.Printf("parse node config fail: %v", err)
return
}
proxyConfig, err := config.ParseProxy(conf.ProxyFile)
if err != nil {
fmt.Printf("parse proxy config fail: %v", err)
return
}
// run route
err = route.Setup(routeConfig)
if err != nil {
fmt.Printf("route setup fail: %v", err)
return
}
// run proxy
err = proxy.Serve(proxyConfig)
if err != nil {
fmt.Printf("proxy setup fail: %v", err)
return
}
// TODO: watch for config file changes
select {}
}

56
src/gtun/proxy/proxy.go Normal file
View File

@@ -0,0 +1,56 @@
package proxy
import (
"encoding/json"
"fmt"
"github.com/ICKelin/gtun/src/internal/logs"
)
var errRegistered = fmt.Errorf("already registered")
var errNotRegister = fmt.Errorf("proxy not register")
// Proxy defines Proxies, such as tproxy_tcp, tproxy_udp,ip_tun, ip_wireguard
type Proxy interface {
Name() string
Setup(region string, cfg json.RawMessage) error
ListenAndServe() error
}
var registerProxy = make(map[string]func() Proxy)
func Register(name string, constructor func() Proxy) error {
if _, ok := registerProxy[name]; ok {
return errRegistered
}
registerProxy[name] = constructor
return nil
}
func Serve(proxyConfig map[string]map[string]string) error {
for region, p := range proxyConfig {
logs.Debug("region %s proxy config %s", region, p)
err := setup(region, p)
if err != nil {
fmt.Printf("region[%s] setup proxy fail: %v\n", region, err)
return err
}
}
return nil
}
func setup(region string, proxyConfigs map[string]string) error {
for name, config := range proxyConfigs {
constructor := registerProxy[name]
if constructor == nil {
return errNotRegister
}
p := constructor()
err := p.Setup(region, []byte(config))
if err != nil {
return err
}
go p.ListenAndServe()
}
return nil
}

View File

@@ -2,14 +2,12 @@ package proxy
import (
"encoding/json"
"fmt"
"github.com/ICKelin/gtun/gtun/route"
"github.com/ICKelin/gtun/internal/logs"
"github.com/ICKelin/gtun/internal/proto"
"github.com/ICKelin/gtun/internal/utils"
"github.com/ICKelin/gtun/src/gtun/route"
"github.com/ICKelin/gtun/src/internal/logs"
"github.com/ICKelin/gtun/src/internal/proto"
"github.com/ICKelin/gtun/src/internal/utils"
"io"
"net"
"strings"
"sync"
"syscall"
"time"
@@ -29,7 +27,6 @@ type TProxyTCPConfig struct {
WriteTimeout int `json:"write_timeout"`
ListenAddr string `json:"listen_addr"`
RateLimit int `json:"rate_limit"`
Region string `json:"region"`
}
type TProxyTCP struct {
@@ -59,22 +56,23 @@ func (p *TProxyTCP) Name() string {
return "tproxy_tcp"
}
func (p *TProxyTCP) Setup(cfgContent json.RawMessage) error {
func (p *TProxyTCP) Setup(region string, cfgContent json.RawMessage) error {
var cfg = TProxyTCPConfig{}
err := json.Unmarshal(cfgContent, &cfg)
if err != nil {
return nil
}
err = p.initConfig(cfg)
logs.Debug("region[%s] proxy %s config %s", region, p.Name(), string(cfgContent))
err = p.initConfig(region, cfg)
if err != nil {
return err
}
return p.initRedirect()
return nil
}
func (p *TProxyTCP) initConfig(cfg TProxyTCPConfig) error {
func (p *TProxyTCP) initConfig(region string, cfg TProxyTCPConfig) error {
tcpReadTimeout := cfg.ReadTimeout
if tcpReadTimeout <= 0 {
tcpReadTimeout = defaultTCPTimeout
@@ -88,7 +86,7 @@ func (p *TProxyTCP) initConfig(cfg TProxyTCPConfig) error {
rateLimit := utils.NewRateLimit()
rateLimit.SetRateLimit(int64(cfg.RateLimit * 1024 * 1024))
p.region = cfg.Region
p.region = region
p.listenAddr = cfg.ListenAddr
p.writeTimeout = time.Duration(tcpWriteTimeout) * time.Second
p.readTimeout = time.Duration(tcpReadTimeout) * time.Second
@@ -102,34 +100,28 @@ func (p *TProxyTCP) initConfig(cfg TProxyTCPConfig) error {
return nil
}
func (p *TProxyTCP) initRedirect() error {
ipPort := strings.Split(p.listenAddr, ":")
if len(ipPort) != 2 {
return fmt.Errorf("invalid listen addr")
}
initRedirect("tcp", p.region, ipPort[1])
return nil
}
func (p *TProxyTCP) ListenAndServe() error {
listener, err := net.Listen("tcp", p.listenAddr)
if err != nil {
logs.Error("%v", err)
return err
}
// set socket with ip transparent option
file, err := listener.(*net.TCPListener).File()
if err != nil {
logs.Error("%v", err)
return err
}
defer file.Close()
err = syscall.SetsockoptInt(int(file.Fd()), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
if err != nil {
logs.Error("%v", err)
return err
}
logs.Info("region[%s] %s listen %s", p.region, p.Name(), p.listenAddr)
for {
conn, err := listener.Accept()
if err != nil {

View File

@@ -5,14 +5,13 @@ import (
"encoding/binary"
"encoding/json"
"fmt"
"github.com/ICKelin/gtun/gtun/route"
"github.com/ICKelin/gtun/internal/logs"
"github.com/ICKelin/gtun/internal/proto"
"github.com/ICKelin/gtun/internal/utils"
"github.com/ICKelin/gtun/src/gtun/route"
"github.com/ICKelin/gtun/src/internal/logs"
"github.com/ICKelin/gtun/src/internal/proto"
utils2 "github.com/ICKelin/gtun/src/internal/utils"
"github.com/ICKelin/optw/transport"
"io"
"net"
"strings"
"sync"
"syscall"
"time"
@@ -42,7 +41,6 @@ type TProxyUDPConfig struct {
SessionTimeout int `json:"session_timeout"`
ListenAddr string `json:"listen_addr"`
RateLimit int `json:"rate_limit"`
Region string `json:"region"`
}
type TProxyUDP struct {
@@ -60,7 +58,7 @@ type TProxyUDP struct {
udpSessions map[string]*udpSession
udpsessLock sync.Mutex
ratelimit *utils.RateLimit
ratelimit *utils2.RateLimit
}
func NewTProxyUDP() Proxy {
@@ -71,22 +69,22 @@ func (p *TProxyUDP) Name() string {
return "tproxy_udp"
}
func (p *TProxyUDP) Setup(cfgContent json.RawMessage) error {
func (p *TProxyUDP) Setup(region string, cfgContent json.RawMessage) error {
var cfg = TProxyUDPConfig{}
err := json.Unmarshal(cfgContent, &cfg)
if err != nil {
return nil
}
err = p.initConfig(cfg)
err = p.initConfig(region, cfg)
if err != nil {
return err
}
return p.initRedirect()
return nil
}
func (p *TProxyUDP) initConfig(cfg TProxyUDPConfig) error {
func (p *TProxyUDP) initConfig(region string, cfg TProxyUDPConfig) error {
readTimeout := cfg.ReadTimeout
if readTimeout <= 0 {
readTimeout = defaultUDPTimeout
@@ -102,10 +100,10 @@ func (p *TProxyUDP) initConfig(cfg TProxyUDPConfig) error {
sessionTimeout = defaultUDPSessionTimeout
}
rateLimit := utils.NewRateLimit()
rateLimit := utils2.NewRateLimit()
rateLimit.SetRateLimit(int64(cfg.RateLimit * 1024 * 1024))
p.region = cfg.Region
p.region = region
p.listenAddr = cfg.ListenAddr
p.writeTimeout = time.Duration(writeTimeout) * time.Second
p.readTimeout = time.Duration(readTimeout) * time.Second
@@ -116,16 +114,6 @@ func (p *TProxyUDP) initConfig(cfg TProxyUDPConfig) error {
return nil
}
func (p *TProxyUDP) initRedirect() error {
ipPort := strings.Split(p.listenAddr, ":")
if len(ipPort) != 2 {
return fmt.Errorf("invalid listen addr")
}
initRedirect("udp", p.region, ipPort[1])
return nil
}
// ListenAndServe listens an udp port, since that we use tproxy to
// redirect traffic to this listened udp port
// so the socket should set to ip transparent option
@@ -177,6 +165,7 @@ func (p *TProxyUDP) ListenAndServe() error {
}
func (p *TProxyUDP) serve(lconn *net.UDPConn) error {
logs.Info("region[%s] %s listen %s", p.region, p.Name(), p.listenAddr)
go p.recycleSession()
buf := make([]byte, 64*1024)
oob := make([]byte, 1024)
@@ -283,7 +272,7 @@ func (p *TProxyUDP) doProxy(stream transport.Stream, sessionKey string, fromaddr
break
}
err = utils.SendUDPViaRaw(p.rawfd, fromaddr, toaddr, buf)
err = utils2.SendUDPViaRaw(p.rawfd, fromaddr, toaddr, buf)
if err != nil {
logs.Error("send via raw socket fail: %v", err)
}

View File

@@ -2,33 +2,41 @@ package route
import (
"fmt"
"github.com/ICKelin/gtun/src/internal/logs"
"github.com/ICKelin/optw/transport/transport_api"
"time"
"github.com/ICKelin/gtun/internal/logs"
"github.com/ICKelin/optw/transport"
)
type ConnectionConfig struct {
Region string
ServerAddr string
AuthKey string
var cm = &connManager{regionConn: map[string][]*conn{}}
type connManager struct {
regionConn map[string][]*conn
}
type Connection struct {
func (cm *connManager) startConn() {
for _, conns := range cm.regionConn {
for _, conn := range conns {
go conn.connect()
}
}
}
type conn struct {
dialer transport.Dialer
region string
scheme string
serverAddr string
}
func CreateConnection(region, scheme, serverAddr, authKey string) (*Connection, error) {
func newConn(region, scheme, serverAddr, authKey string) (*conn, error) {
dialer, err := transport_api.NewDialer(scheme, serverAddr, authKey)
if err != nil {
return nil, err
}
return &Connection{
return &conn{
dialer: dialer,
region: region,
scheme: scheme,
@@ -36,7 +44,7 @@ func CreateConnection(region, scheme, serverAddr, authKey string) (*Connection,
}, nil
}
func (c *Connection) ConnectNextHop() {
func (c *conn) connect() {
for {
conn, err := c.dialer.Dial()
if err != nil {
@@ -46,23 +54,28 @@ func (c *Connection) ConnectNextHop() {
}
logs.Info("connect to %s success", c.String())
hopConn := &HopInfo{Conn: conn}
// add next hop connection to route
GetRouteManager().AddRoute(c.region, hopConn)
routeEle := &routeItem{
region: c.region,
scheme: c.scheme,
serverAddr: c.serverAddr,
Conn: conn,
}
GetRouteManager().addRoute(c.region, routeEle)
tick := time.NewTicker(time.Second * 1)
for range tick.C {
if hopConn.IsClosed() {
if conn.IsClosed() {
break
}
}
// delete next hop connection to route
GetRouteManager().DeleteRoute(c.region, hopConn)
GetRouteManager().deleteRoute(c.region, routeEle)
logs.Warn("reconnect %s", c.String())
}
}
func (c *Connection) String() string {
func (c *conn) String() string {
return fmt.Sprintf("regions[%s] %s://%s", c.region, c.scheme, c.serverAddr)
}

146
src/gtun/route/route.go Normal file
View File

@@ -0,0 +1,146 @@
package route
import (
"fmt"
"github.com/ICKelin/gtun/src/gtun/config"
"github.com/ICKelin/gtun/src/internal/logs"
"github.com/ICKelin/optw/transport"
"sync"
)
var routeManager = &Manager{
tm: tm,
cm: cm,
routeTable: make(map[string][]*routeItem),
}
type routeItem struct {
region string
scheme string
serverAddr string
transport.Conn
}
type Manager struct {
tm *traceManager
cm *connManager
routeTableMu sync.Mutex
routeTable map[string][]*routeItem
}
func GetRouteManager() *Manager {
return routeManager
}
func (routeManager *Manager) Route(region, dip string) transport.Conn {
regionRoutes, ok := routeManager.routeTable[region]
if !ok {
return nil
}
if len(regionRoutes) <= 0 {
return nil
}
bestNode, ok := routeManager.tm.getRegionBestTarget(region)
if ok {
bestAddr := bestNode.serverAddr
for i := 0; i < len(regionRoutes); i++ {
it := regionRoutes[i]
if it.IsClosed() {
logs.Warn("%s %s is closed", region, it.RemoteAddr())
continue
}
if len(bestAddr) != 0 {
// scheme://ip:port match
if bestAddr == it.serverAddr &&
bestNode.scheme == it.scheme {
logs.Debug("region[%s] best node match %s://%s",
region, it.scheme, bestAddr)
return it
}
}
}
} else {
logs.Warn("no best node for region[%s]", region)
}
logs.Warn("region[%s] use random hop", region)
hash := 0
for _, c := range dip {
hash += int(c)
}
hop := regionRoutes[hash%len(regionRoutes)]
if hop == nil || hop.IsClosed() {
return nil
}
return hop
}
func (routeManager *Manager) addRoute(region string, item *routeItem) {
routeManager.routeTableMu.Lock()
defer routeManager.routeTableMu.Unlock()
regionItems := routeManager.routeTable[region]
if regionItems == nil {
regionItems = make([]*routeItem, 0)
}
regionItems = append(regionItems, item)
routeManager.routeTable[region] = regionItems
}
func (routeManager *Manager) deleteRoute(region string, item *routeItem) {
routeManager.routeTableMu.Lock()
defer routeManager.routeTableMu.Unlock()
regionItems := routeManager.routeTable[region]
if regionItems == nil {
return
}
conns := make([]*routeItem, 0, len(regionItems))
for _, it := range regionItems {
if it == item {
continue
}
conns = append(conns, it)
}
routeManager.routeTable[region] = conns
}
func Setup(routeConfig []*config.RouteConfig) error {
for _, cfg := range routeConfig {
conn, err := newConn(cfg.Region, cfg.Scheme, cfg.Server, cfg.AuthKey)
if err != nil {
fmt.Printf("region[%s] connect to %s://%s fail: %v",
cfg.Region, cfg.Scheme, cfg.Server, cfg.AuthKey)
return err
}
cm.regionConn[cfg.Region] = append(cm.regionConn[cfg.Region], conn)
t, ok := tm.regionTrace[cfg.Region]
if !ok {
logs.Debug("add region[%s] trace", cfg.Region)
t = newTrace(cfg.Region)
tm.regionTrace[cfg.Region] = t
} else {
logs.Debug("region[%s] trace exist", cfg.Region)
}
t.addTarget(traceTarget{
traceAddr: cfg.Trace,
serverAddr: cfg.Server,
scheme: cfg.Scheme,
})
}
go tm.startTrace()
go cm.startConn()
return nil
}

View File

@@ -2,82 +2,81 @@ package route
import (
"encoding/binary"
"github.com/ICKelin/gtun/src/internal/logs"
"math"
"net"
"sync"
"time"
"github.com/ICKelin/gtun/internal/logs"
)
var traceManager = &TraceManager{
regionTrace: make(map[string]*Trace),
var tm = &traceManager{
regionTrace: make(map[string]*trace),
}
func GetTraceManager() *TraceManager {
return traceManager
type traceManager struct {
regionTrace map[string]*trace
}
// TraceManager manage region trace
type TraceManager struct {
regionTraceMu sync.Mutex
regionTrace map[string]*Trace
}
func (m *TraceManager) RunRace() {
for _, race := range m.regionTrace {
go race.Run()
func (m *traceManager) addTraces(traces map[string]*trace) {
for k, _ := range traces {
m.regionTrace[k] = traces[k]
}
}
// AddRegionTrace adds a trace instance for region
func (m *TraceManager) AddRegionTrace(region string, race *Trace) {
m.regionTraceMu.Lock()
defer m.regionTraceMu.Unlock()
m.regionTrace[region] = race
func (m *traceManager) startTrace() {
for _, trace := range m.regionTrace {
logs.Debug("region[%s] running trace", trace.region)
go trace.runTraceJob()
}
}
// GetBestNode returns the highest score of region target region
func (m *TraceManager) GetBestNode(region string) string {
regionRace := m.regionTrace[region]
if regionRace == nil {
return ""
func (m *traceManager) getRegionBestTarget(region string) (traceTarget, bool) {
regionTrace := m.regionTrace[region]
if regionTrace == nil {
logs.Warn("trace for region[%s] not exist", region)
return traceTarget{}, false
}
return regionRace.GetBestNode()
return regionTrace.getBestNode()
}
// Trace is a region trace instance
type Trace struct {
type traceTarget struct {
traceAddr string
serverAddr string
scheme string
}
type trace struct {
region string
targets []string
targets []traceTarget
targetScoreMu sync.Mutex
targetScore map[string]float64
targetScore map[traceTarget]float64
totalRtt int32
}
// NewTrace return trace instance
func NewTrace(region string, targets []string) *Trace {
return &Trace{
func newTrace(region string) *trace {
return &trace{
region: region,
targets: targets,
targetScoreMu: sync.Mutex{},
targetScore: make(map[string]float64),
targetScore: make(map[traceTarget]float64),
}
}
// Run trace job
func (r *Trace) Run() {
r.trace()
func (t *trace) addTarget(target traceTarget) {
t.targets = append(t.targets, target)
}
func (t *trace) runTraceJob() {
t.trace()
tick := time.NewTicker(time.Second * 120)
for range tick.C {
r.trace()
t.trace()
}
}
func (r *Trace) trace() {
for _, target := range r.targets {
raddr, err := net.ResolveUDPAddr("udp", target)
func (t *trace) trace() {
for i, target := range t.targets {
raddr, err := net.ResolveUDPAddr("udp", target.traceAddr)
if err != nil {
logs.Error("resolve udp addr: %v", err)
continue
@@ -117,21 +116,20 @@ func (r *Trace) trace() {
diff := time.Now().Sub(beg).Milliseconds()
rtt += int(diff)
}
remoteAddr := rconn.RemoteAddr().String()
rconn.Close()
if rtt < 0 {
rtt = math.MaxInt
}
lossRank := r.calcLossScore(loss)
delayRank := r.calcRttScore(rtt)
lossRank := t.calcLossScore(loss)
delayRank := t.calcRttScore(rtt)
score := lossRank + delayRank
logs.Debug("region[%s] %s loss %d rtt %d lossRank %.4f delayRank %.4f score %.4f",
r.region, target, loss, rtt, lossRank, delayRank, score)
r.targetScoreMu.Lock()
r.targetScore[remoteAddr] = score
r.targetScoreMu.Unlock()
t.region, target, loss, rtt, lossRank, delayRank, score)
t.targetScoreMu.Lock()
t.targetScore[t.targets[i]] = score
t.targetScoreMu.Unlock()
}
}
@@ -140,7 +138,7 @@ func (r *Trace) trace() {
// f(p) = 35+(1.25-p)x10 0.75% < p <= 1.25%,
// f(p) = 30+(2.25-p)x5 1.25% < p <= 2.25%,
// f(p) = 30+(p-2.25)x5x-1 p > 2.25%
func (r *Trace) calcLossScore(loss int) float64 {
func (t *trace) calcLossScore(loss int) float64 {
lossRate := float64(loss) / 60
if 0 < lossRate && lossRate <= 0.75 {
return 40 + (0.75-lossRate)*13
@@ -154,7 +152,7 @@ func (r *Trace) calcLossScore(loss int) float64 {
return 50
}
func (r *Trace) calcRttScore(rtt int) float64 {
func (t *trace) calcRttScore(rtt int) float64 {
avgRtt := float64(rtt) / 60
if 0 < avgRtt && avgRtt < 45.0 {
return 50
@@ -171,17 +169,18 @@ func (r *Trace) calcRttScore(rtt int) float64 {
return 0
}
// GetBestNode of all the targets of trace
func (r *Trace) GetBestNode() string {
r.targetScoreMu.Lock()
defer r.targetScoreMu.Unlock()
func (t *trace) getBestNode() (traceTarget, bool) {
t.targetScoreMu.Lock()
defer t.targetScoreMu.Unlock()
bestScore := float64(-1)
node := ""
for target, score := range r.targetScore {
if bestScore < score {
var node traceTarget
var ok bool = false
for target, score := range t.targetScore {
if bestScore*1000 < score*1000 {
bestScore = score
node = target
ok = true
}
}
return node
return node, ok
}

View File

@@ -1,4 +1,4 @@
package gtund
package main
import (
"encoding/json"

View File

@@ -1,14 +1,13 @@
package gtund
package main
import (
"flag"
"fmt"
"github.com/ICKelin/gtun/src/internal/logs"
"net/http"
_ "net/http/pprof"
"github.com/ICKelin/optw/transport/transport_api"
"github.com/ICKelin/gtun/internal/logs"
)
var version = ""
@@ -17,7 +16,7 @@ func init() {
go http.ListenAndServe(":6060", nil)
}
func Main() {
func main() {
flgVersion := flag.Bool("v", false, "print version")
flgConf := flag.String("c", "", "config file")
flag.Parse()

View File

@@ -1,17 +1,17 @@
package gtund
package main
import (
"encoding/binary"
"encoding/json"
"fmt"
"github.com/ICKelin/gtun/internal/utils"
"github.com/ICKelin/gtun/src/internal/logs"
"github.com/ICKelin/gtun/src/internal/proto"
"github.com/ICKelin/gtun/src/internal/utils"
"io"
"net"
"sync"
"time"
"github.com/ICKelin/gtun/internal/logs"
"github.com/ICKelin/gtun/internal/proto"
"github.com/ICKelin/optw/transport"
)

View File

@@ -1,7 +1,7 @@
package gtund
package main
import (
"github.com/ICKelin/gtun/internal/logs"
"github.com/ICKelin/gtun/src/internal/logs"
"net"
)

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
// +build windows
package logs

View File

@@ -24,13 +24,13 @@
//
// Use it like this:
//
// log.Trace("trace")
// log.Info("info")
// log.Warn("warning")
// log.Debug("debug")
// log.Critical("critical")
// log.Trace("trace")
// log.Info("info")
// log.Warn("warning")
// log.Debug("debug")
// log.Critical("critical")
//
// more docs http://beego.me/docs/module/logs.md
// more docs http://beego.me/docs/module/logs.md
package logs
import (

View File

@@ -2,7 +2,7 @@ package utils
import (
"fmt"
"github.com/ICKelin/gtun/internal/logs"
"github.com/ICKelin/gtun/src/internal/logs"
"os/exec"
"runtime"
"time"

38
src/node-agent/config.go Normal file
View File

@@ -0,0 +1,38 @@
package main
import (
"gopkg.in/yaml.v3"
"os"
)
type Config struct {
GtunTemplateFile string `yaml:"gtun_template_file"`
GtunService string `yaml:"gtun_service"`
GtunConfigFilePath string `yaml:"gtun_config_file_path"`
GtunDynamicConfigFile string `yaml:"gtun_dynamic_config_file"`
Log Log `yaml:"log"`
}
type Log struct {
Days int64 `yaml:"days"`
Level string `yaml:"level"`
Path string `yaml:"path"`
}
func ParseConfig(path string) (*Config, error) {
content, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return ParseBuffer(content)
}
func ParseBuffer(content []byte) (*Config, error) {
conf := Config{}
err := yaml.Unmarshal(content, &conf)
if err != nil {
return nil, err
}
return &conf, err
}

View File

@@ -0,0 +1,9 @@
log:
days: 5
level: Debug
path: gtun.log
gtun_template_file: "/opt/apps/gtun/gtun.template"
gtun_service: "gtun.service"
gtun_config_file_path: "/opt/apps/gtun/gtun.yaml"
gtun_dynamic_config_file: "/opt/apps/gtun/node.json"

59
src/node-agent/main.go Normal file
View File

@@ -0,0 +1,59 @@
package main
import (
"flag"
"fmt"
"github.com/beyond-net/golib/logs"
"github.com/radovskyb/watcher"
"time"
)
var (
confPath = ""
)
func main() {
flag.StringVar(&confPath, "c", "", "config file path")
flag.Parse()
cfg, err := ParseConfig(confPath)
if err != nil {
fmt.Printf("parse config fail: %v\n", err)
return
}
logs.Init(cfg.Log.Path, cfg.Log.Level, 5)
logs.Info("%+v", cfg)
w := watcher.New()
err = w.Add(cfg.GtunDynamicConfigFile)
if err != nil {
logs.Error("watch %s fail: %v", cfg.GtunDynamicConfigFile)
return
}
go func() {
for {
select {
case event := <-w.Event:
logs.Info("file %s modify", event.FileInfo.Name())
switch event.FileInfo.Name() {
case cfg.GtunDynamicConfigFile:
// TODO: reload gtun
default:
}
case err := <-w.Error:
logs.Warn("file watcher error occurs: %v", err)
case <-w.Closed:
return
}
}
}()
err = w.Start(time.Millisecond * 100)
if err != nil {
logs.Warn("file watcher start error: %v", err)
}
select {}
}

90
src/node-agent/service.go Normal file
View File

@@ -0,0 +1,90 @@
package main
import (
"bytes"
"fmt"
"github.com/beyond-net/golib/logs"
"os"
"os/exec"
"text/template"
"time"
)
const (
gtunConfigFile = "/opt/apps/gtun/gtun.yaml"
gtunConfigTemplate = "../etc/gtun.template"
gtunService = "gtun.service"
)
type GtunConfigItem struct {
Region string
Scheme string
ServerIP string
ServerTracePort int
ServerPort int
ListenPort int
Rate int
}
func ReloadGtun(config []*GtunConfigItem) error {
// render config
tpl, err := template.ParseFiles(gtunConfigTemplate)
if err != nil {
return err
}
br := &bytes.Buffer{}
err = tpl.Execute(br, config)
if err != nil {
return err
}
return ReloadService(gtunConfigFile, br.String(), gtunService)
}
func ReloadService(configFile, configContent, service string) error {
// backup config file
_, err := os.Stat(configFile)
reloadSuccess := false
if err == nil {
backupFile := fmt.Sprintf("%s.%d", gtunConfigFile, time.Now().UnixMicro())
_, err = exec.Command("cp", []string{gtunConfigFile, backupFile}...).CombinedOutput()
if err != nil {
return err
}
// recover
defer func() {
if !reloadSuccess {
_, err := exec.Command("cp", []string{backupFile, gtunConfigFile}...).CombinedOutput()
if err != nil {
logs.Error("recover config file fail: %v", err)
}
}
}()
}
// write new config
fp, err := os.Create(configFile)
if err != nil {
return err
}
defer fp.Close()
_, err = fp.Write([]byte(configContent))
if err != nil {
return err
}
// restart service
err = RestartService(service)
if err != nil {
return err
}
reloadSuccess = true
return nil
}
func RestartService(service string) error {
_, err := exec.Command("systemctl", []string{"restart", service}...).CombinedOutput()
return err
}

View File

@@ -0,0 +1,26 @@
package main
import "testing"
func TestBootGtun(t *testing.T) {
ReloadGtun([]*GtunConfigItem{
{
Region: "CN",
Scheme: "kcp",
ServerIP: "127.0.0.1",
ServerTracePort: 3003,
ServerPort: 3002,
ListenPort: 8524,
Rate: 50,
},
{
Region: "US",
Scheme: "kcp",
ServerIP: "127.0.0.1",
ServerTracePort: 4003,
ServerPort: 4002,
ListenPort: 8525,
Rate: 50,
},
})
}

View File

@@ -1,7 +0,0 @@
echo "add no proxy address"
iptables -t mangle -D PREROUTING -m set --match-set GTUN-NOPROXY dst -j ACCEPT
iptables -t mangle -D OUTPUT -m set --match-set GTUN-NOPROXY dst -j ACCEPT
ipset destroy GTUN-NOPROXY
echo "stop gtun"
killall gtun-linux_amd64