BUG-635: implement MD5 auth option for BGP peers 99/6599/4
authorRobert Varga <rovarga@cisco.com>
Tue, 29 Apr 2014 12:43:02 +0000 (14:43 +0200)
committerRobert Varga <rovarga@cisco.com>
Wed, 30 Apr 2014 05:06:05 +0000 (07:06 +0200)
This adds the wiring for the BGP dispatcher to make use of client/server
channels. It also adds validation support.

Change-Id: Ieb2dc4c60dfcd92d8e7a3f732198ad07a734853e
Signed-off-by: Robert Varga <rovarga@cisco.com>
12 files changed:
bgp/pom.xml
bgp/rib-impl-config/pom.xml
bgp/rib-impl-config/src/main/java/org/opendaylight/controller/config/yang/bgp/rib/impl/BGPDispatcherImplModule.java
bgp/rib-impl-config/src/main/java/org/opendaylight/controller/config/yang/bgp/rib/impl/BGPPeerModule.java
bgp/rib-impl-config/src/main/yang/bgp-rib-impl.yang
bgp/rib-impl/pom.xml
bgp/rib-impl/src/main/java/org/opendaylight/protocol/bgp/rib/impl/BGPDispatcherImpl.java
bgp/rib-impl/src/main/java/org/opendaylight/protocol/bgp/rib/impl/BGPPeer.java
bgp/rib-impl/src/main/java/org/opendaylight/protocol/bgp/rib/impl/spi/BGPDispatcher.java
integration-tests/src/test/java/org/opendaylight/protocol/integration/BgpRibImplBundleTest.java
integration-tests/src/test/java/org/opendaylight/protocol/integration/BgpRibMockBundleTest.java
integration-tests/src/test/java/org/opendaylight/protocol/integration/bgp/ParserToSalTest.java

index e8caf67de8bfad8365ad1fd1d0ca932e1425ec1c..31ca786a77ebcca42b291a08c429be057d9611c2 100644 (file)
                 <artifactId>topology-api-config</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>${project.groupId}</groupId>
+                <artifactId>tcpmd5-api</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>${project.groupId}</groupId>
+                <artifactId>tcpmd5-api-cfg</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>${project.groupId}</groupId>
+                <artifactId>tcpmd5-netty</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>${project.groupId}</groupId>
+                <artifactId>tcpmd5-netty-cfg</artifactId>
+                <version>${project.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
index 41fc03b045d87795cfa4c7a49881394fd41cfbf4..5e8e8915ee474672dc9018249cdc450c4551c01f 100644 (file)
             <groupId>${project.groupId}</groupId>
             <artifactId>bgp-parser-impl</artifactId>
         </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>tcpmd5-api-cfg</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>tcpmd5-netty</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>tcpmd5-netty-cfg</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.opendaylight.controller</groupId>
             <artifactId>config-api</artifactId>
index 20ee2623caffb6314d59c037f367bb3a9b07a724..cde62da8e8843a31159b817e2bba055ac5b9fb88 100644 (file)
@@ -44,6 +44,6 @@ org.opendaylight.controller.config.yang.bgp.rib.impl.AbstractBGPDispatcherImplMo
        public java.lang.AutoCloseable createInstance() {
                final BGPExtensionConsumerContext bgpExtensions = getBgpExtensionsDependency();
                return new BGPDispatcherImpl(bgpExtensions.getMessageRegistry(), getTimerDependency(),
-                               getBossGroupDependency(), getWorkerGroupDependency());
+                               getBossGroupDependency(), getWorkerGroupDependency(), getMd5ChannelFactoryDependency(), getMd5ServerChannelFactoryDependency());
        }
 }
index c983e04eaa59f964517990c6e905c16577892089..21ad7acddc683b10d440934acce1cfaf8aa94328 100644 (file)
  */
 package org.opendaylight.controller.config.yang.bgp.rib.impl;
 
+import java.lang.management.ManagementFactory;
 import java.net.InetSocketAddress;
 import java.util.List;
 
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanException;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+
 import org.opendaylight.controller.config.api.JmxAttributeValidationException;
 import org.opendaylight.protocol.bgp.rib.impl.BGPPeer;
 import org.opendaylight.protocol.bgp.rib.impl.spi.BGPSessionPreferences;
@@ -61,6 +69,29 @@ public final class BGPPeerModule extends org.opendaylight.controller.config.yang
                                "value is not set.", hostJmxAttribute);
                JmxAttributeValidationException.checkNotNull(getPort(),
                                "value is not set.", portJmxAttribute);
