网络流量分析软件
目录
网络流量分析软件 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)认证与加密
程序启动时生成随机 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 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 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();
} else
logger.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 Connector
ServerConnector httpConnector = new ServerConnector(server);
httpConnector.setPort(http_port);
server.addConnector(httpConnector);
}
if (https_port > 0) {
// SSL Context Factory
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(keystore_path);
sslContextFactory.setKeyStorePassword(keystore_password);
// HTTPS Configuration
HttpConfiguration https_config = new HttpConfiguration();
https_config.setSecureScheme("https");
https_config.setSecurePort(https_port);
https_config.addCustomizer(new SecureRequestCustomizer());
// HTTPS Connector
ServerConnector 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 Handler
ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setDirectoriesListed(true);
String resource_path = Application.class.getResource("/static").toString();
resourceHandler.setResourceBase(resource_path);
gzipHandlerList.addHandler(resourceHandler);
// WebSocket Handler
ContextHandler wsHandler = new ContextHandler();
wsHandler.setContextPath("/websocket");
wsHandler.setHandler(new WSHandler());
gzipHandlerList.addHandler(wsHandler);
// GZIP Support
GzipHandler gzip = new GzipHandler();
gzip.setHandler(gzipHandlerList);
HandlerList handlerList = new HandlerList();
handlerList.addHandler(gzip);
// Servlet Handler
ServletContextHandler servletHandler = new ServletContextHandler();
servletHandler.addServlet(UploadServlet.class, "/upload");
servletHandler.addServlet(DownloadServlet.class, "/download/*");
handlerList.addHandler(servletHandler);
server.setHandler(handlerList);
// Start Server
server.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);
}
}
}
}