Skip to content

Commit 52b8e52

Browse files
committed
New option for logging real client address when behind address
1 parent bcdfe79 commit 52b8e52

File tree

4 files changed

+34
-17
lines changed

4 files changed

+34
-17
lines changed

Diff for: wstan/__init__.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from configparser import ConfigParser, ParsingError
3131
from collections import deque
3232

33-
__version__ = '0.3.1'
33+
__version__ = '0.3.2'
3434

3535
# patch asyncio because "async" will become a keyword sooner or later
3636
asyncio.async_ = getattr(asyncio, 'ensure_future', None) or getattr(asyncio, 'async')
@@ -164,6 +164,8 @@ def load_config():
164164
# server config
165165
parser.add_argument('-t', '--tun-addr', help='listen address of server, overrides URI')
166166
parser.add_argument('-r', '--tun-port', help='listen port of server, overrides URI', type=int)
167+
parser.add_argument('--x-forward', help='Use X-Forwarded-For as client IP address when behind proxy',
168+
default=False, action='store_true')
167169
if len(sys.argv) == 1:
168170
parser.print_help()
169171
sys.exit(1)

Diff for: wstan/autobahn/websocket/protocol.py

+20-3
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151

5252
from urllib import parse as urlparse
5353

54-
wsschemes = ["ws", "wss"]
54+
wsschemes = ("ws", "wss")
5555
urlparse.uses_relative.extend(wsschemes)
5656
urlparse.uses_netloc.extend(wsschemes)
5757
urlparse.uses_params.extend(wsschemes)
@@ -521,7 +521,8 @@ class WebSocketProtocol(object):
521521
'flashSocketPolicy',
522522
'allowedOrigins',
523523
'allowedOriginsPatterns',
524-
'maxConnections']
524+
'maxConnections',
525+
'trustXForwardedFor']
525526
"""
526527
Configuration attributes specific to servers.
527528
"""
@@ -2389,6 +2390,13 @@ def processHandshake(self):
23892390
except Exception as e:
23902391
self.log.warning("can't parse HTTP header: %s" % e)
23912392

2393+
# replace self.peer if the x-forwarded-for header is present and trusted
2394+
#
2395+
if 'x-forwarded-for' in self.http_headers and self.trustXForwardedFor:
2396+
addresses = [x.strip() for x in self.http_headers['x-forwarded-for'].split(',')]
2397+
trusted_addresses = addresses[-self.trustXForwardedFor:]
2398+
self.peer = trusted_addresses[0]
2399+
23922400
# validate WebSocket opening handshake client request
23932401
#
23942402
if self.debug:
@@ -3012,6 +3020,9 @@ def resetProtocolOptions(self):
30123020
# maximum number of concurrent connections
30133021
self.maxConnections = 0
30143022

3023+
# number of trusted web servers in front of this server
3024+
self.trustXForwardedFor = 0
3025+
30153026
def setProtocolOptions(self,
30163027
versions=None,
30173028
webStatus=None,
@@ -3034,7 +3045,8 @@ def setProtocolOptions(self,
30343045
serveFlashSocketPolicy=None,
30353046
flashSocketPolicy=None,
30363047
allowedOrigins=None,
3037-
maxConnections=None):
3048+
maxConnections=None,
3049+
trustXForwardedFor=None):
30383050
"""
30393051
Set WebSocket protocol options used as defaults for new protocol instances.
30403052
@@ -3161,6 +3173,11 @@ def setProtocolOptions(self,
31613173
assert(maxConnections >= 0)
31623174
self.maxConnections = maxConnections
31633175

3176+
if trustXForwardedFor is not None and trustXForwardedFor != self.trustXForwardedFor:
3177+
assert(type(trustXForwardedFor) is int)
3178+
assert(trustXForwardedFor >= 0)
3179+
self.trustXForwardedFor = trustXForwardedFor
3180+
31643181
def getConnectionCount(self):
31653182
"""
31663183
Get number of currently connected clients.

Diff for: wstan/relay.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def _get_digest(dat):
2424
def _on_pushToTunTaskDone(task):
2525
# suppress annoying "CancelledError exception not retrieved" error on Py3.5+
2626
try:
27-
if not isinstance(task.exception(), CancelledError):
27+
if isinstance(task.exception(), CancelledError):
2828
logging.error("pushToTunTask exception: %s" % type(task.exception()))
2929
except CancelledError: # doc says it will raise this if canceled, but...
3030
pass

Diff for: wstan/server.py

+10-12
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,10 @@ class WSTunServerProtocol(WebSocketServerProtocol, RelayMixin):
2222
def __init__(self):
2323
WebSocketServerProtocol.__init__(self)
2424
RelayMixin.__init__(self)
25-
self.clientInfo = None
2625
self.connectTargetTask = None
2726
self._dataToTarget = bytearray()
2827

