add tcp entrypoint

This commit is contained in:
ginuerzh
2023-12-16 11:58:48 +08:00
parent 1335cebf25
commit e78a75d6bb
25 changed files with 1607 additions and 258 deletions
+1 -3
View File
@@ -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)
+7 -7
View File
@@ -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
View File
@@ -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 {
+2
View File
@@ -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(
+182
View File
@@ -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
}
}
+226
View File
@@ -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
}
+226
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+1
View File
@@ -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 {
+177
View File
@@ -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))
}),
)
}
+405
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,3 +1,3 @@
package version
var Version = "0.1.0"
var Version = "0.2.0"
+4 -4
View File
@@ -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": ""
}
}