Skip to content

Commit

Permalink
Add support for .pcap files
Browse files Browse the repository at this point in the history
Additionally, rename -w option to -a to avoid confusion since now the parameter is not only used for Wireshark dumps, but also pcap files.
  • Loading branch information
rabelenda-abstracta committed Sep 12, 2018
1 parent ea095b2 commit 0ac14bd
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 20 deletions.
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
# Wiresham
Simple TCP service mocking tool for replaying [Wireshark](https://www.wireshark.org/) captured service traffic.

Simple TCP service mocking tool for replaying [tcpdump](http://www.tcpdump.org/) or [Wireshark](https://www.wireshark.org/) captured service traffic.

This project is inspired in other tools like [WireMock](http://wiremock.org/), [mountebank](http://www.mbtest.org/) and [MockTCPServer](https://github.com/CloudRacer/MockTCPServer), but provides following features that are partially supported by listed tools:
* TCP mocking support, with async messages sent (i.e: allows sending welcome messages which are not supported by mountebank).
* Load mocking specification from Wireshark `.json` dump files and provides a reduced `.yaml` format for easy versioning.
* Load mocking specification from tcpdump `.pcap` or Wireshark `.json` dump files and provides a reduced `.yaml` format for easy versioning.
* Allows to easily run the mock embedded in Java projects for easy testing

Take into consideration that this tool is very simple, and only replays TCP traffic that has previously been recorded, so if user interacts with the tool in unexpected ways, then the service will not answer until next expected packet is received. For more complex scenarios consider using one of previously mentioned tools.

## Usage

This tool (as previously listed ones) is particularly useful to implement integration tests without the hassle of flaky connections, or complex environment setup or restrictions (VPN, quotas, etc).

**Note:** If you use `.pcap`, since Wiresham uses [pcap4j](https://www.pcap4j.org/) for `.pcap` files support, you need to install libpcap or winpcap as detailed in [pcap4j website](https://www.pcap4j.org/).

The general use case for the tool takes following steps:
1. User captures with Wireshark traffic between a client application and a service.
1. User stores the captured traffic, filtering with proper condition for service port, in a `.json` file (File -> Export Packet Dissections -> As JSON...)
1. User captures traffic with tcpdump (with something like `tcpdump port 23 -w ~/traffic.pcap`) or Wireshark between a client application and a service.
1. If traffic has been captured with Wireshark then store the captured traffic, filtering with proper condition for service port, in a `.json` file (File -> Export Packet Dissections -> As JSON...)
1. At this point user might follow two potential courses:
1. Start Wiresham in standalone mode with stored `.json` and connect to it with the client application to reproduce previously stored traffic.
1. Start Wiresham in standalone mode with stored `.pcap` or `.json` and connect to it with the client application to reproduce previously stored traffic.

E.g.: `java -jar wiresham-standalone.jar -p 2324 -w 0.0.0.0 wireshark-dump.json`
E.g.: `java -jar wiresham-standalone.jar -p 2324 -a 0.0.0.0 wireshark-dump.json`

> Latest version of wiresham-standalone.jar can be downloaded from [maven central](https://search.maven.org/).

A similar example for a tcpdump traffic:

E.g.: `java -jar wiresham-standalone.jar -p 2324 -a 0.0.0.0 traffic.pcap`

> Run `java -jar wiresham-standalone.jar -h` to get usage instructions and help.
1. Convert the Wireshark dump to the a reduced `.yaml` file (an example file can be found in [simple.yaml](src/test/resources/simple.yaml)), optionally manually tune it (response times or binary packets), add it to the project repository and implement tests using [VirtualTcpService class](src/main/java/us/abstracta/wiresham/VirtualTcpService.java).

To convert an script run something like `java -jar wiresham-standalone.jar -d reduced-dump.yml -w 0.0.0.0 wireshark-dump.json`.
1. Convert the tcpdump or Wireshark dump to the a reduced `.yaml` file (an example file can be found in [simple.yaml](src/test/resources/simple.yaml)), optionally manually tune it (response times or binary packets), add it to the project repository and implement tests using [VirtualTcpService class](src/main/java/us/abstracta/wiresham/VirtualTcpService.java).

To convert an script run something like `java -jar wiresham-standalone.jar -d reduced-dump.yml -a 0.0.0.0 wireshark-dump.json`.

To add Wiresham as dependency in maven project include in `pom.xml` the dependency:

Expand Down
16 changes: 14 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
<version>0.1-SNAPSHOT</version>

<name>${project.artifactId}</name>
<description>Simple TCP service mocking tool for replaying Wireshark captured service traffic</description>
<description>Simple TCP service mocking tool for replaying Wireshark captured service traffic
</description>
<url>https://github.com/abstracta/wiresham</url>

<licenses>
Expand Down Expand Up @@ -80,6 +81,16 @@
<artifactId>args4j</artifactId>
<version>2.33</version>
</dependency>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-core</artifactId>
<version>1.7.3</version>
</dependency>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-packetfactory-static</artifactId>
<version>1.7.3</version>
</dependency>

<dependency>
<groupId>junit</groupId>
Expand Down Expand Up @@ -124,7 +135,8 @@
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>standalone</shadedClassifierName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>us.abstracta.wiresham.VirtualTcpServiceMain</Main-Class>
</manifestEntries>
Expand Down
49 changes: 47 additions & 2 deletions src/main/java/us/abstracta/wiresham/Flow.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,27 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.BaseEncoding;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.pcap4j.core.BpfProgram.BpfCompileMode;
import org.pcap4j.core.NotOpenException;
import org.pcap4j.core.PcapHandle;
import org.pcap4j.core.PcapNativeException;
import org.pcap4j.core.Pcaps;
import org.pcap4j.packet.IpV4Packet;
import org.pcap4j.packet.TcpPacket;
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
Expand Down Expand Up @@ -54,10 +65,10 @@ public String toString() {
return steps.toString();
}

public static Flow fromWiresharkJsonDump(File wiresharkJsonDumpFile, String serverAddress)
public static Flow fromWiresharkJsonDump(File file, String serverAddress)
throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode json = mapper.readTree(wiresharkJsonDumpFile);
JsonNode json = mapper.readTree(file);
return new Flow(StreamSupport.stream(json.spliterator(), false)
.filter(packet -> !packet.at(WIRESHARK_LAYERS_PATH).at(WIRESHARK_TCP_PAYLOAD_PATH).asText()
.isEmpty())
Expand All @@ -75,6 +86,40 @@ public static Flow fromWiresharkJsonDump(File wiresharkJsonDumpFile, String serv
.collect(Collectors.toList()));
}

public static Flow fromPcap(File file, String serverAddress, String filter) throws IOException {
List<PacketStep> steps = new ArrayList<>();
try (PcapHandle pcap = Pcaps.openOffline(file.getAbsolutePath())) {
if (filter != null) {
pcap.setFilter(filter, BpfCompileMode.OPTIMIZE);
}
long lastTimeMillis = 0;
// we can't use getNextPacket and do a while != null due to https://github.com/kaitoy/pcap4j/issues/13
// so we use getNextPacketEx which throws an EOFException when reached end of file
while (true) {
org.pcap4j.packet.Packet p = pcap.getNextPacketEx();
if (!p.contains(TcpPacket.class)) {
continue;
}
org.pcap4j.packet.Packet payload = p.get(TcpPacket.class).getPayload();
if (payload == null) {
continue;
}
String sourceIp = p.get(IpV4Packet.class).getHeader().getSrcAddr().getHostAddress();
String hexDump = BaseEncoding.base16().encode(payload.getRawData());
long timeMillis = pcap.getTimestamp().getTime();
long timeDeltaMillis = lastTimeMillis > 0 ? timeMillis - lastTimeMillis : 0;
lastTimeMillis = timeMillis;
steps.add(serverAddress.equals(sourceIp) ? new ServerPacketStep(hexDump, timeDeltaMillis)
: new ClientPacketStep(hexDump));
}
} catch (EOFException e) {
//just ignore if we reached end of file.
} catch (TimeoutException | PcapNativeException | NotOpenException e) {
throw new IOException("Problem reading file " + file, e);
}
return new Flow(steps);
}

@SuppressWarnings("unchecked")
public static Flow fromYml(File ymlFile) throws FileNotFoundException {
List<PacketStep> packets = (List<PacketStep>) new Yaml(buildYamlConstructor())
Expand Down
32 changes: 24 additions & 8 deletions src/main/java/us/abstracta/wiresham/VirtualTcpServiceMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,14 @@ public class VirtualTcpServiceMain {
+ "javax.net.ssl.keyStorePassword to tune the configuration.")
private boolean sslEnabled;

@Option(name = "-w", aliases = "--wireshark-server-address", metaVar = "ip address",
usage = "When specified, the flow config file is interpreted as a Wireshark generated JSON "
+ "dump file and this IP address identifies the service to be virtualized")
private String wiresharkServerAddress;
@Option(name = "-a", aliases = "--server-address", metaVar = "ip address",
usage = "When using a Wireshark generated JSON dump or PCAP file, this parameter specifies "
+ "the IP address which identifies the service to be virtualized")
private String serverAddress;

@Option(name = "-f", aliases = "--pcap-filter-expression", metaVar = "expression",
usage = "Expression used to filter packets from a PCAP file. Eg: 'port 23'")
private String pcapFilter;

@Option(name = "-d", aliases = "--dump-file", metaVar = ".yml file",
usage = "File path to dump loaded flow config. The virtual service will not be started when "
Expand Down Expand Up @@ -93,17 +97,17 @@ private static void printHelp(CmdLineParser parser, PrintStream printStream) {
printStream.println();
printStream.println(" Examples: \n"
+ command + " -p 2324 login-invalid-creds.yml\n"
+ command + " -p 2324 -w 0.0.0.0 login-invalid-creds-wireshark.json\n"
+ command + " -p 2324 -a 0.0.0.0 login-invalid-creds-wireshark.json\n"
+ command + " -p 2324 -a 0.0.0.0 login-invalid-creds.pcap\n"
+ command + " -p 2324 -a 0.0.0.0 -f \"port 23\" login-invalid-creds.pcap\n"
+ command + " -d login-invalid-creds.yml -w 0.0.0.0 login-invalid-creds-wireshark.json\n");
}

private void run() throws IOException, InterruptedException {
Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
root.setLevel(superVerbose ? Level.TRACE : verbose ? Level.DEBUG : Level.INFO);

Flow flow = wiresharkServerAddress != null
? Flow.fromWiresharkJsonDump(configFile, wiresharkServerAddress)
: Flow.fromYml(configFile);
Flow flow = loadFlow();
if (dumpFile != null) {
flow.saveYml(dumpFile);
} else {
Expand All @@ -125,4 +129,16 @@ private void run() throws IOException, InterruptedException {
}
}

public Flow loadFlow() throws IOException {
if (serverAddress != null) {
if (configFile.getName().toLowerCase().endsWith(".json")) {
return Flow.fromWiresharkJsonDump(configFile, serverAddress);
} else {
return Flow.fromPcap(configFile, serverAddress, pcapFilter);
}
} else {
return Flow.fromYml(configFile);
}
}

}

0 comments on commit 0ac14bd

Please sign in to comment.