2月 21

服务器 自动封锁和解封IP 对付攻击

  服务器流量暴涨,首先通过访问日志查看。每个IP并发不是很高,分布也很广,但是都是只访问主页,可见不是抓取服务导致。通过“netstat -nat|awk ‘{print awk $NF}’|sort|uniq -c|sort -n”t命令查看,连接中还有很多SYN_RECV、TIME_WAIT、ESTABLISHED。看来是小型的攻击。首先是修改web服务的配置,缩减了timeout时间,在nginx上禁止一部分并发比较高的IP,小改系统参数。流量降了20M,但还是太高。nginx虽然禁止了IP,如果用浏览看是一个空白页面,但是每个连接还会产生很小的流量。最后决定将最小的返回值也封掉,直接在iptables上drop掉连接。每个超限IP暂定封2天。写个脚本自动运行。

  此脚本在在centos5.3+python2.6.6使用通过。需要注意,此脚本统计日志完毕会将nginx日志文件清空,如需保留日志,请自行修改。每次加载iptables规则时会清楚上次所有规则,如果有使用其它规则也要自行处理。

#!/bin/python
#-*- coding:utf-8 -*-
# Filename:    drop_ip_iptables.py
# Revision:    1.0
# Date:        2013-2-21
# Author:      simonzhang
# web:         www.simonzhang.net
# Email:       simon-zzm@163.com
### END INIT INFO
import os
import time
from string import strip


#### 参数和脚本中使用到的系统命令
nginx_log = "/usr/local/nginx/logs/nginx.access.log"
# 统计nginx日志中IP访问数量
check_comm = "/bin/cat %s |awk ' ''{print $1}'|sort |uniq -c|sort -n -k1 -r" % nginx_log
# 放在crontab中10分钟跑一次,访问超出n次的全部封掉
overproof = 3000
# 被封地址记录文件
# 文件中记录封IP时间和IP地址。时间单位为秒
lock_ip_list = "/usr/local/nginx/logs/lock_ip_list.txt"
# 被封地址解开时间。时间单位为秒
unlock_time = 3600*24*2 


def manage_lock_ip():
    # 获取当前时间
    get_now_time = int(time.time())
    # 管理日志字典
    man_ip = {}
    # 处理日志中的IP
    try:
        log_file = open('%s' % lock_ip_list, 'rb').readlines()
        for get_ip_info in log_file:
            _get_ip_info = strip(get_ip_info).split(' ')
            man_ip['%s' % _get_ip_info[1]] = int(_get_ip_info[0])
    except:
        exit(0)
    # 清空iptable列表,和ip记录日志
    os.popen('/sbin/iptables -F')
    clean_file = open('%s' % lock_ip_list, 'rb+')
    clean_file.truncate()
    # 开始处理IP,被封没有超时的IP写入iptables和日志中
    log_file = open('%s' % lock_ip_list, 'ab')
    for loop_ip in man_ip.keys():
        if (get_now_time - man_ip[loop_ip]) < unlock_time:
            os.popen('/sbin/iptables -I INPUT -s %s -j DROP' % loop_ip)
            log_file.write('%s %s\n' % (man_ip[loop_ip], loop_ip))
    log_file.close()
        


def main():
    # 已封IP地址字典
    drop_ip_list = {}
    # 加载已封IP日志
    try:
        log_file = open('%s' % lock_ip_list, 'rb').readlines()
        for get_drop_ip_info in log_file:
            _get_drop_ip_info = strip(get_drop_ip_info).split(' ')
            drop_ip_list['%s' % _get_drop_ip_info[1]] = int(_get_drop_ip_info[0])
    except:
        os.mknod('%s' % lock_ip_list)
    # 获取nginx日志中的访问超高的ip并写入日志
    access_high_ip = os.popen('%s' % check_comm).readlines()
    for get_ip_count in access_high_ip:
        try :
            _get_ip_count = strip(get_ip_count).split(' ')
            _get_ip = _get_ip_count[1]
            _get_count = _get_ip_count[0]
        except:
            pass
        if (int(_get_count) > int(overproof)) and (_get_ip not in drop_ip_list.keys()):
            now_time = int(time.time())
            log_file = open('%s' % lock_ip_list, 'ab+')
            log_file.write('%s %s\n' % (now_time, _get_ip))
            log_file.close()
    # 统计完毕清空nginx日志
    log_file = open('%s' % nginx_log, 'wb')
    log_file.truncate()
    # 处理要封的IP和要解开的IP
    manage_lock_ip()


