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

grpc / grpc-java / #20168

06 Feb 2026 10:39AM UTC coverage: 88.682% (-0.04%) from 88.723%
#20168

push

github

web-flow
xds: Remove isXdsSniEnabled and align SNI logic with gRFC A101 (#12625)

## Description
Remove the `isXdsSniEnabled` (GRPC_EXPERIMENTAL_XDS_SNI) guard so that
SNI determination via xDS is always enabled. This aligns the behavior
with
**gRFC A101**, where SNI is determined by xDS configurations such as
`auto_host_sni` or `UpstreamTlsContext.sni`, without relying on an
experimental toggle.

This change does **not** remove the
`GRPC_USE_CHANNEL_AUTHORITY_IF_NO_SNI_APPLICABLE` fallback logic, which
remains unchanged.

## Changes
- Remove the `isXdsSniEnabled` flag and the related conditional logic.
- Remove test cases that specifically covered behavior when the 
experimental flag was disabled, since the flag is no longer supported.

Ref #11784

35399 of 39917 relevant lines covered (88.68%)

0.89 hits per line

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

89.66
/../xds/src/main/java/io/grpc/xds/internal/security/SecurityProtocolNegotiators.java
1
/*
2
 * Copyright 2019 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.xds.internal.security;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20

21
import com.google.common.annotations.VisibleForTesting;
22
import com.google.common.base.Strings;
23
import io.grpc.Attributes;
24
import io.grpc.Grpc;
25
import io.grpc.internal.GrpcUtil;
26
import io.grpc.internal.ObjectPool;
27
import io.grpc.netty.GrpcHttp2ConnectionHandler;
28
import io.grpc.netty.InternalProtocolNegotiationEvent;
29
import io.grpc.netty.InternalProtocolNegotiator;
30
import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator;
31
import io.grpc.netty.InternalProtocolNegotiators;
32
import io.grpc.netty.ProtocolNegotiationEvent;
33
import io.grpc.xds.EnvoyServerProtoData;
34
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
35
import io.grpc.xds.internal.XdsInternalAttributes;
36
import io.grpc.xds.internal.security.trust.CertificateUtils;
37
import io.netty.channel.ChannelHandler;
38
import io.netty.channel.ChannelHandlerAdapter;
39
import io.netty.channel.ChannelHandlerContext;
40
import io.netty.channel.ChannelInboundHandlerAdapter;
41
import io.netty.handler.ssl.SslContext;
42
import io.netty.util.AsciiString;
43
import java.security.cert.CertStoreException;
44
import java.util.AbstractMap;
45
import java.util.ArrayList;
46
import java.util.List;
47
import java.util.concurrent.Executor;
48
import java.util.logging.Level;
49
import java.util.logging.Logger;
50
import javax.annotation.Nullable;
51
import javax.net.ssl.X509TrustManager;
52

53
/**
54
 * Provides client and server side gRPC {@link ProtocolNegotiator}s to provide the SSL
55
 * context.
56
 */
57
@VisibleForTesting
58
public final class SecurityProtocolNegotiators {
59

60
  // Prevent instantiation.
61
  private SecurityProtocolNegotiators() {
62
  }
63

64
  private static final Logger logger
1✔
65
      = Logger.getLogger(SecurityProtocolNegotiators.class.getName());
1✔
66

67
  private static final AsciiString SCHEME = AsciiString.of("http");
1✔
68

69
  public static final Attributes.Key<SslContextProviderSupplier>
70
      ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER =
1✔
71
      Attributes.Key.create("io.grpc.xds.internal.security.server.sslContextProviderSupplier");
1✔
72

73
  /** Attribute key for SslContextProviderSupplier (used from client) for a subchannel. */
74
  @Grpc.TransportAttr
75
  public static final Attributes.Key<SslContextProviderSupplier>
76
      ATTR_SSL_CONTEXT_PROVIDER_SUPPLIER =
1✔
77
      Attributes.Key.create("io.grpc.xds.internal.security.SslContextProviderSupplier");
1✔
78

79
  /**
80
   * Returns a {@link InternalProtocolNegotiator.ClientFactory}.
81
   *
82
   * @param fallbackNegotiator protocol negotiator to use as fallback.
83
   */
84
  public static InternalProtocolNegotiator.ClientFactory clientProtocolNegotiatorFactory(
85
      @Nullable InternalProtocolNegotiator.ClientFactory fallbackNegotiator) {
86
    return new ClientFactory(fallbackNegotiator);
1✔
87
  }
88

89
  public static InternalProtocolNegotiator.ServerFactory serverProtocolNegotiatorFactory(
90
      @Nullable InternalProtocolNegotiator.ServerFactory fallbackNegotiator) {
91
    return new ServerFactory(fallbackNegotiator);
1✔
92
  }
93

94
  private static final class ServerFactory implements InternalProtocolNegotiator.ServerFactory {
95

96
    private final InternalProtocolNegotiator.ServerFactory fallbackProtocolNegotiator;
97

98
    private ServerFactory(InternalProtocolNegotiator.ServerFactory fallbackNegotiator) {
1✔
99
      this.fallbackProtocolNegotiator = fallbackNegotiator;
1✔
100
    }
1✔
101

102
    @Override
103
    public ProtocolNegotiator newNegotiator(ObjectPool<? extends Executor> offloadExecutorPool) {
104
      return new ServerSecurityProtocolNegotiator(
1✔
105
          fallbackProtocolNegotiator.newNegotiator(offloadExecutorPool));
1✔
106
    }
107
  }
108

109
  private static final class ClientFactory implements InternalProtocolNegotiator.ClientFactory {
110

111
    private final InternalProtocolNegotiator.ClientFactory fallbackProtocolNegotiator;
112

113
    private ClientFactory(InternalProtocolNegotiator.ClientFactory fallbackNegotiator) {
1✔
114
      this.fallbackProtocolNegotiator = fallbackNegotiator;
1✔
115
    }
1✔
116

117
    @Override
118
    public ProtocolNegotiator newNegotiator() {
119
      return new ClientSecurityProtocolNegotiator(fallbackProtocolNegotiator.newNegotiator());
1✔
120
    }
121

122
    @Override
123
    public int getDefaultPort() {
124
      return GrpcUtil.DEFAULT_PORT_SSL;
1✔
125
    }
126
  }
127

128
  @VisibleForTesting
129
  static final class ClientSecurityProtocolNegotiator implements ProtocolNegotiator {
130

131
    @Nullable private final ProtocolNegotiator fallbackProtocolNegotiator;
132

133
    ClientSecurityProtocolNegotiator(@Nullable ProtocolNegotiator fallbackProtocolNegotiator) {
1✔
134
      this.fallbackProtocolNegotiator = fallbackProtocolNegotiator;
1✔
135
    }
1✔
136

137
    @Override
138
    public AsciiString scheme() {
139
      return SCHEME;
1✔
140
    }
141

142
    @Override
143
    public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) {
144
      // check if SslContextProviderSupplier was passed via attributes
145
      SslContextProviderSupplier localSslContextProviderSupplier =
1✔
146
          grpcHandler.getEagAttributes().get(ATTR_SSL_CONTEXT_PROVIDER_SUPPLIER);
1✔
147
      if (localSslContextProviderSupplier == null) {
1✔
148
        checkNotNull(
1✔
149
            fallbackProtocolNegotiator, "No TLS config and no fallbackProtocolNegotiator!");
150
        return fallbackProtocolNegotiator.newHandler(grpcHandler);
1✔
151
      }
152
      return new ClientSecurityHandler(grpcHandler, localSslContextProviderSupplier,
1✔
153
          grpcHandler.getEagAttributes().get(XdsInternalAttributes.ATTR_ADDRESS_NAME));
1✔
154
    }
155

156
    @Override
157
    public void close() {}
1✔
158
  }
159

160
  private static class BufferReadsHandler extends ChannelInboundHandlerAdapter {
1✔
161
    private final List<Object> reads = new ArrayList<>();
1✔
162
    private boolean readComplete;
163

164
    @Override
165
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
166
      reads.add(msg);
1✔
167
    }
1✔
168

169
    @Override
170
    public void channelReadComplete(ChannelHandlerContext ctx) {
171
      readComplete = true;
1✔
172
    }
1✔
173

174
    @Override
175
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
176
      for (Object msg : reads) {
1✔
177
        super.channelRead(ctx, msg);
1✔
178
      }
1✔
179
      if (readComplete) {
1✔
180
        super.channelReadComplete(ctx);
1✔
181
      }
182
    }
