#!/usr/bin/env python
# coding=UTF-8
__author__ = 'dj'

import sys
from PyQt4 import QtGui, QtCore
import socket
import fcntl
import struct
from scapy.all import *
import threading
import tempfile

QtCore.QTextCodec.setCodecForTr(QtCore.QTextCodec.codecForName('utf8'))
IFACE = 'eth0'   #网卡名称
STOP = True      #停止嗅探
PACKETS = []     #数据包收集序列
SELECT_ROW = 0   #选择的行
SELECT_INFO = '' #选择行的详细信息
SHOW2STR = ''   #show2()显示的字符串
HEXSTR = ''     #hex()显示的信息
FILTER = None   #过滤规则

#主界面
class SnifferUI(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self, parent)
        self.setWindowTitle(u'网络嗅探器')
        self.resize(700, 650)
        self.setWindowIcon(QtGui.QIcon('icons/logo.ico'))
        screen = QtGui.QDesktopWidget().screenGeometry()
        size = self.geometry()
        self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2)
        self.initUI()

    def initUI(self):
        #工具栏
        #打开文件
        self.open_toolbar = QtGui.QAction(QtGui.QIcon('icons/open.png'), u'打开', self)
        self.open_toolbar.setShortcut('Ctrl+O')
        self.open_toolbar.triggered.connect(self.open_pcap)
        self.toolbar = self.addToolBar(u'打开')
        self.toolbar.addAction(self.open_toolbar)
        #保存
        self.save_toolbar = QtGui.QAction(QtGui.QIcon('icons/save.png'), u'保存', self)
        self.save_toolbar.setShortcut('Ctrl+S')
        self.save_toolbar.triggered.connect(self.save_pcap)
        self.toolbar = self.addToolBar(u'保存')
        self.toolbar.addAction(self.save_toolbar)
        #选择接口
        self.conf_toolbar = QtGui.QAction(QtGui.QIcon('icons/iface.png'), u'配置', self)
        self.conf_toolbar.setShortcut('Ctrl+I')
        self.conf_toolbar.triggered.connect(self.select_iface)
        self.toolbar = self.addToolBar(u'配置')
        self.toolbar.addAction(self.conf_toolbar)
        #开始嗅探
        self.start_toolbar = QtGui.QAction(QtGui.QIcon('icons/start.jpg'), u'开始', self)
        self.start_toolbar.triggered.connect(self.start_sniff)
        self.toolbar = self.addToolBar(u'开始')
        self.toolbar.addAction(self.start_toolbar)
        #停止嗅探
        self.stop_toolbar = QtGui.QAction(QtGui.QIcon('icons/stop.png'), u'停止', self)
        self.stop_toolbar.triggered.connect(self.stop_sniff)
        self.toolbar = self.addToolBar(u'停止')
        self.toolbar.addAction(self.stop_toolbar)
        #重新嗅探
        """
        self.restart_toolbar = QtGui.QAction(QtGui.QIcon('icons/restart.png'), u'重新开始', self)
        self.restart_toolbar.triggered.connect(self.restart_sniff)
        self.toolbar = self.addToolBar(u'重新开始')
        self.toolbar.addAction(self.restart_toolbar)
        """
        #过滤器
        self.filter_toolbar = QtGui.QAction(QtGui.QIcon('icons/filter.png'), u'过滤', self)
        self.filter_toolbar.triggered.connect(self.filter_pcap)
        self.toolbar = self.addToolBar(u'过滤')
        self.toolbar.addAction(self.filter_toolbar)
        #帮助
        self.help_toolbar = QtGui.QAction(QtGui.QIcon('icons/help.png'), u'帮助', self)
        self.help_toolbar.triggered.connect(self.help_me)
        self.toolbar = self.addToolBar(u'帮助')
        self.toolbar.addAction(self.help_toolbar)
        #关于
        self.about_toolbar = QtGui.QAction(QtGui.QIcon('icons/info.png'), u'关于', self)
        self.about_toolbar.triggered.connect(self.about_me)
        self.toolbar = self.addToolBar(u'关于')
        self.toolbar.addAction(self.about_toolbar)
        #退出
        self.exit_toolbar = QtGui.QAction(QtGui.QIcon('icons/exit.ico'), u'退出', self)
        self.connect(self.exit_toolbar, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()'))
        self.toolbar = self.addToolBar(u'退出')
        self.toolbar.addAction(self.exit_toolbar)
        #数据包显示栏
        self.packet_table = Packet_TableView(parent=self)
        self.packet_table.doubleClicked.connect(self.get_row_info)
        #分割栏目
        self.splitter = QtGui.QSplitter(self)
        self.splitter.addWidget(self.packet_table)
        self.splitter.setOrientation(QtCore.Qt.Vertical)
        self.setCentralWidget(self.splitter)

    def get_row_info(self):
        infoUI = ShowInfoUI(parent=self)
        infoUI.show()
        infoUI.exec_()

    #打开数据包
    def open_pcap(self):
        global PACKETS
        open_name = QtGui.QFileDialog.getOpenFileName(self, self.tr('Open Packets'), '.', self.tr("Packets Files(*.pcap *.cap)"))
        name = str(open_name)
        pcap = rdpcap(name)
        PACKETS = list(pcap)
        for packet in PACKETS:
            if int(packet.getlayer(Ether).type) == 34525 and STOP:
                proto = 'IPv6'
                src = str(packet.getlayer(IPv6).src)
                dst = str(packet.getlayer(IPv6).dst)
                info = str(packet.summary())
                self.packet_table.row_append(src, dst, proto, info)
            elif int(packet.getlayer(Ether).type) == 2048 and STOP:
                if int(packet.getlayer(IP).proto) == 6:
                    proto = 'TCP'
                elif int(packet.getlayer(IP).proto) == 17:
                    proto = 'UDP'
                elif int(packet.getlayer(IP).proto) == 1:
                    proto = 'ICMP'
                src = str(packet.getlayer(IP).src)
                dst = str(packet.getlayer(IP).dst)
                info = str(packet.summary())
                self.packet_table.row_append(src, dst, proto, info)
            elif int(packet.getlayer(Ether).type) == 2054 and STOP:
                proto = 'ARP'
                src = str(packet.getlayer(ARP).psrc)
                dst = str(packet.getlayer(ARP).pdst)
                info = str(packet.summary())
                self.packet_table.row_append(src, dst, proto, info)
            else:
                pass

    #保存数据包
    def save_pcap(self):
        save_name = QtGui.QFileDialog.getSaveFileName(self, self.tr("Save Packets"), '.', self.tr("Packets Files(*.pcap)"))
        if save_name:
            name = str(save_name + '.pcap')
            wrpcap(name, PACKETS)
            QtGui.QMessageBox.information(self, u"保存成功", self.tr("数据包保存成功!"))

    #选择网卡
    def select_iface(self):
        iface = IfaceUI(parent=self)
        iface.exec_()

    #处理数据包
    def handle_packets(self, packet):
        if int(packet.getlayer(Ether).type) == 34525 and STOP:
            proto = 'IPv6'
            src = str(packet.getlayer(IPv6).src)
            dst = str(packet.getlayer(IPv6).dst)
            info = str(packet.summary())
            self.packet_table.row_append(src, dst, proto, info)
            PACKETS.append(packet)
        elif int(packet.getlayer(Ether).type) == 2048 and STOP:
            if int(packet.getlayer(IP).proto) == 6:
                proto = 'TCP'
            elif int(packet.getlayer(IP).proto) == 17:
                proto = 'UDP'
            elif int(packet.getlayer(IP).proto) == 1:
                proto = 'ICMP'
            src = str(packet.getlayer(IP).src)
            dst = str(packet.getlayer(IP).dst)
            info = str(packet.summary())
            self.packet_table.row_append(src, dst, proto, info)
            PACKETS.append(packet)
        elif int(packet.getlayer(Ether).type) == 2054 and STOP:
            proto = 'ARP'
            src = str(packet.getlayer(ARP).psrc)
            dst = str(packet.getlayer(ARP).pdst)
            info = str(packet.summary())
            self.packet_table.row_append(src, dst, proto, info)
            PACKETS.append(packet)
        else:
            pass

    #开始嗅探
    def start_sniff(self):
        sniff_thread = threading.Thread(target=sniffer, args=(IFACE, self.handle_packets))
        sniff_thread.start()

    #停止嗅探
    def stop_sniff(self):
        global STOP
        STOP = False

    #重新开始
    def restart_sniff(self):
        self.packet_table.clearSpans()

    #过滤数据包
    def filter_pcap(self):
        global FILTER
        filterUI = FilterUI(parent=self)
        if filterUI.exec_():
            FILTER = str(filterUI.get_value())

    #帮助
    def help_me(self):
        QtGui.QMessageBox.information(self, u"帮助", self.tr("先配置要嗅探的网卡,然后启动嗅探器!"))

    #关于
    def about_me(self):
        QtGui.QMessageBox.information(self, u"关于", self.tr("本程序由软件工程11202班董进编写!"))


#选择网卡对话框
class IfaceUI(QtGui.QDialog):
    def __init__(self, parent=None):
        QtGui.QDialog.__init__(self, parent)
        self.resize(500, 200)
        self.setWindowTitle(u'网络适配器选择')
        screen = QtGui.QDesktopWidget().screenGeometry()
        size = self.geometry()
        self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2)
        self.initUI()
        self.show()

    def initUI(self):
        device_data = get_iface_name()
        iface_num = len(device_data)
        iface_keys = device_data.keys()
        #网卡列表
        self.radio_lists = []
        self.gridlayout = QtGui.QGridLayout()
        self.label_name = QtGui.QLabel(u'接口名')
        self.label_ip = QtGui.QLabel(u'IP地址')
        self.label_receive = QtGui.QLabel(u'接受流量')
        self.label_send = QtGui.QLabel(u'发送流量')
        self.gridlayout.addWidget(self.label_name, 1, 1)
        self.gridlayout.addWidget(self.label_ip, 1, 2)
        self.gridlayout.addWidget(self.label_receive, 1, 3)
        self.gridlayout.addWidget(self.label_send, 1, 4)
        self.setLayout(self.gridlayout)
        for i in range(iface_num):
            iface_name = iface_keys[i]
            self.iface_radio = QtGui.QRadioButton(iface_name)
            if iface_name == 'eth0':
                self.iface_radio.setChecked(True)
            self.gridlayout.addWidget(self.iface_radio, i+2, 1)
            self.radio_lists.append(self.iface_radio)
            self.ip_label = QtGui.QLabel(get_ip_address(iface_name))
            self.gridlayout.addWidget(self.ip_label, i+2, 2)
            data = device_data[iface_name].split(';')
            self.receive_label = QtGui.QLabel(data[0])
            self.send_label = QtGui.QLabel(data[1])
            self.gridlayout.addWidget(self.receive_label, i+2, 3)
            self.gridlayout.addWidget(self.send_label, i+2, 4)
            self.setLayout(self.gridlayout)
        #添加按钮
        self.start_but = QtGui.QPushButton(u'确定', self)
        self.start_but.clicked.connect(self.exit_me)
        self.start_but.setCheckable(False)
        self.gridlayout.addWidget(self.start_but, iface_num + 2, 2)
        self.cancel_but = QtGui.QPushButton(u'取消', self)
        self.connect(self.cancel_but, QtCore.SIGNAL('clicked()'), QtCore.SLOT('close()'))
        self.cancel_but.setCheckable(False)
        self.gridlayout.addWidget(self.cancel_but, iface_num + 2, 3)

    def exit_me(self):
        global IFACE
        for radio in self.radio_lists:
            if radio.isChecked():
                IFACE = radio.text()
        self.setVisible(False)

#显示数据包列表
class Packet_TableView(QtGui.QTableView):
    def __init__(self, parent=None):
        QtGui.QTableView.__init__(self, parent)
        self.model = QtGui.QStandardItemModel(parent=self)
        self.model.setHorizontalHeaderLabels(['Source', 'Destination', 'Protoco', 'Info'])
        self.setModel(self.model)
        self.setColumnWidth(0, 120)
        self.setColumnWidth(1, 120)
        self.setColumnWidth(2, 100)
        self.setColumnWidth(3, 350)
        self.setAlternatingRowColors(True)
        self.setAutoScroll(True)
        self.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) #整行选中
        self.setEditTriggers(QtGui.QTableView.NoEditTriggers) #不可编辑
        self.setSelectionMode(QtGui.QTableView.SingleSelection) #选择单行
        self.show()

    def mouseDoubleClickEvent(self, QMouseEvent):
        global SELECT_ROW, SELECT_INFO, SHOW2STR, HEXSTR
        QtGui.QTableView.mouseDoubleClickEvent(self, QMouseEvent)
        pos = QMouseEvent.pos()
        item = self.indexAt(pos)
        if item:
            SELECT_ROW = int(item.row())
            SELECT_INFO = PACKETS[SELECT_ROW]
            #输出重定向数据
            show2_temp_name = tempfile.NamedTemporaryFile(prefix='show2_', dir='/tmp')
            old = sys.stdout
            show2_file = open(show2_temp_name.name, 'w')
            sys.stdout = show2_file
            SELECT_INFO.show2()
            sys.stdout = old
            show2_file.close()
            hex_temp_name = tempfile.NamedTemporaryFile(prefix='hex_', dir='/tmp')
            hex_file = open(hex_temp_name.name, 'w')
            sys.stdout = hex_file
            hexdump(SELECT_INFO)
            sys.stdout = old
            hex_file.close()
            #读取数据
            with open(show2_temp_name.name, 'r') as show2f:
                SHOW2STR = show2f.read()
            with open(hex_temp_name.name, 'r') as hexf:
                HEXSTR = hexf.read()
            print('--------------------------------------')
            print(SHOW2STR)
            print(HEXSTR)
            print('--------------------------------------')


    #添加行
    def row_append(self, src, dst, proto, info):
        self.model.appendRow((QtGui.QStandardItem(src),
                              QtGui.QStandardItem(dst),
                              QtGui.QStandardItem(proto),
                              QtGui.QStandardItem(info)))

