网络流量分析软件
目录
网络流量分析软件 1
一、选题背景 1
二、方案论证(设计理念) 2
程序后台(数据模块) 2
(1) 数据抓取:pcap4j 库 2
(2) Web 框架:jetty 库 2
(3) WebSocket 交互逻辑 2
(4) 认证与加密 3
程序前端(展示模块) 3
(1) 文件存取:原生 tcpdump 标准 pcap 文件 3
(2) 大量数据与性能:分页与动态读取 3
(3) 数据分析:独立模块 4
三、过程论述 4
- 总体架构与引用第三方库 4
- 定义前后端交互接口 4
客户端 -> 服务器 4
① action=hello 握手消息 4
② action=ping 存活确认消息 5
③ action=command 控制命令消息 5
服务器 -> 客户端 5 - 后台:定义前后端交互的 Packet 模型 6
- 后台:定义可能用到的辅助类 7
- 后台:完成实时捕获后台核心部分 8
- 后台:实现实时捕获模块的后端接口 9
- 前端:实现向接口发送控制命令和从接口获取实时数据 10
- 后台:添加 HTTPS 支持 10
- 综合:添加 AES 加密传输 11
- 后台:添加解析命令行参数功能,灵活配置程序参数 12
- 前端:添加断线重连功能 12
- 后台:添加代理识别,不向代理客户端发送实时数据 13
- 前端:重构提取可重用部分,为实现文件管理模块做准备 13
- 综合:实现文件管理模块接口 13
- 整合调试,打包发布 14
四、结果分析 14 - 运行结果 14
运行结果及分析 14 - 捕获模块稳定性和资源占用分析 21
- 手机端 24
- 环境要求 27
附:使用手册(程序使用方法) 27 - 使用方法 28
- 注意事项 28
五、毕业设计总结 29
二、方案论证(设计理念)
程序后台(数据模块)
(1)数据抓取:pcap4j 库
Pcap4J 是一款十分优秀的数据报文抓取第三方 Java 库,要自己编写报文抓取代码是十分困难的,但是在这个第三方库的支持下,对数据报文的抓取和处理将会容易,大大降低了开发难度。尽管如此,这一部分也将是程序的核心,报文的获取、转换、存储、读取都将在这个部分完成。
(2)Web 框架:jetty 库
本程序虽然以 web 作为主要展示端和控制端,但 web 后台并不是核心所在,所以只需要简单轻量的内嵌式 web 后台就足够了,jetty 便是非常适合的库,能比较容易的实现 web 后台。其中文件上传下载使用 servlet,其他数据交互使用 WebSocket。
(3)WebSocket 交互逻辑
WebSocket 属于 web 的一部分,是前端获取数据的主要来源,它将是整
个程序中最复杂的模块之一,除了负责后端报文的转发和接收前端控制命令外,还需要检测用户的合法性,以及将用户的合法请求信息转换为可识别的控制信息。
(4)认证与加密
程序启动时生成随机 256 位密钥,浏览器的认证与加密将依赖这个密钥。浏览器获取密钥之后,建立 WebSocket 连接和调用后台 API 都将使用这个密钥进行认证和加密。
为确保密钥不会在传输过程中被窃听,使用浏览器地址#hash 作为密钥的来源,这样密钥将不会通过浏览器传输,自然就没有被网络窃听的可能。值得注意的是,在设计的初期是使用 HTTPS 协议进行认证和加密的,但
在开放过程中意识到,SSL 只提供传输的保密性,却并不会提供用户认证的功能,虽然能够较好地防止信息被窃听,却无法保证用户是合法用户。
所以后期加上了 AES 加密,将与 HTTPS 同时存在,并在程序入口处加上了灵活的控制,文档后面会有详细的说明。
程序前端(展示模块)
(1)展示页面:单页面入口,使用 React 实现,进入 React 前统一从 URL 中获取密钥并保存在 sessionStorage 中(这样刷新浏览器也能继续完成认证,且在浏览器或标签关闭时失效)
(2)控制接口:WebSocket 接口
(3)数据获取接口:WebSocket 接口
(4)文件接口:上传与下载均使用 servlet
数据分析与存储
(1)文件存取:原生 tcpdump 标准 pcap 文件
Pcap4J 库可以直接存取 tcpdump 标准 pcap 文件。
(2)大量数据与性能:分页与动态读取
当数据量很大时,在一个页面显示会严重影响性能,甚至程序崩溃,必须做好分页。然而分页也需要一定的技巧,由于 pcap 文件本身是流式的, 而且没有索引,所以很难从一个大的 pcap 文件中分页,只能采用多个文件的方式,借用操作系统的文件管理来辅助分页,规定一个文件最大存多少个包,这样就解决了后台分页问题。
还有就是前端的数据,除了能实时监控数据包之外,还应能够获取历史数据,另外当数据包越积越多,本文转载自http://www.biyezuopin.vip/onews.asp?id=14910前端的内存消耗会越来越大,即使前端分页也无法缓解内存负荷,必须在合适的时间舍弃旧的数据包,需要的时候再向
后台请求获取。
(3)数据分析:独立模块
本程序的数据分析主要有两种形式:一是从开始捕获数据包开始动态统计数据包类型,加以分析;二是从一个 pcap 文件中读取并分析。
这两种分析方式的分析过程是一样的,但是数据来源不同,如果能将分析程序独立出来,就能从不同的来源获取数据后使用统一的接口。另外,动态分析将是一直存在与全局空间的,只有一个,而文件分析则对应每一个文件,可以有多个。
从连接上看,两种连接都应该支持多客户端,不同之处只是实时捕获是多个客户端控制同一个捕获程序,而文件管理部分则是每个客户端对应一个文件管理程序。
package com.xinsane.traffic_analysis;import com.xinsane.traffic_analysis.helper.AESCryptHelper;
import com.xinsane.traffic_analysis.helper.ArgumentsResolver;
import com.xinsane.traffic_analysis.servlet.DownloadServlet;
import com.xinsane.traffic_analysis.servlet.UploadServlet;
import com.xinsane.traffic_analysis.websocket.WSHandler;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class Application {private static final Logger logger = LoggerFactory.getLogger(Application.class);public static ArgumentsResolver.Result config = new ArgumentsResolver.Result();public static int http_port = -1;public static int https_port = -1;public static String dumper_dir = "./dump/";public static int slice_number = 500;private static String keystore_path = "./certs/keystore";private static String keystore_password = "traffic";public static boolean no_aes = false;public static void main(String[] args) {try {resolveArguments(args);if (!no_aes)generateKey();createDumpDir();startWeb();} catch (Exception e) {e.printStackTrace();System.exit(-1);}}private static void resolveArguments(String[] args) {Map<String, String> validOptions = new HashMap<>();validOptions.put("http", "specify the http listen port.");validOptions.put("https", "specify the https listen port.");validOptions.put("dump", "specify the pcap files directory. default ./dump/");validOptions.put("slice", "specify how many packets per pcap file in file mode. default 500");validOptions.put("keystore", "specify the path of SSL keystore file. default ./certs/keystore");validOptions.put("keystore_pass", "specify the password of SSL keystore file. default traffic");Map<String, String> validFeatures = new HashMap<>();validFeatures.put("no_aes", "data will be transferred without encryption if --no_aes is set");config = ArgumentsResolver.resolve(args, validOptions, validFeatures);if (config.args.size() > 0) {ArgumentsResolver.die("Unknown arguments. You can use this program with options and features below.",validOptions, validFeatures);}if (config.options.containsKey("http")) {http_port = Integer.parseInt(config.options.get("http"));logger.debug("http port will be listening on " + http_port);}if (config.options.containsKey("https")) {https_port = Integer.parseInt(config.options.get("https"));logger.debug("https port will be listening on " + https_port);}if (config.options.containsKey("dump"))dumper_dir = config.options.get("dump");if (config.options.containsKey("slice"))slice_number = Integer.parseInt(config.options.get("slice"));if (config.options.containsKey("keystore"))keystore_path = config.options.get("keystore");if (config.options.containsKey("keystore_pass"))keystore_password = config.options.get("keystore_pass");if (config.features.containsKey("no_aes")) {no_aes = true;logger.debug("AES encryption turn off. Data transfer may be at risk.");}if (http_port <= 0 && https_port <= 0) {ArgumentsResolver.die("You should specify either -http or -https option at least.",validOptions, validFeatures);}if (http_port > 0 && no_aes) {logger.warn("it will be at risk to specify a http port with --no_aes set.");}}private static void generateKey() {String keyString = bytes2Hex(AESCryptHelper.key.getEncoded());logger.debug("generate a new key: " + keyString);// 把生成的密钥写入文件File file = new File("./key.txt");try {if (file.exists()) {if (!file.delete())logger.error("can not delete old key file.");}if (file.createNewFile()) {file.deleteOnExit(); // 程序结束后删除BufferedWriter out = new BufferedWriter(new FileWriter(file));out.write(keyString);out.flush();out.close();} elselogger.error("can not write key to the file: " + file.getAbsolutePath());} catch (IOException e) {e.printStackTrace();logger.error("can not write key to the file: " + file.getAbsolutePath());}}private static String bytes2Hex(byte[] bytes) {StringBuilder builder = new StringBuilder(bytes.length * 2);for(byte b : bytes)builder.append(String.format("%02x", b & 0xff));return builder.toString();}private static void startWeb() throws Exception {Server server = new Server();if (http_port > 0) {// HTTP ConnectorServerConnector httpConnector = new ServerConnector(server);httpConnector.setPort(http_port);server.addConnector(httpConnector);}if (https_port > 0) {// SSL Context FactorySslContextFactory sslContextFactory = new SslContextFactory();sslContextFactory.setKeyStorePath(keystore_path);sslContextFactory.setKeyStorePassword(keystore_password);// HTTPS ConfigurationHttpConfiguration https_config = new HttpConfiguration();https_config.setSecureScheme("https");https_config.setSecurePort(https_port);https_config.addCustomizer(new SecureRequestCustomizer());// HTTPS ConnectorServerConnector httpsConnector = new ServerConnector(server,new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),new HttpConnectionFactory(https_config));httpsConnector.setPort(https_port);server.addConnector(httpsConnector);}HandlerList gzipHandlerList = new HandlerList();// Resource HandlerResourceHandler resourceHandler = new ResourceHandler();resourceHandler.setDirectoriesListed(true);String resource_path = Application.class.getResource("/static").toString();resourceHandler.setResourceBase(resource_path);gzipHandlerList.addHandler(resourceHandler);// WebSocket HandlerContextHandler wsHandler = new ContextHandler();wsHandler.setContextPath("/websocket");wsHandler.setHandler(new WSHandler());gzipHandlerList.addHandler(wsHandler);// GZIP SupportGzipHandler gzip = new GzipHandler();gzip.setHandler(gzipHandlerList);HandlerList handlerList = new HandlerList();handlerList.addHandler(gzip);// Servlet HandlerServletContextHandler servletHandler = new ServletContextHandler();servletHandler.addServlet(UploadServlet.class, "/upload");servletHandler.addServlet(DownloadServlet.class, "/download/*");handlerList.addHandler(servletHandler);server.setHandler(handlerList);// Start Serverserver.setStopAtShutdown(true);server.start();}private static void createDumpDir() {File dir = new File(dumper_dir);if (!dir.exists()) {if (!dir.mkdirs()) {logger.error("无法创建dump目录!");System.exit(-1);}}}}