Commit 7a28cead authored by wei.xuan's avatar wei.xuan

v0.0.3

parent 90c6e60d
......@@ -2,4 +2,8 @@
echo-cli
dist/config.yaml
dist
majora-cli
\ No newline at end of file
majora-cli
bin
nohup.out
release
./majora
\ No newline at end of file
export GO111MODULE=on
export GOPROXY="https://goproxy.cn,https://goproxy.io,direct"
LDFLAGS := -s -w
DATE=$(shell date +"%Y-%m-%d")
BUILDINFO := -X main.Version=v0.0.3 -X main.Date=$(DATE)
all:
env CGO_ENABLED=0 go build -trimpath -ldflags '-w -s $(BUILDINFO)' -o bin/majora
clean:
rm -fr majora-go
rm -fr majora
\ No newline at end of file
export PATH := $(GOPATH)/bin:$(PATH)
export GO111MODULE=on
LDFLAGS := -s -w
DATE=$(shell date +"%Y-%m-%d")
version=v0.0.3
BUILDINFO := -X main.Version=$(version) -X main.Date=$(DATE)
os-archs=darwin:amd64 darwin:arm64 freebsd:386 freebsd:amd64 linux:386 linux:amd64 linux:arm linux:arm64 windows:386 windows:amd64 linux:mips64 linux:mips64le
all: build
build: app
app:
@$(foreach n, $(os-archs),\
os=$(shell echo "$(n)" | cut -d : -f 1);\
arch=$(shell echo "$(n)" | cut -d : -f 2);\
gomips=$(shell echo "$(n)" | cut -d : -f 3);\
target_suffix=$${os}_$${arch};\
echo "Build $${os}-$${arch}...";\
env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags '-w -s $(BUILDINFO)' -o ./release/majora_$${target_suffix};\
echo "Build $${os}-$${arch} done";\
)
@mv ./release/majora_windows_386 ./release/majora_windows_386.exe
@mv ./release/majora_windows_amd64 ./release/majora_windows_amd64.exe
\ No newline at end of file
# 本地编译
# 安装
> 从v0.0.3 版本开始,支持通过配置文件的方式启动
```
make build
```
## 下载地址
打开 [](https://oss.virjar.com/majora/bin) 选择 最新的版本
![](download.png)
![](download2.png)
# 运行
## 解压
![img_1.png](start.png)
## 启动
```
./majora-cli -natServer "127.0.0.1:5879"
nohup bash start.sh >> std.log 2>&1 &
```
成功的界面
![img_2.png](start2.png)
# 查看帮助
```
./majora-cli -h
Usage of ./majora-cli:
-account string
account (default "unknown")
-debugPort string
debugPort (default "127.0.0.1:6060")
-log int
log logLevel (default 1)
-natServer string
natServer (default "majora.virjar.com:5879")
-pprof
enable pprof
# 配置文件说明
> 配置文件 默认是 同级目录的 majora.ini 可按需自行修改
```
> 说明
- -account 标识当前用户 默认是unknown
- -debugPort 可忽略 用于性能分析
- -natServer 指定natserver 默认是 majora.virjar.com:5879
- -log 指定log level
# 各平台二进制
放在dist 目录下
# 启动成功界面
![](img.png)
\ No newline at end of file
; 绑定的nat server 地址
; 线上是 majora.virjar.com:5879 测试是 aoba.vip:5879
tunnel_addr = aoba.vip:5879
;一般情况下无需修改
dns_server = 114.114.114.114:53
;bind to local ip 针对多网卡模式
local_ip = 192.168.0.100
; 是否关闭自动更新
disable_update = false
; 日志级别
log_level = 1
; 服务端异常时的重连间隔
reconn_interval = 5s
; 不指定时 会随机生成
client_id =
[extra]
account = superman
[redial]
; 重播的命令
command = /bin/bash
; 重播的执行脚本
exec_path = /root/ppp_auto_with_auth.sh
; 重播的周期
redial_duration = 5m
; 重播执行的时间
wait_time = 10s
```
\ No newline at end of file
#!/usr/bin/env bash
export GO111MODULE="on"
export GOPROXY="https://goproxy.cn,https://goproxy.io,direct"
export CGO_ENABLE=1
DATE=$(date "+%Y%m%d-%H%M%S")
mkdir "$1"
BINARY=majora
VERSION=$1
GOVER=latest
BUILDINFO="-X main.Version=$VERSION -X main.Date=$DATE"
EXTFLAG="--extldflags \"-static -fpic\""
OUTDIR=dist/$1
OUT=$OUTDIR/$BINARY-$1
TOKEN=Aau6PvRI4o0OTYrgpQHzxG7qEDkADx6CaUAJV2
TOKENONLINE=456734sdlasysdhf293r23r
echo "$OUTDIR"
echo "$OUT"
build_local() {
echo "build local..."
go build -a -trimpath -ldflags "-w -s $BUILDINFO" -o $BINARY
if [ $? -ne 0 ]; then
echo "build local fail"
exit 1
fi
echo "build success"
}
build_release() {
xgo -x -out "$OUT" -goproxy $GOPROXY -go $GOVER -ldflags="-s -w $EXTFLAG $BUILDINFO" /home/xuan/code/gcode/majora-go
}
upload() {
sudo chown -R xuan dist
cd "$OUTDIR"
for file in `ls | grep majora`; do
newfile=$(echo "${file}" | sed 's/-10.12//g;s/-4.0//g')
echo "uploading $newfile ..."
curl -F file=@"${file}" -F filename="${newfile}" -F token=$TOKEN http://81.70.224.147:10010/version
curl -F file=@"${file}" -F filename="${newfile}" -F token=$TOKENONLINE https://oss.virjar.com/majora/bin
done
touch latest.txt
echo "$VERSION" > latest.txt
curl -F file=@latest.txt -F token=$TOKEN http://81.70.224.147:10010/version
curl -F file=@latest.txt -F token=$TOKENONLINE https://oss.virjar.com/majora/bin
}
build_local
build_release
upload
\ No newline at end of file
......@@ -2,28 +2,32 @@ package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"math/rand"
"net"
"net/http"
_ "net/http/pprof" //nolint:gosec
"os"
"runtime"
"time"
"virjar.com/majora-go/client"
"virjar.com/majora-go/common"
"virjar.com/majora-go/infra"
"virjar.com/majora-go/logger"
"virjar.com/majora-go/updater"
"virjar.com/majora-go/model"
)
var (
configure string
logLevel int
pprof bool
pprofPort int
natServer string
account string
dnsServer string
disableDNS bool
localAddr string
disableUpdate bool
)
......@@ -35,29 +39,28 @@ var (
func init() {
rand.Seed(time.Now().UnixNano())
flag.StringVar(&configure, "conf", "", "./majora -c path/to/your/majora.ini")
flag.IntVar(&logLevel, "log", 1, "log logLevel")
flag.BoolVar(&pprof, "pprof", false, "enable pprof")
flag.IntVar(&pprofPort, "pprof", 0, "enable pprof")
flag.StringVar(&natServer, "natServer", common.DefNatAddr, "natServer")
flag.StringVar(&account, "account", "unknown", "account")
flag.StringVar(&dnsServer, "dnsServer", common.DNSServer, "custom dns server")
flag.BoolVar(&disableDNS, "disableDns", false, "disable default dns server")
flag.StringVar(&localAddr, "localIp", "", "bind local ip")
flag.BoolVar(&disableUpdate, "disableUpdate", false, "disable self update")
flag.Parse()
}
func initPprof() {
if !pprof {
return
func initial(cfg *model.Configure) {
logger.SetLogLevel(cfg.LogLevel)
if cfg.PprofPort > 0 {
go func() {
log.Printf("enable pprof: %s", common.PprofAddr)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", cfg.PprofPort), nil))
}()
}
go func() {
log.Printf("enable pprof: %s", common.PprofAddr)
log.Fatal(http.ListenAndServe(common.PprofAddr, nil))
}()
// for android
if !disableDNS {
if len(cfg.DNSServer) > 0 {
net.DefaultResolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
......@@ -66,25 +69,45 @@ func initPprof() {
}
logger.Info().Msgf("use custom dns server %s", dnsServer)
}
}
func update() {
if disableUpdate {
return
if !cfg.DisableUpdate {
if _, err := infra.Update("majora", Version); err != nil {
logger.Error().Msgf("check update error %+s", err.Error())
}
}
}
if _, err := updater.Update("majora", Version); err != nil {
logger.Error().Msgf("check update error %+s", err.Error())
}
func parseFromCmd(cfg *model.Configure) {
cfg.TunnelAddr = natServer
cfg.LogLevel = logLevel
cfg.DNSServer = dnsServer
cfg.LocalAddr = localAddr
cfg.DisableUpdate = disableUpdate
// 先兼容吧
cfg.Extra.Account = account
cfg.PprofPort = pprofPort
}
func main() {
initPprof()
logger.SetLogLevel(logLevel)
func cli() {
cfg := model.NewDefMajoraConf()
if len(configure) > 0 {
cfg = model.InitConf(configure)
} else {
parseFromCmd(cfg)
}
initial(cfg)
logger.Info().Msgf("current Version %s, build at %s", Version, Date)
update()
logger.Info().Msgf("hostinfo os:%s, arch:%s", runtime.GOOS, runtime.GOARCH)
client.NewClient(client.WithNatServerAddr(natServer),
client.WithLocalIP(localAddr),
client.WithAccount(account))
cfgInfo, _ := json.Marshal(cfg)
logger.Info().Msgf("config info:%s", string(cfgInfo))
client.NewClientWithConf(cfg)
}
//main start
func main() {
if len(os.Args) > 1 && os.Args[1] == "version" {
fmt.Println(Version)
os.Exit(0)
}
cli()
}
......@@ -3,70 +3,92 @@ package client
import (
"bufio"
"errors"
"fmt"
"io"
"net"
"os"
"sync"
"sync/atomic"
"time"
"github.com/google/uuid"
"virjar.com/majora-go/common"
"virjar.com/majora-go/logger"
"virjar.com/majora-go/model"
"virjar.com/majora-go/protocol"
)
func NewOptions() *Options {
return &Options{
NatHostPort: common.DefNatAddr,
ClientID: uuid.New().String(),
ExtraInfo: make(Extra),
}
}
type Client struct {
Options *Options
config *model.Configure
localAddr net.Addr
natTunnel atomic.Value
Codec protocol.ICodec
connStore sync.Map
cleanup chan struct{}
}
func NewClient(opts ...Option) {
options := NewOptions()
for _, opt := range opts {
opt(options)
}
client := &Client{Options: options, Codec: protocol.Codec}
func NewClientWithConf(cfg *model.Configure) {
client := NewCli(cfg)
client.StartUp()
}
func (client *Client) StartUp() {
if client.Options == nil {
client.Options = NewOptions()
logger.Warn().Msgf("use default nat addr %s", common.DefNatServerHost)
func NewCli(cfg *model.Configure) *Client {
var localAddr net.Addr
if len(cfg.LocalAddr) > 0 {
localAddr = &net.TCPAddr{
IP: net.ParseIP(cfg.LocalAddr),
Port: 0,
}
}
client := &Client{
config: cfg,
localAddr: localAddr,
natTunnel: atomic.Value{},
Codec: protocol.Codec,
connStore: sync.Map{},
cleanup: make(chan struct{}),
}
return client
}
func (client *Client) StartUp() {
client.connect()
client.register()
client.handleNatEvent()
if client.config.Redial.Invalid() {
client.Redial()
}
go func() {
client.handleNatEvent()
}()
// 退出旧的进程
for range client.cleanup {
os.Exit(0)
}
}
func (client *Client) register() {
packet := protocol.TypeRegister.CreatePacket()
packet.Extra = client.Options.ClientID
packet.Data = protocol.EncodeExtra(client.Options.ExtraInfo)
packet.Extra = client.config.ClientID
extraMap := make(map[string]string, 1)
extraMap[common.ExtrakeyUser] = client.config.Extra.Account
packet.Data = protocol.EncodeExtra(extraMap)
if err := client.WriteAndFlush(packet); err != nil {
logger.Error().Msgf("register to nat server with error %s", err.Error())
} else {
logger.Info().Msg("register to nat server success")
logger.Info().Msgf("client %s register to nat server %s success", client.config.ClientID, client.config.TunnelAddr)
}
}
func (client *Client) handleNatEvent() {
for {
reader := bufio.NewReader(client.natTunnel.Load().(net.Conn))
// todo 支持 timeout检测
majoraPacket, err := client.Codec.Decode(reader)
if errors.Is(err, io.EOF) {
// 清理本地session
client.connStore = sync.Map{}
client.reConnect()
continue
}
......@@ -76,7 +98,7 @@ func (client *Client) handleNatEvent() {
continue
}
logger.Debug().Msgf("receive packet type %s", majoraPacket.Ttype.ToString())
logger.Debug().Msgf("receive HeartbeatPacket type %s", majoraPacket.Ttype.ToString())
switch majoraPacket.Ttype {
case protocol.TypeHeartbeat:
client.handleHeartbeatMessage()
......@@ -93,51 +115,3 @@ func (client *Client) handleNatEvent() {
}
}
}
func (client *Client) reConnect() {
// 已经check 过
hostPort := client.Options.NatHostPort
var (
conn net.Conn
err error
)
for {
conn, err = net.DialTimeout(common.TCP, hostPort, common.ConnTimeout)
if err != nil || conn == nil {
// 不断重试
logger.Info().Msgf("reconnect to nathost with error %+v ...", err)
time.Sleep(common.ReConnInterval)
} else {
break
}
}
logger.Info().Msgf("reconnect to nathost %s success ...", hostPort)
client.natTunnel.Store(conn)
client.register()
}
func (client *Client) connect() {
hostPort := client.Options.NatHostPort
if len(hostPort) == 0 {
panic("invalid nat host/port info")
}
dialer := net.Dialer{
Timeout: common.ConnTimeout,
}
if client.Options.LocalAddr != nil {
dialer.LocalAddr = client.Options.LocalAddr
}
conn, err := dialer.Dial(common.TCP, hostPort)
if err != nil || conn == nil {
panic(fmt.Sprintf("connect to nathost %s with err %s", hostPort, err.Error()))
}
logger.Info().Msgf("connect from %s to nathost %s success ...", dialer.LocalAddr.String(), hostPort)
client.natTunnel.Store(conn)
}
package client
import (
"fmt"
"net"
"sync"
"time"
"virjar.com/majora-go/common"
"virjar.com/majora-go/infra"
"virjar.com/majora-go/logger"
"virjar.com/majora-go/protocol"
)
func (client *Client) connect() {
hostPort := client.config.TunnelAddr
if len(hostPort) == 0 {
panic("invalid nat host/port info")
}
dialer := net.Dialer{
Timeout: common.ConnTimeout,
}
if client.localAddr != nil {
dialer.LocalAddr = client.localAddr
}
conn, err := dialer.Dial(common.TCP, hostPort)
if err != nil || conn == nil {
panic(fmt.Sprintf("connect to nathost %s with err %s", hostPort, err.Error()))
}
if dialer.LocalAddr != nil {
logger.Info().Msgf("connect from %s to nathost %s success ...", dialer.LocalAddr.String(), hostPort)
} else {
logger.Info().Msgf("connect to nathost %s success ...", hostPort)
}
client.natTunnel.Store(conn)
}
func (client *Client) reConnect() {
// 已经check 过
hostPort := client.config.TunnelAddr
dialer := net.Dialer{
Timeout: common.ConnTimeout,
}
if client.localAddr != nil {
dialer.LocalAddr = client.localAddr
}
var (
conn net.Conn
err error
)
for {
conn, err = dialer.Dial(common.TCP, hostPort)
if err != nil || conn == nil {
// 不断重试
logger.Info().Msgf("reconnect to nathost with error %+v ...", err)
time.Sleep(client.config.ReconnInterval)
} else {
break
}
}
logger.Info().Msgf("reconnect to nathost %s success ...", hostPort)
client.natTunnel.Store(conn)
client.connStore = sync.Map{}
client.register()
}
func (client *Client) Redial() {
go func() {
var timer *time.Timer
for {
if timer == nil {
timer = time.NewTimer(client.config.Redial.RedialDuration)
} else {
timer.Reset(client.config.Redial.RedialDuration)
}
<-timer.C
majoraPacket := protocol.TypeDisconnect.CreatePacket()
if err := client.WriteAndFlush(majoraPacket); err != nil {
logger.Warn().Msgf("flush to nat server error %s", err.Error())
}
time.Sleep(client.config.Redial.WaitTime)
infra.Redial(client.config, client.cleanup)
}
}()
}
......@@ -13,11 +13,16 @@ import (
"virjar.com/majora-go/protocol"
)
var (
HeartbeatPacket = protocol.TypeHeartbeat.CreatePacket()
DisconnectPacket = protocol.TypeDisconnect.CreatePacket()
)
// todo 心跳超时检测
func (client *Client) handleHeartbeatMessage() {
go func() {
logger.Debug().Msg("receive heartbeat message from nat server")
packet := protocol.TypeHeartbeat.CreatePacket()
if err := client.WriteAndFlush(packet); err != nil {
//logger.Debug().Msg("receive heartbeat message from nat server")
if err := client.WriteAndFlush(HeartbeatPacket); err != nil {
logger.Error().Msgf("flush heart beat message error %s", err.Error())
}
}()
......@@ -49,23 +54,28 @@ func (client *Client) handleConnect(packet *protocol.MajoraPacket) {
Timeout: common.ConnTimeout,
}
if client.Options.LocalAddr != nil {
dialer.LocalAddr = client.Options.LocalAddr
if client.localAddr != nil {
dialer.LocalAddr = client.localAddr
}
conn, err = dialer.Dial(common.TCP, addr)
if err != nil {
client.closeVirtualConnection(packet, "connect to target host error "+err.Error())
return
}
client.AddConnection(packet, conn)
logger.Info().Msgf("connect success to %d->%s ", packet.SerialNumber, hostPort)
client.AddConnection(packet, conn, addr)
majoraPacket := protocol.TypeConnectReady.CreatePacket()
majoraPacket.SerialNumber = packet.SerialNumber
majoraPacket.Extra = client.Options.ClientID
majoraPacket.Extra = client.config.ClientID
if err := client.WriteAndFlush(majoraPacket); err != nil {
logger.Error().Msgf("handleConnect message error %s", err.Error())
// close && clean
_ = conn.Close()
client.removeConnection(packet, "client:"+err.Error())
return
}
client.handleConnection(conn, packet)
......@@ -82,30 +92,51 @@ func (client *Client) WriteAndFlush(packet *protocol.MajoraPacket) error {
return writer.Flush()
}
func (client *Client) WriteAndFlushBytes(packets []byte) error {
writer := bufio.NewWriter(client.natTunnel.Load().(net.Conn))
if _, err := writer.Write(packets); err != nil {
logger.Warn().Msgf("write to nat server error err:%+v", err)
return err
}
return writer.Flush()
}
func (client *Client) handleTransfer(packet *protocol.MajoraPacket) {
go func(packet *protocol.MajoraPacket) {
conn, ok := client.GetConnection(packet, "handleTransfer")
// 如何把这个错误告诉服务端
if !ok {
client.closeVirtualConnection(packet, "")
return
}
if cnt, err := conn.Write(packet.Data); err != nil {
logger.Warn().Msgf("write with error cnt=%d|err=%+v", cnt, err)
client.removeConnection(packet, "write_error")
}
writer := bufio.NewWriter(conn)
flush(client, writer, packet)
}(packet)
}
func flush(client *Client, writer *bufio.Writer, packet *protocol.MajoraPacket) {
if cnt, err := writer.Write(packet.Data); err != nil {
logger.Warn().Msgf("write with error cnt=%d|err=%+v", cnt, err)
client.removeConnection(packet, "write_error")
}
if err := writer.Flush(); err != nil {
logger.Warn().Msgf("flush with error err=%+v", err)
client.removeConnection(packet, "write_error")
}
}
func (client *Client) handleConnection(conn net.Conn, packet *protocol.MajoraPacket) {
logger.Debug().Msg("handleConnection start...")
logger.Info().Msgf("serialNum %d -> handleConnection start...", packet.SerialNumber)
reader := bufio.NewReader(conn)
for {
if _, err := reader.Peek(1); err != nil {
if !errors.Is(err, net.ErrClosed) && !errors.Is(err, io.EOF) {
logger.Error().Msgf("handleConnection peek with error:%+v", err)
logger.Error().Msgf("%d -> handleConnection peek with error:%+v", packet.SerialNumber, err)
}
client.removeConnection(packet, "peek_error")
bufsize := reader.Buffered()
client.removeConnection(packet, fmt.Sprintf("%d->peek_with_error:%s,bufferSize:%d",
packet.SerialNumber, err, bufsize))
break
}
bufsize := reader.Buffered()
......@@ -148,9 +179,9 @@ func (client *Client) handleDestroyMessage() {
}()
}
func (client *Client) AddConnection(packet *protocol.MajoraPacket, conn net.Conn) {
func (client *Client) AddConnection(packet *protocol.MajoraPacket, conn net.Conn, addr string) {
client.connStore.Store(packet.SerialNumber, conn)
logger.Debug().Msgf("create connection for %d", packet.SerialNumber)
logger.Info().Msgf("create connection for %d->%s", packet.SerialNumber, addr)
}
// removeConnection 1. 本地缓存删除 2. 关闭连接 3. 通知natserver
......@@ -169,7 +200,7 @@ func (client *Client) removeConnection(packet *protocol.MajoraPacket, reason str
logger.Debug().Msgf("removeConnection target connection %d, reason %s", packet.SerialNumber, reason)
majoraPacket := protocol.TypeDisconnect.CreatePacket()
majoraPacket.SerialNumber = packet.SerialNumber
majoraPacket.Data = []byte(client.Options.ClientID)
majoraPacket.Data = []byte(client.config.ClientID)
if err := client.WriteAndFlush(majoraPacket); err != nil {
logger.Warn().Msgf("flush to nat server error %s", err.Error())
......@@ -184,6 +215,7 @@ func (client *Client) GetConnection(packet *protocol.MajoraPacket, step string)
// 是否需要主动创建一个
if !ok || load == nil {
logger.Warn().Msgf("can not find connection for %s->%d", step, packet.SerialNumber)
client.closeVirtualConnection(packet, "GetConnection with empty")
return nil, false
}
conn, ok = load.(net.Conn)
......@@ -194,7 +226,7 @@ func (client *Client) closeVirtualConnection(packet *protocol.MajoraPacket, msg
logger.Warn().Msgf("disconnect to server %s", msg)
majoraPacket := protocol.TypeDisconnect.CreatePacket()
majoraPacket.SerialNumber = packet.SerialNumber
majoraPacket.Extra = client.Options.ClientID
majoraPacket.Extra = client.config.ClientID
if err := client.WriteAndFlush(packet); err != nil {
logger.Error().Msgf("closeVirtualConnection with error %+v", err)
......
package client
import (
"virjar.com/majora-go/logger"
"virjar.com/majora-go/protocol"
)
const (
ActionExecShell = "executeShell"
ActionRedial = "redial"
ACTION = "action"
KeyFailedMsg = "errorMsg"
KeyStatusCode = "status"
KeyData = "data"
)
type Callback interface {
OnCmdResponse(bool, map[string]string)
}
type CmdHandler interface {
Action() string
Handle(param map[string]string, callback Callback)
}
type CmdResponse struct {
SerialNumber int64
Client *Client
}
func (c *CmdResponse) OnCmdResponse(_ bool, response map[string]string) {
packet := protocol.TypeControl.CreatePacket()
packet.SerialNumber = c.SerialNumber
packet.Data = protocol.EncodeExtra(response)
if err := c.Client.WriteAndFlush(packet); err != nil {
logger.Error().Msgf("OnCmdResponse error %+v", err)
}
}
//func OnRedialCmdResponse(client *Client, serialNumber int64, success bool, response map[string]string) {
// packet := protocol.TypeControl.CreatePacket()
// packet.SerialNumber = serialNumber
// packet.Data = protocol.EncodeExtra(response)
// if err := client.WriteAndFlush(packet); err != nil {
// logger.Error().Msgf("OnCmdResponse error %+v", err)
// }
// infra.Redial(client.config, client.cleanup)
//}
package client
import (
"virjar.com/majora-go/protocol"
)
var (
handlers = make(map[string]CmdHandler, 2)
)
func init() {
handlers[shellCmd.Action()] = shellCmd
handlers[redialCmd.Action()] = redialCmd
}
type CmdHandlerManager struct{}
func (CmdHandlerManager) HandleCmdMessage(client *Client, packet *protocol.MajoraPacket) {
param := protocol.DecodeExtra(packet.Data)
action, ok := param[ACTION]
hook := &CmdResponse{
SerialNumber: packet.SerialNumber,
Client: client,
}
if !ok || len(action) == 0 {
hook.OnCmdResponse(false, map[string]string{
KeyFailedMsg: "no param: {action} present",
KeyStatusCode: "-1",
})
return
}
cmdHandler, ok := handlers[action]
if !ok || cmdHandler == nil {
hook.OnCmdResponse(false, map[string]string{
KeyFailedMsg: "no action: " + action + " defined",
KeyStatusCode: "-1",
})
return
}
go func() {
cmdHandler.Handle(param, hook)
}()
}
package client
import (
"net"
"virjar.com/majora-go/common"
)
type (
Extra map[string]string
Options struct {
NatHostPort string
ClientID string
Account string
LocalAddr net.Addr
ExtraInfo Extra
}
Option func(*Options)
)
func WithNatServerAddr(natHostPort string) Option {
return func(options *Options) {
options.NatHostPort = natHostPort
}
}
func WithClientID(clientID string) Option {
return func(options *Options) {
options.ClientID = clientID
}
}
func WithAccount(account string) Option {
return func(options *Options) {
options.ExtraInfo[common.ExtrakeyUser] = account
}
}
func WithLocalIP(ip string) Option {
return func(options *Options) {
options.LocalAddr = &net.TCPAddr{
IP: net.ParseIP(ip),
Port: 0,
}
}
}
package client
type RedialCmd struct {
}
func (r RedialCmd) Action() string {
return ActionRedial
}
func (r RedialCmd) Handle(param map[string]string, callback Callback) {
panic("implement me")
}
var (
redialCmd = &RedialCmd{}
)
package client
import (
"os/exec"
"strings"
"virjar.com/majora-go/logger"
)
var (
cmdErrorMap = map[string]string{
KeyFailedMsg: "no param:{cmd} present",
KeyStatusCode: "-1",
}
)
var (
shellCmd = &ShellCmd{}
)
type ShellCmd struct {}
func (e *ShellCmd) Action() string {
return ActionExecShell
}
func (e *ShellCmd) Handle(param map[string]string, callback Callback) {
targetCmd := param["cmd"]
if len(targetCmd) == 0 || len(strings.TrimSpace(targetCmd)) == 0 {
callback.OnCmdResponse(false, cmdErrorMap)
return
}
logger.Info().Msgf("exec cmd %s", targetCmd)
trueCmd := strings.Split(targetCmd, " ")
cmd := exec.Command(trueCmd[0], trueCmd[1:]...) //nolint:gosec
out, err := cmd.CombinedOutput()
if err != nil {
logger.Error().Msgf("exec error %+v", err)
return
}
callback.OnCmdResponse(true, map[string]string{
KeyData: string(out),
KeyStatusCode: "0",
})
}
......@@ -21,13 +21,30 @@ const (
)
const (
DNSServer = "114.114.114.114:53"
)
const (
UpdateServer = "http://81.70.224.147:10010"
Latest = "latest.txt"
UpdateBinaryPath = "/version/"
// VersionTpl majora-v0.0.1-linux-arm64 name-version-os-arch
VersionTpl = "%s-%s-%s-%s"
)
const (
DefaultMode = 0755
)
const (
DefNatServerHost = "majora.virjar.com"
DefNatServerPort = 5879
DefNatAddr = "majora.virjar.com:5879"
PprofAddr = "127.0.0.1:6060"
)
const (
ConnTimeout = time.Second * 10
ConnTimeout = time.Second * 30
ReConnInterval = time.Second * 5
)
......
package common
const (
DNSServer = "114.114.114.114:53"
)
const (
UpdateServer = "http://81.70.224.147:10010"
Latest = "latest.txt"
UpdateBinaryPath = "/version/"
// VersionTpl majora-v0.0.1-linux-arm64 name-version-os-arch
VersionTpl = "%s-%s-%s-%s"
)
const (
DefaultMode = 0755
)
tunnel_addr = aoba.vip:5879
dns_server = 114.114.114.114:53
;bind to local ip
;local_ip = 192.168.0.100
;for performance pprof 0 is close
;pprof_port = 0
disable_update = false
; default is info
log_level = 1
reconn_interval = 5s
;client_id =
[extra]
account = superman
[redial]
command = /bin/bash
exec_path = /root/ppp_auto_with_auth.sh
redial_duration = 5m
wait_time = 10s
\ No newline at end of file
#!/bin/bash
CURDIR=$(cd $(dirname "$0");pwd)
CONF=$CURDIR/majora.ini
chmod +x majora
echo "$CURDIR"/majora -conf "$CONF"
exec "$CURDIR"/majora -conf "$CONF"
\ No newline at end of file
......@@ -6,4 +6,7 @@ require (
github.com/blang/semver/v4 v4.0.0
github.com/google/uuid v1.3.0
github.com/rs/zerolog v1.25.0
gopkg.in/ini.v1 v1.63.2
)
require github.com/stretchr/testify v1.7.0 // indirect
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.25.0 h1:Rj7XygbUHKUlDPcVdoLyR91fJBsduXj5fRxyqIQj/II=
github.com/rs/zerolog v1.25.0/go.mod h1:7KHcEGe0QZPOm2IE4Kpb5rTh6n1h2hIgS5OOnu1rUaI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
......@@ -31,3 +38,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c=
gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
package infra
import (
"os"
"os/exec"
"time"
"virjar.com/majora-go/logger"
)
func Restart() {
cmd := exec.Command(os.Args[0], os.Args[1:]...) //nolint:gosec
cmd.Env = os.Environ()
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
logger.Debug().Msgf("Restart ... %+v", cmd)
if err := cmd.Run(); err != nil {
logger.Error().Msgf("Restart error %+v", err)
}
}
func RestartBySignal(signal chan struct{}) {
go func() {
time.Sleep(time.Second * 5)
logger.Info().Msgf("=============cleanup==============")
signal <- struct{}{}
}()
cmd := exec.Command(os.Args[0], os.Args[1:]...) //nolint:gosec
cmd.Env = os.Environ()
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
logger.Info().Msgf("[RestartBySignal] ... %+v", cmd)
if err := cmd.Run(); err != nil {
logger.Error().Msgf("Restart error %+v", err)
}
}
package infra
import (
"os/exec"
"virjar.com/majora-go/logger"
"virjar.com/majora-go/model"
)
func Redial(cfg *model.Configure, cleanup chan struct{}) {
redial(cfg)
RestartBySignal(cleanup)
}
func redial(cfg *model.Configure) {
execPath := cfg.Redial.ExecPath
if len(execPath) == 0 {
logger.Error().Msgf("redial exec file is empty")
return
}
command := cfg.Redial.Command
if len(command) == 0 {
logger.Error().Msgf("redial command is empty")
return
}
cmd := exec.Command(command, "-c", execPath)
output, err := cmd.Output()
if err != nil {
logger.Error().Msgf("Execute Shell:%s failed with error:%s", command, err.Error())
return
}
logger.Info().Msgf("[redial] redial success %+v resp:%s", cmd, string(output))
}
package infra
import (
"testing"
"virjar.com/majora-go/model"
)
func TestRedial(t *testing.T) {
conf := model.InitConf("/Users/weixuan/code/gcode/majora-go/conf/majora.ini")
redial(conf)
}
package updater
package infra
import (
"errors"
......@@ -7,7 +7,6 @@ import (
"io/ioutil"
"net/http"
"os"
"os/exec"
"runtime"
"strings"
......@@ -72,18 +71,6 @@ func rename(src, dst string) error {
return nil
}
func restart() {
cmd := exec.Command(os.Args[0], os.Args[1:]...) //nolint:gosec
cmd.Env = os.Environ()
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
logger.Debug().Msgf("restart ... %+v", cmd)
if err := cmd.Run(); err != nil {
logger.Error().Msgf("restart error %+v", err)
}
}
func UpdateCore(name, latestVer, targetFile string) (bool, error) {
tf, err := ioutil.TempFile("", "")
if err != nil {
......@@ -104,7 +91,7 @@ func UpdateCore(name, latestVer, targetFile string) (bool, error) {
if err := rename(tf.Name(), targetFile); err != nil {
return false, err
}
restart()
Restart()
return true, nil
}
......@@ -134,6 +121,9 @@ func getLatestVersion() (string, error) {
}
func needUpdate(curVer string) (bool, string) {
if len(curVer) == 0 {
return false, ""
}
latestVer, err := getLatestVersion()
if err != nil {
logger.Error().Msgf("getLatestVersion with error %s", err.Error())
......@@ -162,6 +152,12 @@ func needUpdate(curVer string) (bool, string) {
// Update update to the latest version
func Update(name, curVer string) (bool, error) {
defer func() {
if err := recover(); err != nil {
logger.Error().Msgf("Update with error %+v", err)
}
}()
// get current exec path
executable, err := os.Executable()
if err != nil {
......
......@@ -3,18 +3,20 @@ package main
import "C"
import (
"virjar.com/majora-go/client"
"virjar.com/majora-go/protocol"
"virjar.com/majora-go/model"
)
//export NewDefClient
func NewDefClient(asyn C.int, account *C.char) {
options := client.NewOptions()
conf := model.NewDefMajoraConf()
newAccount := C.GoString(account)
if len(newAccount) > 0 {
options.Account = newAccount
conf.Extra.Account = newAccount
}
cli := &client.Client{Options: options, Codec: protocol.NewDefCodec()}
cli := client.NewCli(conf)
if int(asyn) > 0 {
go cli.StartUp()
......@@ -25,22 +27,22 @@ func NewDefClient(asyn C.int, account *C.char) {
//export NewClientWithNatServer
func NewClientWithNatServer(addr *C.char, clientID *C.char, asyn C.int, account *C.char) {
options := client.NewOptions()
conf := model.NewDefMajoraConf()
newAddr := C.GoString(addr)
newClientID := C.GoString(clientID)
newAccount := C.GoString(account)
if len(newAddr) > 0 {
options.NatHostPort = newAddr
conf.TunnelAddr = newAddr
}
if len(newClientID) > 0 {
options.ClientID = newClientID
conf.ClientID = newClientID
}
if len(newAccount) > 0 {
options.Account = newAccount
conf.Extra.Account = newAccount
}
cli := &client.Client{Options: options, Codec: protocol.NewDefCodec()}
cli := client.NewCli(conf)
if int(asyn) > 0 {
go cli.StartUp()
} else {
......
package model
import (
"time"
"github.com/google/uuid"
"gopkg.in/ini.v1"
"virjar.com/majora-go/common"
"virjar.com/majora-go/logger"
)
type Redial struct {
Command string `ini:"command" json:"command"`
ExecPath string `ini:"exec_path" json:"exec_path"`
RedialDuration time.Duration `ini:"redial_duration" json:"redial_duration"`
WaitTime time.Duration `ini:"wait_time" json:"wait_time"`
}
type Extra struct {
Account string `ini:"account" json:"account"`
}
type Configure struct {
DisableUpdate bool `ini:"disable_update" json:"disable_update"`
LogLevel int `ini:"log_level" json:"log_level"`
PprofPort int `ini:"pprof_port" json:"pprof_port"`
TunnelAddr string `ini:"tunnel_addr" json:"tunnel_addr"`
DNSServer string `ini:"dns_server" json:"dns_server"`
LocalAddr string `ini:"local_ip" json:"local_ip"`
ReconnInterval time.Duration `ini:"reconn_interval" json:"reconn_interval"`
ClientID string `ini:"client_id" json:"client_id"`
Extra Extra `ini:"extra" json:"extra"`
Redial Redial `ini:"redial" json:"redial"`
}
const (
reconninterval = time.Second * 10
)
func NewDefMajoraConf() *Configure {
return &Configure{
DisableUpdate: false,
LogLevel: 1,
PprofPort: 0,
TunnelAddr: common.DefNatAddr,
DNSServer: common.DNSServer, //nolint:typecheck
ReconnInterval: reconninterval,
ClientID: uuid.New().String(),
Extra: Extra{
Account: uuid.New().String(),
},
Redial: Redial{
RedialDuration: reconninterval,
},
}
}
func InitConf(path string) *Configure {
conf := NewDefMajoraConf()
if err := ini.MapTo(conf, path); err != nil {
logger.Error().Msgf("InitConf with error %s, use default...", err.Error())
}
return conf
}
func (r Redial) Invalid() bool {
if len(r.Command) == 0 {
return false
}
if len(r.ExecPath) == 0 {
return false
}
if r.RedialDuration == 0 {
return false
}
return true
}
package model
import (
"testing"
"github.com/google/uuid"
)
func TestInitConf(t *testing.T) {
conf := InitConf("/Users/weixuan/code/gcode/majora-go/conf/majora.ini")
t.Logf("conf %+v", conf)
t.Logf("%s", uuid.NewString())
}
This diff is collapsed.
......@@ -2,6 +2,8 @@ package protocol
import (
"bytes"
"virjar.com/majora-go/logger"
)
func EncodeExtra(extra map[string]string) []byte {
......@@ -21,3 +23,28 @@ func EncodeExtra(extra map[string]string) []byte {
}
return buf.Bytes()
}
func DecodeExtra(input []byte) map[string]string {
if len(input) < 4 {
return map[string]string{}
}
buffer := bytes.NewBuffer(input)
headerSize, err := buffer.ReadByte()
if err != nil {
logger.Error().Msgf("DecodeExtra error %+v", err)
return map[string]string{}
}
data := make(map[string]string)
for i := 0; i < int(headerSize); i++ {
keyLen, _ := buffer.ReadByte()
keyBs := make([]byte, keyLen)
_, _ = buffer.Read(keyBs)
valLen, _ := buffer.ReadByte()
valBs := make([]byte, valLen)
_, _ = buffer.Read(valBs)
data[string(keyBs)] = string(valBs)
}
return data
}
package protocol
import (
"reflect"
"testing"
)
func TestEncodeExtra(t *testing.T) {
data := make(map[string]string)
data["hello"] = "world"
data["1"] = "world1"
data["2"] = "world2"
data["3"] = "world3"
data["account"] = "superman"
extra := EncodeExtra(data)
decodeExtra := DecodeExtra(extra)
t.Logf("TestEncodeExtra %+v", decodeExtra)
t.Logf("equal %v", reflect.DeepEqual(data, decodeExtra))
}
# v0.0.3
- [x] 重播 功能
- [x] 执行脚本 执行间隔
- [ ] http2 fix 抓包
- [x] 重连时间 可配置
- [x] 线程安全check
- [x] 支持config 文件
- [x] 代码的bug fix write/flush
- [ ] control
\ No newline at end of file
#!/usr/bin/env bash
TOKEN=Aau6PvRI4o0OTYrgpQHzxG7qEDkADx6CaUAJV2
TOKENONLINE=456734sdlasysdhf293r23r
make
if [ $? -ne 0 ]; then
echo "make error"
exit 1
fi
majora_version=`./bin/majora version`
echo "build version: $majora_version"
# cross_compiles
make -f ./Makefile.cross
rm -rf ./release/packages
mkdir -p ./release/packages
os_all='linux windows darwin freebsd'
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle'
cd ./release
for os in $os_all; do
for arch in $arch_all; do
majora_dir_name="majora-cli_${majora_version}_${os}_${arch}"
majora_path="./packages/majora-cli_${majora_version}_${os}_${arch}"
if [ "x${os}" = x"windows" ]; then
if [ ! -f "./majora_${os}_${arch}.exe" ]; then
continue
fi
mkdir ${majora_path}
mv ./majora_${os}_${arch}.exe ${majora_path}/majora.exe
else
if [ ! -f "./majora_${os}_${arch}" ]; then
continue
fi
mkdir ${majora_path}
mv ./majora_${os}_${arch} ${majora_path}/majora
fi
cp -rf ../conf/majora.ini ${majora_path}
cp -rf ../conf/start.sh ${majora_path}
# packages
cd ./packages
if [ "x${os}" = x"windows" ]; then
zip -rq ${majora_dir_name}.zip ${majora_dir_name}
curl -F file=@"${majora_dir_name}.zip" -F token=$TOKEN http://81.70.224.147:10010/version/"$majora_version"
curl -F file=@"${majora_dir_name}.zip" -F token=$TOKENONLINE https://oss.virjar.com/majora/bin/"$majora_version"
else
tar -zcf ${majora_dir_name}.tar.gz ${majora_dir_name}
curl -F file=@"${majora_dir_name}.tar.gz" -F token=$TOKEN http://81.70.224.147:10010/version/"$majora_version"
curl -F file=@"${majora_dir_name}.tar.gz" -F token=$TOKENONLINE https://oss.virjar.com/majora/bin/"$majora_version"
fi
cd ..
rm -rf ${majora_path}
done
done
cd -
\ No newline at end of file
start.png

742 KB

Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment