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