[ 
https://issues.apache.org/jira/browse/CLOUDSTACK-9339?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15439991#comment-15439991
 ] 

ASF GitHub Bot commented on CLOUDSTACK-9339:
--------------------------------------------

Github user jburwell commented on a diff in the pull request:

    https://github.com/apache/cloudstack/pull/1659#discussion_r76494735
  
    --- Diff: test/integration/smoke/test_multiple_public_ip_ranges.py ---
    @@ -0,0 +1,887 @@
    +# Licensed to the Apache Software Foundation (ASF) under one
    +# or more contributor license agreements.  See the NOTICE file
    +# distributed with this work for additional information
    +# regarding copyright ownership.  The ASF licenses this file
    +# to you under the Apache License, Version 2.0 (the
    +# "License"); you may not use this file except in compliance
    +# with the License.  You may obtain a copy of the License at
    +#
    +#   http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing,
    +# software distributed under the License is distributed on an
    +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    +# KIND, either express or implied.  See the License for the
    +# specific language governing permissions and limitations
    +# under the License.
    +""" BVT tests for network services on public IP's from different public IP
    +  range than that of associated source NAT IP of the network. Each IP 
associated
    +  with network from a different public IP range results in a new public
    +  interface on VR (eth3, eth4 etc) and iptable
    +"""
    +# Import Local Modules
    +from marvin.codes import (FAILED, STATIC_NAT_RULE, LB_RULE,
    +                          NAT_RULE, PASS)
    +from marvin.cloudstackTestCase import cloudstackTestCase
    +from marvin.cloudstackException import CloudstackAPIException
    +from marvin.cloudstackAPI import rebootRouter
    +from marvin.sshClient import SshClient
    +from marvin.lib.utils import cleanup_resources, get_process_status
    +from marvin.lib.base import (Account,
    +                             VirtualMachine,
    +                             ServiceOffering,
    +                             NATRule,
    +                             PublicIPAddress,
    +                             StaticNATRule,
    +                             FireWallRule,
    +                             Network,
    +                             NetworkOffering,
    +                             LoadBalancerRule,
    +                             PublicIpRange,
    +                             Router,
    +                             VpcOffering,
    +                             VPC,
    +                             NetworkACL)
    +from marvin.lib.common import (get_domain,
    +                               get_zone,
    +                               get_template,
    +                               list_hosts,
    +                               list_publicIP,
    +                               list_nat_rules,
    +                               list_routers,
    +                               list_virtual_machines,
    +                               list_lb_rules,
    +                               list_configurations,
    +                               verifyGuestTrafficPortGroups)
    +from nose.plugins.attrib import attr
    +from ddt import ddt, data
    +# Import System modules
    +import socket
    +import time
    +import logging
    +
    +_multiprocess_shared_ = True
    +
    +logger = logging.getLogger('TestNetworkOps')
    +stream_handler = logging.StreamHandler()
    +logger.setLevel(logging.DEBUG)
    +logger.addHandler(stream_handler)
    +
    +class TestPortForwarding(cloudstackTestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +
    +        testClient = super(TestPortForwarding, cls).getClsTestClient()
    +        cls.apiclient = testClient.getApiClient()
    +        cls.services = testClient.getParsedTestDataConfig()
    +        cls.hypervisor = testClient.getHypervisorInfo()
    +        # Get Zone, Domain and templates
    +        cls.domain = get_domain(cls.apiclient)
    +        cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())
    +        cls.services["virtual_machine"]["zoneid"] = cls.zone.id
    +        cls.services["zoneid"] = cls.zone.id
    +        template = get_template(
    +            cls.apiclient,
    +            cls.zone.id,
    +            cls.services["ostype"]
    +        )
    +        if template == FAILED:
    +            assert False, "get_template() failed to return template with 