class ShowInfoUI(QtGui.QDialog):
    def __init__(self, parent=None):
        QtGui.QDialog.__init__(self, parent)
        self.resize(500, 600)
        self.setWindowTitle(u'数据包详细信息')
        screen = QtGui.QDesktopWidget().screenGeometry()
        size = self.geometry()
        self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2)
        self.initUI()
        self.show()

    def initUI(self):
        self.text_show2 = QtGui.QTextEdit()
        self.text_show2.setText(SHOW2STR)
        self.text_show2.setReadOnly(True)
        self.text_hex = QtGui.QTextEdit()
        self.text_hex.setText(HEXSTR)
        self.text_hex.setReadOnly(True)
        self.save_but = QtGui.QPushButton(u'保存为PDF', self)
        self.save_but.setCheckable(False)
        self.save_but.clicked.connect(self.save_pdf)
        vbox = QtGui.QVBoxLayout()
        vbox.addWidget(self.text_show2)
        vbox.addWidget(self.text_hex)
        vbox.addWidget(self.save_but)
        self.setLayout(vbox)
        """
        self.splitter = QtGui.QSplitter(self)
        self.splitter.addWidget(self.text_show2)
        self.splitter.addWidget(self.text_hex)
        self.splitter.setOrientation(QtCore.Qt.Vertical)
        """

    def save_pdf(self):
        save_name = QtGui.QFileDialog.getSaveFileName(self, self.tr("Save PDF"), '.', self.tr("Packets Files(*.pdf)"))
        if save_name:
            name = str(save_name + '.pdf')
            SELECT_INFO.pdfdump(name)
            QtGui.QMessageBox.information(self, u"保存成功", self.tr("PDF保存成功!"))

class FilterUI(QtGui.QDialog):
    def __init__(self, parent=None):
        QtGui.QDialog.__init__(self, parent)
        self.resize(300, 100)
        self.setWindowTitle(u'过滤器')
        screen = QtGui.QDesktopWidget().screenGeometry()
        size = self.geometry()
        self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2)
        self.initUI()
        self.show()

    def initUI(self):
        grid = QtGui.QGridLayout()

        grid.addWidget(QtGui.QLabel(u'过滤规则:', parent=self), 0, 0, 1, 1)
        self.filter = QtGui.QLineEdit(parent=self)
        grid.addWidget(self.filter, 0, 1, 1, 1)
        # 创建ButtonBox,用户确定和取消
        buttonBox = QtGui.QDialogButtonBox(parent=self)
        buttonBox.setOrientation(QtCore.Qt.Horizontal) # 设置为水平方向
        buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) # 确定和取消两个按钮
        # 连接信号和槽
        buttonBox.accepted.connect(self.accept) # 确定
        buttonBox.rejected.connect(self.reject) # 取消
        # 垂直布局,布局表格及按钮
        layout = QtGui.QVBoxLayout()
        # 加入前面创建的表格布局
        layout.addLayout(grid)
        # 放一个间隔对象美化布局
        spacerItem = QtGui.QSpacerItem(20, 48, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        layout.addItem(spacerItem)
        # ButtonBox
        layout.addWidget(buttonBox)
        self.setLayout(layout)

    def get_value(self):
        return self.filter.text()

#获取网卡名称
def get_iface_name():
    with open('/proc/net/dev') as f:
        net_dump = f.readlines()
    device_data = {}
    for line in net_dump[2:]:
        line = line.split(':')
        device_data[line[0].strip()] = format(float(line[1].split()[0])/(1024.0*1024.0), '0.2f') + " MB;" + format(float(line[1].split()[8])/(1024.0*1024.0), '0.2f') + " MB"
    return device_data

#获取网卡IP地址
def get_ip_address(ifname):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    return socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24])

#嗅探
def sniffer(IFACE, handle):
    sniff(iface=str(IFACE), prn=handle)

def main():
    app = QtGui.QApplication(sys.argv)
    sniffer = SnifferUI()
    sniffer.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()