39 Commits
Author SHA1 Message Date
clowwindy 96a86c028d update CHANGES 2015-02-02 17:29:57 +08:00
clowwindy 2c59ffb952 update rst 2015-02-02 16:42:37 +08:00
clowwindy 27a0c7754d bump 2015-02-02 16:23:17 +08:00
clowwindy 0cd261dd10 catch KeyError 2015-02-02 16:22:56 +08:00
clowwindy fbf15cb942 update command tests 2015-02-02 15:59:44 +08:00
clowwindy 1c81465436 fix command line 2015-02-02 15:57:44 +08:00
clowwindy 73f21ffbf6 do not check config for daemon stop 2015-02-02 15:50:41 +08:00
clowwindy 7aa37cad8e check cipher before daemon start
fix #280
2015-02-02 15:46:22 +08:00
clowwindy ae99698b4e implement --user
fix @278
2015-02-02 15:38:55 +08:00
clowwindy da65d0a2ee update coverage_server 2015-02-01 17:15:10 +08:00
clowwindy 3b7755bd5e fix a travis issue 2015-02-01 09:38:32 +08:00
clowwindy 5a7225c54b fix a problem in test arguments 2015-02-01 09:27:18 +08:00
clowwindy 4aba904d6e fix typo 2015-02-01 09:24:49 +08:00
clowwindy b77f419336 test if localhost is in the default forbidden list 2015-02-01 09:23:45 +08:00
clowwindy c39bbbe237 test invalid CIDR 2015-02-01 09:13:29 +08:00
clowwindy 453a9c61a6 show CIDR error more friendly 2015-02-01 09:09:35 +08:00
clowwindy 54181ef821 Merge pull request #279 from planetmeow/develop/CIDRForbidden
Implement CIDR forbidden blacklist
2015-02-01 09:00:08 +08:00
Sunny 17624d0b99 Fix large file test since forbidden ip list is default to localhost
Use an empty forbidden ip list to override default list should work.
2015-02-01 02:40:48 +08:00
Sunny a0aa9173a8 Fix for Python3 map changed behavior
In Python3, map returns an iterator instead of list in Python2,
which cause map "lazier" than before, wrap with list() force it running.
2015-02-01 02:37:30 +08:00
Sunny 79b9b53dbe Never process empty string and prevent infinite loop
If user provide an empty string as network range, inet_pton will
treate it as an IPv6 unspecified address, it seems a bug but I
can't confirm. Then empty string will be converted to 0, 0 & 1
always be zero, so it caused dead loop.
2015-02-01 01:57:17 +08:00
Sunny 070108f78b Disable forbidden ip feature for local shadowsocks
Since forbidden ip is server-side only, disable it for local-side.
This commit also supress warning about IPv6 loopback because I can
confirm ::1/128 is the only loopback address, not like IPv4.
2015-02-01 01:34:23 +08:00
Sunny b11d848986 Fix for Python3 lambda behavior change
In Python3, lambda no longer support use tuple as syntax.
So, ugly changes is inevitable.
2015-02-01 01:34:18 +08:00
Sunny aa28796524 Make common fit PEP8 2015-02-01 01:34:13 +08:00
Sunny 8af359ae05 Use IPNetwork supporting forbidden ip feature in config utils
This commit also make "forbidden_ip" field available in config file.
If no forbidden ip specified in command line and config file,
default to "127.0.0.0/8,::1".
2015-01-31 22:56:23 +08:00
Sunny 100ebcf064 Add IPNetwork class to support CIDR calculation
Usage:
Use IPNetwork(str|list<str>) to create an IPNetwork object.
Use operator 'in' to determine whether the specified
IP address is in the IP network or not, like:
>>> '192.168.1.1' in IPNetwork('192.168.1.0/24')
True

Both IPv4 and IPv6 address are supported.
Note: When using string to initialize the IPNetwork,
a comma seperated IP network list should be provided.