2928
def onConnect(self, request):
30-
self.clientInfo = '{0}:{1}'.format(*self.transport.get_extra_info('peername'))
3129
# ----- init decryptor -----
3230
if not config.tun_ssl:
3331
if config.compatible:
@@ -60,14 +58,14 @@ def onConnect(self, request):
6058
raise ValueError('wrong command %s' % cmd)
6159
except (ValueError, Base64Error) as e:
6260
logging.error('invalid request: %s (from %s), path: %s' %
63-
(e, self.clientInfo, path))
61+
(e, self.peer, path))
6462
raise ConnectionDeny(400)
6563

6664
if not config.tun_ssl:
6765
# filter replay attack
6866
seen = seenNonceByTime[timestamp // 10]
6967
if nonce in seen:
70-
logging.warning('replay attack detected (from %s)' % self.clientInfo)
68+
logging.warning('replay attack detected (from %s)' % self.peer)
7169
raise ConnectionDeny(400)
7270
seen.add(nonce)
7371

@@ -86,11 +84,11 @@ def onConnect(self, request):
8684

8785
@coroutine
8886
def connectTarget(self, addr, port, data):
89-
logging.info('requested %s <--> %s:%s' % (self.clientInfo, addr, port))
87+
logging.info('requested %s <--> %s:%s' % (self.peer, addr, port))
9088
try:
9189
reader, writer = yield from open_connection(addr, port)
9290
except (ConnectionError, OSError, TimeoutError) as e:
93-
logging.info("can't connect to %s:%s (from %s)" % (addr, port, self.clientInfo))
91+
logging.info("can't connect to %s:%s (from %s)" % (addr, port, self.peer))
9492
return self.resetTunnel(reason="can't connect to target: %s" % e)
9593
self.setProxy(reader, writer)
9694
if data:
@@ -127,15 +125,15 @@ def onResetTunnel(self):
127125
@coroutine
128126
def onMessage(self, dat, isBinary):
129127
if not isBinary:
130-
logging.error('non binary ws message received (from %s)' % self.clientInfo)
128+
logging.error('non binary ws message received (from %s)' % self.peer)
131129
return self.sendClose(3000)
132130

133131
cmd = ord(self.decrypt(dat[:1]))
134132
if cmd == self.CMD_RST:
135133
try:
136134
msg = self.parseResetMessage(dat)
137135
except ValueError as e:
138-
logging.error('invalid reset message: %s (from %s)' % (e, self.clientInfo))
136+
logging.error('invalid reset message: %s (from %s)' % (e, self.peer))
139137
return self.sendClose(3000)
140138
if not msg.startswith(' '):
141139
logging.info('tunnel abnormal reset: %s' % msg)
@@ -146,7 +144,7 @@ def onMessage(self, dat, isBinary):
146144
raise Exception('reset received when not idle')
147145
addr, port, remainData, __ = self.parseRelayHeader(dat)
148146
except Exception as e:
149-
logging.error('invalid request in reused tun: %s (from %s)' % (e, self.clientInfo))
147+
logging.error('invalid request in reused tun: %s (from %s)' % (e, self.peer))
150148
return self.sendClose(3000)
151149
self.connectTargetTask = async_(self.connectTarget(addr, port, remainData))
152150
elif cmd == self.CMD_DAT:
@@ -158,7 +156,7 @@ def onMessage(self, dat, isBinary):
158156
return
159157
self._writer.write(dat)
160158
else:
161-
logging.error('wrong command: %s (from %s)' % (cmd, self.clientInfo))
159+
logging.error('wrong command: %s (from %s)' % (cmd, self.peer))
162160
self.sendClose(3000)
163161

164162
def sendServerStatus(self, redirectUrl=None, redirectAfter=0):
@@ -168,8 +166,7 @@ def onClose(self, wasClean, code, reason, logWarn=True):
168166
"""Logging failed requests."""
169167
logWarn = True
170168
if reason and not self.tunOpen.done():
171-
peer = '{0}:{1}'.format(*self.transport.get_extra_info('peername')) # self.clientInfo is None
172-
logging.warning(reason + ' (from %s)' % peer)
169+
logging.warning(reason + ' (from %s)' % self.peer)
173170
logWarn = False
174171

175172
RelayMixin.onClose(self, wasClean, code, reason, logWarn=logWarn)
@@ -198,6 +195,7 @@ def silent_timeout_err_handler(loop_, context):
198195
factory.autoPingInterval = 400 # only used to clear half-open connections
199196
factory.autoPingTimeout = 30
200197
factory.openHandshakeTimeout = 8 # timeout after TCP established and before succeeded WS handshake
198+
factory.trustXForwardedFor = 1 if config.x_forward else 0
201199
factory.closeHandshakeTimeout = 4
202200

203201

0 commit comments

Comments
 (0)