Call-Home provider migration to transport-api
[netconf.git] / netconf / callhome-server / src / main / java / org / opendaylight / netconf / callhome / server / tls / CallHomeTlsServer.java
diff --git a/netconf/callhome-server/src/main/java/org/opendaylight/netconf/callhome/server/tls/CallHomeTlsServer.java b/netconf/callhome-server/src/main/java/org/opendaylight/netconf/callhome/server/tls/CallHomeTlsServer.java
new file mode 100644 (file)
index 0000000..fbec4c1
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.callhome.server.tls;
+
+import static java.util.Objects.requireNonNull;
+import static org.opendaylight.netconf.client.NetconfClientSessionNegotiatorFactory.DEFAULT_CLIENT_CAPABILITIES;
+
+import io.netty.channel.ChannelOption;
+import io.netty.util.HashedWheelTimer;
+import java.net.InetAddress;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.netconf.callhome.server.CallHomeStatusRecorder;
+import org.opendaylight.netconf.callhome.server.CallHomeTransportChannelListener;
+import org.opendaylight.netconf.client.NetconfClientSessionNegotiatorFactory;
+import org.opendaylight.netconf.transport.api.UnsupportedConfigurationException;
+import org.opendaylight.netconf.transport.tcp.BootstrapFactory;
+import org.opendaylight.netconf.transport.tls.TLSClient;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IetfInetUtil;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev230417.netconf.client.listen.stack.grouping.transport.ssh.ssh.TcpServerParametersBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tcp.server.rev230417.TcpServerGrouping;
+import org.opendaylight.yangtools.yang.common.Uint16;
+
+public final class CallHomeTlsServer implements AutoCloseable {
+    private static final int DEFAULT_PORT = 4335;
+    private static final Integer DEFAULT_TIMEOUT_MILLIS = 5000;
+    private static final Integer DEFAULT_MAX_CONNECTIONS = 64;
+
+    private final CallHomeTlsSessionContextManager contextManager;
+    private final TLSClient client;
+
+    private CallHomeTlsServer(final TcpServerGrouping tcpServerParams,
+            final BootstrapFactory bootstrapFactory,
+            final Integer maxConnections, final Integer timeoutMillis,
+            final NetconfClientSessionNegotiatorFactory negotiationFactory,
+            final CallHomeTlsSessionContextManager contextManager,
+            final CallHomeTlsAuthProvider authProvider,
+            final CallHomeStatusRecorder statusRecorder) {
+        this.contextManager = requireNonNull(contextManager);
+        final var bootstrap = bootstrapFactory.newServerBootstrap()
+            .childOption(ChannelOption.SO_KEEPALIVE, true)
+            .childOption(ChannelOption.SO_BACKLOG, requireNonNull(maxConnections))
+            .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, requireNonNull(timeoutMillis));
+        final var transportChannelListener = new CallHomeTransportChannelListener(requireNonNull(negotiationFactory),
+            contextManager, requireNonNull(statusRecorder));
+        try {
+            client = TLSClient.listen(transportChannelListener, bootstrap, tcpServerParams, authProvider)
+                .get(timeoutMillis, TimeUnit.MILLISECONDS);
+        } catch (UnsupportedConfigurationException | InterruptedException | ExecutionException | TimeoutException e) {
+            throw new IllegalStateException("Could not start TLS Call-Home server", e);
+        }
+    }
+
+    @Override
+    public void close() throws Exception {
+        contextManager.close();
+        client.shutdown().get();
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static final class Builder {
+
+        private InetAddress address;
+        private int port = DEFAULT_PORT;
+        private BootstrapFactory bootstrapFactory;
+        private NetconfClientSessionNegotiatorFactory negotiationFactory;
+        private Integer maxConnections;
+        private Integer timeoutMillis;
+        private CallHomeTlsAuthProvider authProvider;
+        private CallHomeTlsSessionContextManager contextManager;
+        private CallHomeStatusRecorder statusRecorder;
+
+        private Builder() {
+            // on purpose
+        }
+
+        public @NonNull CallHomeTlsServer build() {
+            return new CallHomeTlsServer(
+                toServerParams(address, port),
+                bootstrapFactory == null ? defaultBootstrapFactory() : bootstrapFactory,
+                maxConnections == null ? DEFAULT_MAX_CONNECTIONS : maxConnections,
+                timeoutMillis == null ? DEFAULT_TIMEOUT_MILLIS : timeoutMillis,
+                negotiationFactory == null ? defaultNegotiationFactory() : negotiationFactory,
+                contextManager, authProvider, statusRecorder);
+        }
+
+        public Builder withSessionContextManager(final CallHomeTlsSessionContextManager newContextManager) {
+            this.contextManager = newContextManager;
+            return this;
+        }
+
+        public Builder withAuthProvider(final CallHomeTlsAuthProvider newAuthProvider) {
+            this.authProvider = newAuthProvider;
+            return this;
+        }
+
+        public Builder withStatusRecorder(final CallHomeStatusRecorder newStatusRecorder) {
+            this.statusRecorder = newStatusRecorder;
+            return this;
+        }
+
+        public Builder withAddress(final InetAddress newAddress) {
+            this.address = newAddress;
+            return this;
+        }
+
+        public Builder withPort(final int newPort) {
+            this.port = newPort;
+            return this;
+        }
+
+        public Builder withMaxConnections(final int newMaxConnections) {
+            this.maxConnections = newMaxConnections;
+            return this;
+        }
+
+        public Builder withTimeout(final int newTimeoutMillis) {
+            this.timeoutMillis = newTimeoutMillis;
+            return this;
+        }
+
+        public Builder withBootstrapFactory(final BootstrapFactory newBootstrapFactory) {
+            this.bootstrapFactory = newBootstrapFactory;
+            return this;
+        }
+
+        public Builder withNegotiationFactory(final NetconfClientSessionNegotiatorFactory newNegotiationFactory) {
+            this.negotiationFactory = newNegotiationFactory;
+            return this;
+        }
+    }
+
+    private static TcpServerGrouping toServerParams(final InetAddress address, final int port) {
+        final var ipAddress = IetfInetUtil.ipAddressFor(
+            address == null ? InetAddress.getLoopbackAddress() : address);
+        final var portNumber = new PortNumber(Uint16.valueOf(port < 0 ? DEFAULT_PORT : port));
+        return new TcpServerParametersBuilder().setLocalAddress(ipAddress).setLocalPort(portNumber).build();
+    }
+
+    private static BootstrapFactory defaultBootstrapFactory() {
+        return new BootstrapFactory("tls-call-home-server", 0);
+    }
+
+    private static NetconfClientSessionNegotiatorFactory defaultNegotiationFactory() {
+        return new NetconfClientSessionNegotiatorFactory(new HashedWheelTimer(),
+            Optional.empty(), DEFAULT_TIMEOUT_MILLIS, DEFAULT_CLIENT_CAPABILITIES);
+    }
+}
\ No newline at end of file