forked from Psiphon-Labs/psiphon-tunnel-core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
listener.go
156 lines (136 loc) · 5.08 KB
/
listener.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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/*
* Copyright (c) 2020, Psiphon Inc.
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package server
import (
"net"
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/fragmentor"
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
)
// TacticsListener wraps a net.Listener and applies server-side implementation
// of certain tactics parameters to accepted connections. Tactics filtering is
// limited to GeoIP attributes as the client has not yet sent API parameters.
// GeoIP uses the immediate peer IP, and so TacticsListener is suitable only
// for tactics that do not require the original client GeoIP when fronted.
type TacticsListener struct {
net.Listener
support *SupportServices
tunnelProtocol string
geoIPLookup func(IPaddress string) GeoIPData
}
// NewTacticsListener creates a new TacticsListener.
func NewTacticsListener(
support *SupportServices,
listener net.Listener,
tunnelProtocol string,
geoIPLookup func(IPaddress string) GeoIPData) *TacticsListener {
return &TacticsListener{
Listener: listener,
support: support,
tunnelProtocol: tunnelProtocol,
geoIPLookup: geoIPLookup,
}
}
// Accept calls the underlying listener's Accept, and then applies server-side
// tactics to new connections.
func (listener *TacticsListener) Accept() (net.Conn, error) {
for {
// accept may discard a successfully accepted conn. In that case, accept
// returns nil, nil; call accept until either the conn or err is not nil.
conn, err := listener.accept()
if conn != nil || err != nil {
// Don't modify error from net.Listener
return conn, err
}
}
}
func (listener *TacticsListener) accept() (net.Conn, error) {
conn, err := listener.Listener.Accept()
if err != nil {
// Don't modify error from net.Listener
return nil, err
}
// Limitation: RemoteAddr is the immediate peer IP, which is not the original
// client IP in the case of fronting.
geoIPData := listener.geoIPLookup(
common.IPAddressFromAddr(conn.RemoteAddr()))
p, err := listener.support.ServerTacticsParametersCache.Get(geoIPData)
if err != nil {
conn.Close()
return nil, errors.Trace(err)
}
if p.IsNil() {
// No tactics are configured; use the accepted conn without customization.
return conn, nil
}
// Server-side fragmentation may be synchronized with client-side in two ways.
//
// In the OSSH case, replay is always activated and it is seeded using the
// content of the client's OSSH seed message, which is fully delivered before
// the server sends any bytes. SetReplay is deferred until after the seed
// message is read by obfuscator.NewServerObfuscatedSSHConn. doReplay is set
// to true so no seed is set at this time.
//
// SSH lacks the initial obfuscation message, and meek and other protocols
// transmit downstream data before the initial obfuscation message arrives.
// For these protocols, server-side fragmentation will happen, initially,
// with an uncoordinated coin flip, based on server-side tactics
// configuration. For protocols with multiple underlying TCP connections,
// such as meek, the coin flip is performed independently once per
// TCP connection.
//
// The server-side replay mechanism is used to replay successful server-side
// fragmentation for uncoordinated protocols, subject to replay configuration
// parameters. In this case, the replay seed returned by GetReplayFragmentor
// below is applied.
replaySeed, doReplay := listener.support.ReplayCache.GetReplayFragmentor(
listener.tunnelProtocol, geoIPData)
if listener.tunnelProtocol == protocol.TUNNEL_PROTOCOL_OBFUSCATED_SSH {
replaySeed = nil
doReplay = true
}
var newSeed *prng.Seed
if !doReplay {
var err error
newSeed, err = prng.NewSeed()
if err != nil {
log.WithTraceFields(
LogFields{"error": err}).Warning("failed to seed fragmentor PRNG")
return conn, nil
}
}
fragmentorConfig := fragmentor.NewDownstreamConfig(
p, listener.tunnelProtocol, newSeed)
if fragmentorConfig.MayFragment() {
conn = fragmentor.NewConn(
fragmentorConfig,
func(message string) {
log.WithTraceFields(
LogFields{"message": message}).Debug("Fragmentor")
},
conn)
if doReplay && replaySeed != nil {
conn.(common.FragmentorReplayAccessor).SetReplay(
prng.NewPRNGWithSeed(replaySeed))
}
}
return conn, nil
}