1
+ #!/usr/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # Copyright 2015 clowwindy
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may
7
+ # not use this file except in compliance with the License. You may obtain
8
+ # a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
+ # License for the specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ from __future__ import absolute_import , division , print_function , \
19
+ with_statement
20
+
21
+ import errno
22
+ import traceback
23
+ import socket
24
+ import logging
25
+ import json
26
+ import collections
27
+
28
+ from shadowsocks import common , eventloop , tcprelay , udprelay , asyncdns , shell
29
+
30
+
31
+ BUF_SIZE = 2048
32
+
33
+
34
+ class Manager (object ):
35
+
36
+ def __init__ (self , config ):
37
+ self ._config = config
38
+ self ._relays = {} # (tcprelay, udprelay)
39
+ self ._loop = eventloop .EventLoop ()
40
+ self ._dns_resolver = asyncdns .DNSResolver ()
41
+ self ._dns_resolver .add_to_loop (self ._loop )
42
+ self ._control_socket = socket .socket (socket .AF_INET , socket .SOCK_DGRAM ,
43
+ socket .IPPROTO_UDP )
44
+ self ._statistics = collections .defaultdict (int )
45
+ self ._control_client_addr = None
46
+ try :
47
+ self ._control_socket .bind (('127.0.0.1' ,
48
+ int (config ['manager_port' ])))
49
+ self ._control_socket .setblocking (False )
50
+ except (OSError , IOError ) as e :
51
+ logging .error (e )
52
+ logging .error ('can not bind to manager port' )
53
+ exit (1 )
54
+ self ._loop .add (self ._control_socket ,
55
+ eventloop .POLL_IN , self )
56
+
57
+ port_password = config ['port_password' ]
58
+ del config ['port_password' ]
59
+ for port , password in port_password .items ():
60
+ a_config = config .copy ()
61
+ a_config ['server_port' ] = int (port )
62
+ a_config ['password' ] = password
63
+ self .add_port (a_config )
64
+
65
+ def add_port (self , config ):
66
+ port = int (config ['server_port' ])
67
+ servers = self ._relays .get (port , None )
68
+ if servers :
69
+ logging .error ("server already exists at %s:%d" % (config ['server' ],
70
+ port ))
71
+ return
72
+ logging .info ("adding server at %s:%d" % (config ['server' ], port ))
73
+ t = tcprelay .TCPRelay (config , self ._dns_resolver , False )
74
+ u = udprelay .UDPRelay (config , self ._dns_resolver , False )
75
+ t .add_to_loop (self ._loop )
76
+ u .add_to_loop (self ._loop )
77
+ self ._relays [port ] = (t , u )
78
+
79
+ def remove_port (self , config ):
80
+ port = int (config ['server_port' ])
81
+ servers = self ._relays .get (port , None )
82
+ if servers :
83
+ logging .info ("removing server at %s:%d" % (config ['server' ], port ))
84
+ t , u = servers
85
+ t .close (next_tick = False )
86
+ u .close (next_tick = False )
87
+ del self ._relays [port ]
88
+ else :
89
+ logging .error ("server not exist at %s:%d" % (config ['server' ],
90
+ port ))
91
+
92
+ def handle_event (self , sock , fd , event ):
93
+ if sock == self ._control_socket and event == eventloop .POLL_IN :
94
+ data , self ._control_client_addr = sock .recvfrom (BUF_SIZE )
95
+ parsed = self ._parse_command (data )
96
+ if parsed :
97
+ command , config = parsed
98
+ a_config = self ._config .copy ()
99
+ if config :
100
+ a_config .update (config )
101
+ if 'server_port' not in a_config :
102
+ logging .error ('can not find server_port in config' )
103
+ else :
104
+ if command == 'add' :
105
+ self .add_port (a_config )
106
+ elif command == 'remove' :
107
+ self .remove_port (a_config )
108
+ elif command == 'ping' :
109
+ self ._send_control_data (b'pong' )
110
+ else :
111
+ logging .error ('unknown command %s' , command )
112
+
113
+ def _parse_command (self , data ):
114
+ # commands:
115
+ # add: {"server_port": 8000, "password": "foobar"}
116
+ # remove: {"server_port": 8000"}
117
+ data = common .to_str (data )
118
+ parts = data .split (':' , 1 )
119
+ if len (parts ) < 2 :
120
+ return data , None
121
+ command , config_json = parts
122
+ try :
123
+ config = json .loads (config_json )
124
+ return command , config
125
+ except Exception as e :
126
+ logging .error (e )
127
+ return None
128
+
129
+ def handle_periodic (self ):
130
+ # TODO send statistics
131
+ pass
132
+
133
+ def _send_control_data (self , data ):
134
+ if self ._control_client_addr :
135
+ try :
136
+ self ._control_socket .sendto (data , self ._control_client_addr )
137
+ except (socket .error , OSError , IOError ) as e :
138
+ error_no = eventloop .errno_from_exception (e )
139
+ if error_no in (errno .EAGAIN , errno .EINPROGRESS ,
140
+ errno .EWOULDBLOCK ):
141
+ return
142
+ else :
143
+ shell .print_exception (e )
144
+ if self ._config ['verbose' ]:
145
+ traceback .print_exc ()
146
+
147
+ def run (self ):
148
+ self ._loop .run ()
149
+
150
+
151
+ def run (config ):
152
+ Manager (config ).run ()
0 commit comments