The Azure client libraries for Java offer a consistent and robust logging story to enable client side troubleshooting. But, the client libraries make network calls, over various protocols, which may lead to troubleshooting scenarios that extend outside of the troubleshooting scope provided. When this happens external tooling to diagnose networking issues is the solution. We'll discuss a few applications that are able to diagnose networking issues of various complexity. The scenarios will range from troubleshooting an unexpected response value from a service to root causing a connection closed exception.
Fiddler is an HTTP debugging proxy that allows for requests and responses passed through it to be logged as-is. Capturing the raw requests and responses helps aid in troubleshooting scenarios where the service gets an unexpected request or the client receives an unexpected response. To use Fiddler the client library will need to be configured with an HTTP proxy. If HTTPS is being used additional configuration will be needed if the decrypted request and response bodies need to be inspected.
The Azure HTTP library implementations, azure-core-http-netty
and azure-core-http-okhttp
, offer multiple ways to configure a proxy.
By default, when the HTTP client builder isn't passed Configuration.NONE
, and no proxy has been configured it will inspect the environment for the following
(in order):
HTTPS_PROXY
HTTP_PROXY
https.proxy*
http.proxy*
If the environment is configured with one of the configurations above it will be loaded and used as the proxy for the HttpClient
.
Example
This example sets the default Fiddler proxy with HTTP_PROXY
.
java -DHTTP_PROXY="http://localhost:8888" -jar example.jar
When the default HTTP client builder is used it will scan the environment for proxy configurations and find HTTP_PROXY=http://localhost:8888
and use it as
the proxy for the constructed HttpClient
.
For finer grain reusable configuration a Configuration
object with one of the above environment configurations can be passed into the HTTP client builder to
implicitly configure the proxy for the HttpClient
.
Example
This example creates a new Configuration
that sets the default Fiddler proxy with java.net.httpProxy
configurations.
Configuration configuration = new Configuration()
.put("java.net.useSystemProxies", "true")
.put("http.proxyHost", "localhost")
.put("http.proxyPort", "8888");
HttpClient nettyHttpClient = new NettyAsyncHttpClientBuilder()
.configuration(configuration)
.build();
HttpClient okHttpHttpClient = new OkHttpAsyncHttpClientBuilder()
.configuration(configuration)
.build();
java.net.http*
is split across multiple environment configurations and has a prerequisite configuration to signal that they are allowed to be used. The
HTTP client builder needs to have the constructed Configuration
passed, otherwise it will use the global environment configuration scope. This scoping allows
for configurations that can be shared but don't affect the entire application.
Configuring the HTTP client builder with ProxyOptions
is the most granular way to configure a proxy. This method isn't shareable across multiple locations
but offers the best scoping possible.
Example
This example sets the proxy using ProxyOptions
.
HttpClient nettyHttpClient = new NettyAsyncHttpClientBuilder()
.proxy(new ProxyOptions(ProxyOptions.HTTP, new InetSocketAddress("localhost", 8888)))
.build();
HttpClient okHttpHttpClient = new OkHttpAsyncHttpClientBuilder()
.proxy(new ProxyOptions(ProxyOptions.HTTP, new InetSocketAddress("localhost", 8888)))
.build();
By default Fiddler is only able to capture HTTP traffic. If your application is using HTTPS additional steps must be taken to trust Fiddler's certificate to allow it to capture HTTPS traffic.
This is a high-level guide on trusting Fiddler's certificate. Below will discuss having your JRE trust the certificate. Without trusting the certificate HTTPS request through Fiddler may fail with security warnings.
- Export Fiddler's certificate
- Find the JRE's keytool (usually
jre/bin
) - Find the JRE's cacert (usually
jre/lib/security
) - Run keytool to import the certificate:
sudo keytool -import -file <location of Fiddler certificate> -keystore <location of cacert> -alias Fiddler
- Enter a password
- Trust the certificate
- Export Fiddler's certificate
- Find the JRE's keytool (usually
jre/bin
) - Find the JRE's cacert (usually
jre/lib/security
) - Run keytool to import the certificate:
keytool.exe -import -file <location of Fiddler certificate> -keystore <location of cacert> -alias Fiddler
- Enter a password
- Trust the certificate
Wireshark is a network protocol analyzer that is able to capture traffic going through a network interface without requiring changes to application code. Wireshark is highly configurable and is able to capture very broad to very specific low level network traffic which allows it to aid in troubleshooting scenarios such as a remote host closing a connection or having connections closed during operation. The Wireshark GUI differentiates captures using a color scheme to easily identify unique capture cases such as a TCP retransmission, rst, etc. Captures can also be filtered either at capture time or during analysis.
Capture filters reduce the amount of network calls that are captured for analysis. Without capture filters Wireshark will capture all traffic it is able that goes through a network interface. This can produce massive amounts of data where most of it may be noise to the investigation. So, using a capture filter helps preemptively scope the network traffic being captured to help target an investigation.
Wireshark provides an in-depth guide on configuring traffic capture filters.
Example
This example adds a capture filter to capture network sent to or received from a specific host.
In Wireshark navigate to Capture > Capture Filters...
and add a new filter with the value host <host IP or hostname>
. This will add a filter to only capture traffic
to and from that host. If the application communicates to multiple hosts multiple catpure filters can be added or the host IP/hostname can be or
'd to provide looser
capture filtering.
Reproducing unexpected networking exceptions may requiring running an application for a long time to get the issue to reproduce and see the traffic leading up to it. It may not be possible to maintain all captures in memory, because of this Wireshark provides the ability to log the captures to disk. Persisting to disk ensures that the captures are available for post-processing and prevents the risk of running out of memory while reproducing the issue.
Wireshark provides an in-depth guide on configuring persisting captured traffic to disk.
Example
This example sets up Wireshark to persist captures to disk with multiple file where the files split on either 100k capture or 50MB in size.
In Wireshark navigate to Capture > Options
and navigate to the Output
tab. Enter a file name to use, this will have Wireshark persist captures to a single file.
Enable multiple files by checking Create a new file automatically
and then check after 100000 packets
and after 50 megabytes
, this will have Wireshark create
a new file after one of the predicates are matched. Each new file will use the same base name as the file name entered and will append a unique identifier. If you
want to limit the number of files that Wireshark is able to create check the Use a ring buffer with X files
, this will limit Wireshark to logging with only X files
where upon needing a new file after reaching X the oldest is overwritten.
Some times it isn't possible to tightly scope the traffic capture by Wireshark, for example your application comminucates with multiple hosts using various protocols. In this scenario, generally with using persistent capture outlined above, it is easier to run analysis post network capturing. Wireshark provides the ability to use capture filter-like syntax to be able to analyze captures.
Wireshark provides an in-depth guide on filtering captures.
Example
This example loads a persisted capture file and filters on ip.src_host==<IP>
.
In Wireshark navigate to File > Open
and load a persisted capture from the file location used above. Once the file has loaded below the menu bar there is a filter
input. In the filter input enter ip.src_host==<IP>
, this will limit the capture view to only show captures where the source was from the host with the IP <IP>
.