1 /*
2  * Copyright (c) 2009, 2019, 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.DirectBuffer;
45 import sun.nio.ch.NativeThread;
46 import sun.nio.ch.IOStatus;
47 import sun.nio.ch.IOUtil;
48 import sun.nio.ch.Net;
49 import sun.nio.ch.SelChImpl;
50 import sun.nio.ch.SelectionKeyImpl;
51 import sun.nio.ch.Util;
52 
53 /**
54  * An implementation of SctpServerChannel
55  */
56 public class SctpServerChannelImpl extends SctpServerChannel
57     implements SelChImpl
58 {
59     private final FileDescriptor fd;
60 
61     private final int fdVal;
62 
63     /* IDs of native thread doing accept, for signalling */
64     private volatile long thread = 0;
65 
66     /* Lock held by thread currently blocked in this channel */
67     private final Object lock = new Object();
68 
69     /* Lock held by any thread that modifies the state fields declared below
70      * DO NOT invoke a blocking I/O operation while holding this lock! */
71     private final Object stateLock = new Object();
72 
73     private enum ChannelState {
74         UNINITIALIZED,
75         INUSE,
76         KILLPENDING,
77         KILLED,
78     }
79     /* -- The following fields are protected by stateLock -- */
80     private ChannelState state = ChannelState.UNINITIALIZED;
81 
82     /* Binding: Once bound the port will remain constant. */
83     int port = -1;
84     private HashSet<InetSocketAddress> localAddresses = new HashSet<InetSocketAddress>();
85     /* Has the channel been bound to the wildcard address */
86     private boolean wildcard; /* false */
87 
88     /* -- End of fields protected by stateLock -- */
89 
90     /**
91      * Initializes a new instance of this class.
92      */
SctpServerChannelImpl(SelectorProvider provider)93     public SctpServerChannelImpl(SelectorProvider provider)
94             throws IOException {
95         //TODO: update provider remove public modifier
96         super(provider);
97         this.fd = SctpNet.socket(true);
98         this.fdVal = IOUtil.fdVal(fd);
99         this.state = ChannelState.INUSE;
100     }
101 
102     @Override
bind(SocketAddress local, int backlog)103     public SctpServerChannel bind(SocketAddress local, int backlog)
104             throws IOException {
105         synchronized (lock) {
106             synchronized (stateLock) {
107                 if (!isOpen())
108                     throw new ClosedChannelException();
109                 if (isBound())
110                     SctpNet.throwAlreadyBoundException();
111 
112                 InetSocketAddress isa = (local == null) ?
113                     new InetSocketAddress(0) : Net.checkAddress(local);
114                 SecurityManager sm = System.getSecurityManager();
115                 if (sm != null)
116                     sm.checkListen(isa.getPort());
117                 Net.bind(fd, isa.getAddress(), isa.getPort());
118 
119                 InetSocketAddress boundIsa = Net.localAddress(fd);
120                 port = boundIsa.getPort();
121                 localAddresses.add(isa);
122                     if (isa.getAddress().isAnyLocalAddress())
123                         wildcard = true;
124 
125                 SctpNet.listen(fdVal, backlog < 1 ? 50 : backlog);
126             }
127         }
128         return this;
129     }
130 
131     @Override
bindAddress(InetAddress address)132     public SctpServerChannel bindAddress(InetAddress address)
133             throws IOException {
134         return bindUnbindAddress(address, true);
135     }
136 
137     @Override
unbindAddress(InetAddress address)138     public SctpServerChannel unbindAddress(InetAddress address)
139             throws IOException {
140         return bindUnbindAddress(address, false);
141     }
142 
bindUnbindAddress(InetAddress address, boolean add)143     private SctpServerChannel bindUnbindAddress(InetAddress address, boolean add)
144             throws IOException {
145         if (address == null)
146             throw new IllegalArgumentException();
147 
148         synchronized (lock) {
149             synchronized (stateLock) {
150                 if (!isOpen())
151                     throw new ClosedChannelException();
152                 if (!isBound())
153                     throw new NotYetBoundException();
154                 if (wildcard)
155                     throw new IllegalStateException(
156                             "Cannot add or remove addresses from a channel that is bound to the wildcard address");
157                 if (address.isAnyLocalAddress())
158                     throw new IllegalArgumentException(
159                             "Cannot add or remove the wildcard address");
160                 if (add) {
161                     for (InetSocketAddress addr : localAddresses) {
162                         if (addr.getAddress().equals(address)) {
163                             SctpNet.throwAlreadyBoundException();
164                         }
165                     }
166                 } else { /*removing */
167                     /* Verify that there is more than one address
168                      * and that address is already bound */
169                     if (localAddresses.size() <= 1)
170                         throw new IllegalUnbindException("Cannot remove address from a channel with only one address bound");
171                     boolean foundAddress = false;
172                     for (InetSocketAddress addr : localAddresses) {
173                         if (addr.getAddress().equals(address)) {
174                             foundAddress = true;
175                             break;
176                         }
177                     }
178                     if (!foundAddress )
179                         throw new IllegalUnbindException("Cannot remove address from a channel that is not bound to that address");
180                 }
181 
182                 SctpNet.bindx(fdVal, new InetAddress[]{address}, port, add);
183 
184                 /* Update our internal Set to reflect the addition/removal */
185                 if (add)
186                     localAddresses.add(new InetSocketAddress(address, port));
187                 else {
188                     for (InetSocketAddress addr : localAddresses) {
189                         if (addr.getAddress().equals(address)) {
190                             localAddresses.remove(addr);
191                             break;
192                         }
193                     }
194                 }
195             }
196         }
197         return this;
198     }
199 
isBound()200     private boolean isBound() {
201         synchronized (stateLock) {
202             return port == -1 ? false : true;
203         }
204     }
205 
acceptCleanup()206     private void acceptCleanup() throws IOException {
207         synchronized (stateLock) {
208             thread = 0;
209             if (state == ChannelState.KILLPENDING)
210                 kill();
211         }
212     }
213 
214     @Override
accept()215     public SctpChannel accept() throws IOException {
216         synchronized (lock) {
217             if (!isOpen())
218                 throw new ClosedChannelException();
219             if (!isBound())
220                 throw new NotYetBoundException();
221             SctpChannel sc = null;
222 
223             int n = 0;
224             FileDescriptor newfd = new FileDescriptor();
225             InetSocketAddress[] isaa = new InetSocketAddress[1];
226 
227             try {
228                 begin();
229                 if (!isOpen())
230                     return null;
231                 thread = NativeThread.current();
232                 for (;;) {
233                     n = Net.accept(fd, newfd, isaa);
234                     if ((n == IOStatus.INTERRUPTED) && isOpen())
235                         continue;
236                     break;
237                 }
238             } finally {
239                 acceptCleanup();
240                 end(n > 0);
241                 assert IOStatus.check(n);
242             }
243 
244             if (n < 1)
245                 return null;
246 
247             IOUtil.configureBlocking(newfd, true);
248             InetSocketAddress isa = isaa[0];
249             sc = new SctpChannelImpl(provider(), newfd);
250 
251             SecurityManager sm = System.getSecurityManager();
252             if (sm != null)
253                 sm.checkAccept(isa.getAddress().getHostAddress(),
254                                isa.getPort());
255 
256             return sc;
257         }
258     }
259 
260     @Override
implConfigureBlocking(boolean block)261     protected void implConfigureBlocking(boolean block) throws IOException {
262         IOUtil.configureBlocking(fd, block);
263     }
264 
265     @Override
implCloseSelectableChannel()266     public void implCloseSelectableChannel() throws IOException {
267         synchronized (stateLock) {
268             SctpNet.preClose(fdVal);
269             if (thread != 0)
270                 NativeThread.signal(thread);
271             if (!isRegistered())
272                 kill();
273         }
274     }
275 
276     @Override
kill()277     public void kill() throws IOException {
278         synchronized (stateLock) {
279             if (state == ChannelState.KILLED)
280                 return;
281             if (state == ChannelState.UNINITIALIZED) {
282                 state = ChannelState.KILLED;
283                 return;
284             }
285             assert !isOpen() && !isRegistered();
286 
287             // Postpone the kill if there is a thread in accept
288             if (thread == 0) {
289                 SctpNet.close(fdVal);
290                 state = ChannelState.KILLED;
291             } else {
292                 state = ChannelState.KILLPENDING;
293             }
294         }
295     }
296 
297     @Override
getFD()298     public FileDescriptor getFD() {
299         return fd;
300     }
301 
302     @Override
getFDVal()303     public int getFDVal() {
304         return fdVal;
305     }
306 
307     /**
308      * Translates native poll revent ops into a ready operation ops
309      */
translateReadyOps(int ops, int initialOps, SelectionKeyImpl sk)310     private boolean translateReadyOps(int ops, int initialOps,
311                                      SelectionKeyImpl sk) {
312         int intOps = sk.nioInterestOps();
313         int oldOps = sk.nioReadyOps();
314         int newOps = initialOps;
315 
316         if ((ops & Net.POLLNVAL) != 0) {
317             /* This should only happen if this channel is pre-closed while a
318              * selection operation is in progress
319              * ## Throw an error if this channel has not been pre-closed */
320             return false;
321         }
322 
323         if ((ops & (Net.POLLERR | Net.POLLHUP)) != 0) {
324             newOps = intOps;
325             sk.nioReadyOps(newOps);
326             return (newOps & ~oldOps) != 0;
327         }
328 
329         if (((ops & Net.POLLIN) != 0) &&
330             ((intOps & SelectionKey.OP_ACCEPT) != 0))
331                 newOps |= SelectionKey.OP_ACCEPT;
332 
333         sk.nioReadyOps(newOps);
334         return (newOps & ~oldOps) != 0;
335     }
336 
337     @Override
translateAndUpdateReadyOps(int ops, SelectionKeyImpl sk)338     public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl sk) {
339         return translateReadyOps(ops, sk.nioReadyOps(), sk);
340     }
341 
342     @Override
translateAndSetReadyOps(int ops, SelectionKeyImpl sk)343     public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl sk) {
344         return translateReadyOps(ops, 0, sk);
345     }
346 
347     @Override
translateInterestOps(int ops)348     public int translateInterestOps(int ops) {
349         int newOps = 0;
350         if ((ops & SelectionKey.OP_ACCEPT) != 0)
351             newOps |= Net.POLLIN;
352         return newOps;
353     }
354 
355     @Override
setOption(SctpSocketOption<T> name, T value)356     public <T> SctpServerChannel setOption(SctpSocketOption<T> name, T value)
357             throws IOException {
358         if (name == null)
359             throw new NullPointerException();
360         if (!supportedOptions().contains(name))
361             throw new UnsupportedOperationException("'" + name + "' not supported");
362 
363         synchronized (stateLock) {
364             if (!isOpen())
365                 throw new ClosedChannelException();
366 
367             SctpNet.setSocketOption(fdVal, name, value, 0 /*oneToOne*/);
368             return this;
369         }
370     }
371 
372     @Override
373     @SuppressWarnings("unchecked")
getOption(SctpSocketOption<T> name)374     public <T> T getOption(SctpSocketOption<T> name) throws IOException {
375         if (name == null)
376             throw new NullPointerException();
377         if (!supportedOptions().contains(name))
378             throw new UnsupportedOperationException("'" + name + "' not supported");
379 
380         synchronized (stateLock) {
381             if (!isOpen())
382                 throw new ClosedChannelException();
383 
384             return (T) SctpNet.getSocketOption(fdVal, name, 0 /*oneToOne*/);
385         }
386     }
387 
388     private static class DefaultOptionsHolder {
389         static final Set<SctpSocketOption<?>> defaultOptions = defaultOptions();
390 
defaultOptions()391         private static Set<SctpSocketOption<?>> defaultOptions() {
392             HashSet<SctpSocketOption<?>> set = new HashSet<SctpSocketOption<?>>(1);
393             set.add(SctpStandardSocketOptions.SCTP_INIT_MAXSTREAMS);
394             return Collections.unmodifiableSet(set);
395         }
396     }
397 
398     @Override
supportedOptions()399     public final Set<SctpSocketOption<?>> supportedOptions() {
400         return DefaultOptionsHolder.defaultOptions;
401     }
402 
403     @Override
getAllLocalAddresses()404     public Set<SocketAddress> getAllLocalAddresses()
405             throws IOException {
406         synchronized (stateLock) {
407             if (!isOpen())
408                 throw new ClosedChannelException();
409             if (!isBound())
410                 return Collections.emptySet();
411 
412             return SctpNet.getLocalAddresses(fdVal);
413         }
414     }
415 }
416