description %s" % cls.services[
    +                "ostype"]
    +
    +        # Create an account, network, VM and IP addresses
    +        cls.account = Account.create(
    +            cls.apiclient,
    +            cls.services["account"],
    +            admin=True,
    +            domainid=cls.domain.id
    +        )
    +        cls.services["publiciprange"]["zoneid"] = cls.zone.id
    +        cls.service_offering = ServiceOffering.create(
    +            cls.apiclient,
    +            cls.services["service_offerings"]["tiny"]
    +        )
    +        cls.virtual_machine = VirtualMachine.create(
    +            cls.apiclient,
    +            cls.services["virtual_machine"],
    +            templateid=template.id,
    +            accountid=cls.account.name,
    +            domainid=cls.account.domainid,
    +            serviceofferingid=cls.service_offering.id
    +        )
    +        cls._cleanup = [
    +            cls.virtual_machine,
    +            cls.account,
    +            cls.service_offering
    +        ]
    +
    +    def setUp(self):
    +        self.apiclient = self.testClient.getApiClient()
    +        self.cleanup = []
    +        return
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        try:
    +            cls.apiclient = super(
    +                TestPortForwarding,
    +                cls).getClsTestClient().getApiClient()
    +            cleanup_resources(cls.apiclient, cls._cleanup)
    +        except Exception as e:
    +            raise Exception("Warning: Exception during cleanup : %s" % e)
    +
    +    def tearDown(self):
    +        cleanup_resources(self.apiclient, self.cleanup)
    +        return
    +
    +    @attr(tags=["advanced", "smoke"], required_hardware="true")
    +    def test_port_forwarding_on_ip_from_non_src_nat_ip_range(self):
    +        """Test for port forwarding on a IP which is in pubic IP range 
different
    +           from public IP range that has source NAT IP associated with 
network
    +        """
    +
    +        # Validate the following:
    +        # 1. Create a new public IP range and dedicate to a account
    +        # 2. Acquire a IP from new public range
    +        # 3. create a port forwarding on acquired IP from new range
    +        # 4. Create a firewall rule to open up the port
    +        # 5. Test SSH works to the VM
    +
    +        self.public_ip_range = PublicIpRange.create(
    +                                    self.apiclient,
    +                                    self.services["publiciprange"]
    +                               )
    +
    +        self.debug("Dedicating Public IP range to the account");
    +        dedicate_public_ip_range_response = PublicIpRange.dedicate(
    +                                                self.apiclient,
    +                                                
self.public_ip_range.vlan.id,
    +                                                account=self.account.name,
    +                                                
domainid=self.account.domainid
    +                                            )
    +        ip_address = PublicIPAddress.create(
    +            self.apiclient,
    +            self.account.name,
    +            self.zone.id,
    +            self.account.domainid,
    +            self.services["virtual_machine"]
    +        )
    +        self.cleanup.append(ip_address)
    +        self.cleanup.append(self.public_ip_range)
    +        # Check if VM is in Running state before creating NAT and firewall 
rules
    +        vm_response = VirtualMachine.list(
    +            self.apiclient,
    +            id=self.virtual_machine.id
    +        )
    +
    +        self.assertEqual(
    +            isinstance(vm_response, list),
    +            True,
    +            "Check list VM returns a valid list"
    +        )
    +
    +        self.assertNotEqual(
    +            len(vm_response),
    +            0,
    +            "Check Port Forwarding Rule is created"
    +        )
    +        self.assertEqual(
    +            vm_response[0].state,
    +            'Running',
    +            "VM state should be Running before creating a NAT rule."
    +        )
    +
    +        # Open up firewall port for SSH
    +        FireWallRule.create(
    +            self.apiclient,
    +            ipaddressid=ip_address.ipaddress.id,
    +            protocol=self.services["natrule"]["protocol"],
    +            cidrlist=['0.0.0.0/0'],
    +            startport=self.services["natrule"]["publicport"],
    +            endport=self.services["natrule"]["publicport"]
    +        )
    +
    +        # Create PF rule
    +        nat_rule = NATRule.create(
    +            self.apiclient,
    +            self.virtual_machine,
    +            self.services["natrule"],
    +            ip_address.ipaddress.id
    +        )
    +
    +        try:
    +            logger.debug("SSHing into VM with IP address %s with NAT IP 
%s" %
    +                       (
    +                           self.virtual_machine.ipaddress,
    +                           ip_address.ipaddress.ipaddress
    +                       ))
    +            