1✔
183

184
    @Override
185
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
186
      logger.log(Level.SEVERE, "exceptionCaught", cause);
×
187
      ctx.fireExceptionCaught(cause);
×
188
    }
×
189
  }
190

191
  @VisibleForTesting
192
  static final class ClientSecurityHandler
193
      extends InternalProtocolNegotiators.ProtocolNegotiationHandler {
194
    private final GrpcHttp2ConnectionHandler grpcHandler;
195
    private final SslContextProviderSupplier sslContextProviderSupplier;
196
    private final String sni;
197
    private final boolean autoSniSanValidationDoesNotApply;
198

199
    ClientSecurityHandler(
200
        GrpcHttp2ConnectionHandler grpcHandler,
201
        SslContextProviderSupplier sslContextProviderSupplier,
202
        String endpointHostname) {
203
      super(
1✔
204
          // superclass (InternalProtocolNegotiators.ProtocolNegotiationHandler) expects 'next'
205
          // handler but we don't have a next handler _yet_. So we "disable" superclass's behavior
206
          // here and then manually add 'next' when we call fireProtocolNegotiationEvent()
207
          new ChannelHandlerAdapter() {
1✔
208
            @Override
209
            public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
210
              ctx.pipeline().remove(this);
1✔
211
            }
1✔
212
          }, grpcHandler.getNegotiationLogger());
1✔
213
      checkNotNull(grpcHandler, "grpcHandler");
1✔
214
      this.grpcHandler = grpcHandler;
1✔
215
      this.sslContextProviderSupplier = sslContextProviderSupplier;
1✔
216
      EnvoyServerProtoData.BaseTlsContext tlsContext = sslContextProviderSupplier.getTlsContext();
1✔
217
      UpstreamTlsContext upstreamTlsContext = ((UpstreamTlsContext) tlsContext);
1✔
218

219
      String sniToUse = upstreamTlsContext.getAutoHostSni()
1✔
220
          && !Strings.isNullOrEmpty(endpointHostname)
1✔
221
          ? endpointHostname : upstreamTlsContext.getSni();
1✔
222
      if (sniToUse.isEmpty()) {
1✔
223
        if (CertificateUtils.useChannelAuthorityIfNoSniApplicable) {
1✔
224
          sniToUse = grpcHandler.getAuthority();
×
225
        }
226
        autoSniSanValidationDoesNotApply = true;
1✔
227
      } else {
228
        autoSniSanValidationDoesNotApply = false;
1✔
229
      }
230
      sni = sniToUse;
1✔
231
    }
