mirror of
https://github.com/go-gost/gost-plus.git
synced 2024-08-11 17:43:16 +00:00
add tcp entrypoint
This commit is contained in:
@@ -7,15 +7,13 @@ GOBUILD=CGO_ENABLED=0 go build --ldflags="-s -w" -v -x -a
|
||||
GOFILES=*.go
|
||||
|
||||
PLATFORM_LIST = \
|
||||
darwin-amd64 \
|
||||
darwin-arm64 \
|
||||
linux-amd64
|
||||
|
||||
WINDOWS_ARCH_LIST = \
|
||||
windows-amd64
|
||||
|
||||
linux-amd64:
|
||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build --ldflags="-s -w" -v -x -a
|
||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build --ldflags="-s -w" -v -x -a -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
||||
|
||||
darwin-amd64:
|
||||
GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
||||
|
||||
@@ -6,13 +6,13 @@ A cross-platform GUI client for [GOST.PLUS](https://gost.plus) built with [gioui
|
||||
|
||||
### Desktop
|
||||
|
||||
<img src="img/list.png" width="256" />
|
||||
<img src="img/menu.png" width="256" />
|
||||
<img src="img/add.png" width="256" />
|
||||
<img src="img/edit.png" width="256" />
|
||||
<img src="img/list.png" width="512" />
|
||||
<img src="img/menu.png" width="512" />
|
||||
<img src="img/add.png" width="512" />
|
||||
<img src="img/edit.png" width="512" />
|
||||
|
||||
### Mobile
|
||||
|
||||
<img src="img/list-android.png" width="256" />
|
||||
<img src="img/add-android.png" width="256" />
|
||||
<img src="img/edit-android.png" width="256" />
|
||||
<img src="img/list-android.png" width="512" />
|
||||
<img src="img/add-android.png" width="512" />
|
||||
<img src="img/edit-android.png" width="512" />
|
||||
+4
-3
@@ -107,9 +107,10 @@ type Tunnel struct {
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Settings *Settings
|
||||
Tunnels []*Tunnel
|
||||
Log *config.LogConfig
|
||||
Settings *Settings
|
||||
Tunnels []*Tunnel
|
||||
EntryPoints []*Tunnel
|
||||
Log *config.LogConfig
|
||||
}
|
||||
|
||||
func (c *Config) Load() error {
|
||||
|
||||
@@ -12,12 +12,14 @@ import (
|
||||
"gioui.org/op"
|
||||
"github.com/go-gost/gost-plus/config"
|
||||
"github.com/go-gost/gost-plus/tunnel"
|
||||
"github.com/go-gost/gost-plus/tunnel/entrypoint"
|
||||
"github.com/go-gost/gost-plus/ui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config.Init()
|
||||
tunnel.LoadConfig()
|
||||
entrypoint.LoadConfig()
|
||||
|
||||
go func() {
|
||||
w := app.NewWindow(
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/gost-plus/config"
|
||||
"github.com/go-gost/gost-plus/tunnel"
|
||||
)
|
||||
|
||||
const (
|
||||
TCPEntryPoint = "tcp"
|
||||
UDPEntryPoint = "udp"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEntryPointClosed = errors.New("entrypoint closed")
|
||||
)
|
||||
|
||||
type EntryPoint = tunnel.Tunnel
|
||||
|
||||
type entryPointList struct {
|
||||
list []EntryPoint
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
var (
|
||||
entryPoints entryPointList
|
||||
)
|
||||
|
||||
func Count() int {
|
||||
entryPoints.mux.RLock()
|
||||
defer entryPoints.mux.RUnlock()
|
||||
return len(entryPoints.list)
|
||||
}
|
||||
|
||||
func Add(s EntryPoint) {
|
||||
entryPoints.mux.Lock()
|
||||
defer entryPoints.mux.Unlock()
|
||||
entryPoints.list = append(entryPoints.list, s)
|
||||
}
|
||||
|
||||
func Set(s EntryPoint) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
|
||||
old := Get(s.ID())
|
||||
if old == nil {
|
||||
return
|
||||
}
|
||||
s.Favorite(old.IsFavorite())
|
||||
|
||||
entryPoints.mux.Lock()
|
||||
defer entryPoints.mux.Unlock()
|
||||
|
||||
for i, ep := range entryPoints.list {
|
||||
if ep != nil && ep.ID() == s.ID() {
|
||||
entryPoints.list[i] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetIndex(index int) EntryPoint {
|
||||
entryPoints.mux.RLock()
|
||||
defer entryPoints.mux.RUnlock()
|
||||
if index < 0 || index >= len(entryPoints.list) {
|
||||
return nil
|
||||
}
|
||||
return entryPoints.list[index]
|
||||
}
|
||||
|
||||
func Get(id string) EntryPoint {
|
||||
entryPoints.mux.RLock()
|
||||
defer entryPoints.mux.RUnlock()
|
||||
|
||||
for _, s := range entryPoints.list {
|
||||
if s != nil && s.ID() == id {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Delete(id string) {
|
||||
entryPoints.mux.Lock()
|
||||
defer entryPoints.mux.Unlock()
|
||||
|
||||
for i, s := range entryPoints.list {
|
||||
if s != nil && s.ID() == id {
|
||||
s.Close()
|
||||
entryPoints.list[i] = nil
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func LoadConfig() {
|
||||
for _, ep := range config.Global().EntryPoints {
|
||||
if ep == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
s := createEntryPoint(ep.Type, tunnel.Options{
|
||||
ID: ep.ID,
|
||||
Name: ep.Name,
|
||||
Endpoint: ep.Endpoint,
|
||||
Hostname: ep.Hostname,
|
||||
Username: ep.Username,
|
||||
Password: ep.Password,
|
||||
EnableTLS: ep.EnableTLS,
|
||||
})
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if ep.Closed {
|
||||
s.Close()
|
||||
} else {
|
||||
s.Run()
|
||||
}
|
||||
|
||||
s.Favorite(ep.Favorite)
|
||||
Add(s)
|
||||
}
|
||||
}
|
||||
|
||||
func SaveConfig() error {
|
||||
cfg := config.Global()
|
||||
cfg.EntryPoints = nil
|
||||
|
||||
for i := 0; i < Count(); i++ {
|
||||
ep := GetIndex(i)
|
||||
if ep == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
opts := ep.Options()
|
||||
|
||||
cfg.EntryPoints = append(cfg.EntryPoints, &config.Tunnel{
|
||||
ID: ep.ID(),
|
||||
Name: ep.Name(),
|
||||
Type: ep.Type(),
|
||||
Endpoint: ep.Entrypoint(),
|
||||
Hostname: opts.Hostname,
|
||||
Username: opts.Username,
|
||||
Password: opts.Password,
|
||||
EnableTLS: opts.EnableTLS,
|
||||
Favorite: ep.IsFavorite(),
|
||||
Closed: ep.IsClosed(),
|
||||
})
|
||||
}
|
||||
|
||||
config.Set(cfg)
|
||||
|
||||
if err := cfg.Write(); err != nil {
|
||||
logger.Default().Error(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createEntryPoint(st string, opts tunnel.Options) EntryPoint {
|
||||
options := []tunnel.Option{
|
||||
tunnel.IDOption(opts.ID),
|
||||
tunnel.NameOption(opts.Name),
|
||||
tunnel.EndpointOption(opts.Endpoint),
|
||||
tunnel.HostnameOption(opts.Hostname),
|
||||
tunnel.UsernameOption(opts.Username),
|
||||
tunnel.PasswordOption(opts.Password),
|
||||
tunnel.EnableTLSOption(opts.EnableTLS),
|
||||
}
|
||||
switch st {
|
||||
case TCPEntryPoint:
|
||||
return NewTCPEntryPoint(options...)
|
||||
// case UDPEntryPoint:
|
||||
// return NewU(options...)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/go-gost/core/chain"
|
||||
"github.com/go-gost/core/handler"
|
||||
"github.com/go-gost/core/listener"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/service"
|
||||
"github.com/go-gost/gost-plus/tunnel"
|
||||
"github.com/go-gost/x/config"
|
||||
chain_parser "github.com/go-gost/x/config/parsing/chain"
|
||||
"github.com/go-gost/x/handler/forward/local"
|
||||
"github.com/go-gost/x/hop"
|
||||
"github.com/go-gost/x/listener/tcp"
|
||||
mdx "github.com/go-gost/x/metadata"
|
||||
xservice "github.com/go-gost/x/service"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type tcpEntryPoint struct {
|
||||
endpoint string
|
||||
opts tunnel.Options
|
||||
config *config.Config
|
||||
forward service.Service
|
||||
favorite atomic.Bool
|
||||
|
||||
cclose chan struct{}
|
||||
|
||||
err error
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewTCPEntryPoint(opts ...tunnel.Option) EntryPoint {
|
||||
var options tunnel.Options
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
if options.ID == "" {
|
||||
options.ID = uuid.NewString()
|
||||
}
|
||||
|
||||
v := md5.Sum([]byte(options.ID))
|
||||
endpoint := hex.EncodeToString(v[:8])
|
||||
|
||||
if options.Endpoint == "" {
|
||||
options.Endpoint = "localhost:8000"
|
||||
}
|
||||
|
||||
if options.Name == "" {
|
||||
options.Name = endpoint
|
||||
}
|
||||
|
||||
s := &tcpEntryPoint{
|
||||
endpoint: endpoint,
|
||||
opts: options,
|
||||
cclose: make(chan struct{}),
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *tcpEntryPoint) ID() string {
|
||||
return s.opts.ID
|
||||
}
|
||||
|
||||
func (s *tcpEntryPoint) Type() string {
|
||||
return TCPEntryPoint
|
||||
}
|
||||
|
||||
func (s *tcpEntryPoint) Name() string {
|
||||
return s.opts.Name
|
||||
}
|
||||
|
||||
func (s *tcpEntryPoint) Endpoint() string {
|
||||
return fmt.Sprintf("%s.%s", s.endpoint, tunnel.EndpointAddr)
|
||||
}
|
||||
|
||||
func (s *tcpEntryPoint) Entrypoint() string {
|
||||
return s.opts.Endpoint
|
||||
}
|
||||
|
||||
func (s *tcpEntryPoint) Options() tunnel.Options {
|
||||
return s.opts
|
||||
}
|
||||
|
||||
func (s *tcpEntryPoint) Favorite(b bool) {
|
||||
s.favorite.Store(b)
|
||||
}
|
||||
|
||||
func (s *tcpEntryPoint) IsFavorite() bool {
|
||||
return s.favorite.Load()
|
||||
}
|
||||
|
||||
func (s *tcpEntryPoint) init() error {
|
||||
tcp := &config.ServiceConfig{
|
||||
Name: s.opts.Name,
|
||||
Addr: s.opts.Endpoint,
|
||||
Handler: &config.HandlerConfig{
|
||||
Type: "tcp",
|
||||
Chain: s.opts.Name,
|
||||
},
|
||||
Listener: &config.ListenerConfig{
|
||||
Type: "tcp",
|
||||
},
|
||||
Forwarder: &config.ForwarderConfig{
|
||||
Nodes: []*config.ForwardNodeConfig{
|
||||
{
|
||||
Name: s.opts.Name,
|
||||
Addr: s.Endpoint(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
s.config = &config.Config{
|
||||
Services: []*config.ServiceConfig{tcp},
|
||||
Chains: []*config.ChainConfig{tunnel.ChainConfig(s.opts.ID, s.opts.Name)},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *tcpEntryPoint) Run() (err error) {
|
||||
if s.IsClosed() {
|
||||
return ErrEntryPointClosed
|
||||
}
|
||||
|
||||
defer func() {
|
||||
s.setErr(err)
|
||||
}()
|
||||
|
||||
if err = s.init(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log := logger.Default().WithFields(map[string]any{
|
||||
"kind": "service",
|
||||
"service": s.opts.Name,
|
||||
})
|
||||
|
||||
{
|
||||
var ch chain.Chainer
|
||||
ch, err = chain_parser.ParseChain(s.config.Chains[0], log)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
cfg := s.config.Services[0]
|
||||
ln := tcp.NewListener(
|
||||
listener.AddrOption(cfg.Addr),
|
||||
listener.LoggerOption(log.WithFields(map[string]any{"kind": "listener", "listener": "tcp"})),
|
||||
)
|
||||
if err = ln.Init(mdx.NewMetadata(cfg.Listener.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h := local.NewHandler(
|
||||
handler.RouterOption(chain.NewRouter(
|
||||
chain.ChainRouterOption(ch),
|
||||
chain.LoggerRouterOption(log),
|
||||
)),
|
||||
handler.LoggerOption(log.WithFields(map[string]any{"kind": "handler", "handler": "tcp"})),
|
||||
)
|
||||
if err = h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
node := cfg.Forwarder.Nodes[0]
|
||||
if forwarder, ok := h.(handler.Forwarder); ok {
|
||||
forwarder.Forward(hop.NewHop(
|
||||
hop.NodeOption(chain.NewNode(node.Name, node.Addr)),
|
||||
hop.LoggerOption(log.WithFields(map[string]any{"kind": "hop"})),
|
||||
))
|
||||
}
|
||||
s.forward = xservice.NewService(s.opts.Name, ln, h, xservice.LoggerOption(log))
|
||||
}
|
||||
|
||||
go func() {
|
||||
s.setErr(s.forward.Serve())
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *tcpEntryPoint) Close() error {
|
||||
defer func() {
|
||||
select {
|
||||
case <-s.cclose:
|
||||
default:
|
||||
close(s.cclose)
|
||||
}
|
||||
}()
|
||||
|
||||
if s.forward != nil {
|
||||
return s.forward.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *tcpEntryPoint) IsClosed() bool {
|
||||
select {
|
||||
case <-s.cclose:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *tcpEntryPoint) setErr(err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.err = err
|
||||
}
|
||||
|
||||
func (s *tcpEntryPoint) Err() error {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.err
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/go-gost/core/chain"
|
||||
"github.com/go-gost/core/handler"
|
||||
"github.com/go-gost/core/listener"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/service"
|
||||
"github.com/go-gost/gost-plus/tunnel"
|
||||
"github.com/go-gost/x/config"
|
||||
chain_parser "github.com/go-gost/x/config/parsing/chain"
|
||||
"github.com/go-gost/x/handler/forward/local"
|
||||
"github.com/go-gost/x/hop"
|
||||
"github.com/go-gost/x/listener/tcp"
|
||||
mdx "github.com/go-gost/x/metadata"
|
||||
xservice "github.com/go-gost/x/service"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type udpEntryPoint struct {
|
||||
endpoint string
|
||||
opts tunnel.Options
|
||||
config *config.Config
|
||||
forward service.Service
|
||||
favorite atomic.Bool
|
||||
|
||||
cclose chan struct{}
|
||||
|
||||
err error
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewUDPEntryPoint(opts ...tunnel.Option) EntryPoint {
|
||||
var options tunnel.Options
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
if options.ID == "" {
|
||||
options.ID = uuid.NewString()
|
||||
}
|
||||
|
||||
v := md5.Sum([]byte(options.ID))
|
||||
endpoint := hex.EncodeToString(v[:8])
|
||||
|
||||
if options.Endpoint == "" {
|
||||
options.Endpoint = "localhost:8000"
|
||||
}
|
||||
|
||||
if options.Name == "" {
|
||||
options.Name = endpoint
|
||||
}
|
||||
|
||||
s := &udpEntryPoint{
|
||||
endpoint: endpoint,
|
||||
opts: options,
|
||||
cclose: make(chan struct{}),
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *udpEntryPoint) ID() string {
|
||||
return s.opts.ID
|
||||
}
|
||||
|
||||
func (s *udpEntryPoint) Type() string {
|
||||
return UDPEntryPoint
|
||||
}
|
||||
|
||||
func (s *udpEntryPoint) Name() string {
|
||||
return s.opts.Name
|
||||
}
|
||||
|
||||
func (s *udpEntryPoint) Endpoint() string {
|
||||
return fmt.Sprintf("%s.%s", s.endpoint, tunnel.EndpointAddr)
|
||||
}
|
||||
|
||||
func (s *udpEntryPoint) Entrypoint() string {
|
||||
return s.opts.Endpoint
|
||||
}
|
||||
|
||||
func (s *udpEntryPoint) Options() tunnel.Options {
|
||||
return s.opts
|
||||
}
|
||||
|
||||
func (s *udpEntryPoint) Favorite(b bool) {
|
||||
s.favorite.Store(b)
|
||||
}
|
||||
|
||||
func (s *udpEntryPoint) IsFavorite() bool {
|
||||
return s.favorite.Load()
|
||||
}
|
||||
|
||||
func (s *udpEntryPoint) init() error {
|
||||
tcp := &config.ServiceConfig{
|
||||
Name: s.opts.Name,
|
||||
Addr: s.opts.Endpoint,
|
||||
Handler: &config.HandlerConfig{
|
||||
Type: "udp",
|
||||
Chain: s.opts.Name,
|
||||
},
|
||||
Listener: &config.ListenerConfig{
|
||||
Type: "udp",
|
||||
},
|
||||
Forwarder: &config.ForwarderConfig{
|
||||
Nodes: []*config.ForwardNodeConfig{
|
||||
{
|
||||
Name: s.opts.Name,
|
||||
Addr: s.Endpoint(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
s.config = &config.Config{
|
||||
Services: []*config.ServiceConfig{tcp},
|
||||
Chains: []*config.ChainConfig{tunnel.ChainConfig(s.opts.ID, s.opts.Name)},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *udpEntryPoint) Run() (err error) {
|
||||
if s.IsClosed() {
|
||||
return ErrEntryPointClosed
|
||||
}
|
||||
|
||||
defer func() {
|
||||
s.setErr(err)
|
||||
}()
|
||||
|
||||
if err = s.init(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log := logger.Default().WithFields(map[string]any{
|
||||
"kind": "service",
|
||||
"service": s.opts.Name,
|
||||
})
|
||||
|
||||
{
|
||||
var ch chain.Chainer
|
||||
ch, err = chain_parser.ParseChain(s.config.Chains[0], log)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
cfg := s.config.Services[0]
|
||||
ln := tcp.NewListener(
|
||||
listener.AddrOption(cfg.Addr),
|
||||
listener.LoggerOption(log.WithFields(map[string]any{"kind": "listener", "listener": "udp"})),
|
||||
)
|
||||
if err = ln.Init(mdx.NewMetadata(cfg.Listener.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h := local.NewHandler(
|
||||
handler.RouterOption(chain.NewRouter(
|
||||
chain.ChainRouterOption(ch),
|
||||
chain.LoggerRouterOption(log),
|
||||
)),
|
||||
handler.LoggerOption(log.WithFields(map[string]any{"kind": "handler", "handler": "udp"})),
|
||||
)
|
||||
if err = h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
node := cfg.Forwarder.Nodes[0]
|
||||
if forwarder, ok := h.(handler.Forwarder); ok {
|
||||
forwarder.Forward(hop.NewHop(
|
||||
hop.NodeOption(chain.NewNode(node.Name, node.Addr)),
|
||||
hop.LoggerOption(log.WithFields(map[string]any{"kind": "hop"})),
|
||||
))
|
||||
}
|
||||
s.forward = xservice.NewService(s.opts.Name, ln, h, xservice.LoggerOption(log))
|
||||
}
|
||||
|
||||
go func() {
|
||||
s.setErr(s.forward.Serve())
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *udpEntryPoint) Close() error {
|
||||
defer func() {
|
||||
select {
|
||||
case <-s.cclose:
|
||||
default:
|
||||
close(s.cclose)
|
||||
}
|
||||
}()
|
||||
|
||||
if s.forward != nil {
|
||||
return s.forward.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *udpEntryPoint) IsClosed() bool {
|
||||
select {
|
||||
case <-s.cclose:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *udpEntryPoint) setErr(err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.err = err
|
||||
}
|
||||
|
||||
func (s *udpEntryPoint) Err() error {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.err
|
||||
}
|
||||
+41
-18
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/go-gost/core/auth"
|
||||
@@ -35,6 +36,9 @@ type fileTunnel struct {
|
||||
favorite atomic.Bool
|
||||
|
||||
cclose chan struct{}
|
||||
|
||||
err error
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewFileTunnel(opts ...Option) Tunnel {
|
||||
@@ -55,7 +59,7 @@ func NewFileTunnel(opts ...Option) Tunnel {
|
||||
}
|
||||
|
||||
if options.Name == "" {
|
||||
options.Name = fmt.Sprintf("FILE-%s", endpoint)
|
||||
options.Name = endpoint
|
||||
}
|
||||
|
||||
s := &fileTunnel{
|
||||
@@ -83,8 +87,8 @@ func (s *fileTunnel) Endpoint() string {
|
||||
return s.opts.Endpoint
|
||||
}
|
||||
|
||||
func (f *fileTunnel) Entrypoint() string {
|
||||
return fmt.Sprintf("https://%s.%s", f.endpoint, endpointAddr)
|
||||
func (s *fileTunnel) Entrypoint() string {
|
||||
return fmt.Sprintf("https://%s.%s", s.endpoint, EndpointAddr)
|
||||
}
|
||||
|
||||
func (s *fileTunnel) Options() Options {
|
||||
@@ -132,19 +136,23 @@ func (s *fileTunnel) init() error {
|
||||
|
||||
s.config = &config.Config{
|
||||
Services: []*config.ServiceConfig{file, rtcp},
|
||||
Chains: []*config.ChainConfig{chainConfig(s.opts.ID, s.opts.Name)},
|
||||
Chains: []*config.ChainConfig{ChainConfig(s.opts.ID, s.opts.Name)},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *fileTunnel) Run() error {
|
||||
func (s *fileTunnel) Run() (err error) {
|
||||
if s.IsClosed() {
|
||||
return ErrTunnelClosed
|
||||
}
|
||||
|
||||
if err := s.init(); err != nil {
|
||||
return err
|
||||
defer func() {
|
||||
s.setErr(err)
|
||||
}()
|
||||
|
||||
if err = s.init(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log := logger.Default().WithFields(map[string]any{
|
||||
@@ -157,8 +165,8 @@ func (s *fileTunnel) Run() error {
|
||||
ln := tcp.NewListener(
|
||||
listener.LoggerOption(log.WithFields(map[string]any{"kind": "listener", "listener": "tcp"})),
|
||||
)
|
||||
if err := ln.Init(nil); err != nil {
|
||||
return err
|
||||
if err = ln.Init(nil); err != nil {
|
||||
return
|
||||
}
|
||||
log.Infof("listen on %s", ln.Addr())
|
||||
|
||||
@@ -170,17 +178,18 @@ func (s *fileTunnel) Run() error {
|
||||
handler.LoggerOption(log.WithFields(map[string]any{"kind": "handler", "handler": "file"})),
|
||||
handler.AutherOption(auther),
|
||||
)
|
||||
if err := h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return err
|
||||
if err = h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
s.file = xservice.NewService(s.opts.Name, ln, h, xservice.LoggerOption(log))
|
||||
}
|
||||
|
||||
{
|
||||
ch, err := chain_parser.ParseChain(s.config.Chains[0], log)
|
||||
var ch chain.Chainer
|
||||
ch, err = chain_parser.ParseChain(s.config.Chains[0], log)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
cfg := s.config.Services[1]
|
||||
@@ -189,15 +198,15 @@ func (s *fileTunnel) Run() error {
|
||||
listener.ChainOption(ch),
|
||||
listener.LoggerOption(log.WithFields(map[string]any{"kind": "listener", "listener": "rtcp"})),
|
||||
)
|
||||
if err := ln.Init(mdx.NewMetadata(cfg.Listener.Metadata)); err != nil {
|
||||
return err
|
||||
if err = ln.Init(mdx.NewMetadata(cfg.Listener.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h := remote.NewHandler(
|
||||
handler.LoggerOption(log.WithFields(map[string]any{"kind": "handler", "handler": "rtcp"})),
|
||||
)
|
||||
if err := h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return err
|
||||
if err = h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
if forwarder, ok := h.(handler.Forwarder); ok {
|
||||
forwarder.Forward(hop.NewHop(
|
||||
@@ -210,7 +219,9 @@ func (s *fileTunnel) Run() error {
|
||||
}
|
||||
|
||||
go s.file.Serve()
|
||||
go s.forward.Serve()
|
||||
go func() {
|
||||
s.setErr(s.forward.Serve())
|
||||
}()
|
||||
|
||||
log.Infof("file service run at %s", s.file.Addr())
|
||||
return nil
|
||||
@@ -242,3 +253,15 @@ func (s *fileTunnel) IsClosed() bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *fileTunnel) setErr(err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.err = err
|
||||
}
|
||||
|
||||
func (s *fileTunnel) Err() error {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.err
|
||||
}
|
||||
|
||||
+37
-14
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/go-gost/core/chain"
|
||||
@@ -30,6 +31,9 @@ type httpTunnel struct {
|
||||
favorite atomic.Bool
|
||||
|
||||
cclose chan struct{}
|
||||
|
||||
err error
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewHTTPTunnel(opts ...Option) Tunnel {
|
||||
@@ -50,7 +54,7 @@ func NewHTTPTunnel(opts ...Option) Tunnel {
|
||||
}
|
||||
|
||||
if options.Name == "" {
|
||||
options.Name = fmt.Sprintf("HTTP-%s", endpoint)
|
||||
options.Name = endpoint
|
||||
}
|
||||
|
||||
s := &httpTunnel{
|
||||
@@ -78,8 +82,8 @@ func (s *httpTunnel) Endpoint() string {
|
||||
return s.opts.Endpoint
|
||||
}
|
||||
|
||||
func (f *httpTunnel) Entrypoint() string {
|
||||
return fmt.Sprintf("https://%s.%s", f.endpoint, endpointAddr)
|
||||
func (s *httpTunnel) Entrypoint() string {
|
||||
return fmt.Sprintf("https://%s.%s", s.endpoint, EndpointAddr)
|
||||
}
|
||||
|
||||
func (s *httpTunnel) Options() Options {
|
||||
@@ -134,18 +138,22 @@ func (s *httpTunnel) init() error {
|
||||
|
||||
s.config = &config.Config{
|
||||
Services: []*config.ServiceConfig{rtcp},
|
||||
Chains: []*config.ChainConfig{chainConfig(s.opts.ID, s.opts.Name)},
|
||||
Chains: []*config.ChainConfig{ChainConfig(s.opts.ID, s.opts.Name)},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *httpTunnel) Run() error {
|
||||
func (s *httpTunnel) Run() (err error) {
|
||||
if s.IsClosed() {
|
||||
return ErrTunnelClosed
|
||||
}
|
||||
|
||||
if err := s.init(); err != nil {
|
||||
return err
|
||||
defer func() {
|
||||
s.setErr(err)
|
||||
}()
|
||||
|
||||
if err = s.init(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log := logger.Default().WithFields(map[string]any{
|
||||
@@ -154,10 +162,11 @@ func (s *httpTunnel) Run() error {
|
||||
})
|
||||
|
||||
{
|
||||
ch, err := chain_parser.ParseChain(s.config.Chains[0], log)
|
||||
var ch chain.Chainer
|
||||
ch, err = chain_parser.ParseChain(s.config.Chains[0], log)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
cfg := s.config.Services[0]
|
||||
@@ -166,15 +175,15 @@ func (s *httpTunnel) Run() error {
|
||||
listener.ChainOption(ch),
|
||||
listener.LoggerOption(log.WithFields(map[string]any{"kind": "listener", "listener": "rtcp"})),
|
||||
)
|
||||
if err := ln.Init(mdx.NewMetadata(cfg.Listener.Metadata)); err != nil {
|
||||
return err
|
||||
if err = ln.Init(mdx.NewMetadata(cfg.Listener.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h := remote.NewHandler(
|
||||
handler.LoggerOption(log.WithFields(map[string]any{"kind": "handler", "handler": "rtcp"})),
|
||||
)
|
||||
if err := h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return err
|
||||
if err = h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
node := cfg.Forwarder.Nodes[0]
|
||||
@@ -203,7 +212,9 @@ func (s *httpTunnel) Run() error {
|
||||
s.forward = xservice.NewService(s.opts.Name, ln, h, xservice.LoggerOption(log))
|
||||
}
|
||||
|
||||
go s.forward.Serve()
|
||||
go func() {
|
||||
s.setErr(s.forward.Serve())
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -231,3 +242,15 @@ func (s *httpTunnel) IsClosed() bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *httpTunnel) setErr(err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.err = err
|
||||
}
|
||||
|
||||
func (s *httpTunnel) Err() error {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.err
|
||||
}
|
||||
|
||||
+37
-14
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/go-gost/core/chain"
|
||||
@@ -29,6 +30,9 @@ type tcpTunnel struct {
|
||||
favorite atomic.Bool
|
||||
|
||||
cclose chan struct{}
|
||||
|
||||
err error
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewTCPTunnel(opts ...Option) Tunnel {
|
||||
@@ -49,7 +53,7 @@ func NewTCPTunnel(opts ...Option) Tunnel {
|
||||
}
|
||||
|
||||
if options.Name == "" {
|
||||
options.Name = fmt.Sprintf("TCP-%s", endpoint)
|
||||
options.Name = endpoint
|
||||
}
|
||||
|
||||
s := &tcpTunnel{
|
||||
@@ -77,8 +81,8 @@ func (s *tcpTunnel) Endpoint() string {
|
||||
return s.opts.Endpoint
|
||||
}
|
||||
|
||||
func (f *tcpTunnel) Entrypoint() string {
|
||||
return fmt.Sprintf("%s.%s", f.endpoint, endpointAddr)
|
||||
func (s *tcpTunnel) Entrypoint() string {
|
||||
return fmt.Sprintf("%s.%s", s.endpoint, EndpointAddr)
|
||||
}
|
||||
|
||||
func (s *tcpTunnel) Options() Options {
|
||||
@@ -116,18 +120,22 @@ func (s *tcpTunnel) init() error {
|
||||
|
||||
s.config = &config.Config{
|
||||
Services: []*config.ServiceConfig{rtcp},
|
||||
Chains: []*config.ChainConfig{chainConfig(s.opts.ID, s.opts.Name)},
|
||||
Chains: []*config.ChainConfig{ChainConfig(s.opts.ID, s.opts.Name)},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *tcpTunnel) Run() error {
|
||||
func (s *tcpTunnel) Run() (err error) {
|
||||
if s.IsClosed() {
|
||||
return ErrTunnelClosed
|
||||
}
|
||||
|
||||
if err := s.init(); err != nil {
|
||||
return err
|
||||
defer func() {
|
||||
s.setErr(err)
|
||||
}()
|
||||
|
||||
if err = s.init(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log := logger.Default().WithFields(map[string]any{
|
||||
@@ -136,10 +144,11 @@ func (s *tcpTunnel) Run() error {
|
||||
})
|
||||
|
||||
{
|
||||
ch, err := chain_parser.ParseChain(s.config.Chains[0], log)
|
||||
var ch chain.Chainer
|
||||
ch, err = chain_parser.ParseChain(s.config.Chains[0], log)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
cfg := s.config.Services[0]
|
||||
@@ -148,15 +157,15 @@ func (s *tcpTunnel) Run() error {
|
||||
listener.ChainOption(ch),
|
||||
listener.LoggerOption(log.WithFields(map[string]any{"kind": "listener", "listener": "rtcp"})),
|
||||
)
|
||||
if err := ln.Init(mdx.NewMetadata(cfg.Listener.Metadata)); err != nil {
|
||||
return err
|
||||
if err = ln.Init(mdx.NewMetadata(cfg.Listener.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h := remote.NewHandler(
|
||||
handler.LoggerOption(log.WithFields(map[string]any{"kind": "handler", "handler": "rtcp"})),
|
||||
)
|
||||
if err := h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return err
|
||||
if err = h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
node := cfg.Forwarder.Nodes[0]
|
||||
@@ -169,7 +178,9 @@ func (s *tcpTunnel) Run() error {
|
||||
s.forward = xservice.NewService(s.opts.Name, ln, h, xservice.LoggerOption(log))
|
||||
}
|
||||
|
||||
go s.forward.Serve()
|
||||
go func() {
|
||||
s.setErr(s.forward.Serve())
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -197,3 +208,15 @@ func (s *tcpTunnel) IsClosed() bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *tcpTunnel) setErr(err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.err = err
|
||||
}
|
||||
|
||||
func (s *tcpTunnel) Err() error {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.err
|
||||
}
|
||||
|
||||
+49
-72
@@ -12,9 +12,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
endpointAddr = "gost.plus"
|
||||
serverName = "tunnel.gost.plus"
|
||||
serverAddr = serverName + ":443"
|
||||
EndpointAddr = "gost.plus"
|
||||
ServerName = "tunnel.gost.plus"
|
||||
ServerAddr = ServerName + ":443"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -94,54 +94,64 @@ type Tunnel interface {
|
||||
IsFavorite() bool
|
||||
Close() error
|
||||
IsClosed() bool
|
||||
Err() error
|
||||
}
|
||||
|
||||
type tunnelList struct {
|
||||
tunnels []Tunnel
|
||||
mux sync.RWMutex
|
||||
list []Tunnel
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
func (sl *tunnelList) Count() int {
|
||||
sl.mux.RLock()
|
||||
defer sl.mux.RUnlock()
|
||||
return len(sl.tunnels)
|
||||
var (
|
||||
tunnels tunnelList
|
||||
)
|
||||
|
||||
func Count() int {
|
||||
tunnels.mux.RLock()
|
||||
defer tunnels.mux.RUnlock()
|
||||
return len(tunnels.list)
|
||||
}
|
||||
|
||||
func (sl *tunnelList) Add(s Tunnel) {
|
||||
sl.mux.Lock()
|
||||
defer sl.mux.Unlock()
|
||||
sl.tunnels = append(sl.tunnels, s)
|
||||
func Add(s Tunnel) {
|
||||
tunnels.mux.Lock()
|
||||
defer tunnels.mux.Unlock()
|
||||
tunnels.list = append(tunnels.list, s)
|
||||
}
|
||||
|
||||
func (sl *tunnelList) Set(s Tunnel) {
|
||||
func Set(s Tunnel) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
t := Get(s.ID())
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
s.Favorite(t.IsFavorite())
|
||||
|
||||
sl.mux.Lock()
|
||||
defer sl.mux.Unlock()
|
||||
tunnels.mux.Lock()
|
||||
defer tunnels.mux.Unlock()
|
||||
|
||||
for i, sv := range sl.tunnels {
|
||||
for i, sv := range tunnels.list {
|
||||
if sv != nil && sv.ID() == s.ID() {
|
||||
sl.tunnels[i] = s
|
||||
tunnels.list[i] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sl *tunnelList) Get(index int) Tunnel {
|
||||
sl.mux.RLock()
|
||||
defer sl.mux.RUnlock()
|
||||
if index < 0 || index >= len(sl.tunnels) {
|
||||
func GetIndex(index int) Tunnel {
|
||||
tunnels.mux.RLock()
|
||||
defer tunnels.mux.RUnlock()
|
||||
if index < 0 || index >= len(tunnels.list) {
|
||||
return nil
|
||||
}
|
||||
return sl.tunnels[index]
|
||||
return tunnels.list[index]
|
||||
}
|
||||
|
||||
func (sl *tunnelList) GetID(id string) Tunnel {
|
||||
sl.mux.RLock()
|
||||
defer sl.mux.RUnlock()
|
||||
func Get(id string) Tunnel {
|
||||
tunnels.mux.RLock()
|
||||
defer tunnels.mux.RUnlock()
|
||||
|
||||
for _, s := range sl.tunnels {
|
||||
for _, s := range tunnels.list {
|
||||
if s != nil && s.ID() == id {
|
||||
return s
|
||||
}
|
||||
@@ -149,53 +159,20 @@ func (sl *tunnelList) GetID(id string) Tunnel {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sl *tunnelList) DeleteID(id string) {
|
||||
sl.mux.Lock()
|
||||
defer sl.mux.Unlock()
|
||||
func Delete(id string) {
|
||||
tunnels.mux.Lock()
|
||||
defer tunnels.mux.Unlock()
|
||||
|
||||
for i, s := range sl.tunnels {
|
||||
for i, s := range tunnels.list {
|
||||
if s != nil && s.ID() == id {
|
||||
s.Close()
|
||||
sl.tunnels[i] = nil
|
||||
tunnels.list[i] = nil
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
tunnels tunnelList
|
||||
)
|
||||
|
||||
func TunnelCount() int {
|
||||
return tunnels.Count()
|
||||
}
|
||||
|
||||
func AddTunnel(s Tunnel) {
|
||||
tunnels.Add(s)
|
||||
}
|
||||
|
||||
func SetTunnel(s Tunnel) {
|
||||
t := tunnels.GetID(s.ID())
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
s.Favorite(t.IsFavorite())
|
||||
tunnels.Set(s)
|
||||
}
|
||||
|
||||
func GetTunnel(index int) Tunnel {
|
||||
return tunnels.Get(index)
|
||||
}
|
||||
|
||||
func GetTunnelID(id string) Tunnel {
|
||||
return tunnels.GetID(id)
|
||||
}
|
||||
|
||||
func DeleteTunnel(id string) {
|
||||
tunnels.DeleteID(id)
|
||||
}
|
||||
|
||||
func chainConfig(id string, name string) *xconfig.ChainConfig {
|
||||
func ChainConfig(id string, name string) *xconfig.ChainConfig {
|
||||
return &xconfig.ChainConfig{
|
||||
Name: name,
|
||||
Hops: []*xconfig.HopConfig{
|
||||
@@ -204,7 +181,7 @@ func chainConfig(id string, name string) *xconfig.ChainConfig {
|
||||
Nodes: []*xconfig.NodeConfig{
|
||||
{
|
||||
Name: name,
|
||||
Addr: serverAddr,
|
||||
Addr: ServerAddr,
|
||||
Connector: &xconfig.ConnectorConfig{
|
||||
Type: "tunnel",
|
||||
Metadata: map[string]any{"tunnel.id": id},
|
||||
@@ -213,7 +190,7 @@ func chainConfig(id string, name string) *xconfig.ChainConfig {
|
||||
Type: "wss",
|
||||
TLS: &xconfig.TLSConfig{
|
||||
Secure: true,
|
||||
ServerName: serverName,
|
||||
ServerName: ServerName,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -249,16 +226,16 @@ func LoadConfig() {
|
||||
}
|
||||
|
||||
s.Favorite(tun.Favorite)
|
||||
tunnels.Add(s)
|
||||
Add(s)
|
||||
}
|
||||
}
|
||||
|
||||
func SaveTunnel() error {
|
||||
func SaveConfig() error {
|
||||
cfg := config.Global()
|
||||
cfg.Tunnels = nil
|
||||
|
||||
for i := 0; i < tunnels.Count(); i++ {
|
||||
tun := tunnels.Get(i)
|
||||
for i := 0; i < Count(); i++ {
|
||||
tun := GetIndex(i)
|
||||
if tun == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
+37
-14
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/go-gost/core/chain"
|
||||
@@ -29,6 +30,9 @@ type udpTunnel struct {
|
||||
favorite atomic.Bool
|
||||
|
||||
cclose chan struct{}
|
||||
|
||||
err error
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewUDPTunnel(opts ...Option) Tunnel {
|
||||
@@ -49,7 +53,7 @@ func NewUDPTunnel(opts ...Option) Tunnel {
|
||||
}
|
||||
|
||||
if options.Name == "" {
|
||||
options.Name = fmt.Sprintf("UDP-%s", endpoint)
|
||||
options.Name = endpoint
|
||||
}
|
||||
|
||||
s := &udpTunnel{
|
||||
@@ -77,8 +81,8 @@ func (s *udpTunnel) Endpoint() string {
|
||||
return s.opts.Endpoint
|
||||
}
|
||||
|
||||
func (f *udpTunnel) Entrypoint() string {
|
||||
return fmt.Sprintf("%s.%s", f.endpoint, endpointAddr)
|
||||
func (s *udpTunnel) Entrypoint() string {
|
||||
return fmt.Sprintf("%s.%s", s.endpoint, EndpointAddr)
|
||||
}
|
||||
|
||||
func (s *udpTunnel) Options() Options {
|
||||
@@ -116,18 +120,22 @@ func (s *udpTunnel) init() error {
|
||||
|
||||
s.config = &config.Config{
|
||||
Services: []*config.ServiceConfig{rudp},
|
||||
Chains: []*config.ChainConfig{chainConfig(s.opts.ID, s.opts.Name)},
|
||||
Chains: []*config.ChainConfig{ChainConfig(s.opts.ID, s.opts.Name)},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *udpTunnel) Run() error {
|
||||
func (s *udpTunnel) Run() (err error) {
|
||||
if s.IsClosed() {
|
||||
return ErrTunnelClosed
|
||||
}
|
||||
|
||||
if err := s.init(); err != nil {
|
||||
return err
|
||||
defer func() {
|
||||
s.setErr(err)
|
||||
}()
|
||||
|
||||
if err = s.init(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log := logger.Default().WithFields(map[string]any{
|
||||
@@ -136,10 +144,11 @@ func (s *udpTunnel) Run() error {
|
||||
})
|
||||
|
||||
{
|
||||
ch, err := chain_parser.ParseChain(s.config.Chains[0], log)
|
||||
var ch chain.Chainer
|
||||
ch, err = chain_parser.ParseChain(s.config.Chains[0], log)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
cfg := s.config.Services[0]
|
||||
@@ -148,15 +157,15 @@ func (s *udpTunnel) Run() error {
|
||||
listener.ChainOption(ch),
|
||||
listener.LoggerOption(log.WithFields(map[string]any{"kind": "listener", "listener": "rudp"})),
|
||||
)
|
||||
if err := ln.Init(mdx.NewMetadata(cfg.Listener.Metadata)); err != nil {
|
||||
return err
|
||||
if err = ln.Init(mdx.NewMetadata(cfg.Listener.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h := remote.NewHandler(
|
||||
handler.LoggerOption(log.WithFields(map[string]any{"kind": "handler", "handler": "rudp"})),
|
||||
)
|
||||
if err := h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return err
|
||||
if err = h.Init(mdx.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
node := cfg.Forwarder.Nodes[0]
|
||||
@@ -169,7 +178,9 @@ func (s *udpTunnel) Run() error {
|
||||
s.forward = xservice.NewService(s.opts.Name, ln, h, xservice.LoggerOption(log))
|
||||
}
|
||||
|
||||
go s.forward.Serve()
|
||||
go func() {
|
||||
s.setErr(s.forward.Serve())
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -197,3 +208,15 @@ func (s *udpTunnel) IsClosed() bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *udpTunnel) setErr(err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.err = err
|
||||
}
|
||||
|
||||
func (s *udpTunnel) Err() error {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.err
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ var (
|
||||
IconStop = mustIcon(icons.AVStop)
|
||||
IconBack = mustIcon(icons.NavigationArrowBack)
|
||||
IconClose = mustIcon(icons.ContentClear)
|
||||
IconCopy = mustIcon(icons.ContentContentCopy)
|
||||
)
|
||||
|
||||
func mustIcon(data []byte) *widget.Icon {
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
package page
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"gioui.org/font"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
"gioui.org/x/component"
|
||||
"github.com/go-gost/gost-plus/tunnel/entrypoint"
|
||||
"github.com/go-gost/gost-plus/ui/icons"
|
||||
"golang.org/x/exp/shiny/materialdesign/colornames"
|
||||
)
|
||||
|
||||
type entryPointState struct {
|
||||
editor widget.Clickable
|
||||
}
|
||||
|
||||
type entryPointPage struct {
|
||||
router *Router
|
||||
|
||||
list layout.List
|
||||
|
||||
wgFavorite widget.Clickable
|
||||
wgAdd widget.Clickable
|
||||
|
||||
entryPoints map[int]*entryPointState
|
||||
favorite atomic.Bool
|
||||
}
|
||||
|
||||
func NewEntryPointPage(r *Router) Page {
|
||||
return &entryPointPage{
|
||||
router: r,
|
||||
list: layout.List{
|
||||
Axis: layout.Vertical,
|
||||
Alignment: layout.Middle,
|
||||
},
|
||||
entryPoints: make(map[int]*entryPointState),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *entryPointPage) Init(opts ...PageOption) {
|
||||
p.router.bar.SetActions(
|
||||
[]component.AppBarAction{
|
||||
{
|
||||
OverflowAction: component.OverflowAction{
|
||||
Name: "Favorite",
|
||||
Tag: &p.wgFavorite,
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgFavorite.Clicked(gtx) {
|
||||
p.favorite.Store(!p.favorite.Load())
|
||||
}
|
||||
|
||||
btn := component.SimpleIconButton(bg, fg, &p.wgFavorite, icons.IconFavorite)
|
||||
if p.favorite.Load() {
|
||||
btn.Color = color.NRGBA(colornames.Red500)
|
||||
} else {
|
||||
btn.Color = fg
|
||||
}
|
||||
return btn.Layout(gtx)
|
||||
},
|
||||
},
|
||||
{
|
||||
OverflowAction: component.OverflowAction{
|
||||
Name: "Add",
|
||||
Tag: &p.wgAdd,
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgAdd.Clicked(gtx) {
|
||||
p.router.SwitchTo(Route{Path: PageMenu})
|
||||
}
|
||||
return component.SimpleIconButton(bg, fg, &p.wgAdd, icons.IconAdd).Layout(gtx)
|
||||
},
|
||||
},
|
||||
},
|
||||
[]component.OverflowAction{
|
||||
{
|
||||
Name: "About",
|
||||
Tag: OverflowActionAbout,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
p.router.bar.Title = "EntryPoints"
|
||||
p.router.bar.NavigationIcon = icons.IconHome
|
||||
}
|
||||
|
||||
func (p *entryPointPage) Layout(gtx C, th *material.Theme) D {
|
||||
favorite := p.favorite.Load()
|
||||
// gtx.Constraints.Min.X = gtx.Constraints.Max.X
|
||||
return p.list.Layout(gtx, entrypoint.Count(), func(gtx C, index int) D {
|
||||
s := entrypoint.GetIndex(index)
|
||||
if s == nil {
|
||||
delete(p.entryPoints, index)
|
||||
return D{}
|
||||
}
|
||||
|
||||
if p.entryPoints[index] == nil {
|
||||
p.entryPoints[index] = &entryPointState{}
|
||||
}
|
||||
|
||||
if favorite && !s.IsFavorite() {
|
||||
return D{}
|
||||
}
|
||||
|
||||
return layout.Center.Layout(gtx, func(gtx C) D {
|
||||
return layout.UniformInset(10).Layout(gtx, func(gtx C) D {
|
||||
return component.Surface(th).Layout(gtx, func(gtx C) D {
|
||||
state := p.entryPoints[index]
|
||||
if state.editor.Clicked(gtx) {
|
||||
switch s.Type() {
|
||||
case entrypoint.TCPEntryPoint:
|
||||
p.router.SwitchTo(Route{Path: PageEditTCPEntryPoint, ID: s.ID()})
|
||||
}
|
||||
op.InvalidateOp{}.Add(gtx.Ops)
|
||||
}
|
||||
return state.editor.Layout(gtx, func(gtx C) D {
|
||||
return layout.UniformInset(10).Layout(gtx, func(gtx C) D {
|
||||
return p.layout(gtx, th, s)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (p *entryPointPage) layout(gtx C, th *material.Theme, ep entrypoint.EntryPoint) D {
|
||||
return layout.Flex{
|
||||
Alignment: layout.Middle,
|
||||
Spacing: layout.SpaceBetween,
|
||||
}.Layout(gtx,
|
||||
layout.Flexed(1, func(gtx C) D {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx C) D {
|
||||
label := material.Body1(th, ep.ID())
|
||||
label.Font.Weight = font.Bold
|
||||
return label.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: 5}.Layout),
|
||||
layout.Rigid(material.Body2(th, fmt.Sprintf("Type: %s", strings.ToUpper(ep.Type()))).Layout),
|
||||
layout.Rigid(layout.Spacer{Height: 5}.Layout),
|
||||
layout.Rigid(material.Body2(th, fmt.Sprintf("Name: %s", ep.Name())).Layout),
|
||||
layout.Rigid(layout.Spacer{Height: 5}.Layout),
|
||||
layout.Rigid(material.Body2(th, fmt.Sprintf("Endpoint: %s", ep.Endpoint())).Layout),
|
||||
layout.Rigid(layout.Spacer{Height: 5}.Layout),
|
||||
layout.Rigid(material.Body2(th, fmt.Sprintf("Entrypoint: %s", ep.Entrypoint())).Layout),
|
||||
layout.Rigid(layout.Spacer{Height: 5}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
if err := ep.Err(); !ep.IsClosed() && err != nil {
|
||||
label := material.Body2(th, err.Error())
|
||||
label.Color = color.NRGBA(colornames.Red500)
|
||||
return label.Layout(gtx)
|
||||
}
|
||||
return D{}
|
||||
}),
|
||||
)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: 10}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
c := colornames.Green500
|
||||
if ep.Err() != nil {
|
||||
c = colornames.Red500
|
||||
}
|
||||
if ep.IsClosed() {
|
||||
c = colornames.Grey500
|
||||
}
|
||||
return icons.IconTunnelState.Layout(gtx, color.NRGBA(c))
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
package page
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"gioui.org/layout"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
"gioui.org/x/component"
|
||||
"github.com/go-gost/gost-plus/tunnel"
|
||||
"github.com/go-gost/gost-plus/tunnel/entrypoint"
|
||||
"github.com/go-gost/gost-plus/ui/icons"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/exp/shiny/materialdesign/colornames"
|
||||
)
|
||||
|
||||
type tcpEntryPointAddPage struct {
|
||||
router *Router
|
||||
|
||||
list layout.List
|
||||
wgDone widget.Clickable
|
||||
|
||||
name component.TextField
|
||||
tunnelID component.TextField
|
||||
addr component.TextField
|
||||
}
|
||||
|
||||
func NewTCPEntryPointAddPage(r *Router) Page {
|
||||
return &tcpEntryPointAddPage{
|
||||
router: r,
|
||||
list: layout.List{
|
||||
Axis: layout.Vertical,
|
||||
Alignment: layout.Middle,
|
||||
},
|
||||
name: component.TextField{
|
||||
Editor: widget.Editor{
|
||||
SingleLine: true,
|
||||
},
|
||||
},
|
||||
tunnelID: component.TextField{
|
||||
Editor: widget.Editor{
|
||||
SingleLine: true,
|
||||
},
|
||||
},
|
||||
addr: component.TextField{
|
||||
Editor: widget.Editor{
|
||||
SingleLine: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *tcpEntryPointAddPage) Init(opts ...PageOption) {
|
||||
p.name.SetText("")
|
||||
p.tunnelID.SetText("")
|
||||
p.addr.SetText("")
|
||||
|
||||
p.router.bar.SetActions(
|
||||
[]component.AppBarAction{
|
||||
{
|
||||
OverflowAction: component.OverflowAction{
|
||||
Name: "Create",
|
||||
Tag: &p.wgDone,
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgDone.Clicked(gtx) {
|
||||
defer p.router.SwitchTo(Route{Path: PageEntryPoint})
|
||||
p.createEntryPoint()
|
||||
entrypoint.SaveConfig()
|
||||
}
|
||||
return component.SimpleIconButton(bg, fg, &p.wgDone, icons.IconDone).Layout(gtx)
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
p.router.bar.Title = "TCP"
|
||||
p.router.bar.NavigationIcon = icons.IconClose
|
||||
}
|
||||
|
||||
func (p *tcpEntryPointAddPage) Layout(gtx C, th *material.Theme) D {
|
||||
return p.list.Layout(gtx, 1, func(gtx C, _ int) D {
|
||||
return layout.Center.Layout(gtx, func(gtx C) D {
|
||||
return layout.UniformInset(10).Layout(gtx, func(gtx C) D {
|
||||
return component.Surface(th).Layout(gtx, func(gtx C) D {
|
||||
return layout.UniformInset(10).Layout(gtx, func(gtx C) D {
|
||||
return p.layout(gtx, th)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (p *tcpEntryPointAddPage) layout(gtx C, th *material.Theme) D {
|
||||
return layout.Flex{
|
||||
Axis: layout.Vertical,
|
||||
}.Layout(gtx,
|
||||
layout.Rigid(material.Body1(th, "Create an entrypoint to connect to the specified TCP tunnel").Layout),
|
||||
layout.Rigid(layout.Spacer{Height: 10}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return material.Body1(th, "Entrypoint name").Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return p.name.Layout(gtx, th, "Name")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: 10}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return material.Body1(th, "Tunnel ID").Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
if err := func() error {
|
||||
tid := strings.TrimSpace(p.tunnelID.Text())
|
||||
if tid == "" {
|
||||
return nil
|
||||
}
|
||||
if _, err := uuid.Parse(tid); err != nil {
|
||||
return fmt.Errorf("invalid tunnel ID, should be a valid UUID")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
p.tunnelID.SetError(err.Error())
|
||||
} else {
|
||||
p.tunnelID.ClearError()
|
||||
}
|
||||
|
||||
return p.tunnelID.Layout(gtx, th, "ID")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: 10}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return material.Body1(th, "Entrypoint address").Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
if err := func() error {
|
||||
addr := strings.TrimSpace(p.addr.Text())
|
||||
if addr == "" {
|
||||
return nil
|
||||
}
|
||||
if _, err := net.ResolveTCPAddr("tcp", addr); err != nil {
|
||||
return fmt.Errorf("invalid address format, should be [IP]:PORT or [HOST]:PORT")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
p.addr.SetError(err.Error())
|
||||
} else {
|
||||
p.addr.ClearError()
|
||||
}
|
||||
|
||||
return p.addr.Layout(gtx, th, "Address")
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func (p *tcpEntryPointAddPage) createEntryPoint() error {
|
||||
tun := entrypoint.NewTCPEntryPoint(
|
||||
tunnel.NameOption(strings.TrimSpace(p.name.Text())),
|
||||
tunnel.IDOption(strings.ToLower(strings.TrimSpace(p.tunnelID.Text()))),
|
||||
tunnel.EndpointOption(strings.TrimSpace(p.addr.Text())),
|
||||
)
|
||||
|
||||
entrypoint.Add(tun)
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type tcpEntryPointEditPage struct {
|
||||
router *Router
|
||||
|
||||
id string
|
||||
|
||||
list layout.List
|
||||
wgFavorite widget.Clickable
|
||||
wgState widget.Clickable
|
||||
wgDelete widget.Clickable
|
||||
wgDone widget.Clickable
|
||||
|
||||
name component.TextField
|
||||
tunnelID component.TextField
|
||||
addr component.TextField
|
||||
}
|
||||
|
||||
func NewTCPEntryPointEditPage(r *Router) Page {
|
||||
return &tcpEntryPointEditPage{
|
||||
router: r,
|
||||
list: layout.List{
|
||||
Axis: layout.Vertical,
|
||||
Alignment: layout.Middle,
|
||||
},
|
||||
name: component.TextField{
|
||||
Editor: widget.Editor{
|
||||
SingleLine: true,
|
||||
},
|
||||
},
|
||||
tunnelID: component.TextField{
|
||||
Editor: widget.Editor{
|
||||
SingleLine: true,
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
addr: component.TextField{
|
||||
Editor: widget.Editor{
|
||||
SingleLine: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *tcpEntryPointEditPage) Init(opts ...PageOption) {
|
||||
var options PageOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
p.id = options.ID
|
||||
|
||||
s := entrypoint.Get(p.id)
|
||||
if s != nil {
|
||||
sopts := s.Options()
|
||||
p.name.SetText(sopts.Name)
|
||||
p.tunnelID.SetText(sopts.ID)
|
||||
p.addr.SetText(sopts.Endpoint)
|
||||
}
|
||||
|
||||
actions := []component.AppBarAction{
|
||||
{
|
||||
OverflowAction: component.OverflowAction{
|
||||
Name: "Favorite",
|
||||
Tag: &p.wgFavorite,
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
s := entrypoint.Get(p.id)
|
||||
if s == nil {
|
||||
return D{}
|
||||
}
|
||||
|
||||
if p.wgFavorite.Clicked(gtx) {
|
||||
s.Favorite(!s.IsFavorite())
|
||||
entrypoint.SaveConfig()
|
||||
}
|
||||
|
||||
btn := component.SimpleIconButton(bg, fg, &p.wgFavorite, icons.IconFavorite)
|
||||
if s.IsFavorite() {
|
||||
btn.Color = color.NRGBA(colornames.Red500)
|
||||
} else {
|
||||
btn.Color = fg
|
||||
}
|
||||
return btn.Layout(gtx)
|
||||
},
|
||||
},
|
||||
{
|
||||
OverflowAction: component.OverflowAction{
|
||||
Name: "Start/Stop",
|
||||
Tag: &p.wgState,
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
s := entrypoint.Get(p.id)
|
||||
if p.wgState.Clicked(gtx) && s != nil {
|
||||
if s.IsClosed() {
|
||||
s = p.createEntryPoint()
|
||||
} else {
|
||||
s.Close()
|
||||
}
|
||||
entrypoint.SaveConfig()
|
||||
}
|
||||
|
||||
if s != nil && !s.IsClosed() {
|
||||
return component.SimpleIconButton(bg, fg, &p.wgState, icons.IconStop).Layout(gtx)
|
||||
} else {
|
||||
return component.SimpleIconButton(bg, fg, &p.wgState, icons.IconStart).Layout(gtx)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
OverflowAction: component.OverflowAction{
|
||||
Name: "Delete",
|
||||
Tag: &p.wgDelete,
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgDelete.Clicked(gtx) {
|
||||
entrypoint.Delete(p.id)
|
||||
entrypoint.SaveConfig()
|
||||
p.router.SwitchTo(Route{Path: PageEntryPoint})
|
||||
}
|
||||
return component.SimpleIconButton(bg, fg, &p.wgDelete, icons.IconDelete).Layout(gtx)
|
||||
},
|
||||
},
|
||||
{
|
||||
OverflowAction: component.OverflowAction{
|
||||
Name: "Save",
|
||||
Tag: &p.wgDone,
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgDone.Clicked(gtx) {
|
||||
defer p.router.SwitchTo(Route{Path: PageEntryPoint})
|
||||
|
||||
if s := entrypoint.Get(p.id); s != nil {
|
||||
s.Close()
|
||||
p.createEntryPoint()
|
||||
entrypoint.SaveConfig()
|
||||
}
|
||||
}
|
||||
return component.SimpleIconButton(bg, fg, &p.wgDone, icons.IconDone).Layout(gtx)
|
||||
},
|
||||
},
|
||||
}
|
||||
p.router.bar.SetActions(actions, nil)
|
||||
p.router.bar.Title = "TCP"
|
||||
p.router.bar.NavigationIcon = icons.IconClose
|
||||
}
|
||||
|
||||
func (p *tcpEntryPointEditPage) createEntryPoint() entrypoint.EntryPoint {
|
||||
s := entrypoint.NewTCPEntryPoint(
|
||||
tunnel.IDOption(p.id),
|
||||
tunnel.NameOption(strings.TrimSpace(p.name.Text())),
|
||||
tunnel.IDOption(strings.ToLower(strings.TrimSpace(p.tunnelID.Text()))),
|
||||
tunnel.EndpointOption(strings.TrimSpace(p.addr.Text())),
|
||||
)
|
||||
|
||||
if err := s.Run(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
entrypoint.Set(s)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (p *tcpEntryPointEditPage) Layout(gtx C, th *material.Theme) D {
|
||||
return p.list.Layout(gtx, 1, func(gtx C, _ int) D {
|
||||
return layout.Center.Layout(gtx, func(gtx C) D {
|
||||
return layout.UniformInset(10).Layout(gtx, func(gtx C) D {
|
||||
return component.Surface(th).Layout(gtx, func(gtx C) D {
|
||||
return layout.UniformInset(10).Layout(gtx, func(gtx C) D {
|
||||
return p.layout(gtx, th)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (p *tcpEntryPointEditPage) layout(gtx C, th *material.Theme) D {
|
||||
return layout.Flex{
|
||||
Axis: layout.Vertical,
|
||||
}.Layout(gtx,
|
||||
layout.Rigid(layout.Spacer{Height: 10}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return material.Body1(th, "Entrypoint name").Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return p.name.Layout(gtx, th, "Name")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: 10}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return material.Body1(th, "Tunnel ID").Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
if err := func() error {
|
||||
tid := strings.TrimSpace(p.tunnelID.Text())
|
||||
if tid == "" {
|
||||
return nil
|
||||
}
|
||||
if _, err := uuid.Parse(tid); err != nil {
|
||||
return fmt.Errorf("invalid tunnel ID, should be a valid UUID")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
p.tunnelID.SetError(err.Error())
|
||||
} else {
|
||||
p.tunnelID.ClearError()
|
||||
}
|
||||
|
||||
return p.tunnelID.Layout(gtx, th, "ID")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: 10}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return material.Body1(th, "Entrypoint address").Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
if err := func() error {
|
||||
addr := strings.TrimSpace(p.addr.Text())
|
||||
if addr == "" {
|
||||
return nil
|
||||
}
|
||||
if _, err := net.ResolveTCPAddr("tcp", addr); err != nil {
|
||||
return fmt.Errorf("invalid address format, should be [IP]:PORT or [HOST]:PORT")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
p.addr.SetError(err.Error())
|
||||
} else {
|
||||
p.addr.ClearError()
|
||||
}
|
||||
|
||||
return p.addr.Layout(gtx, th, "Address")
|
||||
}),
|
||||
)
|
||||
}
|
||||
+23
-19
@@ -75,9 +75,9 @@ func (p *fileAddPage) Init(opts ...PageOption) {
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgDone.Clicked(gtx) {
|
||||
defer p.router.SwitchTo(Route{Path: PageHome})
|
||||
defer p.router.SwitchTo(Route{Path: PageTunnel})
|
||||
p.createTunnel()
|
||||
tunnel.SaveTunnel()
|
||||
tunnel.SaveConfig()
|
||||
}
|
||||
return component.SimpleIconButton(bg, fg, &p.wgDone, icons.IconDone).Layout(gtx)
|
||||
},
|
||||
@@ -186,11 +186,13 @@ func (p *fileAddPage) createTunnel() error {
|
||||
tunnel.PasswordOption(password),
|
||||
)
|
||||
|
||||
tunnel.Add(tun)
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
tunnel.AddTunnel(tun)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -252,7 +254,7 @@ func (p *fileEditPage) Init(opts ...PageOption) {
|
||||
}
|
||||
|
||||
p.id = options.ID
|
||||
s := tunnel.GetTunnelID(p.id)
|
||||
s := tunnel.Get(p.id)
|
||||
if s != nil {
|
||||
sopts := s.Options()
|
||||
p.name.SetText(sopts.Name)
|
||||
@@ -271,14 +273,14 @@ func (p *fileEditPage) Init(opts ...PageOption) {
|
||||
Tag: &p.wgFavorite,
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
s := tunnel.GetTunnelID(p.id)
|
||||
s := tunnel.Get(p.id)
|
||||
if s == nil {
|
||||
return D{}
|
||||
}
|
||||
|
||||
if p.wgFavorite.Clicked(gtx) {
|
||||
s.Favorite(!s.IsFavorite())
|
||||
tunnel.SaveTunnel()
|
||||
tunnel.SaveConfig()
|
||||
}
|
||||
|
||||
btn := component.SimpleIconButton(bg, fg, &p.wgFavorite, icons.IconFavorite)
|
||||
@@ -296,7 +298,7 @@ func (p *fileEditPage) Init(opts ...PageOption) {
|
||||
Tag: &p.wgState,
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
s := tunnel.GetTunnelID(p.id)
|
||||
s := tunnel.Get(p.id)
|
||||
if s == nil {
|
||||
return D{}
|
||||
}
|
||||
@@ -307,7 +309,7 @@ func (p *fileEditPage) Init(opts ...PageOption) {
|
||||
} else {
|
||||
s.Close()
|
||||
}
|
||||
tunnel.SaveTunnel()
|
||||
tunnel.SaveConfig()
|
||||
}
|
||||
|
||||
if s != nil && !s.IsClosed() {
|
||||
@@ -324,9 +326,9 @@ func (p *fileEditPage) Init(opts ...PageOption) {
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgDelete.Clicked(gtx) {
|
||||
tunnel.DeleteTunnel(p.id)
|
||||
tunnel.SaveTunnel()
|
||||
p.router.SwitchTo(Route{Path: PageHome})
|
||||
tunnel.Delete(p.id)
|
||||
tunnel.SaveConfig()
|
||||
p.router.SwitchTo(Route{Path: PageTunnel})
|
||||
}
|
||||
return component.SimpleIconButton(bg, fg, &p.wgDelete, icons.IconDelete).Layout(gtx)
|
||||
},
|
||||
@@ -338,12 +340,12 @@ func (p *fileEditPage) Init(opts ...PageOption) {
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgDone.Clicked(gtx) {
|
||||
defer p.router.SwitchTo(Route{Path: PageHome})
|
||||
defer p.router.SwitchTo(Route{Path: PageTunnel})
|
||||
|
||||
if s := tunnel.GetTunnelID(p.id); s != nil {
|
||||
if s := tunnel.Get(p.id); s != nil {
|
||||
s.Close()
|
||||
p.createTunnel()
|
||||
tunnel.SaveTunnel()
|
||||
tunnel.SaveConfig()
|
||||
}
|
||||
}
|
||||
return component.SimpleIconButton(bg, fg, &p.wgDone, icons.IconDone).Layout(gtx)
|
||||
@@ -361,7 +363,7 @@ func (p *fileEditPage) createTunnel() tunnel.Tunnel {
|
||||
username = strings.TrimSpace(p.username.Text())
|
||||
password = strings.TrimSpace(p.password.Text())
|
||||
}
|
||||
s := tunnel.NewFileTunnel(
|
||||
tun := tunnel.NewFileTunnel(
|
||||
tunnel.IDOption(p.id),
|
||||
tunnel.NameOption(strings.TrimSpace(p.name.Text())),
|
||||
tunnel.EndpointOption(strings.TrimSpace(p.path.Text())),
|
||||
@@ -369,12 +371,14 @@ func (p *fileEditPage) createTunnel() tunnel.Tunnel {
|
||||
tunnel.PasswordOption(password),
|
||||
)
|
||||
|
||||
if err := s.Run(); err != nil {
|
||||
tunnel.Set(tun)
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
log.Println(err)
|
||||
}
|
||||
tunnel.SetTunnel(s)
|
||||
|
||||
return s
|
||||
return tun
|
||||
}
|
||||
|
||||
func (p *fileEditPage) Layout(gtx C, th *material.Theme) D {
|
||||
@@ -396,7 +400,7 @@ func (p *fileEditPage) layout(gtx C, th *material.Theme) D {
|
||||
Axis: layout.Vertical,
|
||||
}.Layout(gtx,
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return layoutHeader(gtx, th, tunnel.GetTunnelID(p.id), &p.wgID, &p.wgEntrypoint)
|
||||
return layoutHeader(gtx, th, tunnel.Get(p.id), &p.wgID, &p.wgEntrypoint)
|
||||
}),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
div := component.Divider(th)
|
||||
|
||||
+26
-22
@@ -88,9 +88,9 @@ func (p *httpAddPage) Init(opts ...PageOption) {
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgDone.Clicked(gtx) {
|
||||
defer p.router.SwitchTo(Route{Path: PageHome})
|
||||
defer p.router.SwitchTo(Route{Path: PageTunnel})
|
||||
p.createTunnel()
|
||||
tunnel.SaveTunnel()
|
||||
tunnel.SaveConfig()
|
||||
}
|
||||
return component.SimpleIconButton(bg, fg, &p.wgDone, icons.IconDone).Layout(gtx)
|
||||
},
|
||||
@@ -210,7 +210,7 @@ func (p *httpAddPage) createTunnel() error {
|
||||
if p.bHost.Value {
|
||||
hostname = strings.TrimSpace(p.hostname.Text())
|
||||
}
|
||||
srv := tunnel.NewHTTPTunnel(
|
||||
tun := tunnel.NewHTTPTunnel(
|
||||
tunnel.NameOption(strings.TrimSpace(p.name.Text())),
|
||||
tunnel.EndpointOption(strings.TrimSpace(p.addr.Text())),
|
||||
tunnel.UsernameOption(username),
|
||||
@@ -219,11 +219,13 @@ func (p *httpAddPage) createTunnel() error {
|
||||
tunnel.EnableTLSOption(p.bTLS.Value),
|
||||
)
|
||||
|
||||
if err := srv.Run(); err != nil {
|
||||
tunnel.Add(tun)
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
tunnel.AddTunnel(srv)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -296,7 +298,7 @@ func (p *httpEditPage) Init(opts ...PageOption) {
|
||||
}
|
||||
|
||||
p.id = options.ID
|
||||
s := tunnel.GetTunnelID(p.id)
|
||||
s := tunnel.Get(p.id)
|
||||
if s != nil {
|
||||
sopts := s.Options()
|
||||
p.name.SetText(sopts.Name)
|
||||
@@ -320,14 +322,14 @@ func (p *httpEditPage) Init(opts ...PageOption) {
|
||||
Tag: &p.wgFavorite,
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
s := tunnel.GetTunnelID(p.id)
|
||||
s := tunnel.Get(p.id)
|
||||
if s == nil {
|
||||
return D{}
|
||||
}
|
||||
|
||||
if p.wgFavorite.Clicked(gtx) {
|
||||
s.Favorite(!s.IsFavorite())
|
||||
tunnel.SaveTunnel()
|
||||
tunnel.SaveConfig()
|
||||
}
|
||||
|
||||
btn := component.SimpleIconButton(bg, fg, &p.wgFavorite, icons.IconFavorite)
|
||||
@@ -345,14 +347,14 @@ func (p *httpEditPage) Init(opts ...PageOption) {
|
||||
Tag: &p.wgState,
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
s := tunnel.GetTunnelID(p.id)
|
||||
s := tunnel.Get(p.id)
|
||||
if p.wgState.Clicked(gtx) && s != nil {
|
||||
if s.IsClosed() {
|
||||
s = p.createTunnel()
|
||||
} else {
|
||||
s.Close()
|
||||
}
|
||||
tunnel.SaveTunnel()
|
||||
tunnel.SaveConfig()
|
||||
}
|
||||
|
||||
if s != nil && !s.IsClosed() {
|
||||
@@ -369,9 +371,9 @@ func (p *httpEditPage) Init(opts ...PageOption) {
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgDelete.Clicked(gtx) {
|
||||
tunnel.DeleteTunnel(p.id)
|
||||
tunnel.SaveTunnel()
|
||||
p.router.SwitchTo(Route{Path: PageHome})
|
||||
tunnel.Delete(p.id)
|
||||
tunnel.SaveConfig()
|
||||
p.router.SwitchTo(Route{Path: PageTunnel})
|
||||
}
|
||||
return component.SimpleIconButton(bg, fg, &p.wgDelete, icons.IconDelete).Layout(gtx)
|
||||
},
|
||||
@@ -383,12 +385,12 @@ func (p *httpEditPage) Init(opts ...PageOption) {
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgDone.Clicked(gtx) {
|
||||
defer p.router.SwitchTo(Route{Path: PageHome})
|
||||
defer p.router.SwitchTo(Route{Path: PageTunnel})
|
||||
|
||||
if s := tunnel.GetTunnelID(p.id); s != nil {
|
||||
if s := tunnel.Get(p.id); s != nil {
|
||||
s.Close()
|
||||
p.createTunnel()
|
||||
tunnel.SaveTunnel()
|
||||
tunnel.SaveConfig()
|
||||
}
|
||||
}
|
||||
return component.SimpleIconButton(bg, fg, &p.wgDone, icons.IconDone).Layout(gtx)
|
||||
@@ -410,7 +412,7 @@ func (p *httpEditPage) createTunnel() tunnel.Tunnel {
|
||||
if p.bHost.Value {
|
||||
hostname = strings.TrimSpace(p.hostname.Text())
|
||||
}
|
||||
s := tunnel.NewHTTPTunnel(
|
||||
tun := tunnel.NewHTTPTunnel(
|
||||
tunnel.IDOption(p.id),
|
||||
tunnel.NameOption(strings.TrimSpace(p.name.Text())),
|
||||
tunnel.EndpointOption(strings.TrimSpace(p.addr.Text())),
|
||||
@@ -420,12 +422,14 @@ func (p *httpEditPage) createTunnel() tunnel.Tunnel {
|
||||
tunnel.EnableTLSOption(p.bTLS.Value),
|
||||
)
|
||||
|
||||
if err := s.Run(); err != nil {
|
||||
tunnel.Set(tun)
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
log.Println(err)
|
||||
}
|
||||
tunnel.SetTunnel(s)
|
||||
|
||||
return s
|
||||
return tun
|
||||
}
|
||||
|
||||
func (p *httpEditPage) Layout(gtx C, th *material.Theme) D {
|
||||
@@ -447,7 +451,7 @@ func (p *httpEditPage) layout(gtx C, th *material.Theme) D {
|
||||
Axis: layout.Vertical,
|
||||
}.Layout(gtx,
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return layoutHeader(gtx, th, tunnel.GetTunnelID(p.id), &p.wgID, &p.wgEntrypoint)
|
||||
return layoutHeader(gtx, th, tunnel.Get(p.id), &p.wgID, &p.wgEntrypoint)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: 10}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
@@ -466,7 +470,7 @@ func (p *httpEditPage) layout(gtx C, th *material.Theme) D {
|
||||
if addr == "" {
|
||||
return nil
|
||||
}
|
||||
if _, _, err := net.SplitHostPort(addr); err != nil {
|
||||
if _, err := net.ResolveTCPAddr("tcp", addr); err != nil {
|
||||
return fmt.Errorf("invalid address format, should be [IP]:PORT or [HOST]:PORT")
|
||||
}
|
||||
return nil
|
||||
|
||||
+26
-2
@@ -22,6 +22,9 @@ type menuPage struct {
|
||||
wgHTTP widget.Clickable
|
||||
wgTCP widget.Clickable
|
||||
wgUDP widget.Clickable
|
||||
|
||||
wgEntryPointTCP widget.Clickable
|
||||
wgEntryPointUDP widget.Clickable
|
||||
}
|
||||
|
||||
func NewMenuPage(r *Router) Page {
|
||||
@@ -59,6 +62,10 @@ func (p *menuPage) Layout(gtx C, th *material.Theme) D {
|
||||
p.router.SwitchTo(Route{Path: PageNewUDP})
|
||||
return true
|
||||
}
|
||||
if p.wgEntryPointTCP.Clicked(gtx) {
|
||||
p.router.SwitchTo(Route{Path: PageNewTCPEntryPoint})
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}(); clicked {
|
||||
@@ -120,6 +127,23 @@ func (p *menuPage) Layout(gtx C, th *material.Theme) D {
|
||||
})
|
||||
})
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: 10}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
label := material.H6(th, "EntryPoints")
|
||||
label.Font.Weight = font.Bold
|
||||
return layout.Inset{Top: 5, Bottom: 5}.Layout(gtx, label.Layout)
|
||||
}),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return layout.Inset{Top: 5, Bottom: 5}.Layout(gtx, func(gtx C) D {
|
||||
return component.Surface(th).Layout(gtx, func(gtx C) D {
|
||||
return p.wgEntryPointTCP.Layout(gtx, func(gtx C) D {
|
||||
return layout.UniformInset(10).Layout(gtx, func(gtx C) D {
|
||||
return p.layoutCard(gtx, th, "TCP", "Create an entrypoint to connect to the specified TCP tunnel")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -141,14 +165,14 @@ func (p *menuPage) layoutCard(gtx C, th *material.Theme, name, desc string) D {
|
||||
title.Font.Weight = font.Bold
|
||||
return title.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: 10}.Layout),
|
||||
layout.Rigid(layout.Spacer{Height: 5}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
title := material.Body1(th, desc)
|
||||
return title.Layout(gtx)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: 10}.Layout),
|
||||
layout.Rigid(layout.Spacer{Width: 5}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return icons.IconForward.Layout(gtx, color.NRGBA(colornames.Grey500))
|
||||
}),
|
||||
|
||||
+14
-13
@@ -9,12 +9,13 @@ import (
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
"github.com/go-gost/gost-plus/tunnel"
|
||||
"github.com/go-gost/gost-plus/ui/icons"
|
||||
"golang.org/x/exp/shiny/materialdesign/colornames"
|
||||
)
|
||||
|
||||
const (
|
||||
PageHome = "/"
|
||||
PageMenu = "/menu"
|
||||
PageTunnel = "/tunnel"
|
||||
PageNewFile = "/tunnel/file/create"
|
||||
PageNewHTTP = "/tunnel/http/create"
|
||||
PageNewTCP = "/tunnel/tcp/create"
|
||||
@@ -24,6 +25,10 @@ const (
|
||||
PageEditTCP = "/tunnel/tcp/edit"
|
||||
PageEditUDP = "/tunnel/udp/edit"
|
||||
|
||||
PageEntryPoint = "/entrypoint"
|
||||
PageNewTCPEntryPoint = "/entrypoint/tcp/create"
|
||||
PageEditTCPEntryPoint = "/entrypoint/tcp/edit"
|
||||
|
||||
PageAbout = "/about"
|
||||
)
|
||||
|
||||
@@ -74,15 +79,13 @@ func layoutHeader(gtx C, th *material.Theme, tun tunnel.Tunnel, wgID, wgEntrypoi
|
||||
label.Font.Weight = font.Bold
|
||||
return label.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: 10}.Layout),
|
||||
layout.Rigid(layout.Spacer{Width: 5}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
label := material.Body1(th, "Copy")
|
||||
label.Color = color.NRGBA(colornames.Blue500)
|
||||
c := color.NRGBA(colornames.Blue500)
|
||||
if copied {
|
||||
label = material.Body1(th, "Copied")
|
||||
label.Color = color.NRGBA(colornames.Green500)
|
||||
c = color.NRGBA(colornames.Green500)
|
||||
}
|
||||
return label.Layout(gtx)
|
||||
return icons.IconCopy.Layout(gtx, c)
|
||||
}),
|
||||
)
|
||||
})
|
||||
@@ -107,15 +110,13 @@ func layoutHeader(gtx C, th *material.Theme, tun tunnel.Tunnel, wgID, wgEntrypoi
|
||||
label := material.Body1(th, tun.Entrypoint())
|
||||
return label.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: 10}.Layout),
|
||||
layout.Rigid(layout.Spacer{Width: 5}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
label := material.Body1(th, "Copy")
|
||||
label.Color = color.NRGBA(colornames.Blue500)
|
||||
c := color.NRGBA(colornames.Blue500)
|
||||
if copied {
|
||||
label = material.Body1(th, "Copied")
|
||||
label.Color = color.NRGBA(colornames.Green500)
|
||||
c = color.NRGBA(colornames.Green500)
|
||||
}
|
||||
return label.Layout(gtx)
|
||||
return icons.IconCopy.Layout(gtx, c)
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
+13
-4
@@ -32,7 +32,7 @@ func NewRouter() *Router {
|
||||
pages: make(map[string]Page),
|
||||
}
|
||||
|
||||
r.Register(PageHome, NewTunnelPage(r))
|
||||
r.Register(PageTunnel, NewTunnelPage(r))
|
||||
r.Register(PageMenu, NewMenuPage(r))
|
||||
r.Register(PageNewFile, NewFileAddPage(r))
|
||||
r.Register(PageEditFile, NewFileEditPage(r))
|
||||
@@ -42,9 +42,14 @@ func NewRouter() *Router {
|
||||
r.Register(PageEditTCP, NewTCPEditPage(r))
|
||||
r.Register(PageNewUDP, NewUDPAddPage(r))
|
||||
r.Register(PageEditUDP, NewUDPEditPage(r))
|
||||
|
||||
r.Register(PageEntryPoint, NewEntryPointPage(r))
|
||||
r.Register(PageEditTCPEntryPoint, NewTCPEntryPointEditPage(r))
|
||||
r.Register(PageNewTCPEntryPoint, NewTCPEntryPointAddPage(r))
|
||||
|
||||
r.Register(PageAbout, NewAboutPage(r))
|
||||
|
||||
r.SwitchTo(Route{Path: PageHome})
|
||||
r.SwitchTo(Route{Path: PageTunnel})
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -71,10 +76,14 @@ func (r *Router) Layout(gtx layout.Context, th *material.Theme) layout.Dimension
|
||||
switch event := event.(type) {
|
||||
case component.AppBarNavigationClicked:
|
||||
// log.Printf("navigation clicked: %+v", event)
|
||||
r.SwitchTo(Route{Path: PageHome})
|
||||
if r.current.Path == PageTunnel {
|
||||
r.SwitchTo(Route{Path: PageEntryPoint})
|
||||
} else {
|
||||
r.SwitchTo(Route{Path: PageTunnel})
|
||||
}
|
||||
case component.AppBarContextMenuDismissed:
|
||||
// log.Printf("Context menu dismissed: %+v", event)
|
||||
r.SwitchTo(Route{Path: PageHome})
|
||||
r.SwitchTo(Route{Path: PageTunnel})
|
||||
case component.AppBarOverflowActionClicked:
|
||||
if event.Tag == OverflowActionAbout {
|
||||
r.SwitchTo(Route{Path: PageAbout})
|
||||
|
||||
+26
-22
@@ -59,9 +59,9 @@ func (p *tcpAddPage) Init(opts ...PageOption) {
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgDone.Clicked(gtx) {
|
||||
defer p.router.SwitchTo(Route{Path: PageHome})
|
||||
defer p.router.SwitchTo(Route{Path: PageTunnel})
|
||||
p.createTunnel()
|
||||
tunnel.SaveTunnel()
|
||||
tunnel.SaveConfig()
|
||||
}
|
||||
return component.SimpleIconButton(bg, fg, &p.wgDone, icons.IconDone).Layout(gtx)
|
||||
},
|
||||
@@ -108,7 +108,7 @@ func (p *tcpAddPage) layout(gtx C, th *material.Theme) D {
|
||||
if addr == "" {
|
||||
return nil
|
||||
}
|
||||
if _, _, err := net.SplitHostPort(addr); err != nil {
|
||||
if _, err := net.ResolveTCPAddr("tcp", addr); err != nil {
|
||||
return fmt.Errorf("invalid address format, should be [IP]:PORT or [HOST]:PORT")
|
||||
}
|
||||
return nil
|
||||
@@ -124,16 +124,18 @@ func (p *tcpAddPage) layout(gtx C, th *material.Theme) D {
|
||||
}
|
||||
|
||||
func (p *tcpAddPage) createTunnel() error {
|
||||
srv := tunnel.NewTCPTunnel(
|
||||
tun := tunnel.NewTCPTunnel(
|
||||
tunnel.NameOption(strings.TrimSpace(p.name.Text())),
|
||||
tunnel.EndpointOption(strings.TrimSpace(p.addr.Text())),
|
||||
)
|
||||
|
||||
if err := srv.Run(); err != nil {
|
||||
tunnel.Add(tun)
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
tunnel.AddTunnel(srv)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -182,7 +184,7 @@ func (p *tcpEditPage) Init(opts ...PageOption) {
|
||||
}
|
||||
|
||||
p.id = options.ID
|
||||
s := tunnel.GetTunnelID(p.id)
|
||||
s := tunnel.Get(p.id)
|
||||
if s != nil {
|
||||
sopts := s.Options()
|
||||
p.name.SetText(sopts.Name)
|
||||
@@ -196,14 +198,14 @@ func (p *tcpEditPage) Init(opts ...PageOption) {
|
||||
Tag: &p.wgFavorite,
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
s := tunnel.GetTunnelID(p.id)
|
||||
s := tunnel.Get(p.id)
|
||||
if s == nil {
|
||||
return D{}
|
||||
}
|
||||
|
||||
if p.wgFavorite.Clicked(gtx) {
|
||||
s.Favorite(!s.IsFavorite())
|
||||
tunnel.SaveTunnel()
|
||||
tunnel.SaveConfig()
|
||||
}
|
||||
|
||||
btn := component.SimpleIconButton(bg, fg, &p.wgFavorite, icons.IconFavorite)
|
||||
@@ -221,14 +223,14 @@ func (p *tcpEditPage) Init(opts ...PageOption) {
|
||||
Tag: &p.wgState,
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
s := tunnel.GetTunnelID(p.id)
|
||||
s := tunnel.Get(p.id)
|
||||
if p.wgState.Clicked(gtx) && s != nil {
|
||||
if s.IsClosed() {
|
||||
s = p.createTunnel()
|
||||
} else {
|
||||
s.Close()
|
||||
}
|
||||
tunnel.SaveTunnel()
|
||||
tunnel.SaveConfig()
|
||||
}
|
||||
|
||||
if s != nil && !s.IsClosed() {
|
||||
@@ -245,9 +247,9 @@ func (p *tcpEditPage) Init(opts ...PageOption) {
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgDelete.Clicked(gtx) {
|
||||
tunnel.DeleteTunnel(p.id)
|
||||
tunnel.SaveTunnel()
|
||||
p.router.SwitchTo(Route{Path: PageHome})
|
||||
tunnel.Delete(p.id)
|
||||
tunnel.SaveConfig()
|
||||
p.router.SwitchTo(Route{Path: PageTunnel})
|
||||
}
|
||||
return component.SimpleIconButton(bg, fg, &p.wgDelete, icons.IconDelete).Layout(gtx)
|
||||
},
|
||||
@@ -259,12 +261,12 @@ func (p *tcpEditPage) Init(opts ...PageOption) {
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgDone.Clicked(gtx) {
|
||||
defer p.router.SwitchTo(Route{Path: PageHome})
|
||||
defer p.router.SwitchTo(Route{Path: PageTunnel})
|
||||
|
||||
if s := tunnel.GetTunnelID(p.id); s != nil {
|
||||
if s := tunnel.Get(p.id); s != nil {
|
||||
s.Close()
|
||||
p.createTunnel()
|
||||
tunnel.SaveTunnel()
|
||||
tunnel.SaveConfig()
|
||||
}
|
||||
}
|
||||
return component.SimpleIconButton(bg, fg, &p.wgDone, icons.IconDone).Layout(gtx)
|
||||
@@ -277,18 +279,20 @@ func (p *tcpEditPage) Init(opts ...PageOption) {
|
||||
}
|
||||
|
||||
func (p *tcpEditPage) createTunnel() tunnel.Tunnel {
|
||||
s := tunnel.NewTCPTunnel(
|
||||
tun := tunnel.NewTCPTunnel(
|
||||
tunnel.IDOption(p.id),
|
||||
tunnel.NameOption(strings.TrimSpace(p.name.Text())),
|
||||
tunnel.EndpointOption(strings.TrimSpace(p.addr.Text())),
|
||||
)
|
||||
|
||||
if err := s.Run(); err != nil {
|
||||
tunnel.Set(tun)
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
log.Println(err)
|
||||
}
|
||||
tunnel.SetTunnel(s)
|
||||
|
||||
return s
|
||||
return tun
|
||||
}
|
||||
|
||||
func (p *tcpEditPage) Layout(gtx C, th *material.Theme) D {
|
||||
@@ -310,7 +314,7 @@ func (p *tcpEditPage) layout(gtx C, th *material.Theme) D {
|
||||
Axis: layout.Vertical,
|
||||
}.Layout(gtx,
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return layoutHeader(gtx, th, tunnel.GetTunnelID(p.id), &p.wgID, &p.wgEntrypoint)
|
||||
return layoutHeader(gtx, th, tunnel.Get(p.id), &p.wgID, &p.wgEntrypoint)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: 10}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
|
||||
+16
-4
@@ -94,8 +94,8 @@ func (p *tunnelPage) Init(opts ...PageOption) {
|
||||
func (p *tunnelPage) Layout(gtx C, th *material.Theme) D {
|
||||
favorite := p.favorite.Load()
|
||||
// gtx.Constraints.Min.X = gtx.Constraints.Max.X
|
||||
return p.list.Layout(gtx, tunnel.TunnelCount(), func(gtx C, index int) D {
|
||||
s := tunnel.GetTunnel(index)
|
||||
return p.list.Layout(gtx, tunnel.Count(), func(gtx C, index int) D {
|
||||
s := tunnel.GetIndex(index)
|
||||
if s == nil {
|
||||
delete(p.tunnels, index)
|
||||
return D{}
|
||||
@@ -129,7 +129,7 @@ func (p *tunnelPage) Layout(gtx C, th *material.Theme) D {
|
||||
}
|
||||
return state.editor.Layout(gtx, func(gtx C) D {
|
||||
return layout.UniformInset(10).Layout(gtx, func(gtx C) D {
|
||||
return p.layoutTunnel(gtx, th, s)
|
||||
return p.layout(gtx, th, s)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -138,7 +138,7 @@ func (p *tunnelPage) Layout(gtx C, th *material.Theme) D {
|
||||
})
|
||||
}
|
||||
|
||||
func (p *tunnelPage) layoutTunnel(gtx C, th *material.Theme, s tunnel.Tunnel) D {
|
||||
func (p *tunnelPage) layout(gtx C, th *material.Theme, s tunnel.Tunnel) D {
|
||||
return layout.Flex{
|
||||
Alignment: layout.Middle,
|
||||
Spacing: layout.SpaceBetween,
|
||||
@@ -158,11 +158,23 @@ func (p *tunnelPage) layoutTunnel(gtx C, th *material.Theme, s tunnel.Tunnel) D
|
||||
layout.Rigid(material.Body2(th, fmt.Sprintf("Endpoint: %s", s.Endpoint())).Layout),
|
||||
layout.Rigid(layout.Spacer{Height: 5}.Layout),
|
||||
layout.Rigid(material.Body2(th, fmt.Sprintf("Entrypoint: %s", s.Entrypoint())).Layout),
|
||||
layout.Rigid(layout.Spacer{Height: 5}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
if err := s.Err(); !s.IsClosed() && err != nil {
|
||||
label := material.Body2(th, err.Error())
|
||||
label.Color = color.NRGBA(colornames.Red500)
|
||||
return label.Layout(gtx)
|
||||
}
|
||||
return D{}
|
||||
}),
|
||||
)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: 10}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
c := colornames.Green500
|
||||
if s.Err() != nil {
|
||||
c = colornames.Red500
|
||||
}
|
||||
if s.IsClosed() {
|
||||
c = colornames.Grey500
|
||||
}
|
||||
|
||||
+26
-22
@@ -59,9 +59,9 @@ func (p *udpAddPage) Init(opts ...PageOption) {
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgDone.Clicked(gtx) {
|
||||
defer p.router.SwitchTo(Route{Path: PageHome})
|
||||
defer p.router.SwitchTo(Route{Path: PageTunnel})
|
||||
p.createTunnel()
|
||||
tunnel.SaveTunnel()
|
||||
tunnel.SaveConfig()
|
||||
}
|
||||
return component.SimpleIconButton(bg, fg, &p.wgDone, icons.IconDone).Layout(gtx)
|
||||
},
|
||||
@@ -108,7 +108,7 @@ func (p *udpAddPage) layout(gtx C, th *material.Theme) D {
|
||||
if addr == "" {
|
||||
return nil
|
||||
}
|
||||
if _, _, err := net.SplitHostPort(addr); err != nil {
|
||||
if _, err := net.ResolveUDPAddr("udp", addr); err != nil {
|
||||
return fmt.Errorf("invalid address format, should be [IP]:PORT or [HOST]:PORT")
|
||||
}
|
||||
return nil
|
||||
@@ -124,16 +124,18 @@ func (p *udpAddPage) layout(gtx C, th *material.Theme) D {
|
||||
}
|
||||
|
||||
func (p *udpAddPage) createTunnel() error {
|
||||
srv := tunnel.NewUDPTunnel(
|
||||
tun := tunnel.NewUDPTunnel(
|
||||
tunnel.NameOption(strings.TrimSpace(p.name.Text())),
|
||||
tunnel.EndpointOption(strings.TrimSpace(p.addr.Text())),
|
||||
)
|
||||
|
||||
if err := srv.Run(); err != nil {
|
||||
tunnel.Add(tun)
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
tunnel.AddTunnel(srv)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -182,7 +184,7 @@ func (p *udpEditPage) Init(opts ...PageOption) {
|
||||
}
|
||||
|
||||
p.id = options.ID
|
||||
s := tunnel.GetTunnelID(p.id)
|
||||
s := tunnel.Get(p.id)
|
||||
if s != nil {
|
||||
sopts := s.Options()
|
||||
p.name.SetText(sopts.Name)
|
||||
@@ -196,14 +198,14 @@ func (p *udpEditPage) Init(opts ...PageOption) {
|
||||
Tag: &p.wgFavorite,
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
s := tunnel.GetTunnelID(p.id)
|
||||
s := tunnel.Get(p.id)
|
||||
if s == nil {
|
||||
return D{}
|
||||
}
|
||||
|
||||
if p.wgFavorite.Clicked(gtx) {
|
||||
s.Favorite(!s.IsFavorite())
|
||||
tunnel.SaveTunnel()
|
||||
tunnel.SaveConfig()
|
||||
}
|
||||
|
||||
btn := component.SimpleIconButton(bg, fg, &p.wgFavorite, icons.IconFavorite)
|
||||
@@ -221,14 +223,14 @@ func (p *udpEditPage) Init(opts ...PageOption) {
|
||||
Tag: &p.wgState,
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
s := tunnel.GetTunnelID(p.id)
|
||||
s := tunnel.Get(p.id)
|
||||
if p.wgState.Clicked(gtx) && s != nil {
|
||||
if s.IsClosed() {
|
||||
s = p.createTunnel()
|
||||
} else {
|
||||
s.Close()
|
||||
}
|
||||
tunnel.SaveTunnel()
|
||||
tunnel.SaveConfig()
|
||||
}
|
||||
|
||||
if s != nil && !s.IsClosed() {
|
||||
@@ -245,9 +247,9 @@ func (p *udpEditPage) Init(opts ...PageOption) {
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgDelete.Clicked(gtx) {
|
||||
tunnel.DeleteTunnel(p.id)
|
||||
tunnel.SaveTunnel()
|
||||
p.router.SwitchTo(Route{Path: PageHome})
|
||||
tunnel.Delete(p.id)
|
||||
tunnel.SaveConfig()
|
||||
p.router.SwitchTo(Route{Path: PageTunnel})
|
||||
}
|
||||
return component.SimpleIconButton(bg, fg, &p.wgDelete, icons.IconDelete).Layout(gtx)
|
||||
},
|
||||
@@ -259,13 +261,13 @@ func (p *udpEditPage) Init(opts ...PageOption) {
|
||||
},
|
||||
Layout: func(gtx C, bg, fg color.NRGBA) D {
|
||||
if p.wgDone.Clicked(gtx) {
|
||||
defer p.router.SwitchTo(Route{Path: PageHome})
|
||||
defer p.router.SwitchTo(Route{Path: PageTunnel})
|
||||
|
||||
p.createTunnel()
|
||||
if s := tunnel.GetTunnelID(p.id); s != nil {
|
||||
if s := tunnel.Get(p.id); s != nil {
|
||||
s.Close()
|
||||
p.createTunnel()
|
||||
tunnel.SaveTunnel()
|
||||
tunnel.SaveConfig()
|
||||
}
|
||||
}
|
||||
return component.SimpleIconButton(bg, fg, &p.wgDone, icons.IconDone).Layout(gtx)
|
||||
@@ -278,18 +280,20 @@ func (p *udpEditPage) Init(opts ...PageOption) {
|
||||
}
|
||||
|
||||
func (p *udpEditPage) createTunnel() tunnel.Tunnel {
|
||||
s := tunnel.NewUDPTunnel(
|
||||
tun := tunnel.NewUDPTunnel(
|
||||
tunnel.IDOption(p.id),
|
||||
tunnel.NameOption(strings.TrimSpace(p.name.Text())),
|
||||
tunnel.EndpointOption(strings.TrimSpace(p.addr.Text())),
|
||||
)
|
||||
|
||||
if err := s.Run(); err != nil {
|
||||
tunnel.Set(tun)
|
||||
|
||||
if err := tun.Run(); err != nil {
|
||||
tun.Close()
|
||||
log.Println(err)
|
||||
}
|
||||
tunnel.SetTunnel(s)
|
||||
|
||||
return s
|
||||
return tun
|
||||
}
|
||||
|
||||
func (p *udpEditPage) Layout(gtx C, th *material.Theme) D {
|
||||
@@ -311,7 +315,7 @@ func (p *udpEditPage) layout(gtx C, th *material.Theme) D {
|
||||
Axis: layout.Vertical,
|
||||
}.Layout(gtx,
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return layoutHeader(gtx, th, tunnel.GetTunnelID(p.id), &p.wgID, &p.wgEntrypoint)
|
||||
return layoutHeader(gtx, th, tunnel.Get(p.id), &p.wgID, &p.wgEntrypoint)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: 10}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
package version
|
||||
|
||||
var Version = "0.1.0"
|
||||
var Version = "0.2.0"
|
||||
|
||||
+4
-4
@@ -35,22 +35,22 @@
|
||||
"#1": {
|
||||
"0000": {
|
||||
"fixed": {
|
||||
"file_version": "0.1.0.0",
|
||||
"product_version": "0.1.0.0"
|
||||
"file_version": "0.2.0.0",
|
||||
"product_version": "0.2.0.0"
|
||||
},
|
||||
"info": {
|
||||
"0409": {
|
||||
"Comments": "",
|
||||
"CompanyName": "GOST.PLUS",
|
||||
"FileDescription": "A simple GUI client for GOST.PLUS",
|
||||
"FileVersion": "0.1.0",
|
||||
"FileVersion": "0.2.0",
|
||||
"InternalName": "gost.plus",
|
||||
"LegalCopyright": "Copyright © 2024 GOST.PLUS",
|
||||
"LegalTrademarks": "GOST.PLUS",
|
||||
"OriginalFilename": "gost-plus",
|
||||
"PrivateBuild": "",
|
||||
"ProductName": "GOST.PLUS",
|
||||
"ProductVersion": "0.1.0",
|
||||
"ProductVersion": "0.2.0",
|
||||
"SpecialBuild": ""
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user