Currently, IPNetwork just support standard CIDR like:
x.x.x.x/y eg. 192.168.1.0/24
::x/y     eg. ::1/10
If pure IP address was provided,
it will be treated as implicit IP network,
like 192.168.0.0 will be treated as 192.168.0.0/16
and  192.168.1.1 will be treated as 192.168.1.1/32
This implicit translate may cause some unexpected behavior,
like user provide 192.168.2.0 and expect it will be treated as
192.168.2.0/24 but actually it will be translated to
192.168.2.0/23 because there are 9 continuous 0 from right.
In order to avoid confusion, a warning message
will be displayed when pure IP address was provided.

Other variants of CIDR are not supported yet.
2015-01-31 22:56:16 +08:00
Sunny 8783e0e9ae Move is_ip from asyncdns to common
Since implement CIDR forbidden need this function,
move it to common file seems to be a better choice.
2015-01-31 22:56:09 +08:00
clowwindy ada97ab6d9 improve comments 2015-01-31 02:58:40 +08:00
clowwindy 5316b3bf11 update .gitignore 2015-01-28 17:17:05 +08:00
clowwindy 4a2d98b280 update coverage url 2015-01-24 14:43:11 +08:00
clowwindy 51f47ccb91 update coverage url 2015-01-24 14:28:39 +08:00
clowwindy 70ebd2ef28 bump 2015-01-23 17:35:32 +08:00
clowwindy 1f8819f790 fix #270 2015-01-23 17:33:48 +08:00
clowwindy 0e6a4cd8ff output python version in unit tests 2015-01-23 14:06:01 +08:00
clowwindy f4052fbc84 fix MANIFEST.in 2015-01-21 14:32:55 +08:00
clowwindy 5e5d25efd9 Update CONTRIBUTING.md 2015-01-19 16:20:08 +08:00
clowwindy 2e9ce11ea1 bump 2015-01-18 18:32:04 +08:00
clowwindy 6efb3d00e4 fix #264 2015-01-16 18:52:40 +08:00
clowwindy 13413267dc Update README.md 2015-01-16 17:06:48 +08:00
clowwindy bd22e3ef75 try every dll that matches by name in PATH 2015-01-16 16:50:18 +08:00
20 changed files with 283 additions and 110 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ pip-log.txt
# Unit test / coverage reports
htmlcov
.coverage
.coverage*
.tox
#Translations
+7
View File
@@ -24,6 +24,7 @@ function run_test {
return 0
}
python --version
coverage erase
mkdir tmp
run_test pep8 .
@@ -47,6 +48,12 @@ run_test python tests/test.py --with-coverage -b "-m rc4-md5 -k testrc4 -s 127.0
run_test python tests/test.py --with-coverage -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --workers 1" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -qq -b 127.0.0.1"
run_test python tests/test.py --with-coverage --should-fail --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=127.0.0.1,::1,8.8.8.8" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1"
# test localhost is in the forbidden list by default
run_test python tests/test.py --with-coverage --should-fail --tcp-only --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1"
# test localhost is available when forbidden list is empty
run_test python tests/test.py --with-coverage --tcp-only --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1"
if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then
if [ 3 -eq `cat /proc/sys/net/ipv4/tcp_fastopen` ] ; then
# we have to run it twice:
+11
View File
@@ -1,3 +1,14 @@
2.6.7 2015-02-02
- Support --user
- Support CIDR format in --forbidden-ip
- Minor fixes
2.6.6 2015-01-23
- Fix a crash in forbidden list
2.6.5 2015-01-18
- Try both 32 bit and 64 bit dll on Windows
2.6.4 2015-01-14
- Also search lib* when searching libraries
+2
View File
@@ -21,6 +21,8 @@ a pull request, or ask some of your friends to do so.
3. We don't answer questions of any other types here. Since very few people
are watching the issue tracker here, you'll probably get no help from here.
Read [Troubleshooting] and get help from forums or [mailing lists].
4. Issues in languages other than English will be Google translated into English
later.
[Troubleshooting]: https://github.com/clowwindy/shadowsocks/wiki/Troubleshooting
+1 -1
View File
@@ -1,3 +1,3 @@
recursive-include *.py
recursive-include shadowsocks *.py
include README.rst
include LICENSE
+14 -7
View File
@@ -12,28 +12,35 @@ Server
### Install
#### Debian / Ubuntu:
Debian / Ubuntu:
apt-get install python-pip
pip install shadowsocks
#### CentOS:
CentOS:
yum install python-setuptools && easy_install pip
pip install shadowsocks
#### Windows:
Windows:
See [Install Server on Windows]
### Usage
ssserver -p 8000 -k password -m rc4-md5
ssserver -p 443 -k password -m rc4-md5
To run in the background:
ssserver -p 8000 -k password -m rc4-md5 -d start
ssserver -p 8000 -k password -m rc4-md5 -d stop
sudo ssserver -p 443 -k password -m rc4-md5 --user nobody -d start
To stop:
sudo ssserver -d stop
To check the log:
sudo less /var/log/shadowsocks.log
Check all the options via `-h`. You can also use a [Configuration] file
instead.
@@ -70,7 +77,7 @@ Bugs and Issues
[Build Status]: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat
[Configuration]: https://github.com/shadowsocks/shadowsocks/wiki/Configuration-via-Config-File
[Coverage Status]: https://jenkins.shadowvpn.org/result/shadowsocks
[Coverage]: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/htmlcov/index.html
[Coverage]: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/PYENV/py34/label/linux/htmlcov/index.html
[Debian sid]: https://packages.debian.org/unstable/python/shadowsocks
[iOS]: https://github.com/shadowsocks/shadowsocks-iOS/wiki/Help
[Issue Tracker]: https://github.com/shadowsocks/shadowsocks/issues?state=open
+15 -7
View File
@@ -12,7 +12,6 @@ Install
~~~~~~~
Debian / Ubuntu:
^^^^^^^^^^^^^^^^
::
@@ -20,7 +19,6 @@ Debian / Ubuntu:
pip install shadowsocks
CentOS:
^^^^^^^
::
@@ -28,7 +26,6 @@ CentOS:
pip install shadowsocks
Windows:
^^^^^^^^
See `Install Server on
Windows <https://github.com/shadowsocks/shadowsocks/wiki/Install-Shadowsocks-Server-on-Windows>`__
@@ -38,14 +35,25 @@ Usage
::
ssserver -p 8000 -k password -m rc4-md5
ssserver -p 443 -k password -m rc4-md5
To run in the background:
::
ssserver -p 8000 -k password -m rc4-md5 -d start
ssserver -p 8000 -k password -m rc4-md5 -d stop
sudo ssserver -p 443 -k password -m rc4-md5 --user nobody -d start
To stop:
::
sudo ssserver -d stop
To check the log:
::
sudo less /var/log/shadowsocks.log
Check all the options via ``-h``. You can also use a
`Configuration <https://github.com/shadowsocks/shadowsocks/wiki/Configuration-via-Config-File>`__
@@ -88,4 +96,4 @@ Bugs and Issues
.. |Build Status| image:: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat
:target: https://travis-ci.org/shadowsocks/shadowsocks
.. |Coverage Status| image:: https://jenkins.shadowvpn.org/result/shadowsocks
:target: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/htmlcov/index.html
:target: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/PYENV/py34/label/linux/htmlcov/index.html
+1 -1
View File
@@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f:
setup(
name="shadowsocks",
version="2.6.4",
version="2.6.7",
license='MIT',
description="A fast tunnel proxy that help you get through firewalls",
author='clowwindy',
+8 -23
View File
@@ -93,11 +93,12 @@ def build_address(address):
return b''.join(results)
def build_request(address, qtype, request_id):
header = struct.pack('!HBBHHHH', request_id, 1, 0, 1, 0, 0, 0)
def build_request(address, qtype):
request_id = os.urandom(2)
header = struct.pack('!BBHHHH', 1, 0, 1, 0, 0, 0)
addr = build_address(address)
qtype_qclass = struct.pack('!HH', qtype, QCLASS_IN)
return header + addr + qtype_qclass
return request_id + header + addr + qtype_qclass
def parse_ip(addrtype, data, length, offset):
@@ -232,18 +233,6 @@ def parse_response(data):
return None
def is_ip(address):
for family in (socket.AF_INET, socket.AF_INET6):
try:
if type(address) != str:
address = address.decode('utf8')
socket.inet_pton(family, address)
return family
except (TypeError, ValueError, OSError, IOError):
pass
return False
def is_valid_hostname(hostname):
if len(hostname) > 255:
return False
@@ -270,7 +259,6 @@ class DNSResolver(object):
def __init__(self):
self._loop = None
self._request_id = 1
self._hosts = {}
self._hostname_status = {}
self._hostname_to_cb = {}
@@ -296,7 +284,7 @@ class DNSResolver(object):
parts = line.split()
if len(parts) >= 2:
server = parts[1]
if is_ip(server) == socket.AF_INET:
if common.is_ip(server) == socket.AF_INET:
if type(server) != str:
server = server.decode('utf8')
self._servers.append(server)
@@ -316,7 +304,7 @@ class DNSResolver(object):
parts = line.split()
if len(parts) >= 2:
ip = parts[0]
if is_ip(ip):
if common.is_ip(ip):
for i in range(1, len(parts)):
hostname = parts[i]
if hostname:
@@ -412,10 +400,7 @@ class DNSResolver(object):
del self._hostname_status[hostname]
def _send_req(self, hostname, qtype):
self._request_id += 1
if self._request_id > 32768:
self._request_id = 1
req = build_request(hostname, qtype, self._request_id)
req = build_request(hostname, qtype)
for server in self._servers:
logging.debug('resolving %s with type %d using server %s',
hostname, qtype, server)
@@ -426,7 +411,7 @@ class DNSResolver(object):
hostname = hostname.encode('utf8')
if not hostname:
callback(None, Exception('empty hostname'))
elif is_ip(hostname):
elif common.is_ip(hostname):
callback((hostname, hostname), None)
elif hostname in self._hosts:
logging.debug('hit hosts: %s', hostname)
+83
View File
@@ -101,6 +101,18 @@ def inet_pton(family, addr):
raise RuntimeError("What family?")
def is_ip(address):
for family in (socket.AF_INET, socket.AF_INET6):
try:
if type(address) != str:
address = address.decode('utf8')
inet_pton(family, address)
return family
except (TypeError, ValueError, OSError, IOError):
pass
return False
def patch_socket():
if not hasattr(socket, 'inet_pton'):
socket.inet_pton = inet_pton
@@ -172,6 +184,61 @@ def parse_header(data):
return addrtype, to_bytes(dest_addr), dest_port, header_length
class IPNetwork(object):
ADDRLENGTH = {socket.AF_INET: 32, socket.AF_INET6: 128, False: 0}
def __init__(self, addrs):
self._network_list_v4 = []
self._network_list_v6 = []
if type(addrs) == str:
addrs = addrs.split(',')
list(map(self.add_network, addrs))
def add_network(self, addr):
if addr is "":
return
block = addr.split('/')
addr_family = is_ip(block[0])
addr_len = IPNetwork.ADDRLENGTH[addr_family]
if addr_family is socket.AF_INET:
ip, = struct.unpack("!I", socket.inet_aton(block[0]))
elif addr_family is socket.AF_INET6:
hi, lo = struct.unpack("!QQ", inet_pton(addr_family, block[0]))
ip = (hi << 64) | lo
else:
raise Exception("Not a valid CIDR notation: %s" % addr)
if len(block) is 1:
prefix_size = 0
while (ip & 1) == 0 and ip is not 0:
ip >>= 1
prefix_size += 1
logging.warn("You did't specify CIDR routing prefix size for %s, "
"implicit treated as %s/%d" % (addr, addr, addr_len))
elif block[1].isdigit() and int(block[1]) <= addr_len:
prefix_size = addr_len - int(block[1])
ip >>= prefix_size
else:
raise Exception("Not a valid CIDR notation: %s" % addr)
if addr_family is socket.AF_INET:
self._network_list_v4.append((ip, prefix_size))
else:
self._network_list_v6.append((ip, prefix_size))
def __contains__(self, addr):
addr_family = is_ip(addr)
if addr_family is socket.AF_INET:
ip, = struct.unpack("!I", socket.inet_aton(addr))
return any(map(lambda n_ps: n_ps[0] == ip >> n_ps[1],
self._network_list_v4))
elif addr_family is socket.AF_INET6:
hi, lo = struct.unpack("!QQ", inet_pton(addr_family, addr))
ip = (hi << 64) | lo
return any(map(lambda n_ps: n_ps[0] == ip >> n_ps[1],
self._network_list_v6))
else:
return False
def test_inet_conv():
ipv4 = b'8.8.4.4'
b = inet_pton(socket.AF_INET, ipv4)
@@ -198,7 +265,23 @@ def test_pack_header():
assert pack_addr(b'www.google.com') == b'\x03\x0ewww.google.com'
def test_ip_network():
ip_network = IPNetwork('127.0.0.0/24,::ff:1/112,::1,192.168.1.1,192.0.2.0')
assert '127.0.0.1' in ip_network
assert '127.0.1.1' not in ip_network
assert ':ff:ffff' in ip_network
assert '::ffff:1' not in ip_network
assert '::1' in ip_network
assert '::2' not in ip_network
assert '192.168.1.1' in ip_network
assert '192.168.1.2' not in ip_network
assert '192.0.2.1' in ip_network
assert '192.0.3.1' in ip_network # 192.0.2.0 is treated as 192.0.2.0/23
assert 'www.google.com' not in ip_network
if __name__ == '__main__':
test_inet_conv()
test_parse_header()
test_pack_header()
test_ip_network()
+26 -5
View File
@@ -23,9 +23,28 @@
from __future__ import absolute_import, division, print_function, \
with_statement
import os
import logging
def find_library_nt(name):
# modified from ctypes.util
# ctypes.util.find_library just returns first result he found
# but we want to try them all
# because on Windows, users may have both 32bit and 64bit version installed
results = []
for directory in os.environ['PATH'].split(os.pathsep):
fname = os.path.join(directory, name)
if os.path.isfile(fname):
results.append(fname)
if fname.lower().endswith(".dll"):
continue
fname = fname + ".dll"
if os.path.isfile(fname):
results.append(fname)
return results
def find_library(possible_lib_names, search_symbol, library_name):
import ctypes.util
from ctypes import CDLL
@@ -41,9 +60,12 @@ def find_library(possible_lib_names, search_symbol, library_name):
lib_names.append('lib' + lib_name)
for name in lib_names:
path = ctypes.util.find_library(name)
if path:
paths.append(path)
if os.name == "nt":
paths.extend(find_library_nt(name))
else:
path = ctypes.util.find_library(name)
if path:
paths.append(path)
if not paths:
# We may get here when find_library fails because, for example,
@@ -56,8 +78,7 @@ def find_library(possible_lib_names, search_symbol, library_name):
'/usr/local/lib*/lib%s.*' % name,
'/usr/lib*/lib%s.*' % name,
'lib%s.*' % name,
'%s.dll' % name,
'lib%s.dll' % name]
'%s.dll' % name]
for pat in patterns:
files = glob.glob(pat)
+32 -3
View File
@@ -43,9 +43,6 @@ def daemon_exec(config):
command = 'start'
pid_file = config['pid-file']
log_file = config['log-file']
command = common.to_str(command)
pid_file = common.to_str(pid_file)
log_file = common.to_str(log_file)
if command == 'start':
daemon_start(pid_file, log_file)
elif command == 'stop':
@@ -183,3 +180,35 @@ def daemon_stop(pid_file):
sys.exit(1)
print('stopped')
os.unlink(pid_file)
def set_user(username):
if username is None:
return
import pwd
import grp
try:
pwrec = pwd.getpwnam(username)
except KeyError:
logging.error('user not found: %s' % username)
raise
user = pwrec[0]
uid = pwrec[2]
gid = pwrec[3]
cur_uid = os.getuid()
if uid == cur_uid:
return
if cur_uid != 0:
logging.error('can not set user as nonroot user')
# will raise later
# inspired by supervisor
if hasattr(os, 'setgroups'):
groups = [grprec[2] for grprec in grp.getgrall() if user in grprec[3]]
groups.insert(0, gid)
os.setgroups(groups)
os.setgid(gid)
os.setuid(uid)
+4 -6
View File
@@ -30,8 +30,7 @@ import logging
import signal
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../'))
from shadowsocks import utils, daemon, encrypt, eventloop, tcprelay, udprelay,\
asyncdns
from shadowsocks import utils, daemon, eventloop, tcprelay, udprelay, asyncdns
def main():
@@ -49,8 +48,6 @@ def main():
utils.print_shadowsocks()
encrypt.try_cipher(config['password'], config['method'])
try:
logging.info("starting local at %s:%d" %
(config['local_address'], config['local_port']))
@@ -73,13 +70,14 @@ def main():
sys.exit(1)
signal.signal(signal.SIGINT, int_handler)
daemon.set_user(config.get('user', None))
loop.run()
except (KeyboardInterrupt, IOError, OSError) as e:
except Exception as e:
logging.error(e)
if config['verbose']:
import traceback
traceback.print_exc()
os._exit(1)
sys.exit(1)
if __name__ == '__main__':
main()
+5 -5
View File
@@ -30,8 +30,7 @@ import logging
import signal
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../'))
from shadowsocks import utils, daemon, encrypt, eventloop, tcprelay, udprelay,\
asyncdns
from shadowsocks import utils, daemon, eventloop, tcprelay, udprelay, asyncdns
def main():
@@ -57,7 +56,6 @@ def main():
else:
config['port_password'][str(server_port)] = config['password']
encrypt.try_cipher(config['password'], config['method'])
tcp_servers = []
udp_servers = []
dns_resolver = asyncdns.DNSResolver()
@@ -86,13 +84,15 @@ def main():
loop = eventloop.EventLoop()
dns_resolver.add_to_loop(loop)
list(map(lambda s: s.add_to_loop(loop), tcp_servers + udp_servers))
daemon.set_user(config.get('user', None))
loop.run()
except (KeyboardInterrupt, IOError, OSError) as e:
except Exception as e:
logging.error(e)
if config['verbose']:
import traceback
traceback.print_exc()
os._exit(1)
sys.exit(1)
if int(config['workers']) > 1:
if os.name == 'posix':
+15 -15
View File
@@ -43,30 +43,22 @@ TIMEOUT_PRECISION = 4
MSG_FASTOPEN = 0x20000000
# SOCKS CMD defination
# SOCKS command definition
CMD_CONNECT = 1
CMD_BIND = 2
CMD_UDP_ASSOCIATE = 3
# TCP Relay can be either sslocal or ssserver
# for sslocal it is called is_local=True
# for each opening port, we have a TCP Relay
# for each connection, we have a TCP Relay Handler to handle the connection
# for each handler, we have 2 sockets:
# local: connected to the client
# remote: connected to remote server
# for each handler, we have 2 streams:
# upstream: from client to server direction
# read local and write to remote
# downstream: from server to client direction
# read remote and write to local
# for each handler, it could be at one of several stages:
# sslocal:
# as sslocal:
# stage 0 SOCKS hello received from local, send hello to local
# stage 1 addr received from local, query DNS for remote
# stage 2 UDP assoc
@@ -74,7 +66,7 @@ CMD_UDP_ASSOCIATE = 3
# stage 4 still connecting, more data from local received
# stage 5 remote connected, piping local and remote
# ssserver:
# as ssserver:
# stage 0 just jump to stage 1
# stage 1 addr received from local, query DNS for remote
# stage 3 DNS resolved, connect to remote
@@ -89,11 +81,16 @@ STAGE_CONNECTING = 4
STAGE_STREAM = 5
STAGE_DESTROYED = -1
# stream direction
# for each handler, we have 2 stream directions:
# upstream: from client to server direction
# read local and write to remote
# downstream: from server to client direction
# read remote and write to local
STREAM_UP = 0
STREAM_DOWN = 1
# stream wait status, indicating it's waiting for reading, etc
# for each stream, it's waiting for reading, or writing, or both
WAIT_STATUS_INIT = 0
WAIT_STATUS_READING = 1
WAIT_STATUS_WRITING = 2
@@ -112,6 +109,9 @@ class TCPRelayHandler(object):
self._remote_sock = None
self._config = config
self._dns_resolver = dns_resolver
# TCP Relay works as either sslocal or ssserver
# if is_local, this is sslocal
self._is_local = is_local
self._stage = STAGE_INIT
self._encryptor = encrypt.Encryptor(config['password'],
@@ -387,7 +387,7 @@ class TCPRelayHandler(object):
self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING)
self._update_stream(STREAM_DOWN, WAIT_STATUS_READING)
return
except (OSError, IOError) as e:
except Exception as e:
logging.error(e)
if self._config['verbose']:
traceback.print_exc()
+49 -32
View File
@@ -29,7 +29,8 @@ import json
import sys
import getopt
import logging
from shadowsocks.common import to_bytes, to_str
from shadowsocks.common import to_bytes, to_str, IPNetwork
from shadowsocks import encrypt
VERBOSE_LEVEL = 5
@@ -68,7 +69,28 @@ def find_config():
return None
def check_config(config):
def check_config(config, is_local):
if config.get('daemon', None) == 'stop':
# no need to specify configuration for daemon stop
return
if is_local and not config.get('password', None):
logging.error('password not specified')
print_help(is_local)
sys.exit(2)
if not is_local and not config.get('password', None) \
and not config.get('port_password', None):
logging.error('password or port_password not specified')
print_help(is_local)
sys.exit(2)
if 'local_port' in config:
config['local_port'] = int(config['local_port'])
if 'server_port' in config and type(config['server_port']) != list:
config['server_port'] = int(config['server_port'])
if config.get('local_address', '') in [b'0.0.0.0']:
logging.warn('warning: local set to listen on 0.0.0.0, it\'s not safe')
if config.get('server', '') in [b'127.0.0.1', b'localhost']:
@@ -89,7 +111,13 @@ def check_config(config):
if config.get('password') in [b'mypassword']:
logging.error('DON\'T USE DEFAULT PASSWORD! Please change it in your '
'config.json!')
exit(1)
sys.exit(1)
if config.get('user', None) is not None:
if os.name != 'posix':
logging.error('user can be used only on Unix')
sys.exit(1)
encrypt.try_cipher(config['password'], config['method'])
def get_config(is_local):
@@ -97,11 +125,11 @@ def get_config(is_local):
format='%(levelname)-s: %(message)s')
if is_local:
shortopts = 'hd:s:b:p:k:l:m:c:t:vq'
longopts = ['help', 'fast-open', 'pid-file=', 'log-file=']
longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'user=']
else:
shortopts = 'hd:s:p:k:m:c:t:vq'
longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=',
'forbidden-ip=']
'forbidden-ip=', 'user=']
try:
config_path = find_config()
optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
@@ -147,6 +175,8 @@ def get_config(is_local):
config['fast_open'] = True
elif key == '--workers':
config['workers'] = int(value)
elif key == '--user':
config['user'] = to_str(value)
elif key == '--forbidden-ip':
config['forbidden_ip'] = to_str(value).split(',')
elif key in ('-h', '--help'):
@@ -156,11 +186,11 @@ def get_config(is_local):
print_server_help()
sys.exit(0)
elif key == '-d':
config['daemon'] = value
config['daemon'] = to_str(value)
elif key == '--pid-file':
config['pid-file'] = value
config['pid-file'] = to_str(value)
elif key == '--log-file':
config['log-file'] = value
config['log-file'] = to_str(value)
elif key == '-q':
v_count -= 1
config['verbose'] = v_count
@@ -193,25 +223,14 @@ def get_config(is_local):
sys.exit(2)
else:
config['server'] = config.get('server', '0.0.0.0')
try:
config['forbidden_ip'] = \
IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1/128'))
except Exception as e:
logging.error(e)
sys.exit(2)
config['server_port'] = config.get('server_port', 8388)
if is_local and not config.get('password', None):
logging.error('password not specified')
print_help(is_local)
sys.exit(2)
if not is_local and not config.get('password', None) \
and not config.get('port_password', None):
logging.error('password or port_password not specified')
print_help(is_local)
sys.exit(2)
if 'local_port' in config:
config['local_port'] = int(config['local_port'])
if 'server_port' in config and type(config['server_port']) != list:
config['server_port'] = int(config['server_port'])
logging.getLogger('').handlers = []
logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE')
if config['verbose'] >= 2:
@@ -228,7 +247,7 @@ def get_config(is_local):
format='%(asctime)s %(levelname)-8s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
check_config(config)
check_config(config, is_local)
return config
@@ -241,9 +260,7 @@ def print_help(is_local):
def print_local_help():
print('''usage: sslocal [-h] -s SERVER_ADDR [-p SERVER_PORT]
[-b LOCAL_ADDR] [-l LOCAL_PORT] -k PASSWORD [-m METHOD]
[-t TIMEOUT] [-c CONFIG] [--fast-open] [-v] -[d] [-q]
print('''usage: sslocal [OPTION]...
A fast tunnel proxy that helps you bypass firewalls.
You can supply configurations via either config file or command line arguments.
@@ -264,6 +281,7 @@ General options:
-d start/stop/restart daemon mode
--pid-file PID_FILE pid file for daemon mode
--log-file LOG_FILE log file for daemon mode
--user USER username to run as
-v, -vv verbose mode
-q, -qq quiet mode, only show warnings/errors
@@ -272,9 +290,7 @@ Online help: <https://github.com/shadowsocks/shadowsocks>
def print_server_help():
print('''usage: ssserver [-h] [-s SERVER_ADDR] [-p SERVER_PORT] -k PASSWORD
-m METHOD [-t TIMEOUT] [-c CONFIG] [--fast-open]
[--workers WORKERS] [-v] [-d start] [-q]
print('''usage: ssserver [OPTION]...
A fast tunnel proxy that helps you bypass firewalls.
You can supply configurations via either config file or command line arguments.
@@ -295,6 +311,7 @@ General options:
-d start/stop/restart daemon mode
--pid-file PID_FILE pid file for daemon mode
--log-file LOG_FILE log file for daemon mode
--user USER username to run as
-v, -vv verbose mode
-q, -qq quiet mode, only show warnings/errors
+1 -1
View File
@@ -11,7 +11,7 @@ if __name__ == '__main__':
with open('/tmp/%s-coverage' % project, 'rb') as f:
coverage = f.read().strip()
n = int(coverage.strip('%'))
if n > 80:
if n >= 80:
color = 'brightgreen'
else:
color = 'yellow'
+3
View File
@@ -41,6 +41,7 @@ parser.add_argument('-a', '--client-args', type=str, default=None)
parser.add_argument('-b', '--server-args', type=str, default=None)
parser.add_argument('--with-coverage', action='store_true', default=None)
parser.add_argument('--should-fail', action='store_true', default=None)
parser.add_argument('--tcp-only', action='store_true', default=None)
parser.add_argument('--url', type=str, default='http://www.example.com/')
parser.add_argument('--dns', type=str, default='8.8.8.8')
@@ -127,6 +128,8 @@ try:
else:
if r != 0:
sys.exit(1)
if config.tcp_only:
break
p4 = Popen(['socksify', 'dig', '@%s' % config.dns,
'www.google.com'],
stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
+4 -2
View File
@@ -30,11 +30,13 @@ $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d sto
assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -k testrc4 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": server addr not specified"
$LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop
assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": password not specified"
assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " password not specified"
$LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop
assert "$SERVER 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": password or port_password not specified"
assert "$SERVER 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " password or port_password not specified"
$LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop
assert "$SERVER 2>&1 --forbidden-ip 127.0.0.1/4a -m rc4-md5 -k 12345 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": Not a valid CIDR notation: 127.0.0.1/4a"
$LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop
assert_end command
+1 -1
View File
@@ -8,7 +8,7 @@ mkdir -p tmp
$PYTHON shadowsocks/local.py -c tests/aes.json &
LOCAL=$!
$PYTHON shadowsocks/server.py -c tests/aes.json &
$PYTHON shadowsocks/server.py -c tests/aes.json --forbidden-ip "" &
SERVER=$!
sleep 3