View Javadoc
1   /*
2    * Copyright 2024 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://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, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package io.netty.channel.socket.nio;
18  
19  import io.netty.channel.ChannelConfig;
20  import io.netty.channel.ChannelException;
21  import io.netty.channel.ChannelMetadata;
22  import io.netty.channel.ChannelOption;
23  import io.netty.channel.ChannelOutboundBuffer;
24  import io.netty.channel.DefaultChannelConfig;
25  import io.netty.channel.ServerChannelRecvByteBufAllocator;
26  import io.netty.channel.nio.AbstractNioMessageChannel;
27  import io.netty.util.NetUtil;
28  import io.netty.util.internal.PlatformDependent;
29  import io.netty.util.internal.SocketUtils;
30  import io.netty.util.internal.logging.InternalLogger;
31  import io.netty.util.internal.logging.InternalLoggerFactory;
32  
33  import java.io.IOException;
34  import java.lang.reflect.Method;
35  
36  import java.net.SocketAddress;
37  import java.nio.channels.SelectionKey;
38  import java.nio.channels.ServerSocketChannel;
39  import java.nio.channels.SocketChannel;
40  import java.nio.channels.spi.SelectorProvider;
41  import java.util.ArrayList;
42  import java.util.List;
43  import java.util.Map;
44  
45  import static io.netty.channel.ChannelOption.SO_BACKLOG;
46  import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
47  
48  
49  /**
50   * A {@link io.netty.channel.ServerChannel} implementation which uses
51   * NIO selector based implementation to support UNIX Domain Sockets. This is only supported when using Java 16+.
52   */
53  public final class NioServerDomainSocketChannel extends AbstractNioMessageChannel
54          implements io.netty.channel.ServerChannel {
55      private static final Method OPEN_SERVER_SOCKET_CHANNEL_WITH_FAMILY =
56              SelectorProviderUtil.findOpenMethod("openServerSocketChannel");
57      private static final InternalLogger logger = InternalLoggerFactory.getInstance(NioServerDomainSocketChannel.class);
58      private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16);
59      private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
60      private final NioDomainServerSocketChannelConfig config;
61      private volatile boolean bound;
62  
63      // Package-private for testing.
64      static ServerSocketChannel newChannel(SelectorProvider provider) {
65          if (PlatformDependent.javaVersion() < 16) {
66              throw new UnsupportedOperationException("Only supported with Java 16+");
67          }
68          try {
69              ServerSocketChannel channel =
70                      SelectorProviderUtil.newDomainSocketChannel(OPEN_SERVER_SOCKET_CHANNEL_WITH_FAMILY, provider);
71              if (channel == null) {
72                  throw new ChannelException("Failed to open a socket.");
73              }
74              return channel;
75          } catch (IOException e) {
76              throw new ChannelException("Failed to open a socket.", e);
77          }
78      }
79  
80      @Override
81      protected ServerSocketChannel javaChannel() {
82          return (ServerSocketChannel) super.javaChannel();
83      }
84  
85      /**
86       * Create a new instance
87       */
88      public NioServerDomainSocketChannel() {
89          this(DEFAULT_SELECTOR_PROVIDER);
90      }
91  
92      /**
93       * Create a new instance using the given {@link SelectorProvider}.
94       */
95      public NioServerDomainSocketChannel(SelectorProvider provider) {
96          this(newChannel(provider));
97      }
98  
99      /**
100      * Create a new instance using the given {@link ServerSocketChannel}.
101      */
102     public NioServerDomainSocketChannel(ServerSocketChannel channel) {
103         super(null, channel, SelectionKey.OP_ACCEPT);
104         if (PlatformDependent.javaVersion() < 16) {
105             throw new UnsupportedOperationException("Only supported with Java 16+");
106         }
107         config = new NioDomainServerSocketChannelConfig(this);
108         try {
109             // Check if we already have a local address bound.
110             bound = channel.getLocalAddress() != null;
111         } catch (IOException e) {
112             throw new ChannelException(e);
113         }
114     }
115 
116     @Override
117     public ChannelConfig config() {
118         return config;
119     }
120 
121     @Override
122     public ChannelMetadata metadata() {
123         return METADATA;
124     }
125 
126     @Override
127     public boolean isActive() {
128         // As java.nio.ServerSocketChannel.isBound() will continue to return true even after the channel was closed
129         // we will also need to check if it is open.
130         return isOpen() && bound;
131     }
132 
133     @Override
134     protected void doBind(SocketAddress localAddress) throws Exception {
135         javaChannel().bind(localAddress, config.getBacklog());
136         bound = true;
137     }
138 
139     @Override
140     protected void doDisconnect() throws Exception {
141         throw new UnsupportedOperationException();
142     }
143 
144     @Override
145     protected int doReadMessages(List<Object> buf) throws Exception {
146         SocketChannel ch = SocketUtils.accept(javaChannel());
147         try {
148             if (ch != null) {
149                 buf.add(new NioDomainSocketChannel(this, ch));
150                 return 1;
151             }
152         } catch (Throwable t) {
153             logger.warn("Failed to create a new channel from an accepted socket.", t);
154 
155             try {
156                 ch.close();
157             } catch (Throwable t2) {
158                 logger.warn("Failed to close a socket.", t2);
159             }
160         }
161 
162         return 0;
163     }
164 
165     @Override
166     protected boolean doWriteMessage(Object msg, ChannelOutboundBuffer in) throws Exception {
167         throw new UnsupportedOperationException();
168     }
169 
170     @Override
171     protected void doClose() throws Exception {
172         // Obtain the localAddress before we close the channel so it will not return null if we did not retrieve
173         // it before.
174         SocketAddress local = localAddress();
175         try {
176             super.doClose();
177         } finally {
178             javaChannel().close();
179             if (local != null) {
180                 NioDomainSocketUtil.deleteSocketFile(local);
181             }
182         }
183     }
184 
185     @Override
186     protected SocketAddress localAddress0() {
187         // do not use unsafe which uses native transport (epoll or kqueue)
188         // do not use javaChannel().socket() which is not nio
189         try {
190             return javaChannel().getLocalAddress();
191         } catch (Exception ignore) {
192             // ignore
193         }
194         return null;
195     }
196 
197     @Override
198     protected SocketAddress remoteAddress0() {
199         return null;
200     }
201 
202     private final class NioDomainServerSocketChannelConfig extends DefaultChannelConfig {
203 
204         private volatile int backlog = NetUtil.SOMAXCONN;
205 
206         private NioDomainServerSocketChannelConfig(NioServerDomainSocketChannel channel) {
207             super(channel, new ServerChannelRecvByteBufAllocator());
208         }
209 
210         @Override
211         protected void autoReadCleared() {
212             clearReadPending();
213         }
214 
215         @Override
216         public Map<ChannelOption<?>, Object> getOptions() {
217             List<ChannelOption<?>> options = new ArrayList<ChannelOption<?>>();
218             options.add(SO_BACKLOG);
219             for (ChannelOption<?> opt : NioChannelOption.getOptions(jdkChannel())) {
220                 options.add(opt);
221             }
222             return getOptions(super.getOptions(), options.toArray(new ChannelOption[0]));
223         }
224 
225         @SuppressWarnings("unchecked")
226         @Override
227         public <T> T getOption(ChannelOption<T> option) {
228             if (option == SO_BACKLOG) {
229                 return (T) Integer.valueOf(getBacklog());
230             }
231             if (option instanceof NioChannelOption) {
232                 return NioChannelOption.getOption(jdkChannel(), (NioChannelOption<T>) option);
233             }
234 
235             return super.getOption(option);
236         }
237 
238         @Override
239         public <T> boolean setOption(ChannelOption<T> option, T value) {
240             if (option == SO_BACKLOG) {
241                 validate(option, value);
242                 setBacklog((Integer) value);
243             } else if (option instanceof NioChannelOption) {
244                 return NioChannelOption.setOption(jdkChannel(), (NioChannelOption<T>) option, value);
245             } else {
246                 return super.setOption(option, value);
247             }
248 
249             return true;
250         }
251 
252         private int getBacklog() {
253             return backlog;
254         }
255 
256         private NioDomainServerSocketChannelConfig setBacklog(int backlog) {
257             checkPositiveOrZero(backlog, "backlog");
258             this.backlog = backlog;
259             return this;
260         }
261 
262         private ServerSocketChannel jdkChannel() {
263             return ((NioServerDomainSocketChannel) channel).javaChannel();
264         }
265     }
266 
267     // Override just to to be able to call directly via unit tests.
268     @Override
269     protected boolean closeOnReadError(Throwable cause) {
270         return super.closeOnReadError(cause);
271     }
272 
273     @Override
274     protected boolean doConnect(
275             SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
276         throw new UnsupportedOperationException();
277     }
278 
279     @Override
280     protected void doFinishConnect() throws Exception {
281         throw new UnsupportedOperationException();
282     }
283 }