Compare commits
119 Commits
e1eb346228
...
main
Author | SHA1 | Date | |
---|---|---|---|
437ab8ccef | |||
714748d8e2 | |||
f5a95505a9 | |||
98966f85f6 | |||
c8cb71cf95 | |||
e2a3180ec7 | |||
42ceb5f3fa | |||
9f8d3e2696 | |||
7ec1387132 | |||
7d3ce7147a | |||
0d76146bc3 | |||
1dcb1605b7 | |||
6078a41504 | |||
0696f4f560 | |||
c001458b24 | |||
10c139e837 | |||
31a4223aab | |||
41cd725c1c | |||
6200bc5460 | |||
7fb4fbaae2 | |||
d818acc53d | |||
05cb0823b4 | |||
d0f1663bf3 | |||
3fd91b2c45 | |||
5767beb9af | |||
6e670b4924 | |||
8331fdc1a2 | |||
d092540f08 | |||
7835696166 | |||
c5bac71eaf | |||
9addb5d35f | |||
e12cd28413 | |||
6baf3b2b53 | |||
01eb2edd6e | |||
cea0efcb18 | |||
db9fc1f4d9 | |||
54e9e208f4 | |||
f06433d431 | |||
8318643735 | |||
deb6f7b05a | |||
dee3711dd4 | |||
be864129dc | |||
e1c288f17f | |||
a1f8d4cf22 | |||
9c3a4d0c17 | |||
b41df682e1 | |||
918b887517 | |||
76cba687ed | |||
fd28add458 | |||
0cfd241e00 | |||
5dbe9cd34e | |||
494f11836c | |||
41503373d3 | |||
e01a6b347c | |||
8cde9f08d4 | |||
9865914436 | |||
5fa6cd466b | |||
3714138656 | |||
f1f74ed48d | |||
cd32380425 | |||
4d3fb7db65 | |||
8105545e98 | |||
1e6fbed19d | |||
b398816c96 | |||
09593fd678 | |||
befe65b486 | |||
030d62af12 | |||
ae13d0c4ed | |||
7b1d383813 | |||
6e9887f726 | |||
ecc1d4580f | |||
e56c45b3bf | |||
b6fb296608 | |||
bec93289f5 | |||
04e2de609e | |||
71a42af593 | |||
5df95159a3 | |||
2d63e81e62 | |||
97885bcae1 | |||
5c2695e46b | |||
75f72e7c88 | |||
7363986737 | |||
429bb6cd63 | |||
d9aaa0a0ab | |||
7a6b820b92 | |||
c7b7bfd25f | |||
1c49023c37 | |||
b5c1ae2a73 | |||
2b3a841299 | |||
81f7bb0c0d | |||
a0efb55c3e | |||
be7f4f4da5 | |||
f2536a0acc | |||
cb18a44cfa | |||
fb465133b9 | |||
d5108e9859 | |||
3dc5d9c91e | |||
ec51c101ec | |||
ef3e80efb8 | |||
1bc8406907 | |||
0fb57cb77b | |||
16327fc576 | |||
148dfbcfe1 | |||
8bee855aa8 | |||
b7992a0bb7 | |||
2fa5817e88 | |||
a97be385ec | |||
d3afe29d5a | |||
2655da937f | |||
c5f63328b2 | |||
810356efe5 | |||
90305e3eed | |||
cfc0db4b54 | |||
0809f9bedc | |||
e2f1d58c5e | |||
edda6d169b | |||
9118acb268 | |||
369a41eeb2 | |||
c237b8a842 |
33
Makefile
33
Makefile
@ -9,17 +9,26 @@ NAME=hodu
|
|||||||
VERSION=1.0.0
|
VERSION=1.0.0
|
||||||
|
|
||||||
SRCS=\
|
SRCS=\
|
||||||
|
atom.go \
|
||||||
|
bulletin.go \
|
||||||
client.go \
|
client.go \
|
||||||
client-ctl.go \
|
client-ctl.go \
|
||||||
|
client-metrics.go \
|
||||||
client-peer.go \
|
client-peer.go \
|
||||||
|
client-pty.go \
|
||||||
hodu.go \
|
hodu.go \
|
||||||
hodu.pb.go \
|
hodu.pb.go \
|
||||||
hodu_grpc.pb.go \
|
hodu_grpc.pb.go \
|
||||||
|
jwt.go \
|
||||||
packet.go \
|
packet.go \
|
||||||
|
pty.go \
|
||||||
server.go \
|
server.go \
|
||||||
server-ctl.go \
|
server-ctl.go \
|
||||||
|
server-metrics.go \
|
||||||
server-peer.go \
|
server-peer.go \
|
||||||
server-proxy.go \
|
server-pty.go \
|
||||||
|
server-pxy.go \
|
||||||
|
server-rpx.go \
|
||||||
system.go \
|
system.go \
|
||||||
transform.go \
|
transform.go \
|
||||||
|
|
||||||
@ -30,6 +39,7 @@ DATA = \
|
|||||||
xterm.html
|
xterm.html
|
||||||
|
|
||||||
CMD_DATA=\
|
CMD_DATA=\
|
||||||
|
cmd/rsa.key \
|
||||||
cmd/tls.crt \
|
cmd/tls.crt \
|
||||||
cmd/tls.key
|
cmd/tls.key
|
||||||
|
|
||||||
@ -41,13 +51,19 @@ CMD_SRCS=\
|
|||||||
all: $(NAME)
|
all: $(NAME)
|
||||||
|
|
||||||
$(NAME): $(DATA) $(SRCS) $(CMD_DATA) $(CMD_SRCS)
|
$(NAME): $(DATA) $(SRCS) $(CMD_DATA) $(CMD_SRCS)
|
||||||
##CGO_ENABLED=0 go build -x -ldflags "-X 'main.HODU_NAME=$(NAME)' -X 'main.HODU_VERSION=$(VERSION)'" -o $@ $(CMD_SRCS)
|
CGO_ENABLED=0 go build -x -ldflags "-X 'main.HODU_NAME=$(NAME)' -X 'main.HODU_VERSION=$(VERSION)'" -o $@ $(CMD_SRCS)
|
||||||
CGO_ENABLED=1 go build -x -ldflags "-X 'main.HODU_NAME=$(NAME)' -X 'main.HODU_VERSION=$(VERSION)'" -o $@ $(CMD_SRCS)
|
##CGO_ENABLED=1 go build -x -ldflags "-X 'main.HODU_NAME=$(NAME)' -X 'main.HODU_VERSION=$(VERSION)'" -o $@ $(CMD_SRCS)
|
||||||
##CGO_ENABLED=1 go build -x -ldflags "-X 'main.HODU_NAME=$(NAME)' -X 'main.HODU_VERSION=$(VERSION)' -linkmode external -extldflags=-static" -o $@ $(CMD_SRCS)
|
##CGO_ENABLED=1 go build -x -ldflags "-X 'main.HODU_NAME=$(NAME)' -X 'main.HODU_VERSION=$(VERSION)' -linkmode external -extldflags=-static" -o $@ $(CMD_SRCS)
|
||||||
|
|
||||||
|
$(NAME).debug: $(DATA) $(SRCS) $(CMD_DATA) $(CMD_SRCS)
|
||||||
|
CGO_ENABLED=1 go build -race -x -ldflags "-X 'main.HODU_NAME=$(NAME)' -X 'main.HODU_VERSION=$(VERSION)'" -o $@ $(CMD_SRCS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
go clean -x -i
|
go clean -x -i
|
||||||
rm -f $(NAME)
|
rm -f $(NAME) $(NAME).debug
|
||||||
|
|
||||||
|
check:
|
||||||
|
go test -x
|
||||||
|
|
||||||
hodu.pb.go: hodu.proto
|
hodu.pb.go: hodu.proto
|
||||||
protoc --go_out=. --go_opt=paths=source_relative \
|
protoc --go_out=. --go_opt=paths=source_relative \
|
||||||
@ -70,9 +86,12 @@ xterm.css:
|
|||||||
sed -r -i 's|^/\*# sourceMappingURL=/.+ \*/$$||g' "$@"
|
sed -r -i 's|^/\*# sourceMappingURL=/.+ \*/$$||g' "$@"
|
||||||
|
|
||||||
cmd/tls.crt:
|
cmd/tls.crt:
|
||||||
openssl req -x509 -newkey rsa:4096 -keyout cmd/tls.key -out cmd/tls.crt -sha256 -days 36500 -nodes -subj "/CN=$(NAME)" --addext "subjectAltName=DNS:$(NAME),IP:10.0.0.1,IP:::1"
|
openssl req -x509 -newkey rsa:4096 -keyout cmd/tls.key -out cmd/tls.crt -sha256 -days 36500 -nodes -subj "/CN=$(NAME)" --addext "subjectAltName=DNS:$(NAME),IP:127.0.0.1,IP:::1"
|
||||||
|
|
||||||
cmd/tls.key:
|
cmd/tls.key:
|
||||||
openssl req -x509 -newkey rsa:4096 -keyout cmd/tls.key -out cmd/tls.crt -sha256 -days 36500 -nodes -subj "/CN=$(NAME)" --addext "subjectAltName=DNS:$(NAME),IP:10.0.0.1,IP:::1"
|
openssl req -x509 -newkey rsa:4096 -keyout cmd/tls.key -out cmd/tls.crt -sha256 -days 36500 -nodes -subj "/CN=$(NAME)" --addext "subjectAltName=DNS:$(NAME),IP:127.0.0.1,IP:::1"
|
||||||
|
|
||||||
.PHONY: clean
|
cmd/rsa.key:
|
||||||
|
openssl genrsa -traditional -out cmd/rsa.key 2048
|
||||||
|
|
||||||
|
.PHONY: all clean test
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
## normal operation
|
## normal operation
|
||||||
- ./hodu server --rpc-on=0.0.0.0:9999 --ctl-on=0.0.0.0:8888 --pxy-on=0.0.0.0:9998 --wpx-on=0.0.0.0:9997
|
- ./hodu server --rpc-on=0.0.0.0:9999 --ctl-on=0.0.0.0:8888 --pxy-on=0.0.0.0:9998 --wpx-on=0.0.0.0:9997
|
||||||
- ./hodu client --rpc-to=127.0.0.1:9999 --ctl-on=127.0.0.1:7777 "127.0.0.2:22,0.0.0.0:12345,ssh,Access SSH Server"
|
- ./hodu client --rpc-to=127.0.0.1:9999 --ctl-on=127.0.0.1:1107 "127.0.0.2:22,0.0.0.0:12345,ssh,Access SSH Server"
|
||||||
|
|
||||||
## server.json
|
## server.json
|
||||||
```
|
```
|
||||||
@ -29,13 +29,13 @@
|
|||||||
"client-peer-addr": "192.168.1.104:22",
|
"client-peer-addr": "192.168.1.104:22",
|
||||||
"client-peer-name": "Star gate",
|
"client-peer-name": "Star gate",
|
||||||
"server-peer-option": "tcp4 ssh",
|
"server-peer-option": "tcp4 ssh",
|
||||||
"server-peer-service-addr": "0.0.0.0:0",
|
"server-peer-svc-addr": "0.0.0.0:0",
|
||||||
"server-peer-service-net": "",
|
"server-peer-svc-net": "",
|
||||||
"lifetime": "0"
|
"lifetime": "0"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Run this command:
|
Run this command:
|
||||||
```
|
```
|
||||||
curl -X POST --data-binary @client-route.json http://127.0.0.1:7777/_ctl/client-conns/1/routes
|
curl -X POST --data-binary @client-route.json http://127.0.0.1:1107/_ctl/client-conns/1/routes
|
||||||
```
|
```
|
||||||
|
21
atom.go
Normal file
21
atom.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package hodu
|
||||||
|
|
||||||
|
import "sync/atomic"
|
||||||
|
|
||||||
|
type Atom[T any] struct {
|
||||||
|
val atomic.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (av* Atom[T]) Set(v T) {
|
||||||
|
av.val.Store(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (av* Atom[T]) Get() T {
|
||||||
|
var v interface{}
|
||||||
|
v = av.val.Load()
|
||||||
|
if v == nil {
|
||||||
|
var t T
|
||||||
|
return t // return the zero-value
|
||||||
|
}
|
||||||
|
return v.(T)
|
||||||
|
}
|
282
bulletin.go
Normal file
282
bulletin.go
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
package hodu
|
||||||
|
|
||||||
|
import "container/list"
|
||||||
|
import "container/ring"
|
||||||
|
import "errors"
|
||||||
|
import "sync"
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type BulletinSubscription[T interface{}] struct {
|
||||||
|
C chan T
|
||||||
|
b *Bulletin[T]
|
||||||
|
topic string
|
||||||
|
node *list.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
type BulletinSubscriptionList = *list.List
|
||||||
|
|
||||||
|
type BulletinSubscriptionMap map[string]BulletinSubscriptionList
|
||||||
|
|
||||||
|
type Bulletin[T interface{}] struct {
|
||||||
|
svc Service
|
||||||
|
|
||||||
|
sbsc_map BulletinSubscriptionMap
|
||||||
|
sbsc_list *list.List
|
||||||
|
sbsc_mtx sync.RWMutex
|
||||||
|
blocked bool
|
||||||
|
|
||||||
|
r_mtx sync.RWMutex
|
||||||
|
r *ring.Ring
|
||||||
|
r_head *ring.Ring
|
||||||
|
r_tail *ring.Ring
|
||||||
|
r_len int
|
||||||
|
r_cap int
|
||||||
|
r_chan chan struct{}
|
||||||
|
stop_chan chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBulletin[T interface{}](svc Service, capa int) *Bulletin[T] {
|
||||||
|
var r *ring.Ring
|
||||||
|
|
||||||
|
r = ring.New(capa)
|
||||||
|
return &Bulletin[T]{
|
||||||
|
sbsc_map: make(BulletinSubscriptionMap, 0),
|
||||||
|
sbsc_list: list.New(),
|
||||||
|
r: r,
|
||||||
|
r_head: r,
|
||||||
|
r_tail: r,
|
||||||
|
r_cap: capa,
|
||||||
|
r_len: 0,
|
||||||
|
r_chan: make(chan struct{}, 1),
|
||||||
|
stop_chan: make(chan struct{}, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bulletin[T]) unsubscribe_list_nolock(sl BulletinSubscriptionList) {
|
||||||
|
var sbsc *BulletinSubscription[T]
|
||||||
|
var e *list.Element
|
||||||
|
|
||||||
|
for e = sl.Front(); e != nil; e = e.Next() {
|
||||||
|
sbsc = e.Value.(*BulletinSubscription[T])
|
||||||
|
sl.Remove(sbsc.node)
|
||||||
|
close(sbsc.C)
|
||||||
|
sbsc.b = nil
|
||||||
|
sbsc.node = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bulletin[T]) unsubscribe_all_nolock() {
|
||||||
|
var topic string
|
||||||
|
var sl BulletinSubscriptionList
|
||||||
|
|
||||||
|
for topic, sl = range b.sbsc_map {
|
||||||
|
b.unsubscribe_list_nolock(sl)
|
||||||
|
delete(b.sbsc_map, topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.unsubscribe_list_nolock(b.sbsc_list)
|
||||||
|
b.blocked = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bulletin[T]) UnsubscribeAll() {
|
||||||
|
b.sbsc_mtx.Lock()
|
||||||
|
b.unsubscribe_all_nolock()
|
||||||
|
b.sbsc_mtx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bulletin[T]) Block() {
|
||||||
|
b.sbsc_mtx.Lock()
|
||||||
|
b.blocked = true
|
||||||
|
b.sbsc_mtx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bulletin[T]) Unblock() {
|
||||||
|
b.sbsc_mtx.Lock()
|
||||||
|
b.blocked = false
|
||||||
|
b.sbsc_mtx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bulletin[T]) Subscribe(topic string) (*BulletinSubscription[T], error) {
|
||||||
|
var sbsc BulletinSubscription[T]
|
||||||
|
|
||||||
|
b.sbsc_mtx.Lock()
|
||||||
|
if b.blocked {
|
||||||
|
b.sbsc_mtx.Unlock()
|
||||||
|
return nil, errors.New("blocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
sbsc.C = make(chan T, 128) // TODO: size?
|
||||||
|
sbsc.b = b
|
||||||
|
sbsc.topic = topic
|
||||||
|
|
||||||
|
if topic == "" {
|
||||||
|
sbsc.node = b.sbsc_list.PushBack(&sbsc)
|
||||||
|
} else {
|
||||||
|
var sbsc_list BulletinSubscriptionList
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
sbsc_list, ok = b.sbsc_map[topic]
|
||||||
|
if !ok {
|
||||||
|
sbsc_list = list.New()
|
||||||
|
b.sbsc_map[topic] = sbsc_list
|
||||||
|
}
|
||||||
|
sbsc.node = sbsc_list.PushBack(&sbsc)
|
||||||
|
}
|
||||||
|
b.sbsc_mtx.Unlock()
|
||||||
|
return &sbsc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bulletin[T]) Unsubscribe(sbsc *BulletinSubscription[T]) {
|
||||||
|
if sbsc.b == b && sbsc.node != nil {
|
||||||
|
var sl BulletinSubscriptionList
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
b.sbsc_mtx.Lock()
|
||||||
|
if sbsc.topic == "" {
|
||||||
|
b.sbsc_list.Remove(sbsc.node)
|
||||||
|
close(sbsc.C)
|
||||||
|
sbsc.node = nil
|
||||||
|
sbsc.b = nil
|
||||||
|
} else {
|
||||||
|
sl, ok = b.sbsc_map[sbsc.topic]
|
||||||
|
if ok {
|
||||||
|
sl.Remove(sbsc.node)
|
||||||
|
close(sbsc.C)
|
||||||
|
sbsc.node = nil
|
||||||
|
sbsc.b = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.sbsc_mtx.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bulletin[T]) Publish(topic string, data T) {
|
||||||
|
var sl BulletinSubscriptionList
|
||||||
|
var ok bool
|
||||||
|
if topic == "" { return }
|
||||||
|
|
||||||
|
b.sbsc_mtx.Lock()
|
||||||
|
if b.blocked {
|
||||||
|
b.sbsc_mtx.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sl, ok = b.sbsc_map[topic]
|
||||||
|
if ok {
|
||||||
|
var sbsc *BulletinSubscription[T]
|
||||||
|
var e *list.Element
|
||||||
|
for e = sl.Front(); e != nil; e = e.Next() {
|
||||||
|
sbsc = e.Value.(*BulletinSubscription[T])
|
||||||
|
select {
|
||||||
|
case sbsc.C <- data:
|
||||||
|
// ok. could be written.
|
||||||
|
default:
|
||||||
|
// channel full. discard it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.sbsc_mtx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bulletin[T]) Enqueue(data T) {
|
||||||
|
// hopefuly, it's fater to use a single mutex, a ring buffer, and a notification channel than
|
||||||
|
// to use a channel to pass messages. TODO: performance verification
|
||||||
|
b.r_mtx.Lock()
|
||||||
|
if b.blocked {
|
||||||
|
b.r_mtx.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.r_len < b.r_cap {
|
||||||
|
b.r_len++
|
||||||
|
} else {
|
||||||
|
b.r_head = b.r_head.Next()
|
||||||
|
}
|
||||||
|
b.r_tail.Value = data // update the value at the current position
|
||||||
|
b.r_tail = b.r_tail.Next() // move the current position
|
||||||
|
select {
|
||||||
|
case b.r_chan <- struct{}{}:
|
||||||
|
// write success
|
||||||
|
default:
|
||||||
|
// don't care if not writable
|
||||||
|
}
|
||||||
|
b.r_mtx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bulletin[T]) Dequeue() (T, bool) {
|
||||||
|
var v T
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
b.r_mtx.Lock()
|
||||||
|
|
||||||
|
if b.r_len > 0 {
|
||||||
|
v = b.r_head.Value.(T) // store the value for returning
|
||||||
|
b.r_head.Value = nil // nullify the value
|
||||||
|
b.r_head = b.r_head.Next() // advance the head position
|
||||||
|
b.r_len--
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
|
||||||
|
b.r_mtx.Unlock()
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bulletin[T]) RunTask(wg *sync.WaitGroup) {
|
||||||
|
var done bool
|
||||||
|
var tmr *time.Timer
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
tmr = time.NewTimer(3 * time.Second)
|
||||||
|
for !done {
|
||||||
|
var msg T
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
msg, ok = b.Dequeue()
|
||||||
|
if !ok {
|
||||||
|
select {
|
||||||
|
case <-b.stop_chan:
|
||||||
|
// this may break the loop prematurely while there
|
||||||
|
// are messages to read as it uses two different channels:
|
||||||
|
// one for stop, another for notification
|
||||||
|
done = true
|
||||||
|
case <-b.r_chan:
|
||||||
|
// noti received.
|
||||||
|
tmr.Stop()
|
||||||
|
tmr.Reset(3 * time.Second)
|
||||||
|
case <-tmr.C:
|
||||||
|
// try to dequeue again
|
||||||
|
tmr.Reset(3 * time.Second)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// forward msg to all subscribers...
|
||||||
|
var e *list.Element
|
||||||
|
var sbsc *BulletinSubscription[T]
|
||||||
|
|
||||||
|
tmr.Stop()
|
||||||
|
|
||||||
|
b.sbsc_mtx.Lock()
|
||||||
|
for e = b.sbsc_list.Front(); e != nil; e = e.Next() {
|
||||||
|
sbsc = e.Value.(*BulletinSubscription[T])
|
||||||
|
select {
|
||||||
|
case sbsc.C <- msg:
|
||||||
|
// ok. could be written.
|
||||||
|
default:
|
||||||
|
// channel full. discard it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.sbsc_mtx.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmr.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bulletin[T]) ReqStop() {
|
||||||
|
select {
|
||||||
|
case b.stop_chan <- struct{}{}:
|
||||||
|
// write success
|
||||||
|
default:
|
||||||
|
// ignore failure
|
||||||
|
}
|
||||||
|
}
|
139
bulletin_test.go
Normal file
139
bulletin_test.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
package hodu_test
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
import "hodu"
|
||||||
|
import "sync"
|
||||||
|
import "testing"
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
func TestBulletin1(t *testing.T) {
|
||||||
|
var b *hodu.Bulletin[string]
|
||||||
|
var s1 *hodu.BulletinSubscription[string]
|
||||||
|
var s2 *hodu.BulletinSubscription[string]
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var nmsgs1 int
|
||||||
|
var nmsgs2 int
|
||||||
|
|
||||||
|
b = hodu.NewBulletin[string](nil, 100)
|
||||||
|
|
||||||
|
s1, _ = b.Subscribe("t1")
|
||||||
|
s2, _ = b.Subscribe("t2")
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
var m string
|
||||||
|
var ok bool
|
||||||
|
var c1 chan string
|
||||||
|
var c2 chan string
|
||||||
|
|
||||||
|
c1 = s1.C
|
||||||
|
c2 = s2.C
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
for c1 != nil || c2 != nil {
|
||||||
|
select {
|
||||||
|
case m, ok = <-c1:
|
||||||
|
if ok { fmt.Printf ("s1: %+v\n", m); nmsgs1++ } else { c1 = nil; fmt.Printf ("s1 closed\n")}
|
||||||
|
|
||||||
|
case m, ok = <-c2:
|
||||||
|
if ok { fmt.Printf ("s2: %+v\n", m); nmsgs2++ } else { c2 = nil; fmt.Printf ("s2 closed\n") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
b.Publish("t1", "donkey")
|
||||||
|
b.Publish("t2", "monkey")
|
||||||
|
b.Publish("t1", "donkey kong")
|
||||||
|
b.Publish("t2", "monkey hong")
|
||||||
|
b.Publish("t3", "home")
|
||||||
|
b.Publish("t2", "fire")
|
||||||
|
b.Publish("t1", "sunflower")
|
||||||
|
b.Publish("t2", "itsy bitsy spider")
|
||||||
|
b.Publish("t3", "marigold")
|
||||||
|
b.Publish("t3", "parrot")
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
b.Publish("t2", "tiger")
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
b.Unsubscribe(s2)
|
||||||
|
b.Publish("t2", "lion king")
|
||||||
|
b.Publish("t2", "fly to the skyp")
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
b.Block()
|
||||||
|
b.UnsubscribeAll()
|
||||||
|
wg.Wait()
|
||||||
|
fmt.Printf ("---------------------\n")
|
||||||
|
|
||||||
|
if nmsgs1 != 3 { t.Errorf("number of messages for s1 received must be 3, but got %d\n", nmsgs1) }
|
||||||
|
if nmsgs2 != 5 { t.Errorf("number of messages for s2 received must be 5, but got %d\n", nmsgs2) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBulletin2(t *testing.T) {
|
||||||
|
var b *hodu.Bulletin[string]
|
||||||
|
var s1 *hodu.BulletinSubscription[string]
|
||||||
|
var s2 *hodu.BulletinSubscription[string]
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var nmsgs1 int
|
||||||
|
var nmsgs2 int
|
||||||
|
|
||||||
|
b = hodu.NewBulletin[string](nil, 13) // if the size is too small, some messages are lost
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go b.RunTask(&wg)
|
||||||
|
|
||||||
|
s1, _ = b.Subscribe("")
|
||||||
|
s2, _ = b.Subscribe("")
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
var m string
|
||||||
|
var ok bool
|
||||||
|
var c1 chan string
|
||||||
|
var c2 chan string
|
||||||
|
|
||||||
|
c1 = s1.C
|
||||||
|
c2 = s2.C
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
for c1 != nil || c2 != nil {
|
||||||
|
select {
|
||||||
|
case m, ok = <-c1:
|
||||||
|
if ok { fmt.Printf ("s1: %+v\n", m); nmsgs1++ } else { c1 = nil; fmt.Printf ("s1 closed\n")}
|
||||||
|
|
||||||
|
case m, ok = <-c2:
|
||||||
|
if ok { fmt.Printf ("s2: %+v\n", m); nmsgs2++ } else { c2 = nil; fmt.Printf ("s2 closed\n") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
b.Enqueue("donkey")
|
||||||
|
b.Enqueue("monkey")
|
||||||
|
b.Enqueue("donkey kong")
|
||||||
|
b.Enqueue("monkey hong")
|
||||||
|
b.Enqueue("home")
|
||||||
|
b.Enqueue("fire")
|
||||||
|
b.Enqueue("sunflower")
|
||||||
|
b.Enqueue("itsy bitsy spider")
|
||||||
|
b.Enqueue("marigold")
|
||||||
|
b.Enqueue("parrot")
|
||||||
|
b.Enqueue("tiger")
|
||||||
|
b.Enqueue("walrus")
|
||||||
|
b.Enqueue("donkey runs")
|
||||||
|
// without this unsubscription may happen before s2.C can receive messages
|
||||||
|
// 100 millisconds must be longer than enough for all messages to be received
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
b.Unsubscribe(s2)
|
||||||
|
b.Enqueue("lion king")
|
||||||
|
b.Enqueue("fly to the ground")
|
||||||
|
b.Enqueue("dig it")
|
||||||
|
b.Enqueue("dig it dawg")
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
b.UnsubscribeAll()
|
||||||
|
b.ReqStop()
|
||||||
|
wg.Wait()
|
||||||
|
fmt.Printf ("---------------------\n")
|
||||||
|
|
||||||
|
if nmsgs1 != 17 { t.Errorf("number of messages for s1 received must be 17, but got %d\n", nmsgs1) }
|
||||||
|
if nmsgs2 != 13 { t.Errorf("number of messages for s2 received must be 13, but got %d\n", nmsgs2) }
|
||||||
|
}
|
932
client-ctl.go
932
client-ctl.go
File diff suppressed because it is too large
Load Diff
126
client-metrics.go
Normal file
126
client-metrics.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package hodu
|
||||||
|
|
||||||
|
import "runtime"
|
||||||
|
import "strings"
|
||||||
|
import "github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
|
type ClientCollector struct {
|
||||||
|
client *Client
|
||||||
|
BuildInfo *prometheus.Desc
|
||||||
|
ClientConns *prometheus.Desc
|
||||||
|
ClientRoutes *prometheus.Desc
|
||||||
|
ClientPeers *prometheus.Desc
|
||||||
|
PtySessions *prometheus.Desc
|
||||||
|
RptySessions *prometheus.Desc
|
||||||
|
RpxSessions *prometheus.Desc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClientCollector returns a new ClientCollector with all prometheus.Desc initialized
|
||||||
|
func NewClientCollector(client *Client) ClientCollector {
|
||||||
|
var prefix string
|
||||||
|
|
||||||
|
// prometheus doesn't like a dash. change it to an underscore
|
||||||
|
prefix = strings.ReplaceAll(client.Name(), "-", "_") + "_"
|
||||||
|
return ClientCollector{
|
||||||
|
client: client,
|
||||||
|
|
||||||
|
BuildInfo: prometheus.NewDesc(
|
||||||
|
prefix + "build_info",
|
||||||
|
"Build information",
|
||||||
|
[]string{
|
||||||
|
"goarch",
|
||||||
|
"goos",
|
||||||
|
"goversion",
|
||||||
|
}, nil,
|
||||||
|
),
|
||||||
|
|
||||||
|
ClientConns: prometheus.NewDesc(
|
||||||
|
prefix + "client_conns",
|
||||||
|
"Number of client connections from clients",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
ClientRoutes: prometheus.NewDesc(
|
||||||
|
prefix + "client_routes",
|
||||||
|
"Number of client-side routes",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
ClientPeers: prometheus.NewDesc(
|
||||||
|
prefix + "client_peers",
|
||||||
|
"Number of client-side peers",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
PtySessions: prometheus.NewDesc(
|
||||||
|
prefix + "pty_sessions",
|
||||||
|
"Number of pty sessions",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
RptySessions: prometheus.NewDesc(
|
||||||
|
prefix + "rpty_sessions",
|
||||||
|
"Number of rpty sessions",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
RpxSessions: prometheus.NewDesc(
|
||||||
|
prefix + "rpx_sessions",
|
||||||
|
"Number of rpx sessions",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ClientCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||||
|
ch <- c.BuildInfo
|
||||||
|
ch <- c.ClientConns
|
||||||
|
ch <- c.ClientRoutes
|
||||||
|
ch <- c.ClientPeers
|
||||||
|
ch <- c.PtySessions
|
||||||
|
ch <- c.RptySessions
|
||||||
|
ch <- c.RpxSessions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ClientCollector) Collect(ch chan<- prometheus.Metric) {
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.BuildInfo,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
1,
|
||||||
|
runtime.GOARCH,
|
||||||
|
runtime.GOOS,
|
||||||
|
runtime.Version(),
|
||||||
|
)
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.ClientConns,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
float64(c.client.stats.conns.Load()),
|
||||||
|
)
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.ClientRoutes,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
float64(c.client.stats.routes.Load()),
|
||||||
|
)
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.ClientPeers,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
float64(c.client.stats.peers.Load()),
|
||||||
|
)
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.PtySessions,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
float64(c.client.stats.pty_sessions.Load()),
|
||||||
|
)
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.RptySessions,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
float64(c.client.stats.rpty_sessions.Load()),
|
||||||
|
)
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.RpxSessions,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
float64(c.client.stats.rpx_sessions.Load()),
|
||||||
|
)
|
||||||
|
}
|
@ -26,34 +26,49 @@ func (cpc *ClientPeerConn) RunTask(wg *sync.WaitGroup) error {
|
|||||||
var n int
|
var n int
|
||||||
|
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
cpc.route.cts.C.FirePeerEvent(CLIENT_EVENT_PEER_STARTED, cpc)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
n, err = cpc.conn.Read(buf[:])
|
n, err = cpc.conn.Read(buf[:])
|
||||||
|
if n > 0 {
|
||||||
|
var err2 error
|
||||||
|
err2 = cpc.route.cts.psc.Send(MakePeerDataPacket(cpc.route.Id, cpc.conn_id, buf[0:n]))
|
||||||
|
if err2 != nil {
|
||||||
|
cpc.route.cts.C.log.Write(cpc.route.cts.Sid, LOG_ERROR,
|
||||||
|
"Failed to write peer(%d,%d,%s,%s) data to server - %s",
|
||||||
|
cpc.route.Id, cpc.conn_id, cpc.conn.RemoteAddr().String(), cpc.conn.LocalAddr().String(), err2.Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, io.EOF) || strings.Contains(err.Error(), "use of closed network connection") { // i hate checking this condition with strings.Contains()
|
if errors.Is(err, io.EOF) || strings.Contains(err.Error(), "use of closed network connection") { // i hate checking this condition with strings.Contains()
|
||||||
cpc.route.cts.cli.log.Write(cpc.route.cts.sid, LOG_INFO,
|
cpc.route.cts.C.log.Write(cpc.route.cts.Sid, LOG_INFO,
|
||||||
"Client-side peer(%d,%d,%s,%s) closed",
|
"Client-side peer(%d,%d,%s,%s) closed",
|
||||||
cpc.route.id, cpc.conn_id, cpc.conn.RemoteAddr().String(), cpc.conn.LocalAddr().String())
|
cpc.route.Id, cpc.conn_id, cpc.conn.RemoteAddr().String(), cpc.conn.LocalAddr().String())
|
||||||
} else {
|
} else {
|
||||||
cpc.route.cts.cli.log.Write(cpc.route.cts.sid, LOG_ERROR,
|
cpc.route.cts.C.log.Write(cpc.route.cts.Sid, LOG_ERROR,
|
||||||
"Failed to read from client-side peer(%d,%d,%s,%s) - %s",
|
"Failed to read from client-side peer(%d,%d,%s,%s) - %s",
|
||||||
cpc.route.id, cpc.conn_id, cpc.conn.RemoteAddr().String(), cpc.conn.LocalAddr().String(), err.Error())
|
cpc.route.Id, cpc.conn_id, cpc.conn.RemoteAddr().String(), cpc.conn.LocalAddr().String(), err.Error())
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cpc.route.cts.psc.Send(MakePeerDataPacket(cpc.route.id, cpc.conn_id, buf[0:n]))
|
|
||||||
if err != nil {
|
|
||||||
cpc.route.cts.cli.log.Write(cpc.route.cts.sid, LOG_ERROR,
|
|
||||||
"Failed to write peer(%d,%d,%s,%s) data to server - %s",
|
|
||||||
cpc.route.id, cpc.conn_id, cpc.conn.RemoteAddr().String(), cpc.conn.LocalAddr().String(), err.Error())
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cpc.route.cts.psc.Send(MakePeerStoppedPacket(cpc.route.id, cpc.conn_id, cpc.conn.RemoteAddr().String(), cpc.conn.LocalAddr().String())) // nothing much to do upon failure. no error check here
|
cpc.route.cts.psc.Send(MakePeerStoppedPacket(cpc.route.Id, cpc.conn_id, cpc.conn.RemoteAddr().String(), cpc.conn.LocalAddr().String())) // nothing much to do upon failure. no error check here
|
||||||
cpc.ReqStop()
|
cpc.ReqStop()
|
||||||
cpc.route.RemoveClientPeerConn(cpc)
|
cpc.route.RemoveClientPeerConn(cpc)
|
||||||
|
|
||||||
|
cpc.route.cts.C.ptc_mtx.Lock()
|
||||||
|
cpc.route.cts.C.ptc_list.Remove(cpc.node_in_client)
|
||||||
|
cpc.node_in_client = nil
|
||||||
|
cpc.route.cts.C.ptc_mtx.Unlock()
|
||||||
|
|
||||||
|
cpc.route.cts.ptc_mtx.Lock()
|
||||||
|
cpc.route.cts.ptc_list.Remove(cpc.node_in_conn)
|
||||||
|
cpc.node_in_conn = nil
|
||||||
|
cpc.route.cts.ptc_mtx.Unlock()
|
||||||
|
|
||||||
|
cpc.route.cts.C.FirePeerEvent(CLIENT_EVENT_PEER_STOPPED, cpc)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
299
client-pty.go
Normal file
299
client-pty.go
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
package hodu
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
import "errors"
|
||||||
|
import "io"
|
||||||
|
import "net/http"
|
||||||
|
import "os"
|
||||||
|
import "os/exec"
|
||||||
|
import "strconv"
|
||||||
|
import "strings"
|
||||||
|
import "sync"
|
||||||
|
import "text/template"
|
||||||
|
|
||||||
|
import pts "github.com/creack/pty"
|
||||||
|
import "golang.org/x/net/websocket"
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
type client_pty_ws struct {
|
||||||
|
C *Client
|
||||||
|
Id string
|
||||||
|
ws *websocket.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type client_pty_xterm_file struct {
|
||||||
|
client_ctl
|
||||||
|
file string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------
|
||||||
|
|
||||||
|
func (pty *client_pty_ws) Identity() string {
|
||||||
|
return pty.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pty *client_pty_ws) ServeWebsocket(ws *websocket.Conn) (int, error) {
|
||||||
|
var c *Client
|
||||||
|
var req *http.Request
|
||||||
|
//var username string
|
||||||
|
//var password string
|
||||||
|
var in *os.File
|
||||||
|
var out *os.File
|
||||||
|
var tty *os.File
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
var pfd [2]int = [2]int{ -1, -1 }
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var conn_ready_chan chan bool
|
||||||
|
var err error
|
||||||
|
|
||||||
|
c = pty.C
|
||||||
|
req = ws.Request()
|
||||||
|
conn_ready_chan = make(chan bool, 3)
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
var conn_ready bool
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
defer ws.Close() // dirty way to break the main loop
|
||||||
|
|
||||||
|
conn_ready = <-conn_ready_chan
|
||||||
|
if conn_ready { // connected
|
||||||
|
var poll_fds []unix.PollFd
|
||||||
|
var buf [2048]byte
|
||||||
|
var n int
|
||||||
|
var err error
|
||||||
|
|
||||||
|
poll_fds = []unix.PollFd{
|
||||||
|
unix.PollFd{Fd: int32(out.Fd()), Events: unix.POLLIN},
|
||||||
|
unix.PollFd{Fd: int32(pfd[0]), Events: unix.POLLIN},
|
||||||
|
}
|
||||||
|
|
||||||
|
c.stats.pty_sessions.Add(1)
|
||||||
|
for {
|
||||||
|
n, err = unix.Poll(poll_fds, -1) // -1 means wait indefinitely
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, unix.EINTR) { continue }
|
||||||
|
c.log.Write("", LOG_ERROR, "[%s] Failed to poll pty stdout - %s", req.RemoteAddr, err.Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if n == 0 { // timed out
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poll_fds[0].Revents & (unix.POLLERR | unix.POLLHUP | unix.POLLNVAL)) != 0 {
|
||||||
|
c.log.Write(pty.Id, LOG_DEBUG, "[%s] EOF detected on pty stdout", req.RemoteAddr)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (poll_fds[1].Revents & (unix.POLLERR | unix.POLLHUP | unix.POLLNVAL)) != 0 {
|
||||||
|
c.log.Write(pty.Id, LOG_DEBUG, "[%s] EOF detected on pty event pipe", req.RemoteAddr)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poll_fds[0].Revents & unix.POLLIN) != 0 {
|
||||||
|
n, err = out.Read(buf[:])
|
||||||
|
if n > 0 {
|
||||||
|
var err2 error
|
||||||
|
err2 = send_ws_data_for_xterm(ws, "iov", string(buf[:n]))
|
||||||
|
if err2 != nil {
|
||||||
|
c.log.Write(pty.Id, LOG_ERROR, "[%s] Failed to send to websocket - %s", req.RemoteAddr, err2.Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
c.log.Write(pty.Id, LOG_ERROR, "[%s] Failed to read pty stdout - %s", req.RemoteAddr, err.Error())
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (poll_fds[1].Revents & unix.POLLIN) != 0 {
|
||||||
|
c.log.Write(pty.Id, LOG_DEBUG, "[%s] Stop request noticed on pty event pipe", req.RemoteAddr)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.stats.pty_sessions.Add(-1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ws_recv_loop:
|
||||||
|
for {
|
||||||
|
var msg []byte
|
||||||
|
err = websocket.Message.Receive(ws, &msg)
|
||||||
|
if err != nil { goto done }
|
||||||
|
|
||||||
|
if len(msg) > 0 {
|
||||||
|
var ev json_xterm_ws_event
|
||||||
|
err = json.Unmarshal(msg, &ev)
|
||||||
|
if err == nil {
|
||||||
|
switch ev.Type {
|
||||||
|
case "open":
|
||||||
|
if tty == nil && len(ev.Data) == 2 {
|
||||||
|
// not using username and password for now...
|
||||||
|
//username = string(ev.Data[0])
|
||||||
|
//password = string(ev.Data[1])
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
err = unix.Pipe(pfd[:])
|
||||||
|
if err != nil {
|
||||||
|
c.log.Write(pty.Id, LOG_ERROR, "[%s] Failed to create event pipe for pty - %s", req.RemoteAddr, err.Error())
|
||||||
|
send_ws_data_for_xterm(ws, "error", err.Error())
|
||||||
|
ws.Close() // dirty way to flag out the error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, tty, err = connect_pty(c.pty_shell, c.pty_user)
|
||||||
|
if err != nil {
|
||||||
|
c.log.Write(pty.Id, LOG_ERROR, "[%s] Failed to connect pty - %s", req.RemoteAddr, err.Error())
|
||||||
|
send_ws_data_for_xterm(ws, "error", err.Error())
|
||||||
|
ws.Close() // dirty way to flag out the error
|
||||||
|
unix.Close(pfd[0]); pfd[0] = -1
|
||||||
|
unix.Close(pfd[1]); pfd[1] = -1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = send_ws_data_for_xterm(ws, "status", "opened")
|
||||||
|
if err != nil {
|
||||||
|
c.log.Write(pty.Id, LOG_ERROR, "[%s] Failed to write opened event to websocket - %s", req.RemoteAddr, err.Error())
|
||||||
|
ws.Close() // dirty way to flag out the error
|
||||||
|
unix.Close(pfd[0]); pfd[0] = -1
|
||||||
|
unix.Close(pfd[1]); pfd[1] = -1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.log.Write(pty.Id, LOG_DEBUG, "[%s] Opened pty session", req.RemoteAddr)
|
||||||
|
out = tty
|
||||||
|
in = tty
|
||||||
|
conn_ready_chan <- true
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
case "close":
|
||||||
|
if tty != nil {
|
||||||
|
tty.Close()
|
||||||
|
tty = nil
|
||||||
|
}
|
||||||
|
if pfd[1] >= 0 {
|
||||||
|
unix.Write(pfd[1], []byte{0})
|
||||||
|
}
|
||||||
|
break ws_recv_loop
|
||||||
|
|
||||||
|
case "iov":
|
||||||
|
if tty != nil {
|
||||||
|
var i int
|
||||||
|
for i, _ = range ev.Data {
|
||||||
|
in.Write([]byte(ev.Data[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "size":
|
||||||
|
if tty != nil && len(ev.Data) == 2 {
|
||||||
|
var rows int
|
||||||
|
var cols int
|
||||||
|
rows, _ = strconv.Atoi(ev.Data[0])
|
||||||
|
cols, _ = strconv.Atoi(ev.Data[1])
|
||||||
|
pts.Setsize(tty, &pts.Winsize{Rows: uint16(rows), Cols: uint16(cols)})
|
||||||
|
c.log.Write(pty.Id, LOG_DEBUG, "[%s] Resized terminal to %d,%d", req.RemoteAddr, rows, cols)
|
||||||
|
// ignore error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tty != nil {
|
||||||
|
err = send_ws_data_for_xterm(ws, "status", "closed")
|
||||||
|
if err != nil { goto done }
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
conn_ready_chan <- false
|
||||||
|
ws.Close()
|
||||||
|
if cmd != nil {
|
||||||
|
// kill the child process underneath to close ptym(the master pty).
|
||||||
|
//cmd.Process.Signal(syscall.SIGTERM)
|
||||||
|
cmd.Process.Kill()
|
||||||
|
}
|
||||||
|
if tty != nil { tty.Close() }
|
||||||
|
if cmd != nil { cmd.Wait() }
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// close the event pipe after all goroutines are over
|
||||||
|
if pfd[0] >= 0 { unix.Close(pfd[0]) }
|
||||||
|
if pfd[1] >= 0 { unix.Close(pfd[1]) }
|
||||||
|
|
||||||
|
c.log.Write(pty.Id, LOG_DEBUG, "[%s] Ended pty session", req.RemoteAddr)
|
||||||
|
|
||||||
|
return http.StatusOK, err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------------------------------
|
||||||
|
|
||||||
|
func (pty *client_pty_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
|
||||||
|
var c *Client
|
||||||
|
var status_code int
|
||||||
|
var err error
|
||||||
|
|
||||||
|
c = pty.c
|
||||||
|
|
||||||
|
switch pty.file {
|
||||||
|
case "xterm.js":
|
||||||
|
status_code = WriteJsRespHeader(w, http.StatusOK)
|
||||||
|
w.Write(xterm_js)
|
||||||
|
case "xterm-addon-fit.js":
|
||||||
|
status_code = WriteJsRespHeader(w, http.StatusOK)
|
||||||
|
w.Write(xterm_addon_fit_js)
|
||||||
|
case "xterm.css":
|
||||||
|
status_code = WriteCssRespHeader(w, http.StatusOK)
|
||||||
|
w.Write(xterm_css)
|
||||||
|
case "xterm.html":
|
||||||
|
var tmpl *template.Template
|
||||||
|
|
||||||
|
tmpl = template.New("")
|
||||||
|
if c.xterm_html != "" {
|
||||||
|
_, err = tmpl.Parse(c.xterm_html)
|
||||||
|
} else {
|
||||||
|
_, err = tmpl.Parse(xterm_html)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
status_code = WriteEmptyRespHeader(w, http.StatusInternalServerError)
|
||||||
|
goto oops
|
||||||
|
} else {
|
||||||
|
status_code = WriteHtmlRespHeader(w, http.StatusOK)
|
||||||
|
tmpl.Execute(w,
|
||||||
|
&xterm_session_info{
|
||||||
|
Mode: "pty",
|
||||||
|
ConnId: "-1",
|
||||||
|
RouteId: "-1",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case "_forbidden":
|
||||||
|
status_code = WriteEmptyRespHeader(w, http.StatusForbidden)
|
||||||
|
|
||||||
|
case "_notfound":
|
||||||
|
status_code = WriteEmptyRespHeader(w, http.StatusNotFound)
|
||||||
|
|
||||||
|
default:
|
||||||
|
if strings.HasPrefix(pty.file, "_redir:") {
|
||||||
|
status_code = http.StatusMovedPermanently
|
||||||
|
w.Header().Set("Location", pty.file[7:])
|
||||||
|
w.WriteHeader(status_code)
|
||||||
|
} else {
|
||||||
|
status_code = WriteEmptyRespHeader(w, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//done:
|
||||||
|
return status_code, nil
|
||||||
|
|
||||||
|
oops:
|
||||||
|
return status_code, err
|
||||||
|
}
|
24
client_test.go
Normal file
24
client_test.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package hodu_test
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
import "hodu"
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
type TestLogger struct {}
|
||||||
|
|
||||||
|
func (l *TestLogger) Write(id string, level hodu.LogLevel, fmtstr string, args ...interface{}) {}
|
||||||
|
func (l *TestLogger) WriteWithCallDepth(id string, level hodu.LogLevel, call_depth int, fmtstr string, args ...interface{}) {}
|
||||||
|
func (l *TestLogger) Rotate() {}
|
||||||
|
func (l *TestLogger) Close() {}
|
||||||
|
|
||||||
|
func TestClient001(t *testing.T) {
|
||||||
|
var c *hodu.Client
|
||||||
|
var r *hodu.ClientRoute
|
||||||
|
var err error
|
||||||
|
|
||||||
|
c = hodu.NewClient(context.Background(), "test-client", &TestLogger{}, &hodu.ClientConfig{})
|
||||||
|
|
||||||
|
r, err = c.FindClientRouteByServerPeerSvcPortIdStr("100", "200")
|
||||||
|
if err == nil { t.Errorf("Search on empty client structure must have failed") }
|
||||||
|
if r != nil { t.Errorf("Main route must not be nil upon no error") }
|
||||||
|
}
|
200
cmd/config.go
200
cmd/config.go
@ -1,16 +1,20 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import "crypto/rsa"
|
||||||
import "crypto/tls"
|
import "crypto/tls"
|
||||||
import "crypto/x509"
|
import "crypto/x509"
|
||||||
import "errors"
|
import "encoding/base64"
|
||||||
|
import "encoding/pem"
|
||||||
import "fmt"
|
import "fmt"
|
||||||
import "hodu"
|
import "hodu"
|
||||||
import "io"
|
import "net/netip"
|
||||||
import "io/ioutil"
|
|
||||||
import "os"
|
import "os"
|
||||||
|
import "strings"
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
import "gopkg.in/yaml.v3"
|
//import "gopkg.in/yaml.v3"
|
||||||
|
import yaml "github.com/goccy/go-yaml"
|
||||||
|
|
||||||
|
|
||||||
type ServerTLSConfig struct {
|
type ServerTLSConfig struct {
|
||||||
Enabled bool `yaml:"enabled"`
|
Enabled bool `yaml:"enabled"`
|
||||||
@ -42,9 +46,37 @@ type ClientTLSConfig struct {
|
|||||||
ServerName string `yaml:"server-name"`
|
ServerName string `yaml:"server-name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HttpAccessRule struct {
|
||||||
|
Prefix string `yaml:"prefix"`
|
||||||
|
OrgNets []string `yaml:"origin-networks"`
|
||||||
|
Action string `yaml:"action"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpAuthConfig struct {
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
Realm string `yaml:"realm"`
|
||||||
|
Creds []string `yaml:"credentials"`
|
||||||
|
TokenTtl string `yaml:"token-ttl"`
|
||||||
|
TokenRsaKeyText string `yaml:"token-rsa-key-text"`
|
||||||
|
TokenRsaKeyFile string `yaml:"token-rsa-key-file"`
|
||||||
|
AccessRules []HttpAccessRule `yaml:"access-rules"`
|
||||||
|
}
|
||||||
|
|
||||||
type CTLServiceConfig struct {
|
type CTLServiceConfig struct {
|
||||||
Prefix string `yaml:"prefix"` // url prefix for control channel endpoints
|
Prefix string `yaml:"prefix"` // url prefix for control channel endpoints
|
||||||
Addrs []string `yaml:"addresses"`
|
Addrs []string `yaml:"addresses"`
|
||||||
|
Cors bool `yaml:"cors"`
|
||||||
|
Auth HttpAuthConfig `yaml:"auth"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RPXServiceConfig struct {
|
||||||
|
Addrs []string `yaml:"addresses"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RPXClientTokenConfig struct {
|
||||||
|
AttrName string `yaml:"attr-name"`
|
||||||
|
Regex string `yaml:"regex"`
|
||||||
|
SubmatchIndex int `yaml:"submatch-index"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PXYServiceConfig struct {
|
type PXYServiceConfig struct {
|
||||||
@ -72,6 +104,8 @@ type ServerAppConfig struct {
|
|||||||
LogRotate int `yaml:"log-rotate"`
|
LogRotate int `yaml:"log-rotate"`
|
||||||
MaxPeers int `yaml:"max-peer-conns"` // maximum number of connections from peers
|
MaxPeers int `yaml:"max-peer-conns"` // maximum number of connections from peers
|
||||||
MaxRpcConns int `yaml:"max-rpc-conns"` // maximum number of rpc connections
|
MaxRpcConns int `yaml:"max-rpc-conns"` // maximum number of rpc connections
|
||||||
|
PtyUser string `yaml:"pty-user"`
|
||||||
|
PtyShell string `yaml:"pty-shell"`
|
||||||
XtermHtmlFile string `yaml:"xterm-html-file"`
|
XtermHtmlFile string `yaml:"xterm-html-file"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +117,11 @@ type ClientAppConfig struct {
|
|||||||
MaxPeers int `yaml:"max-peer-conns"` // maximum number of connections from peers
|
MaxPeers int `yaml:"max-peer-conns"` // maximum number of connections from peers
|
||||||
MaxRpcConns int `yaml:"max-rpc-conns"` // maximum number of rpc connections
|
MaxRpcConns int `yaml:"max-rpc-conns"` // maximum number of rpc connections
|
||||||
PeerConnTmout time.Duration `yaml:"peer-conn-timeout"`
|
PeerConnTmout time.Duration `yaml:"peer-conn-timeout"`
|
||||||
|
TokenText string `yaml:"token-text"`
|
||||||
|
TokenFile string `yaml:"token-file"`
|
||||||
|
PtyUser string `yaml:"pty-user"`
|
||||||
|
PtyShell string `yaml:"pty-shell"`
|
||||||
|
XtermHtmlFile string `yaml:"xterm-html-file"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
@ -93,9 +132,18 @@ type ServerConfig struct {
|
|||||||
TLS ServerTLSConfig `yaml:"tls"`
|
TLS ServerTLSConfig `yaml:"tls"`
|
||||||
} `yaml:"ctl"`
|
} `yaml:"ctl"`
|
||||||
|
|
||||||
|
RPX struct {
|
||||||
|
Service RPXServiceConfig `yaml:"service"`
|
||||||
|
TLS ServerTLSConfig `yaml:"tls"`
|
||||||
|
ClientToken RPXClientTokenConfig `yaml:"client-token"`
|
||||||
|
} `yaml:"rpx"`
|
||||||
|
|
||||||
PXY struct {
|
PXY struct {
|
||||||
Service PXYServiceConfig `yaml:"service"`
|
Service PXYServiceConfig `yaml:"service"`
|
||||||
TLS ServerTLSConfig `yaml:"tls"`
|
TLS ServerTLSConfig `yaml:"tls"`
|
||||||
|
Target struct {
|
||||||
|
TLS ClientTLSConfig `yaml:"tls"`
|
||||||
|
} `yaml:"target"`
|
||||||
} `yaml:"pxy"`
|
} `yaml:"pxy"`
|
||||||
|
|
||||||
WPX struct {
|
WPX struct {
|
||||||
@ -120,48 +168,40 @@ type ClientConfig struct {
|
|||||||
Endpoint RPCEndpointConfig `yaml:"endpoint"`
|
Endpoint RPCEndpointConfig `yaml:"endpoint"`
|
||||||
TLS ClientTLSConfig `yaml:"tls"`
|
TLS ClientTLSConfig `yaml:"tls"`
|
||||||
} `yaml:"rpc"`
|
} `yaml:"rpc"`
|
||||||
|
RPX struct {
|
||||||
|
Target struct {
|
||||||
|
Addr string `yaml:"address"`
|
||||||
|
TLS ClientTLSConfig `yaml:"tls"`
|
||||||
|
} `yaml:"target"`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func load_server_config(cfgfile string) (*ServerConfig, error) {
|
func load_server_config_to(cfgfile string, cfg *ServerConfig) error {
|
||||||
var cfg ServerConfig
|
|
||||||
var f *os.File
|
var f *os.File
|
||||||
var yd *yaml.Decoder
|
var yd *yaml.Decoder
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
f, err = os.Open(cfgfile)
|
f, err = os.Open(cfgfile)
|
||||||
if err != nil && errors.Is(err, io.EOF) {
|
if err != nil { return err }
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
yd = yaml.NewDecoder(f)
|
yd = yaml.NewDecoder(f, yaml.AllowDuplicateMapKey(), yaml.DisallowUnknownField())
|
||||||
err = yd.Decode(&cfg)
|
err = yd.Decode(cfg)
|
||||||
f.Close()
|
f.Close()
|
||||||
if err != nil {
|
return err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &cfg, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func load_client_config(cfgfile string) (*ClientConfig, error) {
|
func load_client_config_to(cfgfile string, cfg *ClientConfig) error {
|
||||||
var cfg ClientConfig
|
|
||||||
var f *os.File
|
var f *os.File
|
||||||
var yd *yaml.Decoder
|
var yd *yaml.Decoder
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
f, err = os.Open(cfgfile)
|
f, err = os.Open(cfgfile)
|
||||||
if err != nil && errors.Is(err, io.EOF) {
|
if err != nil { return err }
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
yd = yaml.NewDecoder(f)
|
yd = yaml.NewDecoder(f, yaml.AllowDuplicateMapKey(), yaml.DisallowUnknownField())
|
||||||
err = yd.Decode(&cfg)
|
err = yd.Decode(cfg)
|
||||||
f.Close()
|
f.Close()
|
||||||
if err != nil {
|
return err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &cfg, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -237,7 +277,7 @@ func make_tls_server_config(cfg *ServerTLSConfig) (*tls.Config, error) {
|
|||||||
cert, err = tls.X509KeyPair(hodu_tls_cert_text, hodu_tls_key_text)
|
cert, err = tls.X509KeyPair(hodu_tls_cert_text, hodu_tls_key_text)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load key pair - %s", err)
|
return nil, fmt.Errorf("failed to load key pair - %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
cert_pool = x509.NewCertPool()
|
cert_pool = x509.NewCertPool()
|
||||||
@ -248,7 +288,7 @@ func make_tls_server_config(cfg *ServerTLSConfig) (*tls.Config, error) {
|
|||||||
}
|
}
|
||||||
} else if cfg.ClientCACertFile != "" {
|
} else if cfg.ClientCACertFile != "" {
|
||||||
var text []byte
|
var text []byte
|
||||||
text, err = ioutil.ReadFile(cfg.ClientCACertFile)
|
text, err = os.ReadFile(cfg.ClientCACertFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load ca certficate file %s - %s", cfg.ClientCACertFile, err.Error())
|
return nil, fmt.Errorf("failed to load ca certficate file %s - %s", cfg.ClientCACertFile, err.Error())
|
||||||
}
|
}
|
||||||
@ -295,7 +335,7 @@ func make_tls_client_config(cfg *ClientTLSConfig) (*tls.Config, error) {
|
|||||||
cert, err = tls.X509KeyPair(hodu_tls_cert_text, hodu_tls_key_text)
|
cert, err = tls.X509KeyPair(hodu_tls_cert_text, hodu_tls_key_text)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load key pair - %s", err)
|
return nil, fmt.Errorf("failed to load key pair - %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
cert_pool = x509.NewCertPool()
|
cert_pool = x509.NewCertPool()
|
||||||
@ -306,7 +346,7 @@ func make_tls_client_config(cfg *ClientTLSConfig) (*tls.Config, error) {
|
|||||||
}
|
}
|
||||||
} else if cfg.ServerCACertFile != "" {
|
} else if cfg.ServerCACertFile != "" {
|
||||||
var text []byte
|
var text []byte
|
||||||
text, err = ioutil.ReadFile(cfg.ServerCACertFile)
|
text, err = os.ReadFile(cfg.ServerCACertFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load ca certficate file %s - %s", cfg.ServerCACertFile, err.Error())
|
return nil, fmt.Errorf("failed to load ca certficate file %s - %s", cfg.ServerCACertFile, err.Error())
|
||||||
}
|
}
|
||||||
@ -334,3 +374,99 @@ func make_tls_client_config(cfg *ClientTLSConfig) (*tls.Config, error) {
|
|||||||
|
|
||||||
return tlscfg, nil
|
return tlscfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
func make_http_auth_config(cfg *HttpAuthConfig) (*hodu.HttpAuthConfig, error) {
|
||||||
|
var config hodu.HttpAuthConfig
|
||||||
|
var cred string
|
||||||
|
var b []byte
|
||||||
|
var x []string
|
||||||
|
var rsa_key_text []byte
|
||||||
|
var rk *rsa.PrivateKey
|
||||||
|
var pb *pem.Block
|
||||||
|
var rule HttpAccessRule
|
||||||
|
var idx int
|
||||||
|
var err error
|
||||||
|
|
||||||
|
config.Enabled = cfg.Enabled
|
||||||
|
config.Realm = cfg.Realm
|
||||||
|
config.Creds = make(hodu.HttpAuthCredMap)
|
||||||
|
config.TokenTtl, err = hodu.ParseDurationString(cfg.TokenTtl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid token ttl %s - %s", cred, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert user credentials
|
||||||
|
for _, cred = range cfg.Creds {
|
||||||
|
b, err = base64.StdEncoding.DecodeString(cred)
|
||||||
|
if err == nil { cred = string(b) }
|
||||||
|
|
||||||
|
// each entry must be of the form username:password
|
||||||
|
x = strings.Split(cred, ":")
|
||||||
|
if len(x) != 2 {
|
||||||
|
return nil, fmt.Errorf("invalid auth credential - %s", cred)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Creds[x[0]] = x[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// load rsa key
|
||||||
|
if cfg.TokenRsaKeyText == "" && cfg.TokenRsaKeyFile != "" {
|
||||||
|
rsa_key_text, err = os.ReadFile(cfg.TokenRsaKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read %s - %s", cfg.TokenRsaKeyFile, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rsa_key_text) == 0 { rsa_key_text = []byte(cfg.TokenRsaKeyText) }
|
||||||
|
if len(rsa_key_text) == 0 { rsa_key_text = hodu_rsa_key_text }
|
||||||
|
|
||||||
|
pb, b = pem.Decode(rsa_key_text)
|
||||||
|
if pb == nil || len(b) > 0 {
|
||||||
|
return nil, fmt.Errorf("invalid token rsa key text %s - no block or too many blocks", string(rsa_key_text))
|
||||||
|
}
|
||||||
|
|
||||||
|
rk, err = x509.ParsePKCS1PrivateKey(pb.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid token rsa key text %s - %s", string(rsa_key_text), err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
config.TokenRsaKey = rk
|
||||||
|
|
||||||
|
// load access rules
|
||||||
|
config.AccessRules = make([]hodu.HttpAccessRule, len(cfg.AccessRules))
|
||||||
|
for idx, rule = range cfg.AccessRules {
|
||||||
|
var action hodu.HttpAccessAction
|
||||||
|
var orgnet string
|
||||||
|
var orgnet_idx int
|
||||||
|
|
||||||
|
if rule.Prefix == "" {
|
||||||
|
return nil, fmt.Errorf("blank access rule prefix not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToLower(rule.Action) {
|
||||||
|
case "accept":
|
||||||
|
action = hodu.HTTP_ACCESS_ACCEPT
|
||||||
|
case "reject":
|
||||||
|
action = hodu.HTTP_ACCESS_REJECT
|
||||||
|
case "auth-required":
|
||||||
|
action = hodu.HTTP_ACCESS_AUTH_REQUIRED
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid access rule action %s", rule.Action)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.AccessRules[idx] = hodu.HttpAccessRule{
|
||||||
|
Prefix: rule.Prefix,
|
||||||
|
Action: action,
|
||||||
|
OrgNets: make([]netip.Prefix, len(rule.OrgNets)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for orgnet_idx, orgnet = range rule.OrgNets {
|
||||||
|
var netpfx netip.Prefix
|
||||||
|
netpfx, err = netip.ParsePrefix(orgnet)
|
||||||
|
if err != nil { return nil, fmt.Errorf("invalid network %s - %s", orgnet, err.Error()) }
|
||||||
|
config.AccessRules[idx].OrgNets[orgnet_idx] = netpfx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ import "runtime"
|
|||||||
import "strings"
|
import "strings"
|
||||||
import "sync"
|
import "sync"
|
||||||
import "sync/atomic"
|
import "sync/atomic"
|
||||||
|
import "syscall"
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type app_logger_msg_t struct {
|
type app_logger_msg_t struct {
|
||||||
@ -28,16 +29,44 @@ type AppLogger struct {
|
|||||||
msg_chan chan app_logger_msg_t
|
msg_chan chan app_logger_msg_t
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
|
||||||
|
use_color bool
|
||||||
closed atomic.Bool
|
closed atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAppLogger (id string, w io.Writer, mask hodu.LogMask) *AppLogger {
|
func _is_ansi_tty(fd uintptr) bool {
|
||||||
|
var st syscall.Stat_t
|
||||||
|
var err error
|
||||||
|
|
||||||
|
err = syscall.Fstat(int(fd), &st)
|
||||||
|
if err != nil { return false }
|
||||||
|
if (st.Mode & syscall.S_IFMT) == syscall.S_IFCHR {
|
||||||
|
var term string
|
||||||
|
// i assume this fd is bound to the current terminal if it's a character device
|
||||||
|
// if the assumption is wrong, you simply get extraneous ansi code in the output.
|
||||||
|
term = os.Getenv("TERM")
|
||||||
|
if term != "" && term != "dumb" { return true }
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func NewAppLogger(id string, w io.Writer, mask hodu.LogMask) *AppLogger {
|
||||||
var l *AppLogger
|
var l *AppLogger
|
||||||
|
var f *os.File
|
||||||
|
var ok bool
|
||||||
|
var use_color bool
|
||||||
|
|
||||||
|
use_color = false
|
||||||
|
f, ok = w.(*os.File)
|
||||||
|
if ok { use_color = _is_ansi_tty(f.Fd()) }
|
||||||
|
|
||||||
l = &AppLogger{
|
l = &AppLogger{
|
||||||
id: id,
|
id: id,
|
||||||
out: w,
|
out: w,
|
||||||
mask: mask,
|
mask: mask,
|
||||||
msg_chan: make(chan app_logger_msg_t, 256),
|
msg_chan: make(chan app_logger_msg_t, 256),
|
||||||
|
use_color: use_color,
|
||||||
}
|
}
|
||||||
l.closed.Store(false)
|
l.closed.Store(false)
|
||||||
l.wg.Add(1)
|
l.wg.Add(1)
|
||||||
@ -45,7 +74,7 @@ func NewAppLogger (id string, w io.Writer, mask hodu.LogMask) *AppLogger {
|
|||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAppLoggerToFile (id string, file_name string, max_size int64, rotate int, mask hodu.LogMask) (*AppLogger, error) {
|
func NewAppLoggerToFile(id string, file_name string, max_size int64, rotate int, mask hodu.LogMask) (*AppLogger, error) {
|
||||||
var l *AppLogger
|
var l *AppLogger
|
||||||
var f *os.File
|
var f *os.File
|
||||||
var matched bool
|
var matched bool
|
||||||
@ -73,6 +102,7 @@ func NewAppLoggerToFile (id string, file_name string, max_size int64, rotate int
|
|||||||
file_max_size: max_size,
|
file_max_size: max_size,
|
||||||
file_rotate: rotate,
|
file_rotate: rotate,
|
||||||
msg_chan: make(chan app_logger_msg_t, 256),
|
msg_chan: make(chan app_logger_msg_t, 256),
|
||||||
|
use_color: _is_ansi_tty(f.Fd()),
|
||||||
}
|
}
|
||||||
l.closed.Store(false)
|
l.closed.Store(false)
|
||||||
l.wg.Add(1)
|
l.wg.Add(1)
|
||||||
@ -121,7 +151,6 @@ main_loop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (l *AppLogger) Write(id string, level hodu.LogLevel, fmtstr string, args ...interface{}) {
|
func (l *AppLogger) Write(id string, level hodu.LogLevel, fmtstr string, args ...interface{}) {
|
||||||
if l.mask & hodu.LogMask(level) == 0 { return }
|
if l.mask & hodu.LogMask(level) == 0 { return }
|
||||||
l.write(id, level, 1, fmtstr, args...)
|
l.write(id, level, 1, fmtstr, args...)
|
||||||
@ -148,10 +177,10 @@ func (l *AppLogger) write(id string, level hodu.LogLevel, call_depth int, fmtstr
|
|||||||
now = time.Now()
|
now = time.Now()
|
||||||
|
|
||||||
_, off_s = now.Zone()
|
_, off_s = now.Zone()
|
||||||
off_m = off_s / 60;
|
off_m = off_s / 60
|
||||||
off_h = off_m / 60;
|
off_h = off_m / 60
|
||||||
off_m = off_m % 60;
|
off_m = off_m % 60
|
||||||
if off_m < 0 { off_m = -off_m; }
|
if off_m < 0 { off_m = -off_m }
|
||||||
|
|
||||||
sb.WriteString(
|
sb.WriteString(
|
||||||
fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d %+03d%02d ",
|
fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d %+03d%02d ",
|
||||||
@ -170,7 +199,15 @@ func (l *AppLogger) write(id string, level hodu.LogLevel, call_depth int, fmtstr
|
|||||||
}
|
}
|
||||||
sb.WriteString(": ")
|
sb.WriteString(": ")
|
||||||
msg = fmt.Sprintf(fmtstr, args...)
|
msg = fmt.Sprintf(fmtstr, args...)
|
||||||
sb.WriteString(msg)
|
if (l.use_color) {
|
||||||
|
var code string
|
||||||
|
code = l.log_level_to_ansi_code(level)
|
||||||
|
sb.WriteString(code)
|
||||||
|
sb.WriteString(msg)
|
||||||
|
if code != "" { sb.WriteString("\x1B[0m") }
|
||||||
|
} else {
|
||||||
|
sb.WriteString(msg)
|
||||||
|
}
|
||||||
if msg[len(msg) - 1] != '\n' { sb.WriteRune('\n') }
|
if msg[len(msg) - 1] != '\n' { sb.WriteRune('\n') }
|
||||||
|
|
||||||
// use queue to avoid blocking operation as much as possible
|
// use queue to avoid blocking operation as much as possible
|
||||||
@ -212,3 +249,24 @@ func (l *AppLogger) rotate() {
|
|||||||
l.out = l.file
|
l.out = l.file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l* AppLogger) log_level_to_ansi_code(level hodu.LogLevel) string {
|
||||||
|
switch level {
|
||||||
|
case hodu.LOG_ERROR:
|
||||||
|
return "\x1B[31m" // red
|
||||||
|
|
||||||
|
case hodu.LOG_WARN:
|
||||||
|
return "\x1B[33m" // yellow
|
||||||
|
|
||||||
|
case hodu.LOG_INFO:
|
||||||
|
if (l.mask & hodu.LogMask(hodu.LOG_DEBUG)) != 0 {
|
||||||
|
// if debug is enabled, change the color of info.
|
||||||
|
// otherwisse no color
|
||||||
|
return "\x1B[32m" // green
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
312
cmd/main.go
312
cmd/main.go
@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "context"
|
import "context"
|
||||||
import "crypto/tls"
|
|
||||||
import _ "embed"
|
import _ "embed"
|
||||||
import "flag"
|
import "flag"
|
||||||
import "fmt"
|
import "fmt"
|
||||||
@ -10,10 +9,11 @@ import "io"
|
|||||||
import "net"
|
import "net"
|
||||||
import "os"
|
import "os"
|
||||||
import "os/signal"
|
import "os/signal"
|
||||||
|
import "path/filepath"
|
||||||
|
import "regexp"
|
||||||
import "strings"
|
import "strings"
|
||||||
import "sync"
|
import "sync"
|
||||||
import "syscall"
|
import "syscall"
|
||||||
import "time"
|
|
||||||
|
|
||||||
// Don't change these items to 'const' as they can be overridden externally with a linker option
|
// Don't change these items to 'const' as they can be overridden externally with a linker option
|
||||||
var HODU_NAME string = "hodu"
|
var HODU_NAME string = "hodu"
|
||||||
@ -23,6 +23,8 @@ var HODU_VERSION string = "0.0.0"
|
|||||||
var hodu_tls_cert_text []byte
|
var hodu_tls_cert_text []byte
|
||||||
//go:embed tls.key
|
//go:embed tls.key
|
||||||
var hodu_tls_key_text []byte
|
var hodu_tls_key_text []byte
|
||||||
|
//go:embed rsa.key
|
||||||
|
var hodu_rsa_key_text []byte
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
type signal_handler struct {
|
type signal_handler struct {
|
||||||
@ -64,7 +66,7 @@ chan_loop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sh *signal_handler) StartService(data interface{}) {
|
func (sh *signal_handler) StartService(data interface{}) {
|
||||||
// this isn't actually used standalone..
|
// this isn't actually used standalone..
|
||||||
// if we are to implement it, it must use the wait group for signal handler itself
|
// if we are to implement it, it must use the wait group for signal handler itself
|
||||||
// however, this service is run through another service.
|
// however, this service is run through another service.
|
||||||
// sh.wg.Add(1)
|
// sh.wg.Add(1)
|
||||||
@ -89,59 +91,82 @@ func (sh *signal_handler) WriteLog(id string, level hodu.LogLevel, fmt string, a
|
|||||||
|
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
|
|
||||||
func server_main(ctl_addrs []string, rpc_addrs []string, pxy_addrs []string, wpx_addrs []string, cfg *ServerConfig) error {
|
func server_main(ctl_addrs []string, rpc_addrs []string, rpx_addrs[] string, pxy_addrs []string, wpx_addrs []string, logfile string, cfg *ServerConfig) error {
|
||||||
var s *hodu.Server
|
var s *hodu.Server
|
||||||
var ctltlscfg *tls.Config
|
var config *hodu.ServerConfig
|
||||||
var rpctlscfg *tls.Config
|
|
||||||
var pxytlscfg *tls.Config
|
|
||||||
var wpxtlscfg *tls.Config
|
|
||||||
var ctl_prefix string
|
|
||||||
var logger *AppLogger
|
var logger *AppLogger
|
||||||
var log_mask hodu.LogMask
|
var logmask hodu.LogMask
|
||||||
var logfile string
|
|
||||||
var logfile_maxsize int64
|
var logfile_maxsize int64
|
||||||
var logfile_rotate int
|
var logfile_rotate int
|
||||||
var max_rpc_conns int
|
var pty_user string
|
||||||
var max_peers int
|
var pty_shell string
|
||||||
var xterm_html_file string
|
var xterm_html_file string
|
||||||
var xterm_html string
|
var xterm_html string
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
log_mask = hodu.LOG_ALL
|
logmask = hodu.LOG_ALL
|
||||||
|
|
||||||
if cfg != nil {
|
config = &hodu.ServerConfig{
|
||||||
ctltlscfg, err = make_tls_server_config(&cfg.CTL.TLS)
|
CtlAddrs: ctl_addrs,
|
||||||
if err != nil { return err }
|
RpcAddrs: rpc_addrs,
|
||||||
rpctlscfg, err = make_tls_server_config(&cfg.RPC.TLS)
|
RpxAddrs: rpx_addrs,
|
||||||
if err != nil { return err }
|
PxyAddrs: pxy_addrs,
|
||||||
pxytlscfg, err = make_tls_server_config(&cfg.PXY.TLS)
|
WpxAddrs: wpx_addrs,
|
||||||
if err != nil { return err }
|
|
||||||
wpxtlscfg, err = make_tls_server_config(&cfg.WPX.TLS)
|
|
||||||
if err != nil { return err }
|
|
||||||
|
|
||||||
if len(ctl_addrs) <= 0 { ctl_addrs = cfg.CTL.Service.Addrs }
|
|
||||||
if len(rpc_addrs) <= 0 { rpc_addrs = cfg.RPC.Service.Addrs }
|
|
||||||
if len(pxy_addrs) <= 0 { pxy_addrs = cfg.PXY.Service.Addrs }
|
|
||||||
if len(wpx_addrs) <= 0 { wpx_addrs = cfg.WPX.Service.Addrs }
|
|
||||||
|
|
||||||
ctl_prefix = cfg.CTL.Service.Prefix
|
|
||||||
log_mask = log_strings_to_mask(cfg.APP.LogMask)
|
|
||||||
logfile = cfg.APP.LogFile
|
|
||||||
logfile_maxsize = cfg.APP.LogMaxSize
|
|
||||||
logfile_rotate = cfg.APP.LogRotate
|
|
||||||
max_rpc_conns = cfg.APP.MaxRpcConns
|
|
||||||
max_peers = cfg.APP.MaxPeers
|
|
||||||
xterm_html_file = cfg.APP.XtermHtmlFile
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(rpc_addrs) <= 0 {
|
if cfg != nil {
|
||||||
|
config.CtlTls, err = make_tls_server_config(&cfg.CTL.TLS)
|
||||||
|
if err != nil { return err }
|
||||||
|
config.RpcTls, err = make_tls_server_config(&cfg.RPC.TLS)
|
||||||
|
if err != nil { return err }
|
||||||
|
config.RpxTls, err = make_tls_server_config(&cfg.RPX.TLS)
|
||||||
|
if err != nil { return err }
|
||||||
|
config.PxyTls, err = make_tls_server_config(&cfg.PXY.TLS)
|
||||||
|
if err != nil { return err }
|
||||||
|
config.PxyTargetTls, err = make_tls_client_config(&cfg.PXY.Target.TLS)
|
||||||
|
if err != nil { return err }
|
||||||
|
config.WpxTls, err = make_tls_server_config(&cfg.WPX.TLS)
|
||||||
|
if err != nil { return err }
|
||||||
|
|
||||||
|
if len(config.CtlAddrs) <= 0 { config.CtlAddrs = cfg.CTL.Service.Addrs }
|
||||||
|
if len(config.RpcAddrs) <= 0 { config.RpcAddrs = cfg.RPC.Service.Addrs }
|
||||||
|
if len(config.RpxAddrs) <= 0 { config.RpxAddrs = cfg.RPX.Service.Addrs }
|
||||||
|
if len(config.PxyAddrs) <= 0 { config.PxyAddrs = cfg.PXY.Service.Addrs }
|
||||||
|
if len(config.WpxAddrs) <= 0 { config.WpxAddrs = cfg.WPX.Service.Addrs }
|
||||||
|
|
||||||
|
config.RpxClientTokenAttrName = cfg.RPX.ClientToken.AttrName
|
||||||
|
if cfg.RPX.ClientToken.Regex != "" {
|
||||||
|
config.RpxClientTokenRegex, err = regexp.Compile(cfg.RPX.ClientToken.Regex)
|
||||||
|
if err != nil { return err }
|
||||||
|
}
|
||||||
|
config.RpxClientTokenSubmatchIndex = cfg.RPX.ClientToken.SubmatchIndex
|
||||||
|
|
||||||
|
config.CtlCors = cfg.CTL.Service.Cors
|
||||||
|
config.CtlAuth, err = make_http_auth_config(&cfg.CTL.Service.Auth)
|
||||||
|
if err != nil { return err }
|
||||||
|
|
||||||
|
config.CtlPrefix = cfg.CTL.Service.Prefix
|
||||||
|
config.RpcMaxConns = cfg.APP.MaxRpcConns
|
||||||
|
config.MaxPeers = cfg.APP.MaxPeers
|
||||||
|
|
||||||
|
pty_user = cfg.APP.PtyUser
|
||||||
|
pty_shell = cfg.APP.PtyShell
|
||||||
|
xterm_html_file = cfg.APP.XtermHtmlFile
|
||||||
|
|
||||||
|
logmask = log_strings_to_mask(cfg.APP.LogMask)
|
||||||
|
if logfile == "" { logfile = cfg.APP.LogFile }
|
||||||
|
logfile_maxsize = cfg.APP.LogMaxSize
|
||||||
|
logfile_rotate = cfg.APP.LogRotate
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(config.RpcAddrs) <= 0 {
|
||||||
return fmt.Errorf("no rpc service addresses specified")
|
return fmt.Errorf("no rpc service addresses specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
if logfile == "" {
|
if logfile == "" {
|
||||||
logger = NewAppLogger("server", os.Stderr, log_mask)
|
logger = NewAppLogger("server", os.Stderr, logmask)
|
||||||
} else {
|
} else {
|
||||||
logger, err = NewAppLoggerToFile("server", logfile, logfile_maxsize, logfile_rotate, log_mask)
|
logger, err = NewAppLoggerToFile("server", logfile, logfile_maxsize, logfile_rotate, logmask)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to initialize logger - %s", err.Error())
|
return fmt.Errorf("failed to initialize logger - %s", err.Error())
|
||||||
}
|
}
|
||||||
@ -156,28 +181,18 @@ func server_main(ctl_addrs []string, rpc_addrs []string, pxy_addrs []string, wpx
|
|||||||
xterm_html = string(tmp)
|
xterm_html = string(tmp)
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err = hodu.NewServer(
|
s, err = hodu.NewServer(context.Background(), HODU_NAME, logger, config)
|
||||||
context.Background(),
|
|
||||||
logger,
|
|
||||||
ctl_addrs,
|
|
||||||
rpc_addrs,
|
|
||||||
pxy_addrs,
|
|
||||||
wpx_addrs,
|
|
||||||
ctl_prefix,
|
|
||||||
ctltlscfg,
|
|
||||||
rpctlscfg,
|
|
||||||
pxytlscfg,
|
|
||||||
wpxtlscfg,
|
|
||||||
max_rpc_conns,
|
|
||||||
max_peers)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create new server - %s", err.Error())
|
return fmt.Errorf("failed to create server - %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pty_user != "" { s.SetPtyUser(pty_user) }
|
||||||
|
if pty_shell != "" { s.SetPtyShell(pty_shell) }
|
||||||
if xterm_html != "" { s.SetXtermHtml(xterm_html) }
|
if xterm_html != "" { s.SetXtermHtml(xterm_html) }
|
||||||
|
|
||||||
s.StartService(nil)
|
s.StartService(nil)
|
||||||
s.StartCtlService()
|
s.StartCtlService()
|
||||||
|
s.StartRpxService()
|
||||||
s.StartPxyService()
|
s.StartPxyService()
|
||||||
s.StartWpxService()
|
s.StartWpxService()
|
||||||
s.StartExtService(&signal_handler{svc:s}, nil)
|
s.StartExtService(&signal_handler{svc:s}, nil)
|
||||||
@ -248,50 +263,70 @@ func parse_client_route_config(v string) (*hodu.ClientRouteConfig, error) {
|
|||||||
ptc_name = strings.TrimSpace(va[3])
|
ptc_name = strings.TrimSpace(va[3])
|
||||||
}
|
}
|
||||||
|
|
||||||
return &hodu.ClientRouteConfig{PeerAddr: va[0], PeerName: ptc_name, Option: option, ServiceAddr: svc_addr}, nil // TODO: other fields
|
return &hodu.ClientRouteConfig{PeerAddr: va[0], PeerName: ptc_name, ServiceOption: option, ServiceAddr: svc_addr}, nil // TODO: other fields
|
||||||
}
|
}
|
||||||
|
|
||||||
func client_main(ctl_addrs []string, rpc_addrs []string, route_configs []string, cfg *ClientConfig) error {
|
func client_main(ctl_addrs []string, rpc_addrs []string, route_configs []string, logfile string, cfg *ClientConfig) error {
|
||||||
var c *hodu.Client
|
var c *hodu.Client
|
||||||
var ctltlscfg *tls.Config
|
var config *hodu.ClientConfig
|
||||||
var rpctlscfg *tls.Config
|
var cc hodu.ClientConnConfig
|
||||||
var ctl_prefix string
|
|
||||||
var cc hodu.ClientConfig
|
|
||||||
var logger *AppLogger
|
var logger *AppLogger
|
||||||
var log_mask hodu.LogMask
|
var logmask hodu.LogMask
|
||||||
var logfile string
|
|
||||||
var logfile_maxsize int64
|
var logfile_maxsize int64
|
||||||
var logfile_rotate int
|
var logfile_rotate int
|
||||||
var max_rpc_conns int
|
var pty_user string
|
||||||
var max_peers int
|
var pty_shell string
|
||||||
var peer_conn_tmout time.Duration
|
var xterm_html_file string
|
||||||
|
var xterm_html string
|
||||||
var i int
|
var i int
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
log_mask = hodu.LOG_ALL
|
logmask = hodu.LOG_ALL
|
||||||
if cfg != nil {
|
|
||||||
ctltlscfg, err = make_tls_server_config(&cfg.CTL.TLS)
|
config = &hodu.ClientConfig{
|
||||||
if err != nil {
|
CtlAddrs: ctl_addrs,
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
rpctlscfg, err = make_tls_client_config(&cfg.RPC.TLS)
|
if cfg != nil {
|
||||||
if err != nil {
|
config.CtlTls, err = make_tls_server_config(&cfg.CTL.TLS)
|
||||||
return err
|
if err != nil { return err }
|
||||||
}
|
config.RpcTls, err = make_tls_client_config(&cfg.RPC.TLS)
|
||||||
|
if err != nil { return err }
|
||||||
|
config.RpxTargetTls, err = make_tls_client_config(&cfg.RPX.Target.TLS)
|
||||||
|
if err != nil { return err }
|
||||||
|
|
||||||
if len(ctl_addrs) <= 0 { ctl_addrs = cfg.CTL.Service.Addrs }
|
|
||||||
if len(rpc_addrs) <= 0 { rpc_addrs = cfg.RPC.Endpoint.Addrs }
|
if len(rpc_addrs) <= 0 { rpc_addrs = cfg.RPC.Endpoint.Addrs }
|
||||||
ctl_prefix = cfg.CTL.Service.Prefix
|
if len(config.CtlAddrs) <= 0 { config.CtlAddrs = cfg.CTL.Service.Addrs }
|
||||||
|
|
||||||
|
config.RpxTargetAddr = cfg.RPX.Target.Addr
|
||||||
|
config.CtlPrefix = cfg.CTL.Service.Prefix
|
||||||
|
config.CtlCors = cfg.CTL.Service.Cors
|
||||||
|
config.CtlAuth, err = make_http_auth_config(&cfg.CTL.Service.Auth)
|
||||||
|
if err != nil { return err }
|
||||||
|
|
||||||
cc.ServerSeedTmout = cfg.RPC.Endpoint.SeedTmout
|
cc.ServerSeedTmout = cfg.RPC.Endpoint.SeedTmout
|
||||||
cc.ServerAuthority = cfg.RPC.Endpoint.Authority
|
cc.ServerAuthority = cfg.RPC.Endpoint.Authority
|
||||||
log_mask = log_strings_to_mask(cfg.APP.LogMask)
|
logmask = log_strings_to_mask(cfg.APP.LogMask)
|
||||||
logfile = cfg.APP.LogFile
|
if logfile == "" { logfile = cfg.APP.LogFile }
|
||||||
logfile_maxsize = cfg.APP.LogMaxSize
|
logfile_maxsize = cfg.APP.LogMaxSize
|
||||||
logfile_rotate = cfg.APP.LogRotate
|
logfile_rotate = cfg.APP.LogRotate
|
||||||
max_rpc_conns = cfg.APP.MaxRpcConns
|
pty_user = cfg.APP.PtyUser
|
||||||
max_peers = cfg.APP.MaxPeers
|
pty_shell = cfg.APP.PtyShell
|
||||||
peer_conn_tmout = cfg.APP.PeerConnTmout
|
xterm_html_file = cfg.APP.XtermHtmlFile
|
||||||
|
config.RpcConnMax = cfg.APP.MaxRpcConns
|
||||||
|
config.PeerConnMax = cfg.APP.MaxPeers
|
||||||
|
config.PeerConnTmout = cfg.APP.PeerConnTmout
|
||||||
|
|
||||||
|
if cfg.APP.TokenText != "" {
|
||||||
|
config.Token = cfg.APP.TokenText
|
||||||
|
} else if cfg.APP.TokenFile != "" {
|
||||||
|
var bytes []byte
|
||||||
|
bytes, err = os.ReadFile(cfg.APP.TokenFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read token file - %s", err.Error())
|
||||||
|
}
|
||||||
|
config.Token = string(bytes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// unlke the server, we allow the client to start with no rpc address.
|
// unlke the server, we allow the client to start with no rpc address.
|
||||||
@ -306,23 +341,28 @@ func client_main(ctl_addrs []string, rpc_addrs []string, route_configs []string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if logfile == "" {
|
if logfile == "" {
|
||||||
logger = NewAppLogger("server", os.Stderr, log_mask)
|
logger = NewAppLogger("client", os.Stderr, logmask)
|
||||||
} else {
|
} else {
|
||||||
logger, err = NewAppLoggerToFile("server", logfile, logfile_maxsize, logfile_rotate, log_mask)
|
logger, err = NewAppLoggerToFile("client", logfile, logfile_maxsize, logfile_rotate, logmask)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to initialize logger - %s", err.Error())
|
return fmt.Errorf("failed to initialize logger - %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c = hodu.NewClient(
|
|
||||||
context.Background(),
|
if xterm_html_file != "" {
|
||||||
logger,
|
var tmp []byte
|
||||||
ctl_addrs,
|
tmp, err = os.ReadFile(xterm_html_file)
|
||||||
ctl_prefix,
|
if err != nil {
|
||||||
ctltlscfg,
|
return fmt.Errorf("failed to read %s - %s", xterm_html_file, err.Error())
|
||||||
rpctlscfg,
|
}
|
||||||
max_rpc_conns,
|
xterm_html = string(tmp)
|
||||||
max_peers,
|
}
|
||||||
peer_conn_tmout)
|
|
||||||
|
c = hodu.NewClient(context.Background(), HODU_NAME, logger, config)
|
||||||
|
|
||||||
|
if pty_user != "" { c.SetPtyUser(pty_user) }
|
||||||
|
if pty_shell != "" { c.SetPtyShell(pty_shell) }
|
||||||
|
if xterm_html != "" { c.SetXtermHtml(xterm_html) }
|
||||||
|
|
||||||
c.StartService(&cc)
|
c.StartService(&cc)
|
||||||
c.StartCtlService() // control channel
|
c.StartCtlService() // control channel
|
||||||
@ -342,14 +382,17 @@ func main() {
|
|||||||
if strings.EqualFold(os.Args[1], "server") {
|
if strings.EqualFold(os.Args[1], "server") {
|
||||||
var rpc_addrs []string
|
var rpc_addrs []string
|
||||||
var ctl_addrs []string
|
var ctl_addrs []string
|
||||||
|
var rpx_addrs []string
|
||||||
var pxy_addrs []string
|
var pxy_addrs []string
|
||||||
var wpx_addrs []string
|
var wpx_addrs []string
|
||||||
var cfgfile string
|
var cfgfile string
|
||||||
|
var cfgpat string
|
||||||
var logfile string
|
var logfile string
|
||||||
var cfg *ServerConfig
|
var cfg ServerConfig
|
||||||
|
|
||||||
ctl_addrs = make([]string, 0)
|
ctl_addrs = make([]string, 0)
|
||||||
rpc_addrs = make([]string, 0)
|
rpc_addrs = make([]string, 0)
|
||||||
|
rpx_addrs = make([]string, 0)
|
||||||
pxy_addrs = make([]string, 0)
|
pxy_addrs = make([]string, 0)
|
||||||
wpx_addrs = make([]string, 0)
|
wpx_addrs = make([]string, 0)
|
||||||
|
|
||||||
@ -362,6 +405,10 @@ func main() {
|
|||||||
rpc_addrs = append(rpc_addrs, v)
|
rpc_addrs = append(rpc_addrs, v)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
flgs.Func("rpx-on", "specify a rpx listening address", func(v string) error {
|
||||||
|
rpx_addrs = append(rpx_addrs, v)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
flgs.Func("pxy-on", "specify a proxy listening address", func(v string) error {
|
flgs.Func("pxy-on", "specify a proxy listening address", func(v string) error {
|
||||||
pxy_addrs = append(pxy_addrs, v)
|
pxy_addrs = append(pxy_addrs, v)
|
||||||
return nil
|
return nil
|
||||||
@ -374,30 +421,50 @@ func main() {
|
|||||||
logfile = v
|
logfile = v
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
flgs.Func("config-file", "specify a configuration file path", func(v string) error {
|
flgs.Func("config-file", "specify a primary configuration file path", func(v string) error {
|
||||||
cfgfile = v
|
cfgfile = v
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
// TODO: add a command line option to specify log file and mask.
|
flgs.Func("config-file-pattern", "specify a file pattern for additional configuration files", func(v string) error {
|
||||||
|
cfgpat = v
|
||||||
|
return nil
|
||||||
|
})
|
||||||
flgs.SetOutput(io.Discard) // prevent usage output
|
flgs.SetOutput(io.Discard) // prevent usage output
|
||||||
err = flgs.Parse(os.Args[2:])
|
err = flgs.Parse(os.Args[2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf ("ERROR: %s\n", err.Error())
|
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
|
||||||
goto wrong_usage
|
goto wrong_usage
|
||||||
}
|
}
|
||||||
|
|
||||||
if flgs.NArg() > 0 { goto wrong_usage }
|
if flgs.NArg() > 0 { goto wrong_usage }
|
||||||
|
|
||||||
if cfgfile != "" {
|
if cfgfile != "" {
|
||||||
cfg, err = load_server_config(cfgfile)
|
err = load_server_config_to(cfgfile, &cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf ("ERROR: failed to load configuration file %s - %s\n", cfgfile, err.Error())
|
fmt.Fprintf(os.Stderr, "ERROR: failed to load configuration file %s - %s\n", cfgfile, err.Error())
|
||||||
goto oops
|
goto oops
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if logfile != "" { cfg.APP.LogFile = logfile }
|
if cfgpat != "" {
|
||||||
|
var file string
|
||||||
|
var matches []string
|
||||||
|
|
||||||
err = server_main(ctl_addrs, rpc_addrs, pxy_addrs, wpx_addrs, cfg)
|
matches, err = filepath.Glob(cfgpat)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "ERROR: failed to match the pattern %s - %s\n", cfgpat, err.Error())
|
||||||
|
goto oops
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file = range matches {
|
||||||
|
err = load_server_config_to(file, &cfg)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "ERROR: failed to load configuration file %s - %s\n", file, err.Error())
|
||||||
|
goto oops
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = server_main(ctl_addrs, rpc_addrs, rpx_addrs, pxy_addrs, wpx_addrs, logfile, &cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "ERROR: server error - %s\n", err.Error())
|
fmt.Fprintf(os.Stderr, "ERROR: server error - %s\n", err.Error())
|
||||||
goto oops
|
goto oops
|
||||||
@ -406,8 +473,9 @@ func main() {
|
|||||||
var rpc_addrs []string
|
var rpc_addrs []string
|
||||||
var ctl_addrs []string
|
var ctl_addrs []string
|
||||||
var cfgfile string
|
var cfgfile string
|
||||||
|
var cfgpat string
|
||||||
var logfile string
|
var logfile string
|
||||||
var cfg *ClientConfig
|
var cfg ClientConfig
|
||||||
|
|
||||||
ctl_addrs = make([]string, 0)
|
ctl_addrs = make([]string, 0)
|
||||||
rpc_addrs = make([]string, 0)
|
rpc_addrs = make([]string, 0)
|
||||||
@ -429,24 +497,44 @@ func main() {
|
|||||||
cfgfile = v
|
cfgfile = v
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
// TODO: add a command line option to specify log file and mask.
|
flgs.Func("config-file-pattern", "specify a file pattern for additional configuration files", func(v string) error {
|
||||||
|
cfgpat = v
|
||||||
|
return nil
|
||||||
|
})
|
||||||
flgs.SetOutput(io.Discard)
|
flgs.SetOutput(io.Discard)
|
||||||
err = flgs.Parse(os.Args[2:])
|
err = flgs.Parse(os.Args[2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf ("ERROR: %s\n", err.Error())
|
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
|
||||||
goto wrong_usage
|
goto wrong_usage
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfgfile != "" {
|
if cfgfile != "" {
|
||||||
cfg, err = load_client_config(cfgfile)
|
err = load_client_config_to(cfgfile, &cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf ("ERROR: failed to load configuration file %s - %s\n", cfgfile, err.Error())
|
fmt.Fprintf(os.Stderr, "ERROR: failed to load configuration file %s - %s\n", cfgfile, err.Error())
|
||||||
goto oops
|
goto oops
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if logfile != "" { cfg.APP.LogFile = logfile }
|
if cfgpat != "" {
|
||||||
|
var file string
|
||||||
|
var matches []string
|
||||||
|
|
||||||
err = client_main(ctl_addrs, rpc_addrs, flgs.Args(), cfg)
|
matches, err = filepath.Glob(cfgpat)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "ERROR: failed to match the pattern %s - %s\n", cfgpat, err.Error())
|
||||||
|
goto oops
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file = range matches {
|
||||||
|
err = load_client_config_to(file, &cfg)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "ERROR: failed to load configuration file %s - %s\n", file, err.Error())
|
||||||
|
goto oops
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client_main(ctl_addrs, rpc_addrs, flgs.Args(), logfile, &cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "ERROR: client error - %s\n", err.Error())
|
fmt.Fprintf(os.Stderr, "ERROR: client error - %s\n", err.Error())
|
||||||
goto oops
|
goto oops
|
||||||
@ -461,8 +549,8 @@ func main() {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
|
||||||
wrong_usage:
|
wrong_usage:
|
||||||
fmt.Fprintf(os.Stderr, "USAGE: %s server --rpc-on=addr:port --ctl-on=addr:port --pxy-on=addr:port --wpx-on=addr:port [--config-file=file]\n", os.Args[0])
|
fmt.Fprintf(os.Stderr, "USAGE: %s server --rpc-on=addr:port --ctl-on=addr:port --rpx-on=addr:port --pxy-on=addr:port --wpx-on=addr:port [--config-file=file] [--config-file-pattern=pattern]\n", os.Args[0])
|
||||||
fmt.Fprintf(os.Stderr, " %s client --rpc-to=addr:port --ctl-on=addr:port [--config-file=file] [peer-addr:peer-port ...]\n", os.Args[0])
|
fmt.Fprintf(os.Stderr, " %s client --rpc-to=addr:port --ctl-on=addr:port [--config-file=file] [--config-file-pattern=pattern] [peer-addr:peer-port ...]\n", os.Args[0])
|
||||||
fmt.Fprintf(os.Stderr, " %s version\n", os.Args[0])
|
fmt.Fprintf(os.Stderr, " %s version\n", os.Args[0])
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
||||||
|
27
cmd/rsa.key
Normal file
27
cmd/rsa.key
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEAsTC9roInjDzu12tjv1CsOM4jvuB6/5vv+cmOMF5GLMVTnJCW
|
||||||
|
6U9onsOi6iN2rzlf5glkjdtijXCPL6QEX3YLYPD4NFCiOGIPhCHjWC4nBjI7LEEm
|
||||||
|
0SqrArMhPiyYLmnkA961a7mDw9dcr5JQBDq2ZyTe917N229Jr4PCZbHLboOxNlp3
|
||||||
|
QLSyxE5tfKZea53qm8SUF8maBvnOH8igvuYOek3iRMg3T+GoxCqy2gE1qznvwsaK
|
||||||
|
PdmTTzbIbc7XNU7t5yT6fZTvjUqs4WBuHqud4unE//KAT5vfxDdQFGcb45oMwxcK
|
||||||
|
bf03w4ZsBNvAcgCkWW+ophEOZRPkKrluHjVdNwIDAQABAoIBAARZ/5aNEL6TcoQs
|
||||||
|
2X7F0uz0NxGFfs/POxYF2q2aaxvHXtXOAT7KmfWoNVSNuWj1PkMugN8w/5scpA+V
|
||||||
|
9huIESB42oeiYVGEKwBiOqycOY4f5q8gDH1/kEKZNpxJyRT+ucBUlF0IadGB9P9E
|
||||||
|
1x07eeZPlAA8Pk8AzSz3zerkcmwM2lYYG851QyuiiTReSec3LLDcJvG5xAXZrIY0
|
||||||
|
Zwm7qv8uvjJGqMVYlywMnRngeNywP9ZaOJ38vdmWMu4bBF+QwydOAB9A7O9zluDZ
|
||||||
|
wK7OBedAZkRT15luZ1lkuTrKVZEaugD8dbt6BBLuhbPRRGuFb4WoNaVI3CRu9RSX
|
||||||
|
72gYkRECgYEA9x0IAFGc8DmCHOP/S+uy0VjvLGYh4QN3/0UOLRvoREzF0FtAxqci
|
||||||
|
bPASGmSCJEDL93JNjlxhITDUUawyOGRgAAXyAkE9MWmv18+pfTNTDeoaeXsBqcLz
|
||||||
|
f9LCNc3mCx93tvCK7gfIYs8Ef0QKfdrsQwMGlutgXmjE+pexNXPFWEcCgYEAt4/8
|
||||||
|
gsXi7tsCQp1YiP7VFZjoXSLejq+7pQrGV58PzlZKiOH/M5S6YS8wgm5oIEMLq2UP
|
||||||
|
nUn+FBCJ/I2b6HIdVq/Jr77XHcBFSZZEQbXe2gxTTucj6BTja1kSEilOquaaPvbR
|
||||||
|
WEs0+50rsgH0nLqSbMZZRkxOAUu9nObFvHA6O5ECgYEAzzd8+id13suam/dkoZlo
|
||||||
|
PbzB8w1B45oxCdIybQk13/AxAONEklCcwZUe2RrnNtdPMpSbDIHSwS5dHI+1HSyu
|
||||||
|
g9Z4dgOW+NSTK/lrOx3Ky6Q/xxaq8lwULF/jk5KxESq2DKXxGmFUW+cU8lNwKNFn
|
||||||
|
xVnIMM335bMdWrXRV+1Y0wkCgYBbXYOl47Esij35wi+LIKwW7+DYWr7D7pxLba2D
|
||||||
|
d1x6q2C1+Sb5GZIbRU2z3hhd1oE8cjTvaSDaA9Fqr2FmtUX9G8obe7W+zTCvi+e1
|
||||||
|
fTzK80+T+mBY5+y6Rb9E4uKRFe64YEma1PQuOPDCzU5fpE21bpSI9PnukzBxpDvP
|
||||||
|
q1yQwQKBgQCXiW8UghuwIp3INFzBTedBHNKBwRd82ZIhBWLcgWxC/EyWsRRFpJj4
|
||||||
|
HlVRYOvi2Q3DV6+Yn8zg3OeBhudGfCRCTkENbzAalcWqr9qb3Q4y26tZZQ9yNKk1
|
||||||
|
jJ2OfVw4K/6L49iVNF/2kLdbRebQXwngQUmiZSai5MlrHOFYkkiwaA==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
15
go.mod
15
go.mod
@ -3,13 +3,24 @@ module hodu
|
|||||||
go 1.22.0
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/creack/pty v1.1.24
|
||||||
|
github.com/goccy/go-yaml v1.17.1
|
||||||
|
github.com/prometheus/client_golang v1.20.5
|
||||||
golang.org/x/crypto v0.26.0
|
golang.org/x/crypto v0.26.0
|
||||||
golang.org/x/net v0.28.0
|
golang.org/x/net v0.28.0
|
||||||
golang.org/x/sys v0.24.0
|
golang.org/x/sys v0.24.0
|
||||||
golang.org/x/text v0.21.0
|
golang.org/x/text v0.21.0
|
||||||
google.golang.org/grpc v1.67.1
|
google.golang.org/grpc v1.67.1
|
||||||
google.golang.org/protobuf v1.34.2
|
google.golang.org/protobuf v1.34.2
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
require (
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.9 // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
|
github.com/prometheus/common v0.55.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||||
|
)
|
||||||
|
26
go.sum
26
go.sum
@ -1,5 +1,27 @@
|
|||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||||
|
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||||
|
github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY=
|
||||||
|
github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||||
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
|
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||||
|
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||||
|
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||||
|
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||||
|
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||||
|
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||||
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||||
@ -16,7 +38,3 @@ google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
|||||||
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
|
362
hodu.go
362
hodu.go
@ -1,15 +1,20 @@
|
|||||||
package hodu
|
package hodu
|
||||||
|
|
||||||
|
import "bytes"
|
||||||
|
import "crypto/rsa"
|
||||||
|
import _ "embed"
|
||||||
|
import "encoding/base64"
|
||||||
|
import "fmt"
|
||||||
import "net"
|
import "net"
|
||||||
import "net/http"
|
import "net/http"
|
||||||
import "net/netip"
|
import "net/netip"
|
||||||
import "os"
|
import "os"
|
||||||
|
import "regexp"
|
||||||
import "runtime"
|
import "runtime"
|
||||||
import "strings"
|
import "strings"
|
||||||
import "sync"
|
import "sync"
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
const HODU_RPC_VERSION uint32 = 0x010000
|
const HODU_RPC_VERSION uint32 = 0x010000
|
||||||
|
|
||||||
type LogLevel int
|
type LogLevel int
|
||||||
@ -28,6 +33,10 @@ const LOG_NONE LogMask = LogMask(0)
|
|||||||
var IPV4_PREFIX_ZERO = netip.MustParsePrefix("0.0.0.0/0")
|
var IPV4_PREFIX_ZERO = netip.MustParsePrefix("0.0.0.0/0")
|
||||||
var IPV6_PREFIX_ZERO = netip.MustParsePrefix("::/0")
|
var IPV6_PREFIX_ZERO = netip.MustParsePrefix("::/0")
|
||||||
|
|
||||||
|
type Named struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
Write(id string, level LogLevel, fmtstr string, args ...interface{})
|
Write(id string, level LogLevel, fmtstr string, args ...interface{})
|
||||||
WriteWithCallDepth(id string, level LogLevel, call_depth int, fmtstr string, args ...interface{})
|
WriteWithCallDepth(id string, level LogLevel, call_depth int, fmtstr string, args ...interface{})
|
||||||
@ -44,6 +53,100 @@ type Service interface {
|
|||||||
WriteLog(id string, level LogLevel, fmtstr string, args ...interface{})
|
WriteLog(id string, level LogLevel, fmtstr string, args ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HttpAccessAction int
|
||||||
|
const (
|
||||||
|
HTTP_ACCESS_ACCEPT HttpAccessAction = iota
|
||||||
|
HTTP_ACCESS_REJECT
|
||||||
|
HTTP_ACCESS_AUTH_REQUIRED
|
||||||
|
)
|
||||||
|
|
||||||
|
type HttpAccessRule struct {
|
||||||
|
Prefix string
|
||||||
|
OrgNets []netip.Prefix
|
||||||
|
Action HttpAccessAction
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpAuthCredMap map[string]string
|
||||||
|
|
||||||
|
type HttpAuthConfig struct {
|
||||||
|
Enabled bool
|
||||||
|
Realm string
|
||||||
|
Creds HttpAuthCredMap
|
||||||
|
TokenTtl time.Duration
|
||||||
|
TokenRsaKey *rsa.PrivateKey
|
||||||
|
AccessRules []HttpAccessRule
|
||||||
|
}
|
||||||
|
|
||||||
|
type JsonErrmsg struct {
|
||||||
|
Text string `json:"error-text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type json_in_notice struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type json_out_go_stats struct {
|
||||||
|
CPUs int `json:"cpus"`
|
||||||
|
Goroutines int `json:"goroutines"`
|
||||||
|
NumCgoCalls int64 `json:"num-cgo-calls"`
|
||||||
|
NumGCs uint32 `json:"num-gcs"`
|
||||||
|
AllocBytes uint64 `json:"memory-alloc-bytes"`
|
||||||
|
TotalAllocBytes uint64 `json:"memory-total-alloc-bytes"`
|
||||||
|
SysBytes uint64 `json:"memory-sys-bytes"`
|
||||||
|
Lookups uint64 `json:"memory-lookups"`
|
||||||
|
MemAllocs uint64 `json:"memory-num-allocs"`
|
||||||
|
MemFrees uint64 `json:"memory-num-frees"`
|
||||||
|
|
||||||
|
HeapAllocBytes uint64 `json:"memory-heap-alloc-bytes"`
|
||||||
|
HeapSysBytes uint64 `json:"memory-heap-sys-bytes"`
|
||||||
|
HeapIdleBytes uint64 `json:"memory-heap-idle-bytes"`
|
||||||
|
HeapInuseBytes uint64 `json:"memory-heap-inuse-bytes"`
|
||||||
|
HeapReleasedBytes uint64 `json:"memory-heap-released-bytes"`
|
||||||
|
HeapObjects uint64 `json:"memory-heap-objects"`
|
||||||
|
StackInuseBytes uint64 `json:"memory-stack-inuse-bytes"`
|
||||||
|
StackSysBytes uint64 `json:"memory-stack-sys-bytes"`
|
||||||
|
MSpanInuseBytes uint64 `json:"memory-mspan-inuse-bytes"`
|
||||||
|
MSpanSysBytes uint64 `json:"memory-mspan-sys-bytes"`
|
||||||
|
MCacheInuseBytes uint64 `json:"memory-mcache-inuse-bytes"`
|
||||||
|
MCacheSysBytes uint64 `json:"memory-mcache-sys-bytes"`
|
||||||
|
BuckHashSysBytes uint64 `json:"memory-buck-hash-sys-bytes"`
|
||||||
|
GCSysBytes uint64 `json:"memory-gc-sys-bytes"`
|
||||||
|
OtherSysBytes uint64 `json:"memory-other-sys-bytes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type json_xterm_ws_event struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Data []string `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
//go:embed xterm.js
|
||||||
|
var xterm_js []byte
|
||||||
|
//go:embed xterm-addon-fit.js
|
||||||
|
var xterm_addon_fit_js []byte
|
||||||
|
//go:embed xterm.css
|
||||||
|
var xterm_css []byte
|
||||||
|
//go:embed xterm.html
|
||||||
|
var xterm_html string
|
||||||
|
|
||||||
|
type xterm_session_info struct {
|
||||||
|
Mode string
|
||||||
|
ConnId string
|
||||||
|
RouteId string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
func (n *Named) SetName(name string) {
|
||||||
|
n.name = name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Named) Name() string {
|
||||||
|
return n.name
|
||||||
|
}
|
||||||
|
|
||||||
func TcpAddrStrClass(addr string) string {
|
func TcpAddrStrClass(addr string) string {
|
||||||
// the string is supposed to be addr:port
|
// the string is supposed to be addr:port
|
||||||
|
|
||||||
@ -53,7 +156,7 @@ func TcpAddrStrClass(addr string) string {
|
|||||||
ap, err = netip.ParseAddrPort(addr)
|
ap, err = netip.ParseAddrPort(addr)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if ap.Addr().Is6() { return "tcp6" }
|
if ap.Addr().Is6() { return "tcp6" }
|
||||||
if ap.Addr().Is4() { return "tcp4" }
|
if ap.Addr().Is4() || ap.Addr().Is4In6() { return "tcp4" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +164,9 @@ func TcpAddrStrClass(addr string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TcpAddrClass(addr *net.TCPAddr) string {
|
func TcpAddrClass(addr *net.TCPAddr) string {
|
||||||
if addr.AddrPort().Addr().Is4() {
|
var netip_addr netip.Addr
|
||||||
|
netip_addr = addr.AddrPort().Addr()
|
||||||
|
if netip_addr.Is4() || netip_addr.Is4In6() {
|
||||||
return "tcp4"
|
return "tcp4"
|
||||||
} else {
|
} else {
|
||||||
return "tcp6"
|
return "tcp6"
|
||||||
@ -76,8 +181,6 @@ func word_to_route_option(word string) RouteOption {
|
|||||||
return RouteOption(ROUTE_OPTION_TCP6)
|
return RouteOption(ROUTE_OPTION_TCP6)
|
||||||
case "tcp":
|
case "tcp":
|
||||||
return RouteOption(ROUTE_OPTION_TCP)
|
return RouteOption(ROUTE_OPTION_TCP)
|
||||||
case "tty":
|
|
||||||
return RouteOption(ROUTE_OPTION_TTY)
|
|
||||||
case "http":
|
case "http":
|
||||||
return RouteOption(ROUTE_OPTION_HTTP)
|
return RouteOption(ROUTE_OPTION_HTTP)
|
||||||
case "https":
|
case "https":
|
||||||
@ -89,7 +192,7 @@ func word_to_route_option(word string) RouteOption {
|
|||||||
return RouteOption(ROUTE_OPTION_UNSPEC)
|
return RouteOption(ROUTE_OPTION_UNSPEC)
|
||||||
}
|
}
|
||||||
|
|
||||||
func string_to_route_option(desc string) RouteOption {
|
func StringToRouteOption(desc string) RouteOption {
|
||||||
var fld string
|
var fld string
|
||||||
var option RouteOption
|
var option RouteOption
|
||||||
var p RouteOption
|
var p RouteOption
|
||||||
@ -103,13 +206,12 @@ func string_to_route_option(desc string) RouteOption {
|
|||||||
return option
|
return option
|
||||||
}
|
}
|
||||||
|
|
||||||
func (option RouteOption) string() string {
|
func (option RouteOption) String() string {
|
||||||
var str string
|
var str string
|
||||||
str = ""
|
str = ""
|
||||||
if option & RouteOption(ROUTE_OPTION_TCP6) != 0 { str += " tcp6" }
|
if option & RouteOption(ROUTE_OPTION_TCP6) != 0 { str += " tcp6" }
|
||||||
if option & RouteOption(ROUTE_OPTION_TCP4) != 0 { str += " tcp4" }
|
if option & RouteOption(ROUTE_OPTION_TCP4) != 0 { str += " tcp4" }
|
||||||
if option & RouteOption(ROUTE_OPTION_TCP) != 0 { str += " tcp" }
|
if option & RouteOption(ROUTE_OPTION_TCP) != 0 { str += " tcp" }
|
||||||
if option & RouteOption(ROUTE_OPTION_TTY) != 0 { str += " tty" }
|
|
||||||
if option & RouteOption(ROUTE_OPTION_HTTP) != 0 { str += " http" }
|
if option & RouteOption(ROUTE_OPTION_HTTP) != 0 { str += " http" }
|
||||||
if option & RouteOption(ROUTE_OPTION_HTTPS) != 0 { str += " https" }
|
if option & RouteOption(ROUTE_OPTION_HTTPS) != 0 { str += " https" }
|
||||||
if option & RouteOption(ROUTE_OPTION_SSH) != 0 { str += " ssh" }
|
if option & RouteOption(ROUTE_OPTION_SSH) != 0 { str += " ssh" }
|
||||||
@ -119,8 +221,9 @@ func (option RouteOption) string() string {
|
|||||||
|
|
||||||
func dump_call_frame_and_exit(log Logger, req *http.Request, err interface{}) {
|
func dump_call_frame_and_exit(log Logger, req *http.Request, err interface{}) {
|
||||||
var buf []byte
|
var buf []byte
|
||||||
buf = make([]byte, 65536); buf = buf[:min(65536, runtime.Stack(buf, false))]
|
buf = make([]byte, 65536)
|
||||||
log.Write("", LOG_ERROR, "[%s] %s %s - %v\n%s", req.RemoteAddr, req.Method, req.URL.String(), err, string(buf))
|
buf = buf[:min(65536, runtime.Stack(buf, false))]
|
||||||
|
log.Write("", LOG_ERROR, "[%s] %s %s - %v\n%s", req.RemoteAddr, req.Method, req.RequestURI, err, string(buf))
|
||||||
log.Close()
|
log.Close()
|
||||||
os.Exit(99) // fatal error. treat panic() as a fatal runtime error
|
os.Exit(99) // fatal error. treat panic() as a fatal runtime error
|
||||||
}
|
}
|
||||||
@ -153,7 +256,7 @@ func get_last_rune_of_non_empty_string(s string) rune {
|
|||||||
return tmp[len(tmp) - 1]
|
return tmp[len(tmp) - 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func parse_duration_string(dur string) (time.Duration, error) {
|
func ParseDurationString(dur string) (time.Duration, error) {
|
||||||
// i want the input to be in seconds with resolution of 9 digits after
|
// i want the input to be in seconds with resolution of 9 digits after
|
||||||
// the decimal point. For example, 0.05 to mean 500ms.
|
// the decimal point. For example, 0.05 to mean 500ms.
|
||||||
// however, i don't care if a unit is part of the input.
|
// however, i don't care if a unit is part of the input.
|
||||||
@ -166,13 +269,19 @@ func parse_duration_string(dur string) (time.Duration, error) {
|
|||||||
return time.ParseDuration(tmp)
|
return time.ParseDuration(tmp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DurationToSecString(d time.Duration) string {
|
||||||
|
return fmt.Sprintf("%.09f", d.Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
|
||||||
func WriteJsonRespHeader(w http.ResponseWriter, status_code int) int {
|
func WriteJsonRespHeader(w http.ResponseWriter, status_code int) int {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(status_code)
|
w.WriteHeader(status_code)
|
||||||
return status_code
|
return status_code
|
||||||
}
|
}
|
||||||
|
|
||||||
func write_js_resp_header(w http.ResponseWriter, status_code int) int {
|
func WriteJsRespHeader(w http.ResponseWriter, status_code int) int {
|
||||||
w.Header().Set("Content-Type", "application/javascript")
|
w.Header().Set("Content-Type", "application/javascript")
|
||||||
w.WriteHeader(status_code)
|
w.WriteHeader(status_code)
|
||||||
return status_code
|
return status_code
|
||||||
@ -195,6 +304,8 @@ func WriteEmptyRespHeader(w http.ResponseWriter, status_code int) int {
|
|||||||
return status_code
|
return status_code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
|
||||||
func server_route_to_proxy_info(r *ServerRoute) *ServerRouteProxyInfo {
|
func server_route_to_proxy_info(r *ServerRoute) *ServerRouteProxyInfo {
|
||||||
return &ServerRouteProxyInfo{
|
return &ServerRouteProxyInfo{
|
||||||
SvcOption: r.SvcOption,
|
SvcOption: r.SvcOption,
|
||||||
@ -214,3 +325,230 @@ func proxy_info_to_server_route(pi *ServerRouteProxyInfo) *ServerRoute {
|
|||||||
SvcPermNet: pi.SvcPermNet,
|
SvcPermNet: pi.SvcPermNet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
|
||||||
|
func (stats *json_out_go_stats) from_runtime_stats() {
|
||||||
|
var mstat runtime.MemStats
|
||||||
|
|
||||||
|
runtime.ReadMemStats(&mstat)
|
||||||
|
|
||||||
|
stats.CPUs = runtime.NumCPU()
|
||||||
|
stats.Goroutines = runtime.NumGoroutine()
|
||||||
|
stats.NumCgoCalls = runtime.NumCgoCall()
|
||||||
|
stats.NumGCs = mstat.NumGC
|
||||||
|
|
||||||
|
stats.AllocBytes = mstat.Alloc
|
||||||
|
stats.TotalAllocBytes = mstat.TotalAlloc
|
||||||
|
stats.SysBytes = mstat.Sys
|
||||||
|
stats.Lookups = mstat.Lookups
|
||||||
|
stats.MemAllocs = mstat.Mallocs
|
||||||
|
stats.MemFrees = mstat.Frees
|
||||||
|
|
||||||
|
stats.HeapAllocBytes = mstat.HeapAlloc
|
||||||
|
stats.HeapSysBytes = mstat.HeapSys
|
||||||
|
stats.HeapIdleBytes = mstat.HeapIdle
|
||||||
|
stats.HeapInuseBytes = mstat.HeapInuse
|
||||||
|
stats.HeapReleasedBytes = mstat.HeapReleased
|
||||||
|
stats.HeapObjects = mstat.HeapObjects
|
||||||
|
stats.StackInuseBytes = mstat.StackInuse
|
||||||
|
stats.StackSysBytes = mstat.StackSys
|
||||||
|
stats.MSpanInuseBytes = mstat.MSpanInuse
|
||||||
|
stats.MSpanSysBytes = mstat.MSpanSys
|
||||||
|
stats.MCacheInuseBytes = mstat.MCacheInuse
|
||||||
|
stats.MCacheSysBytes = mstat.MCacheSys
|
||||||
|
stats.BuckHashSysBytes = mstat.BuckHashSys
|
||||||
|
stats.GCSysBytes = mstat.GCSys
|
||||||
|
stats.OtherSysBytes = mstat.OtherSys
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
|
||||||
|
func (auth *HttpAuthConfig) Authenticate(req *http.Request) (int, string) {
|
||||||
|
var rule HttpAccessRule
|
||||||
|
var raddrport netip.AddrPort
|
||||||
|
var raddr netip.Addr
|
||||||
|
var err error
|
||||||
|
|
||||||
|
raddrport, err = netip.ParseAddrPort(req.RemoteAddr)
|
||||||
|
if err == nil { raddr = raddrport.Addr() }
|
||||||
|
|
||||||
|
for _, rule = range auth.AccessRules {
|
||||||
|
// i don't take into account X-Forwarded-For and similar headers
|
||||||
|
var pfxd string = rule.Prefix
|
||||||
|
if pfxd[len(pfxd) -1] != '/' { pfxd = pfxd + "/" }
|
||||||
|
if req.URL.Path == rule.Prefix || strings.HasPrefix(req.URL.Path, pfxd) {
|
||||||
|
var org_net_ok bool
|
||||||
|
|
||||||
|
if len(rule.OrgNets) > 0 && raddr.IsValid() {
|
||||||
|
var netpfx netip.Prefix
|
||||||
|
|
||||||
|
org_net_ok = false
|
||||||
|
for _, netpfx = range rule.OrgNets {
|
||||||
|
if err == nil && netpfx.Contains(raddr) {
|
||||||
|
org_net_ok = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
org_net_ok = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if org_net_ok {
|
||||||
|
if rule.Action == HTTP_ACCESS_ACCEPT {
|
||||||
|
return http.StatusOK, ""
|
||||||
|
} else if rule.Action == HTTP_ACCESS_REJECT {
|
||||||
|
return http.StatusForbidden, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP_ACCESS_AUTH_REQUIRED.
|
||||||
|
// move on to authentication if enabled. acceped if disabled
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if auth != nil && auth.Enabled {
|
||||||
|
var auth_hdr string
|
||||||
|
var username string
|
||||||
|
var password string
|
||||||
|
var credpass string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
auth_hdr = req.Header.Get("Authorization")
|
||||||
|
if auth_hdr != "" {
|
||||||
|
var auth_parts []string
|
||||||
|
|
||||||
|
auth_parts = strings.Fields(auth_hdr)
|
||||||
|
if len(auth_parts) == 2 && strings.EqualFold(auth_parts[0], "Bearer") && auth.TokenRsaKey != nil {
|
||||||
|
var jwt *JWT[ServerTokenClaim]
|
||||||
|
var claim ServerTokenClaim
|
||||||
|
jwt = NewJWT(auth.TokenRsaKey, &claim)
|
||||||
|
err = jwt.VerifyRS512(strings.TrimSpace(auth_parts[1]))
|
||||||
|
if err == nil {
|
||||||
|
// verification ok. let's check the actual payload
|
||||||
|
var now time.Time
|
||||||
|
now = time.Now()
|
||||||
|
if now.After(time.Unix(claim.IssuedAt, 0)) && now.Before(time.Unix(claim.ExpiresAt, 0)) { return http.StatusOK, "" } // not expired
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this application wants these two header values to be base64-encoded
|
||||||
|
username = req.Header.Get("X-Auth-Username")
|
||||||
|
password = req.Header.Get("X-Auth-Password")
|
||||||
|
if username != "" {
|
||||||
|
var tmp []byte
|
||||||
|
tmp, err = base64.StdEncoding.DecodeString(username)
|
||||||
|
if err != nil { return http.StatusBadRequest, "" }
|
||||||
|
username = string(tmp)
|
||||||
|
}
|
||||||
|
if password != "" {
|
||||||
|
var tmp []byte
|
||||||
|
tmp, err = base64.StdEncoding.DecodeString(password)
|
||||||
|
if err != nil { return http.StatusBadRequest, "" }
|
||||||
|
password = string(tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fall back to basic authentication
|
||||||
|
if username == "" && password == "" && auth.Realm != "" {
|
||||||
|
username, password, ok = req.BasicAuth()
|
||||||
|
if !ok { return http.StatusUnauthorized, auth.Realm }
|
||||||
|
}
|
||||||
|
|
||||||
|
credpass, ok = auth.Creds[username]
|
||||||
|
if !ok || credpass != password {
|
||||||
|
return http.StatusUnauthorized, auth.Realm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.StatusOK, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
|
||||||
|
func get_http_req_line_and_headers(r *http.Request, force_host bool) []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var name string
|
||||||
|
var value string
|
||||||
|
var values []string
|
||||||
|
var host_found bool
|
||||||
|
var x_forwarded_host_found bool
|
||||||
|
var x_forwarded_proto_found bool
|
||||||
|
|
||||||
|
fmt.Fprintf(&buf, "%s %s %s\r\n", r.Method, r.RequestURI, r.Proto)
|
||||||
|
|
||||||
|
for name, values = range r.Header {
|
||||||
|
if strings.EqualFold(name, "Accept-Encoding") { // TODO: make it generic. parameterize it??
|
||||||
|
// skip Accept-Encoding as the go client side
|
||||||
|
// doesn't function properly when a certain enconding
|
||||||
|
// is specified. resp.Body.Read() returned EOF when
|
||||||
|
// not working
|
||||||
|
continue
|
||||||
|
} else if strings.EqualFold(name, "Host") {
|
||||||
|
host_found = true
|
||||||
|
} else if strings.EqualFold(name, "X-Forwarded-Host") {
|
||||||
|
x_forwarded_host_found = true
|
||||||
|
} else if strings.EqualFold(name, "X-Forwarded-Proto") {
|
||||||
|
x_forwarded_proto_found = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, value = range values {
|
||||||
|
fmt.Fprintf(&buf, "%s: %s\r\n", name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if force_host && !host_found && r.Host != "" {
|
||||||
|
fmt.Fprintf(&buf, "Host: %s\r\n", r.Host)
|
||||||
|
}
|
||||||
|
if !x_forwarded_host_found && r.Host != "" {
|
||||||
|
fmt.Fprintf(&buf, "X-Forwarded-Host: %s\r\n", r.Host)
|
||||||
|
}
|
||||||
|
if !x_forwarded_proto_found && r.Host != "" {
|
||||||
|
var proto string
|
||||||
|
if r.TLS != nil { proto = "https" } else { proto = "http" }
|
||||||
|
fmt.Fprintf(&buf, "X-Forwarded-Proto: %s\r\n", proto)
|
||||||
|
}
|
||||||
|
// TODO: host and x-forwarded-for, etc???
|
||||||
|
|
||||||
|
buf.WriteString("\r\n") // End of headers
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func get_http_resp_line_and_headers(r *http.Response) []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var name string
|
||||||
|
var value string
|
||||||
|
var values []string
|
||||||
|
|
||||||
|
fmt.Fprintf(&buf, "%s %s\r\n", r.Proto, r.Status)
|
||||||
|
|
||||||
|
for name, values = range r.Header {
|
||||||
|
for _, value = range values {
|
||||||
|
fmt.Fprintf(&buf, "%s: %s\r\n", name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString("\r\n") // End of headers
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func get_regex_submatch(re *regexp.Regexp, str string, n int) string {
|
||||||
|
var idxs []int
|
||||||
|
var pos int
|
||||||
|
var start int
|
||||||
|
var end int
|
||||||
|
|
||||||
|
idxs = re.FindStringSubmatchIndex(str)
|
||||||
|
if idxs == nil { return "" }
|
||||||
|
|
||||||
|
pos = n * 2
|
||||||
|
if pos + 1 >= len(idxs) { return "" }
|
||||||
|
|
||||||
|
start, end = idxs[pos], idxs[pos + 1]
|
||||||
|
if start == -1 || end == -1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return str[start:end]
|
||||||
|
}
|
725
hodu.pb.go
725
hodu.pb.go
@ -1,6 +1,6 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.35.1
|
// protoc-gen-go v1.36.7
|
||||||
// protoc v3.19.6
|
// protoc v3.19.6
|
||||||
// source: hodu.proto
|
// source: hodu.proto
|
||||||
|
|
||||||
@ -11,6 +11,7 @@ import (
|
|||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
sync "sync"
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -27,10 +28,9 @@ const (
|
|||||||
ROUTE_OPTION_TCP ROUTE_OPTION = 1
|
ROUTE_OPTION_TCP ROUTE_OPTION = 1
|
||||||
ROUTE_OPTION_TCP4 ROUTE_OPTION = 2
|
ROUTE_OPTION_TCP4 ROUTE_OPTION = 2
|
||||||
ROUTE_OPTION_TCP6 ROUTE_OPTION = 4
|
ROUTE_OPTION_TCP6 ROUTE_OPTION = 4
|
||||||
ROUTE_OPTION_TTY ROUTE_OPTION = 8
|
ROUTE_OPTION_HTTP ROUTE_OPTION = 8
|
||||||
ROUTE_OPTION_HTTP ROUTE_OPTION = 16
|
ROUTE_OPTION_HTTPS ROUTE_OPTION = 16
|
||||||
ROUTE_OPTION_HTTPS ROUTE_OPTION = 32
|
ROUTE_OPTION_SSH ROUTE_OPTION = 32
|
||||||
ROUTE_OPTION_SSH ROUTE_OPTION = 64
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enum value maps for ROUTE_OPTION.
|
// Enum value maps for ROUTE_OPTION.
|
||||||
@ -40,20 +40,18 @@ var (
|
|||||||
1: "TCP",
|
1: "TCP",
|
||||||
2: "TCP4",
|
2: "TCP4",
|
||||||
4: "TCP6",
|
4: "TCP6",
|
||||||
8: "TTY",
|
8: "HTTP",
|
||||||
16: "HTTP",
|
16: "HTTPS",
|
||||||
32: "HTTPS",
|
32: "SSH",
|
||||||
64: "SSH",
|
|
||||||
}
|
}
|
||||||
ROUTE_OPTION_value = map[string]int32{
|
ROUTE_OPTION_value = map[string]int32{
|
||||||
"UNSPEC": 0,
|
"UNSPEC": 0,
|
||||||
"TCP": 1,
|
"TCP": 1,
|
||||||
"TCP4": 2,
|
"TCP4": 2,
|
||||||
"TCP6": 4,
|
"TCP6": 4,
|
||||||
"TTY": 8,
|
"HTTP": 8,
|
||||||
"HTTP": 16,
|
"HTTPS": 16,
|
||||||
"HTTPS": 32,
|
"SSH": 32,
|
||||||
"SSH": 64,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -97,21 +95,43 @@ const (
|
|||||||
PACKET_KIND_PEER_ABORTED PACKET_KIND = 7
|
PACKET_KIND_PEER_ABORTED PACKET_KIND = 7
|
||||||
PACKET_KIND_PEER_EOF PACKET_KIND = 8
|
PACKET_KIND_PEER_EOF PACKET_KIND = 8
|
||||||
PACKET_KIND_PEER_DATA PACKET_KIND = 9
|
PACKET_KIND_PEER_DATA PACKET_KIND = 9
|
||||||
|
PACKET_KIND_CONN_DESC PACKET_KIND = 11
|
||||||
|
PACKET_KIND_CONN_ERROR PACKET_KIND = 12
|
||||||
|
PACKET_KIND_CONN_NOTICE PACKET_KIND = 13
|
||||||
|
PACKET_KIND_RPTY_START PACKET_KIND = 14
|
||||||
|
PACKET_KIND_RPTY_STOP PACKET_KIND = 15
|
||||||
|
PACKET_KIND_RPTY_DATA PACKET_KIND = 16
|
||||||
|
PACKET_KIND_RPTY_SIZE PACKET_KIND = 17 // terminal size
|
||||||
|
PACKET_KIND_RPX_START PACKET_KIND = 18
|
||||||
|
PACKET_KIND_RPX_STOP PACKET_KIND = 19
|
||||||
|
PACKET_KIND_RPX_DATA PACKET_KIND = 20
|
||||||
|
PACKET_KIND_RPX_EOF PACKET_KIND = 21
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enum value maps for PACKET_KIND.
|
// Enum value maps for PACKET_KIND.
|
||||||
var (
|
var (
|
||||||
PACKET_KIND_name = map[int32]string{
|
PACKET_KIND_name = map[int32]string{
|
||||||
0: "RESERVED",
|
0: "RESERVED",
|
||||||
1: "ROUTE_START",
|
1: "ROUTE_START",
|
||||||
2: "ROUTE_STOP",
|
2: "ROUTE_STOP",
|
||||||
3: "ROUTE_STARTED",
|
3: "ROUTE_STARTED",
|
||||||
4: "ROUTE_STOPPED",
|
4: "ROUTE_STOPPED",
|
||||||
5: "PEER_STARTED",
|
5: "PEER_STARTED",
|
||||||
6: "PEER_STOPPED",
|
6: "PEER_STOPPED",
|
||||||
7: "PEER_ABORTED",
|
7: "PEER_ABORTED",
|
||||||
8: "PEER_EOF",
|
8: "PEER_EOF",
|
||||||
9: "PEER_DATA",
|
9: "PEER_DATA",
|
||||||
|
11: "CONN_DESC",
|
||||||
|
12: "CONN_ERROR",
|
||||||
|
13: "CONN_NOTICE",
|
||||||
|
14: "RPTY_START",
|
||||||
|
15: "RPTY_STOP",
|
||||||
|
16: "RPTY_DATA",
|
||||||
|
17: "RPTY_SIZE",
|
||||||
|
18: "RPX_START",
|
||||||
|
19: "RPX_STOP",
|
||||||
|
20: "RPX_DATA",
|
||||||
|
21: "RPX_EOF",
|
||||||
}
|
}
|
||||||
PACKET_KIND_value = map[string]int32{
|
PACKET_KIND_value = map[string]int32{
|
||||||
"RESERVED": 0,
|
"RESERVED": 0,
|
||||||
@ -124,6 +144,17 @@ var (
|
|||||||
"PEER_ABORTED": 7,
|
"PEER_ABORTED": 7,
|
||||||
"PEER_EOF": 8,
|
"PEER_EOF": 8,
|
||||||
"PEER_DATA": 9,
|
"PEER_DATA": 9,
|
||||||
|
"CONN_DESC": 11,
|
||||||
|
"CONN_ERROR": 12,
|
||||||
|
"CONN_NOTICE": 13,
|
||||||
|
"RPTY_START": 14,
|
||||||
|
"RPTY_STOP": 15,
|
||||||
|
"RPTY_DATA": 16,
|
||||||
|
"RPTY_SIZE": 17,
|
||||||
|
"RPX_START": 18,
|
||||||
|
"RPX_STOP": 19,
|
||||||
|
"RPX_DATA": 20,
|
||||||
|
"RPX_EOF": 21,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -155,12 +186,11 @@ func (PACKET_KIND) EnumDescriptor() ([]byte, []int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Seed struct {
|
type Seed struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Version uint32 `protobuf:"varint,1,opt,name=Version,proto3" json:"Version,omitempty"`
|
||||||
|
Flags uint64 `protobuf:"varint,2,opt,name=Flags,proto3" json:"Flags,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
Version uint32 `protobuf:"varint,1,opt,name=Version,proto3" json:"Version,omitempty"`
|
|
||||||
Flags uint64 `protobuf:"varint,2,opt,name=Flags,proto3" json:"Flags,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Seed) Reset() {
|
func (x *Seed) Reset() {
|
||||||
@ -208,30 +238,29 @@ func (x *Seed) GetFlags() uint64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RouteDesc struct {
|
type RouteDesc struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
RouteId uint32 `protobuf:"varint,1,opt,name=RouteId,proto3" json:"RouteId,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
// C->S(ROUTE_START/STOP): client-side peer address
|
||||||
|
// S->C(ROUTE_STARTED/STOPPED): server-side listening address
|
||||||
RouteId uint32 `protobuf:"varint,1,opt,name=RouteId,proto3" json:"RouteId,omitempty"`
|
|
||||||
// C->S(ROUTE_START): client-side peer address
|
|
||||||
// S->C(ROUTE_STARTED): server-side listening address
|
|
||||||
TargetAddrStr string `protobuf:"bytes,2,opt,name=TargetAddrStr,proto3" json:"TargetAddrStr,omitempty"`
|
TargetAddrStr string `protobuf:"bytes,2,opt,name=TargetAddrStr,proto3" json:"TargetAddrStr,omitempty"`
|
||||||
// C->S(ROUTE_START): human-readable name of client-side peer
|
// C->S(ROUTE_START/STOPPED): human-readable name of client-side peer
|
||||||
// S->C(ROUTE_STARTED): clone as sent by C
|
// S->C(ROUTE_STARTED/STOPPED): clone as sent by C
|
||||||
TargetName string `protobuf:"bytes,3,opt,name=TargetName,proto3" json:"TargetName,omitempty"`
|
TargetName string `protobuf:"bytes,3,opt,name=TargetName,proto3" json:"TargetName,omitempty"`
|
||||||
// C->S(ROUTE_START): desired listening option on the server-side(e.g. tcp, tcp4, tcp6) +
|
// C->S(ROUTE_START): requested listening option on the server-side(e.g. tcp, tcp4, tcp6) +
|
||||||
//
|
//
|
||||||
// hint to the service-side peer(e.g. local) +
|
// hint to the service-side peer(e.g. local) +
|
||||||
// hint to the client-side peer(e.g. tty, http, https)
|
// hint to the client-side peer(e.g. tty, http, https)
|
||||||
//
|
//
|
||||||
// S->C(ROUTE_STARTED): cloned as sent by C.
|
// S->C(ROUTE_STARTED): cloned as sent by C.
|
||||||
ServiceOption uint32 `protobuf:"varint,4,opt,name=ServiceOption,proto3" json:"ServiceOption,omitempty"`
|
ServiceOption uint32 `protobuf:"varint,4,opt,name=ServiceOption,proto3" json:"ServiceOption,omitempty"`
|
||||||
// C->S(ROUTE_START): desired lisening address on the service-side
|
// C->S(ROUTE_START): requested lisening address on the service-side
|
||||||
// S->C(ROUTE_STARTED): cloned as sent by C
|
// S->C(ROUTE_STARTED): cloned as sent by C
|
||||||
ServiceAddrStr string `protobuf:"bytes,5,opt,name=ServiceAddrStr,proto3" json:"ServiceAddrStr,omitempty"`
|
ServiceAddrStr string `protobuf:"bytes,5,opt,name=ServiceAddrStr,proto3" json:"ServiceAddrStr,omitempty"`
|
||||||
// C->S(ROUTE_START): permitted network of server-side peers.
|
// C->S(ROUTE_START): requested permitted network of server-side peers.
|
||||||
// S->C(ROUTE_STARTED): cloned as sent by C.
|
// S->C(ROUTE_STARTED): actual permitted network of server-side peers
|
||||||
ServiceNetStr string `protobuf:"bytes,6,opt,name=ServiceNetStr,proto3" json:"ServiceNetStr,omitempty"`
|
ServiceNetStr string `protobuf:"bytes,6,opt,name=ServiceNetStr,proto3" json:"ServiceNetStr,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RouteDesc) Reset() {
|
func (x *RouteDesc) Reset() {
|
||||||
@ -307,14 +336,13 @@ func (x *RouteDesc) GetServiceNetStr() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PeerDesc struct {
|
type PeerDesc struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
RouteId uint32 `protobuf:"varint,1,opt,name=RouteId,proto3" json:"RouteId,omitempty"`
|
||||||
|
PeerId uint32 `protobuf:"varint,2,opt,name=PeerId,proto3" json:"PeerId,omitempty"`
|
||||||
|
RemoteAddrStr string `protobuf:"bytes,3,opt,name=RemoteAddrStr,proto3" json:"RemoteAddrStr,omitempty"`
|
||||||
|
LocalAddrStr string `protobuf:"bytes,4,opt,name=LocalAddrStr,proto3" json:"LocalAddrStr,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
RouteId uint32 `protobuf:"varint,1,opt,name=RouteId,proto3" json:"RouteId,omitempty"`
|
|
||||||
PeerId uint32 `protobuf:"varint,2,opt,name=PeerId,proto3" json:"PeerId,omitempty"`
|
|
||||||
RemoteAddrStr string `protobuf:"bytes,3,opt,name=RemoteAddrStr,proto3" json:"RemoteAddrStr,omitempty"`
|
|
||||||
LocalAddrStr string `protobuf:"bytes,4,opt,name=LocalAddrStr,proto3" json:"LocalAddrStr,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *PeerDesc) Reset() {
|
func (x *PeerDesc) Reset() {
|
||||||
@ -376,13 +404,12 @@ func (x *PeerDesc) GetLocalAddrStr() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PeerData struct {
|
type PeerData struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
RouteId uint32 `protobuf:"varint,1,opt,name=RouteId,proto3" json:"RouteId,omitempty"`
|
||||||
|
PeerId uint32 `protobuf:"varint,2,opt,name=PeerId,proto3" json:"PeerId,omitempty"`
|
||||||
|
Data []byte `protobuf:"bytes,3,opt,name=Data,proto3" json:"Data,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
RouteId uint32 `protobuf:"varint,1,opt,name=RouteId,proto3" json:"RouteId,omitempty"`
|
|
||||||
PeerId uint32 `protobuf:"varint,2,opt,name=PeerId,proto3" json:"PeerId,omitempty"`
|
|
||||||
Data []byte `protobuf:"bytes,3,opt,name=Data,proto3" json:"Data,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *PeerData) Reset() {
|
func (x *PeerData) Reset() {
|
||||||
@ -436,23 +463,271 @@ func (x *PeerData) GetData() []byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Packet struct {
|
type ConnDesc struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Token string `protobuf:"bytes,1,opt,name=Token,proto3" json:"Token,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
Kind PACKET_KIND `protobuf:"varint,1,opt,name=Kind,proto3,enum=PACKET_KIND" json:"Kind,omitempty"`
|
func (x *ConnDesc) Reset() {
|
||||||
// Types that are assignable to U:
|
*x = ConnDesc{}
|
||||||
|
mi := &file_hodu_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ConnDesc) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ConnDesc) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ConnDesc) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_hodu_proto_msgTypes[4]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnDesc.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ConnDesc) Descriptor() ([]byte, []int) {
|
||||||
|
return file_hodu_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ConnDesc) GetToken() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Token
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnError struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
ErrorId uint32 `protobuf:"varint,1,opt,name=ErrorId,proto3" json:"ErrorId,omitempty"`
|
||||||
|
Text string `protobuf:"bytes,2,opt,name=Text,proto3" json:"Text,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ConnError) Reset() {
|
||||||
|
*x = ConnError{}
|
||||||
|
mi := &file_hodu_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ConnError) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ConnError) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ConnError) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_hodu_proto_msgTypes[5]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnError.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ConnError) Descriptor() ([]byte, []int) {
|
||||||
|
return file_hodu_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ConnError) GetErrorId() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.ErrorId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ConnError) GetText() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Text
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnNotice struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Text string `protobuf:"bytes,1,opt,name=Text,proto3" json:"Text,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ConnNotice) Reset() {
|
||||||
|
*x = ConnNotice{}
|
||||||
|
mi := &file_hodu_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ConnNotice) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ConnNotice) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ConnNotice) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_hodu_proto_msgTypes[6]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnNotice.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ConnNotice) Descriptor() ([]byte, []int) {
|
||||||
|
return file_hodu_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ConnNotice) GetText() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Text
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type RptyEvent struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Id uint64 `protobuf:"varint,1,opt,name=Id,proto3" json:"Id,omitempty"`
|
||||||
|
Data []byte `protobuf:"bytes,2,opt,name=Data,proto3" json:"Data,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RptyEvent) Reset() {
|
||||||
|
*x = RptyEvent{}
|
||||||
|
mi := &file_hodu_proto_msgTypes[7]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RptyEvent) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RptyEvent) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RptyEvent) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_hodu_proto_msgTypes[7]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RptyEvent.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RptyEvent) Descriptor() ([]byte, []int) {
|
||||||
|
return file_hodu_proto_rawDescGZIP(), []int{7}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RptyEvent) GetId() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RptyEvent) GetData() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Data
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RpxEvent struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Id uint64 `protobuf:"varint,1,opt,name=Id,proto3" json:"Id,omitempty"`
|
||||||
|
Data []byte `protobuf:"bytes,2,opt,name=Data,proto3" json:"Data,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RpxEvent) Reset() {
|
||||||
|
*x = RpxEvent{}
|
||||||
|
mi := &file_hodu_proto_msgTypes[8]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RpxEvent) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RpxEvent) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RpxEvent) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_hodu_proto_msgTypes[8]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RpxEvent.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RpxEvent) Descriptor() ([]byte, []int) {
|
||||||
|
return file_hodu_proto_rawDescGZIP(), []int{8}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RpxEvent) GetId() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RpxEvent) GetData() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Data
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Packet struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Kind PACKET_KIND `protobuf:"varint,1,opt,name=Kind,proto3,enum=PACKET_KIND" json:"Kind,omitempty"`
|
||||||
|
// Types that are valid to be assigned to U:
|
||||||
//
|
//
|
||||||
// *Packet_Route
|
// *Packet_Route
|
||||||
// *Packet_Peer
|
// *Packet_Peer
|
||||||
// *Packet_Data
|
// *Packet_Data
|
||||||
U isPacket_U `protobuf_oneof:"U"`
|
// *Packet_Conn
|
||||||
|
// *Packet_ConnErr
|
||||||
|
// *Packet_ConnNoti
|
||||||
|
// *Packet_RptyEvt
|
||||||
|
// *Packet_RpxEvt
|
||||||
|
U isPacket_U `protobuf_oneof:"U"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Packet) Reset() {
|
func (x *Packet) Reset() {
|
||||||
*x = Packet{}
|
*x = Packet{}
|
||||||
mi := &file_hodu_proto_msgTypes[4]
|
mi := &file_hodu_proto_msgTypes[9]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -464,7 +739,7 @@ func (x *Packet) String() string {
|
|||||||
func (*Packet) ProtoMessage() {}
|
func (*Packet) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Packet) ProtoReflect() protoreflect.Message {
|
func (x *Packet) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_hodu_proto_msgTypes[4]
|
mi := &file_hodu_proto_msgTypes[9]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -477,7 +752,7 @@ func (x *Packet) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use Packet.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Packet.ProtoReflect.Descriptor instead.
|
||||||
func (*Packet) Descriptor() ([]byte, []int) {
|
func (*Packet) Descriptor() ([]byte, []int) {
|
||||||
return file_hodu_proto_rawDescGZIP(), []int{4}
|
return file_hodu_proto_rawDescGZIP(), []int{9}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Packet) GetKind() PACKET_KIND {
|
func (x *Packet) GetKind() PACKET_KIND {
|
||||||
@ -487,30 +762,81 @@ func (x *Packet) GetKind() PACKET_KIND {
|
|||||||
return PACKET_KIND_RESERVED
|
return PACKET_KIND_RESERVED
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Packet) GetU() isPacket_U {
|
func (x *Packet) GetU() isPacket_U {
|
||||||
if m != nil {
|
if x != nil {
|
||||||
return m.U
|
return x.U
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Packet) GetRoute() *RouteDesc {
|
func (x *Packet) GetRoute() *RouteDesc {
|
||||||
if x, ok := x.GetU().(*Packet_Route); ok {
|
if x != nil {
|
||||||
return x.Route
|
if x, ok := x.U.(*Packet_Route); ok {
|
||||||
|
return x.Route
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Packet) GetPeer() *PeerDesc {
|
func (x *Packet) GetPeer() *PeerDesc {
|
||||||
if x, ok := x.GetU().(*Packet_Peer); ok {
|
if x != nil {
|
||||||
return x.Peer
|
if x, ok := x.U.(*Packet_Peer); ok {
|
||||||
|
return x.Peer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Packet) GetData() *PeerData {
|
func (x *Packet) GetData() *PeerData {
|
||||||
if x, ok := x.GetU().(*Packet_Data); ok {
|
if x != nil {
|
||||||
return x.Data
|
if x, ok := x.U.(*Packet_Data); ok {
|
||||||
|
return x.Data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Packet) GetConn() *ConnDesc {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.U.(*Packet_Conn); ok {
|
||||||
|
return x.Conn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Packet) GetConnErr() *ConnError {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.U.(*Packet_ConnErr); ok {
|
||||||
|
return x.ConnErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Packet) GetConnNoti() *ConnNotice {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.U.(*Packet_ConnNoti); ok {
|
||||||
|
return x.ConnNoti
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Packet) GetRptyEvt() *RptyEvent {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.U.(*Packet_RptyEvt); ok {
|
||||||
|
return x.RptyEvt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Packet) GetRpxEvt() *RpxEvent {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.U.(*Packet_RpxEvt); ok {
|
||||||
|
return x.RpxEvt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -531,119 +857,182 @@ type Packet_Data struct {
|
|||||||
Data *PeerData `protobuf:"bytes,4,opt,name=Data,proto3,oneof"`
|
Data *PeerData `protobuf:"bytes,4,opt,name=Data,proto3,oneof"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Packet_Conn struct {
|
||||||
|
Conn *ConnDesc `protobuf:"bytes,5,opt,name=Conn,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Packet_ConnErr struct {
|
||||||
|
ConnErr *ConnError `protobuf:"bytes,6,opt,name=ConnErr,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Packet_ConnNoti struct {
|
||||||
|
ConnNoti *ConnNotice `protobuf:"bytes,7,opt,name=ConnNoti,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Packet_RptyEvt struct {
|
||||||
|
RptyEvt *RptyEvent `protobuf:"bytes,8,opt,name=RptyEvt,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Packet_RpxEvt struct {
|
||||||
|
RpxEvt *RpxEvent `protobuf:"bytes,9,opt,name=RpxEvt,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
func (*Packet_Route) isPacket_U() {}
|
func (*Packet_Route) isPacket_U() {}
|
||||||
|
|
||||||
func (*Packet_Peer) isPacket_U() {}
|
func (*Packet_Peer) isPacket_U() {}
|
||||||
|
|
||||||
func (*Packet_Data) isPacket_U() {}
|
func (*Packet_Data) isPacket_U() {}
|
||||||
|
|
||||||
|
func (*Packet_Conn) isPacket_U() {}
|
||||||
|
|
||||||
|
func (*Packet_ConnErr) isPacket_U() {}
|
||||||
|
|
||||||
|
func (*Packet_ConnNoti) isPacket_U() {}
|
||||||
|
|
||||||
|
func (*Packet_RptyEvt) isPacket_U() {}
|
||||||
|
|
||||||
|
func (*Packet_RpxEvt) isPacket_U() {}
|
||||||
|
|
||||||
var File_hodu_proto protoreflect.FileDescriptor
|
var File_hodu_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_hodu_proto_rawDesc = []byte{
|
const file_hodu_proto_rawDesc = "" +
|
||||||
0x0a, 0x0a, 0x68, 0x6f, 0x64, 0x75, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x36, 0x0a, 0x04,
|
"\n" +
|
||||||
0x53, 0x65, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
|
"\n" +
|
||||||
0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14,
|
"hodu.proto\"6\n" +
|
||||||
0x0a, 0x05, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x46,
|
"\x04Seed\x12\x18\n" +
|
||||||
0x6c, 0x61, 0x67, 0x73, 0x22, 0xdf, 0x01, 0x0a, 0x09, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x44, 0x65,
|
"\aVersion\x18\x01 \x01(\rR\aVersion\x12\x14\n" +
|
||||||
0x73, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x49, 0x64, 0x18, 0x01, 0x20,
|
"\x05Flags\x18\x02 \x01(\x04R\x05Flags\"\xdf\x01\n" +
|
||||||
0x01, 0x28, 0x0d, 0x52, 0x07, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0d,
|
"\tRouteDesc\x12\x18\n" +
|
||||||
0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x53, 0x74, 0x72, 0x18, 0x02, 0x20,
|
"\aRouteId\x18\x01 \x01(\rR\aRouteId\x12$\n" +
|
||||||
0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x53,
|
"\rTargetAddrStr\x18\x02 \x01(\tR\rTargetAddrStr\x12\x1e\n" +
|
||||||
0x74, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65,
|
"\n" +
|
||||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4e, 0x61,
|
"TargetName\x18\x03 \x01(\tR\n" +
|
||||||
0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4f, 0x70, 0x74,
|
"TargetName\x12$\n" +
|
||||||
0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69,
|
"\rServiceOption\x18\x04 \x01(\rR\rServiceOption\x12&\n" +
|
||||||
0x63, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0e, 0x53, 0x65, 0x72, 0x76,
|
"\x0eServiceAddrStr\x18\x05 \x01(\tR\x0eServiceAddrStr\x12$\n" +
|
||||||
0x69, 0x63, 0x65, 0x41, 0x64, 0x64, 0x72, 0x53, 0x74, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
|
"\rServiceNetStr\x18\x06 \x01(\tR\rServiceNetStr\"\x86\x01\n" +
|
||||||
0x52, 0x0e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x64, 0x64, 0x72, 0x53, 0x74, 0x72,
|
"\bPeerDesc\x12\x18\n" +
|
||||||
0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x65, 0x74, 0x53, 0x74,
|
"\aRouteId\x18\x01 \x01(\rR\aRouteId\x12\x16\n" +
|
||||||
0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
"\x06PeerId\x18\x02 \x01(\rR\x06PeerId\x12$\n" +
|
||||||
0x4e, 0x65, 0x74, 0x53, 0x74, 0x72, 0x22, 0x86, 0x01, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x44,
|
"\rRemoteAddrStr\x18\x03 \x01(\tR\rRemoteAddrStr\x12\"\n" +
|
||||||
0x65, 0x73, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x49, 0x64, 0x18, 0x01,
|
"\fLocalAddrStr\x18\x04 \x01(\tR\fLocalAddrStr\"P\n" +
|
||||||
0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a,
|
"\bPeerData\x12\x18\n" +
|
||||||
0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x50,
|
"\aRouteId\x18\x01 \x01(\rR\aRouteId\x12\x16\n" +
|
||||||
0x65, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41,
|
"\x06PeerId\x18\x02 \x01(\rR\x06PeerId\x12\x12\n" +
|
||||||
0x64, 0x64, 0x72, 0x53, 0x74, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x52, 0x65,
|
"\x04Data\x18\x03 \x01(\fR\x04Data\" \n" +
|
||||||
0x6d, 0x6f, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x53, 0x74, 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x4c,
|
"\bConnDesc\x12\x14\n" +
|
||||||
0x6f, 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x53, 0x74, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28,
|
"\x05Token\x18\x01 \x01(\tR\x05Token\"9\n" +
|
||||||
0x09, 0x52, 0x0c, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x53, 0x74, 0x72, 0x22,
|
"\tConnError\x12\x18\n" +
|
||||||
0x50, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x52,
|
"\aErrorId\x18\x01 \x01(\rR\aErrorId\x12\x12\n" +
|
||||||
0x6f, 0x75, 0x74, 0x65, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x52, 0x6f,
|
"\x04Text\x18\x02 \x01(\tR\x04Text\" \n" +
|
||||||
0x75, 0x74, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x64, 0x18,
|
"\n" +
|
||||||
0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a,
|
"ConnNotice\x12\x12\n" +
|
||||||
0x04, 0x44, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x44, 0x61, 0x74,
|
"\x04Text\x18\x01 \x01(\tR\x04Text\"/\n" +
|
||||||
0x61, 0x22, 0x95, 0x01, 0x0a, 0x06, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x20, 0x0a, 0x04,
|
"\tRptyEvent\x12\x0e\n" +
|
||||||
0x4b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0c, 0x2e, 0x50, 0x41, 0x43,
|
"\x02Id\x18\x01 \x01(\x04R\x02Id\x12\x12\n" +
|
||||||
0x4b, 0x45, 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x52, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x22,
|
"\x04Data\x18\x02 \x01(\fR\x04Data\".\n" +
|
||||||
0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e,
|
"\bRpxEvent\x12\x0e\n" +
|
||||||
0x52, 0x6f, 0x75, 0x74, 0x65, 0x44, 0x65, 0x73, 0x63, 0x48, 0x00, 0x52, 0x05, 0x52, 0x6f, 0x75,
|
"\x02Id\x18\x01 \x01(\x04R\x02Id\x12\x12\n" +
|
||||||
0x74, 0x65, 0x12, 0x1f, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
|
"\x04Data\x18\x02 \x01(\fR\x04Data\"\xd6\x02\n" +
|
||||||
0x32, 0x09, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x44, 0x65, 0x73, 0x63, 0x48, 0x00, 0x52, 0x04, 0x50,
|
"\x06Packet\x12 \n" +
|
||||||
0x65, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28,
|
"\x04Kind\x18\x01 \x01(\x0e2\f.PACKET_KINDR\x04Kind\x12\"\n" +
|
||||||
0x0b, 0x32, 0x09, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x04,
|
"\x05Route\x18\x02 \x01(\v2\n" +
|
||||||
0x44, 0x61, 0x74, 0x61, 0x42, 0x03, 0x0a, 0x01, 0x55, 0x2a, 0x5e, 0x0a, 0x0c, 0x52, 0x4f, 0x55,
|
".RouteDescH\x00R\x05Route\x12\x1f\n" +
|
||||||
0x54, 0x45, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x4e, 0x53,
|
"\x04Peer\x18\x03 \x01(\v2\t.PeerDescH\x00R\x04Peer\x12\x1f\n" +
|
||||||
0x50, 0x45, 0x43, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x01, 0x12, 0x08,
|
"\x04Data\x18\x04 \x01(\v2\t.PeerDataH\x00R\x04Data\x12\x1f\n" +
|
||||||
0x0a, 0x04, 0x54, 0x43, 0x50, 0x34, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x54, 0x43, 0x50, 0x36,
|
"\x04Conn\x18\x05 \x01(\v2\t.ConnDescH\x00R\x04Conn\x12&\n" +
|
||||||
0x10, 0x04, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x54, 0x59, 0x10, 0x08, 0x12, 0x08, 0x0a, 0x04, 0x48,
|
"\aConnErr\x18\x06 \x01(\v2\n" +
|
||||||
0x54, 0x54, 0x50, 0x10, 0x10, 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x20,
|
".ConnErrorH\x00R\aConnErr\x12)\n" +
|
||||||
0x12, 0x07, 0x0a, 0x03, 0x53, 0x53, 0x48, 0x10, 0x40, 0x2a, 0xb5, 0x01, 0x0a, 0x0b, 0x50, 0x41,
|
"\bConnNoti\x18\a \x01(\v2\v.ConnNoticeH\x00R\bConnNoti\x12&\n" +
|
||||||
0x43, 0x4b, 0x45, 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53,
|
"\aRptyEvt\x18\b \x01(\v2\n" +
|
||||||
0x45, 0x52, 0x56, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x52, 0x4f, 0x55, 0x54, 0x45,
|
".RptyEventH\x00R\aRptyEvt\x12#\n" +
|
||||||
0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x4f, 0x55, 0x54,
|
"\x06RpxEvt\x18\t \x01(\v2\t.RpxEventH\x00R\x06RpxEvtB\x03\n" +
|
||||||
0x45, 0x5f, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x52, 0x4f, 0x55, 0x54,
|
"\x01U*U\n" +
|
||||||
0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x52,
|
"\fROUTE_OPTION\x12\n" +
|
||||||
0x4f, 0x55, 0x54, 0x45, 0x5f, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, 0x04, 0x12, 0x10,
|
"\n" +
|
||||||
0x0a, 0x0c, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10, 0x05,
|
"\x06UNSPEC\x10\x00\x12\a\n" +
|
||||||
0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x45, 0x44,
|
"\x03TCP\x10\x01\x12\b\n" +
|
||||||
0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54,
|
"\x04TCP4\x10\x02\x12\b\n" +
|
||||||
0x45, 0x44, 0x10, 0x07, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x45, 0x4f, 0x46,
|
"\x04TCP6\x10\x04\x12\b\n" +
|
||||||
0x10, 0x08, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x10,
|
"\x04HTTP\x10\b\x12\t\n" +
|
||||||
0x09, 0x32, 0x49, 0x0a, 0x04, 0x48, 0x6f, 0x64, 0x75, 0x12, 0x19, 0x0a, 0x07, 0x47, 0x65, 0x74,
|
"\x05HTTPS\x10\x10\x12\a\n" +
|
||||||
0x53, 0x65, 0x65, 0x64, 0x12, 0x05, 0x2e, 0x53, 0x65, 0x65, 0x64, 0x1a, 0x05, 0x2e, 0x53, 0x65,
|
"\x03SSH\x10 *\xda\x02\n" +
|
||||||
0x65, 0x64, 0x22, 0x00, 0x12, 0x26, 0x0a, 0x0c, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x74,
|
"\vPACKET_KIND\x12\f\n" +
|
||||||
0x72, 0x65, 0x61, 0x6d, 0x12, 0x07, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x1a, 0x07, 0x2e,
|
"\bRESERVED\x10\x00\x12\x0f\n" +
|
||||||
0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06,
|
"\vROUTE_START\x10\x01\x12\x0e\n" +
|
||||||
0x2e, 0x2f, 0x68, 0x6f, 0x64, 0x75, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
"\n" +
|
||||||
}
|
"ROUTE_STOP\x10\x02\x12\x11\n" +
|
||||||
|
"\rROUTE_STARTED\x10\x03\x12\x11\n" +
|
||||||
|
"\rROUTE_STOPPED\x10\x04\x12\x10\n" +
|
||||||
|
"\fPEER_STARTED\x10\x05\x12\x10\n" +
|
||||||
|
"\fPEER_STOPPED\x10\x06\x12\x10\n" +
|
||||||
|
"\fPEER_ABORTED\x10\a\x12\f\n" +
|
||||||
|
"\bPEER_EOF\x10\b\x12\r\n" +
|
||||||
|
"\tPEER_DATA\x10\t\x12\r\n" +
|
||||||
|
"\tCONN_DESC\x10\v\x12\x0e\n" +
|
||||||
|
"\n" +
|
||||||
|
"CONN_ERROR\x10\f\x12\x0f\n" +
|
||||||
|
"\vCONN_NOTICE\x10\r\x12\x0e\n" +
|
||||||
|
"\n" +
|
||||||
|
"RPTY_START\x10\x0e\x12\r\n" +
|
||||||
|
"\tRPTY_STOP\x10\x0f\x12\r\n" +
|
||||||
|
"\tRPTY_DATA\x10\x10\x12\r\n" +
|
||||||
|
"\tRPTY_SIZE\x10\x11\x12\r\n" +
|
||||||
|
"\tRPX_START\x10\x12\x12\f\n" +
|
||||||
|
"\bRPX_STOP\x10\x13\x12\f\n" +
|
||||||
|
"\bRPX_DATA\x10\x14\x12\v\n" +
|
||||||
|
"\aRPX_EOF\x10\x152I\n" +
|
||||||
|
"\x04Hodu\x12\x19\n" +
|
||||||
|
"\aGetSeed\x12\x05.Seed\x1a\x05.Seed\"\x00\x12&\n" +
|
||||||
|
"\fPacketStream\x12\a.Packet\x1a\a.Packet\"\x00(\x010\x01B\bZ\x06./hodub\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_hodu_proto_rawDescOnce sync.Once
|
file_hodu_proto_rawDescOnce sync.Once
|
||||||
file_hodu_proto_rawDescData = file_hodu_proto_rawDesc
|
file_hodu_proto_rawDescData []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
func file_hodu_proto_rawDescGZIP() []byte {
|
func file_hodu_proto_rawDescGZIP() []byte {
|
||||||
file_hodu_proto_rawDescOnce.Do(func() {
|
file_hodu_proto_rawDescOnce.Do(func() {
|
||||||
file_hodu_proto_rawDescData = protoimpl.X.CompressGZIP(file_hodu_proto_rawDescData)
|
file_hodu_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_hodu_proto_rawDesc), len(file_hodu_proto_rawDesc)))
|
||||||
})
|
})
|
||||||
return file_hodu_proto_rawDescData
|
return file_hodu_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_hodu_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
var file_hodu_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||||
var file_hodu_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
var file_hodu_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
|
||||||
var file_hodu_proto_goTypes = []any{
|
var file_hodu_proto_goTypes = []any{
|
||||||
(ROUTE_OPTION)(0), // 0: ROUTE_OPTION
|
(ROUTE_OPTION)(0), // 0: ROUTE_OPTION
|
||||||
(PACKET_KIND)(0), // 1: PACKET_KIND
|
(PACKET_KIND)(0), // 1: PACKET_KIND
|
||||||
(*Seed)(nil), // 2: Seed
|
(*Seed)(nil), // 2: Seed
|
||||||
(*RouteDesc)(nil), // 3: RouteDesc
|
(*RouteDesc)(nil), // 3: RouteDesc
|
||||||
(*PeerDesc)(nil), // 4: PeerDesc
|
(*PeerDesc)(nil), // 4: PeerDesc
|
||||||
(*PeerData)(nil), // 5: PeerData
|
(*PeerData)(nil), // 5: PeerData
|
||||||
(*Packet)(nil), // 6: Packet
|
(*ConnDesc)(nil), // 6: ConnDesc
|
||||||
|
(*ConnError)(nil), // 7: ConnError
|
||||||
|
(*ConnNotice)(nil), // 8: ConnNotice
|
||||||
|
(*RptyEvent)(nil), // 9: RptyEvent
|
||||||
|
(*RpxEvent)(nil), // 10: RpxEvent
|
||||||
|
(*Packet)(nil), // 11: Packet
|
||||||
}
|
}
|
||||||
var file_hodu_proto_depIdxs = []int32{
|
var file_hodu_proto_depIdxs = []int32{
|
||||||
1, // 0: Packet.Kind:type_name -> PACKET_KIND
|
1, // 0: Packet.Kind:type_name -> PACKET_KIND
|
||||||
3, // 1: Packet.Route:type_name -> RouteDesc
|
3, // 1: Packet.Route:type_name -> RouteDesc
|
||||||
4, // 2: Packet.Peer:type_name -> PeerDesc
|
4, // 2: Packet.Peer:type_name -> PeerDesc
|
||||||
5, // 3: Packet.Data:type_name -> PeerData
|
5, // 3: Packet.Data:type_name -> PeerData
|
||||||
2, // 4: Hodu.GetSeed:input_type -> Seed
|
6, // 4: Packet.Conn:type_name -> ConnDesc
|
||||||
6, // 5: Hodu.PacketStream:input_type -> Packet
|
7, // 5: Packet.ConnErr:type_name -> ConnError
|
||||||
2, // 6: Hodu.GetSeed:output_type -> Seed
|
8, // 6: Packet.ConnNoti:type_name -> ConnNotice
|
||||||
6, // 7: Hodu.PacketStream:output_type -> Packet
|
9, // 7: Packet.RptyEvt:type_name -> RptyEvent
|
||||||
6, // [6:8] is the sub-list for method output_type
|
10, // 8: Packet.RpxEvt:type_name -> RpxEvent
|
||||||
4, // [4:6] is the sub-list for method input_type
|
2, // 9: Hodu.GetSeed:input_type -> Seed
|
||||||
4, // [4:4] is the sub-list for extension type_name
|
11, // 10: Hodu.PacketStream:input_type -> Packet
|
||||||
4, // [4:4] is the sub-list for extension extendee
|
2, // 11: Hodu.GetSeed:output_type -> Seed
|
||||||
0, // [0:4] is the sub-list for field type_name
|
11, // 12: Hodu.PacketStream:output_type -> Packet
|
||||||
|
11, // [11:13] is the sub-list for method output_type
|
||||||
|
9, // [9:11] is the sub-list for method input_type
|
||||||
|
9, // [9:9] is the sub-list for extension type_name
|
||||||
|
9, // [9:9] is the sub-list for extension extendee
|
||||||
|
0, // [0:9] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_hodu_proto_init() }
|
func init() { file_hodu_proto_init() }
|
||||||
@ -651,18 +1040,23 @@ func file_hodu_proto_init() {
|
|||||||
if File_hodu_proto != nil {
|
if File_hodu_proto != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
file_hodu_proto_msgTypes[4].OneofWrappers = []any{
|
file_hodu_proto_msgTypes[9].OneofWrappers = []any{
|
||||||
(*Packet_Route)(nil),
|
(*Packet_Route)(nil),
|
||||||
(*Packet_Peer)(nil),
|
(*Packet_Peer)(nil),
|
||||||
(*Packet_Data)(nil),
|
(*Packet_Data)(nil),
|
||||||
|
(*Packet_Conn)(nil),
|
||||||
|
(*Packet_ConnErr)(nil),
|
||||||
|
(*Packet_ConnNoti)(nil),
|
||||||
|
(*Packet_RptyEvt)(nil),
|
||||||
|
(*Packet_RpxEvt)(nil),
|
||||||
}
|
}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_hodu_proto_rawDesc,
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_hodu_proto_rawDesc), len(file_hodu_proto_rawDesc)),
|
||||||
NumEnums: 2,
|
NumEnums: 2,
|
||||||
NumMessages: 5,
|
NumMessages: 10,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
@ -672,7 +1066,6 @@ func file_hodu_proto_init() {
|
|||||||
MessageInfos: file_hodu_proto_msgTypes,
|
MessageInfos: file_hodu_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_hodu_proto = out.File
|
File_hodu_proto = out.File
|
||||||
file_hodu_proto_rawDesc = nil
|
|
||||||
file_hodu_proto_goTypes = nil
|
file_hodu_proto_goTypes = nil
|
||||||
file_hodu_proto_depIdxs = nil
|
file_hodu_proto_depIdxs = nil
|
||||||
}
|
}
|
||||||
|
74
hodu.proto
74
hodu.proto
@ -23,35 +23,34 @@ enum ROUTE_OPTION {
|
|||||||
TCP = 1;
|
TCP = 1;
|
||||||
TCP4 = 2;
|
TCP4 = 2;
|
||||||
TCP6 = 4;
|
TCP6 = 4;
|
||||||
TTY = 8;
|
HTTP = 8;
|
||||||
HTTP = 16;
|
HTTPS = 16;
|
||||||
HTTPS = 32;
|
SSH = 32;
|
||||||
SSH = 64;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
message RouteDesc {
|
message RouteDesc {
|
||||||
uint32 RouteId = 1;
|
uint32 RouteId = 1;
|
||||||
|
|
||||||
// C->S(ROUTE_START): client-side peer address
|
// C->S(ROUTE_START/STOP): client-side peer address
|
||||||
// S->C(ROUTE_STARTED): server-side listening address
|
// S->C(ROUTE_STARTED/STOPPED): server-side listening address
|
||||||
string TargetAddrStr = 2;
|
string TargetAddrStr = 2;
|
||||||
|
|
||||||
// C->S(ROUTE_START): human-readable name of client-side peer
|
// C->S(ROUTE_START/STOPPED): human-readable name of client-side peer
|
||||||
// S->C(ROUTE_STARTED): clone as sent by C
|
// S->C(ROUTE_STARTED/STOPPED): clone as sent by C
|
||||||
string TargetName= 3;
|
string TargetName= 3;
|
||||||
|
|
||||||
// C->S(ROUTE_START): desired listening option on the server-side(e.g. tcp, tcp4, tcp6) +
|
// C->S(ROUTE_START): requested listening option on the server-side(e.g. tcp, tcp4, tcp6) +
|
||||||
// hint to the service-side peer(e.g. local) +
|
// hint to the service-side peer(e.g. local) +
|
||||||
// hint to the client-side peer(e.g. tty, http, https)
|
// hint to the client-side peer(e.g. tty, http, https)
|
||||||
// S->C(ROUTE_STARTED): cloned as sent by C.
|
// S->C(ROUTE_STARTED): cloned as sent by C.
|
||||||
uint32 ServiceOption = 4;
|
uint32 ServiceOption = 4;
|
||||||
|
|
||||||
// C->S(ROUTE_START): desired lisening address on the service-side
|
// C->S(ROUTE_START): requested lisening address on the service-side
|
||||||
// S->C(ROUTE_STARTED): cloned as sent by C
|
// S->C(ROUTE_STARTED): cloned as sent by C
|
||||||
string ServiceAddrStr = 5;
|
string ServiceAddrStr = 5;
|
||||||
|
|
||||||
// C->S(ROUTE_START): permitted network of server-side peers.
|
// C->S(ROUTE_START): requested permitted network of server-side peers.
|
||||||
// S->C(ROUTE_STARTED): cloned as sent by C.
|
// S->C(ROUTE_STARTED): actual permitted network of server-side peers
|
||||||
string ServiceNetStr = 6;
|
string ServiceNetStr = 6;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -64,8 +63,31 @@ message PeerDesc {
|
|||||||
|
|
||||||
message PeerData {
|
message PeerData {
|
||||||
uint32 RouteId = 1;
|
uint32 RouteId = 1;
|
||||||
uint32 PeerId = 2;
|
uint32 PeerId = 2;
|
||||||
bytes Data = 3;
|
bytes Data = 3;
|
||||||
|
};
|
||||||
|
|
||||||
|
message ConnDesc {
|
||||||
|
string Token = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
message ConnError {
|
||||||
|
uint32 ErrorId = 1;
|
||||||
|
string Text = 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
message ConnNotice {
|
||||||
|
string Text = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
message RptyEvent {
|
||||||
|
uint64 Id = 1;
|
||||||
|
bytes Data = 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
message RpxEvent {
|
||||||
|
uint64 Id = 1;
|
||||||
|
bytes Data = 2;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum PACKET_KIND {
|
enum PACKET_KIND {
|
||||||
@ -79,14 +101,32 @@ enum PACKET_KIND {
|
|||||||
PEER_ABORTED = 7;
|
PEER_ABORTED = 7;
|
||||||
PEER_EOF = 8;
|
PEER_EOF = 8;
|
||||||
PEER_DATA = 9;
|
PEER_DATA = 9;
|
||||||
|
CONN_DESC = 11;
|
||||||
|
CONN_ERROR = 12;
|
||||||
|
CONN_NOTICE = 13;
|
||||||
|
|
||||||
|
RPTY_START = 14;
|
||||||
|
RPTY_STOP = 15;
|
||||||
|
RPTY_DATA = 16;
|
||||||
|
RPTY_SIZE = 17; // terminal size
|
||||||
|
|
||||||
|
RPX_START = 18;
|
||||||
|
RPX_STOP = 19;
|
||||||
|
RPX_DATA = 20;
|
||||||
|
RPX_EOF = 21;
|
||||||
};
|
};
|
||||||
|
|
||||||
message Packet {
|
message Packet {
|
||||||
PACKET_KIND Kind = 1;
|
PACKET_KIND Kind = 1;
|
||||||
|
|
||||||
oneof U {
|
oneof U {
|
||||||
RouteDesc Route = 2;
|
RouteDesc Route = 2;
|
||||||
PeerDesc Peer = 3;
|
PeerDesc Peer = 3;
|
||||||
PeerData Data = 4;
|
PeerData Data = 4;
|
||||||
|
ConnDesc Conn = 5;
|
||||||
|
ConnError ConnErr = 6;
|
||||||
|
ConnNotice ConnNoti = 7;
|
||||||
|
RptyEvent RptyEvt = 8;
|
||||||
|
RpxEvent RpxEvt = 9;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
134
jwt.go
Normal file
134
jwt.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package hodu
|
||||||
|
|
||||||
|
import "crypto"
|
||||||
|
//import "crypto/hmac"
|
||||||
|
import "crypto/rand"
|
||||||
|
import "crypto/rsa"
|
||||||
|
import "encoding/base64"
|
||||||
|
import "encoding/json"
|
||||||
|
import "fmt"
|
||||||
|
import "hash"
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
/*
|
||||||
|
func Sign(data []byte, privkey *rsa.PrivateKey) ([]byte, error) {
|
||||||
|
var h hash.Hash
|
||||||
|
|
||||||
|
h = crypto.SHA512.New()
|
||||||
|
h.Write(data)
|
||||||
|
|
||||||
|
//fmt.Printf("%+v\n", h.Sum(nil))
|
||||||
|
return rsa.SignPKCS1v15(rand.Reader, privkey, crypto.SHA512, h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Verify(data []byte, pubkey *rsa.PublicKey, sig []byte) error {
|
||||||
|
var h hash.Hash
|
||||||
|
|
||||||
|
h = crypto.SHA512.New()
|
||||||
|
h.Write(data)
|
||||||
|
|
||||||
|
return rsa.VerifyPKCS1v15(pubkey, crypto.SHA512, h.Sum(nil), sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SignHS512(data []byte, key string) ([]byte, error) {
|
||||||
|
var h hash.Hash
|
||||||
|
|
||||||
|
h = hmac.New(crypto.SHA512.New, []byte(key))
|
||||||
|
h.Write(data)
|
||||||
|
|
||||||
|
return h.Sum(nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func VerifyHS512(data []byte, key string, sig []byte) error {
|
||||||
|
var h hash.Hash
|
||||||
|
|
||||||
|
h = crypto.SHA512.New()
|
||||||
|
h.Write(data)
|
||||||
|
|
||||||
|
if !hmac.Equal(h.Sum(nil), sig) { return fmt.Errorf("invalid signature") }
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type JWT[T any] struct {
|
||||||
|
key *rsa.PrivateKey
|
||||||
|
claims *T
|
||||||
|
}
|
||||||
|
|
||||||
|
type JWTHeader struct {
|
||||||
|
Algo string `json:"alg"`
|
||||||
|
Type string `json:"typ"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JWTClaimMap map[string]interface{}
|
||||||
|
|
||||||
|
func NewJWT[T any](key *rsa.PrivateKey, claims *T) *JWT[T] {
|
||||||
|
return &JWT[T]{key: key, claims: claims}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JWT[T]) SignRS512() (string, error) {
|
||||||
|
var h JWTHeader
|
||||||
|
var hb []byte
|
||||||
|
var cb []byte
|
||||||
|
var ss string
|
||||||
|
var sb []byte
|
||||||
|
var hs hash.Hash
|
||||||
|
var err error
|
||||||
|
|
||||||
|
h.Algo = "RS512"
|
||||||
|
h.Type = "JWT"
|
||||||
|
|
||||||
|
hb, err = json.Marshal(h)
|
||||||
|
if err != nil { return "", err }
|
||||||
|
|
||||||
|
cb, err = json.Marshal(j.claims)
|
||||||
|
if err != nil { return "", err }
|
||||||
|
|
||||||
|
ss = base64.RawURLEncoding.EncodeToString(hb) + "." + base64.RawURLEncoding.EncodeToString(cb)
|
||||||
|
|
||||||
|
hs = crypto.SHA512.New()
|
||||||
|
hs.Write([]byte(ss))
|
||||||
|
|
||||||
|
sb, err = rsa.SignPKCS1v15(rand.Reader, j.key, crypto.SHA512, hs.Sum(nil))
|
||||||
|
if err != nil { return "", err }
|
||||||
|
|
||||||
|
//fmt.Printf ("%+v %+v %s\n", string(hb), string(cb), (ss + "." + base64.RawURLEncoding.EncodeToString(sb)))
|
||||||
|
return ss + "." + base64.RawURLEncoding.EncodeToString(sb), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JWT[T]) VerifyRS512(tok string) error {
|
||||||
|
var segs []string
|
||||||
|
var hb []byte
|
||||||
|
var cb []byte
|
||||||
|
var ss []byte
|
||||||
|
var jh JWTHeader
|
||||||
|
var hs hash.Hash
|
||||||
|
var err error
|
||||||
|
|
||||||
|
segs = strings.Split(tok, ".")
|
||||||
|
if len(segs) != 3 { return fmt.Errorf("invalid token") }
|
||||||
|
|
||||||
|
hb, err = base64.RawURLEncoding.DecodeString(segs[0])
|
||||||
|
if err != nil { return fmt.Errorf("invalid header - %s", err.Error()) }
|
||||||
|
err = json.Unmarshal(hb, &jh)
|
||||||
|
if err != nil { return fmt.Errorf("invalid header - %s", err.Error()) }
|
||||||
|
|
||||||
|
if jh.Algo != "RS512" || jh.Type != "JWT" { return fmt.Errorf("invalid header content %+v", jh) }
|
||||||
|
|
||||||
|
cb, err = base64.RawURLEncoding.DecodeString(segs[1])
|
||||||
|
if err != nil { return fmt.Errorf("invalid claims - %s", err.Error()) }
|
||||||
|
err = json.Unmarshal(cb, j.claims)
|
||||||
|
if err != nil { return fmt.Errorf("invalid claims - %s", err.Error()) }
|
||||||
|
|
||||||
|
ss, err = base64.RawURLEncoding.DecodeString(segs[2])
|
||||||
|
if err != nil { return fmt.Errorf("invalid signature - %s", err.Error()) }
|
||||||
|
|
||||||
|
hs = crypto.SHA512.New()
|
||||||
|
hs.Write([]byte(segs[0]))
|
||||||
|
hs.Write([]byte("."))
|
||||||
|
hs.Write([]byte(segs[1]))
|
||||||
|
err = rsa.VerifyPKCS1v15(&j.key.PublicKey, crypto.SHA512, hs.Sum(nil), ss)
|
||||||
|
if err != nil { return fmt.Errorf("unverifiable signature - %s", err.Error()) }
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
39
jwt_test.go
Normal file
39
jwt_test.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package hodu_test
|
||||||
|
|
||||||
|
import "crypto/rand"
|
||||||
|
import "crypto/rsa"
|
||||||
|
import "hodu"
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestJwt(t *testing.T) {
|
||||||
|
var tok string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
type JWTClaim struct {
|
||||||
|
Abc string `json:"abc"`
|
||||||
|
Donkey string `json:"donkey"`
|
||||||
|
IssuedAt int `json:"iat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var jc JWTClaim
|
||||||
|
jc.Abc = "def"
|
||||||
|
jc.Donkey = "kong"
|
||||||
|
jc.IssuedAt = 111
|
||||||
|
|
||||||
|
var key *rsa.PrivateKey
|
||||||
|
key, err = rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil { t.Fatalf("keygen failure - %s", err.Error()) }
|
||||||
|
|
||||||
|
var j *hodu.JWT[JWTClaim]
|
||||||
|
j = hodu.NewJWT(key, &jc)
|
||||||
|
tok, err = j.SignRS512()
|
||||||
|
if err != nil { t.Fatalf("signing failure - %s", err.Error()) }
|
||||||
|
|
||||||
|
jc = JWTClaim{}
|
||||||
|
err = j.VerifyRS512(tok)
|
||||||
|
if err != nil { t.Fatalf("verification failure - %s", err.Error()) }
|
||||||
|
|
||||||
|
if jc.Abc != "def" { t.Fatal("decoding failure of Abc field") }
|
||||||
|
if jc.Donkey != "kong" { t.Fatal("decoding failure of Donkey field") }
|
||||||
|
if jc.IssuedAt != 111 { t.Fatal("decoding failure of Issued field") }
|
||||||
|
}
|
47
packet.go
47
packet.go
@ -62,3 +62,50 @@ func MakePeerDataPacket(route_id RouteId, peer_id PeerId, data []byte) *Packet {
|
|||||||
return &Packet{Kind: PACKET_KIND_PEER_DATA,
|
return &Packet{Kind: PACKET_KIND_PEER_DATA,
|
||||||
U: &Packet_Data{Data: &PeerData{RouteId: uint32(route_id), PeerId: uint32(peer_id), Data: data}}}
|
U: &Packet_Data{Data: &PeerData{RouteId: uint32(route_id), PeerId: uint32(peer_id), Data: data}}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MakeConnDescPacket(token string) *Packet {
|
||||||
|
return &Packet{Kind: PACKET_KIND_CONN_DESC,U: &Packet_Conn{Conn: &ConnDesc{Token: token}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeConnErrorPacket(error_id uint32, msg string) *Packet {
|
||||||
|
return &Packet{Kind: PACKET_KIND_CONN_ERROR, U: &Packet_ConnErr{ConnErr: &ConnError{ErrorId: error_id, Text: msg}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeConnNoticePacket(msg string) *Packet {
|
||||||
|
return &Packet{Kind: PACKET_KIND_CONN_NOTICE, U: &Packet_ConnNoti{ConnNoti: &ConnNotice{Text: msg}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeRptyStartPacket(id uint64) *Packet {
|
||||||
|
return &Packet{Kind: PACKET_KIND_RPTY_START, U: &Packet_RptyEvt{RptyEvt: &RptyEvent{Id: id}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeRptyStopPacket(id uint64, msg string) *Packet {
|
||||||
|
// the rpty stop conveys an error/info message
|
||||||
|
return &Packet{Kind: PACKET_KIND_RPTY_STOP, U: &Packet_RptyEvt{RptyEvt: &RptyEvent{Id: id, Data: []byte(msg)}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeRptyDataPacket(id uint64, data []byte) *Packet {
|
||||||
|
return &Packet{Kind: PACKET_KIND_RPTY_DATA, U: &Packet_RptyEvt{RptyEvt: &RptyEvent{Id: id, Data: data}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeRptySizePacket(id uint64, data []byte) *Packet {
|
||||||
|
return &Packet{Kind: PACKET_KIND_RPTY_SIZE, U: &Packet_RptyEvt{RptyEvt: &RptyEvent{Id: id, Data: data}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeRpxStartPacket(id uint64, hdr_part []byte) *Packet {
|
||||||
|
// the rpx start conveys the data unlike other Start packets...
|
||||||
|
return &Packet{Kind: PACKET_KIND_RPX_START, U: &Packet_RpxEvt{RpxEvt: &RpxEvent{Id: id, Data: hdr_part}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeRpxStopPacket(id uint64) *Packet {
|
||||||
|
// the rpx start conveys the data unlike other Start packets...
|
||||||
|
return &Packet{Kind: PACKET_KIND_RPX_STOP, U: &Packet_RpxEvt{RpxEvt: &RpxEvent{Id: id}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeRpxDataPacket(id uint64, data_part []byte) *Packet {
|
||||||
|
return &Packet{Kind: PACKET_KIND_RPX_DATA, U: &Packet_RpxEvt{RpxEvt: &RpxEvent{Id: id, Data: data_part}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeRpxEofPacket(id uint64) *Packet {
|
||||||
|
return &Packet{Kind: PACKET_KIND_RPX_EOF, U: &Packet_RpxEvt{RpxEvt: &RpxEvent{Id: id}}}
|
||||||
|
}
|
||||||
|
71
pty.go
Normal file
71
pty.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package hodu
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
import "fmt"
|
||||||
|
import "os"
|
||||||
|
import "os/exec"
|
||||||
|
import "os/user"
|
||||||
|
import "strconv"
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
import pts "github.com/creack/pty"
|
||||||
|
import "golang.org/x/net/websocket"
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
func connect_pty(pty_shell string, pty_user string) (*exec.Cmd, *os.File, error) {
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
var tty *os.File
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if pty_shell == "" {
|
||||||
|
return nil, nil, fmt.Errorf("blank pty shell")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = exec.Command(pty_shell)
|
||||||
|
if pty_user != "" {
|
||||||
|
var uid int
|
||||||
|
var gid int
|
||||||
|
var u *user.User
|
||||||
|
|
||||||
|
u, err = user.Lookup(pty_user)
|
||||||
|
if err != nil { return nil, nil, err }
|
||||||
|
|
||||||
|
uid, _ = strconv.Atoi(u.Uid)
|
||||||
|
gid, _ = strconv.Atoi(u.Gid)
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
Credential: &syscall.Credential{
|
||||||
|
Uid: uint32(uid),
|
||||||
|
Gid: uint32(gid),
|
||||||
|
},
|
||||||
|
Setsid: true,
|
||||||
|
}
|
||||||
|
cmd.Dir = u.HomeDir
|
||||||
|
cmd.Env = append(cmd.Env,
|
||||||
|
"HOME=" + u.HomeDir,
|
||||||
|
"LOGNAME=" + u.Username,
|
||||||
|
"PATH=" + os.Getenv("PATH"),
|
||||||
|
"SHELL=" + pty_shell,
|
||||||
|
"TERM=xterm",
|
||||||
|
"USER=" + u.Username,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
tty, err = pts.Start(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//syscall.SetNonblock(int(tty.Fd()), true)
|
||||||
|
unix.SetNonblock(int(tty.Fd()), true)
|
||||||
|
|
||||||
|
return cmd, tty, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func send_ws_data_for_xterm(ws *websocket.Conn, type_val string, data string) error {
|
||||||
|
var msg []byte
|
||||||
|
var err error
|
||||||
|
msg, err = json.Marshal(json_xterm_ws_event{Type: type_val, Data: []string{ data } })
|
||||||
|
if err == nil { err = websocket.Message.Send(ws, msg) }
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
870
server-ctl.go
870
server-ctl.go
File diff suppressed because it is too large
Load Diff
139
server-metrics.go
Normal file
139
server-metrics.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
package hodu
|
||||||
|
|
||||||
|
import "runtime"
|
||||||
|
import "strings"
|
||||||
|
import "github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
|
type ServerCollector struct {
|
||||||
|
server *Server
|
||||||
|
BuildInfo *prometheus.Desc
|
||||||
|
ServerConns *prometheus.Desc
|
||||||
|
ServerRoutes *prometheus.Desc
|
||||||
|
ServerPeers *prometheus.Desc
|
||||||
|
SshProxySessions *prometheus.Desc
|
||||||
|
PtySessions *prometheus.Desc
|
||||||
|
RptySessions *prometheus.Desc
|
||||||
|
RpxSessions *prometheus.Desc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServerCollector returns a new ServerCollector with all prometheus.Desc initialized
|
||||||
|
func NewServerCollector(server *Server) ServerCollector {
|
||||||
|
var prefix string
|
||||||
|
|
||||||
|
// prometheus doesn't like a dash. change it to an underscore
|
||||||
|
prefix = strings.ReplaceAll(server.Name(), "-", "_") + "_"
|
||||||
|
return ServerCollector{
|
||||||
|
server: server,
|
||||||
|
|
||||||
|
BuildInfo: prometheus.NewDesc(
|
||||||
|
prefix + "build_info",
|
||||||
|
"Build information",
|
||||||
|
[]string{
|
||||||
|
"goarch",
|
||||||
|
"goos",
|
||||||
|
"goversion",
|
||||||
|
}, nil,
|
||||||
|
),
|
||||||
|
|
||||||
|
ServerConns: prometheus.NewDesc(
|
||||||
|
prefix + "server_conns",
|
||||||
|
"Number of server connections from clients",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
ServerRoutes: prometheus.NewDesc(
|
||||||
|
prefix + "server_routes",
|
||||||
|
"Number of server-side routes",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
ServerPeers: prometheus.NewDesc(
|
||||||
|
prefix + "server_peers",
|
||||||
|
"Number of server-side peers",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
SshProxySessions: prometheus.NewDesc(
|
||||||
|
prefix + "pxy_ssh_sessions",
|
||||||
|
"Number of SSH proxy sessions",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
PtySessions: prometheus.NewDesc(
|
||||||
|
prefix + "pty_sessions",
|
||||||
|
"Number of pty session",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
RptySessions: prometheus.NewDesc(
|
||||||
|
prefix + "rpty_sessions",
|
||||||
|
"Number of rpty session",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
RpxSessions: prometheus.NewDesc(
|
||||||
|
prefix + "rpx_sessions",
|
||||||
|
"Number of rpx session",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ServerCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||||
|
ch <- c.BuildInfo
|
||||||
|
ch <- c.ServerConns
|
||||||
|
ch <- c.ServerRoutes
|
||||||
|
ch <- c.ServerPeers
|
||||||
|
ch <- c.SshProxySessions
|
||||||
|
ch <- c.PtySessions
|
||||||
|
ch <- c.RptySessions
|
||||||
|
ch <- c.RpxSessions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ServerCollector) Collect(ch chan<- prometheus.Metric) {
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.BuildInfo,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
1,
|
||||||
|
runtime.GOARCH,
|
||||||
|
runtime.GOOS,
|
||||||
|
runtime.Version(),
|
||||||
|
)
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.ServerConns,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
float64(c.server.stats.conns.Load()),
|
||||||
|
)
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.ServerRoutes,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
float64(c.server.stats.routes.Load()),
|
||||||
|
)
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.ServerPeers,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
float64(c.server.stats.peers.Load()),
|
||||||
|
)
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.SshProxySessions,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
float64(c.server.stats.ssh_proxy_sessions.Load()),
|
||||||
|
)
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.PtySessions,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
float64(c.server.stats.pty_sessions.Load()),
|
||||||
|
)
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.RptySessions,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
float64(c.server.stats.rpty_sessions.Load()),
|
||||||
|
)
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.RpxSessions,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
float64(c.server.stats.rpx_sessions.Load()),
|
||||||
|
)
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package hodu
|
package hodu
|
||||||
|
|
||||||
|
import "container/list"
|
||||||
import "context"
|
import "context"
|
||||||
import "errors"
|
import "errors"
|
||||||
import "io"
|
import "io"
|
||||||
@ -10,10 +11,13 @@ import "sync/atomic"
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type ServerPeerConn struct {
|
type ServerPeerConn struct {
|
||||||
route *ServerRoute
|
route *ServerRoute
|
||||||
conn_id PeerId
|
conn_id PeerId
|
||||||
cts *ClientConn
|
conn *net.TCPConn
|
||||||
conn *net.TCPConn
|
Created time.Time
|
||||||
|
|
||||||
|
node_in_server *list.Element
|
||||||
|
node_in_conn *list.Element
|
||||||
|
|
||||||
stop_chan chan bool
|
stop_chan chan bool
|
||||||
stop_req atomic.Bool
|
stop_req atomic.Bool
|
||||||
@ -22,6 +26,8 @@ type ServerPeerConn struct {
|
|||||||
client_peer_started atomic.Bool
|
client_peer_started atomic.Bool
|
||||||
client_peer_stopped atomic.Bool
|
client_peer_stopped atomic.Bool
|
||||||
client_peer_eof atomic.Bool
|
client_peer_eof atomic.Bool
|
||||||
|
client_peer_laddr Atom[string]
|
||||||
|
client_peer_raddr Atom[string]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServerPeerConn(r *ServerRoute, c *net.TCPConn, id PeerId) *ServerPeerConn {
|
func NewServerPeerConn(r *ServerRoute, c *net.TCPConn, id PeerId) *ServerPeerConn {
|
||||||
@ -30,6 +36,7 @@ func NewServerPeerConn(r *ServerRoute, c *net.TCPConn, id PeerId) *ServerPeerCon
|
|||||||
spc.route = r
|
spc.route = r
|
||||||
spc.conn = c
|
spc.conn = c
|
||||||
spc.conn_id = id
|
spc.conn_id = id
|
||||||
|
spc.Created = time.Now()
|
||||||
|
|
||||||
spc.stop_chan = make(chan bool, 8)
|
spc.stop_chan = make(chan bool, 8)
|
||||||
spc.stop_req.Store(false)
|
spc.stop_req.Store(false)
|
||||||
@ -54,21 +61,23 @@ func (spc *ServerPeerConn) RunTask(wg *sync.WaitGroup) {
|
|||||||
|
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
|
spc.route.Cts.S.FirePeerEvent(SERVER_EVENT_PEER_STARTED, spc)
|
||||||
|
|
||||||
conn_raddr = spc.conn.RemoteAddr().String()
|
conn_raddr = spc.conn.RemoteAddr().String()
|
||||||
conn_laddr = spc.conn.LocalAddr().String()
|
conn_laddr = spc.conn.LocalAddr().String()
|
||||||
|
|
||||||
pss = spc.route.Cts.pss
|
pss = spc.route.Cts.pss
|
||||||
err = pss.Send(MakePeerStartedPacket(spc.route.Id, spc.conn_id, conn_raddr, conn_laddr))
|
err = pss.Send(MakePeerStartedPacket(spc.route.Id, spc.conn_id, conn_raddr, conn_laddr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
spc.route.Cts.svr.log.Write(spc.route.Cts.sid, LOG_ERROR,
|
spc.route.Cts.S.log.Write(spc.route.Cts.Sid, LOG_ERROR,
|
||||||
"Failed to send peer_started event(%d,%d,%s,%s) to client - %s",
|
"Failed to send %s event(%d,%d,%s,%s) to client - %s",
|
||||||
spc.route.Id, spc.conn_id, conn_raddr, conn_laddr, err.Error())
|
PACKET_KIND_PEER_STARTED.String(), spc.route.Id, spc.conn_id, conn_raddr, conn_laddr, err.Error())
|
||||||
goto done_without_stop
|
goto done_without_stop
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up a timer to set waiting duration until the connection is
|
// set up a timer to set waiting duration until the connection is
|
||||||
// actually established on the client side and it's informed...
|
// actually established on the client side and it's informed...
|
||||||
waitctx, cancel_wait = context.WithTimeout(spc.route.Cts.svr.ctx, 5 * time.Second) // TODO: make this configurable
|
waitctx, cancel_wait = context.WithTimeout(spc.route.Cts.S.Ctx, 5 * time.Second) // TODO: make this configurable
|
||||||
wait_for_started:
|
wait_for_started:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@ -93,31 +102,33 @@ wait_for_started:
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
n, err = spc.conn.Read(buf[:])
|
n, err = spc.conn.Read(buf[:])
|
||||||
|
if n > 0 {
|
||||||
|
var err2 error
|
||||||
|
err2 = pss.Send(MakePeerDataPacket(spc.route.Id, spc.conn_id, buf[:n]))
|
||||||
|
if err2 != nil {
|
||||||
|
spc.route.Cts.S.log.Write(spc.route.Cts.Sid, LOG_ERROR,
|
||||||
|
"Failed to send %s from peer(%d,%d,%s,%s) to client - %s",
|
||||||
|
PACKET_KIND_PEER_DATA.String(), spc.route.Id, spc.conn_id, conn_raddr, conn_laddr, err2.Error())
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, io.EOF) || strings.Contains(err.Error(), "use of closed network connection") { // i don't like this way to check this error.
|
if errors.Is(err, io.EOF) || strings.Contains(err.Error(), "use of closed network connection") { // i don't like this way to check this error.
|
||||||
err = pss.Send(MakePeerEofPacket(spc.route.Id, spc.conn_id))
|
err = pss.Send(MakePeerEofPacket(spc.route.Id, spc.conn_id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
spc.route.Cts.svr.log.Write(spc.route.Cts.sid, LOG_ERROR,
|
spc.route.Cts.S.log.Write(spc.route.Cts.Sid, LOG_ERROR,
|
||||||
"Failed to send peer_eof event(%d,%d,%s,%s) to client - %s",
|
"Failed to send %s event(%d,%d,%s,%s) to client - %s",
|
||||||
spc.route.Id, spc.conn_id, conn_raddr, conn_laddr, err.Error())
|
PACKET_KIND_PEER_EOF.String(), spc.route.Id, spc.conn_id, conn_raddr, conn_laddr, err.Error())
|
||||||
goto done
|
goto done
|
||||||
}
|
}
|
||||||
goto wait_for_stopped
|
goto wait_for_stopped
|
||||||
} else {
|
} else {
|
||||||
spc.route.Cts.svr.log.Write(spc.route.Cts.sid, LOG_ERROR,
|
spc.route.Cts.S.log.Write(spc.route.Cts.Sid, LOG_ERROR,
|
||||||
"Failed to read data from peer(%d,%d,%s,%s) - %s",
|
"Failed to read data from peer(%d,%d,%s,%s) - %s",
|
||||||
spc.route.Id, spc.conn_id, conn_raddr, conn_laddr, err.Error())
|
spc.route.Id, spc.conn_id, conn_raddr, conn_laddr, err.Error())
|
||||||
goto done
|
goto done
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pss.Send(MakePeerDataPacket(spc.route.Id, spc.conn_id, buf[:n]))
|
|
||||||
if err != nil {
|
|
||||||
spc.route.Cts.svr.log.Write(spc.route.Cts.sid, LOG_ERROR,
|
|
||||||
"Failed to send data from peer(%d,%d,%s,%s) to client - %s",
|
|
||||||
spc.route.Id, spc.conn_id, conn_raddr, conn_laddr, err.Error())
|
|
||||||
goto done
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wait_for_stopped:
|
wait_for_stopped:
|
||||||
@ -133,15 +144,27 @@ wait_for_stopped:
|
|||||||
done:
|
done:
|
||||||
err = pss.Send(MakePeerStoppedPacket(spc.route.Id, spc.conn_id, spc.conn.RemoteAddr().String(), spc.conn.LocalAddr().String()))
|
err = pss.Send(MakePeerStoppedPacket(spc.route.Id, spc.conn_id, spc.conn.RemoteAddr().String(), spc.conn.LocalAddr().String()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
spc.route.Cts.svr.log.Write(spc.route.Cts.sid, LOG_ERROR,
|
spc.route.Cts.S.log.Write(spc.route.Cts.Sid, LOG_ERROR,
|
||||||
"Failed to send peer_stopped(%d,%d,%s,%s) to client - %s",
|
"Failed to send %s(%d,%d,%s,%s) to client - %s",
|
||||||
spc.route.Id, spc.conn_id, conn_raddr, conn_laddr, err.Error())
|
PACKET_KIND_PEER_STOPPED.String(), spc.route.Id, spc.conn_id, conn_raddr, conn_laddr, err.Error())
|
||||||
// nothing much to do about the failure of sending this
|
// nothing much to do about the failure of sending this
|
||||||
}
|
}
|
||||||
|
|
||||||
done_without_stop:
|
done_without_stop:
|
||||||
spc.ReqStop()
|
spc.ReqStop()
|
||||||
spc.route.RemoveServerPeerConn(spc)
|
spc.route.RemoveServerPeerConn(spc)
|
||||||
|
|
||||||
|
spc.route.Cts.S.pts_mtx.Lock()
|
||||||
|
spc.route.Cts.S.pts_list.Remove(spc.node_in_server)
|
||||||
|
spc.node_in_server = nil
|
||||||
|
spc.route.Cts.S.pts_mtx.Unlock()
|
||||||
|
|
||||||
|
spc.route.Cts.pts_mtx.Lock()
|
||||||
|
spc.route.Cts.pts_list.Remove(spc.node_in_conn)
|
||||||
|
spc.node_in_conn = nil
|
||||||
|
spc.route.Cts.pts_mtx.Unlock()
|
||||||
|
|
||||||
|
spc.route.Cts.S.FirePeerEvent(SERVER_EVENT_PEER_STOPPED, spc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (spc *ServerPeerConn) ReqStop() {
|
func (spc *ServerPeerConn) ReqStop() {
|
||||||
@ -159,14 +182,29 @@ func (spc *ServerPeerConn) ReqStop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (spc *ServerPeerConn) ReportEvent(event_type PACKET_KIND, event_data interface{}) error {
|
func (spc *ServerPeerConn) ReportPacket(packet_type PACKET_KIND, event_data interface{}) error {
|
||||||
|
|
||||||
switch event_type {
|
switch packet_type {
|
||||||
case PACKET_KIND_PEER_STARTED:
|
case PACKET_KIND_PEER_STARTED:
|
||||||
|
var ok bool
|
||||||
|
var pd *PeerDesc
|
||||||
|
|
||||||
|
pd, ok = event_data.(*PeerDesc)
|
||||||
|
if !ok {
|
||||||
|
// something wrong. leave it unknown.
|
||||||
|
spc.client_peer_laddr.Set("")
|
||||||
|
spc.client_peer_raddr.Set("")
|
||||||
|
} else {
|
||||||
|
spc.client_peer_laddr.Set(pd.LocalAddrStr)
|
||||||
|
spc.client_peer_raddr.Set(pd.RemoteAddrStr)
|
||||||
|
}
|
||||||
|
|
||||||
if spc.client_peer_started.CompareAndSwap(false, true) {
|
if spc.client_peer_started.CompareAndSwap(false, true) {
|
||||||
spc.client_peer_status_chan <- true
|
spc.client_peer_status_chan <- true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spc.route.Cts.S.FirePeerEvent(SERVER_EVENT_PEER_UPDATED, spc)
|
||||||
|
|
||||||
case PACKET_KIND_PEER_ABORTED:
|
case PACKET_KIND_PEER_ABORTED:
|
||||||
spc.ReqStop()
|
spc.ReqStop()
|
||||||
|
|
||||||
@ -192,21 +230,21 @@ func (spc *ServerPeerConn) ReportEvent(event_type PACKET_KIND, event_data interf
|
|||||||
var err error
|
var err error
|
||||||
_, err = spc.conn.Write(data)
|
_, err = spc.conn.Write(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
spc.route.Cts.svr.log.Write(spc.route.Cts.sid, LOG_ERROR,
|
spc.route.Cts.S.log.Write(spc.route.Cts.Sid, LOG_ERROR,
|
||||||
"Failed to write data from %s to peer(%d,%d,%s) - %s",
|
"Failed to write data from %s to peer(%d,%d,%s) - %s",
|
||||||
spc.route.Cts.RemoteAddr, spc.route.Id, spc.conn_id, spc.conn.RemoteAddr().String(), err.Error())
|
spc.route.Cts.RemoteAddr, spc.route.Id, spc.conn_id, spc.conn.RemoteAddr().String(), err.Error())
|
||||||
spc.ReqStop()
|
spc.ReqStop()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// this must not happen.
|
// this must not happen.
|
||||||
spc.route.Cts.svr.log.Write(spc.route.Cts.sid, LOG_ERROR,
|
spc.route.Cts.S.log.Write(spc.route.Cts.Sid, LOG_ERROR,
|
||||||
"Protocol error - invalid data in peer_data event from %s to peer(%d,%d,%s)",
|
"Protocol error - invalid data in peer_data event from %s to peer(%d,%d,%s)",
|
||||||
spc.route.Cts.RemoteAddr, spc.route.Id, spc.conn_id, spc.conn.RemoteAddr().String())
|
spc.route.Cts.RemoteAddr, spc.route.Id, spc.conn_id, spc.conn.RemoteAddr().String())
|
||||||
spc.ReqStop()
|
spc.ReqStop()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// protocol error. the client must not relay more data from the client-side peer after EOF.
|
// protocol error. the client must not relay more data from the client-side peer after EOF.
|
||||||
spc.route.Cts.svr.log.Write(spc.route.Cts.sid, LOG_ERROR,
|
spc.route.Cts.S.log.Write(spc.route.Cts.Sid, LOG_ERROR,
|
||||||
"Protocol error - redundant data from %s to (%d,%d,%s)",
|
"Protocol error - redundant data from %s to (%d,%d,%s)",
|
||||||
spc.route.Cts.RemoteAddr, spc.route.Id, spc.conn_id, spc.conn.RemoteAddr().String())
|
spc.route.Cts.RemoteAddr, spc.route.Id, spc.conn_id, spc.conn.RemoteAddr().String())
|
||||||
spc.ReqStop()
|
spc.ReqStop()
|
||||||
|
400
server-pty.go
Normal file
400
server-pty.go
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
package hodu
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
import "errors"
|
||||||
|
import "fmt"
|
||||||
|
import "io"
|
||||||
|
import "net/http"
|
||||||
|
import "os"
|
||||||
|
import "os/exec"
|
||||||
|
import "strconv"
|
||||||
|
import "strings"
|
||||||
|
import "sync"
|
||||||
|
import "text/template"
|
||||||
|
|
||||||
|
import pts "github.com/creack/pty"
|
||||||
|
import "golang.org/x/net/websocket"
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
type server_pty_ws struct {
|
||||||
|
S *Server
|
||||||
|
Id string
|
||||||
|
ws *websocket.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type server_rpty_ws struct {
|
||||||
|
S *Server
|
||||||
|
Id string
|
||||||
|
ws *websocket.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type server_pty_xterm_file struct {
|
||||||
|
ServerCtl
|
||||||
|
file string
|
||||||
|
mode string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------
|
||||||
|
|
||||||
|
func (pty *server_pty_ws) Identity() string {
|
||||||
|
return pty.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pty *server_pty_ws) ServeWebsocket(ws *websocket.Conn) (int, error) {
|
||||||
|
var s *Server
|
||||||
|
var req *http.Request
|
||||||
|
//var username string
|
||||||
|
//var password string
|
||||||
|
var in *os.File
|
||||||
|
var out *os.File
|
||||||
|
var tty *os.File
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
var pfd [2]int = [2]int{ -1, -1 }
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var conn_ready_chan chan bool
|
||||||
|
var err error
|
||||||
|
|
||||||
|
s = pty.S
|
||||||
|
req = ws.Request()
|
||||||
|
conn_ready_chan = make(chan bool, 3)
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
var conn_ready bool
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
defer ws.Close() // dirty way to break the main loop
|
||||||
|
|
||||||
|
conn_ready = <-conn_ready_chan
|
||||||
|
if conn_ready { // connected
|
||||||
|
var poll_fds []unix.PollFd
|
||||||
|
var buf [2048]byte
|
||||||
|
var n int
|
||||||
|
var err error
|
||||||
|
|
||||||
|
poll_fds = []unix.PollFd{
|
||||||
|
unix.PollFd{Fd: int32(out.Fd()), Events: unix.POLLIN},
|
||||||
|
unix.PollFd{Fd: int32(pfd[0]), Events: unix.POLLIN},
|
||||||
|
}
|
||||||
|
|
||||||
|
s.stats.pty_sessions.Add(1)
|
||||||
|
for {
|
||||||
|
n, err = unix.Poll(poll_fds, -1) // -1 means wait indefinitely
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, unix.EINTR) { continue }
|
||||||
|
s.log.Write("", LOG_ERROR, "[%s] Failed to poll pty stdout - %s", req.RemoteAddr, err.Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if n == 0 { // timed out
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poll_fds[0].Revents & (unix.POLLERR | unix.POLLHUP | unix.POLLNVAL)) != 0 {
|
||||||
|
s.log.Write(pty.Id, LOG_DEBUG, "[%s] EOF detected on pty stdout", req.RemoteAddr)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (poll_fds[1].Revents & (unix.POLLERR | unix.POLLHUP | unix.POLLNVAL)) != 0 {
|
||||||
|
s.log.Write(pty.Id, LOG_DEBUG, "[%s] EOF detected on pty event pipe", req.RemoteAddr)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poll_fds[0].Revents & unix.POLLIN) != 0 {
|
||||||
|
n, err = out.Read(buf[:])
|
||||||
|
if n > 0 {
|
||||||
|
var err2 error
|
||||||
|
err2 = send_ws_data_for_xterm(ws, "iov", string(buf[:n]))
|
||||||
|
if err2 != nil {
|
||||||
|
s.log.Write(pty.Id, LOG_ERROR, "[%s] Failed to send to websocket - %s", req.RemoteAddr, err2.Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
s.log.Write(pty.Id, LOG_ERROR, "[%s] Failed to read pty stdout - %s", req.RemoteAddr, err.Error())
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (poll_fds[1].Revents & unix.POLLIN) != 0 {
|
||||||
|
s.log.Write(pty.Id, LOG_DEBUG, "[%s] Stop request noticed on pty event pipe", req.RemoteAddr)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.stats.pty_sessions.Add(-1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ws_recv_loop:
|
||||||
|
for {
|
||||||
|
var msg []byte
|
||||||
|
err = websocket.Message.Receive(ws, &msg)
|
||||||
|
if err != nil { goto done }
|
||||||
|
|
||||||
|
if len(msg) > 0 {
|
||||||
|
var ev json_xterm_ws_event
|
||||||
|
err = json.Unmarshal(msg, &ev)
|
||||||
|
if err == nil {
|
||||||
|
switch ev.Type {
|
||||||
|
case "open":
|
||||||
|
if tty == nil && len(ev.Data) == 2 {
|
||||||
|
// not using username and password for now...
|
||||||
|
//username = string(ev.Data[0])
|
||||||
|
//password = string(ev.Data[1])
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
err = unix.Pipe(pfd[:])
|
||||||
|
if err != nil {
|
||||||
|
s.log.Write(pty.Id, LOG_ERROR, "[%s] Failed to create event pipe for pty - %s", req.RemoteAddr, err.Error())
|
||||||
|
send_ws_data_for_xterm(ws, "error", err.Error())
|
||||||
|
ws.Close() // dirty way to flag out the error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, tty, err = connect_pty(s.pty_shell, s.pty_user)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Write(pty.Id, LOG_ERROR, "[%s] Failed to connect pty - %s", req.RemoteAddr, err.Error())
|
||||||
|
send_ws_data_for_xterm(ws, "error", err.Error())
|
||||||
|
ws.Close() // dirty way to flag out the error - this will make websocket.MessageReceive to fail
|
||||||
|
unix.Close(pfd[0]); pfd[0] = -1
|
||||||
|
unix.Close(pfd[1]); pfd[1] = -1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = send_ws_data_for_xterm(ws, "status", "opened")
|
||||||
|
if err != nil {
|
||||||
|
s.log.Write(pty.Id, LOG_ERROR, "[%s] Failed to write 'opened' event to websocket - %s", req.RemoteAddr, err.Error())
|
||||||
|
ws.Close() // dirty way to flag out the error
|
||||||
|
unix.Close(pfd[0]); pfd[0] = -1
|
||||||
|
unix.Close(pfd[1]); pfd[1] = -1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.log.Write(pty.Id, LOG_DEBUG, "[%s] Opened pty session", req.RemoteAddr)
|
||||||
|
out = tty
|
||||||
|
in = tty
|
||||||
|
conn_ready_chan <- true
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
case "close":
|
||||||
|
if tty != nil {
|
||||||
|
tty.Close()
|
||||||
|
tty = nil
|
||||||
|
}
|
||||||
|
if pfd[1] >= 0 {
|
||||||
|
unix.Write(pfd[1], []byte{0})
|
||||||
|
}
|
||||||
|
break ws_recv_loop
|
||||||
|
|
||||||
|
case "iov":
|
||||||
|
if tty != nil {
|
||||||
|
var i int
|
||||||
|
for i, _ = range ev.Data {
|
||||||
|
in.Write([]byte(ev.Data[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "size":
|
||||||
|
if tty != nil && len(ev.Data) == 2 {
|
||||||
|
var rows int
|
||||||
|
var cols int
|
||||||
|
rows, _ = strconv.Atoi(ev.Data[0])
|
||||||
|
cols, _ = strconv.Atoi(ev.Data[1])
|
||||||
|
pts.Setsize(tty, &pts.Winsize{Rows: uint16(rows), Cols: uint16(cols)})
|
||||||
|
s.log.Write(pty.Id, LOG_DEBUG, "[%s] Resized terminal to %d,%d", req.RemoteAddr, rows, cols)
|
||||||
|
// ignore error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tty != nil {
|
||||||
|
err = send_ws_data_for_xterm(ws, "status", "closed")
|
||||||
|
if err != nil { goto done }
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
conn_ready_chan <- false
|
||||||
|
ws.Close()
|
||||||
|
if cmd != nil {
|
||||||
|
// kill the child process underneath to close ptym(the master pty).
|
||||||
|
//cmd.Process.Signal(syscall.SIGTERM)
|
||||||
|
cmd.Process.Kill()
|
||||||
|
}
|
||||||
|
if tty != nil { tty.Close() }
|
||||||
|
if cmd != nil { cmd.Wait() }
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// close the event pipe after all goroutines are over
|
||||||
|
if pfd[0] >= 0 { unix.Close(pfd[0]) }
|
||||||
|
if pfd[1] >= 0 { unix.Close(pfd[1]) }
|
||||||
|
|
||||||
|
s.log.Write(pty.Id, LOG_DEBUG, "[%s] Ended pty session", req.RemoteAddr)
|
||||||
|
|
||||||
|
return http.StatusOK, err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------------------------------
|
||||||
|
func (rpty *server_rpty_ws) Identity() string {
|
||||||
|
return rpty.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rpty *server_rpty_ws) ServeWebsocket(ws *websocket.Conn) (int, error) {
|
||||||
|
var s *Server
|
||||||
|
var req *http.Request
|
||||||
|
var token string
|
||||||
|
var cts *ServerConn
|
||||||
|
//var username string
|
||||||
|
//var password string
|
||||||
|
var rp *ServerRpty
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var err error
|
||||||
|
|
||||||
|
s = rpty.S
|
||||||
|
req = ws.Request()
|
||||||
|
token = req.FormValue("client-token")
|
||||||
|
if token == "" {
|
||||||
|
ws.Close()
|
||||||
|
return http.StatusBadRequest, fmt.Errorf("no client token specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
cts = s.FindServerConnByClientToken(token)
|
||||||
|
if cts == nil {
|
||||||
|
ws.Close()
|
||||||
|
return http.StatusBadRequest, fmt.Errorf("invalid client token - %s", token)
|
||||||
|
}
|
||||||
|
|
||||||
|
ws_recv_loop:
|
||||||
|
for {
|
||||||
|
var msg []byte
|
||||||
|
err = websocket.Message.Receive(ws, &msg)
|
||||||
|
if err != nil { goto done }
|
||||||
|
|
||||||
|
if len(msg) > 0 {
|
||||||
|
var ev json_xterm_ws_event
|
||||||
|
err = json.Unmarshal(msg, &ev)
|
||||||
|
if err == nil {
|
||||||
|
switch ev.Type {
|
||||||
|
case "open":
|
||||||
|
if rp == nil && len(ev.Data) == 2 {
|
||||||
|
//username = string(ev.Data[0])
|
||||||
|
//password = string(ev.Data[1])
|
||||||
|
|
||||||
|
rp, err = cts.StartRpty(ws)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Write(rpty.Id, LOG_ERROR, "[%s] Failed to connect pty - %s", req.RemoteAddr, err.Error())
|
||||||
|
send_ws_data_for_xterm(ws, "error", err.Error())
|
||||||
|
ws.Close() // dirty way to flag out the error by making websocket.Message.Receive() fail
|
||||||
|
} else {
|
||||||
|
err = send_ws_data_for_xterm(ws, "status", "opened")
|
||||||
|
if err != nil {
|
||||||
|
s.log.Write(rpty.Id, LOG_ERROR, "[%s] Failed to write 'opened' event to websocket - %s", req.RemoteAddr, err.Error())
|
||||||
|
ws.Close() // dirty way to flag out the error
|
||||||
|
} else {
|
||||||
|
s.log.Write(rpty.Id, LOG_DEBUG, "[%s] Opened pty session", req.RemoteAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "close":
|
||||||
|
// just break out of the loop and let the remainder to close resources
|
||||||
|
break ws_recv_loop
|
||||||
|
|
||||||
|
case "iov":
|
||||||
|
var i int
|
||||||
|
for i, _ = range ev.Data {
|
||||||
|
cts.WriteRpty(ws, []byte(ev.Data[i]))
|
||||||
|
// ignore error for now
|
||||||
|
}
|
||||||
|
|
||||||
|
case "size":
|
||||||
|
if len(ev.Data) == 2 {
|
||||||
|
cts.WriteRptySize(ws, []byte(fmt.Sprintf("%s %s", ev.Data[0], ev.Data[1])))
|
||||||
|
s.log.Write(rpty.Id, LOG_DEBUG, "[%s] Requested to resize rpty terminal to %s,%s", req.RemoteAddr, ev.Data[0], ev.Data[1])
|
||||||
|
// ignore error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
cts.StopRpty(ws)
|
||||||
|
ws.Close() // don't care about multiple closes
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
s.log.Write(rpty.Id, LOG_DEBUG, "[%s] Ended rpty session for %s", req.RemoteAddr, token)
|
||||||
|
|
||||||
|
return http.StatusOK, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------
|
||||||
|
|
||||||
|
func (pty *server_pty_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
|
||||||
|
var s *Server
|
||||||
|
var status_code int
|
||||||
|
var err error
|
||||||
|
|
||||||
|
s = pty.S
|
||||||
|
|
||||||
|
switch pty.file {
|
||||||
|
case "xterm.js":
|
||||||
|
status_code = WriteJsRespHeader(w, http.StatusOK)
|
||||||
|
w.Write(xterm_js)
|
||||||
|
case "xterm-addon-fit.js":
|
||||||
|
status_code = WriteJsRespHeader(w, http.StatusOK)
|
||||||
|
w.Write(xterm_addon_fit_js)
|
||||||
|
case "xterm.css":
|
||||||
|
status_code = WriteCssRespHeader(w, http.StatusOK)
|
||||||
|
w.Write(xterm_css)
|
||||||
|
case "xterm.html":
|
||||||
|
var tmpl *template.Template
|
||||||
|
|
||||||
|
tmpl = template.New("")
|
||||||
|
if s.xterm_html != "" {
|
||||||
|
_, err = tmpl.Parse(s.xterm_html)
|
||||||
|
} else {
|
||||||
|
_, err = tmpl.Parse(xterm_html)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
status_code = WriteEmptyRespHeader(w, http.StatusInternalServerError)
|
||||||
|
goto oops
|
||||||
|
} else {
|
||||||
|
status_code = WriteHtmlRespHeader(w, http.StatusOK)
|
||||||
|
tmpl.Execute(w,
|
||||||
|
&xterm_session_info{
|
||||||
|
Mode: pty.mode,
|
||||||
|
ConnId: "-1",
|
||||||
|
RouteId: "-1",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
case "_forbidden":
|
||||||
|
status_code = WriteEmptyRespHeader(w, http.StatusForbidden)
|
||||||
|
|
||||||
|
case "_notfound":
|
||||||
|
status_code = WriteEmptyRespHeader(w, http.StatusNotFound)
|
||||||
|
|
||||||
|
default:
|
||||||
|
if strings.HasPrefix(pty.file, "_redir:") {
|
||||||
|
status_code = http.StatusMovedPermanently
|
||||||
|
w.Header().Set("Location", pty.file[7:])
|
||||||
|
w.WriteHeader(status_code)
|
||||||
|
} else {
|
||||||
|
status_code = WriteEmptyRespHeader(w, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//done:
|
||||||
|
return status_code, nil
|
||||||
|
|
||||||
|
oops:
|
||||||
|
return status_code, err
|
||||||
|
}
|
@ -3,8 +3,8 @@ package hodu
|
|||||||
import "bufio"
|
import "bufio"
|
||||||
import "context"
|
import "context"
|
||||||
import "crypto/tls"
|
import "crypto/tls"
|
||||||
import _ "embed"
|
|
||||||
import "encoding/json"
|
import "encoding/json"
|
||||||
|
import "errors"
|
||||||
import "fmt"
|
import "fmt"
|
||||||
import "io"
|
import "io"
|
||||||
import "net"
|
import "net"
|
||||||
@ -21,32 +21,23 @@ import "golang.org/x/crypto/ssh"
|
|||||||
import "golang.org/x/net/http/httpguts"
|
import "golang.org/x/net/http/httpguts"
|
||||||
import "golang.org/x/net/websocket"
|
import "golang.org/x/net/websocket"
|
||||||
|
|
||||||
//go:embed xterm.js
|
type server_pxy struct {
|
||||||
var xterm_js []byte
|
S *Server
|
||||||
//go:embed xterm-addon-fit.js
|
Id string
|
||||||
var xterm_addon_fit_js []byte
|
|
||||||
//go:embed xterm.css
|
|
||||||
var xterm_css []byte
|
|
||||||
//go:embed xterm.html
|
|
||||||
var xterm_html string
|
|
||||||
|
|
||||||
type server_proxy struct {
|
|
||||||
s *Server
|
|
||||||
id string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type server_proxy_http_main struct {
|
type server_pxy_http_main struct {
|
||||||
server_proxy
|
server_pxy
|
||||||
prefix string
|
prefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
type server_proxy_xterm_file struct {
|
type server_pxy_xterm_file struct {
|
||||||
server_proxy
|
server_pxy
|
||||||
file string
|
file string
|
||||||
}
|
}
|
||||||
|
|
||||||
type server_proxy_http_wpx struct {
|
type server_pxy_http_wpx struct {
|
||||||
server_proxy
|
server_pxy
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is minimal information for wpx to work
|
// this is minimal information for wpx to work
|
||||||
@ -184,17 +175,28 @@ func mutate_proxy_req_headers(req *http.Request, newreq *http.Request, path_pref
|
|||||||
return upgrade_required
|
return upgrade_required
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *server_proxy) GetId() string {
|
// ------------------------------------
|
||||||
return pxy.id
|
|
||||||
|
func (pxy *server_pxy) Identity() string {
|
||||||
|
return pxy.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *server_pxy) Cors(req *http.Request) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *server_pxy) Authenticate(req *http.Request) (int, string) {
|
||||||
|
return http.StatusOK, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
|
|
||||||
func prevent_follow_redirect (req *http.Request, via []*http.Request) error {
|
func prevent_follow_redirect(req *http.Request, via []*http.Request) error {
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *server_proxy_http_main) get_route_proxy_info(req *http.Request, in_wpx_mode bool) (*ServerRouteProxyInfo, error) {
|
func (pxy *server_pxy_http_main) get_route_proxy_info(req *http.Request, in_wpx_mode bool) (*ServerRouteProxyInfo, error) {
|
||||||
|
var s *Server
|
||||||
var conn_id string
|
var conn_id string
|
||||||
var route_id string
|
var route_id string
|
||||||
var r *ServerRoute
|
var r *ServerRoute
|
||||||
@ -202,6 +204,8 @@ func (pxy *server_proxy_http_main) get_route_proxy_info(req *http.Request, in_wp
|
|||||||
var path_prefix string
|
var path_prefix string
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
s = pxy.S
|
||||||
|
|
||||||
if in_wpx_mode { // for wpx
|
if in_wpx_mode { // for wpx
|
||||||
conn_id = req.PathValue("port_id")
|
conn_id = req.PathValue("port_id")
|
||||||
route_id = pxy.prefix // this is PORT_ID_MARKER
|
route_id = pxy.prefix // this is PORT_ID_MARKER
|
||||||
@ -215,12 +219,12 @@ func (pxy *server_proxy_http_main) get_route_proxy_info(req *http.Request, in_wp
|
|||||||
path_prefix = fmt.Sprintf("%s/%s/%s", pxy.prefix, conn_id, route_id)
|
path_prefix = fmt.Sprintf("%s/%s/%s", pxy.prefix, conn_id, route_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err = pxy.s.FindServerRouteByIdStr(conn_id, route_id)
|
r, err = s.FindServerRouteByIdStr(conn_id, route_id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !in_wpx_mode || pxy.s.wpx_foreign_port_proxy_maker == nil { return nil, err }
|
if !in_wpx_mode || s.wpx_foreign_port_proxy_maker == nil { return nil, err }
|
||||||
|
|
||||||
// call this callback only in the wpx mode
|
// call this callback only in the wpx mode
|
||||||
pi, err = pxy.s.wpx_foreign_port_proxy_maker("http", conn_id)
|
pi, err = s.wpx_foreign_port_proxy_maker("http", conn_id)
|
||||||
if err != nil { return nil, err }
|
if err != nil { return nil, err }
|
||||||
pi.IsForeign = true // just to ensure this
|
pi.IsForeign = true // just to ensure this
|
||||||
} else {
|
} else {
|
||||||
@ -234,7 +238,7 @@ func (pxy *server_proxy_http_main) get_route_proxy_info(req *http.Request, in_wp
|
|||||||
return pi, nil
|
return pi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *server_proxy_http_main) serve_upgraded(w http.ResponseWriter, req *http.Request, proxy_res *http.Response) error {
|
func (pxy *server_pxy_http_main) serve_upgraded(w http.ResponseWriter, req *http.Request, proxy_res *http.Response) error {
|
||||||
var err_chan chan error
|
var err_chan chan error
|
||||||
var proxy_res_body io.ReadWriteCloser
|
var proxy_res_body io.ReadWriteCloser
|
||||||
var rc *http.ResponseController
|
var rc *http.ResponseController
|
||||||
@ -285,30 +289,36 @@ func (pxy *server_proxy_http_main) serve_upgraded(w http.ResponseWriter, req *ht
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *server_proxy_http_main) addr_to_transport (ctx context.Context, addr *net.TCPAddr) (*http.Transport, error) {
|
func (pxy *server_pxy_http_main) addr_to_transport(ctx context.Context, addr *net.TCPAddr) (*http.Transport, error) {
|
||||||
var dialer *net.Dialer
|
var dialer *net.Dialer
|
||||||
var waitctx context.Context
|
var waitctx context.Context
|
||||||
var cancel_wait context.CancelFunc
|
var cancel_wait context.CancelFunc
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
|
var tls_config *tls.Config
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// establish the connection.
|
// establish the connection.
|
||||||
dialer = &net.Dialer{}
|
dialer = &net.Dialer{}
|
||||||
waitctx, cancel_wait = context.WithTimeout(ctx, 3 * time.Second) // TODO: make timeout configurable
|
waitctx, cancel_wait = context.WithTimeout(ctx, 5 * time.Second) // TODO: make timeout configurable
|
||||||
conn, err = dialer.DialContext(waitctx, TcpAddrClass(addr), addr.String())
|
conn, err = dialer.DialContext(waitctx, TcpAddrClass(addr), addr.String())
|
||||||
cancel_wait()
|
cancel_wait()
|
||||||
if err != nil { return nil, err }
|
if err != nil { return nil, err }
|
||||||
|
|
||||||
|
if pxy.S.Cfg.PxyTargetTls != nil {
|
||||||
|
tls_config = pxy.S.Cfg.PxyTargetTls.Clone()
|
||||||
|
} else {
|
||||||
|
tls_config = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
}
|
||||||
// create a transport that uses the connection
|
// create a transport that uses the connection
|
||||||
return &http.Transport{
|
return &http.Transport{
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
},
|
},
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // TODO: make this part configurable?
|
TLSClientConfig: tls_config,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *server_proxy_http_main) req_to_proxy_url (req *http.Request, r *ServerRouteProxyInfo) *url.URL {
|
func (pxy *server_pxy_http_main) req_to_proxy_url(req *http.Request, r *ServerRouteProxyInfo) *url.URL {
|
||||||
var proxy_proto string
|
var proxy_proto string
|
||||||
var proxy_url_path string
|
var proxy_url_path string
|
||||||
|
|
||||||
@ -333,7 +343,7 @@ func (pxy *server_proxy_http_main) req_to_proxy_url (req *http.Request, r *Serve
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *server_proxy_http_main) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
|
func (pxy *server_pxy_http_main) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
|
||||||
var s *Server
|
var s *Server
|
||||||
var pi *ServerRouteProxyInfo
|
var pi *ServerRouteProxyInfo
|
||||||
var status_code int
|
var status_code int
|
||||||
@ -347,7 +357,7 @@ func (pxy *server_proxy_http_main) ServeHTTP(w http.ResponseWriter, req *http.Re
|
|||||||
var upgrade_required bool
|
var upgrade_required bool
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
s = pxy.s
|
s = pxy.S
|
||||||
in_wpx_mode = (pxy.prefix == PORT_ID_MARKER)
|
in_wpx_mode = (pxy.prefix == PORT_ID_MARKER)
|
||||||
|
|
||||||
pi, err = pxy.get_route_proxy_info(req, in_wpx_mode)
|
pi, err = pxy.get_route_proxy_info(req, in_wpx_mode)
|
||||||
@ -364,16 +374,16 @@ func (pxy *server_proxy_http_main) ServeHTTP(w http.ResponseWriter, req *http.Re
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
addr = svc_addr_to_dst_addr(pi.SvcAddr)
|
addr = svc_addr_to_dst_addr(pi.SvcAddr)
|
||||||
transport, err = pxy.addr_to_transport(s.ctx, addr)
|
transport, err = pxy.addr_to_transport(s.Ctx, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
status_code = WriteEmptyRespHeader(w, http.StatusBadGateway)
|
status_code = WriteEmptyRespHeader(w, http.StatusBadGateway)
|
||||||
goto oops
|
goto oops
|
||||||
}
|
}
|
||||||
proxy_url = pxy.req_to_proxy_url(req, pi)
|
proxy_url = pxy.req_to_proxy_url(req, pi)
|
||||||
|
|
||||||
s.log.Write(pxy.id, LOG_INFO, "[%s] %s %s -> %+v", req.RemoteAddr, req.Method, req.URL.String(), proxy_url)
|
s.log.Write(pxy.Id, LOG_INFO, "[%s] %s %s -> %+v", req.RemoteAddr, req.Method, req.RequestURI, proxy_url)
|
||||||
|
|
||||||
proxy_req, err = http.NewRequestWithContext(s.ctx, req.Method, proxy_url.String(), req.Body)
|
proxy_req, err = http.NewRequestWithContext(s.Ctx, req.Method, proxy_url.String(), req.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
status_code = WriteEmptyRespHeader(w, http.StatusInternalServerError)
|
status_code = WriteEmptyRespHeader(w, http.StatusInternalServerError)
|
||||||
goto oops
|
goto oops
|
||||||
@ -397,7 +407,7 @@ func (pxy *server_proxy_http_main) ServeHTTP(w http.ResponseWriter, req *http.Re
|
|||||||
} else {
|
} else {
|
||||||
status_code = resp.StatusCode
|
status_code = resp.StatusCode
|
||||||
if upgrade_required && resp.StatusCode == http.StatusSwitchingProtocols {
|
if upgrade_required && resp.StatusCode == http.StatusSwitchingProtocols {
|
||||||
s.log.Write(pxy.id, LOG_INFO, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.URL.String(), status_code)
|
s.log.Write(pxy.Id, LOG_INFO, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.RequestURI, status_code)
|
||||||
err = pxy.serve_upgraded(w, req, resp)
|
err = pxy.serve_upgraded(w, req, resp)
|
||||||
if err != nil { goto oops }
|
if err != nil { goto oops }
|
||||||
return 0, nil// print the log mesage before calling serve_upgraded() and exit here
|
return 0, nil// print the log mesage before calling serve_upgraded() and exit here
|
||||||
@ -422,7 +432,7 @@ func (pxy *server_proxy_http_main) ServeHTTP(w http.ResponseWriter, req *http.Re
|
|||||||
|
|
||||||
_, err = io.Copy(w, resp_body)
|
_, err = io.Copy(w, resp_body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Write(pxy.id, LOG_WARN, "[%s] %s %s %s", req.RemoteAddr, req.Method, req.URL.String(), err.Error())
|
s.log.Write(pxy.Id, LOG_WARN, "[%s] %s %s %s", req.RemoteAddr, req.Method, req.RequestURI, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: handle trailers
|
// TODO: handle trailers
|
||||||
@ -438,7 +448,7 @@ oops:
|
|||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
|
|
||||||
func (pxy *server_proxy_http_wpx) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
|
func (pxy *server_pxy_http_wpx) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
|
||||||
var status_code int
|
var status_code int
|
||||||
// var err error
|
// var err error
|
||||||
|
|
||||||
@ -454,24 +464,19 @@ func (pxy *server_proxy_http_wpx) ServeHTTP(w http.ResponseWriter, req *http.Req
|
|||||||
}
|
}
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
|
|
||||||
type server_proxy_xterm_session_info struct {
|
func (pxy *server_pxy_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
|
||||||
ConnId string
|
|
||||||
RouteId string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *server_proxy_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
|
|
||||||
var s *Server
|
var s *Server
|
||||||
var status_code int
|
var status_code int
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
s = pxy.s
|
s = pxy.S
|
||||||
|
|
||||||
switch pxy.file {
|
switch pxy.file {
|
||||||
case "xterm.js":
|
case "xterm.js":
|
||||||
status_code = write_js_resp_header(w, http.StatusOK)
|
status_code = WriteJsRespHeader(w, http.StatusOK)
|
||||||
w.Write(xterm_js)
|
w.Write(xterm_js)
|
||||||
case "xterm-addon-fit.js":
|
case "xterm-addon-fit.js":
|
||||||
status_code = write_js_resp_header(w, http.StatusOK)
|
status_code = WriteJsRespHeader(w, http.StatusOK)
|
||||||
w.Write(xterm_addon_fit_js)
|
w.Write(xterm_addon_fit_js)
|
||||||
case "xterm.css":
|
case "xterm.css":
|
||||||
status_code = WriteCssRespHeader(w, http.StatusOK)
|
status_code = WriteCssRespHeader(w, http.StatusOK)
|
||||||
@ -483,13 +488,13 @@ func (pxy *server_proxy_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.R
|
|||||||
|
|
||||||
// this endpoint is registered for /_ssh/{conn_id}/{route_id}/ under pxy.
|
// this endpoint is registered for /_ssh/{conn_id}/{route_id}/ under pxy.
|
||||||
// and for /_ssh/{port_id} under wpx.
|
// and for /_ssh/{port_id} under wpx.
|
||||||
if pxy.id == HS_ID_WPX {
|
if pxy.Id == HS_ID_WPX {
|
||||||
conn_id = req.PathValue("port_id")
|
conn_id = req.PathValue("port_id")
|
||||||
route_id = PORT_ID_MARKER
|
route_id = PORT_ID_MARKER
|
||||||
_, err = s.FindServerRouteByIdStr(conn_id, route_id)
|
_, err = s.FindServerRouteByIdStr(conn_id, route_id)
|
||||||
if err != nil && pxy.s.wpx_foreign_port_proxy_maker != nil {
|
if err != nil && s.wpx_foreign_port_proxy_maker != nil {
|
||||||
_, err = pxy.s.wpx_foreign_port_proxy_maker("ssh", conn_id)
|
_, err = s.wpx_foreign_port_proxy_maker("ssh", conn_id)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
conn_id = req.PathValue("conn_id")
|
conn_id = req.PathValue("conn_id")
|
||||||
route_id = req.PathValue("route_id")
|
route_id = req.PathValue("route_id")
|
||||||
@ -512,7 +517,8 @@ func (pxy *server_proxy_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.R
|
|||||||
} else {
|
} else {
|
||||||
status_code = WriteHtmlRespHeader(w, http.StatusOK)
|
status_code = WriteHtmlRespHeader(w, http.StatusOK)
|
||||||
tmpl.Execute(w,
|
tmpl.Execute(w,
|
||||||
&server_proxy_xterm_session_info{
|
&xterm_session_info{
|
||||||
|
Mode: "ssh",
|
||||||
ConnId: conn_id,
|
ConnId: conn_id,
|
||||||
RouteId: route_id,
|
RouteId: route_id,
|
||||||
})
|
})
|
||||||
@ -528,8 +534,17 @@ func (pxy *server_proxy_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.R
|
|||||||
case "_forbidden":
|
case "_forbidden":
|
||||||
status_code = WriteEmptyRespHeader(w, http.StatusForbidden)
|
status_code = WriteEmptyRespHeader(w, http.StatusForbidden)
|
||||||
|
|
||||||
default:
|
case "_notfound":
|
||||||
status_code = WriteEmptyRespHeader(w, http.StatusNotFound)
|
status_code = WriteEmptyRespHeader(w, http.StatusNotFound)
|
||||||
|
|
||||||
|
default:
|
||||||
|
if strings.HasPrefix(pxy.file, "_redir:") {
|
||||||
|
status_code = http.StatusMovedPermanently
|
||||||
|
w.Header().Set("Location", pxy.file[7:])
|
||||||
|
w.WriteHeader(status_code)
|
||||||
|
} else {
|
||||||
|
status_code = WriteEmptyRespHeader(w, http.StatusNotFound)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//done:
|
//done:
|
||||||
@ -540,31 +555,20 @@ oops:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
|
type server_pxy_ssh_ws struct {
|
||||||
type server_proxy_ssh_ws struct {
|
S *Server
|
||||||
s *Server
|
|
||||||
ws *websocket.Conn
|
ws *websocket.Conn
|
||||||
id string
|
Id string
|
||||||
}
|
}
|
||||||
|
|
||||||
type json_ssh_ws_event struct {
|
func (pxy *server_pxy_ssh_ws) Identity() string {
|
||||||
Type string `json:"type"`
|
return pxy.Id
|
||||||
Data []string `json:"data"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: put this task to sync group.
|
// TODO: put this task to sync group.
|
||||||
// TODO: put the above proxy task to sync group too.
|
// TODO: put the above proxy task to sync group too.
|
||||||
|
|
||||||
func (pxy *server_proxy_ssh_ws) send_ws_data(ws *websocket.Conn, type_val string, data string) error {
|
func (pxy *server_pxy_ssh_ws) connect_ssh(ctx context.Context, username string, password string, r *ServerRoute) (*ssh.Client, *ssh.Session, io.Writer, io.Reader, error) {
|
||||||
var msg []byte
|
|
||||||
var err error
|
|
||||||
|
|
||||||
msg, err = json.Marshal(json_ssh_ws_event{Type: type_val, Data: []string{ data } })
|
|
||||||
if err == nil { err = websocket.Message.Send(ws, msg) }
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *server_proxy_ssh_ws) connect_ssh (ctx context.Context, username string, password string, r *ServerRoute) ( *ssh.Client, *ssh.Session, io.Writer, io.Reader, error) {
|
|
||||||
var cc *ssh.ClientConfig
|
var cc *ssh.ClientConfig
|
||||||
var addr *net.TCPAddr
|
var addr *net.TCPAddr
|
||||||
var dialer *net.Dialer
|
var dialer *net.Dialer
|
||||||
@ -578,6 +582,10 @@ func (pxy *server_proxy_ssh_ws) connect_ssh (ctx context.Context, username strin
|
|||||||
var out io.Reader // ooutput from target
|
var out io.Reader // ooutput from target
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
// [NOTE]
|
||||||
|
// There is no authentication implemented for this websocket endpoint
|
||||||
|
// I suppose authentication should be done at the ssh layer.
|
||||||
|
// However, this can open doors to DoS attacks.
|
||||||
cc = &ssh.ClientConfig{
|
cc = &ssh.ClientConfig{
|
||||||
User: username,
|
User: username,
|
||||||
Auth: []ssh.AuthMethod{ ssh.Password(password) },
|
Auth: []ssh.AuthMethod{ ssh.Password(password) },
|
||||||
@ -625,9 +633,10 @@ oops:
|
|||||||
return nil, nil, nil, nil, err
|
return nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *server_proxy_ssh_ws) ServeWebsocket(ws *websocket.Conn) {
|
func (pxy *server_pxy_ssh_ws) ServeWebsocket(ws *websocket.Conn) (int, error) {
|
||||||
var s *Server
|
var s *Server
|
||||||
var req *http.Request
|
var req *http.Request
|
||||||
|
var port_id string
|
||||||
var conn_id string
|
var conn_id string
|
||||||
var route_id string
|
var route_id string
|
||||||
var r *ServerRoute
|
var r *ServerRoute
|
||||||
@ -640,21 +649,28 @@ func (pxy *server_proxy_ssh_ws) ServeWebsocket(ws *websocket.Conn) {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var conn_ready_chan chan bool
|
var conn_ready_chan chan bool
|
||||||
var connect_ssh_ctx context.Context
|
var connect_ssh_ctx context.Context
|
||||||
var connect_ssh_cancel context.CancelFunc
|
var connect_ssh_cancel Atom[context.CancelFunc]
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
s = pxy.s
|
s = pxy.S
|
||||||
req = ws.Request()
|
req = ws.Request()
|
||||||
conn_ready_chan = make(chan bool, 3)
|
conn_ready_chan = make(chan bool, 3)
|
||||||
|
|
||||||
|
port_id = req.PathValue("port_id")
|
||||||
conn_id = req.PathValue("conn_id")
|
conn_id = req.PathValue("conn_id")
|
||||||
route_id = req.PathValue("route_id")
|
route_id = req.PathValue("route_id")
|
||||||
|
if port_id != "" && conn_id == "" && route_id == "" {
|
||||||
|
// called using the wpx endpoint. pxy.Id must be HS_ID_WPX
|
||||||
|
conn_id = port_id
|
||||||
|
route_id = PORT_ID_MARKER
|
||||||
|
}
|
||||||
|
|
||||||
r, err = s.FindServerRouteByIdStr(conn_id, route_id)
|
r, err = s.FindServerRouteByIdStr(conn_id, route_id)
|
||||||
if err != nil && route_id == PORT_ID_MARKER && pxy.s.wpx_foreign_port_proxy_maker != nil {
|
if err != nil && route_id == PORT_ID_MARKER && s.wpx_foreign_port_proxy_maker != nil {
|
||||||
var pi *ServerRouteProxyInfo
|
var pi *ServerRouteProxyInfo
|
||||||
pi, err = pxy.s.wpx_foreign_port_proxy_maker("ssh", conn_id)
|
pi, err = s.wpx_foreign_port_proxy_maker("ssh", conn_id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.send_ws_data(ws, "error", err.Error())
|
send_ws_data_for_xterm(ws, "error", err.Error())
|
||||||
goto done
|
goto done
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -666,7 +682,7 @@ func (pxy *server_proxy_ssh_ws) ServeWebsocket(ws *websocket.Conn) {
|
|||||||
r = proxy_info_to_server_route(pi)
|
r = proxy_info_to_server_route(pi)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.send_ws_data(ws, "error", err.Error())
|
send_ws_data_for_xterm(ws, "error", err.Error())
|
||||||
goto done
|
goto done
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -679,27 +695,27 @@ func (pxy *server_proxy_ssh_ws) ServeWebsocket(ws *websocket.Conn) {
|
|||||||
|
|
||||||
conn_ready = <-conn_ready_chan
|
conn_ready = <-conn_ready_chan
|
||||||
if conn_ready { // connected
|
if conn_ready { // connected
|
||||||
var buf []byte
|
var buf [2048]byte
|
||||||
var n int
|
var n int
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
s.stats.ssh_proxy_sessions.Add(1)
|
s.stats.ssh_proxy_sessions.Add(1)
|
||||||
buf = make([]byte, 2048)
|
|
||||||
for {
|
for {
|
||||||
n, err = out.Read(buf)
|
n, err = out.Read(buf[:])
|
||||||
if err != nil {
|
|
||||||
if err != io.EOF {
|
|
||||||
s.log.Write(pxy.id, LOG_ERROR, "Read from SSH stdout error - %s", err.Error())
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
err = pxy.send_ws_data(ws, "iov", string(buf[:n]))
|
var err2 error
|
||||||
if err != nil {
|
err2 = send_ws_data_for_xterm(ws, "iov", string(buf[:n]))
|
||||||
s.log.Write(pxy.id, LOG_ERROR, "Failed to send to websocket - %s", err.Error())
|
if err2 != nil {
|
||||||
|
s.log.Write(pxy.Id, LOG_ERROR, "[%s] Failed to send to websocket - %s", req.RemoteAddr, err2.Error())
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
s.log.Write(pxy.Id, LOG_ERROR, "[%s] Failed to read from SSH stdout - %s", req.RemoteAddr, err.Error())
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s.stats.ssh_proxy_sessions.Add(-1)
|
s.stats.ssh_proxy_sessions.Add(-1)
|
||||||
}
|
}
|
||||||
@ -712,16 +728,19 @@ ws_recv_loop:
|
|||||||
if err != nil { goto done }
|
if err != nil { goto done }
|
||||||
|
|
||||||
if len(msg) > 0 {
|
if len(msg) > 0 {
|
||||||
var ev json_ssh_ws_event
|
var ev json_xterm_ws_event
|
||||||
err = json.Unmarshal(msg, &ev)
|
err = json.Unmarshal(msg, &ev)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
switch ev.Type {
|
switch ev.Type {
|
||||||
case "open":
|
case "open":
|
||||||
if sess == nil && len(ev.Data) == 2 {
|
if sess == nil && len(ev.Data) == 2 {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
|
||||||
username = string(ev.Data[0])
|
username = string(ev.Data[0])
|
||||||
password = string(ev.Data[1])
|
password = string(ev.Data[1])
|
||||||
|
|
||||||
connect_ssh_ctx, connect_ssh_cancel = context.WithTimeout(req.Context(), 10 * time.Second) // TODO: configurable timeout
|
connect_ssh_ctx, cancel = context.WithTimeout(req.Context(), 10 * time.Second) // TODO: configurable timeout
|
||||||
|
connect_ssh_cancel.Set(cancel)
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
@ -730,26 +749,27 @@ ws_recv_loop:
|
|||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
c, sess, in, out, err = pxy.connect_ssh(connect_ssh_ctx, username, password, r)
|
c, sess, in, out, err = pxy.connect_ssh(connect_ssh_ctx, username, password, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Write(pxy.id, LOG_ERROR, "failed to connect ssh - %s", err.Error())
|
s.log.Write(pxy.Id, LOG_ERROR, "[%s] Failed to connect ssh - %s", req.RemoteAddr, err.Error())
|
||||||
pxy.send_ws_data(ws, "error", err.Error())
|
send_ws_data_for_xterm(ws, "error", err.Error())
|
||||||
ws.Close() // dirty way to flag out the error
|
ws.Close() // dirty way to flag out the error
|
||||||
} else {
|
} else {
|
||||||
err = pxy.send_ws_data(ws, "status", "opened")
|
err = send_ws_data_for_xterm(ws, "status", "opened")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Write(pxy.id, LOG_ERROR, "Failed to write opened event to websocket - %s", err.Error())
|
s.log.Write(pxy.Id, LOG_ERROR, "[%s] Failed to write opened event to websocket - %s", req.RemoteAddr, err.Error())
|
||||||
ws.Close() // dirty way to flag out the error
|
ws.Close() // dirty way to flag out the error
|
||||||
} else {
|
} else {
|
||||||
|
s.log.Write(pxy.Id, LOG_DEBUG, "[%s] Opened SSH session", req.RemoteAddr)
|
||||||
conn_ready_chan <- true
|
conn_ready_chan <- true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
connect_ssh_cancel()
|
(connect_ssh_cancel.Get())()
|
||||||
connect_ssh_cancel = nil
|
connect_ssh_cancel.Set(nil) // @@@ use atomic
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
case "close":
|
case "close":
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
cancel = connect_ssh_cancel // is it a good way to avoid mutex?
|
cancel = connect_ssh_cancel.Get() // is it a good way to avoid mutex against Set() marked with @@@ above?
|
||||||
if cancel != nil { cancel() }
|
if cancel != nil { cancel() }
|
||||||
break ws_recv_loop
|
break ws_recv_loop
|
||||||
|
|
||||||
@ -768,7 +788,7 @@ ws_recv_loop:
|
|||||||
rows, _ = strconv.Atoi(ev.Data[0])
|
rows, _ = strconv.Atoi(ev.Data[0])
|
||||||
cols, _ = strconv.Atoi(ev.Data[1])
|
cols, _ = strconv.Atoi(ev.Data[1])
|
||||||
sess.WindowChange(rows, cols)
|
sess.WindowChange(rows, cols)
|
||||||
s.log.Write(pxy.id, LOG_DEBUG, "Resized terminal to %d,%d", rows, cols)
|
s.log.Write(pxy.Id, LOG_DEBUG, "[%s] Resized terminal to %d,%d", req.RemoteAddr, rows, cols)
|
||||||
// ignore error
|
// ignore error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -777,7 +797,7 @@ ws_recv_loop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sess != nil {
|
if sess != nil {
|
||||||
err = pxy.send_ws_data(ws, "status", "closed")
|
err = send_ws_data_for_xterm(ws, "status", "closed")
|
||||||
if err != nil { goto done }
|
if err != nil { goto done }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -787,9 +807,7 @@ done:
|
|||||||
if sess != nil { sess.Close() }
|
if sess != nil { sess.Close() }
|
||||||
if c != nil { c.Close() }
|
if c != nil { c.Close() }
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
if err != nil {
|
s.log.Write(pxy.Id, LOG_DEBUG, "[%s] Ended SSH Session", req.RemoteAddr)
|
||||||
s.log.Write(pxy.id, LOG_ERROR, "[%s] %s %s - %s", req.RemoteAddr, req.Method, req.URL.String(), err.Error())
|
|
||||||
} else {
|
return http.StatusOK, err
|
||||||
s.log.Write(pxy.id, LOG_DEBUG, "[%s] %s %s - ended", req.RemoteAddr, req.Method, req.URL.String())
|
|
||||||
}
|
|
||||||
}
|
}
|
330
server-rpx.go
Normal file
330
server-rpx.go
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
package hodu
|
||||||
|
|
||||||
|
import "bufio"
|
||||||
|
import "bytes"
|
||||||
|
import "errors"
|
||||||
|
import "fmt"
|
||||||
|
import "io"
|
||||||
|
import "net"
|
||||||
|
import "net/http"
|
||||||
|
import "strconv"
|
||||||
|
import "strings"
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type server_rpx struct {
|
||||||
|
S *Server
|
||||||
|
Id string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
func (rpx *server_rpx) Identity() string {
|
||||||
|
return rpx.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rpx *server_rpx) Cors(req *http.Request) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rpx *server_rpx) Authenticate(req *http.Request) (int, string) {
|
||||||
|
return http.StatusOK, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rpx *server_rpx) get_client_token(req *http.Request) string {
|
||||||
|
var val string
|
||||||
|
|
||||||
|
// TODO: enhance this client token extraction logic with some expression language?
|
||||||
|
val = req.Header.Get(rpx.S.Cfg.RpxClientTokenAttrName)
|
||||||
|
if val == "" { val = req.Host }
|
||||||
|
|
||||||
|
if rpx.S.Cfg.RpxClientTokenRegex != nil {
|
||||||
|
val = get_regex_submatch(rpx.S.Cfg.RpxClientTokenRegex, val, rpx.S.Cfg.RpxClientTokenSubmatchIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rpx* server_rpx) handle_header_data(rpx_id uint64, data []byte, w http.ResponseWriter) (int, error) {
|
||||||
|
var sc *bufio.Scanner
|
||||||
|
var line string
|
||||||
|
var flds []string
|
||||||
|
var status_code int
|
||||||
|
var err error
|
||||||
|
|
||||||
|
sc = bufio.NewScanner(bytes.NewReader(data))
|
||||||
|
sc.Scan()
|
||||||
|
line = sc.Text()
|
||||||
|
|
||||||
|
flds = strings.Fields(line)
|
||||||
|
if (len(flds) < 2) { // i care about the status code..
|
||||||
|
return http.StatusBadGateway, fmt.Errorf("invalid response status for rpx(%d) - %s", rpx_id, line)
|
||||||
|
}
|
||||||
|
status_code, err = strconv.Atoi(flds[1])
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusBadGateway, fmt.Errorf("invalid response code for rpx(%d) - %s", rpx_id, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
for sc.Scan() {
|
||||||
|
line = sc.Text()
|
||||||
|
if line == "" { break }
|
||||||
|
flds = strings.SplitN(line, ":", 2)
|
||||||
|
if len(flds) == 2 {
|
||||||
|
w.Header().Add(strings.TrimSpace(flds[0]), strings.TrimSpace(flds[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = sc.Err()
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusBadGateway, fmt.Errorf("failed to parse response for rpx(%d) - %s", rpx_id, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(status_code)
|
||||||
|
return status_code, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rpx *server_rpx) handle_response(srpx *ServerRpx, req *http.Request, w http.ResponseWriter, ws_upgrade bool, wg *sync.WaitGroup) {
|
||||||
|
var start_resp []byte
|
||||||
|
var status_code int
|
||||||
|
var buf [4096]byte
|
||||||
|
var n int
|
||||||
|
var wr io.Writer
|
||||||
|
var wrote_br_chan bool
|
||||||
|
var err error
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case start_resp = <- srpx.start_chan:
|
||||||
|
// received the header. ready to proceed to the body
|
||||||
|
// do nothing. just continue
|
||||||
|
status_code, err = rpx.handle_header_data(srpx.id, start_resp, w)
|
||||||
|
if err != nil { goto done }
|
||||||
|
|
||||||
|
case <- srpx.done_chan:
|
||||||
|
err = fmt.Errorf("rpx(%d) terminated before receiving header", srpx.id)
|
||||||
|
status_code = http.StatusBadGateway
|
||||||
|
goto done
|
||||||
|
case <- req.Context().Done():
|
||||||
|
err = fmt.Errorf("rpx(%d) terminated before receiving header - %s", srpx.id, req.Context().Err().Error())
|
||||||
|
status_code = http.StatusBadGateway
|
||||||
|
goto done
|
||||||
|
|
||||||
|
// no default. block
|
||||||
|
}
|
||||||
|
|
||||||
|
if ws_upgrade && status_code == http.StatusSwitchingProtocols {
|
||||||
|
var hijk http.Hijacker
|
||||||
|
var conn net.Conn
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
hijk, ok = w.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("failed to upgrade rpx(%d) - not a hijacker", srpx.id)
|
||||||
|
status_code = http.StatusInternalServerError
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, _, err = hijk.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to upgrade rpx(%d) - %s", srpx.id, err.Error())
|
||||||
|
status_code = http.StatusInternalServerError
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
|
||||||
|
// websocket upgrade is successful
|
||||||
|
srpx.br = conn
|
||||||
|
srpx.br_chan <- true // inform another goroutine that the protocol switching is completed.
|
||||||
|
wrote_br_chan = true
|
||||||
|
|
||||||
|
wr = conn
|
||||||
|
} else {
|
||||||
|
if ws_upgrade {
|
||||||
|
srpx.br_chan <- false
|
||||||
|
wrote_br_chan = true
|
||||||
|
} // indicate upgrade failure
|
||||||
|
wr = w
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err = srpx.pr.Read(buf[:])
|
||||||
|
if n > 0 {
|
||||||
|
var err2 error
|
||||||
|
_, err2 = wr.Write(buf[:n])
|
||||||
|
if err2 != nil {
|
||||||
|
err = err2
|
||||||
|
status_code = http.StatusInternalServerError
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
status_code = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
// just send another in case the code got jump into this part for an error
|
||||||
|
// may not be consumed but the channel is large enough for redundant data
|
||||||
|
srpx.resp_status_code = status_code
|
||||||
|
srpx.resp_error = err
|
||||||
|
|
||||||
|
if ws_upgrade && !wrote_br_chan {
|
||||||
|
srpx.br_chan <- false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rpx *server_rpx) alloc_server_rpx(cts *ServerConn, req *http.Request) (*ServerRpx, error) {
|
||||||
|
var srpx *ServerRpx
|
||||||
|
var start_id uint64
|
||||||
|
var assigned_id uint64
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
cts.rpx_mtx.Lock()
|
||||||
|
start_id = cts.rpx_next_id
|
||||||
|
for {
|
||||||
|
_, ok = cts.rpx_map[cts.rpx_next_id]
|
||||||
|
if !ok {
|
||||||
|
assigned_id = cts.rpx_next_id
|
||||||
|
cts.rpx_next_id++
|
||||||
|
if cts.rpx_next_id == 0 { cts.rpx_next_id++ }
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cts.rpx_next_id++
|
||||||
|
if cts.rpx_next_id == 0 { cts.rpx_next_id++ }
|
||||||
|
if cts.rpx_next_id == start_id {
|
||||||
|
// unlikely to happen but it cycled through the whole range.
|
||||||
|
cts.rpx_mtx.Unlock()
|
||||||
|
return nil, fmt.Errorf("failed to assign id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
srpx = &ServerRpx{
|
||||||
|
id: assigned_id,
|
||||||
|
start_chan: make(chan []byte, 5),
|
||||||
|
done_chan: make(chan bool, 5),
|
||||||
|
br_chan: make(chan bool, 5),
|
||||||
|
}
|
||||||
|
srpx.br = req.Body
|
||||||
|
srpx.pr, srpx.pw = io.Pipe()
|
||||||
|
cts.rpx_map[assigned_id] = srpx
|
||||||
|
|
||||||
|
cts.rpx_mtx.Unlock()
|
||||||
|
cts.S.stats.rpx_sessions.Add(1)
|
||||||
|
return srpx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rpx *server_rpx) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
|
||||||
|
var s *Server
|
||||||
|
var client_token string
|
||||||
|
var start_sent bool
|
||||||
|
var cts *ServerConn
|
||||||
|
var status_code int
|
||||||
|
var srpx *ServerRpx
|
||||||
|
var ws_upgrade bool
|
||||||
|
var buf [4096]byte
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var err error
|
||||||
|
|
||||||
|
s = rpx.S
|
||||||
|
client_token = rpx.get_client_token(req)
|
||||||
|
cts = s.FindServerConnByClientToken(client_token)
|
||||||
|
if cts == nil {
|
||||||
|
status_code = WriteEmptyRespHeader(w, http.StatusNotFound)
|
||||||
|
err = fmt.Errorf("unknown client token - %s", client_token)
|
||||||
|
goto oops
|
||||||
|
}
|
||||||
|
|
||||||
|
srpx, err = rpx.alloc_server_rpx(cts, req)
|
||||||
|
if err != nil {
|
||||||
|
status_code = WriteEmptyRespHeader(w, http.StatusServiceUnavailable)
|
||||||
|
err = fmt.Errorf("unable to allocate rpx - %s", err.Error())
|
||||||
|
goto oops
|
||||||
|
}
|
||||||
|
|
||||||
|
// arrange to clear the rpx_map entry when this function exits
|
||||||
|
defer func() {
|
||||||
|
cts.rpx_mtx.Lock()
|
||||||
|
delete(cts.rpx_map, srpx.id)
|
||||||
|
cts.rpx_mtx.Unlock()
|
||||||
|
cts.S.stats.rpx_sessions.Add(-1)
|
||||||
|
}()
|
||||||
|
|
||||||
|
ws_upgrade = strings.EqualFold(req.Header.Get("Upgrade"), "websocket") && strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade");
|
||||||
|
if ws_upgrade && req.ContentLength > 0 {
|
||||||
|
// while other webservers are ok with upgrade request with body payload,
|
||||||
|
// this program rejects such a request for impelementation limitation as
|
||||||
|
// it's not dealing with a raw byte but is using the standard web server handler.
|
||||||
|
status_code = WriteEmptyRespHeader(w, http.StatusBadRequest)
|
||||||
|
err = fmt.Errorf("failed to assign id")
|
||||||
|
goto oops
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cts.pss.Send(MakeRpxStartPacket(srpx.id, get_http_req_line_and_headers(req, true)))
|
||||||
|
if err != nil {
|
||||||
|
status_code = WriteEmptyRespHeader(w, http.StatusBadGateway)
|
||||||
|
goto oops
|
||||||
|
}
|
||||||
|
start_sent = true
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go rpx.handle_response(srpx, req, w, ws_upgrade, &wg)
|
||||||
|
|
||||||
|
if ws_upgrade {
|
||||||
|
// wait until the protocol switching is done in rpx.handle_response()
|
||||||
|
var upgraded bool
|
||||||
|
upgraded = <- srpx.br_chan
|
||||||
|
if upgraded {
|
||||||
|
// arrange to close the hijacked connection inside rpx.handle_response()
|
||||||
|
defer srpx.br.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
var n int
|
||||||
|
n, err = srpx.br.Read(buf[:])
|
||||||
|
if n > 0 {
|
||||||
|
var err2 error
|
||||||
|
err2 = cts.pss.Send(MakeRpxDataPacket(srpx.id, buf[:n]))
|
||||||
|
if err2 != nil {
|
||||||
|
status_code = WriteEmptyRespHeader(w, http.StatusBadGateway)
|
||||||
|
goto oops
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
err = cts.pss.Send(MakeRpxEofPacket(srpx.id))
|
||||||
|
if err != nil {
|
||||||
|
status_code = WriteEmptyRespHeader(w, http.StatusBadGateway)
|
||||||
|
goto oops
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
status_code = WriteEmptyRespHeader(w, http.StatusInternalServerError)
|
||||||
|
goto oops
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
if srpx.resp_error != nil {
|
||||||
|
status_code = WriteEmptyRespHeader(w, srpx.resp_status_code)
|
||||||
|
err = srpx.resp_error
|
||||||
|
goto oops
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <- srpx.done_chan:
|
||||||
|
// anything to do?
|
||||||
|
case <- req.Context().Done():
|
||||||
|
// anything to do?
|
||||||
|
// no default. block
|
||||||
|
}
|
||||||
|
|
||||||
|
cts.pss.Send(MakeRpxStopPacket(srpx.id))
|
||||||
|
return srpx.resp_status_code, nil
|
||||||
|
|
||||||
|
oops:
|
||||||
|
if srpx != nil && start_sent { cts.pss.Send(MakeRpxStopPacket(srpx.id)) }
|
||||||
|
return status_code, err
|
||||||
|
}
|
@ -68,7 +68,7 @@ func (t *Transformer) Transform(dst []byte, src []byte, at_eof bool) (int, int,
|
|||||||
err = transform.ErrShortSrc
|
err = transform.ErrShortSrc
|
||||||
|
|
||||||
done:
|
done:
|
||||||
return ndst, nsrc, err
|
return ndst, nsrc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transformer) copy_all(dst []byte, src []byte) (int, error) {
|
func (t *Transformer) copy_all(dst []byte, src []byte) (int, error) {
|
||||||
|
97
xterm.html
97
xterm.html
@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
<title>Terminal</title>
|
<title>Terminal</title>
|
||||||
<link rel="stylesheet" href="/_ssh/xterm.css" />
|
<link rel="stylesheet" href="xterm.css" />
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -86,10 +86,11 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script src="/_ssh/xterm.js"></script>
|
<script src="xterm.js"></script>
|
||||||
<script src="/_ssh/xterm-addon-fit.js"></script>
|
<script src="xterm-addon-fit.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const xt_mode = '{{ .Mode }}';
|
||||||
const conn_id = '{{ .ConnId }}';
|
const conn_id = '{{ .ConnId }}';
|
||||||
const route_id = '{{ .RouteId }}';
|
const route_id = '{{ .RouteId }}';
|
||||||
|
|
||||||
@ -99,12 +100,28 @@ window.onload = function(event) {
|
|||||||
const terminal_status = document.getElementById('terminal-status');
|
const terminal_status = document.getElementById('terminal-status');
|
||||||
const terminal_errmsg = document.getElementById('terminal-errmsg');
|
const terminal_errmsg = document.getElementById('terminal-errmsg');
|
||||||
const terminal_view_container = document.getElementById('terminal-view-container');
|
const terminal_view_container = document.getElementById('terminal-view-container');
|
||||||
|
const terminal_connect = document.getElementById('terminal-connect');
|
||||||
const terminal_disconnect = document.getElementById('terminal-disconnect');
|
const terminal_disconnect = document.getElementById('terminal-disconnect');
|
||||||
const login_container = document.getElementById('login-container');
|
const login_container = document.getElementById('login-container');
|
||||||
const login_form_title = document.getElementById('login-form-title');
|
const login_form_title = document.getElementById('login-form-title');
|
||||||
const login_form = document.getElementById('login-form');
|
const login_form = document.getElementById('login-form');
|
||||||
|
const login_ssh_part = document.getElementById('login-ssh-part');
|
||||||
|
const login_pty_part = document.getElementById('login-pty-part');
|
||||||
const username_field = document.getElementById('username');
|
const username_field = document.getElementById('username');
|
||||||
const password_field= document.getElementById('password');
|
const password_field= document.getElementById('password');
|
||||||
|
const qparams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
if (xt_mode == 'ssh') {
|
||||||
|
login_ssh_part.style.display = 'block';
|
||||||
|
username_field.disabled = false;
|
||||||
|
password_field.disabled = false;
|
||||||
|
login_pty_part.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
login_ssh_part.style.display = 'none';
|
||||||
|
username_field.disabled = true;
|
||||||
|
password_field.disabled = true;
|
||||||
|
login_pty_part.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
const term = new window.Terminal({
|
const term = new window.Terminal({
|
||||||
lineHeight: 1.2,
|
lineHeight: 1.2,
|
||||||
@ -133,11 +150,19 @@ window.onload = function(event) {
|
|||||||
|
|
||||||
let fetch_session_info = async function() {
|
let fetch_session_info = async function() {
|
||||||
let url = window.location.protocol + '//' + window.location.host;
|
let url = window.location.protocol + '//' + window.location.host;
|
||||||
url += `/_ssh/server-conns/${conn_id}/routes/${route_id}`;
|
let pathname = window.location.pathname;
|
||||||
|
//pathname = pathname.replace(/\/$/, '');
|
||||||
|
pathname = pathname.substring(0, pathname.lastIndexOf('/'));
|
||||||
|
url += pathname + `/session-info`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(url);
|
const resp = await fetch(url);
|
||||||
if (!resp.ok) throw new Error(`HTTP error in getting route(${conn_id},${route_id}) info - status ${resp.status}`);
|
if (!resp.ok) {
|
||||||
|
if (xt_mode == 'ssh')
|
||||||
|
throw new Error(`HTTP error in getting route(${conn_id},${route_id}) information - status ${resp.status}`);
|
||||||
|
else
|
||||||
|
throw new Error(`HTTP error in getting session information - status ${resp.status}`);
|
||||||
|
}
|
||||||
const route = await resp.json()
|
const route = await resp.json()
|
||||||
if ('client-peer-name' in route) {
|
if ('client-peer-name' in route) {
|
||||||
set_terminal_target(route['client-peer-name']) // change to the name
|
set_terminal_target(route['client-peer-name']) // change to the name
|
||||||
@ -151,11 +176,15 @@ window.onload = function(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let toggle_login_form = function(visible) {
|
let toggle_login_form = function(visible) {
|
||||||
if (visible) fetch_session_info();
|
if (visible && xt_mode == 'ssh') fetch_session_info();
|
||||||
login_container.style.visibility = (visible? 'visible': 'hidden');
|
login_container.style.visibility = (visible? 'visible': 'hidden');
|
||||||
terminal_disconnect.style.visibility = (visible? 'hidden': 'visible');
|
terminal_disconnect.style.visibility = (visible? 'hidden': 'visible');
|
||||||
if (visible) username_field.focus();
|
if (visible) {
|
||||||
else term.focus();
|
if (xt_mode == 'ssh') username_field.focus();
|
||||||
|
else terminal_connect.focus();
|
||||||
|
} else {
|
||||||
|
term.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle_login_form(true);
|
toggle_login_form(true);
|
||||||
@ -166,12 +195,27 @@ window.onload = function(event) {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
toggle_login_form(false)
|
toggle_login_form(false)
|
||||||
|
|
||||||
const username = username_field.value.trim();
|
let username = '';
|
||||||
const password = password_field.value.trim();
|
let password = '';
|
||||||
|
|
||||||
let prefix = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
if (xt_mode == 'ssh') {
|
||||||
let url = prefix + window.location.host+ `/_ssh-ws/${conn_id}/${route_id}`;
|
username = username_field.value.trim();
|
||||||
const socket = new WebSocket(url)
|
password = password_field.value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefix = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
||||||
|
let pathname = window.location.pathname;
|
||||||
|
//pathname = pathname.replace(/\/$/, '');
|
||||||
|
pathname = pathname.substring(0, pathname.lastIndexOf('/'));
|
||||||
|
let url = prefix + window.location.host + pathname + '/ws';
|
||||||
|
|
||||||
|
if (xt_mode == 'rpty') {
|
||||||
|
// when accessing rpty, the server requires a client token
|
||||||
|
let client_token = qparams.get('client-token');
|
||||||
|
if (client_token != null && client_token != '') url += '?client-token=' + client_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
const socket = new WebSocket(url);
|
||||||
socket.binaryType = 'arraybuffer';
|
socket.binaryType = 'arraybuffer';
|
||||||
|
|
||||||
set_terminal_status('Connecting...', '');
|
set_terminal_status('Connecting...', '');
|
||||||
@ -217,7 +261,8 @@ window.onload = function(event) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
socket.onerror = function(event) {
|
socket.onerror = function(event) {
|
||||||
set_terminal_status('Disconnected', event);
|
//set_terminal_status('Disconnected', event);
|
||||||
|
set_terminal_status('Disconnected', '');
|
||||||
toggle_login_form(true)
|
toggle_login_form(true)
|
||||||
window.onresize = adjust_terminal_size_unconnected;
|
window.onresize = adjust_terminal_size_unconnected;
|
||||||
};
|
};
|
||||||
@ -249,15 +294,21 @@ window.onload = function(event) {
|
|||||||
<div id="login-form-container">
|
<div id="login-form-container">
|
||||||
<div id="login-form-title"></div>
|
<div id="login-form-title"></div>
|
||||||
<form id="login-form">
|
<form id="login-form">
|
||||||
<label>
|
<div id="login-ssh-part" style="display: none;">
|
||||||
Username: <input type="text" id="username" required />
|
<label>
|
||||||
</label>
|
Username: <input type="text" id="username" disabled required />
|
||||||
<br /><br />
|
</label>
|
||||||
<label>
|
<br /><br />
|
||||||
Password: <input type="password" id="password" required />
|
<label>
|
||||||
</label>
|
Password: <input type="password" id="password" disabled required />
|
||||||
<br /><br />
|
</label>
|
||||||
<button type="submit">Connect</button>
|
</div>
|
||||||
|
<div id="login-pty-part" style="display: none;">
|
||||||
|
Click Connect below to start a new terminal session
|
||||||
|
</div>
|
||||||
|
<div id="login-submit-part" style="padding-top: 1em;">
|
||||||
|
<button type="submit" id="terminal-connect">Connect</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user