2222package com .microsoft .applicationinsights .agent .internal .common ;
2323
2424import com .microsoft .applicationinsights .agent .internal .configuration .DefaultEndpoints ;
25+ import io .netty .handler .ssl .SslHandshakeTimeoutException ;
2526import java .io .File ;
27+ import java .io .IOException ;
2628import java .net .UnknownHostException ;
29+ import java .security .NoSuchAlgorithmException ;
30+ import java .util .ArrayList ;
31+ import java .util .Arrays ;
32+ import java .util .List ;
2733import java .util .concurrent .atomic .AtomicBoolean ;
34+ import javax .net .ssl .SSLContext ;
2835import javax .net .ssl .SSLHandshakeException ;
36+ import javax .net .ssl .SSLSocketFactory ;
2937import org .checkerframework .checker .nullness .qual .Nullable ;
3038import org .slf4j .Logger ;
39+ import org .slf4j .LoggerFactory ;
3140
3241public class NetworkFriendlyExceptions {
3342
43+ private static final List <FriendlyExceptionDetector > DETECTORS ;
44+ private static final Logger logger = LoggerFactory .getLogger (NetworkFriendlyExceptions .class );
45+
46+ static {
47+ DETECTORS = new ArrayList <>();
48+ // Note this order is important to determine the right exception!
49+ // For example SSLHandshakeException extends IOException
50+ DETECTORS .add (SslExceptionDetector .create ());
51+ DETECTORS .add (UnknownHostExceptionDetector .create ());
52+ try {
53+ DETECTORS .add (CipherExceptionDetector .create ());
54+ } catch (NoSuchAlgorithmException e ) {
55+ logger .debug (e .getMessage (), e );
56+ }
57+ }
58+
3459 // returns true if the exception was "handled" and the caller should not log it
3560 public static boolean logSpecialOneTimeFriendlyException (
3661 Throwable error , String url , AtomicBoolean alreadySeen , Logger logger ) {
37- // Handle SSL cert exceptions
38- SSLHandshakeException sslException = getCausedByOfType (error , SSLHandshakeException .class );
39- if (sslException != null ) {
40- if (!alreadySeen .getAndSet (true )) {
41- logger .error (getSslFriendlyMessage (url ));
62+ return logSpecialOneTimeFriendlyException (error , url , alreadySeen , logger , DETECTORS );
63+ }
64+
65+ public static boolean logSpecialOneTimeFriendlyException (
66+ Throwable error ,
67+ String url ,
68+ AtomicBoolean alreadySeen ,
69+ Logger logger ,
70+ List <FriendlyExceptionDetector > detectors ) {
71+
72+ for (FriendlyExceptionDetector detector : detectors ) {
73+ if (detector .detect (error )) {
74+ if (!alreadySeen .getAndSet (true )) {
75+ logger .error (detector .message (url ));
76+ }
77+ return true ;
4278 }
43- return true ;
44- }
45- UnknownHostException unknownHostException =
46- getCausedByOfType (error , UnknownHostException .class );
47- if (unknownHostException != null && !alreadySeen .getAndSet (true )) {
48- // TODO log friendly message with instructions how to troubleshoot
49- // e.g. wrong host address or cannot reach address due to network issues...
50- return false ;
5179 }
5280 return false ;
5381 }
@@ -65,51 +93,161 @@ private static <T extends Exception> T getCausedByOfType(Throwable throwable, Cl
6593 return getCausedByOfType (cause , type );
6694 }
6795
68- private static String getSslFriendlyMessage (String url ) {
69- return FriendlyException .populateFriendlyMessage (
70- getSslFriendlyExceptionBanner (url ),
71- getSslFriendlyExceptionAction (url ),
72- "Unable to find valid certification path to requested target." ,
73- "This message is only logged the first time it occurs after startup." );
74- }
75-
76- private static String getSslFriendlyExceptionBanner (String url ) {
77- if (url .equals (DefaultEndpoints .LIVE_ENDPOINT )) {
96+ private static String getFriendlyExceptionBanner (String url ) {
97+ if (url .contains (DefaultEndpoints .LIVE_ENDPOINT )) {
7898 return "ApplicationInsights Java Agent failed to connect to Live metric end point." ;
7999 }
80100 return "ApplicationInsights Java Agent failed to send telemetry data." ;
81101 }
82102
83- private static String getSslFriendlyExceptionAction (String url ) {
84- String customJavaKeyStorePath = getCustomJavaKeystorePath ();
85- if (customJavaKeyStorePath != null ) {
103+ interface FriendlyExceptionDetector {
104+ boolean detect (Throwable error );
105+
106+ String message (String url );
107+ }
108+
109+ static class SslExceptionDetector implements FriendlyExceptionDetector {
110+
111+ static SslExceptionDetector create () {
112+ return new SslExceptionDetector ();
113+ }
114+
115+ @ Override
116+ public boolean detect (Throwable error ) {
117+ if (error instanceof SslHandshakeTimeoutException ) {
118+ return false ;
119+ }
120+ SSLHandshakeException sslException = getCausedByOfType (error , SSLHandshakeException .class );
121+ return sslException != null ;
122+ }
123+
124+ @ Override
125+ public String message (String url ) {
126+ return FriendlyException .populateFriendlyMessage (
127+ "Unable to find valid certification path to requested target." ,
128+ getSslFriendlyExceptionAction (url ),
129+ getFriendlyExceptionBanner (url ),
130+ "This message is only logged the first time it occurs after startup." );
131+ }
132+
133+ private static String getJavaCacertsPath () {
134+ String javaHome = System .getProperty ("java.home" );
135+ return new File (javaHome , "lib/security/cacerts" ).getPath ();
136+ }
137+
138+ @ Nullable
139+ private static String getCustomJavaKeystorePath () {
140+ String cacertsPath = System .getProperty ("javax.net.ssl.trustStore" );
141+ if (cacertsPath != null ) {
142+ return new File (cacertsPath ).getPath ();
143+ }
144+ return null ;
145+ }
146+
147+ private static String getSslFriendlyExceptionAction (String url ) {
148+ String customJavaKeyStorePath = getCustomJavaKeystorePath ();
149+ if (customJavaKeyStorePath != null ) {
150+ return "Please import the SSL certificate from "
151+ + url
152+ + ", into your custom java key store located at:\n "
153+ + customJavaKeyStorePath
154+ + "\n "
155+ + "Learn more about importing the certificate here: https://go.microsoft.com/fwlink/?linkid=2151450" ;
156+ }
86157 return "Please import the SSL certificate from "
87158 + url
88- + ", into your custom java key store located at:\n "
89- + customJavaKeyStorePath
159+ + ", into the default java key store located at:\n "
160+ + getJavaCacertsPath ()
90161 + "\n "
91162 + "Learn more about importing the certificate here: https://go.microsoft.com/fwlink/?linkid=2151450" ;
92163 }
93- return "Please import the SSL certificate from "
94- + url
95- + ", into the default java key store located at:\n "
96- + getJavaCacertsPath ()
97- + "\n "
98- + "Learn more about importing the certificate here: https://go.microsoft.com/fwlink/?linkid=2151450" ;
99164 }
100165
101- private static String getJavaCacertsPath () {
102- String javaHome = System .getProperty ("java.home" );
103- return new File (javaHome , "lib/security/cacerts" ).getPath ();
166+ static class UnknownHostExceptionDetector implements FriendlyExceptionDetector {
167+
168+ static UnknownHostExceptionDetector create () {
169+ return new UnknownHostExceptionDetector ();
170+ }
171+
172+ @ Override
173+ public boolean detect (Throwable error ) {
174+ UnknownHostException unknownHostException =
175+ getCausedByOfType (error , UnknownHostException .class );
176+ return unknownHostException != null ;
177+ }
178+
179+ @ Override
180+ public String message (String url ) {
181+ return FriendlyException .populateFriendlyMessage (
182+ "Unable to resolve host in end point url" ,
183+ getUnknownHostFriendlyExceptionAction (url ),
184+ getFriendlyExceptionBanner (url ),
185+ "This message is only logged the first time it occurs after startup." );
186+ }
187+
188+ private static String getUnknownHostFriendlyExceptionAction (String url ) {
189+ return "Please upgrade your network to resolve the host in url :"
190+ + url
191+ + "\n Learn more about troubleshooting unknown host exception here: https://go.microsoft.com/fwlink/?linkid=2185830" ;
192+ }
104193 }
105194
106- @ Nullable
107- private static String getCustomJavaKeystorePath () {
108- String cacertsPath = System .getProperty ("javax.net.ssl.trustStore" );
109- if (cacertsPath != null ) {
110- return new File (cacertsPath ).getPath ();
195+ static class CipherExceptionDetector implements FriendlyExceptionDetector {
196+
197+ private static final List <String > EXPECTED_CIPHERS =
198+ Arrays .asList (
199+ "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" ,
200+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" ,
201+ "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384" ,
202+ "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" );
203+ private final List <String > cipherSuitesFromJvm ;
204+
205+ static CipherExceptionDetector create () throws NoSuchAlgorithmException {
206+ SSLSocketFactory socketFactory = SSLContext .getDefault ().getSocketFactory ();
207+ return new CipherExceptionDetector (Arrays .asList (socketFactory .getSupportedCipherSuites ()));
208+ }
209+
210+ CipherExceptionDetector (List <String > cipherSuitesFromJvm ) {
211+ this .cipherSuitesFromJvm = cipherSuitesFromJvm ;
212+ }
213+
214+ @ Override
215+ public boolean detect (Throwable error ) {
216+ IOException exception = getCausedByOfType (error , IOException .class );
217+ if (exception == null ) {
218+ return false ;
219+ }
220+ for (String cipher : EXPECTED_CIPHERS ) {
221+ if (cipherSuitesFromJvm .contains (cipher )) {
222+ return false ;
223+ }
224+ }
225+ return true ;
226+ }
227+
228+ @ Override
229+ public String message (String url ) {
230+ return FriendlyException .populateFriendlyMessage (
231+ "Probable root cause may be : missing cipher suites which are expected by the requested target." ,
232+ getCipherFriendlyExceptionAction (url ),
233+ getFriendlyExceptionBanner (url ),
234+ "This message is only logged the first time it occurs after startup." );
235+ }
236+
237+ private static String getCipherFriendlyExceptionAction (String url ) {
238+ StringBuilder actionBuilder = new StringBuilder ();
239+ actionBuilder
240+ .append (
241+ "The Application Insights Java agent detects that you do not have any of the following cipher suites that are supported by the endpoint it connects to: "
242+ + url )
243+ .append ("\n " );
244+ for (String missingCipher : EXPECTED_CIPHERS ) {
245+ actionBuilder .append (missingCipher ).append ("\n " );
246+ }
247+ actionBuilder .append (
248+ "Learn more about troubleshooting this network issue related to cipher suites here: https://go.microsoft.com/fwlink/?linkid=2185426" );
249+ return actionBuilder .toString ();
111250 }
112- return null ;
113251 }
114252
115253 private NetworkFriendlyExceptions () {}
0 commit comments