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

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