self.virtual_machine.get_ssh_client(ip_address.ipaddress.ipaddress)
    +        except Exception as e:
    +            self.fail(
    +                "SSH Access failed for %s: %s" %
    +                (self.virtual_machine.ipaddress, e)
    +            )
    +
    +        nat_rule.delete(self.apiclient)
    +
    +class TestStaticNat(cloudstackTestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +
    +        testClient = super(TestStaticNat, cls).getClsTestClient()
    +        cls.apiclient = testClient.getApiClient()
    +        cls.services = testClient.getParsedTestDataConfig()
    +        cls.hypervisor = testClient.getHypervisorInfo()
    +        # Get Zone, Domain and templates
    +        cls.domain = get_domain(cls.apiclient)
    +        cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())
    +        cls.services["virtual_machine"]["zoneid"] = cls.zone.id
    +        cls.services["zoneid"] = cls.zone.id
    +        template = get_template(
    +            cls.apiclient,
    +            cls.zone.id,
    +            cls.services["ostype"]
    +        )
    +        if template == FAILED:
    +            assert False, "get_template() failed to return template with 
description %s" % cls.services[
    +                "ostype"]
    +
    +        # Create an account, network, VM and IP addresses
    +        cls.account = Account.create(
    +            cls.apiclient,
    +            cls.services["account"],
    +            admin=True,
    +            domainid=cls.domain.id
    +        )
    +        cls.services["publiciprange"]["zoneid"] = cls.zone.id
    +        cls.service_offering = ServiceOffering.create(
    +            cls.apiclient,
    +            cls.services["service_offerings"]["tiny"]
    +        )
    +        cls.virtual_machine = VirtualMachine.create(
    +            cls.apiclient,
    +            cls.services["virtual_machine"],
    +            templateid=template.id,
    +            accountid=cls.account.name,
    +            domainid=cls.account.domainid,
    +            serviceofferingid=cls.service_offering.id
    +        )
    +        cls.defaultNetworkId = cls.virtual_machine.nic[0].networkid
    +        cls._cleanup = [
    +            cls.virtual_machine,
    +            cls.account,
    +            cls.service_offering
    +        ]
    +
    +    def setUp(self):
    +        self.apiclient = self.testClient.getApiClient()
    +        self.cleanup = []
    +        return
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        try:
    +            cls.apiclient = super(
    +                TestStaticNat,
    +                cls).getClsTestClient().getApiClient()
    +            cleanup_resources(cls.apiclient, cls._cleanup)
    +        except Exception as e:
    +            raise Exception("Warning: Exception during cleanup : %s" % e)
    +
    +    def tearDown(self):
    +        cleanup_resources(self.apiclient, self.cleanup)
    +        return
    +
    +    @attr(tags=["advanced", "smoke"], required_hardware="true")
    +    def test_static_nat_on_ip_from_non_src_nat_ip_range(self):
    +        """Test for static nat on a IP which is in pubic IP range different
    +           from public IP range that has source NAT IP associated with 
network
    +        """
    +
    +        # Validate the following:
    +        # 1. Create a new public IP range and dedicate to a account
    +        # 2. Acquire a IP from new public range
    +        # 3. Enable static NAT on acquired IP from new range
    +        # 4. Create a firewall rule to open up the port
    +        # 5. Test SSH works to the VM
    +
    +        self.public_ip_range = PublicIpRange.create(
    +                                    self.apiclient,
    +                                    self.services["publiciprange"]
    +                               )
    +
    +        self.debug("Dedicating Public IP range to the account");
    +        dedicate_public_ip_range_response = PublicIpRange.dedicate(
    +                                                self.apiclient,
    +                                                
self.public_ip_range.vlan.id,
    +                                                account=self.account.name,
    +                                                
domainid=self.account.domainid
    +                                            )
    +        ip_address = PublicIPAddress.create(
    +            self.apiclient,
    +            self.account.name,
    +            self.zone.id,
    +            self.account.domainid,
    +            self.services["virtual_machine"]
    +        )
    +        self.cleanup.append(ip_address)
    +        self.cleanup.append(self.public_ip_range)
    --- End diff --
    
    Please see previous notes regarding extracting the dedication/creation of 
