-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathproxy.go
121 lines (96 loc) · 3.16 KB
/
proxy.go
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
package spice
import (
"context"
"fmt"
"net"
"github.com/jsimonetti/go-spice/red"
)
// Proxy is the server object for this spice proxy.
type Proxy struct {
// WithAuthenticator can be provided to implement custom authentication
// By default, "auth-less" no-op mode is enabled.
authenticator map[red.AuthMethod]Authenticator
// WithLogger can be used to provide a custom logger.
// Defaults to a logrus implementation.
log Logger
// WithDialer can be used to provide a custom dialer to reach compute nodes
// the network is always of type 'tcp' and the computeAddress is the compute node
// computeAddress that is return by an Authenticator.
dial func(ctx context.Context, network, addr string) (net.Conn, error)
// sessionTable holds all the sessions for this proxy
sessionTable *sessionTable
// optional function called when main channel is closed
closeCallback func(destination string) error
}
// New returns a new *Proxy with the options applied
func New(options ...Option) (*Proxy, error) {
proxy := &Proxy{}
proxy.authenticator = make(map[red.AuthMethod]Authenticator)
for _, option := range options {
if err := proxy.SetOption(option); err != nil {
return nil, fmt.Errorf("could not set option: %v", err)
}
}
if len(proxy.authenticator) < 1 {
proxy.authenticator[red.AuthMethodSpice] = &noopAuth{}
}
if proxy.log == nil {
proxy.log = defaultLogger()
}
if proxy.dial == nil {
proxy.dial = defaultDialer()
}
proxy.sessionTable = newSessionTable()
return proxy, nil
}
// ListenAndServe is used to create a listener and serve on it
func (p *Proxy) ListenAndServe(network, addr string) error {
l, err := net.Listen(network, addr)
if err != nil {
return err
}
p.log.Debug(fmt.Sprintf("listening on %s", l.Addr().String()))
return p.Serve(l)
}
// Serve is used to serve connections from a listener
func (p *Proxy) Serve(l net.Listener) error {
for {
tenant, err := l.Accept()
if err != nil {
return err
}
p.log.WithFields("tenant", tenant.RemoteAddr().String()).Debug("accepted connection")
go p.ServeConn(tenant)
}
}
// ServeConn is used to serve a single connection.
func (p *Proxy) ServeConn(tenant net.Conn) error {
defer tenant.Close()
handShake, err := newTenantHandshake(p, p.log.WithFields("tenant", tenant.RemoteAddr().String()))
if err != nil {
return err
}
var compute net.Conn
handShake.log.Debug("starting handshake")
for !handShake.Done() {
if compute, err = handShake.clientLinkStage(tenant); err != nil {
handShake.log.WithError(err).Info("handshake failed")
return err
}
}
handShake.log.Info("connection established")
flow := newFlow(tenant, compute)
if err := flow.Pipe(); err != nil {
handShake.log.WithError(err).Error("close error")
}
// if connection was closed and it's the main channel, call the closeCallback
if handShake.channelType == red.ChannelMain && p.closeCallback != nil {
handShake.log.Info("clossing connection of main channel")
if err := p.closeCallback(handShake.destination); err != nil {
handShake.log.WithError(err).Error("error in connection closing callback")
}
}
handShake.log.Info("connection closed")
p.sessionTable.Disconnect(handShake.sessionID)
return nil
}