+
+               if (getPassword() != null) {
+                       /*
+                        *  This is a nasty hack, but we don't have another clean solution. We cannot allow
+                        *  password being set if the injected dispatcher does not have the optional
+                        *  md5-server-channel-factory set.
+                        *
+                        *  FIXME: this is a use case for Module interfaces, e.g. RibImplModule
+                        *         should something like isMd5ServerSupported()
+                        */
+                       final MBeanServer srv = ManagementFactory.getPlatformMBeanServer();
+                       try {
+                               // FIXME: AbstractRIBImplModule.bgpDispatcherJmxAttribute.getAttributeName()
+                               final ObjectName disp = (ObjectName) srv.getAttribute(getRib(), "BgpDispatcher");
+
+                               // FIXME: AbstractBGPDispatcherImplModule.md5ChannelFactoryJmxAttribute.getAttributeName()
+                               final Object cf = srv.getAttribute(disp, "Md5ChannelFactory");
+                               JmxAttributeValidationException.checkCondition(cf != null, "Underlying dispatcher does not support MD5 clients", this.passwordJmxAttribute);
+                       } catch (AttributeNotFoundException | InstanceNotFoundException
+                                       | MBeanException | ReflectionException e) {
+                               JmxAttributeValidationException.wrap(e, passwordJmxAttribute);
+                       }
+               }
        }
 
        private InetSocketAddress createAddress() {
@@ -111,7 +142,14 @@ public final class BGPPeerModule extends org.opendaylight.controller.config.yang
                        remoteAs = r.getLocalAs();
                }
 
-               return new BGPPeer(peerName(getHost()), createAddress(),
+               final String password;
+               if (getPassword() != null) {
+                       password = getPassword().getValue();
+               } else {
+                       password = null;
+               }
+
+               return new BGPPeer(peerName(getHost()), createAddress(), password,
                                new BGPSessionPreferences(r.getLocalAs(), getHoldtimer(), r.getBgpIdentifier(), tlvs), remoteAs, r);
        }
 }
index 2088596d8dff894f6a015ab0f818b9afceb1b8f4..b0df01aae22db95c7a04fe25fceb6a32e3fd0ea9 100644 (file)
@@ -15,6 +15,8 @@ module bgp-rib-impl {
     import netty { prefix netty; revision-date 2013-11-19; }
     import config { prefix config; revision-date 2013-04-05; }
     import protocol-framework { prefix pf; revision-date 2014-03-13; }
+    import odl-tcpmd5-cfg { prefix tcpmd5; revision-date 2014-04-27; }
+    import odl-tcpmd5-netty-cfg { prefix tcpmd5n; revision-date 2014-04-27; }
 
     organization "Cisco Systems, Inc.";
 
@@ -101,6 +103,24 @@ module bgp-rib-impl {
                     }
                 }
             }
+
+            container md5-channel-factory {
+                uses config:service-ref {
+                    refine type {
+                        mandatory false;
+                        config:required-identity tcpmd5n:md5-channel-factory;
+                    }
+                }
+            }
+
+            container md5-server-channel-factory {
+                uses config:service-ref {
+                    refine type {
+                        mandatory false;
+                        config:required-identity tcpmd5n:md5-server-channel-factory;
+                    }
+                }
+            }
         }
     }
 
@@ -205,6 +225,11 @@ module bgp-rib-impl {
                 type uint32;
             }
 