if __name__ == '__main__':
    main()

再补充两条日志分析命令
# 单位时间内统计单个IP只访问首页的数量:
#check_comm = “/bin/cat %s|grep ‘GET / HTTP/1.1’|awk ‘ ”{print $1}’|sort |uniq -c|sort -n -k1 -r” % nginx_log
# 单位时间内统计单个IP访问相同页面的数量
#check_comm = “/bin/cat %s|awk -F'”‘ ‘{print $1 $2}’|awk ‘ ”{print $1″ “$6” “$7” “$8}’|sort -k2,4 -r|uniq -c|sort -n -k1 -r” % nginx_log

源码下载
drop_ip_tables

12月 04

python 源码删除注释并编译成字节码

  上线需要,将py的源码中注释删掉,然后编译成字节码,这样加载速度会比较快。写此脚本主要是为了删除注释。当然如果上线不想放py源码,则在最后增加删除源码即可。我把这个代码起名为咕噜咕噜。python 源码删除注释并编译。

#!/bin/env python
# -*- coding:utf-8 -*-
# -------------------------------
# Filename:    
# Revision:
# Date:        2012-12-3
# Author:      simonzhang
# Email:       simon-zzm@163.com
# Web:         www.simonzhang.net 
# -------------------------------
import os
import re
import sys
import shutil
import compileall


def delete_Notes(py_file):
    # 原始文件只读打开,处理文件追加打开
    _tmp_sr_file = open(py_file, "rb").readlines()
    _tmp_de_file = open("%s.swp" % py_file, "ab")
    _skip_status = 0
    _now_line = 0
    _multi_count = 0 
    # 循环处理
    for line in _tmp_sr_file:
        # 跳过前10行,因为我的开头注释有10行
        if _now_line > 10:
            # 获取开头一位和三位
            try:
                _single_row_notes = line.strip()[0]
            except:
                _single_row_notes = ""
            try: 
                _multi_row_notes = line.strip()[0:3]
            except:
                _multi_row_notes = ""
            # 获取行是否为注释
            if _single_row_notes == "#":
                _skip_status = 1
            elif _multi_row_notes == "'''":
                if _multi_count == 0:
                    _skip_status = 1
                    _multi_count = 1
                else:
                    _skip_status = 1
                    _multi_count = 0
            elif _multi_count == 1:
                _skip_status = 1
            else:
                _skip_status = 0
        else:
            _skip_status = 0
        # 判断是否跳过写入
        if _skip_status == 0:
            _tmp_de_file.write(line)
        _now_line += 1
    _tmp_de_file.close()
    # 处理完毕将临时文件处理为原始文件
    shutil.move("%s.swp" % py_file, py_file)
        

def main():
    _get_src_path = sys.argv[1]
    _get_dec_path = sys.argv[2]
    if os.path.exists(_get_src_path):
        # 拷贝原始文件夹
        shutil.copytree(_get_src_path, _get_dec_path)
        # 删除原始文件中的注释
        find_py_file = re.compile(r"^.*\.py$")
        find_walk = os.walk(_get_dec_path)
        for path,dirs,files in find_walk:
            for f in files:
                if find_py_file.search(f):
                    delete_Notes("%s/%s" % (path, f))
        # 编译成字节码
        compileall.compile_dir(_get_dec_path)
    else:
        print "Path Error!"

if __name__ == "__main__":
    main()

使用方法,
gulugulu.py 源码路径 目标路径

python 源码删除注释并编译

11月 18