1✔
232

233
    @VisibleForTesting
234
    String getSni() {
235
      return sni;
1✔
236
    }
237

238
    @Override
239
    protected void handlerAdded0(final ChannelHandlerContext ctx) {
240
      final BufferReadsHandler bufferReads = new BufferReadsHandler();
1✔
241
      ctx.pipeline().addBefore(ctx.name(), null, bufferReads);
1✔
242

243
      sslContextProviderSupplier.updateSslContext(
1✔
244
          new SslContextProvider.Callback(ctx.executor()) {
1✔
245

246
            @Override
247
            public void updateSslContextAndExtendedX509TrustManager(
248
                AbstractMap.SimpleImmutableEntry<SslContext, X509TrustManager> sslContextAndTm) {
249
              if (ctx.isRemoved()) {
1✔
250
                return;
1✔
251
              }
252
              logger.log(
1✔
253
                  Level.FINEST,
254
                  "ClientSecurityHandler.updateSslContext authority={0}, ctx.name={1}",
255
                  new Object[]{grpcHandler.getAuthority(), ctx.name()});
1✔
256
              ChannelHandler handler =
1✔
257
                  InternalProtocolNegotiators.tls(
1✔
258
                      sslContextAndTm.getKey(), sni, sslContextAndTm.getValue())
1✔
259
                      .newHandler(grpcHandler);
1✔
260

261
              // Delegate rest of handshake to TLS handler
262
              ctx.pipeline().addAfter(ctx.name(), null, handler);
1✔
263
              fireProtocolNegotiationEvent(ctx);
1✔
264
              ctx.pipeline().remove(bufferReads);
1✔
265
            }
1✔
266

267
            @Override
268
            public void onException(Throwable throwable) {
269
              ctx.fireExceptionCaught(throwable);
×
270
            }
×
271
          },
272
          autoSniSanValidationDoesNotApply);
273
    }
