• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #20027

24 Oct 2025 10:28PM UTC coverage: 88.611% (+0.04%) from 88.57%
#20027

push

github

ejona86
alts: Remove dep on grpclb

There are no longer any users of grpclb on directpath, so remove the
special-casing logic to choose between TLS and ALTS for grpclb-provided
backends.

Removing the grpclb dep can speed channel startup, as grpclb's DNS
resolver does SRV lookups which are no longer needed.

34957 of 39450 relevant lines covered (88.61%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.85
/../core/src/main/java/io/grpc/internal/JndiResourceResolverFactory.java
1
/*
2
 * Copyright 2018 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.internal;
18

19
import android.annotation.SuppressLint;
20
import com.google.common.annotations.VisibleForTesting;
21
import com.google.common.base.Verify;
22
import io.grpc.internal.DnsNameResolver.ResourceResolver;
23
import io.grpc.internal.DnsNameResolver.SrvRecord;
24
import java.util.ArrayList;
25
import java.util.Arrays;
26
import java.util.Collections;
27
import java.util.Hashtable;
28
import java.util.List;
29
import java.util.logging.Level;
30
import java.util.logging.Logger;
31
import java.util.regex.Pattern;
32
import javax.annotation.Nullable;
33
import javax.naming.NamingEnumeration;
34
import javax.naming.NamingException;
35
import javax.naming.directory.Attribute;
36
import javax.naming.directory.DirContext;
37
import javax.naming.directory.InitialDirContext;
38
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
39

40
/**
41
 * {@link JndiResourceResolverFactory} resolves additional records for the DnsNameResolver.
42
 */
43
final class JndiResourceResolverFactory implements DnsNameResolver.ResourceResolverFactory {
44

45
  @Nullable
46
  @SuppressWarnings("StaticAssignmentOfThrowable")
47
  private static final Throwable JNDI_UNAVAILABILITY_CAUSE = initJndi();
1✔
48

49
  // @UsedReflectively
50
  public JndiResourceResolverFactory() {}
1✔
51

52
  /**
53
   * Returns whether the JNDI DNS resolver is available.  This is accomplished by looking up a
54
   * particular class.  It is believed to be the default (only?) DNS resolver that will actually be
55
   * used.  It is provided by the OpenJDK, but unlikely Android.  Actual resolution will be done by
56
   * using a service provider when a hostname query is present, so the {@code DnsContextFactory}
57
   * may not actually be used to perform the query.  This is believed to be "okay."
58
   */
59
  @Nullable
60
  private static Throwable initJndi() {
61
    try {
62
      Class.forName("javax.naming.directory.InitialDirContext");
1✔
63
      Class.forName("com.sun.jndi.dns.DnsContextFactory");
1✔
64
    } catch (ClassNotFoundException e) {
×
65
      return e;
×
66
    } catch (RuntimeException e) {
×
67
      return e;
×
68
    } catch (Error e) {
×
69
      return e;
×
70
    }
1✔
71
    return null;
1✔
72
  }
73

74
  @Nullable
75
  @Override
76
  public ResourceResolver newResourceResolver() {
77
    if (unavailabilityCause() != null) {
1✔
78
      return null;
×
79
    }
80
    return new JndiResourceResolver(new JndiRecordFetcher());
1✔
81
  }
82

83
  @Nullable
84
  @Override
85
  public Throwable unavailabilityCause() {
86
    return JNDI_UNAVAILABILITY_CAUSE;
1✔
87
  }
88

89
  @VisibleForTesting
90
  interface RecordFetcher {
91
    List<String> getAllRecords(String recordType, String name) throws NamingException;
92
  }
93

94
  @VisibleForTesting
95
  static final class JndiResourceResolver implements DnsNameResolver.ResourceResolver {
96
    private static final Logger logger =
1✔
97
        Logger.getLogger(JndiResourceResolver.class.getName());
1✔
98

99
    private static final Pattern whitespace = Pattern.compile("\\s+");
1✔
100

101
    private final RecordFetcher recordFetcher;
102

103
    public JndiResourceResolver(RecordFetcher recordFetcher) {
1✔
104
      this.recordFetcher = recordFetcher;
1✔
105
    }
1✔
106

107
    @Override
108
    public List<String> resolveTxt(String serviceConfigHostname) throws NamingException {
109
      if (logger.isLoggable(Level.FINER)) {
1✔
110
        logger.log(
×
111
            Level.FINER, "About to query TXT records for {0}", new Object[]{serviceConfigHostname});
112
      }
113
      List<String> serviceConfigRawTxtRecords =
1✔
114
          recordFetcher.getAllRecords("TXT", "dns:///" + serviceConfigHostname);
1✔
115
      if (logger.isLoggable(Level.FINER)) {
1✔
116
        logger.log(
×
117
            Level.FINER, "Found {0} TXT records", new Object[]{serviceConfigRawTxtRecords.size()});
×
118
      }
119
      List<String> serviceConfigTxtRecords =
1✔
120
          new ArrayList<>(serviceConfigRawTxtRecords.size());
1✔
121
      for (String serviceConfigRawTxtRecord : serviceConfigRawTxtRecords) {
1✔
122
        serviceConfigTxtRecords.add(unquote(serviceConfigRawTxtRecord));
1✔
123
      }
1✔
124
      return Collections.unmodifiableList(serviceConfigTxtRecords);
1✔
125
    }
126

127
    @Override
128
    public List<SrvRecord> resolveSrv(String host) throws Exception {
129
      if (logger.isLoggable(Level.FINER)) {
1✔
130
        logger.log(
×
131
            Level.FINER, "About to query SRV records for {0}", new Object[]{host});
132
      }
133
      List<String> rawSrvRecords =
1✔
134
          recordFetcher.getAllRecords("SRV", "dns:///" + host);
1✔
135
      if (logger.isLoggable(Level.FINER)) {
1✔
136
        logger.log(
×
137
            Level.FINER, "Found {0} SRV records", new Object[]{rawSrvRecords.size()});
×
138
      }
139
      List<SrvRecord> srvRecords = new ArrayList<>(rawSrvRecords.size());
1✔
140
      Exception first = null;
1✔
141
      Level level = Level.WARNING;
1✔
142
      for (String rawSrv : rawSrvRecords) {
1✔
143
        try {
144
          String[] parts = whitespace.split(rawSrv, 5);
1✔
145
          Verify.verify(parts.length == 4, "Bad SRV Record: %s", rawSrv);
1✔
146
          // SRV requires the host name to be absolute
147
          if (!parts[3].endsWith(".")) {
1✔
148
            throw new RuntimeException("Returned SRV host does not end in period: " + parts[3]);
1✔
149
          }
150
          srvRecords.add(new SrvRecord(parts[3], Integer.parseInt(parts[2])));
1✔
151
        } catch (RuntimeException e) {
1✔
152
          logger.log(level, "Failed to construct SRV record " + rawSrv, e);
1✔
153
          if (first == null) {
1✔
154
            first = e;
1✔
155
            level = Level.FINE;
1✔
156
          }
157
        }
1✔
158
      }
1✔
159
      if (srvRecords.isEmpty() && first != null) {
1✔
160
        throw first;
×
161
      }
162
      return Collections.unmodifiableList(srvRecords);
1✔
163
    }
164

165
    /**
166
     * Undo the quoting done in {@link com.sun.jndi.dns.ResourceRecord#decodeTxt}.
167
     */
168
    @VisibleForTesting
169
    static String unquote(String txtRecord) {
170
      StringBuilder sb = new StringBuilder(txtRecord.length());
1✔
171
      boolean inquote = false;
1✔
172
      for (int i = 0; i < txtRecord.length(); i++) {
1✔
173
        char c = txtRecord.charAt(i);
1✔
174
        if (!inquote) {
1✔
175
          if (c == ' ') {
1✔
176
            continue;
1✔
177
          } else if (c == '"') {
1✔
178
            inquote = true;
1✔
179
            continue;
1✔
180
          }
181
        } else {
182
          if (c == '"') {
1✔
183
            inquote = false;
1✔
184
            continue;
1✔
185
          } else if (c == '\\') {
1✔
186
            c = txtRecord.charAt(++i);
1✔
187
            assert c == '"' || c == '\\';
1✔
188
          }
189
        }
190
        sb.append(c);
1✔
191
      }
192
      return sb.toString();
1✔
193
    }
194
  }
195

196
  @VisibleForTesting
1✔
197
  @IgnoreJRERequirement
198
  @SuppressWarnings({"JdkObsolete", "BanJNDI"})
199
  // javax.naming.* is only loaded reflectively and is never loaded for Android
200
  // The lint issue id is supposed to be "InvalidPackage" but it doesn't work, don't know why.
201
  // Use "all" as the lint issue id to suppress all types of lint error.
202
  @SuppressLint("all")
203
  static final class JndiRecordFetcher implements RecordFetcher {
1✔
204
    @Override
205
    public List<String> getAllRecords(String recordType, String name) throws NamingException {
206
      checkAvailable();
1✔
207
      String[] rrType = new String[]{recordType};
1✔
208
      List<String> records = new ArrayList<>();
1✔
209

210
      Hashtable<String, String> env = new Hashtable<>();
1✔
211
      env.put("com.sun.jndi.ldap.connect.timeout", "5000");
1✔
212
      env.put("com.sun.jndi.ldap.read.timeout", "5000");
1✔
213
      DirContext dirContext = new InitialDirContext(env);
1✔
214

215
      try {
216
        javax.naming.directory.Attributes attrs = dirContext.getAttributes(name, rrType);
1✔
217
        NamingEnumeration<? extends Attribute> rrGroups = attrs.getAll();
1✔
218

219
        try {
220
          while (rrGroups.hasMore()) {
1✔
221
            Attribute rrEntry = rrGroups.next();
×
222
            assert Arrays.asList(rrType).contains(rrEntry.getID());
×
223
            NamingEnumeration<?> rrValues = rrEntry.getAll();
×
224
            try {
225
              while (rrValues.hasMore()) {
×
226
                records.add(String.valueOf(rrValues.next()));
×
227
              }
228
            } catch (NamingException ne) {
×
229
              closeThenThrow(rrValues, ne);
×
230
            }
×
231
            rrValues.close();
×
232
          }
×
233
        } catch (NamingException ne) {
×
234
          closeThenThrow(rrGroups, ne);
×
235
        }
1✔
236
        rrGroups.close();
1✔
237
      } catch (NamingException ne) {
×
238
        closeThenThrow(dirContext, ne);
×
239
      }
1✔
240
      dirContext.close();
1✔
241

242
      return records;
1✔
243
    }
244

245
    private static void closeThenThrow(NamingEnumeration<?> namingEnumeration, NamingException e)
246
        throws NamingException {
247
      try {
248
        namingEnumeration.close();
×
249
      } catch (NamingException ignored) {
×
250
        // ignore
251
      }
×
252
      throw e;
×
253
    }
254

255
    private static void closeThenThrow(DirContext ctx, NamingException e) throws NamingException {
256
      try {
257
        ctx.close();
×
258
      } catch (NamingException ignored) {
×
259
        // ignore
260
      }
×
261
      throw e;
×
262
    }
263

264
    private static void checkAvailable() {
265
      if (JNDI_UNAVAILABILITY_CAUSE != null) {
1✔
266
        throw new UnsupportedOperationException(
×
267
            "JNDI is not currently available", JNDI_UNAVAILABILITY_CAUSE);
×
268
      }
269
    }
1✔
270
  }
271
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc