da62bbecd5
Fixes bug 1048681 This commit fixes a bug that quantum agent using a namespace does not work with NEC plugin. NEC plugin uses an Open vSwitch as an OpenFlow switch, but an OVS port created by ovs-vsctl add-port becomes down when the port is moved to some network namespace. Usual OpenFlow controllers respect the OpenFlow port status. Since DHCP server (dnsmasq) is connected to the down port, DHCP server disappears from the virtual network. This behavior can be avoided if we use a similar approach used in BridgeInterfaceDriver, i.e., we first create a veth pair, add one veth device to OVS and move the other veth device to a network namespace. To accomplish it this patch introduces a new interface driver OVSVethInterfaceDriver. In addition, this patch adds a 'prefix' paramter to unplug(). When deleting a veth pair, a veth device added to OVS bridge is not removed automatically. Thus we need a mean to know the veth device name from a device_name to be removed. It is the reason to add 'prefix' parameter to unplug(). Change-Id: I3f4eae371a27a3171a6a8c9e38ddc9354ed1b6b0
517 lines
22 KiB
Python
517 lines
22 KiB
Python
"""
|
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
#
|
|
# Copyright 2012 Nicira Networks, Inc. All rights reserved.
|
|
#
|
|
# Licensed 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.
|
|
#
|
|
# @author: Dan Wendlandt, Nicira, Inc
|
|
#
|
|
"""
|
|
|
|
import logging
|
|
import sys
|
|
import time
|
|
|
|
import netaddr
|
|
|
|
from quantum.agent.common import config
|
|
from quantum.agent.linux import interface
|
|
from quantum.agent.linux import ip_lib
|
|
from quantum.agent.linux import iptables_manager
|
|
from quantum.agent.linux import utils
|
|
from quantum.db import l3_db
|
|
from quantum.openstack.common import cfg
|
|
from quantum.openstack.common import importutils
|
|
from quantumclient.v2_0 import client
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
NS_PREFIX = 'qrouter-'
|
|
INTERNAL_DEV_PREFIX = 'qr-'
|
|
EXTERNAL_DEV_PREFIX = 'qg-'
|
|
|
|
|
|
class RouterInfo(object):
|
|
|
|
def __init__(self, router_id, root_helper, use_namespaces):
|
|
self.router_id = router_id
|
|
self.ex_gw_port = None
|
|
self.internal_ports = []
|
|
self.floating_ips = []
|
|
self.root_helper = root_helper
|
|
self.use_namespaces = use_namespaces
|
|
|
|
self.iptables_manager = iptables_manager.IptablesManager(
|
|
root_helper=root_helper,
|
|
#FIXME(danwent): use_ipv6=True,
|
|
namespace=self.ns_name())
|
|
|
|
def ns_name(self):
|
|
if self.use_namespaces:
|
|
return NS_PREFIX + self.router_id
|
|
|
|
|
|
class L3NATAgent(object):
|
|
|
|
OPTS = [
|
|
cfg.StrOpt('admin_user'),
|
|
cfg.StrOpt('admin_password'),
|
|
cfg.StrOpt('admin_tenant_name'),
|
|
cfg.StrOpt('auth_url'),
|
|
cfg.StrOpt('auth_strategy', default='keystone'),
|
|
cfg.StrOpt('auth_region'),
|
|
cfg.StrOpt('root_helper', default='sudo'),
|
|
cfg.StrOpt('external_network_bridge', default='br-ex',
|
|
help="Name of bridge used for external network traffic."),
|
|
cfg.StrOpt('interface_driver',
|
|
help="The driver used to manage the virtual interface."),
|
|
cfg.IntOpt('polling_interval',
|
|
default=3,
|
|
help="The time in seconds between state poll requests."),
|
|
cfg.StrOpt('metadata_ip', default='',
|
|
help="IP address used by Nova metadata server."),
|
|
cfg.IntOpt('metadata_port',
|
|
default=8775,
|
|
help="TCP Port used by Nova metadata server."),
|
|
#FIXME(danwent): not currently used
|
|
cfg.BoolOpt('send_arp_for_ha',
|
|
default=True,
|
|
help="Send gratuitious ARP when router IP is configured"),
|
|
cfg.BoolOpt('use_namespaces', default=True,
|
|
help="Allow overlapping IP."),
|
|
cfg.StrOpt('router_id', default='',
|
|
help="If namespaces is disabled, the l3 agent can only"
|
|
" confgure a router that has the matching router ID."),
|
|
cfg.BoolOpt('handle_internal_only_routers',
|
|
default=True,
|
|
help="Agent should implement routers with no gateway"),
|
|
cfg.StrOpt('gateway_external_network_id', default='',
|
|
help="UUID of external network for routers implemented "
|
|
"by the agents."),
|
|
]
|
|
|
|
def __init__(self, conf):
|
|
self.conf = conf
|
|
self.router_info = {}
|
|
|
|
if not conf.interface_driver:
|
|
LOG.error(_('You must specify an interface driver'))
|
|
sys.exit(1)
|
|
try:
|
|
self.driver = importutils.import_object(conf.interface_driver,
|
|
conf)
|
|
except:
|
|
LOG.exception(_("Error importing interface driver '%s'"
|
|
% conf.interface_driver))
|
|
sys.exit(1)
|
|
|
|
self.polling_interval = conf.polling_interval
|
|
|
|
if (self.conf.external_network_bridge and
|
|
not ip_lib.device_exists(self.conf.external_network_bridge)):
|
|
raise Exception("external network bridge '%s' does not exist"
|
|
% self.conf.external_network_bridge)
|
|
|
|
self.qclient = client.Client(
|
|
username=self.conf.admin_user,
|
|
password=self.conf.admin_password,
|
|
tenant_name=self.conf.admin_tenant_name,
|
|
auth_url=self.conf.auth_url,
|
|
auth_strategy=self.conf.auth_strategy,
|
|
auth_region=self.conf.auth_region
|
|
)
|
|
|
|
self._destroy_all_router_namespaces()
|
|
|
|
def _destroy_all_router_namespaces(self):
|
|
"""Destroy all router namespaces on the host to eliminate
|
|
all stale linux devices, iptables rules, and namespaces.
|
|
"""
|
|
root_ip = ip_lib.IPWrapper(self.conf.root_helper)
|
|
for ns in root_ip.get_namespaces(self.conf.root_helper):
|
|
if ns.startswith(NS_PREFIX):
|
|
try:
|
|
self._destroy_router_namespace(ns)
|
|
except:
|
|
LOG.exception("couldn't delete namespace '%s'" % ns)
|
|
|
|
def _destroy_router_namespace(self, namespace):
|
|
ns_ip = ip_lib.IPWrapper(self.conf.root_helper,
|
|
namespace=namespace)
|
|
for d in ns_ip.get_devices(exclude_loopback=True):
|
|
if d.name.startswith(INTERNAL_DEV_PREFIX):
|
|
# device is on default bridge
|
|
self.driver.unplug(d.name, namespace=namespace,
|
|
prefix=INTERNAL_DEV_PREFIX)
|
|
elif d.name.startswith(EXTERNAL_DEV_PREFIX):
|
|
self.driver.unplug(d.name,
|
|
bridge=self.conf.external_network_bridge,
|
|
namespace=namespace,
|
|
prefix=EXTERNAL_DEV_PREFIX)
|
|
#(TODO) Address the failure for the deletion of the namespace
|
|
|
|
def _create_router_namespace(self, ri):
|
|
ip_wrapper_root = ip_lib.IPWrapper(self.conf.root_helper)
|
|
ip_wrapper = ip_wrapper_root.ensure_namespace(ri.ns_name())
|
|
ip_wrapper.netns.execute(['sysctl', '-w', 'net.ipv4.ip_forward=1'])
|
|
|
|
def daemon_loop(self):
|
|
#TODO(danwent): this simple diff logic does not handle if
|
|
# details of a router port (e.g., IP, mac) are changed behind
|
|
# our back. Will fix this properly with update notifications.
|
|
|
|
while True:
|
|
try:
|
|
self.do_single_loop()
|
|
except:
|
|
LOG.exception("Error running l3_nat daemon_loop")
|
|
|
|
time.sleep(self.polling_interval)
|
|
|
|
def _fetch_external_net_id(self):
|
|
"""Find UUID of single external network for this agent"""
|
|
if self.conf.gateway_external_network_id:
|
|
return self.conf.gateway_external_network_id
|
|
|
|
params = {'router:external': True}
|
|
ex_nets = self.qclient.list_networks(**params)['networks']
|
|
if len(ex_nets) > 1:
|
|
raise Exception("must configure 'external_network_id' if "
|
|
"Quantum has more than one external network.")
|
|
if len(ex_nets) == 0:
|
|
return None
|
|
return ex_nets[0]['id']
|
|
|
|
def do_single_loop(self):
|
|
prev_router_ids = set(self.router_info)
|
|
cur_router_ids = set()
|
|
|
|
target_ex_net_id = self._fetch_external_net_id()
|
|
|
|
# identify and update new or modified routers
|
|
for r in self.qclient.list_routers()['routers']:
|
|
if not r['admin_state_up']:
|
|
continue
|
|
|
|
ex_net_id = (r['external_gateway_info'] and
|
|
r['external_gateway_info'].get('network_id'))
|
|
if not ex_net_id and not self.conf.handle_internal_only_routers:
|
|
continue
|
|
|
|
if ex_net_id and ex_net_id != target_ex_net_id:
|
|
continue
|
|
|
|
# If namespaces are disabled, only process the router associated
|
|
# with the configured agent id.
|
|
if (self.conf.use_namespaces or
|
|
r['id'] == self.conf.router_id):
|
|
cur_router_ids.add(r['id'])
|
|
else:
|
|
continue
|
|
if r['id'] not in self.router_info:
|
|
self.router_info[r['id']] = RouterInfo(
|
|
r['id'], self.conf.root_helper, self.conf.use_namespaces)
|
|
if self.conf.use_namespaces:
|
|
self._create_router_namespace(self.router_info[r['id']])
|
|
|
|
ri = self.router_info[r['id']]
|
|
self.process_router(ri)
|
|
|
|
# identify and remove routers that no longer exist
|
|
for router_id in prev_router_ids - cur_router_ids:
|
|
ri = self.router_info[router_id]
|
|
del self.router_info[router_id]
|
|
self._destroy_router_namespace(ri.ns_name())
|
|
prev_router_ids = cur_router_ids
|
|
|
|
def _set_subnet_info(self, port):
|
|
ips = port['fixed_ips']
|
|
if not ips:
|
|
raise Exception("Router port %s has no IP address" % port['id'])
|
|
if len(ips) > 1:
|
|
LOG.error("Ignoring multiple IPs on router port %s" % port['id'])
|
|
port['subnet'] = self.qclient.show_subnet(
|
|
ips[0]['subnet_id'])['subnet']
|
|
prefixlen = netaddr.IPNetwork(port['subnet']['cidr']).prefixlen
|
|
port['ip_cidr'] = "%s/%s" % (ips[0]['ip_address'], prefixlen)
|
|
|
|
def process_router(self, ri):
|
|
|
|
ex_gw_port = self._get_ex_gw_port(ri)
|
|
|
|
internal_ports = self.qclient.list_ports(
|
|
device_id=ri.router_id,
|
|
device_owner=l3_db.DEVICE_OWNER_ROUTER_INTF)['ports']
|
|
|
|
existing_port_ids = set([p['id'] for p in ri.internal_ports])
|
|
current_port_ids = set([p['id'] for p in internal_ports
|
|
if p['admin_state_up']])
|
|
new_ports = [p for p in internal_ports if
|
|
p['id'] in current_port_ids and
|
|
p['id'] not in existing_port_ids]
|
|
old_ports = [p for p in ri.internal_ports if
|
|
p['id'] not in current_port_ids]
|
|
|
|
for p in new_ports:
|
|
self._set_subnet_info(p)
|
|
ri.internal_ports.append(p)
|
|
self.internal_network_added(ri, ex_gw_port,
|
|
p['network_id'], p['id'],
|
|
p['ip_cidr'], p['mac_address'])
|
|
|
|
for p in old_ports:
|
|
ri.internal_ports.remove(p)
|
|
self.internal_network_removed(ri, ex_gw_port, p['id'],
|
|
p['ip_cidr'])
|
|
|
|
internal_cidrs = [p['ip_cidr'] for p in ri.internal_ports]
|
|
|
|
if ex_gw_port and not ri.ex_gw_port:
|
|
self._set_subnet_info(ex_gw_port)
|
|
self.external_gateway_added(ri, ex_gw_port, internal_cidrs)
|
|
elif not ex_gw_port and ri.ex_gw_port:
|
|
self.external_gateway_removed(ri, ri.ex_gw_port,
|
|
internal_cidrs)
|
|
|
|
if ri.ex_gw_port or ex_gw_port:
|
|
self.process_router_floating_ips(ri, ex_gw_port)
|
|
|
|
ri.ex_gw_port = ex_gw_port
|
|
|
|
def process_router_floating_ips(self, ri, ex_gw_port):
|
|
floating_ips = self.qclient.list_floatingips(
|
|
router_id=ri.router_id)['floatingips']
|
|
existing_floating_ip_ids = set([fip['id'] for fip in ri.floating_ips])
|
|
cur_floating_ip_ids = set([fip['id'] for fip in floating_ips])
|
|
|
|
id_to_fixed_map = {}
|
|
|
|
for fip in floating_ips:
|
|
if fip['port_id']:
|
|
if fip['id'] not in existing_floating_ip_ids:
|
|
ri.floating_ips.append(fip)
|
|
self.floating_ip_added(ri, ex_gw_port,
|
|
fip['floating_ip_address'],
|
|
fip['fixed_ip_address'])
|
|
|
|
# store to see if floatingip was remapped
|
|
id_to_fixed_map[fip['id']] = fip['fixed_ip_address']
|
|
|
|
floating_ip_ids_to_remove = (existing_floating_ip_ids -
|
|
cur_floating_ip_ids)
|
|
for fip in ri.floating_ips:
|
|
if fip['id'] in floating_ip_ids_to_remove:
|
|
ri.floating_ips.remove(fip)
|
|
self.floating_ip_removed(ri, ri.ex_gw_port,
|
|
fip['floating_ip_address'],
|
|
fip['fixed_ip_address'])
|
|
else:
|
|
# handle remapping of a floating IP
|
|
cur_fixed_ip = id_to_fixed_map[fip['id']]
|
|
existing_fixed_ip = fip['fixed_ip_address']
|
|
if (cur_fixed_ip and existing_fixed_ip and
|
|
cur_fixed_ip != existing_fixed_ip):
|
|
floating_ip = fip['floating_ip_address']
|
|
self.floating_ip_removed(ri, ri.ex_gw_port,
|
|
floating_ip, existing_fixed_ip)
|
|
self.floating_ip_added(ri, ri.ex_gw_port,
|
|
floating_ip, cur_fixed_ip)
|
|
|
|
def _get_ex_gw_port(self, ri):
|
|
ports = self.qclient.list_ports(
|
|
device_id=ri.router_id,
|
|
device_owner=l3_db.DEVICE_OWNER_ROUTER_GW)['ports']
|
|
if not ports:
|
|
return None
|
|
elif len(ports) == 1:
|
|
return ports[0]
|
|
else:
|
|
LOG.error("Ignoring multiple gateway ports for router %s"
|
|
% ri.router_id)
|
|
|
|
def get_internal_device_name(self, port_id):
|
|
return (INTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
|
|
|
|
def get_external_device_name(self, port_id):
|
|
return (EXTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
|
|
|
|
def external_gateway_added(self, ri, ex_gw_port, internal_cidrs):
|
|
|
|
interface_name = self.get_external_device_name(ex_gw_port['id'])
|
|
ex_gw_ip = ex_gw_port['fixed_ips'][0]['ip_address']
|
|
if not ip_lib.device_exists(interface_name,
|
|
root_helper=self.conf.root_helper,
|
|
namespace=ri.ns_name()):
|
|
self.driver.plug(ex_gw_port['network_id'],
|
|
ex_gw_port['id'], interface_name,
|
|
ex_gw_port['mac_address'],
|
|
bridge=self.conf.external_network_bridge,
|
|
namespace=ri.ns_name(),
|
|
prefix=EXTERNAL_DEV_PREFIX)
|
|
self.driver.init_l3(interface_name, [ex_gw_port['ip_cidr']],
|
|
namespace=ri.ns_name())
|
|
|
|
gw_ip = ex_gw_port['subnet']['gateway_ip']
|
|
if ex_gw_port['subnet']['gateway_ip']:
|
|
cmd = ['route', 'add', 'default', 'gw', gw_ip]
|
|
if self.conf.use_namespaces:
|
|
ip_wrapper = ip_lib.IPWrapper(self.conf.root_helper,
|
|
namespace=ri.ns_name())
|
|
ip_wrapper.netns.execute(cmd, check_exit_code=False)
|
|
else:
|
|
utils.execute(cmd, check_exit_code=False,
|
|
root_helper=self.conf.root_helper)
|
|
|
|
for (c, r) in self.external_gateway_filter_rules():
|
|
ri.iptables_manager.ipv4['filter'].add_rule(c, r)
|
|
for (c, r) in self.external_gateway_nat_rules(ex_gw_ip,
|
|
internal_cidrs,
|
|
interface_name):
|
|
ri.iptables_manager.ipv4['nat'].add_rule(c, r)
|
|
ri.iptables_manager.apply()
|
|
|
|
def external_gateway_removed(self, ri, ex_gw_port, internal_cidrs):
|
|
|
|
interface_name = self.get_external_device_name(ex_gw_port['id'])
|
|
if ip_lib.device_exists(interface_name,
|
|
root_helper=self.conf.root_helper,
|
|
namespace=ri.ns_name()):
|
|
self.driver.unplug(interface_name,
|
|
bridge=self.conf.external_network_bridge,
|
|
namespace=ri.ns_name(),
|
|
prefix=EXTERNAL_DEV_PREFIX)
|
|
|
|
ex_gw_ip = ex_gw_port['fixed_ips'][0]['ip_address']
|
|
for c, r in self.external_gateway_filter_rules():
|
|
ri.iptables_manager.ipv4['filter'].remove_rule(c, r)
|
|
for c, r in self.external_gateway_nat_rules(ex_gw_ip, internal_cidrs,
|
|
interface_name):
|
|
ri.iptables_manager.ipv4['nat'].remove_rule(c, r)
|
|
ri.iptables_manager.apply()
|
|
|
|
def external_gateway_filter_rules(self):
|
|
rules = []
|
|
if self.conf.metadata_ip:
|
|
rules.append(('INPUT', '-s 0.0.0.0/0 -d %s '
|
|
'-p tcp -m tcp --dport %s '
|
|
'-j ACCEPT' %
|
|
(self.conf.metadata_ip, self.conf.metadata_port)))
|
|
return rules
|
|
|
|
def external_gateway_nat_rules(self, ex_gw_ip, internal_cidrs,
|
|
interface_name):
|
|
rules = [('POSTROUTING', '! -i %(interface_name)s '
|
|
'! -o %(interface_name)s -m conntrack ! '
|
|
'--ctstate DNAT -j ACCEPT' % locals())]
|
|
if self.conf.metadata_ip:
|
|
rules.append(('PREROUTING', '-s 0.0.0.0/0 -d 169.254.169.254/32 '
|
|
'-p tcp -m tcp --dport 80 -j DNAT '
|
|
'--to-destination %s:%s' %
|
|
(self.conf.metadata_ip, self.conf.metadata_port)))
|
|
for cidr in internal_cidrs:
|
|
rules.extend(self.internal_network_nat_rules(ex_gw_ip, cidr))
|
|
return rules
|
|
|
|
def internal_network_added(self, ri, ex_gw_port, network_id, port_id,
|
|
internal_cidr, mac_address):
|
|
interface_name = self.get_internal_device_name(port_id)
|
|
if not ip_lib.device_exists(interface_name,
|
|
root_helper=self.conf.root_helper,
|
|
namespace=ri.ns_name()):
|
|
self.driver.plug(network_id, port_id, interface_name, mac_address,
|
|
namespace=ri.ns_name(),
|
|
prefix=INTERNAL_DEV_PREFIX)
|
|
|
|
self.driver.init_l3(interface_name, [internal_cidr],
|
|
namespace=ri.ns_name())
|
|
|
|
if ex_gw_port:
|
|
ex_gw_ip = ex_gw_port['fixed_ips'][0]['ip_address']
|
|
for c, r in self.internal_network_nat_rules(ex_gw_ip,
|
|
internal_cidr):
|
|
ri.iptables_manager.ipv4['nat'].add_rule(c, r)
|
|
ri.iptables_manager.apply()
|
|
|
|
def internal_network_removed(self, ri, ex_gw_port, port_id, internal_cidr):
|
|
interface_name = self.get_internal_device_name(port_id)
|
|
if ip_lib.device_exists(interface_name,
|
|
root_helper=self.conf.root_helper,
|
|
namespace=ri.ns_name()):
|
|
self.driver.unplug(interface_name, namespace=ri.ns_name(),
|
|
prefix=INTERNAL_DEV_PREFIX)
|
|
|
|
if ex_gw_port:
|
|
ex_gw_ip = ex_gw_port['fixed_ips'][0]['ip_address']
|
|
for c, r in self.internal_network_nat_rules(ex_gw_ip,
|
|
internal_cidr):
|
|
ri.iptables_manager.ipv4['nat'].remove_rule(c, r)
|
|
ri.iptables_manager.apply()
|
|
|
|
def internal_network_nat_rules(self, ex_gw_ip, internal_cidr):
|
|
rules = [('snat', '-s %s -j SNAT --to-source %s' %
|
|
(internal_cidr, ex_gw_ip))]
|
|
if self.conf.metadata_ip:
|
|
rules.append(('POSTROUTING', '-s %s -d %s/32 -j ACCEPT' %
|
|
(internal_cidr, self.conf.metadata_ip)))
|
|
return rules
|
|
|
|
def floating_ip_added(self, ri, ex_gw_port, floating_ip, fixed_ip):
|
|
ip_cidr = str(floating_ip) + '/32'
|
|
interface_name = self.get_external_device_name(ex_gw_port['id'])
|
|
device = ip_lib.IPDevice(interface_name, self.conf.root_helper,
|
|
namespace=ri.ns_name())
|
|
|
|
if not ip_cidr in [addr['cidr'] for addr in device.addr.list()]:
|
|
net = netaddr.IPNetwork(ip_cidr)
|
|
device.addr.add(net.version, ip_cidr, str(net.broadcast))
|
|
|
|
for chain, rule in self.floating_forward_rules(floating_ip, fixed_ip):
|
|
ri.iptables_manager.ipv4['nat'].add_rule(chain, rule)
|
|
ri.iptables_manager.apply()
|
|
|
|
def floating_ip_removed(self, ri, ex_gw_port, floating_ip, fixed_ip):
|
|
ip_cidr = str(floating_ip) + '/32'
|
|
net = netaddr.IPNetwork(ip_cidr)
|
|
interface_name = self.get_external_device_name(ex_gw_port['id'])
|
|
|
|
device = ip_lib.IPDevice(interface_name, self.conf.root_helper,
|
|
namespace=ri.ns_name())
|
|
device.addr.delete(net.version, ip_cidr)
|
|
|
|
for chain, rule in self.floating_forward_rules(floating_ip, fixed_ip):
|
|
ri.iptables_manager.ipv4['nat'].remove_rule(chain, rule)
|
|
ri.iptables_manager.apply()
|
|
|
|
def floating_forward_rules(self, floating_ip, fixed_ip):
|
|
return [('PREROUTING', '-d %s -j DNAT --to %s' %
|
|
(floating_ip, fixed_ip)),
|
|
('OUTPUT', '-d %s -j DNAT --to %s' %
|
|
(floating_ip, fixed_ip)),
|
|
('float-snat', '-s %s -j SNAT --to %s' %
|
|
(fixed_ip, floating_ip))]
|
|
|
|
|
|
def main():
|
|
conf = config.setup_conf()
|
|
conf.register_opts(L3NATAgent.OPTS)
|
|
conf.register_opts(interface.OPTS)
|
|
conf(sys.argv)
|
|
config.setup_logging(conf)
|
|
|
|
mgr = L3NATAgent(conf)
|
|
mgr.daemon_loop()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|