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