Subversion Repositories Programming Utils

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
86 rm5248 1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one
3
 * or more contributor license agreements.  See the NOTICE file
4
 * distributed with this work for additional information
5
 * regarding copyright ownership.  The ASF licenses this file
6
 * to you under the Apache License, Version 2.0 (the
7
 * "License"); you may not use this file except in compliance
8
 * with the License.  You may obtain a copy of the License at
9
 *
10
 *   http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing,
13
 * software distributed under the License is distributed on an
14
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
 * KIND, either express or implied.  See the License for the
16
 * specific language governing permissions and limitations
17
 * under the License.
18
 */
19
package org.apache.sshd.common.forward;
20
 
21
import java.io.IOException;
22
import java.net.InetSocketAddress;
23
import java.net.SocketAddress;
24
import java.util.HashMap;
25
import java.util.HashSet;
26
import java.util.Map;
27
import java.util.Set;
28
 
29
import org.apache.sshd.ClientChannel;
30
import org.apache.sshd.client.future.OpenFuture;
31
import org.apache.sshd.common.Closeable;
32
import org.apache.sshd.common.ForwardingFilter;
33
import org.apache.sshd.common.Session;
34
import org.apache.sshd.common.SshConstants;
35
import org.apache.sshd.common.SshException;
36
import org.apache.sshd.common.SshdSocketAddress;
37
import org.apache.sshd.common.TcpipForwarder;
38
import org.apache.sshd.common.future.SshFutureListener;
39
import org.apache.sshd.common.io.IoAcceptor;
40
import org.apache.sshd.common.io.IoHandler;
41
import org.apache.sshd.common.io.IoSession;
42
import org.apache.sshd.common.session.ConnectionService;
43
import org.apache.sshd.common.util.Buffer;
44
import org.apache.sshd.common.util.CloseableUtils;
45
import org.apache.sshd.common.util.Readable;
46
 
47
/**
48
 * TODO Add javadoc
49
 *
50
 * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
51
 */
52
public class DefaultTcpipForwarder extends CloseableUtils.AbstractInnerCloseable implements TcpipForwarder, IoHandler {
53
 
54
    private final ConnectionService service;
55
    private final Session session;
56
    private final Map<Integer, SshdSocketAddress> localToRemote = new HashMap<Integer, SshdSocketAddress>();
57
    private final Map<Integer, SshdSocketAddress> remoteToLocal = new HashMap<Integer, SshdSocketAddress>();
58
    private final Set<SshdSocketAddress> localForwards = new HashSet<SshdSocketAddress>();
59
    protected IoAcceptor acceptor;
60
 
61
    public DefaultTcpipForwarder(ConnectionService service) {
62
        this.service = service;
63
        this.session = service.getSession();
64
    }
65
 
66
    //
67
    // TcpIpForwarder implementation
68
    //
69
 
70
    public synchronized SshdSocketAddress startLocalPortForwarding(SshdSocketAddress local, SshdSocketAddress remote) throws IOException {
71
        if (local == null) {
72
            throw new IllegalArgumentException("Local address is null");
73
        }
74
        if (remote == null) {
75
            throw new IllegalArgumentException("Remote address is null");
76
        }
77
        if (local.getPort() < 0) {
78
            throw new IllegalArgumentException("Invalid local port: " + local.getPort());
79
        }
80
        if (isClosed()) {
81
            throw new IllegalStateException("TcpipForwarder is closed");
82
        }
83
        if (isClosing()) {
84
            throw new IllegalStateException("TcpipForwarder is closing");
85
        }
86
        SshdSocketAddress bound = doBind(local);
87
        localToRemote.put(bound.getPort(), remote);
88
        return bound;
89
    }
90
 
91
    public synchronized void stopLocalPortForwarding(SshdSocketAddress local) throws IOException {
92
        if (localToRemote.remove(local.getPort()) != null && acceptor != null) {
93
            acceptor.unbind(local.toInetSocketAddress());
94
            if (acceptor.getBoundAddresses().isEmpty()) {
95
                close();
96
            }
97
        }
98
    }
99
 
100
    public synchronized SshdSocketAddress startRemotePortForwarding(SshdSocketAddress remote, SshdSocketAddress local) throws IOException {
101
        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_GLOBAL_REQUEST);
102
        buffer.putString("tcpip-forward");
103
        buffer.putBoolean(true);
104
        buffer.putString(remote.getHostName());
105
        buffer.putInt(remote.getPort());
106
        Buffer result = session.request(buffer);
107
        if (result == null) {
108
            throw new SshException("Tcpip forwarding request denied by server");
109
        }
110
        int port = remote.getPort() == 0 ? result.getInt() : remote.getPort();
111
        // TODO: Is it really safe to only store the local address after the request ?
112
        remoteToLocal.put(port, local);
113
        return new SshdSocketAddress(remote.getHostName(), port);
114
    }
115
 
116
    public synchronized void stopRemotePortForwarding(SshdSocketAddress remote) throws IOException {
117
        if (remoteToLocal.remove(remote.getPort()) != null) {
118
            Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_GLOBAL_REQUEST);
119
            buffer.putString("cancel-tcpip-forward");
120
            buffer.putBoolean(false);
121
            buffer.putString(remote.getHostName());
122
            buffer.putInt(remote.getPort());
123
            session.writePacket(buffer);
124
        }
125
    }
126
 
127
    public synchronized SshdSocketAddress getForwardedPort(int remotePort) {
128
        return remoteToLocal.get(remotePort);
129
    }
130
 
131
    public synchronized SshdSocketAddress localPortForwardingRequested(SshdSocketAddress local) throws IOException {
132
        if (local == null) {
133
            throw new IllegalArgumentException("Local address is null");
134
        }
135
        if (local.getPort() < 0) {
136
            throw new IllegalArgumentException("Invalid local port: " + local.getPort());
137
        }
138
        final ForwardingFilter filter = session.getFactoryManager().getTcpipForwardingFilter();
139
        if (filter == null || !filter.canListen(local, session)) {
140
            throw new IOException("Rejected address: " + local);
141
        }
142
        SshdSocketAddress bound = doBind(local);
143
        localForwards.add(bound);
144
        return bound;
145
    }
146
 
147
    public synchronized void localPortForwardingCancelled(SshdSocketAddress local) throws IOException {
148
        if (localForwards.remove(local) && acceptor != null) {
149
            acceptor.unbind(local.toInetSocketAddress());
150
            if (acceptor.getBoundAddresses().isEmpty()) {
151
                acceptor.close(true);
152
                acceptor = null;
153
            }
154
        }
155
    }
156
 
157
    public synchronized void close() {
158
        close(true);
159
    }
160
 
161
    @Override
162
    protected synchronized Closeable getInnerCloseable() {
163
        return builder().close(acceptor).build();
164
    }
165
 
166
    //
167
    // IoHandler implementation
168
    //
169
 
170
    public void sessionCreated(final IoSession session) throws Exception {
171
        final TcpipClientChannel channel;
172
        int localPort = ((InetSocketAddress) session.getLocalAddress()).getPort();
173
        if (localToRemote.containsKey(localPort)) {
174
            SshdSocketAddress remote = localToRemote.get(localPort);
175
            channel = new TcpipClientChannel(TcpipClientChannel.Type.Direct, session, remote);
176
        } else {
177
            channel = new TcpipClientChannel(TcpipClientChannel.Type.Forwarded, session, null);
178
        }
179
        session.setAttribute(TcpipClientChannel.class, channel);
180
        this.service.registerChannel(channel);
181
        channel.open().addListener(new SshFutureListener<OpenFuture>() {
182
            public void operationComplete(OpenFuture future) {
183
                Throwable t = future.getException();
184
                if (t != null) {
185
                    DefaultTcpipForwarder.this.service.unregisterChannel(channel);
186
                    channel.close(false);
187
                }
188
            }
189
        });
190
    }
191
 
192
    public void sessionClosed(IoSession session) throws Exception {
193
        TcpipClientChannel channel = (TcpipClientChannel) session.getAttribute(TcpipClientChannel.class);
194
        if (channel != null) {
195
            log.debug("IoSession {} closed, will now close the channel", session);
196
            channel.close(false);
197
        }
198
    }
199
 
200
    public void messageReceived(IoSession session, Readable message) throws Exception {
201
        TcpipClientChannel channel = (TcpipClientChannel) session.getAttribute(TcpipClientChannel.class);
202
        Buffer buffer = new Buffer();
203
        buffer.putBuffer(message);
204
        channel.waitFor(ClientChannel.OPENED | ClientChannel.CLOSED, Long.MAX_VALUE);
205
        channel.getInvertedIn().write(buffer.array(), buffer.rpos(), buffer.available());
206
        channel.getInvertedIn().flush();
207
    }
208
 
209
    public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
210
        cause.printStackTrace();
211
        session.close(false);
212
    }
213
 
214
    //
215
    // Private methods
216
    //
217
 
218
    private SshdSocketAddress doBind(SshdSocketAddress address) throws IOException {
219
        if (acceptor == null) {
220
            acceptor = session.getFactoryManager().getIoServiceFactory().createAcceptor(this);
221
        }
222
        Set<SocketAddress> before = acceptor.getBoundAddresses();
223
        try {
224
            acceptor.bind(address.toInetSocketAddress());
225
            Set<SocketAddress> after = acceptor.getBoundAddresses();
226
            after.removeAll(before);
227
            if (after.isEmpty()) {
228
                throw new IOException("Error binding to " + address + ": no local addresses bound");
229
            }
230
            if (after.size() > 1) {
231
                throw new IOException("Multiple local addresses have been bound for " + address);
232
            }
233
            InetSocketAddress result = (InetSocketAddress) after.iterator().next();
234
            return new SshdSocketAddress(address.getHostName(), result.getPort());
235
        } catch (IOException bindErr) {
236
            if (acceptor.getBoundAddresses().isEmpty()) {
237
                close();
238
            }
239
            throw bindErr;
240
        }
241
    }
242
 
243
    public String toString() {
244
        return getClass().getSimpleName() + "[" + session + "]";
245
    }
246
 
247
}