OpenStack Networking (Neutron)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

iptables_firewall.py 41KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919
  1. # Copyright 2012, Nachi Ueno, NTT MCL, Inc.
  2. # All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. # not use this file except in compliance with the License. You may obtain
  6. # a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations
  14. # under the License.
  15. import collections
  16. import netaddr
  17. from neutron_lib import constants
  18. from oslo_config import cfg
  19. from oslo_log import log as logging
  20. from oslo_utils import netutils
  21. import six
  22. from neutron._i18n import _, _LI
  23. from neutron.agent import firewall
  24. from neutron.agent.linux import ip_conntrack
  25. from neutron.agent.linux import ipset_manager
  26. from neutron.agent.linux import iptables_comments as ic
  27. from neutron.agent.linux import iptables_manager
  28. from neutron.agent.linux import utils
  29. from neutron.common import constants as n_const
  30. from neutron.common import ipv6_utils
  31. from neutron.common import utils as c_utils
  32. LOG = logging.getLogger(__name__)
  33. SG_CHAIN = 'sg-chain'
  34. SPOOF_FILTER = 'spoof-filter'
  35. CHAIN_NAME_PREFIX = {firewall.INGRESS_DIRECTION: 'i',
  36. firewall.EGRESS_DIRECTION: 'o',
  37. SPOOF_FILTER: 's'}
  38. IPSET_DIRECTION = {firewall.INGRESS_DIRECTION: 'src',
  39. firewall.EGRESS_DIRECTION: 'dst'}
  40. LINUX_DEV_PREFIX_LEN = n_const.LINUX_DEV_PREFIX_LEN
  41. LINUX_DEV_LEN = n_const.LINUX_DEV_LEN
  42. MAX_CONNTRACK_ZONES = ip_conntrack.MAX_CONNTRACK_ZONES
  43. comment_rule = iptables_manager.comment_rule
  44. def get_hybrid_port_name(port_name):
  45. return (constants.TAP_DEVICE_PREFIX + port_name)[:n_const.LINUX_DEV_LEN]
  46. class mac_iptables(netaddr.mac_eui48):
  47. """mac format class for netaddr to match iptables representation."""
  48. word_sep = ':'
  49. class IptablesFirewallDriver(firewall.FirewallDriver):
  50. """Driver which enforces security groups through iptables rules."""
  51. IPTABLES_DIRECTION = {firewall.INGRESS_DIRECTION: 'physdev-out',
  52. firewall.EGRESS_DIRECTION: 'physdev-in'}
  53. CONNTRACK_ZONE_PER_PORT = False
  54. def __init__(self, namespace=None):
  55. self.iptables = iptables_manager.IptablesManager(
  56. state_less=True,
  57. use_ipv6=ipv6_utils.is_enabled(),
  58. namespace=namespace)
  59. # TODO(majopela, shihanzhang): refactor out ipset to a separate
  60. # driver composed over this one
  61. self.ipset = ipset_manager.IpsetManager(namespace=namespace)
  62. # list of port which has security group
  63. self.filtered_ports = {}
  64. self.unfiltered_ports = {}
  65. self.ipconntrack = ip_conntrack.get_conntrack(
  66. self.iptables.get_rules_for_table, self.filtered_ports,
  67. self.unfiltered_ports, namespace=namespace,
  68. zone_per_port=self.CONNTRACK_ZONE_PER_PORT)
  69. self._add_fallback_chain_v4v6()
  70. self._defer_apply = False
  71. self._pre_defer_filtered_ports = None
  72. self._pre_defer_unfiltered_ports = None
  73. # List of security group rules for ports residing on this host
  74. self.sg_rules = {}
  75. self.pre_sg_rules = None
  76. # List of security group member ips for ports residing on this host
  77. self.sg_members = collections.defaultdict(
  78. lambda: collections.defaultdict(list))
  79. self.pre_sg_members = None
  80. self.enable_ipset = cfg.CONF.SECURITYGROUP.enable_ipset
  81. self._enabled_netfilter_for_bridges = False
  82. self.updated_rule_sg_ids = set()
  83. self.updated_sg_members = set()
  84. self.devices_with_updated_sg_members = collections.defaultdict(list)
  85. def _enable_netfilter_for_bridges(self):
  86. # we only need to set these values once, but it has to be when
  87. # we create a bridge; before that the bridge module might not
  88. # be loaded and the proc values aren't there.
  89. if self._enabled_netfilter_for_bridges:
  90. return
  91. else:
  92. self._enabled_netfilter_for_bridges = True
  93. # These proc values ensure that netfilter is enabled on
  94. # bridges; essential for enforcing security groups rules with
  95. # OVS Hybrid. Distributions can differ on whether this is
  96. # enabled by default or not (Ubuntu - yes, Redhat - no, for
  97. # example).
  98. LOG.debug("Enabling netfilter for bridges")
  99. entries = utils.execute(['sysctl', '-N', 'net.bridge'],
  100. run_as_root=True).splitlines()
  101. for proto in ('arp', 'ip', 'ip6'):
  102. knob = 'net.bridge.bridge-nf-call-%stables' % proto
  103. if 'net.bridge.bridge-nf-call-%stables' % proto not in entries:
  104. raise SystemExit(
  105. _("sysctl value %s not present on this system.") % knob)
  106. enabled = utils.execute(['sysctl', '-b', knob])
  107. if enabled != '1':
  108. utils.execute(
  109. ['sysctl', '-w', '%s=1' % knob], run_as_root=True)
  110. @property
  111. def ports(self):
  112. return dict(self.filtered_ports, **self.unfiltered_ports)
  113. def _update_remote_security_group_members(self, sec_group_ids):
  114. for sg_id in sec_group_ids:
  115. for device in self.filtered_ports.values():
  116. if sg_id in device.get('security_group_source_groups', []):
  117. self.devices_with_updated_sg_members[sg_id].append(device)
  118. def security_group_updated(self, action_type, sec_group_ids,
  119. device_ids=None):
  120. device_ids = device_ids or []
  121. if action_type == 'sg_rule':
  122. self.updated_rule_sg_ids.update(sec_group_ids)
  123. elif action_type == 'sg_member':
  124. if device_ids:
  125. self.updated_sg_members.update(device_ids)
  126. else:
  127. self._update_remote_security_group_members(sec_group_ids)
  128. def update_security_group_rules(self, sg_id, sg_rules):
  129. LOG.debug("Update rules of security group (%s)", sg_id)
  130. self.sg_rules[sg_id] = sg_rules
  131. def update_security_group_members(self, sg_id, sg_members):
  132. LOG.debug("Update members of security group (%s)", sg_id)
  133. self.sg_members[sg_id] = collections.defaultdict(list, sg_members)
  134. if self.enable_ipset:
  135. self._update_ipset_members(sg_id, sg_members)
  136. def _update_ipset_members(self, sg_id, sg_members):
  137. devices = self.devices_with_updated_sg_members.pop(sg_id, None)
  138. for ip_version, current_ips in sg_members.items():
  139. add_ips, del_ips = self.ipset.set_members(
  140. sg_id, ip_version, current_ips)
  141. if devices and del_ips:
  142. # remove prefix from del_ips
  143. ips = [str(netaddr.IPNetwork(del_ip).ip) for del_ip in del_ips]
  144. self.ipconntrack.delete_conntrack_state_by_remote_ips(
  145. devices, ip_version, ips)
  146. def _set_ports(self, port):
  147. if not firewall.port_sec_enabled(port):
  148. self.unfiltered_ports[port['device']] = port
  149. self.filtered_ports.pop(port['device'], None)
  150. else:
  151. self.filtered_ports[port['device']] = port
  152. self.unfiltered_ports.pop(port['device'], None)
  153. def _unset_ports(self, port):
  154. self.unfiltered_ports.pop(port['device'], None)
  155. self.filtered_ports.pop(port['device'], None)
  156. def _remove_conntrack_entries_from_port_deleted(self, port):
  157. device_info = self.filtered_ports.get(port['device'])
  158. if not device_info:
  159. return
  160. for ethertype in [constants.IPv4, constants.IPv6]:
  161. self.ipconntrack.delete_conntrack_state_by_remote_ips(
  162. [device_info], ethertype, set())
  163. def prepare_port_filter(self, port):
  164. LOG.debug("Preparing device (%s) filter", port['device'])
  165. self._set_ports(port)
  166. self._enable_netfilter_for_bridges()
  167. # each security group has it own chains
  168. self._setup_chains()
  169. return self.iptables.apply()
  170. def update_port_filter(self, port):
  171. LOG.debug("Updating device (%s) filter", port['device'])
  172. if port['device'] not in self.ports:
  173. LOG.info(_LI('Attempted to update port filter which is not '
  174. 'filtered %s'), port['device'])
  175. return
  176. self._remove_chains()
  177. self._set_ports(port)
  178. self._setup_chains()
  179. return self.iptables.apply()
  180. def remove_port_filter(self, port):
  181. LOG.debug("Removing device (%s) filter", port['device'])
  182. if port['device'] not in self.ports:
  183. LOG.info(_LI('Attempted to remove port filter which is not '
  184. 'filtered %r'), port)
  185. return
  186. self._remove_chains()
  187. self._remove_conntrack_entries_from_port_deleted(port)
  188. self._unset_ports(port)
  189. self._setup_chains()
  190. return self.iptables.apply()
  191. def _add_accept_rule_port_sec(self, port, direction):
  192. self._update_port_sec_rules(port, direction, add=True)
  193. def _remove_rule_port_sec(self, port, direction):
  194. self._update_port_sec_rules(port, direction, add=False)
  195. def _remove_rule_from_chain_v4v6(self, chain_name, ipv4_rules, ipv6_rules):
  196. for rule in ipv4_rules:
  197. self.iptables.ipv4['filter'].remove_rule(chain_name, rule)
  198. for rule in ipv6_rules:
  199. self.iptables.ipv6['filter'].remove_rule(chain_name, rule)
  200. def _setup_chains(self):
  201. """Setup ingress and egress chain for a port."""
  202. if not self._defer_apply:
  203. self._setup_chains_apply(self.filtered_ports,
  204. self.unfiltered_ports)
  205. def _setup_chains_apply(self, ports, unfiltered_ports):
  206. self._add_chain_by_name_v4v6(SG_CHAIN)
  207. # sort by port so we always do this deterministically between
  208. # agent restarts and don't cause unnecessary rule differences
  209. for pname in sorted(ports):
  210. port = ports[pname]
  211. self._add_conntrack_jump(port)
  212. self._setup_chain(port, firewall.INGRESS_DIRECTION)
  213. self._setup_chain(port, firewall.EGRESS_DIRECTION)
  214. self.iptables.ipv4['filter'].add_rule(SG_CHAIN, '-j ACCEPT')
  215. self.iptables.ipv6['filter'].add_rule(SG_CHAIN, '-j ACCEPT')
  216. for port in unfiltered_ports.values():
  217. self._add_accept_rule_port_sec(port, firewall.INGRESS_DIRECTION)
  218. self._add_accept_rule_port_sec(port, firewall.EGRESS_DIRECTION)
  219. def _remove_chains(self):
  220. """Remove ingress and egress chain for a port."""
  221. if not self._defer_apply:
  222. self._remove_chains_apply(self.filtered_ports,
  223. self.unfiltered_ports)
  224. def _remove_chains_apply(self, ports, unfiltered_ports):
  225. for port in ports.values():
  226. self._remove_chain(port, firewall.INGRESS_DIRECTION)
  227. self._remove_chain(port, firewall.EGRESS_DIRECTION)
  228. self._remove_chain(port, SPOOF_FILTER)
  229. self._remove_conntrack_jump(port)
  230. for port in unfiltered_ports.values():
  231. self._remove_rule_port_sec(port, firewall.INGRESS_DIRECTION)
  232. self._remove_rule_port_sec(port, firewall.EGRESS_DIRECTION)
  233. self._remove_chain_by_name_v4v6(SG_CHAIN)
  234. def _setup_chain(self, port, DIRECTION):
  235. self._add_chain(port, DIRECTION)
  236. self._add_rules_by_security_group(port, DIRECTION)
  237. def _remove_chain(self, port, DIRECTION):
  238. chain_name = self._port_chain_name(port, DIRECTION)
  239. self._remove_chain_by_name_v4v6(chain_name)
  240. def _add_fallback_chain_v4v6(self):
  241. self.iptables.ipv4['filter'].add_chain('sg-fallback')
  242. self.iptables.ipv4['filter'].add_rule('sg-fallback', '-j DROP',
  243. comment=ic.UNMATCH_DROP)
  244. self.iptables.ipv6['filter'].add_chain('sg-fallback')
  245. self.iptables.ipv6['filter'].add_rule('sg-fallback', '-j DROP',
  246. comment=ic.UNMATCH_DROP)
  247. def _add_raw_chain(self, chain_name):
  248. self.iptables.ipv4['raw'].add_chain(chain_name)
  249. self.iptables.ipv6['raw'].add_chain(chain_name)
  250. def _add_chain_by_name_v4v6(self, chain_name):
  251. self.iptables.ipv4['filter'].add_chain(chain_name)
  252. self.iptables.ipv6['filter'].add_chain(chain_name)
  253. def _remove_raw_chain(self, chain_name):
  254. self.iptables.ipv4['raw'].remove_chain(chain_name)
  255. self.iptables.ipv6['raw'].remove_chain(chain_name)
  256. def _remove_chain_by_name_v4v6(self, chain_name):
  257. self.iptables.ipv4['filter'].remove_chain(chain_name)
  258. self.iptables.ipv6['filter'].remove_chain(chain_name)
  259. def _add_rules_to_chain_v4v6(self, chain_name, ipv4_rules, ipv6_rules,
  260. comment=None):
  261. for rule in ipv4_rules:
  262. self.iptables.ipv4['filter'].add_rule(chain_name, rule,
  263. comment=comment)
  264. for rule in ipv6_rules:
  265. self.iptables.ipv6['filter'].add_rule(chain_name, rule,
  266. comment=comment)
  267. def _get_device_name(self, port):
  268. return port['device']
  269. def _update_port_sec_rules(self, port, direction, add=False):
  270. # add/remove rules in FORWARD and INPUT chain
  271. device = self._get_device_name(port)
  272. jump_rule = ['-m physdev --%s %s --physdev-is-bridged '
  273. '-j ACCEPT' % (self.IPTABLES_DIRECTION[direction],
  274. device)]
  275. if add:
  276. self._add_rules_to_chain_v4v6(
  277. 'FORWARD', jump_rule, jump_rule, comment=ic.PORT_SEC_ACCEPT)
  278. else:
  279. self._remove_rule_from_chain_v4v6('FORWARD', jump_rule, jump_rule)
  280. if direction == firewall.EGRESS_DIRECTION:
  281. jump_rule = ['-m physdev --%s %s --physdev-is-bridged '
  282. '-j ACCEPT' % (self.IPTABLES_DIRECTION[direction],
  283. device)]
  284. if add:
  285. self._add_rules_to_chain_v4v6('INPUT', jump_rule, jump_rule,
  286. comment=ic.PORT_SEC_ACCEPT)
  287. else:
  288. self._remove_rule_from_chain_v4v6(
  289. 'INPUT', jump_rule, jump_rule)
  290. def _add_chain(self, port, direction):
  291. chain_name = self._port_chain_name(port, direction)
  292. self._add_chain_by_name_v4v6(chain_name)
  293. # Note(nati) jump to the security group chain (SG_CHAIN)
  294. # This is needed because the packet may much two rule in port
  295. # if the two port is in the same host
  296. # We accept the packet at the end of SG_CHAIN.
  297. # jump to the security group chain
  298. device = self._get_device_name(port)
  299. jump_rule = ['-m physdev --%s %s --physdev-is-bridged '
  300. '-j $%s' % (self.IPTABLES_DIRECTION[direction],
  301. device,
  302. SG_CHAIN)]
  303. self._add_rules_to_chain_v4v6('FORWARD', jump_rule, jump_rule,
  304. comment=ic.VM_INT_SG)
  305. # jump to the chain based on the device
  306. jump_rule = ['-m physdev --%s %s --physdev-is-bridged '
  307. '-j $%s' % (self.IPTABLES_DIRECTION[direction],
  308. device,
  309. chain_name)]
  310. self._add_rules_to_chain_v4v6(SG_CHAIN, jump_rule, jump_rule,
  311. comment=ic.SG_TO_VM_SG)
  312. if direction == firewall.EGRESS_DIRECTION:
  313. self._add_rules_to_chain_v4v6('INPUT', jump_rule, jump_rule,
  314. comment=ic.INPUT_TO_SG)
  315. def _get_br_device_name(self, port):
  316. return ('brq' + port['network_id'])[:n_const.LINUX_DEV_LEN]
  317. def _get_jump_rules(self, port):
  318. zone = self.ipconntrack.get_device_zone(port)
  319. br_dev = self._get_br_device_name(port)
  320. port_dev = self._get_device_name(port)
  321. # match by interface for bridge input
  322. match_interface = '-i %s'
  323. match_physdev = '-m physdev --physdev-in %s'
  324. # comment to prevent duplicate warnings for different devices using
  325. # same bridge. truncate start to remove prefixes
  326. comment = '-m comment --comment "Set zone for %s"' % port['device'][4:]
  327. rules = []
  328. for dev, match in ((br_dev, match_physdev), (br_dev, match_interface),
  329. (port_dev, match_physdev)):
  330. match = match % dev
  331. rule = '%s %s -j CT --zone %s' % (match, comment, zone)
  332. rules.append(rule)
  333. return rules
  334. def _add_conntrack_jump(self, port):
  335. for jump_rule in self._get_jump_rules(port):
  336. self._add_raw_rule('PREROUTING', jump_rule)
  337. def _remove_conntrack_jump(self, port):
  338. for jump_rule in self._get_jump_rules(port):
  339. self._remove_raw_rule('PREROUTING', jump_rule)
  340. def _add_raw_rule(self, chain, rule, comment=None):
  341. self.iptables.ipv4['raw'].add_rule(chain, rule, comment=comment)
  342. self.iptables.ipv6['raw'].add_rule(chain, rule, comment=comment)
  343. def _remove_raw_rule(self, chain, rule):
  344. self.iptables.ipv4['raw'].remove_rule(chain, rule)
  345. self.iptables.ipv6['raw'].remove_rule(chain, rule)
  346. def _split_sgr_by_ethertype(self, security_group_rules):
  347. ipv4_sg_rules = []
  348. ipv6_sg_rules = []
  349. for rule in security_group_rules:
  350. if rule.get('ethertype') == constants.IPv4:
  351. ipv4_sg_rules.append(rule)
  352. elif rule.get('ethertype') == constants.IPv6:
  353. if rule.get('protocol') == 'icmp':
  354. rule['protocol'] = 'ipv6-icmp'
  355. ipv6_sg_rules.append(rule)
  356. return ipv4_sg_rules, ipv6_sg_rules
  357. def _select_sgr_by_direction(self, port, direction):
  358. return [rule
  359. for rule in port.get('security_group_rules', [])
  360. if rule['direction'] == direction]
  361. def _setup_spoof_filter_chain(self, port, table, mac_ip_pairs, rules):
  362. if mac_ip_pairs:
  363. chain_name = self._port_chain_name(port, SPOOF_FILTER)
  364. table.add_chain(chain_name)
  365. for mac, ip in mac_ip_pairs:
  366. if ip is None:
  367. # If fixed_ips is [] this rule will be added to the end
  368. # of the list after the allowed_address_pair rules.
  369. table.add_rule(chain_name,
  370. '-m mac --mac-source %s -j RETURN'
  371. % mac.upper(), comment=ic.PAIR_ALLOW)
  372. else:
  373. # we need to convert it into a prefix to match iptables
  374. ip = c_utils.ip_to_cidr(ip)
  375. table.add_rule(chain_name,
  376. '-s %s -m mac --mac-source %s -j RETURN'
  377. % (ip, mac.upper()), comment=ic.PAIR_ALLOW)
  378. table.add_rule(chain_name, '-j DROP', comment=ic.PAIR_DROP)
  379. rules.append('-j $%s' % chain_name)
  380. def _build_ipv4v6_mac_ip_list(self, mac, ip_address, mac_ipv4_pairs,
  381. mac_ipv6_pairs):
  382. mac = str(netaddr.EUI(mac, dialect=mac_iptables))
  383. if netaddr.IPNetwork(ip_address).version == 4:
  384. mac_ipv4_pairs.append((mac, ip_address))
  385. else:
  386. mac_ipv6_pairs.append((mac, ip_address))
  387. lla = str(netutils.get_ipv6_addr_by_EUI64(
  388. constants.IPv6_LLA_PREFIX, mac))
  389. if (mac, lla) not in mac_ipv6_pairs:
  390. # only add once so we don't generate duplicate rules
  391. mac_ipv6_pairs.append((mac, lla))
  392. def _spoofing_rule(self, port, ipv4_rules, ipv6_rules):
  393. # Fixed rules for traffic sourced from unspecified addresses: 0.0.0.0
  394. # and ::
  395. # Allow dhcp client discovery and request
  396. ipv4_rules += [comment_rule('-s 0.0.0.0/32 -d 255.255.255.255/32 '
  397. '-p udp -m udp --sport 68 --dport 67 '
  398. '-j RETURN', comment=ic.DHCP_CLIENT)]
  399. # Allow neighbor solicitation and multicast listener discovery
  400. # from the unspecified address for duplicate address detection
  401. for icmp6_type in constants.ICMPV6_ALLOWED_UNSPEC_ADDR_TYPES:
  402. ipv6_rules += [comment_rule('-s ::/128 -d ff02::/16 '
  403. '-p ipv6-icmp -m icmp6 '
  404. '--icmpv6-type %s -j RETURN' %
  405. icmp6_type,
  406. comment=ic.IPV6_ICMP_ALLOW)]
  407. mac_ipv4_pairs = []
  408. mac_ipv6_pairs = []
  409. if isinstance(port.get('allowed_address_pairs'), list):
  410. for address_pair in port['allowed_address_pairs']:
  411. self._build_ipv4v6_mac_ip_list(address_pair['mac_address'],
  412. address_pair['ip_address'],
  413. mac_ipv4_pairs,
  414. mac_ipv6_pairs)
  415. for ip in port['fixed_ips']:
  416. self._build_ipv4v6_mac_ip_list(port['mac_address'], ip,
  417. mac_ipv4_pairs, mac_ipv6_pairs)
  418. if not port['fixed_ips']:
  419. mac_ipv4_pairs.append((port['mac_address'], None))
  420. mac_ipv6_pairs.append((port['mac_address'], None))
  421. self._setup_spoof_filter_chain(port, self.iptables.ipv4['filter'],
  422. mac_ipv4_pairs, ipv4_rules)
  423. self._setup_spoof_filter_chain(port, self.iptables.ipv6['filter'],
  424. mac_ipv6_pairs, ipv6_rules)
  425. # Fixed rules for traffic after source address is verified
  426. # Allow dhcp client renewal and rebinding
  427. ipv4_rules += [comment_rule('-p udp -m udp --sport 68 --dport 67 '
  428. '-j RETURN', comment=ic.DHCP_CLIENT)]
  429. # Drop Router Advts from the port.
  430. ipv6_rules += [comment_rule('-p ipv6-icmp -m icmp6 --icmpv6-type %s '
  431. '-j DROP' % constants.ICMPV6_TYPE_RA,
  432. comment=ic.IPV6_RA_DROP)]
  433. ipv6_rules += [comment_rule('-p ipv6-icmp -j RETURN',
  434. comment=ic.IPV6_ICMP_ALLOW)]
  435. ipv6_rules += [comment_rule('-p udp -m udp --sport 546 '
  436. '-m udp --dport 547 '
  437. '-j RETURN', comment=ic.DHCP_CLIENT)]
  438. def _drop_dhcp_rule(self, ipv4_rules, ipv6_rules):
  439. #Note(nati) Drop dhcp packet from VM
  440. ipv4_rules += [comment_rule('-p udp -m udp --sport 67 '
  441. '-m udp --dport 68 '
  442. '-j DROP', comment=ic.DHCP_SPOOF)]
  443. ipv6_rules += [comment_rule('-p udp -m udp --sport 547 '
  444. '-m udp --dport 546 '
  445. '-j DROP', comment=ic.DHCP_SPOOF)]
  446. def _accept_inbound_icmpv6(self):
  447. # Allow multicast listener, neighbor solicitation and
  448. # neighbor advertisement into the instance
  449. icmpv6_rules = []
  450. for icmp6_type in firewall.ICMPV6_ALLOWED_TYPES:
  451. icmpv6_rules += ['-p ipv6-icmp -m icmp6 --icmpv6-type %s '
  452. '-j RETURN' % icmp6_type]
  453. return icmpv6_rules
  454. def _select_sg_rules_for_port(self, port, direction):
  455. """Select rules from the security groups the port is member of."""
  456. port_sg_ids = port.get('security_groups', [])
  457. port_rules = []
  458. for sg_id in port_sg_ids:
  459. for rule in self.sg_rules.get(sg_id, []):
  460. if rule['direction'] == direction:
  461. if self.enable_ipset:
  462. port_rules.append(rule)
  463. else:
  464. port_rules.extend(
  465. self._expand_sg_rule_with_remote_ips(
  466. rule, port, direction))
  467. return port_rules
  468. def _expand_sg_rule_with_remote_ips(self, rule, port, direction):
  469. """Expand a remote group rule to rule per remote group IP."""
  470. remote_group_id = rule.get('remote_group_id')
  471. if remote_group_id:
  472. ethertype = rule['ethertype']
  473. port_ips = port.get('fixed_ips', [])
  474. for ip in self.sg_members[remote_group_id][ethertype]:
  475. if ip not in port_ips:
  476. ip_rule = rule.copy()
  477. direction_ip_prefix = firewall.DIRECTION_IP_PREFIX[
  478. direction]
  479. ip_prefix = str(netaddr.IPNetwork(ip).cidr)
  480. ip_rule[direction_ip_prefix] = ip_prefix
  481. yield ip_rule
  482. else:
  483. yield rule
  484. def _get_remote_sg_ids(self, port, direction=None):
  485. sg_ids = port.get('security_groups', [])
  486. remote_sg_ids = {constants.IPv4: set(), constants.IPv6: set()}
  487. for sg_id in sg_ids:
  488. for rule in self.sg_rules.get(sg_id, []):
  489. if not direction or rule['direction'] == direction:
  490. remote_sg_id = rule.get('remote_group_id')
  491. ether_type = rule.get('ethertype')
  492. if remote_sg_id and ether_type:
  493. remote_sg_ids[ether_type].add(remote_sg_id)
  494. return remote_sg_ids
  495. def _add_rules_by_security_group(self, port, direction):
  496. # select rules for current port and direction
  497. security_group_rules = self._select_sgr_by_direction(port, direction)
  498. security_group_rules += self._select_sg_rules_for_port(port, direction)
  499. # split groups by ip version
  500. # for ipv4, iptables command is used
  501. # for ipv6, iptables6 command is used
  502. ipv4_sg_rules, ipv6_sg_rules = self._split_sgr_by_ethertype(
  503. security_group_rules)
  504. ipv4_iptables_rules = []
  505. ipv6_iptables_rules = []
  506. # include fixed egress/ingress rules
  507. if direction == firewall.EGRESS_DIRECTION:
  508. self._add_fixed_egress_rules(port,
  509. ipv4_iptables_rules,
  510. ipv6_iptables_rules)
  511. elif direction == firewall.INGRESS_DIRECTION:
  512. ipv6_iptables_rules += self._accept_inbound_icmpv6()
  513. # include IPv4 and IPv6 iptable rules from security group
  514. ipv4_iptables_rules += self._convert_sgr_to_iptables_rules(
  515. ipv4_sg_rules)
  516. ipv6_iptables_rules += self._convert_sgr_to_iptables_rules(
  517. ipv6_sg_rules)
  518. # finally add the rules to the port chain for a given direction
  519. self._add_rules_to_chain_v4v6(self._port_chain_name(port, direction),
  520. ipv4_iptables_rules,
  521. ipv6_iptables_rules)
  522. def _add_fixed_egress_rules(self, port, ipv4_iptables_rules,
  523. ipv6_iptables_rules):
  524. self._spoofing_rule(port,
  525. ipv4_iptables_rules,
  526. ipv6_iptables_rules)
  527. self._drop_dhcp_rule(ipv4_iptables_rules, ipv6_iptables_rules)
  528. def _generate_ipset_rule_args(self, sg_rule, remote_gid):
  529. ethertype = sg_rule.get('ethertype')
  530. ipset_name = self.ipset.get_name(remote_gid, ethertype)
  531. if not self.ipset.set_name_exists(ipset_name):
  532. #NOTE(mangelajo): ipsets for empty groups are not created
  533. # thus we can't reference them.
  534. return None
  535. ipset_direction = IPSET_DIRECTION[sg_rule.get('direction')]
  536. args = self._generate_protocol_and_port_args(sg_rule)
  537. args += ['-m set', '--match-set', ipset_name, ipset_direction]
  538. args += ['-j RETURN']
  539. return args
  540. def _generate_protocol_and_port_args(self, sg_rule):
  541. args = self._protocol_arg(sg_rule.get('protocol'))
  542. args += self._port_arg('sport',
  543. sg_rule.get('protocol'),
  544. sg_rule.get('source_port_range_min'),
  545. sg_rule.get('source_port_range_max'))
  546. args += self._port_arg('dport',
  547. sg_rule.get('protocol'),
  548. sg_rule.get('port_range_min'),
  549. sg_rule.get('port_range_max'))
  550. return args
  551. def _generate_plain_rule_args(self, sg_rule):
  552. # These arguments MUST be in the format iptables-save will
  553. # display them: source/dest, protocol, sport, dport, target
  554. # Otherwise the iptables_manager code won't be able to find
  555. # them to preserve their [packet:byte] counts.
  556. args = self._ip_prefix_arg('s', sg_rule.get('source_ip_prefix'))
  557. args += self._ip_prefix_arg('d', sg_rule.get('dest_ip_prefix'))
  558. args += self._generate_protocol_and_port_args(sg_rule)
  559. args += ['-j RETURN']
  560. return args
  561. def _convert_sg_rule_to_iptables_args(self, sg_rule):
  562. remote_gid = sg_rule.get('remote_group_id')
  563. if self.enable_ipset and remote_gid:
  564. return self._generate_ipset_rule_args(sg_rule, remote_gid)
  565. else:
  566. return self._generate_plain_rule_args(sg_rule)
  567. def _convert_sgr_to_iptables_rules(self, security_group_rules):
  568. iptables_rules = []
  569. self._allow_established(iptables_rules)
  570. seen_sg_rules = set()
  571. for rule in security_group_rules:
  572. args = self._convert_sg_rule_to_iptables_args(rule)
  573. if args:
  574. rule_command = ' '.join(args)
  575. if rule_command in seen_sg_rules:
  576. # since these rules are from multiple security groups,
  577. # there may be duplicates so we prune them out here
  578. continue
  579. seen_sg_rules.add(rule_command)
  580. iptables_rules.append(rule_command)
  581. self._drop_invalid_packets(iptables_rules)
  582. iptables_rules += [comment_rule('-j $sg-fallback',
  583. comment=ic.UNMATCHED)]
  584. return iptables_rules
  585. def _drop_invalid_packets(self, iptables_rules):
  586. # Always drop invalid packets
  587. iptables_rules += [comment_rule('-m state --state ' 'INVALID -j DROP',
  588. comment=ic.INVALID_DROP)]
  589. return iptables_rules
  590. def _allow_established(self, iptables_rules):
  591. # Allow established connections
  592. iptables_rules += [comment_rule(
  593. '-m state --state RELATED,ESTABLISHED -j RETURN',
  594. comment=ic.ALLOW_ASSOC)]
  595. return iptables_rules
  596. def _protocol_arg(self, protocol):
  597. if not protocol:
  598. return []
  599. if protocol == 'icmpv6':
  600. protocol = 'ipv6-icmp'
  601. iptables_rule = ['-p', protocol]
  602. return iptables_rule
  603. def _port_arg(self, direction, protocol, port_range_min, port_range_max):
  604. if (protocol not in ['udp', 'tcp', 'icmp', 'ipv6-icmp']
  605. or port_range_min is None):
  606. return []
  607. protocol_modules = {'udp': 'udp', 'tcp': 'tcp',
  608. 'icmp': 'icmp', 'ipv6-icmp': 'icmp6'}
  609. # iptables adds '-m protocol' when the port number is specified
  610. args = ['-m', protocol_modules[protocol]]
  611. if protocol in ['icmp', 'ipv6-icmp']:
  612. protocol_type = 'icmpv6' if protocol == 'ipv6-icmp' else 'icmp'
  613. # Note(xuhanp): port_range_min/port_range_max represent
  614. # icmp type/code when protocol is icmp or icmpv6
  615. args += ['--%s-type' % protocol_type, '%s' % port_range_min]
  616. # icmp code can be 0 so we cannot use "if port_range_max" here
  617. if port_range_max is not None:
  618. args[-1] += '/%s' % port_range_max
  619. elif port_range_min == port_range_max:
  620. args += ['--%s' % direction, '%s' % (port_range_min,)]
  621. else:
  622. args += ['-m', 'multiport', '--%ss' % direction,
  623. '%s:%s' % (port_range_min, port_range_max)]
  624. return args
  625. def _ip_prefix_arg(self, direction, ip_prefix):
  626. #NOTE (nati) : source_group_id is converted to list of source_
  627. # ip_prefix in server side
  628. if ip_prefix:
  629. if '/' not in ip_prefix:
  630. # we need to convert it into a prefix to match iptables
  631. ip_prefix = c_utils.ip_to_cidr(ip_prefix)
  632. elif ip_prefix.endswith('/0'):
  633. # an allow for every address is not a constraint so
  634. # iptables drops it
  635. return []
  636. return ['-%s' % direction, ip_prefix]
  637. return []
  638. def _port_chain_name(self, port, direction):
  639. return iptables_manager.get_chain_name(
  640. '%s%s' % (CHAIN_NAME_PREFIX[direction], port['device'][3:]))
  641. def filter_defer_apply_on(self):
  642. if not self._defer_apply:
  643. self.iptables.defer_apply_on()
  644. self._pre_defer_filtered_ports = dict(self.filtered_ports)
  645. self._pre_defer_unfiltered_ports = dict(self.unfiltered_ports)
  646. self.pre_sg_members = dict(self.sg_members)
  647. self.pre_sg_rules = dict(self.sg_rules)
  648. self._defer_apply = True
  649. def _remove_unused_security_group_info(self):
  650. """Remove any unnecessary local security group info or unused ipsets.
  651. This function has to be called after applying the last iptables
  652. rules, so we're in a point where no iptable rule depends
  653. on an ipset we're going to delete.
  654. """
  655. filtered_ports = self.filtered_ports.values()
  656. remote_sgs_to_remove = self._determine_remote_sgs_to_remove(
  657. filtered_ports)
  658. for ip_version, remote_sg_ids in six.iteritems(remote_sgs_to_remove):
  659. if self.enable_ipset:
  660. self._remove_ipsets_for_remote_sgs(ip_version, remote_sg_ids)
  661. self._remove_sg_members(remote_sgs_to_remove)
  662. # Remove unused security group rules
  663. for remove_group_id in self._determine_sg_rules_to_remove(
  664. filtered_ports):
  665. self.sg_rules.pop(remove_group_id, None)
  666. def _determine_remote_sgs_to_remove(self, filtered_ports):
  667. """Calculate which remote security groups we don't need anymore.
  668. We do the calculation for each ip_version.
  669. """
  670. sgs_to_remove_per_ipversion = {constants.IPv4: set(),
  671. constants.IPv6: set()}
  672. remote_group_id_sets = self._get_remote_sg_ids_sets_by_ipversion(
  673. filtered_ports)
  674. for ip_version, remote_group_id_set in (
  675. six.iteritems(remote_group_id_sets)):
  676. sgs_to_remove_per_ipversion[ip_version].update(
  677. set(self.pre_sg_members) - remote_group_id_set)
  678. return sgs_to_remove_per_ipversion
  679. def _get_remote_sg_ids_sets_by_ipversion(self, filtered_ports):
  680. """Given a port, calculates the remote sg references by ip_version."""
  681. remote_group_id_sets = {constants.IPv4: set(),
  682. constants.IPv6: set()}
  683. for port in filtered_ports:
  684. remote_sg_ids = self._get_remote_sg_ids(port)
  685. for ip_version in (constants.IPv4, constants.IPv6):
  686. remote_group_id_sets[ip_version] |= remote_sg_ids[ip_version]
  687. return remote_group_id_sets
  688. def _determine_sg_rules_to_remove(self, filtered_ports):
  689. """Calculate which security groups need to be removed.
  690. We find out by subtracting our previous sg group ids,
  691. with the security groups associated to a set of ports.
  692. """
  693. port_group_ids = self._get_sg_ids_set_for_ports(filtered_ports)
  694. return set(self.pre_sg_rules) - port_group_ids
  695. def _get_sg_ids_set_for_ports(self, filtered_ports):
  696. """Get the port security group ids as a set."""
  697. port_group_ids = set()
  698. for port in filtered_ports:
  699. port_group_ids.update(port.get('security_groups', []))
  700. return port_group_ids
  701. def _remove_ipsets_for_remote_sgs(self, ip_version, remote_sg_ids):
  702. """Remove system ipsets matching the provided parameters."""
  703. for remote_sg_id in remote_sg_ids:
  704. self.ipset.destroy(remote_sg_id, ip_version)
  705. def _remove_sg_members(self, remote_sgs_to_remove):
  706. """Remove sg_member entries."""
  707. ipv4_sec_group_set = remote_sgs_to_remove.get(constants.IPv4)
  708. ipv6_sec_group_set = remote_sgs_to_remove.get(constants.IPv6)
  709. for sg_id in (ipv4_sec_group_set & ipv6_sec_group_set):
  710. if sg_id in self.sg_members:
  711. del self.sg_members[sg_id]
  712. def _find_deleted_sg_rules(self, sg_id):
  713. del_rules = list()
  714. for pre_rule in self.pre_sg_rules.get(sg_id, []):
  715. if pre_rule not in self.sg_rules.get(sg_id, []):
  716. del_rules.append(pre_rule)
  717. return del_rules
  718. def _find_devices_on_security_group(self, sg_id):
  719. device_list = list()
  720. for device in self.filtered_ports.values():
  721. if sg_id in device.get('security_groups', []):
  722. device_list.append(device)
  723. return device_list
  724. def _clean_deleted_sg_rule_conntrack_entries(self):
  725. deleted_sg_ids = set()
  726. for sg_id in self.updated_rule_sg_ids:
  727. del_rules = self._find_deleted_sg_rules(sg_id)
  728. if not del_rules:
  729. continue
  730. device_list = self._find_devices_on_security_group(sg_id)
  731. for rule in del_rules:
  732. self.ipconntrack.delete_conntrack_state_by_rule(
  733. device_list, rule)
  734. deleted_sg_ids.add(sg_id)
  735. for id in deleted_sg_ids:
  736. self.updated_rule_sg_ids.remove(id)
  737. def _clean_updated_sg_member_conntrack_entries(self):
  738. updated_device_ids = set()
  739. for device in self.updated_sg_members:
  740. sec_group_change = False
  741. device_info = self.filtered_ports.get(device)
  742. pre_device_info = self._pre_defer_filtered_ports.get(device)
  743. if not device_info or not pre_device_info:
  744. continue
  745. for sg_id in pre_device_info.get('security_groups', []):
  746. if sg_id not in device_info.get('security_groups', []):
  747. sec_group_change = True
  748. break
  749. if not sec_group_change:
  750. continue
  751. for ethertype in [constants.IPv4, constants.IPv6]:
  752. self.ipconntrack.delete_conntrack_state_by_remote_ips(
  753. [device_info], ethertype, set())
  754. updated_device_ids.add(device)
  755. for id in updated_device_ids:
  756. self.updated_sg_members.remove(id)
  757. def _clean_deleted_remote_sg_members_conntrack_entries(self):
  758. deleted_sg_ids = set()
  759. for sg_id, devices in self.devices_with_updated_sg_members.items():
  760. for ethertype in [constants.IPv4, constants.IPv6]:
  761. pre_ips = self._get_sg_members(
  762. self.pre_sg_members, sg_id, ethertype)
  763. cur_ips = self._get_sg_members(
  764. self.sg_members, sg_id, ethertype)
  765. ips = (pre_ips - cur_ips)
  766. if devices and ips:
  767. self.ipconntrack.delete_conntrack_state_by_remote_ips(
  768. devices, ethertype, ips)
  769. deleted_sg_ids.add(sg_id)
  770. for id in deleted_sg_ids:
  771. self.devices_with_updated_sg_members.pop(id, None)
  772. def _remove_conntrack_entries_from_sg_updates(self):
  773. self._clean_deleted_sg_rule_conntrack_entries()
  774. self._clean_updated_sg_member_conntrack_entries()
  775. if not self.enable_ipset:
  776. self._clean_deleted_remote_sg_members_conntrack_entries()
  777. def _get_sg_members(self, sg_info, sg_id, ethertype):
  778. return set(sg_info.get(sg_id, {}).get(ethertype, []))
  779. def filter_defer_apply_off(self):
  780. if self._defer_apply:
  781. self._defer_apply = False
  782. self._remove_chains_apply(self._pre_defer_filtered_ports,
  783. self._pre_defer_unfiltered_ports)
  784. self._setup_chains_apply(self.filtered_ports,
  785. self.unfiltered_ports)
  786. self.iptables.defer_apply_off()
  787. self._remove_conntrack_entries_from_sg_updates()
  788. self._remove_unused_security_group_info()
  789. self._pre_defer_filtered_ports = None
  790. self._pre_defer_unfiltered_ports = None
  791. class OVSHybridIptablesFirewallDriver(IptablesFirewallDriver):
  792. OVS_HYBRID_TAP_PREFIX = constants.TAP_DEVICE_PREFIX
  793. OVS_HYBRID_PLUG_REQUIRED = True
  794. CONNTRACK_ZONE_PER_PORT = True
  795. def _port_chain_name(self, port, direction):
  796. return iptables_manager.get_chain_name(
  797. '%s%s' % (CHAIN_NAME_PREFIX[direction], port['device']))
  798. def _get_br_device_name(self, port):
  799. return ('qvb' + port['device'])[:n_const.LINUX_DEV_LEN]
  800. def _get_device_name(self, port):
  801. return get_hybrid_port_name(port['device'])