View Javadoc

1   /**
2    * This file is part of Waarp Project.
3    * 
4    * Copyright 2009, Frederic Bregier, and individual contributors by the @author tags. See the
5    * COPYRIGHT.txt in the distribution for a full listing of individual contributors.
6    * 
7    * All Waarp Project is free software: you can redistribute it and/or modify it under the terms of
8    * the GNU General Public License as published by the Free Software Foundation, either version 3 of
9    * the License, or (at your option) any later version.
10   * 
11   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
12   * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13   * Public License for more details.
14   * 
15   * You should have received a copy of the GNU General Public License along with Waarp . If not, see
16   * <http://www.gnu.org/licenses/>.
17   */
18  package org.waarp.ftp.core.config;
19  
20  import java.net.InetAddress;
21  import java.net.InetSocketAddress;
22  import java.util.concurrent.ConcurrentHashMap;
23  import java.util.concurrent.ExecutorService;
24  import java.util.concurrent.Executors;
25  import java.util.concurrent.ScheduledExecutorService;
26  
27  import io.netty.bootstrap.Bootstrap;
28  import io.netty.bootstrap.ServerBootstrap;
29  import io.netty.channel.Channel;
30  import io.netty.channel.ChannelException;
31  import io.netty.channel.ChannelFuture;
32  import io.netty.channel.EventLoopGroup;
33  import io.netty.channel.group.ChannelGroup;
34  import io.netty.channel.group.DefaultChannelGroup;
35  import io.netty.channel.nio.NioEventLoopGroup;
36  import io.netty.handler.traffic.ChannelTrafficShapingHandler;
37  import io.netty.handler.traffic.GlobalChannelTrafficShapingHandler;
38  import io.netty.util.concurrent.EventExecutorGroup;
39  
40  import org.waarp.common.command.exception.Reply425Exception;
41  import org.waarp.common.crypto.ssl.WaarpSslUtility;
42  import org.waarp.common.logging.WaarpLogger;
43  import org.waarp.common.logging.WaarpLoggerFactory;
44  import org.waarp.common.utility.DetectionUtils;
45  import org.waarp.common.utility.WaarpNettyUtil;
46  import org.waarp.common.utility.WaarpThreadFactory;
47  import org.waarp.ftp.core.control.FtpInitializer;
48  import org.waarp.ftp.core.control.ftps.FtpsInitializer;
49  import org.waarp.ftp.core.data.handler.FtpDataInitializer;
50  import org.waarp.ftp.core.data.handler.ftps.FtpsDataInitializer;
51  import org.waarp.ftp.core.exception.FtpNoConnectionException;
52  import org.waarp.ftp.core.session.FtpSession;
53  import org.waarp.ftp.core.session.FtpSessionReference;
54  import org.waarp.ftp.core.utils.FtpChannelUtils;
55  import org.waarp.ftp.core.utils.FtpShutdownHook;
56  
57  /**
58   * Internal configuration of the FTP server, related to Netty
59   * 
60   * @author Frederic Bregier
61   * 
62   */
63  public class FtpInternalConfiguration {
64      // Static values
65      /**
66       * Internal Logger
67       */
68      private static final WaarpLogger logger = WaarpLoggerFactory.getLogger(FtpInternalConfiguration.class);
69  
70      // Network Internals
71      /**
72       * Time elapse for retry in ms
73       */
74      public static final long RETRYINMS = 10;
75  
76      /**
77       * Number of retry before error
78       */
79      public static final int RETRYNB = 3;
80  
81      /**
82       * Time elapse for WRITE OR CLOSE WAIT elaps in ms
83       */
84      public static final long WAITFORNETOP = 1000;
85      /**
86       * Hack to say Windows or Unix (USR1 not OK on Windows)
87       */
88      static Boolean ISUNIX = null;
89  
90      /**
91       * Default size for buffers (NIO)
92       */
93      public static final int BUFFERSIZEDEFAULT = 0x10000; // 64K
94  
95      // Dynamic values
96      /**
97       * List of all Command Channels to enable the close call on them using Netty ChannelGroup
98       */
99      private ChannelGroup commandChannelGroup = null;
100 
101     /**
102      * ExecutorService Boss
103      */
104     private final EventLoopGroup execBoss;
105 
106     /**
107      * ExecutorService Worker
108      */
109     private final EventLoopGroup execWorker;
110 
111     /**
112      * Bootstrap for Command server
113      */
114     private ServerBootstrap serverBootstrap = null;
115 
116     /**
117      * List of all Data Channels to enable the close call on them using Netty ChannelGroup
118      */
119     private ChannelGroup dataChannelGroup = null;
120 
121     /**
122      * ExecutorService Data Passive Boss
123      */
124     private final EventLoopGroup execPassiveDataBoss;
125 
126     /**
127      * ExecutorService Command Event Loop
128      */
129     private final EventLoopGroup execCommandEvent;
130 
131     /**
132      * ExecutorService Data Event Loop
133      */
134     private final EventLoopGroup execDataEvent;
135 
136     /**
137      * ExecutorService Data Active Worker
138      */
139     private final EventLoopGroup execDataWorker;
140 
141     /**
142      * FtpSession references used by Data Connection process
143      */
144     private final FtpSessionReference ftpSessionReference = new FtpSessionReference();
145 
146     /**
147      * Bootstrap for Active connections
148      */
149     private Bootstrap activeBootstrap = null;
150 
151     /**
152      * ServerBootStrap for Passive connections
153      */
154     private ServerBootstrap passiveBootstrap = null;
155 
156     /**
157      * Scheduler for TrafficCounter
158      */
159     private ScheduledExecutorService executorService =
160             Executors.newScheduledThreadPool(2, new WaarpThreadFactory("TimerTrafficFtp"));
161 
162     /**
163      * Global TrafficCounter (set from global configuration)
164      */
165     private FtpGlobalTrafficShapingHandler globalTrafficShapingHandler = null;
166 
167     /**
168      * Does the FTP will be SSL native based (990 989 port)
169      */
170     private boolean usingNativeSsl = false;
171 
172     /**
173      * Does the FTP accept AUTH and PROT
174      */
175     private boolean acceptAuthProt = false;
176     /**
177      * Bootstrap for Active Ssl connections
178      */
179     private Bootstrap activeSslBootstrap = null;
180 
181     /**
182      * ServerBootStrap for Passive Ssl connections
183      */
184     private ServerBootstrap passiveSslBootstrap = null;
185 
186     /**
187      * 
188      * @author Frederic Bregier org.waarp.ftp.core.config BindAddress
189      * 
190      */
191     public static class BindAddress {
192         /**
193          * Parent passive channel
194          */
195         public final Channel parent;
196 
197         /**
198          * Number of binded Data connections
199          */
200         volatile public int nbBind = 0;
201 
202         /**
203          * Constructor
204          * 
205          * @param channel
206          */
207         public BindAddress(Channel channel) {
208             parent = channel;
209             nbBind = 0;
210         }
211     }
212 
213     /**
214      * List of already bind local addresses for Passive connections
215      */
216     private final ConcurrentHashMap<InetSocketAddress, BindAddress> hashBindPassiveDataConn =
217             new ConcurrentHashMap<InetSocketAddress, BindAddress>();
218 
219     /**
220      * Global Configuration
221      */
222     private final FtpConfiguration configuration;
223 
224     /**
225      * Constructor
226      * 
227      * @param configuration
228      */
229     public FtpInternalConfiguration(FtpConfiguration configuration) {
230         this.configuration = configuration;
231         ISUNIX = !DetectionUtils.isWindows();
232         configuration.getShutdownConfiguration().timeout = configuration.getTIMEOUTCON();
233         new FtpShutdownHook(configuration.getShutdownConfiguration(), configuration);
234         execCommandEvent = new NioEventLoopGroup(configuration.getCLIENT_THREAD(), new WaarpThreadFactory("Command"));
235         execDataEvent = new NioEventLoopGroup(configuration.getCLIENT_THREAD(), new WaarpThreadFactory("Data"));
236         execBoss = new NioEventLoopGroup(configuration.getSERVER_THREAD(), new WaarpThreadFactory("CommandBoss", false));
237         execWorker = new NioEventLoopGroup(configuration.getCLIENT_THREAD(), new WaarpThreadFactory("CommandWorker"));
238         execPassiveDataBoss = new NioEventLoopGroup(configuration.getSERVER_THREAD() * 2, new WaarpThreadFactory(
239                 "PassiveDataBoss"));
240         execDataWorker = new NioEventLoopGroup(configuration.getCLIENT_THREAD() * 2, new WaarpThreadFactory("DataWorker"));
241     }
242 
243     /**
244      * Startup the server
245      * 
246      * @throws FtpNoConnectionException
247      * 
248      */
249     public void serverStartup() throws FtpNoConnectionException {
250         WaarpLoggerFactory.setDefaultFactory(WaarpLoggerFactory
251                 .getDefaultFactory());
252         // Command
253         commandChannelGroup = new DefaultChannelGroup(configuration.fromClass.getName(), execWorker.next());
254         // Data
255         dataChannelGroup = new DefaultChannelGroup(configuration.fromClass.getName() + ".data", execWorker.next());
256 
257         // Passive Data Connections
258         passiveBootstrap = new ServerBootstrap();
259         WaarpNettyUtil.setServerBootstrap(passiveBootstrap, execPassiveDataBoss, execDataWorker,
260                 (int) configuration.getTIMEOUTCON());
261         if (usingNativeSsl) {
262             passiveBootstrap.childHandler(new FtpsDataInitializer(
263                     configuration.dataBusinessHandler, configuration, false));
264         } else {
265             passiveBootstrap.childHandler(new FtpDataInitializer(
266                     configuration.dataBusinessHandler, configuration, false));
267         }
268         if (acceptAuthProt) {
269             passiveSslBootstrap = new ServerBootstrap();
270             WaarpNettyUtil.setServerBootstrap(passiveSslBootstrap, execPassiveDataBoss, execDataWorker,
271                     (int) configuration.getTIMEOUTCON());
272             passiveSslBootstrap.childHandler(new FtpsDataInitializer(
273                     configuration.dataBusinessHandler, configuration, false));
274         } else {
275             passiveSslBootstrap = passiveBootstrap;
276         }
277 
278         // Active Data Connections
279         activeBootstrap = new Bootstrap();
280         WaarpNettyUtil.setBootstrap(activeBootstrap, execDataWorker, (int) configuration.getTIMEOUTCON());
281         if (usingNativeSsl) {
282             activeBootstrap.handler(new FtpsDataInitializer(
283                     configuration.dataBusinessHandler, configuration, true));
284         } else {
285             activeBootstrap.handler(new FtpDataInitializer(
286                     configuration.dataBusinessHandler, configuration, true));
287         }
288         if (acceptAuthProt) {
289             activeSslBootstrap = new Bootstrap();
290             WaarpNettyUtil.setBootstrap(activeSslBootstrap, execDataWorker, (int) configuration.getTIMEOUTCON());
291             activeSslBootstrap.handler(new FtpsDataInitializer(
292                     configuration.dataBusinessHandler, configuration, true));
293         } else {
294             activeSslBootstrap = activeBootstrap;
295         }
296 
297         // Main Command server
298         serverBootstrap = new ServerBootstrap();
299         WaarpNettyUtil.setServerBootstrap(serverBootstrap, execBoss, execWorker, (int) configuration.getTIMEOUTCON());
300         if (usingNativeSsl) {
301             serverBootstrap.childHandler(new FtpsInitializer(
302                     configuration.businessHandler, configuration));
303         } else {
304             serverBootstrap.childHandler(new FtpInitializer(
305                     configuration.businessHandler, configuration));
306         }
307 
308         try {
309             FtpChannelUtils.addCommandChannel(serverBootstrap.bind(
310                     new InetSocketAddress(configuration.getServerPort())).sync().channel(),
311                     configuration);
312         } catch (InterruptedException e) {
313             throw new FtpNoConnectionException("Can't initiate the FTP server", e);
314         }
315 
316         // Init Shutdown Hook handler
317         configuration.getShutdownConfiguration().timeout = configuration.getTIMEOUTCON();
318         FtpShutdownHook.addShutdownHook();
319         // Factory for TrafficShapingHandler
320         globalTrafficShapingHandler = new FtpGlobalTrafficShapingHandler(executorService,
321                 configuration.getServerGlobalWriteLimit(),
322                 configuration.getServerGlobalReadLimit(),
323                 configuration.getServerChannelWriteLimit(),
324                 configuration.getServerChannelReadLimit(),
325                 configuration.getDelayLimit());
326     }
327 
328     /**
329      * 
330      * @return an ExecutorService
331      */
332     public ExecutorService getWorker() {
333         return execWorker;
334     }
335 
336     /**
337      * Add a session from a couple of addresses
338      * 
339      * @param ipOnly
340      * @param fullIp
341      * @param session
342      */
343     public void setNewFtpSession(InetAddress ipOnly, InetSocketAddress fullIp,
344             FtpSession session) {
345         ftpSessionReference.setNewFtpSession(ipOnly, fullIp, session);
346     }
347 
348     /**
349      * Return and remove the FtpSession
350      * 
351      * @param channel
352      * @param active
353      * @return the FtpSession if it exists associated to this channel
354      */
355     public FtpSession getFtpSession(Channel channel, boolean active, boolean remove) {
356         if (active) {
357             return ftpSessionReference.getActiveFtpSession(channel, remove);
358         } else {
359             return ftpSessionReference.getPassiveFtpSession(channel, remove);
360         }
361     }
362 
363     /**
364      * Remove the FtpSession
365      * 
366      * @param ipOnly
367      * @param fullIp
368      */
369     public void delFtpSession(InetAddress ipOnly, InetSocketAddress fullIp) {
370         ftpSessionReference.delFtpSession(ipOnly, fullIp);
371     }
372 
373     /**
374      * Test if the couple of addresses is already in the context
375      * 
376      * @param ipOnly
377      * @param fullIp
378      * @return True if the couple is present
379      */
380     public boolean hasFtpSession(InetAddress ipOnly, InetSocketAddress fullIp) {
381         return ftpSessionReference.contains(ipOnly, fullIp);
382     }
383 
384     /**
385      * 
386      * @return the number of Active Sessions
387      */
388     public int getNumberSessions() {
389         return ftpSessionReference.sessionsNumber();
390     }
391 
392     /**
393      * Try to add a Passive Channel listening to the specified local address
394      * 
395      * @param address
396      * @param ssl
397      * @throws Reply425Exception
398      *             in case the channel cannot be opened
399      */
400     public void bindPassive(InetSocketAddress address, boolean ssl) throws Reply425Exception {
401         configuration.bindLock();
402         try {
403             BindAddress bindAddress = hashBindPassiveDataConn.get(address);
404             if (bindAddress == null) {
405                 logger.debug("Bind really to {}", address);
406                 Channel parentChannel = null;
407                 try {
408                     ChannelFuture future = null;
409                     if (ssl) {
410                         future = passiveSslBootstrap.bind(address);
411                     } else {
412                         future = passiveBootstrap.bind(address);
413                     }
414                     if (future.await(configuration.getTIMEOUTCON())) {
415                         parentChannel = future.sync().channel();
416                     } else {
417                         logger.warn("Cannot open passive connection due to Timeout");
418                         throw new Reply425Exception(
419                                 "Cannot open a Passive Connection due to Timeout");
420                     }
421                 } catch (ChannelException e) {
422                     logger.warn("Cannot open passive connection {}", e
423                             .getMessage());
424                     throw new Reply425Exception(
425                             "Cannot open a Passive Connection");
426                 } catch (InterruptedException e) {
427                     logger.warn("Cannot open passive connection {}", e
428                             .getMessage());
429                     throw new Reply425Exception(
430                             "Cannot open a Passive Connection");
431                 }
432                 bindAddress = new BindAddress(parentChannel);
433                 FtpChannelUtils.addDataChannel(parentChannel, configuration);
434                 hashBindPassiveDataConn.put(address, bindAddress);
435             }
436             bindAddress.nbBind++;
437             logger.debug("Bind number to {} is {}", address, bindAddress.nbBind);
438         } finally {
439             configuration.bindUnlock();
440         }
441     }
442 
443     /**
444      * Try to unbind (closing the parent channel) the Passive Channel listening to the specified
445      * local address if the last one. It returns only when the underlying parent channel is closed
446      * if this was the last session that wants to open on this local address.
447      * 
448      * @param address
449      */
450     public void unbindPassive(InetSocketAddress address) {
451         configuration.bindLock();
452         try {
453             BindAddress bindAddress = hashBindPassiveDataConn.get(address);
454             if (bindAddress != null) {
455                 bindAddress.nbBind--;
456                 logger.debug("Bind number to {} left is {}", address, bindAddress.nbBind);
457                 if (bindAddress.nbBind == 0) {
458                     WaarpSslUtility.closingSslChannel(bindAddress.parent);
459                     hashBindPassiveDataConn.remove(address);
460                 }
461             } else {
462                 logger.warn("No Bind to {}", address);
463             }
464         } finally {
465             configuration.bindUnlock();
466         }
467     }
468 
469     /**
470      * 
471      * @return the number of Binded Passive Connections
472      */
473     public int getNbBindedPassive() {
474         return hashBindPassiveDataConn.size();
475     }
476 
477     /**
478      * Return the associated Executor for Command Event
479      * 
480      * @return the Command Event Executor
481      */
482     public EventExecutorGroup getExecutor() {
483         return execCommandEvent;
484     }
485 
486     /**
487      * Return the associated Executor for Data Event
488      * 
489      * @return the Data Event Executor
490      */
491     public EventExecutorGroup getDataExecutor() {
492         return execDataEvent;
493     }
494 
495     /**
496      * @param ssl
497      * @return the ActiveBootstrap
498      */
499     public Bootstrap getActiveBootstrap(boolean ssl) {
500         if (ssl) {
501             return activeSslBootstrap;
502         } else {
503             return activeBootstrap;
504         }
505     }
506 
507     /**
508      * @return the commandChannelGroup
509      */
510     public ChannelGroup getCommandChannelGroup() {
511         return commandChannelGroup;
512     }
513 
514     /**
515      * @return the dataChannelGroup
516      */
517     public ChannelGroup getDataChannelGroup() {
518         return dataChannelGroup;
519     }
520 
521     /**
522      * 
523      * @return The TrafficCounterFactory
524      */
525     public FtpGlobalTrafficShapingHandler getGlobalTrafficShapingHandler() {
526         return globalTrafficShapingHandler;
527     }
528 
529     /**
530      * 
531      * @return a new ChannelTrafficShapingHandler
532      */
533     public ChannelTrafficShapingHandler newChannelTrafficShapingHandler() {
534         if (configuration.getServerChannelWriteLimit() == 0 &&
535                 configuration.getServerChannelReadLimit() == 0) {
536             return null;
537         }
538         if (globalTrafficShapingHandler instanceof GlobalChannelTrafficShapingHandler) {
539             return null;
540         }
541         return new FtpChannelTrafficShapingHandler(
542                 configuration.getServerChannelWriteLimit(),
543                 configuration.getServerChannelReadLimit(),
544                 configuration.getDelayLimit());
545     }
546 
547     public void releaseResources() {
548         WaarpSslUtility.forceCloseAllSslChannels();
549         execBoss.shutdownGracefully();
550         execWorker.shutdownGracefully();
551         execPassiveDataBoss.shutdownGracefully();
552         execDataWorker.shutdownGracefully();
553         //execCommandEvent.shutdownGracefully();
554         //execDataEvent.shutdownGracefully();
555         globalTrafficShapingHandler.release();
556         executorService.shutdown();
557     }
558 
559     public boolean isAcceptAuthProt() {
560         return acceptAuthProt;
561     }
562 
563     /**
564      * @return the usingNativeSsl
565      */
566     public boolean isUsingNativeSsl() {
567         return usingNativeSsl;
568     }
569 
570     /**
571      * @param usingNativeSsl
572      *            the usingNativeSsl to set
573      */
574     public void setUsingNativeSsl(boolean usingNativeSsl) {
575         this.usingNativeSsl = usingNativeSsl;
576     }
577 
578     /**
579      * @param acceptAuthProt
580      *            the acceptAuthProt to set
581      */
582     public void setAcceptAuthProt(boolean acceptAuthProt) {
583         this.acceptAuthProt = acceptAuthProt;
584     }
585 
586 }