@@ -86,7 +86,7 @@ public ResourceResolver newResourceResolver() {
86
86
if (unavailabilityCause () != null ) {
87
87
return null ;
88
88
}
89
- return new JndiResourceResolver ();
89
+ return new JndiResourceResolver (new JndiRecordFetcher () );
90
90
}
91
91
92
92
@ Nullable
@@ -95,22 +95,32 @@ public Throwable unavailabilityCause() {
95
95
return JNDI_UNAVAILABILITY_CAUSE ;
96
96
}
97
97
98
+ @ VisibleForTesting
99
+ interface RecordFetcher {
100
+ List <String > getAllRecords (String recordType , String name ) throws NamingException ;
101
+ }
102
+
98
103
@ VisibleForTesting
99
104
static final class JndiResourceResolver implements DnsNameResolver .ResourceResolver {
100
105
private static final Logger logger =
101
106
Logger .getLogger (JndiResourceResolver .class .getName ());
102
107
103
108
private static final Pattern whitespace = Pattern .compile ("\\ s+" );
104
109
110
+ private final RecordFetcher recordFetcher ;
111
+
112
+ public JndiResourceResolver (RecordFetcher recordFetcher ) {
113
+ this .recordFetcher = recordFetcher ;
114
+ }
115
+
105
116
@ Override
106
117
public List <String > resolveTxt (String serviceConfigHostname ) throws NamingException {
107
- checkAvailable ();
108
118
if (logger .isLoggable (Level .FINER )) {
109
119
logger .log (
110
120
Level .FINER , "About to query TXT records for {0}" , new Object []{serviceConfigHostname });
111
121
}
112
122
List <String > serviceConfigRawTxtRecords =
113
- getAllRecords ("TXT" , "dns:///" + serviceConfigHostname );
123
+ recordFetcher . getAllRecords ("TXT" , "dns:///" + serviceConfigHostname );
114
124
if (logger .isLoggable (Level .FINER )) {
115
125
logger .log (
116
126
Level .FINER , "Found {0} TXT records" , new Object []{serviceConfigRawTxtRecords .size ()});
@@ -126,13 +136,12 @@ public List<String> resolveTxt(String serviceConfigHostname) throws NamingExcept
126
136
@ Override
127
137
public List <EquivalentAddressGroup > resolveSrv (
128
138
AddressResolver addressResolver , String grpclbHostname ) throws Exception {
129
- checkAvailable ();
130
139
if (logger .isLoggable (Level .FINER )) {
131
140
logger .log (
132
141
Level .FINER , "About to query SRV records for {0}" , new Object []{grpclbHostname });
133
142
}
134
143
List <String > grpclbSrvRecords =
135
- getAllRecords ("SRV" , "dns:///" + grpclbHostname );
144
+ recordFetcher . getAllRecords ("SRV" , "dns:///" + grpclbHostname );
136
145
if (logger .isLoggable (Level .FINER )) {
137
146
logger .log (
138
147
Level .FINER , "Found {0} SRV records" , new Object []{grpclbSrvRecords .size ()});
@@ -144,14 +153,23 @@ public List<EquivalentAddressGroup> resolveSrv(
144
153
for (String srvRecord : grpclbSrvRecords ) {
145
154
try {
146
155
SrvRecord record = parseSrvRecord (srvRecord );
156
+ // SRV requires the host name to be absolute
157
+ if (!record .host .endsWith ("." )) {
158
+ throw new RuntimeException ("Returned SRV host does not end in period: " + record .host );
159
+ }
147
160
161
+ // Strip trailing dot for appearance's sake. It _should_ be fine either way, but most
162
+ // people expect to see it without the dot.
163
+ String authority = record .host .substring (0 , record .host .length () - 1 );
164
+ // But we want to use the trailing dot for the IP lookup. The dot makes the name absolute
165
+ // instead of relative and so will avoid the search list like that in resolv.conf.
148
166
List <? extends InetAddress > addrs = addressResolver .resolveAddress (record .host );
149
167
List <SocketAddress > sockaddrs = new ArrayList <>(addrs .size ());
150
168
for (InetAddress addr : addrs ) {
151
169
sockaddrs .add (new InetSocketAddress (addr , record .port ));
152
170
}
153
171
Attributes attrs = Attributes .newBuilder ()
154
- .set (GrpcAttributes .ATTR_LB_ADDR_AUTHORITY , record . host )
172
+ .set (GrpcAttributes .ATTR_LB_ADDR_AUTHORITY , authority )
155
173
.build ();
156
174
balancerAddresses .add (
157
175
new EquivalentAddressGroup (Collections .unmodifiableList (sockaddrs ), attrs ));
@@ -176,8 +194,7 @@ public List<EquivalentAddressGroup> resolveSrv(
176
194
return Collections .unmodifiableList (balancerAddresses );
177
195
}
178
196
179
- @ VisibleForTesting
180
- static final class SrvRecord {
197
+ private static final class SrvRecord {
181
198
SrvRecord (String host , int port ) {
182
199
this .host = host ;
183
200
this .port = port ;
@@ -187,17 +204,50 @@ static final class SrvRecord {
187
204
final int port ;
188
205
}
189
206
190
- @ VisibleForTesting
191
207
@ SuppressWarnings ("BetaApi" ) // Verify is only kinda beta
192
- static SrvRecord parseSrvRecord (String rawRecord ) {
208
+ private static SrvRecord parseSrvRecord (String rawRecord ) {
193
209
String [] parts = whitespace .split (rawRecord );
194
210
Verify .verify (parts .length == 4 , "Bad SRV Record: %s" , rawRecord );
195
211
return new SrvRecord (parts [3 ], Integer .parseInt (parts [2 ]));
196
212
}
197
213
198
- @ IgnoreJRERequirement
199
- private static List <String > getAllRecords (String recordType , String name )
200
- throws NamingException {
214
+ /**
215
+ * Undo the quoting done in {@link com.sun.jndi.dns.ResourceRecord#decodeTxt}.
216
+ */
217
+ @ VisibleForTesting
218
+ static String unquote (String txtRecord ) {
219
+ StringBuilder sb = new StringBuilder (txtRecord .length ());
220
+ boolean inquote = false ;
221
+ for (int i = 0 ; i < txtRecord .length (); i ++) {
222
+ char c = txtRecord .charAt (i );
223
+ if (!inquote ) {
224
+ if (c == ' ' ) {
225
+ continue ;
226
+ } else if (c == '"' ) {
227
+ inquote = true ;
228
+ continue ;
229
+ }
230
+ } else {
231
+ if (c == '"' ) {
232
+ inquote = false ;
233
+ continue ;
234
+ } else if (c == '\\' ) {
235
+ c = txtRecord .charAt (++i );
236
+ assert c == '"' || c == '\\' ;
237
+ }
238
+ }
239
+ sb .append (c );
240
+ }
241
+ return sb .toString ();
242
+ }
243
+ }
244
+
245
+ @ VisibleForTesting
246
+ @ IgnoreJRERequirement
247
+ static final class JndiRecordFetcher implements RecordFetcher {
248
+ @ Override
249
+ public List <String > getAllRecords (String recordType , String name ) throws NamingException {
250
+ checkAvailable ();
201
251
String [] rrType = new String []{recordType };
202
252
List <String > records = new ArrayList <>();
203
253
@@ -237,7 +287,6 @@ private static List<String> getAllRecords(String recordType, String name)
237
287
return records ;
238
288
}
239
289
240
- @ IgnoreJRERequirement
241
290
private static void closeThenThrow (NamingEnumeration <?> namingEnumeration , NamingException e )
242
291
throws NamingException {
243
292
try {
@@ -248,7 +297,6 @@ private static void closeThenThrow(NamingEnumeration<?> namingEnumeration, Namin
248
297
throw e ;
249
298
}
250
299
251
- @ IgnoreJRERequirement
252
300
private static void closeThenThrow (DirContext ctx , NamingException e ) throws NamingException {
253
301
try {
254
302
ctx .close ();
@@ -258,36 +306,6 @@ private static void closeThenThrow(DirContext ctx, NamingException e) throws Nam
258
306
throw e ;
259
307
}
260
308
261
- /**
262
- * Undo the quoting done in {@link com.sun.jndi.dns.ResourceRecord#decodeTxt}.
263
- */
264
- @ VisibleForTesting
265
- static String unquote (String txtRecord ) {
266
- StringBuilder sb = new StringBuilder (txtRecord .length ());
267
- boolean inquote = false ;
268
- for (int i = 0 ; i < txtRecord .length (); i ++) {
269
- char c = txtRecord .charAt (i );
270
- if (!inquote ) {
271
- if (c == ' ' ) {
272
- continue ;
273
- } else if (c == '"' ) {
274
- inquote = true ;
275
- continue ;
276
- }
277
- } else {
278
- if (c == '"' ) {
279
- inquote = false ;
280
- continue ;
281
- } else if (c == '\\' ) {
282
- c = txtRecord .charAt (++i );
283
- assert c == '"' || c == '\\' ;
284
- }
285
- }
286
- sb .append (c );
287
- }
288
- return sb .toString ();
289
- }
290
-
291
309
private static void checkAvailable () {
292
310
if (JNDI_UNAVAILABILITY_CAUSE != null ) {
293
311
throw new UnsupportedOperationException (
0 commit comments