public IP ranges to private utility method.


> Virtual Routers don't handle Multiple Public Interfaces
> -------------------------------------------------------
>
>                 Key: CLOUDSTACK-9339
>                 URL: https://issues.apache.org/jira/browse/CLOUDSTACK-9339
>             Project: CloudStack
>          Issue Type: Bug
>      Security Level: Public(Anyone can view this level - this is the 
> default.) 
>          Components: Virtual Router
>    Affects Versions: 4.8.0
>            Reporter: dsclose
>              Labels: firewall, nat, router
>
> There are a series of issues with the way Virtual Routers manage multiple 
> public interfaces. These are more pronounced on redundant virtual router 
> setups. I have not attempted to examine these issues in a VPC context. 
> Outside of a VPC context, however, the following is expected behaviour:
> * eth0 connects the router to the guest network.
> * In RvR setups, keepalived manages the guests' gateway IP as a virtual IP on 
> eth0.
> * eth1 provides a local link to the hypervisor, allowing Cloudstack to issue 
> commands to the router.
> * eth2 is the routers public interface. By default, a single public IP will 
> be setup on eth2 along with the necessary iptables and ip rules to source-NAT 
> guest traffic to that public IP.
> * When a public IP address is assigned to the router that is on a separate 
> subnet to the source-NAT IP, a new interface is configured, such as eth3, and 
> the IP is assigned to that interface.
> * This can result in eth3, eth4, eth5, etc. being created depending upon how 
> many public subnets the router has to work with.
> The above all works. The following, however, is currently not working:
> * Public interfaces should be set to DOWN on backup redundant routers. The 
> master.py script is responsible for setting public interfaces to UP during a 
> keepalived transition. Currently the check_is_up method of the CsIP class 
> brings all interfaces UP on both RvR. A proposed fix for this has been 
> discussed on the mailing list. That fix will leave public interfaces DOWN on 
> RvR allowing the keepalived transition to control the state of public 
> interfaces. Issue #1413 includes a commit that contradicts the proposed fix 
> so it is unclear what the current state of the code should be.
> * Newly created interfaces should be set to UP on master redundant routers. 
> Assuming public interfaces should be default be DOWN on an RvR we need to 
> accommodate the fact that, as interfaces are created, no keepalived 
> transition occurs. This means that assigning an IP from a new public subnet 
> will have no effect (as the interface will be down) until the network is 
> restarted with a "clean up."
> * Public interfaces other than eth2 do not forward traffic. There are two 
> iptables rules in the FORWARD chain of the filter table created for eth2 that 
> allow forwarding between eth2 and eth0. Equivalent rules are not created for 
> other public interfaces so forwarded traffic is dropped.
> * Outbound traffic from guest VMs does not honour static-NAT rules. Instead, 
> outbound traffic is source-NAT'd to the networks default source-NAT IP. New 
> connections from guests that are destined for public networks are processed 
> like so:
> 1. Traffic is matched against the following rule in the mangle table that 
> marks the connection with a 0x0:
> *mangle
> -A PREROUTING -i eth0 -m state --state NEW -j CONNMARK --set-xmark 
> 0x0/0xffffffff
> 2. There are no "ip rule" statements that match a connection marked 0x0, so 
> the kernel routes the connection via the default gateway. That gateway is on 
> source-NAT subnet, so the connection is routed out of eth2.
> 3. The following iptables rules are then matched in the filter table:
> *filter
> -A FORWARD -i eth0 -o eth2 -j FW_OUTBOUND
> -A FW_OUTBOUND -j FW_EGRESS_RULES
> -A FW_EGRESS_RULES -j ACCEPT
> 4. Finally, the following rule is matched from the nat table, where the IP 
> address is the source-NAT IP:
> *nat
> -A POSTROUTING -o eth2 -j SNAT --to-source 123.4.5.67
>  



--
This message was sent by Atlassian JIRA
(v6.3.4#6332)

Reply via email to