1 /*
  2  * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 package sun.nio.ch.sctp;
 26 
 27 import java.net.SocketAddress;
 28 import java.net.InetSocketAddress;
 29 import java.net.InetAddress;
 30 import java.io.FileDescriptor;
 31 import java.io.IOException;
 32 import java.util.Collections;
 33 import java.util.Set;
 34 import java.util.HashSet;
 35 import java.nio.channels.SelectionKey;
 36 import java.nio.channels.ClosedChannelException;
 37 import java.nio.channels.NotYetBoundException;
 38 import java.nio.channels.spi.SelectorProvider;
 39 import com.sun.nio.sctp.IllegalUnbindException;
 40 import com.sun.nio.sctp.SctpChannel;
 41 import com.sun.nio.sctp.SctpServerChannel;
 42 import com.sun.nio.sctp.SctpSocketOption;
 43 import com.sun.nio.sctp.SctpStandardSocketOptions;
 44 import sun.nio.ch.NativeThread;
 45 import sun.nio.ch.IOStatus;
 46 import sun.nio.ch.IOUtil;
 47 import sun.nio.ch.Net;
 48 import sun.nio.ch.SelChImpl;
 49 import sun.nio.ch.SelectionKeyImpl;
 50 
 51 /**
 52  * An implementation of SctpServerChannel
 53  */
 54 public class SctpServerChannelImpl extends SctpServerChannel
 55     implements SelChImpl
 56 {
 57     private final FileDescriptor fd;
 58 
 59     private final int fdVal;
 60 
 61     /* IDs of native thread doing accept, for signalling */
 62     private volatile long thread;
 63 
 64     /* Lock held by thread currently blocked in this channel */
 65     private final Object lock = new Object();
 66 
 67     /* Lock held by any thread that modifies the state fields declared below
 68      * DO NOT invoke a blocking I/O operation while holding this lock! */
 69     private final Object stateLock = new Object();
 70 
 71     private enum ChannelState {
 72         UNINITIALIZED,
 73         INUSE,
 74         KILLPENDING,
 75         KILLED,
 76     }
 77     /* -- The following fields are protected by stateLock -- */
 78     private ChannelState state = ChannelState.UNINITIALIZED;
 79 
 80     /* Binding: Once bound the port will remain constant. */
 81     int port = -1;
 82     private final HashSet<InetSocketAddress> localAddresses = new HashSet<>();
 83     /* Has the channel been bound to the wildcard address */
 84     private boolean wildcard; /* false */
 85 
 86     /* -- End of fields protected by stateLock -- */
 87 
 88     /**
 89      * Initializes a new instance of this class.
 90      */
 91     public SctpServerChannelImpl(SelectorProvider provider)
 92             throws IOException {
 93         //TODO: update provider remove public modifier
 94         super(provider);
 95         this.fd = SctpNet.socket(true);
 96         this.fdVal = IOUtil.fdVal(fd);
 97         this.state = ChannelState.INUSE;
 98     }
 99 
100     @Override
101     public SctpServerChannel bind(SocketAddress local, int backlog)
102             throws IOException {
103         synchronized (lock) {
104             synchronized (stateLock) {
105                 if (!isOpen())
106                     throw new ClosedChannelException();
107                 if (isBound())
108                     SctpNet.throwAlreadyBoundException();
109 
110                 InetSocketAddress isa = (local == null) ?
111                     new InetSocketAddress(0) : Net.checkAddress(local);
112                 Net.bind(fd, isa.getAddress(), isa.getPort());
113 
114                 InetSocketAddress boundIsa = Net.localAddress(fd);
115                 port = boundIsa.getPort();
116                 localAddresses.add(isa);
117                     if (isa.getAddress().isAnyLocalAddress())
118                         wildcard = true;
119 
120                 SctpNet.listen(fdVal, backlog < 1 ? 50 : backlog);
121             }
122         }
123         return this;
124     }
125 
126     @Override
127     public SctpServerChannel bindAddress(InetAddress address)
128             throws IOException {
129         return bindUnbindAddress(address, true);
130     }
131 
132     @Override
133     public SctpServerChannel unbindAddress(InetAddress address)
134             throws IOException {
135         return bindUnbindAddress(address, false);
136     }
137 
138     private SctpServerChannel bindUnbindAddress(InetAddress address, boolean add)
139             throws IOException {
140         if (address == null)
141             throw new IllegalArgumentException();
142 
143         synchronized (lock) {
144             synchronized (stateLock) {
145                 if (!isOpen())
146                     throw new ClosedChannelException();
147                 if (!isBound())
148                     throw new NotYetBoundException();
149                 if (wildcard)
150                     throw new IllegalStateException(
151                             "Cannot add or remove addresses from a channel that is bound to the wildcard address");
152                 if (address.isAnyLocalAddress())
153                     throw new IllegalArgumentException(
154                             "Cannot add or remove the wildcard address");
155                 if (add) {
156                     for (InetSocketAddress addr : localAddresses) {
157                         if (addr.getAddress().equals(address)) {
158                             SctpNet.throwAlreadyBoundException();
159                         }
160                     }
161                 } else { /*removing */
162                     /* Verify that there is more than one address
163                      * and that address is already bound */
164                     if (localAddresses.size() <= 1)
165                         throw new IllegalUnbindException("Cannot remove address from a channel with only one address bound");
166                     boolean foundAddress = false;
167                     for (InetSocketAddress addr : localAddresses) {
168                         if (addr.getAddress().equals(address)) {
169                             foundAddress = true;
170                             break;
171                         }
172                     }
173                     if (!foundAddress )
174                         throw new IllegalUnbindException("Cannot remove address from a channel that is not bound to that address");
175                 }
176 
177                 SctpNet.bindx(fdVal, new InetAddress[]{address}, port, add);
178 
179                 /* Update our internal Set to reflect the addition/removal */
180                 if (add)
181                     localAddresses.add(new InetSocketAddress(address, port));
182                 else {
183                     for (InetSocketAddress addr : localAddresses) {
184                         if (addr.getAddress().equals(address)) {
185                             localAddresses.remove(addr);
186                             break;
187                         }
188                     }
189                 }
190             }
191         }
192         return this;
193     }
194 
195     private boolean isBound() {
196         synchronized (stateLock) {
197             return port != -1;
198         }
199     }
200 
201     private void acceptCleanup() throws IOException {
202         synchronized (stateLock) {
203             thread = 0;
204             if (state == ChannelState.KILLPENDING)
205                 kill();
206         }
207     }
208 
209     @Override
210     public SctpChannel accept() throws IOException {
211         synchronized (lock) {
212             if (!isOpen())
213                 throw new ClosedChannelException();
214             if (!isBound())
215                 throw new NotYetBoundException();
216 
217             int n = 0;
218             FileDescriptor newfd = new FileDescriptor();
219             InetSocketAddress[] isaa = new InetSocketAddress[1];
220 
221             try {
222                 begin();
223                 if (!isOpen())
224                     return null;
225                 thread = NativeThread.current();
226                 for (;;) {
227                     n = Net.accept(fd, newfd, isaa);
228                     if ((n == IOStatus.INTERRUPTED) && isOpen())
229                         continue;
230                     break;
231                 }
232             } finally {
233                 acceptCleanup();
234                 end(n > 0);
235                 assert IOStatus.check(n);
236             }
237 
238             if (n < 1)
239                 return null;
240 
241             IOUtil.configureBlocking(newfd, true);
242             return new SctpChannelImpl(provider(), newfd);
243         }
244     }
245 
246     @Override
247     protected void implConfigureBlocking(boolean block) throws IOException {
248         IOUtil.configureBlocking(fd, block);
249     }
250 
251     @Override
252     public void implCloseSelectableChannel() throws IOException {
253         synchronized (stateLock) {
254             if (state != ChannelState.KILLED)
255                 SctpNet.preClose(fdVal);
256             if (thread != 0)
257                 NativeThread.signal(thread);
258             if (!isRegistered())
259                 kill();
260         }
261     }
262 
263     @Override
264     public void kill() throws IOException {
265         synchronized (stateLock) {
266             if (state == ChannelState.KILLED)
267                 return;
268             if (state == ChannelState.UNINITIALIZED) {
269                 state = ChannelState.KILLED;
270                 SctpNet.close(fdVal);
271                 return;
272             }
273             assert !isOpen() && !isRegistered();
274 
275             // Postpone the kill if there is a thread in accept
276             if (thread == 0) {
277                 state = ChannelState.KILLED;
278                 SctpNet.close(fdVal);
279             } else {
280                 state = ChannelState.KILLPENDING;
281             }
282         }
283     }
284 
285     @Override
286     public FileDescriptor getFD() {
287         return fd;
288     }
289 
290     @Override
291     public int getFDVal() {
292         return fdVal;
293     }
294 
295     /**
296      * Translates native poll revent ops into a ready operation ops
297      */
298     private boolean translateReadyOps(int ops, int initialOps,
299                                      SelectionKeyImpl sk) {
300         int intOps = sk.nioInterestOps();
301         int oldOps = sk.nioReadyOps();
302         int newOps = initialOps;
303 
304         if ((ops & Net.POLLNVAL) != 0) {
305             /* This should only happen if this channel is pre-closed while a
306              * selection operation is in progress
307              * ## Throw an error if this channel has not been pre-closed */
308             return false;
309         }
310 
311         if ((ops & (Net.POLLERR | Net.POLLHUP)) != 0) {
312             newOps = intOps;
313             sk.nioReadyOps(newOps);
314             return (newOps & ~oldOps) != 0;
315         }
316 
317         if (((ops & Net.POLLIN) != 0) &&
318             ((intOps & SelectionKey.OP_ACCEPT) != 0))
319                 newOps |= SelectionKey.OP_ACCEPT;
320 
321         sk.nioReadyOps(newOps);
322         return (newOps & ~oldOps) != 0;
323     }
324 
325     @Override
326     public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl sk) {
327         return translateReadyOps(ops, sk.nioReadyOps(), sk);
328     }
329 
330     @Override
331     public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl sk) {
332         return translateReadyOps(ops, 0, sk);
333     }
334 
335     @Override
336     public int translateInterestOps(int ops) {
337         int newOps = 0;
338         if ((ops & SelectionKey.OP_ACCEPT) != 0)
339             newOps |= Net.POLLIN;
340         return newOps;
341     }
342 
343     @Override
344     public <T> SctpServerChannel setOption(SctpSocketOption<T> name, T value)
345             throws IOException {
346         if (name == null)
347             throw new NullPointerException();
348         if (!supportedOptions().contains(name))
349             throw new UnsupportedOperationException("'" + name + "' not supported");
350 
351         synchronized (stateLock) {
352             if (!isOpen())
353                 throw new ClosedChannelException();
354 
355             SctpNet.setSocketOption(fdVal, name, value, 0 /*oneToOne*/);
356             return this;
357         }
358     }
359 
360     @Override
361     @SuppressWarnings("unchecked")
362     public <T> T getOption(SctpSocketOption<T> name) throws IOException {
363         if (name == null)
364             throw new NullPointerException();
365         if (!supportedOptions().contains(name))
366             throw new UnsupportedOperationException("'" + name + "' not supported");
367 
368         synchronized (stateLock) {
369             if (!isOpen())
370                 throw new ClosedChannelException();
371 
372             return (T) SctpNet.getSocketOption(fdVal, name, 0 /*oneToOne*/);
373         }
374     }
375 
376     @Override
377     public final Set<SctpSocketOption<?>> supportedOptions() {
378         final class Holder {
379             static final Set<SctpSocketOption<?>> DEFAULT_OPTIONS =
380                     Set.of(SctpStandardSocketOptions.SCTP_INIT_MAXSTREAMS);
381         }
382         return Holder.DEFAULT_OPTIONS;
383     }
384 
385     @Override
386     public Set<SocketAddress> getAllLocalAddresses()
387             throws IOException {
388         synchronized (stateLock) {
389             if (!isOpen())
390                 throw new ClosedChannelException();
391             if (!isBound())
392                 return Collections.emptySet();
393 
394             return SctpNet.getLocalAddresses(fdVal);
395         }
396     }
397 }