1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package loopback
import (
"cmp"
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"strings"
"sync"
"time"
"github.com/johnandersen777/socialweb-computer-kcp/internal/contract/component"
"github.com/johnandersen777/socialweb-computer-kcp/internal/contract/ingress"
)
const DefaultHost = "127.0.0.1"
type Config struct {
Host string
Protocol string
}
type Bridge struct {
cfg Config
target ingress.Target
listener net.Listener
server *http.Server
ctx context.Context
cancel context.CancelFunc
done chan struct{}
closeOnce sync.Once
conns sync.WaitGroup
}
func Start(ctx context.Context, cfg Config, target ingress.Target) (*Bridge, error) {
if target == nil {
return nil, errors.New("loopback: the target is not configured")
}
host := strings.TrimSpace(cfg.Host)
host = cmp.Or(host, DefaultHost)
listener, err := net.Listen("tcp", net.JoinHostPort(host, "0"))
if err != nil {
return nil, fmt.Errorf("loopback: listening on %s: %w", host, err)
}
ctx, cancel := context.WithCancel(ctx)
bridge := &Bridge{cfg: cfg, target: target, listener: listener, ctx: ctx, cancel: cancel, done: make(chan struct{})}
switch strings.ToLower(strings.TrimSpace(cfg.Protocol)) {
case component.ProtocolHTTP, component.ProtocolWS:
bridge.server = &http.Server{Handler: target, ReadHeaderTimeout: 10 * time.Second}
go func() {
defer close(bridge.done)
_ = bridge.server.Serve(listener)
}()
case component.ProtocolTCP, component.ProtocolSSH:
go bridge.serveStreams()
default:
cancel()
_ = listener.Close()
return nil, fmt.Errorf("loopback: unknown protocol %q", cfg.Protocol)
}
return bridge, nil
}
func (b *Bridge) Address() string {
return b.listener.Addr().String()
}
func (b *Bridge) serveStreams() {
defer close(b.done)
for {
conn, err := b.listener.Accept()
if err != nil {
return
}
b.conns.Go(func() { b.bridge(conn) })
}
}
func (b *Bridge) bridge(conn net.Conn) {
defer conn.Close()
stop := context.AfterFunc(b.ctx, func() { _ = conn.Close() })
defer stop()
stream, err := b.target.DialStream(b.ctx)
if err != nil {
return
}
defer stream.Close()
done := make(chan struct{}, 2)
go func() {
_, _ = io.Copy(stream, conn)
done <- struct{}{}
}()
go func() {
_, _ = io.Copy(conn, stream)
done <- struct{}{}
}()
<-done
_ = conn.Close()
_ = stream.Close()
<-done
}
func (b *Bridge) Close() error {
b.closeOnce.Do(func() {
b.cancel()
if b.server != nil {
_ = b.server.Close()
return
}
_ = b.listener.Close()
})
<-b.done
b.conns.Wait()
return nil
}