View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.waarp.ftp.core.session;
21  
22  import io.netty.channel.Channel;
23  import org.waarp.common.logging.SysErrLogger;
24  import org.waarp.common.logging.WaarpLogger;
25  import org.waarp.common.logging.WaarpLoggerFactory;
26  import org.waarp.ftp.core.utils.FtpChannelUtils;
27  
28  import java.net.InetAddress;
29  import java.net.InetSocketAddress;
30  import java.net.UnknownHostException;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.Enumeration;
34  import java.util.concurrent.ConcurrentHashMap;
35  
36  /**
37   * Class that allows to retrieve a session when a connection occurs on the Data
38   * network based on the
39   * {@link InetAddress} of the remote client and the {@link InetSocketAddress} of
40   * the server for Passive and
41   * reverse for Active connections. This is particularly useful for Passive mode
42   * connection since there is no
43   * way to pass the session to the connected channel without this reference.
44   */
45  public class FtpSessionReference {
46    /**
47     * Internal Logger
48     */
49    private static final WaarpLogger logger =
50        WaarpLoggerFactory.getLogger(FtpSessionReference.class);
51  
52    private static final byte[] LOOPBACK = { 0, 0, 0, 0 };
53    private static final byte[] LOOPBACK2 = { 127, 0, 0, 1 };
54    private static final InetAddress LOOPBACK_ADDRESS;
55  
56    static {
57      InetAddress address = null;
58      try {
59        address = InetAddress.getByAddress(LOOPBACK2);
60      } catch (final UnknownHostException e) {
61        SysErrLogger.FAKE_LOGGER.ignoreLog(e);
62      }
63      LOOPBACK_ADDRESS = address;
64    }
65  
66    /**
67     * Index of FtpSession References
68     */
69    public static class P2PAddress {
70      /**
71       * Remote Inet Address (no port)
72       */
73      public final InetAddress ipOnly;
74  
75      /**
76       * Local Inet Socket Address (with port)
77       */
78      public final InetSocketAddress fullIp;
79  
80      public final long creationTime = System.currentTimeMillis();
81  
82      /**
83       * Constructor from Channel
84       *
85       * @param channel
86       */
87      public P2PAddress(final Channel channel) {
88        InetAddress ip = FtpChannelUtils.getRemoteInetAddress(channel);
89        if (isLoopback(ip)) {
90          ipOnly = LOOPBACK_ADDRESS;
91        } else {
92          ipOnly = ip;
93        }
94        InetSocketAddress isa = (InetSocketAddress) channel.localAddress();
95        if (isLoopback(isa.getAddress())) {
96          fullIp = new InetSocketAddress(LOOPBACK_ADDRESS, isa.getPort());
97        } else {
98          fullIp = isa;
99        }
100     }
101 
102     /**
103      * Constructor from addresses
104      *
105      * @param address
106      * @param inetSocketAddress
107      */
108     public P2PAddress(final InetAddress address,
109                       final InetSocketAddress inetSocketAddress) {
110       if (isLoopback(address)) {
111         ipOnly = LOOPBACK_ADDRESS;
112       } else {
113         ipOnly = address;
114       }
115       if (isLoopback(inetSocketAddress.getAddress())) {
116         fullIp = new InetSocketAddress(LOOPBACK_ADDRESS,
117                                        inetSocketAddress.getPort());
118       } else {
119         fullIp = inetSocketAddress;
120       }
121     }
122 
123     /**
124      * @return True if the P2Paddress is valid
125      */
126     private final boolean isValid() {
127       return ipOnly != null && fullIp != null;
128     }
129 
130     @Override
131     public final boolean equals(final Object o) {
132       if (o == null) {
133         return false;
134       }
135       if (o instanceof P2PAddress) {
136         final P2PAddress p2paddress = (P2PAddress) o;
137         if (p2paddress.isValid() && isValid()) {
138           boolean equal = p2paddress.fullIp.equals(fullIp) &&
139                           p2paddress.ipOnly.equals(ipOnly);
140           if (!equal) {
141             boolean sameIp =
142                 p2paddress.fullIp.getAddress().equals(fullIp.getAddress()) ||
143                 (isLoopback(fullIp.getAddress()) &&
144                  isLoopback(p2paddress.fullIp.getAddress()));
145             equal = sameIp && p2paddress.fullIp.getPort() == fullIp.getPort();
146           }
147           return equal;
148         }
149       }
150       return false;
151     }
152 
153     @Override
154     public final int hashCode() {
155       return fullIp.hashCode() + ipOnly.hashCode();
156     }
157 
158     @Override
159     public String toString() {
160       return ipOnly.toString() + "-" + fullIp.toString();
161     }
162   }
163 
164   private static boolean isLoopback(final InetAddress inetAddress) {
165     return Arrays.equals(inetAddress.getAddress(), LOOPBACK2) ||
166            Arrays.equals(inetAddress.getAddress(), LOOPBACK) ||
167            inetAddress.isAnyLocalAddress() || inetAddress.isLoopbackAddress();
168   }
169 
170   /**
171    * Reference of FtpSession from InetSocketAddress
172    */
173   private final ConcurrentHashMap<P2PAddress, FtpSession> hashMap =
174       new ConcurrentHashMap<P2PAddress, FtpSession>();
175 
176   /**
177    * Constructor
178    */
179   public FtpSessionReference() {
180     // nothing
181   }
182 
183   private void cleanFtpSession() {
184     // 10 minutes ago
185     final long nowInPast = System.currentTimeMillis() - 10 * 60 * 1000;
186     ArrayList<P2PAddress> list = new ArrayList<P2PAddress>();
187     Enumeration<P2PAddress> enumeration = hashMap.keys();
188     while (enumeration.hasMoreElements()) {
189       final P2PAddress entry = enumeration.nextElement();
190       if (entry.creationTime < nowInPast) {
191         list.add(entry);
192       }
193     }
194     for (P2PAddress pAddress : list) {
195       hashMap.remove(pAddress);
196     }
197     list.clear();
198   }
199 
200   /**
201    * Add a session from a couple of addresses
202    *
203    * @param ipOnly
204    * @param fullIp
205    * @param session
206    */
207   public final void setNewFtpSession(final InetAddress ipOnly,
208                                      final InetSocketAddress fullIp,
209                                      final FtpSession session) {
210     cleanFtpSession();
211     final P2PAddress pAddress = new P2PAddress(ipOnly, fullIp);
212     if (!pAddress.isValid()) {
213       logger.error(
214           "Couple invalid in setNewFtpSession: " + ipOnly + " : " + fullIp);
215       return;
216     }
217     hashMap.put(pAddress, session);
218   }
219 
220   /**
221    * Return and remove the FtpSession
222    *
223    * @param channel
224    *
225    * @return the FtpSession if it exists associated to this channel
226    */
227   public final FtpSession getPassiveFtpSession(final Channel channel) {
228     cleanFtpSession();
229     // First check passive connection
230     final P2PAddress pAddress = new P2PAddress(channel);
231     if (!pAddress.isValid()) {
232       logger.error("Couple invalid in getPassiveFtpSession: " + channel);
233       return null;
234     }
235     return hashMap.remove(pAddress);
236   }
237 
238   /**
239    * @param channel
240    *
241    * @return the associated removed Passive FtpSession or null if not found
242    */
243   public final FtpSession findPassive(final Channel channel) {
244     cleanFtpSession();
245     InetAddress remote =
246         ((InetSocketAddress) channel.remoteAddress()).getAddress();
247     if (isLoopback(remote)) {
248       remote = LOOPBACK_ADDRESS;
249     }
250     final int port = ((InetSocketAddress) channel.localAddress()).getPort();
251     P2PAddress entry = null;
252     Enumeration<P2PAddress> enumeration = hashMap.keys();
253     while (enumeration.hasMoreElements()) {
254       entry = enumeration.nextElement();
255       if (entry.fullIp.getPort() == port && (entry.ipOnly.equals(remote) ||
256                                              Arrays.equals(
257                                                  entry.ipOnly.getAddress(),
258                                                  remote.getAddress()))) {
259         break;
260       }
261       entry = null;
262     }
263     if (entry != null) {
264       logger.debug("Found from port only: {}", entry);
265       return hashMap.remove(entry);
266     }
267     return null;
268   }
269 
270   /**
271    * Remove one FtpSession if possible
272    *
273    * @param channel
274    */
275   public final void delFtpSession(final Channel channel) {
276     cleanFtpSession();
277     final P2PAddress pAddress;
278     pAddress = new P2PAddress(channel);
279     if (!pAddress.isValid()) {
280       return;
281     }
282     hashMap.remove(pAddress);
283   }
284 
285   /**
286    * Remove the FtpSession from couple of addresses
287    *
288    * @param ipOnly
289    * @param fullIp
290    */
291   public final void delFtpSession(final InetAddress ipOnly,
292                                   final InetSocketAddress fullIp) {
293     cleanFtpSession();
294     final P2PAddress pAddress = new P2PAddress(ipOnly, fullIp);
295     if (!pAddress.isValid()) {
296       logger.error(
297           "Couple invalid in delFtpSession: " + ipOnly + " : " + fullIp);
298       return;
299     }
300     hashMap.remove(pAddress);
301   }
302 
303   /**
304    * @return the number of active sessions
305    */
306   public final int sessionsNumber() {
307     cleanFtpSession();
308     return hashMap.size();
309   }
310 
311 }