1✔
274

275
    @Override
276
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
277
        throws Exception {
278
      logger.log(Level.SEVERE, "exceptionCaught", cause);
×
279
      ctx.fireExceptionCaught(cause);
×
280
    }
×
281
  }
282

283
  private static final class ServerSecurityProtocolNegotiator implements ProtocolNegotiator {
284

285
    @Nullable private final ProtocolNegotiator fallbackProtocolNegotiator;
286

287
    /** Constructor. */
288
    @VisibleForTesting
289
    public ServerSecurityProtocolNegotiator(
290
        @Nullable ProtocolNegotiator fallbackProtocolNegotiator) {
1✔
291
      this.fallbackProtocolNegotiator = fallbackProtocolNegotiator;
1✔
292
    }
1✔
293

294
    @Override
295
    public AsciiString scheme() {
296
      return SCHEME;
×
297
    }
298

299
    @Override
300
    public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) {
301
      return new HandlerPickerHandler(grpcHandler, fallbackProtocolNegotiator);
1✔
302
    }
303

304
    @Override
305
    public void close() {}
×
306
  }
307

308
  @VisibleForTesting
309
  static final class HandlerPickerHandler
310
      extends ChannelInboundHandlerAdapter {
311
    private final GrpcHttp2ConnectionHandler grpcHandler;
312
    @Nullable private final ProtocolNegotiator fallbackProtocolNegotiator;
313

314
    HandlerPickerHandler(
315
        GrpcHttp2ConnectionHandler grpcHandler,
316
        @Nullable ProtocolNegotiator fallbackProtocolNegotiator) {
1✔
317
      this.grpcHandler = checkNotNull(grpcHandler, "grpcHandler");
1✔
318
      this.fallbackProtocolNegotiator = fallbackProtocolNegotiator;
1✔
319
    }
1✔
320

321
    @Override
322
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
323
      if (evt instanceof ProtocolNegotiationEvent) {
1✔
324
        ProtocolNegotiationEvent pne = (ProtocolNegotiationEvent)evt;
1✔
325
        SslContextProviderSupplier sslContextProviderSupplier = InternalProtocolNegotiationEvent
1✔
326
                .getAttributes(pne).get(ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER);
1✔
327
        if (sslContextProviderSupplier == null) {
1✔
328
          logger.log(Level.FINE, "No sslContextProviderSupplier found in filterChainMatch "
1✔
329
              + "for connection from {0} to {1}",
330
              new Object[]{ctx.channel().remoteAddress(), ctx.channel().localAddress()});
1✔
331
          if (fallbackProtocolNegotiator == null) {
1✔
332
            ctx.fireExceptionCaught(new CertStoreException("No certificate source found!"));
1✔
333
            return;
1✔
334
          }
335
          logger.log(Level.FINE, "Using fallback credentials for connection from {0} to {1}",
1✔
336
              new Object[]{ctx.channel().remoteAddress(), ctx.channel().localAddress()});
1✔
337
          ctx.pipeline()
1✔
338
              .replace(
1✔
339
                  this,
340
                  null,
341
                  fallbackProtocolNegotiator.newHandler(grpcHandler));
1✔
342
          ctx.fireUserEventTriggered(pne);
1✔
343
          return;
1✔
344
        } else {
345
          ctx.pipeline()
1✔
346
              .replace(
1✔
347
                  this,
348
                  null,
349
                  new ServerSecurityHandler(
350
                      grpcHandler, sslContextProviderSupplier));
351
          ctx.fireUserEventTriggered(pne);
1✔
352
          return;
1✔
353
        }
354
      } else {
355
        super.userEventTriggered(ctx, evt);
×
356
      }
357
    }