tornado 使用配置文件的问题测试

  使用tornado做个能承担高负载的接口,配置部分是否要使用配置文件(ConfigParser)。现在有两个问题需要测试。第一、配置文件是否一次性加载,我可不希望,每次调用都会加载配置文件。第二、修改配置文件是否可以自动加载。在tornado中py文件可以自动加载,这样服务就不需要重启,服务也不会间断。
  首先是做一个tornado的测试页。在目录opt下建立testconfig文件夹,在testconf下编写代码。共有4个文件。

  主文件 main.py 代码如下:

#!/bin/env python
# -*- coding:utf-8 -*-
# -------------------------------------------------------------------------------
# Filename:    main.py
# Revision:    1.0
# Date:        2012-11-18
# Author:      simonzhang
# Email:       simon-zzm@163.com
# Web:         www.simonzhang.net
# -----------------------------------------------------------------------------
import tornado.ioloop
import tornado.web
from index import *
 
application = tornado.web.Application([
    (r"/", MainHandler),
])
 
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

  主文件要调用的部分 index.py 代码如下:

#!/bin/env python
# -*- coding:utf-8 -*-
# -------------------------------------------------------------------------------
# Filename:    main.py
# Revision:    1.0
# Date:        2012-11-18
# Author:      simonzhang
# Email:       simon-zzm@163.com
# Web:         www.simonzhang.net
# -----------------------------------------------------------------------------
import tornado.ioloop
import tornado.web
import ConfigParser

# 配置进行全局加载,如果是放到类中肯定每次都有IO。
cf = ConfigParser.ConfigParser()
cf.read("config.properties")
get_index_file_path = cf.get(cf.sections()[0], "path")
 
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        read_file = open(get_index_file_path, "rb").read()
        self.write("%s" % read_file)

  配置文件名为config.properties内容如下:

[context_path]
path = index.txt

  创建一个index.txt文件,在里面写点要显示的文件。

  开始编写监控配置文件IO的脚本。监控文件变化的部分详见:http://www.simonzhang.net/?p=429。还不知道watchdog能不能做到这个。
  脚本名为 watchfile.py 代码如下:

#!/bin/env python
# -*- coding:utf-8 -*-
# -------------------------------------------------------------------------------
# Filename:    watchfile.py
# Revision:    1.0
# Date:        2012-11-17
# Author:      simonzhang
# Email:       simon-zzm@163.com
# Web:         www.simonzhang.net
# -----------------------------------------------------------------------------
import re
import pyinotify

wm = pyinotify.WatchManager()
mask = pyinotify.IN_OPEN

class EventHandler(pyinotify.ProcessEvent):
    def process_IN_OPEN(self, event):
        self.rebuild(event)
    def rebuild(self, event):
        if (event.dir == False) and (event.name == 'config.properties') :
            print "open config file"

def main():
    handler = EventHandler()
    notifier = pyinotify.Notifier(wm, handler)
    wdd = wm.add_watch('/opt/testconfig',mask, rec=True,auto_add=True )
    notifier.loop()

if __name__ == "__main__":
    main()

  最终测试结果。第一、配置文件是在服务启动时一次加载。第二、配置文件不能自动加载,修改完配置文件必须要重启服务。
  使用ConfigParser来做配置文件,自然非常方便,tornado重启速度很快,但是我还是希望能自动加载,因为在几百台服务的情况下,能自动加载自然比需要重启更方便。所以当前就是把配置直接写到代码中,然后找个文件记录配置位置。之后再研究一下能不能热重启。如果大家有好的办法也烦请请告诉我一声。

4月 08

python的聪明组合

  一直在买双色就是没有中过,看过高人指点,根据“聪明组合”写了这个脚本。在技术上没有任何难度,都是体力活。为了大家方便。
  运行脚本输入12个红球数,组合成10组。然后在自己加上篮球即可。

#!/bin/env python
# -*- coding:utf-8 -*-
# -------------------------------------------
# Filename:    clever12.py
# Revision:    1.0
# Date:        2012-3-13
# Author:      simonzhang
# WEB:         www.simonzhang.net
# Email:       simon-zzm@163.com
# -------------------------------------------

def run_group(di):
    fen = di.split(' ')
    A = fen[0]
    B = fen[1]
    C = fen[2]
    D = fen[3]
    E = fen[4]
    F = fen[5]
    G = fen[6]
    H = fen[7]
    I = fen[8]
    J = fen[9]
    K = fen[10]
    L = fen[11]
    print("%s %s %s %s %s %s"%(A,B,D,E,K,L))
    print("%s %s %s %s %s %s"%(A,B,E,F,H,I))
    print("%s %s %s %s %s %s"%(A,B,E,G,I,K))
    print("%s %s %s %s %s %s"%(A,B,E,I,J,L))
    print("%s %s %s %s %s %s"%(A,C,D,E,F,L))
    print("%s %s %s %s %s %s"%(A,C,D,G,H,J))
    print("%s %s %s %s %s %s"%(A,C,D,I,K,L))
    print("%s %s %s %s %s %s"%(A,C,F,G,H,L))
    print("%s %s %s %s %s %s"%(A,C,F,H,J,K))
    print("%s %s %s %s %s %s"%(A,D,F,G,J,L))

def main():
    get_list = raw_input("12 number :")
    if len(get_list) == 35:
        run_group(get_list)
    else:
        print "input error"

if __name__ == "__main__":
    main()
3月 22

python 多线程程序,控制线程数

python 控制线程数

         用python写多线程的脚本,需要控制线程数。需要自己判断线程是否运行完毕。测试代码如下:

#!/bin/env python
# -------------------------------------------------------------------------------
# Filename:    my-thread.py
# Revision:
# Date:        2012-12-19
# Author:      simonzhang
# web :        www.simonzhang.net
# Email:       simon-zzm@163.com
# ------------------------------------------------------------------------------- 

####加载多线程模块
import threading
####需要个随机数和延迟,为测试用
import random
from time import sleep

#### 多线程运行的测试部分。循环3次,每次间隔0到2的随机秒数,
#### 等待后打印,运行总次数,线程数和循环值
def test_func(thread_number,sequence):
    for i in range(3):
        sleep(random.randint(0,2))
        print('run sequence:%s thread %d is running %d ' % (sequence,thread_number,i))   

def main():
    #### 定义循环序列,就是一个线程池
    threads = []
    #### 定义总共运行的次数
    all_number = 5
    #### 定义运行所使用的线程数
    thread_lines = 3
    #### 定义开始线程数
    start_line = 0
    #### 首先构建线程池
    for i in range(0,thread_lines):
        t = threading.Thread(target=test_func, args=(i,start_line,))
        threads.append(t)
        start_line +=1
    #### 运行第一批线程的任务
    for t in threads:
        t.start()
    #### 循环运行全部任务
    for number_line in xrange(start_line,all_number):
        #### 初始化当前线程的状态
        thread_status = False
        #### 初始化检查循环线程的开始值
        loop_line = 0
        #### 开始循环检查线程池中的线程状态
        while thread_status == False :
            #### 如果检查当前线程,如果线程停止,代表任务完成,则分配给此线程新任务,
            #### 如果检查当先线程正在运行,则开始检查下一个线程,直到分配完新任务。
            #### 如果线程池中线程全部在运行,则开始从头检查
            if threads[loop_line].isAlive() == False :
                t = threading.Thread(target=test_func, args=(loop_line,number_line,))
                threads[loop_line]=t
                threads[loop_line].start()
                thread_status = True
            else:
                if loop_line >= thread_lines-1 :
                    loop_line=0
                else:
                    loop_line+=1
    #### 等待超时
    sleep(30)
    #### 结束所有线程
    for number_line in xrange(start_line,thread_lines):
        thread[number_line].exit()

if __name__ == "__main__":
    main()

          本代码存在一个问题,运行完毕后主进程在运行,程序走到下一步,但是还有线程也在运行。所以需要在调用全部完毕后,有一段检查线程是否全部结束,可以使用jion()进行阻塞判断即可。根据实际需要编写。