+            leaf password {
+                type tcpmd5:rfc2385-key;
+                description "RFC2385 shared secret";
+            }
+
             container rib {
                 uses config:service-ref {
                     refine type {
index 995acf2488b85683081523995a4e0f70b814a480..f50c427e92a12b31110a3a4f94f0d38dc6f84a43 100644 (file)
             <groupId>${project.groupId}</groupId>
             <artifactId>bgp-util</artifactId>
         </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>tcpmd5-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>tcpmd5-netty</artifactId>
+        </dependency>
         <dependency>
             <groupId>com.google.code.findbugs</groupId>
             <artifactId>jsr305</artifactId>
index e4bb409c3c02d7e7c7c37a8d440f51f406d4cd46..b6381a11d83c643931962a15bb0d5c401708490b 100644 (file)
@@ -7,6 +7,8 @@
  */
 package org.opendaylight.protocol.bgp.rib.impl;
 
+import io.netty.bootstrap.Bootstrap;
+import io.netty.bootstrap.ServerBootstrap;
 import io.netty.channel.EventLoopGroup;
 import io.netty.channel.socket.SocketChannel;
 import io.netty.util.Timer;
@@ -15,6 +17,10 @@ import io.netty.util.concurrent.Promise;
 
 import java.net.InetSocketAddress;
 
+import org.opendaylight.bgpcep.tcpmd5.KeyMapping;
+import org.opendaylight.bgpcep.tcpmd5.netty.MD5ChannelFactory;
+import org.opendaylight.bgpcep.tcpmd5.netty.MD5ChannelOption;
+import org.opendaylight.bgpcep.tcpmd5.netty.MD5ServerChannelFactory;
 import org.opendaylight.protocol.bgp.parser.BGPSessionListener;
 import org.opendaylight.protocol.bgp.parser.spi.MessageRegistry;
 import org.opendaylight.protocol.bgp.rib.impl.spi.BGPDispatcher;
@@ -31,17 +37,26 @@ import com.google.common.base.Preconditions;
  * Implementation of BGPDispatcher.
  */
 public final class BGPDispatcherImpl extends AbstractDispatcher<BGPSessionImpl, BGPSessionListener> implements BGPDispatcher, AutoCloseable {
+       private final MD5ServerChannelFactory<?> scf;
+       private final MD5ChannelFactory<?> cf;
        private final BGPHandlerFactory hf;
        private final Timer timer;
+       private KeyMapping keys;
 
        public BGPDispatcherImpl(final MessageRegistry messageRegistry, final Timer timer, final EventLoopGroup bossGroup, final EventLoopGroup workerGroup) {
+               this(messageRegistry, timer, bossGroup, workerGroup, null, null);
+       }
+
+       public BGPDispatcherImpl(final MessageRegistry messageRegistry, final Timer timer, final EventLoopGroup bossGroup, final EventLoopGroup workerGroup, final MD5ChannelFactory<?> cf, final MD5ServerChannelFactory<?> scf) {
                super(bossGroup, workerGroup);
                this.timer = Preconditions.checkNotNull(timer);
                this.hf = new BGPHandlerFactory(messageRegistry);
+               this.cf = cf;
+               this.scf = scf;
        }
 
        @Override
-       public Future<BGPSessionImpl> createClient(final InetSocketAddress address, final BGPSessionPreferences preferences,
+       public synchronized Future<BGPSessionImpl> createClient(final InetSocketAddress address, final BGPSessionPreferences preferences,
                        final AsNumber remoteAs, final BGPSessionListener listener, final ReconnectStrategy strategy) {
                final BGPSessionNegotiatorFactory snf = new BGPSessionNegotiatorFactory(this.timer, preferences, remoteAs);
                final SessionListenerFactory<BGPSessionListener> slf = new SessionListenerFactory<BGPSessionListener>() {
@@ -65,6 +80,19 @@ public final class BGPDispatcherImpl extends AbstractDispatcher<BGPSessionImpl,
                        final BGPSessionPreferences preferences, final AsNumber remoteAs,
                        final BGPSessionListener listener, final ReconnectStrategyFactory connectStrategyFactory,
                        final ReconnectStrategyFactory reestablishStrategyFactory) {
+               return this.createReconnectingClient(address, preferences, remoteAs, listener, connectStrategyFactory, reestablishStrategyFactory, null);
+       }
+
+       @Override
+       public void close() {
+       }
+
+       @Override
+       public synchronized Future<Void> createReconnectingClient(final InetSocketAddress address,
+                       final BGPSessionPreferences preferences, final AsNumber remoteAs,
+                       final BGPSessionListener listener,
+                       final ReconnectStrategyFactory connectStrategyFactory,
+                       final ReconnectStrategyFactory reestablishStrategyFactory, final KeyMapping keys) {
                final BGPSessionNegotiatorFactory snf = new BGPSessionNegotiatorFactory(this.timer, preferences, remoteAs);
                final SessionListenerFactory<BGPSessionListener> slf = new SessionListenerFactory<BGPSessionListener>() {
                        @Override
@@ -73,7 +101,8 @@ public final class BGPDispatcherImpl extends AbstractDispatcher<BGPSessionImpl,
                        }
                };
 
-               return super.createReconnectingClient(address, connectStrategyFactory, reestablishStrategyFactory.createReconnectStrategy(), new PipelineInitializer<BGPSessionImpl>() {
+               this.keys = keys;
+               final Future<Void> ret = super.createReconnectingClient(address, connectStrategyFactory, reestablishStrategyFactory.createReconnectStrategy(), new PipelineInitializer<BGPSessionImpl>() {
                        @Override
                        public void initializeChannel(final SocketChannel ch, final Promise<BGPSessionImpl> promise) {
                                ch.pipeline().addLast(BGPDispatcherImpl.this.hf.getDecoders());
@@ -81,9 +110,32 @@ public final class BGPDispatcherImpl extends AbstractDispatcher<BGPSessionImpl,
                                ch.pipeline().addLast(BGPDispatcherImpl.this.hf.getEncoders());
                        }
                });
+               this.keys = null;
+
+               return ret;
        }
 
+
        @Override
-       public void close() {
+       protected void customizeBootstrap(final Bootstrap b) {
+               if (keys != null && !keys.isEmpty()) {
+                       if (cf == null) {
+                               throw new UnsupportedOperationException("No key access instance available, cannot use key mapping");
+                       }
+                       b.channelFactory(cf);
+                       b.option(MD5ChannelOption.TCP_MD5SIG, keys);
+               }
        }
+
+       @Override
+       protected void customizeBootstrap(final ServerBootstrap b) {
+               if (keys != null && !keys.isEmpty()) {
+                       if (scf == null) {
+                               throw new UnsupportedOperationException("No key access instance available, cannot use key mapping");
+                       }
+                       b.channelFactory(scf);
+                       b.option(MD5ChannelOption.TCP_MD5SIG, keys);
+               }
+       }
+
 }
index 86d90248801914177d66fb743dc1721993c5ba54..235f4e972d45db7b4e6aaf0e22b9ab0bbd0317b7 100644 (file)
@@ -16,6 +16,7 @@ import java.util.Set;
 
 import javax.annotation.concurrent.GuardedBy;
 
+import org.opendaylight.bgpcep.tcpmd5.KeyMapping;
 import org.opendaylight.protocol.bgp.parser.BGPSession;
 import org.opendaylight.protocol.bgp.parser.BGPSessionListener;
 import org.opendaylight.protocol.bgp.parser.BGPTerminationReason;
@@ -31,6 +32,7 @@ import org.opendaylight.yangtools.yang.binding.Notification;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Charsets;
 import com.google.common.base.Objects;
 import com.google.common.base.Objects.ToStringHelper;
 import com.google.common.base.Preconditions;
@@ -51,11 +53,20 @@ public final class BGPPeer implements BGPSessionListener, Peer, AutoCloseable {
        private Future<Void> cf;
        private BGPSession session;
 
-       public BGPPeer(final String name, final InetSocketAddress address, final BGPSessionPreferences prefs,
+       public BGPPeer(final String name, final InetSocketAddress address, final String password, final BGPSessionPreferences prefs,
                        final AsNumber remoteAs, final RIB rib) {
                this.rib = Preconditions.checkNotNull(rib);
                this.name = Preconditions.checkNotNull(name);
-               this.cf = rib.getDispatcher().createReconnectingClient(address, prefs, remoteAs, this, rib.getTcpStrategyFactory(), rib.getSessionStrategyFactory());
+
+               final KeyMapping keys;
+               if (password != null) {
+                       keys = new KeyMapping();
+                       keys.put(address.getAddress(), password.getBytes(Charsets.US_ASCII));
+               } else {
+                       keys = null;
+               }
+
+               this.cf = rib.getDispatcher().createReconnectingClient(address, prefs, remoteAs, this, rib.getTcpStrategyFactory(), rib.getSessionStrategyFactory(), keys);
        }
 
        @Override
index 67d21d2bc09d7c46f7b3f9d0f01379151d0a34ea..320698878ac7010c5c1e1ccb5d6347bfba4b9167 100644 (file)
@@ -11,6 +11,7 @@ import io.netty.util.concurrent.Future;
 
 import java.net.InetSocketAddress;
 
+import org.opendaylight.bgpcep.tcpmd5.KeyMapping;
 import org.opendaylight.protocol.bgp.parser.BGPSession;
 import org.opendaylight.protocol.bgp.parser.BGPSessionListener;
 import org.opendaylight.protocol.framework.ReconnectStrategy;
@@ -24,7 +25,7 @@ public interface BGPDispatcher {
 
        /**
         * Creates BGP client.
-        * 
+        *
         * @param address Peer address
         * @param preferences connection attributes required for connection
         * @param listener BGP message listener
@@ -35,4 +36,7 @@ public interface BGPDispatcher {
 
        Future<Void> createReconnectingClient(InetSocketAddress address, BGPSessionPreferences preferences, AsNumber remoteAs,
                        BGPSessionListener listener, ReconnectStrategyFactory connectStrategyFactory, ReconnectStrategyFactory reestablishStrategyFactory);
+
+       Future<Void> createReconnectingClient(InetSocketAddress address, BGPSessionPreferences preferences, AsNumber remoteAs,
+                       BGPSessionListener listener, ReconnectStrategyFactory connectStrategyFactory, ReconnectStrategyFactory reestablishStrategyFactory, KeyMapping keys);
 }
index 4c5eb468bb47f59efccd79a55563c7f587394f35..1e1b3b293f0923befb7c088316bdd4bd799f389b 100644 (file)
@@ -16,7 +16,8 @@ public final class BgpRibImplBundleTest extends AbstractBundleTest {
        protected Collection<String> prerequisiteBundles() {
                return Lists.newArrayList("concepts", "bgp-concepts", "bgp-linkstate", "bgp-parser-api",
                                "bgp-parser-impl", "bgp-parser-spi", "bgp-rib-api", "bgp-rib-spi",
-                               "bgp-util", "rsvp-api", "util");
+                               "bgp-util", "rsvp-api", "tcpmd5-api", "tcpmd5-jni", "tcpmd5-netty",
+                               "tcpmd5-nio", "util");
        }
 
        @Override
index ecb7292525e72ff8070a03b5132ef62988840430..ce3b0f79b14261fd377cc3f87cdced55622c86cf 100644 (file)
@@ -16,7 +16,8 @@ public final class BgpRibMockBundleTest extends AbstractBundleTest {
        protected Collection<String> prerequisiteBundles() {
                return Lists.newArrayList("concepts", "bgp-concepts", "bgp-linkstate", "bgp-parser-api",
                                "bgp-parser-impl", "bgp-parser-spi", "bgp-rib-api", "bgp-rib-impl",
-                               "bgp-rib-spi", "bgp-util", "rsvp-api", "util");
+                               "bgp-rib-spi", "bgp-util", "rsvp-api", "tcpmd5-api", "tcpmd5-jni",
+                               "tcpmd5-nio", "tcpmd5-netty", "util");
        }
 
        @Override
index fde8121d8506da7421fe9e9a156dd3eee003c702..87e4c2cf1b888baf91b077fe4f6bc93224ff6550 100644 (file)
@@ -29,6 +29,7 @@ import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
+import org.opendaylight.bgpcep.tcpmd5.KeyMapping;
 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
 import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction;
 import org.opendaylight.controller.sal.binding.api.data.DataProviderService;
@@ -159,7 +160,7 @@ public class ParserToSalTest {
 
                Mockito.doReturn(GlobalEventExecutor.INSTANCE.newSucceededFuture(null)).when(this.dispatcher).
                createReconnectingClient(Mockito.any(InetSocketAddress.class), Mockito.any(BGPSessionPreferences.class), Mockito.any(AsNumber.class),
-                               Mockito.any(BGPSessionListener.class), Mockito.eq(this.tcpStrategyFactory), Mockito.eq(this.sessionStrategy));
+                               Mockito.any(BGPSessionListener.class), Mockito.eq(this.tcpStrategyFactory), Mockito.eq(this.sessionStrategy), Mockito.any(KeyMapping.class));
 
                this.ext = new SimpleRIBExtensionProviderContext();
                this.baseact = new RIBActivator();
@@ -178,7 +179,7 @@ public class ParserToSalTest {
        private void runTestWithTables(final List<BgpTableType> tables) {
                final RIBImpl rib = new RIBImpl(new RibId("testRib"), new AsNumber(72L), new Ipv4Address("127.0.0.1"), this.ext,
                                this.dispatcher, this.tcpStrategyFactory, this.sessionStrategy, this.providerService, tables);
-               final BGPPeer peer = new BGPPeer("peer-" + this.mock.toString(), null, null, rib.getLocalAs(), rib);
+               final BGPPeer peer = new BGPPeer("peer-" + this.mock.toString(), null, null, null, rib.getLocalAs(), rib);
 
                ListenerRegistration<?> reg = this.mock.registerUpdateListener(peer);
                reg.close();