×
358
  }
359

360
  @VisibleForTesting
361
  static final class ServerSecurityHandler
362
          extends InternalProtocolNegotiators.ProtocolNegotiationHandler {
363
    private final GrpcHttp2ConnectionHandler grpcHandler;
364
    private final SslContextProviderSupplier sslContextProviderSupplier;
365

366
    ServerSecurityHandler(
367
            GrpcHttp2ConnectionHandler grpcHandler,
368
            SslContextProviderSupplier sslContextProviderSupplier) {
369
      super(
1✔
370
          // superclass (InternalProtocolNegotiators.ProtocolNegotiationHandler) expects 'next'
371
          // handler but we don't have a next handler _yet_. So we "disable" superclass's behavior
372
          // here and then manually add 'next' when we call fireProtocolNegotiationEvent()
373
          new ChannelHandlerAdapter() {
1✔
374
            @Override
375
            public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
376
              ctx.pipeline().remove(this);
1✔
377
            }
1✔
378
          }, grpcHandler.getNegotiationLogger());
1✔
379
      checkNotNull(grpcHandler, "grpcHandler");
1✔
380
      this.grpcHandler = grpcHandler;
1✔
381
      this.sslContextProviderSupplier = sslContextProviderSupplier;
1✔
382
    }
1✔
383

384
    @Override
385
    protected void handlerAdded0(final ChannelHandlerContext ctx) {
386
      final BufferReadsHandler bufferReads = new BufferReadsHandler();
1✔
387
      ctx.pipeline().addBefore(ctx.name(), null, bufferReads);
1✔
388

389
      sslContextProviderSupplier.updateSslContext(
1✔
390
          new SslContextProvider.Callback(ctx.executor()) {
1✔
391

392
            @Override
393
            public void updateSslContextAndExtendedX509TrustManager(
394
                AbstractMap.SimpleImmutableEntry<SslContext, X509TrustManager> sslContextAndTm) {
395
              ChannelHandler handler = InternalProtocolNegotiators.serverTls(
1✔
396
                  sslContextAndTm.getKey()).newHandler(grpcHandler);
1✔
397

398
              // Delegate rest of handshake to TLS handler
399
              if (!ctx.isRemoved()) {
1✔
400
                ctx.pipeline().addAfter(ctx.name(), null, handler);
1✔
401
                fireProtocolNegotiationEvent(ctx);
1✔
402
                ctx.pipeline().remove(bufferReads);
1✔
403
              }
404
            }
1✔
405

406
            @Override
407
            public void onException(Throwable throwable) {
408
              ctx.fireExceptionCaught(throwable);
×
409
            }
×
410
          },
411
          false);
412
    }
1✔
413
  }
414
}
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

© 2026 Coveralls, Inc