2 * Copyright (c) 2016 Brocade Communication Systems and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
9 package org.opendaylight.netconf.callhome.protocol;
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Throwables;
13 import io.netty.channel.EventLoopGroup;
14 import io.netty.util.concurrent.GlobalEventExecutor;
15 import io.netty.util.concurrent.Promise;
16 import java.io.IOException;
17 import java.net.InetSocketAddress;
18 import java.net.SocketAddress;
19 import java.security.PublicKey;
20 import java.util.concurrent.ConcurrentHashMap;
21 import java.util.concurrent.ConcurrentMap;
22 import javax.annotation.Nullable;
23 import javax.annotation.concurrent.GuardedBy;
24 import org.apache.sshd.ClientChannel;
25 import org.apache.sshd.ClientSession;
26 import org.apache.sshd.client.future.AuthFuture;
27 import org.apache.sshd.client.future.OpenFuture;
28 import org.apache.sshd.client.session.ClientSessionImpl;
29 import org.apache.sshd.common.Session;
30 import org.apache.sshd.common.future.SshFutureListener;
31 import org.opendaylight.netconf.client.NetconfClientSession;
32 import org.opendaylight.netconf.client.NetconfClientSessionListener;
33 import org.opendaylight.netconf.client.NetconfClientSessionNegotiatorFactory;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
37 class CallHomeSessionContext implements CallHomeProtocolSessionContext {
39 private static final Logger LOG = LoggerFactory.getLogger(CallHomeSessionContext.class);
40 static final Session.AttributeKey<CallHomeSessionContext> SESSION_KEY = new Session.AttributeKey<>();
42 private static final String NETCONF = "netconf";
44 private final ClientSessionImpl sshSession;
45 private final CallHomeAuthorization authorization;
46 private final Factory factory;
48 private volatile MinaSshNettyChannel nettyChannel = null;
49 private volatile boolean activated;
51 private InetSocketAddress remoteAddress;
52 private PublicKey serverKey;
54 CallHomeSessionContext(ClientSession sshSession, CallHomeAuthorization authorization,
55 SocketAddress remoteAddress, Factory factory) {
56 this.authorization = Preconditions.checkNotNull(authorization, "authorization");
57 Preconditions.checkArgument(this.authorization.isServerAllowed(), "Server was not allowed.");
58 Preconditions.checkArgument(sshSession instanceof ClientSessionImpl,
59 "sshSession must implement ClientSessionImpl");
60 this.factory = Preconditions.checkNotNull(factory, "factory");
61 this.sshSession = (ClientSessionImpl) sshSession;
62 this.sshSession.setAttribute(SESSION_KEY, this);
63 this.remoteAddress = (InetSocketAddress) this.sshSession.getIoSession().getRemoteAddress();
64 this.serverKey = this.sshSession.getKex().getServerKey();
67 static CallHomeSessionContext getFrom(ClientSession sshSession) {
68 return sshSession.getAttribute(SESSION_KEY);
71 AuthFuture authorize() throws IOException {
72 authorization.applyTo(sshSession);
73 return sshSession.auth();
76 void openNetconfChannel() {
77 LOG.debug("Opening NETCONF Subsystem on {}", sshSession);
79 final ClientChannel netconfChannel = sshSession.createSubsystemChannel(NETCONF);
80 netconfChannel.setStreaming(ClientChannel.Streaming.Async);
81 netconfChannel.open().addListener(newSshFutureListener(netconfChannel));
82 } catch (IOException e) {
83 throw Throwables.propagate(e);
87 SshFutureListener<OpenFuture> newSshFutureListener(final ClientChannel netconfChannel) {
89 if (future.isOpened()) {
90 netconfChannelOpened(netconfChannel);
92 channelOpenFailed(future.getException());
97 private void channelOpenFailed(Throwable e) {
98 LOG.error("Unable to open netconf subsystem, disconnecting.", e);
99 sshSession.close(false);
102 private void netconfChannelOpened(ClientChannel netconfChannel) {
103 nettyChannel = newMinaSshNettyChannel(netconfChannel);
104 factory.getChannelOpenListener().onNetconfSubsystemOpened(CallHomeSessionContext.this,
105 listener -> doActivate(listener));
109 private synchronized Promise<NetconfClientSession> doActivate(NetconfClientSessionListener listener) {
111 return newSessionPromise().setFailure(new IllegalStateException("Session already activated."));
114 LOG.info("Activating Netconf channel for {} with {}", getRemoteAddress(), listener);
115 Promise<NetconfClientSession> activationPromise = newSessionPromise();
116 factory.getChannelInitializer(listener).initialize(nettyChannel, activationPromise);
117 factory.getNettyGroup().register(nettyChannel).awaitUninterruptibly(500);
118 return activationPromise;
121 protected MinaSshNettyChannel newMinaSshNettyChannel(ClientChannel netconfChannel) {
122 return new MinaSshNettyChannel(this, sshSession, netconfChannel);
125 private Promise<NetconfClientSession> newSessionPromise() {
126 return GlobalEventExecutor.INSTANCE.newPromise();
130 public PublicKey getRemoteServerKey() {
135 public String getRemoteServerVersion() {
136 return sshSession.getServerVersion();
140 public InetSocketAddress getRemoteAddress() {
141 return remoteAddress;
145 public String getSessionName() {
146 return authorization.getSessionName();
150 factory.remove(this);
153 static class Factory {
155 private final EventLoopGroup nettyGroup;
156 private final NetconfClientSessionNegotiatorFactory negotiatorFactory;
157 private final CallHomeNetconfSubsystemListener subsystemListener;
158 private final ConcurrentMap<String, CallHomeSessionContext> sessions = new ConcurrentHashMap<>();
160 Factory(EventLoopGroup nettyGroup, NetconfClientSessionNegotiatorFactory negotiatorFactory,
161 CallHomeNetconfSubsystemListener subsystemListener) {
162 this.nettyGroup = Preconditions.checkNotNull(nettyGroup, "nettyGroup");
163 this.negotiatorFactory = Preconditions.checkNotNull(negotiatorFactory, "negotiatorFactory");
164 this.subsystemListener = Preconditions.checkNotNull(subsystemListener);
167 void remove(CallHomeSessionContext session) {
168 sessions.remove(session.getSessionName(), session);
171 ReverseSshChannelInitializer getChannelInitializer(NetconfClientSessionListener listener) {
172 return ReverseSshChannelInitializer.create(negotiatorFactory, listener);
175 CallHomeNetconfSubsystemListener getChannelOpenListener() {
176 return this.subsystemListener;
180 CallHomeSessionContext createIfNotExists(ClientSession sshSession, CallHomeAuthorization authorization,
181 SocketAddress remoteAddress) {
182 CallHomeSessionContext session = new CallHomeSessionContext(sshSession, authorization,
183 remoteAddress, this);
184 CallHomeSessionContext preexisting = sessions.putIfAbsent(session.getSessionName(), session);
185 // If preexisting is null - session does not exist, so we can safely create new one, otherwise we return
186 // null and incoming connection will be rejected.
187 return preexisting == null ? session : null;
190 EventLoopGroup getNettyGroup() {