1 /*
   2  * Copyright (c) 2000, 2018, 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 
  26 package rdma.ch;
  27 
  28 import java.io.FileDescriptor;
  29 import java.io.IOException;
  30 import java.net.InetSocketAddress;
  31 import java.net.ServerSocket;
  32 import java.net.SocketAddress;
  33 import java.net.SocketOption;
  34 import java.net.StandardSocketOptions;
  35 import java.nio.channels.AlreadyBoundException;
  36 import java.nio.channels.ServerSocketChannel;
  37 import java.nio.channels.SocketChannel;
  38 import java.nio.channels.spi.SelectorProvider;
  39 import java.util.Collections;
  40 import java.util.HashSet;
  41 import java.util.Objects;
  42 import java.util.Set;
  43 import sun.nio.ch.ServerSocketChannelImpl;
  44 import sun.nio.ch.IOStatus;
  45 import sun.nio.ch.IOUtil;
  46 import sun.net.ext.RdmaSocketOptions;
  47 
  48 public class RdmaServerSocketChannelImpl
  49     extends ServerSocketChannelImpl
  50 {
  51     protected RdmaServerSocketChannelImpl(SelectorProvider sp) throws IOException {
  52         super(sp);
  53     }
  54 
  55     protected RdmaServerSocketChannelImpl(SelectorProvider sp, FileDescriptor fd, boolean bound)
  56         throws IOException
  57     {
  58         super(sp, fd, bound);
  59     }
  60 
  61     protected FileDescriptor createFD() throws IOException {
  62         return RdmaNet.serverSocket(true);
  63     }
  64 
  65     protected InetSocketAddress createLocalAddress(FileDescriptor fd)
  66         throws IOException {
  67         return RdmaNet.localAddress(fd);
  68     }
  69 
  70     @Override
  71     public SocketAddress getLocalAddress() throws IOException {
  72         synchronized (stateLock) {
  73             ensureOpen();
  74             return (localAddress == null)
  75                     ? null
  76                     : RdmaNet.getRevealedLocalAddress(localAddress);
  77         }
  78     }
  79 
  80     @Override
  81     public <T> ServerSocketChannel setOption(SocketOption<T> name, T value)
  82         throws IOException
  83     {
  84         Objects.requireNonNull(name);
  85         if (!supportedOptions().contains(name))
  86             throw new UnsupportedOperationException("'" + name + "' not supported");
  87         synchronized (stateLock) {
  88             ensureOpen();
  89 
  90             if (name == StandardSocketOptions.SO_REUSEADDR && RdmaNet.useExclusiveBind()) {
  91                 isReuseAddress = (Boolean)value;
  92             } else {
  93                 RdmaNet.setSocketOption(fd, RdmaNet.UNSPEC, name, value);
  94             }
  95             return this;
  96         }
  97     }
  98 
  99     @Override
 100     @SuppressWarnings("unchecked")
 101     public <T> T getOption(SocketOption<T> name)
 102         throws IOException
 103     {
 104         Objects.requireNonNull(name);
 105         if (!supportedOptions().contains(name))
 106             throw new UnsupportedOperationException("'" + name + "' not supported");
 107 
 108         synchronized (stateLock) {
 109             ensureOpen();
 110             if (name == StandardSocketOptions.SO_REUSEADDR && RdmaNet.useExclusiveBind()) {
 111                 return (T)Boolean.valueOf(isReuseAddress);
 112             }
 113             return (T) RdmaNet.getSocketOption(fd, RdmaNet.UNSPEC, name);
 114         }
 115     }
 116 
 117     private static class DefaultOptionsHolder {
 118         static final Set<SocketOption<?>> defaultOptions = defaultOptions();
 119 
 120         private static Set<SocketOption<?>> defaultOptions() {
 121             HashSet<SocketOption<?>> set = new HashSet<>(2);
 122             set.add(StandardSocketOptions.SO_RCVBUF);
 123             set.add(StandardSocketOptions.SO_REUSEADDR);
 124             if (RdmaNet.isRdmaAvailable()) {
 125                 RdmaSocketOptions rdmaOptions =
 126                         RdmaSocketOptions.getInstance();
 127                 set.addAll(rdmaOptions.options());
 128             }
 129             return Collections.unmodifiableSet(set);
 130         }
 131     }
 132 
 133     public final Set<SocketOption<?>> supportedOptions() {
 134         return DefaultOptionsHolder.defaultOptions;
 135     }
 136 
 137     @Override
 138     public ServerSocketChannel bind(SocketAddress local, int backlog) throws IOException {
 139         synchronized (stateLock) {
 140             ensureOpen();
 141             if (localAddress != null)
 142                 throw new AlreadyBoundException();
 143             InetSocketAddress isa = (local == null)
 144                                     ? new InetSocketAddress(0)
 145                                     : RdmaNet.checkAddress(local);
 146             SecurityManager sm = System.getSecurityManager();
 147             if (sm != null)
 148                 sm.checkListen(isa.getPort());
 149             RdmaNet.bind(fd, isa.getAddress(), isa.getPort());
 150             RdmaNet.listen(fd, backlog < 1 ? 50 : backlog);
 151             localAddress = RdmaNet.localAddress(fd);
 152         }
 153         return this;
 154     }
 155 
 156     @Override
 157     public SocketChannel accept() throws IOException {
 158         acceptLock.lock();
 159         try {
 160             int n = 0;
 161             FileDescriptor newfd = new FileDescriptor();
 162             InetSocketAddress[] isaa = new InetSocketAddress[1];
 163 
 164             boolean blocking = isBlocking();
 165             try {
 166                 begin(blocking);
 167                 do {
 168                     n = accept(this.fd, newfd, isaa);
 169                 } while (n == IOStatus.INTERRUPTED && isOpen());
 170             } finally {
 171                 end(blocking, n > 0);
 172                 assert IOStatus.check(n);
 173             }
 174 
 175             if (n < 1)
 176                 return null;
 177 
 178             // newly accepted socket is initially in blocking mode
 179             RdmaNet.configureBlocking(newfd, true);
 180 
 181             InetSocketAddress isa = isaa[0];
 182             SocketChannel sc = new RdmaSocketChannelImpl(provider(), newfd, isa);
 183 
 184             // check permitted to accept connections from the remote address
 185             SecurityManager sm = System.getSecurityManager();
 186             if (sm != null) {
 187                 try {
 188                     sm.checkAccept(isa.getAddress().getHostAddress(), isa.getPort());
 189                 } catch (SecurityException x) {
 190                     sc.close();
 191                     throw x;
 192                 }
 193             }
 194             return sc;
 195 
 196         } finally {
 197             acceptLock.unlock();
 198         }
 199     }
 200 
 201     @Override
 202     protected void implConfigureBlocking(boolean block) throws IOException {
 203         acceptLock.lock();
 204         try {
 205             synchronized (stateLock) {
 206                 ensureOpen();
 207                 RdmaNet.configureBlocking(fd, block);
 208             }
 209         } finally {
 210             acceptLock.unlock();
 211         }
 212     }
 213 
 214     /**
 215      * Poll this channel's socket for a new connection up to the given timeout.
 216      * @return {@code true} if there is a connection to accept
 217      */
 218     boolean pollAccept(long timeout) throws IOException {
 219         assert Thread.holdsLock(blockingLock()) && isBlocking();
 220         acceptLock.lock();
 221         try {
 222             boolean polled = false;
 223             try {
 224                 begin(true);
 225                 int events = RdmaNet.poll(fd, RdmaNet.POLLIN, timeout);
 226                 polled = (events != 0);
 227             } finally {
 228                 end(true, polled);
 229             }
 230             return polled;
 231         } finally {
 232             acceptLock.unlock();
 233         }
 234     }
 235 
 236     public String toString() {
 237         StringBuilder sb = new StringBuilder();
 238         sb.append(this.getClass().getName());
 239         sb.append('[');
 240         if (!isOpen()) {
 241             sb.append("closed");
 242         } else {
 243             synchronized (stateLock) {
 244                 InetSocketAddress addr = localAddress;
 245                 if (addr == null) {
 246                     sb.append("unbound");
 247                 } else {
 248                     sb.append(RdmaNet.getRevealedLocalAddressAsString(addr));
 249                 }
 250             }
 251         }
 252         sb.append(']');
 253         return sb.toString();
 254     }
 255 
 256     private int accept(FileDescriptor ssfd,
 257                        FileDescriptor newfd,
 258                        InetSocketAddress[] isaa)
 259         throws IOException
 260     {
 261         return accept0(ssfd, newfd, isaa);
 262     }
 263 
 264     // -- Native methods --
 265 
 266     private native int accept0(FileDescriptor ssfd,
 267                                FileDescriptor newfd,
 268                                InetSocketAddress[] isaa)
 269         throws IOException;
 270 
 271     private static native void initIDs();
 272 
 273     static {
 274         IOUtil.load();
 275         initIDs();
 276         nd = new RdmaSocketDispatcher();
 277     }
 278 }