1#!/usr/bin/python 2# 3# Copyright 2014 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Partial Python implementation of iproute functionality.""" 18 19# pylint: disable=g-bad-todo 20 21import errno 22import os 23import socket 24import struct 25import sys 26 27import csocket 28import cstruct 29import netlink 30 31### Base netlink constants. See include/uapi/linux/netlink.h. 32NETLINK_ROUTE = 0 33 34# Data structure formats. 35# These aren't constants, they're classes. So, pylint: disable=invalid-name 36NLMsgHdr = cstruct.Struct("NLMsgHdr", "=LHHLL", "length type flags seq pid") 37NLMsgErr = cstruct.Struct("NLMsgErr", "=i", "error") 38NLAttr = cstruct.Struct("NLAttr", "=HH", "nla_len nla_type") 39 40# Alignment / padding. 41NLA_ALIGNTO = 4 42 43 44### rtnetlink constants. See include/uapi/linux/rtnetlink.h. 45# Message types. 46RTM_NEWLINK = 16 47RTM_DELLINK = 17 48RTM_GETLINK = 18 49RTM_NEWADDR = 20 50RTM_DELADDR = 21 51RTM_GETADDR = 22 52RTM_NEWROUTE = 24 53RTM_DELROUTE = 25 54RTM_GETROUTE = 26 55RTM_NEWNEIGH = 28 56RTM_DELNEIGH = 29 57RTM_GETNEIGH = 30 58RTM_NEWRULE = 32 59RTM_DELRULE = 33 60RTM_GETRULE = 34 61 62# Routing message type values (rtm_type). 63RTN_UNSPEC = 0 64RTN_UNICAST = 1 65RTN_UNREACHABLE = 7 66 67# Routing protocol values (rtm_protocol). 68RTPROT_UNSPEC = 0 69RTPROT_STATIC = 4 70 71# Route scope values (rtm_scope). 72RT_SCOPE_UNIVERSE = 0 73RT_SCOPE_LINK = 253 74 75# Named routing tables. 76RT_TABLE_UNSPEC = 0 77 78# Routing attributes. 79RTA_DST = 1 80RTA_SRC = 2 81RTA_OIF = 4 82RTA_GATEWAY = 5 83RTA_PRIORITY = 6 84RTA_PREFSRC = 7 85RTA_METRICS = 8 86RTA_CACHEINFO = 12 87RTA_TABLE = 15 88RTA_MARK = 16 89RTA_PREF = 20 90RTA_UID = 25 91 92# Route metric attributes. 93RTAX_MTU = 2 94RTAX_HOPLIMIT = 10 95 96# Data structure formats. 97IfinfoMsg = cstruct.Struct( 98 "IfinfoMsg", "=BBHiII", "family pad type index flags change") 99RTMsg = cstruct.Struct( 100 "RTMsg", "=BBBBBBBBI", 101 "family dst_len src_len tos table protocol scope type flags") 102RTACacheinfo = cstruct.Struct( 103 "RTACacheinfo", "=IIiiI", "clntref lastuse expires error used") 104 105 106### Interface address constants. See include/uapi/linux/if_addr.h. 107# Interface address attributes. 108IFA_ADDRESS = 1 109IFA_LOCAL = 2 110IFA_LABEL = 3 111IFA_CACHEINFO = 6 112 113# Address flags. 114IFA_F_SECONDARY = 0x01 115IFA_F_TEMPORARY = IFA_F_SECONDARY 116IFA_F_NODAD = 0x02 117IFA_F_OPTIMISTIC = 0x04 118IFA_F_DADFAILED = 0x08 119IFA_F_HOMEADDRESS = 0x10 120IFA_F_DEPRECATED = 0x20 121IFA_F_TENTATIVE = 0x40 122IFA_F_PERMANENT = 0x80 123 124# Data structure formats. 125IfAddrMsg = cstruct.Struct( 126 "IfAddrMsg", "=BBBBI", 127 "family prefixlen flags scope index") 128IFACacheinfo = cstruct.Struct( 129 "IFACacheinfo", "=IIII", "prefered valid cstamp tstamp") 130NDACacheinfo = cstruct.Struct( 131 "NDACacheinfo", "=IIII", "confirmed used updated refcnt") 132 133 134### Neighbour table entry constants. See include/uapi/linux/neighbour.h. 135# Neighbour cache entry attributes. 136NDA_DST = 1 137NDA_LLADDR = 2 138NDA_CACHEINFO = 3 139NDA_PROBES = 4 140 141# Neighbour cache entry states. 142NUD_PERMANENT = 0x80 143 144# Data structure formats. 145NdMsg = cstruct.Struct( 146 "NdMsg", "=BxxxiHBB", 147 "family ifindex state flags type") 148 149 150### FIB rule constants. See include/uapi/linux/fib_rules.h. 151FRA_IIFNAME = 3 152FRA_PRIORITY = 6 153FRA_FWMARK = 10 154FRA_SUPPRESS_PREFIXLEN = 14 155FRA_TABLE = 15 156FRA_FWMASK = 16 157FRA_OIFNAME = 17 158FRA_UID_RANGE = 20 159 160# Data structure formats. 161FibRuleUidRange = cstruct.Struct("FibRuleUidRange", "=II", "start end") 162 163# Link constants. See include/uapi/linux/if_link.h. 164IFLA_ADDRESS = 1 165IFLA_BROADCAST = 2 166IFLA_IFNAME = 3 167IFLA_MTU = 4 168IFLA_QDISC = 6 169IFLA_STATS = 7 170IFLA_TXQLEN = 13 171IFLA_MAP = 14 172IFLA_OPERSTATE = 16 173IFLA_LINKMODE = 17 174IFLA_STATS64 = 23 175IFLA_AF_SPEC = 26 176IFLA_GROUP = 27 177IFLA_EXT_MASK = 29 178IFLA_PROMISCUITY = 30 179IFLA_NUM_TX_QUEUES = 31 180IFLA_NUM_RX_QUEUES = 32 181IFLA_CARRIER = 33 182 183 184def CommandVerb(command): 185 return ["NEW", "DEL", "GET", "SET"][command % 4] 186 187 188def CommandSubject(command): 189 return ["LINK", "ADDR", "ROUTE", "NEIGH", "RULE"][(command - 16) / 4] 190 191 192def CommandName(command): 193 try: 194 return "RTM_%s%s" % (CommandVerb(command), CommandSubject(command)) 195 except IndexError: 196 return "RTM_%d" % command 197 198 199class IPRoute(netlink.NetlinkSocket): 200 """Provides a tiny subset of iproute functionality.""" 201 202 FAMILY = NETLINK_ROUTE 203 204 def _NlAttrIPAddress(self, nla_type, family, address): 205 return self._NlAttr(nla_type, socket.inet_pton(family, address)) 206 207 def _NlAttrInterfaceName(self, nla_type, interface): 208 return self._NlAttr(nla_type, interface + "\x00") 209 210 def _GetConstantName(self, value, prefix): 211 return super(IPRoute, self)._GetConstantName(__name__, value, prefix) 212 213 def _Decode(self, command, msg, nla_type, nla_data): 214 """Decodes netlink attributes to Python types. 215 216 Values for which the code knows the type (e.g., the fwmark ID in a 217 RTM_NEWRULE command) are decoded to Python integers, strings, etc. Values 218 of unknown type are returned as raw byte strings. 219 220 Args: 221 command: An integer. 222 - If positive, the number of the rtnetlink command being carried out. 223 This is used to interpret the attributes. For example, for an 224 RTM_NEWROUTE command, attribute type 3 is the incoming interface and 225 is an integer, but for a RTM_NEWRULE command, attribute type 3 is the 226 incoming interface name and is a string. 227 - If negative, one of the following (negative) values: 228 - RTA_METRICS: Interpret as nested route metrics. 229 family: The address family. Used to convert IP addresses into strings. 230 nla_type: An integer, then netlink attribute type. 231 nla_data: A byte string, the netlink attribute data. 232 233 Returns: 234 A tuple (name, data): 235 - name is a string (e.g., "FRA_PRIORITY") if we understood the attribute, 236 or an integer if we didn't. 237 - data can be an integer, a string, a nested dict of attributes as 238 returned by _ParseAttributes (e.g., for RTA_METRICS), a cstruct.Struct 239 (e.g., RTACacheinfo), etc. If we didn't understand the attribute, it 240 will be the raw byte string. 241 """ 242 if command == -RTA_METRICS: 243 name = self._GetConstantName(nla_type, "RTAX_") 244 elif CommandSubject(command) == "ADDR": 245 name = self._GetConstantName(nla_type, "IFA_") 246 elif CommandSubject(command) == "LINK": 247 name = self._GetConstantName(nla_type, "IFLA_") 248 elif CommandSubject(command) == "RULE": 249 name = self._GetConstantName(nla_type, "FRA_") 250 elif CommandSubject(command) == "ROUTE": 251 name = self._GetConstantName(nla_type, "RTA_") 252 elif CommandSubject(command) == "NEIGH": 253 name = self._GetConstantName(nla_type, "NDA_") 254 else: 255 # Don't know what this is. Leave it as an integer. 256 name = nla_type 257 258 if name in ["FRA_PRIORITY", "FRA_FWMARK", "FRA_TABLE", "FRA_FWMASK", 259 "RTA_OIF", "RTA_PRIORITY", "RTA_TABLE", "RTA_MARK", 260 "IFLA_MTU", "IFLA_TXQLEN", "IFLA_GROUP", "IFLA_EXT_MASK", 261 "IFLA_PROMISCUITY", "IFLA_NUM_RX_QUEUES", 262 "IFLA_NUM_TX_QUEUES", "NDA_PROBES", "RTAX_MTU", 263 "RTAX_HOPLIMIT"]: 264 data = struct.unpack("=I", nla_data)[0] 265 elif name == "FRA_SUPPRESS_PREFIXLEN": 266 data = struct.unpack("=i", nla_data)[0] 267 elif name in ["IFLA_LINKMODE", "IFLA_OPERSTATE", "IFLA_CARRIER"]: 268 data = ord(nla_data) 269 elif name in ["IFA_ADDRESS", "IFA_LOCAL", "RTA_DST", "RTA_SRC", 270 "RTA_GATEWAY", "RTA_PREFSRC", "RTA_UID", 271 "NDA_DST"]: 272 data = socket.inet_ntop(msg.family, nla_data) 273 elif name in ["FRA_IIFNAME", "FRA_OIFNAME", "IFLA_IFNAME", "IFLA_QDISC", 274 "IFA_LABEL"]: 275 data = nla_data.strip("\x00") 276 elif name == "RTA_METRICS": 277 data = self._ParseAttributes(-RTA_METRICS, None, nla_data) 278 elif name == "RTA_CACHEINFO": 279 data = RTACacheinfo(nla_data) 280 elif name == "IFA_CACHEINFO": 281 data = IFACacheinfo(nla_data) 282 elif name == "NDA_CACHEINFO": 283 data = NDACacheinfo(nla_data) 284 elif name in ["NDA_LLADDR", "IFLA_ADDRESS"]: 285 data = ":".join(x.encode("hex") for x in nla_data) 286 elif name == "FRA_UID_RANGE": 287 data = FibRuleUidRange(nla_data) 288 else: 289 data = nla_data 290 291 return name, data 292 293 def __init__(self): 294 super(IPRoute, self).__init__() 295 296 def _AddressFamily(self, version): 297 return {4: socket.AF_INET, 6: socket.AF_INET6}[version] 298 299 def _SendNlRequest(self, command, data, flags=0): 300 """Sends a netlink request and expects an ack.""" 301 302 flags |= netlink.NLM_F_REQUEST 303 if CommandVerb(command) != "GET": 304 flags |= netlink.NLM_F_ACK 305 if CommandVerb(command) == "NEW": 306 if not flags & netlink.NLM_F_REPLACE: 307 flags |= (netlink.NLM_F_EXCL | netlink.NLM_F_CREATE) 308 309 super(IPRoute, self)._SendNlRequest(command, data, flags) 310 311 def _Rule(self, version, is_add, rule_type, table, match_nlattr, priority): 312 """Python equivalent of "ip rule <add|del> <match_cond> lookup <table>". 313 314 Args: 315 version: An integer, 4 or 6. 316 is_add: True to add a rule, False to delete it. 317 rule_type: Type of rule, e.g., RTN_UNICAST or RTN_UNREACHABLE. 318 table: If nonzero, rule looks up this table. 319 match_nlattr: A blob of struct nlattrs that express the match condition. 320 If None, match everything. 321 priority: An integer, the priority. 322 323 Raises: 324 IOError: If the netlink request returns an error. 325 ValueError: If the kernel's response could not be parsed. 326 """ 327 # Create a struct rtmsg specifying the table and the given match attributes. 328 family = self._AddressFamily(version) 329 rtmsg = RTMsg((family, 0, 0, 0, RT_TABLE_UNSPEC, 330 RTPROT_STATIC, RT_SCOPE_UNIVERSE, rule_type, 0)).Pack() 331 rtmsg += self._NlAttrU32(FRA_PRIORITY, priority) 332 if match_nlattr: 333 rtmsg += match_nlattr 334 if table: 335 rtmsg += self._NlAttrU32(FRA_TABLE, table) 336 337 # Create a netlink request containing the rtmsg. 338 command = RTM_NEWRULE if is_add else RTM_DELRULE 339 self._SendNlRequest(command, rtmsg) 340 341 def DeleteRulesAtPriority(self, version, priority): 342 family = self._AddressFamily(version) 343 rtmsg = RTMsg((family, 0, 0, 0, RT_TABLE_UNSPEC, 344 RTPROT_STATIC, RT_SCOPE_UNIVERSE, RTN_UNICAST, 0)).Pack() 345 rtmsg += self._NlAttrU32(FRA_PRIORITY, priority) 346 while True: 347 try: 348 self._SendNlRequest(RTM_DELRULE, rtmsg) 349 except IOError, e: 350 if e.errno == -errno.ENOENT: 351 break 352 else: 353 raise 354 355 def FwmarkRule(self, version, is_add, fwmark, table, priority): 356 nlattr = self._NlAttrU32(FRA_FWMARK, fwmark) 357 return self._Rule(version, is_add, RTN_UNICAST, table, nlattr, priority) 358 359 def IifRule(self, version, is_add, iif, table, priority): 360 nlattr = self._NlAttrInterfaceName(FRA_IIFNAME, iif) 361 return self._Rule(version, is_add, RTN_UNICAST, table, nlattr, priority) 362 363 def OifRule(self, version, is_add, oif, table, priority): 364 nlattr = self._NlAttrInterfaceName(FRA_OIFNAME, oif) 365 return self._Rule(version, is_add, RTN_UNICAST, table, nlattr, priority) 366 367 def UidRangeRule(self, version, is_add, start, end, table, priority): 368 nlattr = self._NlAttrInterfaceName(FRA_IIFNAME, "lo") 369 nlattr += self._NlAttr(FRA_UID_RANGE, 370 FibRuleUidRange((start, end)).Pack()) 371 return self._Rule(version, is_add, RTN_UNICAST, table, nlattr, priority) 372 373 def UnreachableRule(self, version, is_add, priority): 374 return self._Rule(version, is_add, RTN_UNREACHABLE, None, None, priority) 375 376 def DefaultRule(self, version, is_add, table, priority): 377 return self.FwmarkRule(version, is_add, 0, table, priority) 378 379 def CommandToString(self, command, data): 380 try: 381 name = CommandName(command) 382 subject = CommandSubject(command) 383 struct_type = { 384 "ADDR": IfAddrMsg, 385 "LINK": IfinfoMsg, 386 "NEIGH": NdMsg, 387 "ROUTE": RTMsg, 388 "RULE": RTMsg, 389 }[subject] 390 parsed = self._ParseNLMsg(data, struct_type) 391 return "%s %s" % (name, str(parsed)) 392 except IndexError: 393 raise ValueError("Don't know how to print command type %s" % name) 394 395 def MaybeDebugCommand(self, command, unused_flags, data): 396 subject = CommandSubject(command) 397 if "ALL" not in self.NL_DEBUG and subject not in self.NL_DEBUG: 398 return 399 print self.CommandToString(command, data) 400 401 def MaybeDebugMessage(self, message): 402 hdr = NLMsgHdr(message) 403 self.MaybeDebugCommand(hdr.type, message) 404 405 def PrintMessage(self, message): 406 hdr = NLMsgHdr(message) 407 print self.CommandToString(hdr.type, message) 408 409 def DumpRules(self, version): 410 """Returns the IP rules for the specified IP version.""" 411 # Create a struct rtmsg specifying the table and the given match attributes. 412 family = self._AddressFamily(version) 413 rtmsg = RTMsg((family, 0, 0, 0, 0, 0, 0, 0, 0)) 414 return self._Dump(RTM_GETRULE, rtmsg, RTMsg, "") 415 416 def DumpLinks(self): 417 ifinfomsg = IfinfoMsg((0, 0, 0, 0, 0, 0)) 418 return self._Dump(RTM_GETLINK, ifinfomsg, IfinfoMsg, "") 419 420 def DumpAddresses(self, version): 421 family = self._AddressFamily(version) 422 ifaddrmsg = IfAddrMsg((family, 0, 0, 0, 0)) 423 return self._Dump(RTM_GETADDR, ifaddrmsg, IfAddrMsg, "") 424 425 def _Address(self, version, command, addr, prefixlen, flags, scope, ifindex): 426 """Adds or deletes an IP address.""" 427 family = self._AddressFamily(version) 428 ifaddrmsg = IfAddrMsg((family, prefixlen, flags, scope, ifindex)).Pack() 429 ifaddrmsg += self._NlAttrIPAddress(IFA_ADDRESS, family, addr) 430 if version == 4: 431 ifaddrmsg += self._NlAttrIPAddress(IFA_LOCAL, family, addr) 432 self._SendNlRequest(command, ifaddrmsg) 433 434 def AddAddress(self, address, prefixlen, ifindex): 435 self._Address(6 if ":" in address else 4, 436 RTM_NEWADDR, address, prefixlen, 437 IFA_F_PERMANENT, RT_SCOPE_UNIVERSE, ifindex) 438 439 def DelAddress(self, address, prefixlen, ifindex): 440 self._Address(6 if ":" in address else 4, 441 RTM_DELADDR, address, prefixlen, 0, 0, ifindex) 442 443 def GetAddress(self, address, ifindex=0): 444 """Returns an ifaddrmsg for the requested address.""" 445 if ":" not in address: 446 # The address is likely an IPv4 address. RTM_GETADDR without the 447 # NLM_F_DUMP flag is not supported by the kernel. We do not currently 448 # implement parsing dump results. 449 raise NotImplementedError("IPv4 RTM_GETADDR not implemented.") 450 self._Address(6, RTM_GETADDR, address, 0, 0, RT_SCOPE_UNIVERSE, ifindex) 451 return self._GetMsg(IfAddrMsg) 452 453 def _Route(self, version, command, table, dest, prefixlen, nexthop, dev, 454 mark, uid): 455 """Adds, deletes, or queries a route.""" 456 family = self._AddressFamily(version) 457 scope = RT_SCOPE_UNIVERSE if nexthop else RT_SCOPE_LINK 458 rtmsg = RTMsg((family, prefixlen, 0, 0, RT_TABLE_UNSPEC, 459 RTPROT_STATIC, scope, RTN_UNICAST, 0)).Pack() 460 if command == RTM_NEWROUTE and not table: 461 # Don't allow setting routes in table 0, since its behaviour is confusing 462 # and differs between IPv4 and IPv6. 463 raise ValueError("Cowardly refusing to add a route to table 0") 464 if table: 465 rtmsg += self._NlAttrU32(FRA_TABLE, table) 466 if dest != "default": # The default is the default route. 467 rtmsg += self._NlAttrIPAddress(RTA_DST, family, dest) 468 if nexthop: 469 rtmsg += self._NlAttrIPAddress(RTA_GATEWAY, family, nexthop) 470 if dev: 471 rtmsg += self._NlAttrU32(RTA_OIF, dev) 472 if mark is not None: 473 rtmsg += self._NlAttrU32(RTA_MARK, mark) 474 if uid is not None: 475 rtmsg += self._NlAttrU32(RTA_UID, uid) 476 self._SendNlRequest(command, rtmsg) 477 478 def AddRoute(self, version, table, dest, prefixlen, nexthop, dev): 479 self._Route(version, RTM_NEWROUTE, table, dest, prefixlen, nexthop, dev, 480 None, None) 481 482 def DelRoute(self, version, table, dest, prefixlen, nexthop, dev): 483 self._Route(version, RTM_DELROUTE, table, dest, prefixlen, nexthop, dev, 484 None, None) 485 486 def GetRoutes(self, dest, oif, mark, uid): 487 version = 6 if ":" in dest else 4 488 prefixlen = {4: 32, 6: 128}[version] 489 self._Route(version, RTM_GETROUTE, 0, dest, prefixlen, None, oif, mark, uid) 490 data = self._Recv() 491 # The response will either be an error or a list of routes. 492 if NLMsgHdr(data).type == netlink.NLMSG_ERROR: 493 self._ParseAck(data) 494 routes = self._GetMsgList(RTMsg, data, False) 495 return routes 496 497 def DumpRoutes(self, version, ifindex): 498 ndmsg = NdMsg((self._AddressFamily(version), 0, 0, 0, 0)) 499 return [(m, r) for (m, r) in self._Dump(RTM_GETROUTE, ndmsg, NdMsg, "") 500 if r['RTA_TABLE'] == ifindex] 501 502 def _Neighbour(self, version, is_add, addr, lladdr, dev, state, flags=0): 503 """Adds or deletes a neighbour cache entry.""" 504 family = self._AddressFamily(version) 505 506 # Convert the link-layer address to a raw byte string. 507 if is_add and lladdr: 508 lladdr = lladdr.split(":") 509 if len(lladdr) != 6: 510 raise ValueError("Invalid lladdr %s" % ":".join(lladdr)) 511 lladdr = "".join(chr(int(hexbyte, 16)) for hexbyte in lladdr) 512 513 ndmsg = NdMsg((family, dev, state, 0, RTN_UNICAST)).Pack() 514 ndmsg += self._NlAttrIPAddress(NDA_DST, family, addr) 515 if is_add and lladdr: 516 ndmsg += self._NlAttr(NDA_LLADDR, lladdr) 517 command = RTM_NEWNEIGH if is_add else RTM_DELNEIGH 518 self._SendNlRequest(command, ndmsg, flags) 519 520 def AddNeighbour(self, version, addr, lladdr, dev): 521 self._Neighbour(version, True, addr, lladdr, dev, NUD_PERMANENT) 522 523 def DelNeighbour(self, version, addr, lladdr, dev): 524 self._Neighbour(version, False, addr, lladdr, dev, 0) 525 526 def UpdateNeighbour(self, version, addr, lladdr, dev, state): 527 self._Neighbour(version, True, addr, lladdr, dev, state, 528 flags=netlink.NLM_F_REPLACE) 529 530 def DumpNeighbours(self, version): 531 ndmsg = NdMsg((self._AddressFamily(version), 0, 0, 0, 0)) 532 return self._Dump(RTM_GETNEIGH, ndmsg, NdMsg, "") 533 534 def ParseNeighbourMessage(self, msg): 535 msg, _ = self._ParseNLMsg(msg, NdMsg) 536 return msg 537 538 539if __name__ == "__main__": 540 iproute = IPRoute() 541 iproute.DEBUG = True 542 iproute.DumpRules(6) 543 iproute.DumpLinks() 544 print iproute.GetRoutes("2001:4860:4860::8888", 0, 0, None) 545