• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
17import fcntl
18import os
19import random
20import re
21from socket import *  # pylint: disable=wildcard-import
22import struct
23import unittest
24
25from scapy import all as scapy
26
27import csocket
28
29# TODO: Move these to csocket.py.
30SOL_IPV6 = 41
31IP_RECVERR = 11
32IPV6_RECVERR = 25
33IP_TRANSPARENT = 19
34IPV6_TRANSPARENT = 75
35IPV6_TCLASS = 67
36IPV6_FLOWLABEL_MGR = 32
37IPV6_FLOWINFO_SEND = 33
38
39SO_BINDTODEVICE = 25
40SO_MARK = 36
41SO_PROTOCOL = 38
42SO_DOMAIN = 39
43SO_COOKIE = 57
44
45ETH_P_IP = 0x0800
46ETH_P_IPV6 = 0x86dd
47
48IPPROTO_GRE = 47
49
50SIOCSIFHWADDR = 0x8924
51
52IPV6_FL_A_GET = 0
53IPV6_FL_A_PUT = 1
54IPV6_FL_A_RENEW = 1
55
56IPV6_FL_F_CREATE = 1
57IPV6_FL_F_EXCL = 2
58
59IPV6_FL_S_NONE = 0
60IPV6_FL_S_EXCL = 1
61IPV6_FL_S_ANY = 255
62
63IFNAMSIZ = 16
64
65IPV4_PING = "\x08\x00\x00\x00\x0a\xce\x00\x03"
66IPV6_PING = "\x80\x00\x00\x00\x0a\xce\x00\x03"
67
68IPV4_ADDR = "8.8.8.8"
69IPV4_ADDR2 = "8.8.4.4"
70IPV6_ADDR = "2001:4860:4860::8888"
71IPV6_ADDR2 = "2001:4860:4860::8844"
72
73IPV6_SEQ_DGRAM_HEADER = ("  sl  "
74                         "local_address                         "
75                         "remote_address                        "
76                         "st tx_queue rx_queue tr tm->when retrnsmt"
77                         "   uid  timeout inode ref pointer drops\n")
78
79UDP_HDR_LEN = 8
80
81# Arbitrary packet payload.
82UDP_PAYLOAD = str(scapy.DNS(rd=1,
83                            id=random.randint(0, 65535),
84                            qd=scapy.DNSQR(qname="wWW.GoOGle.CoM",
85                                           qtype="AAAA")))
86
87# Unix group to use if we want to open sockets as non-root.
88AID_INET = 3003
89
90# Kernel log verbosity levels.
91KERN_INFO = 6
92
93LINUX_VERSION = csocket.LinuxVersion()
94
95
96def GetWildcardAddress(version):
97  return {4: "0.0.0.0", 6: "::"}[version]
98
99def GetIpHdrLength(version):
100  return {4: 20, 6: 40}[version]
101
102def GetAddressFamily(version):
103  return {4: AF_INET, 5: AF_INET6, 6: AF_INET6}[version]
104
105
106def AddressLengthBits(version):
107  return {4: 32, 6: 128}[version]
108
109def GetAddressVersion(address):
110  if ":" not in address:
111    return 4
112  if address.startswith("::ffff"):
113    return 5
114  return 6
115
116def SetSocketTos(s, tos):
117  level = {AF_INET: SOL_IP, AF_INET6: SOL_IPV6}[s.family]
118  option = {AF_INET: IP_TOS, AF_INET6: IPV6_TCLASS}[s.family]
119  s.setsockopt(level, option, tos)
120
121
122def SetNonBlocking(fd):
123  flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
124  fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
125
126
127# Convenience functions to create sockets.
128def Socket(family, sock_type, protocol):
129  s = socket(family, sock_type, protocol)
130  csocket.SetSocketTimeout(s, 5000)
131  return s
132
133
134def PingSocket(family):
135  proto = {AF_INET: IPPROTO_ICMP, AF_INET6: IPPROTO_ICMPV6}[family]
136  return Socket(family, SOCK_DGRAM, proto)
137
138
139def IPv4PingSocket():
140  return PingSocket(AF_INET)
141
142
143def IPv6PingSocket():
144  return PingSocket(AF_INET6)
145
146
147def TCPSocket(family):
148  s = Socket(family, SOCK_STREAM, IPPROTO_TCP)
149  SetNonBlocking(s.fileno())
150  return s
151
152
153def IPv4TCPSocket():
154  return TCPSocket(AF_INET)
155
156
157def IPv6TCPSocket():
158  return TCPSocket(AF_INET6)
159
160
161def UDPSocket(family):
162  return Socket(family, SOCK_DGRAM, IPPROTO_UDP)
163
164
165def RawGRESocket(family):
166  s = Socket(family, SOCK_RAW, IPPROTO_GRE)
167  return s
168
169
170def BindRandomPort(version, sock):
171  addr = {4: "0.0.0.0", 5: "::", 6: "::"}[version]
172  sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
173  sock.bind((addr, 0))
174  if sock.getsockopt(SOL_SOCKET, SO_PROTOCOL) == IPPROTO_TCP:
175    sock.listen(100)
176  port = sock.getsockname()[1]
177  return port
178
179
180def EnableFinWait(sock):
181  # Disabling SO_LINGER causes sockets to go into FIN_WAIT on close().
182  sock.setsockopt(SOL_SOCKET, SO_LINGER, struct.pack("ii", 0, 0))
183
184
185def DisableFinWait(sock):
186  # Enabling SO_LINGER with a timeout of zero causes close() to send RST.
187  sock.setsockopt(SOL_SOCKET, SO_LINGER, struct.pack("ii", 1, 0))
188
189
190def CreateSocketPair(family, socktype, addr):
191  clientsock = socket(family, socktype, 0)
192  listensock = socket(family, socktype, 0)
193  listensock.bind((addr, 0))
194  addr = listensock.getsockname()
195  if socktype == SOCK_STREAM:
196    listensock.listen(1)
197  clientsock.connect(listensock.getsockname())
198  if socktype == SOCK_STREAM:
199    acceptedsock, _ = listensock.accept()
200    DisableFinWait(clientsock)
201    DisableFinWait(acceptedsock)
202    listensock.close()
203  else:
204    listensock.connect(clientsock.getsockname())
205    acceptedsock = listensock
206  return clientsock, acceptedsock
207
208
209def GetInterfaceIndex(ifname):
210  s = UDPSocket(AF_INET)
211  ifr = struct.pack("%dsi" % IFNAMSIZ, ifname, 0)
212  ifr = fcntl.ioctl(s, scapy.SIOCGIFINDEX, ifr)
213  return struct.unpack("%dsi" % IFNAMSIZ, ifr)[1]
214
215
216def SetInterfaceHWAddr(ifname, hwaddr):
217  s = UDPSocket(AF_INET)
218  hwaddr = hwaddr.replace(":", "")
219  hwaddr = hwaddr.decode("hex")
220  if len(hwaddr) != 6:
221    raise ValueError("Unknown hardware address length %d" % len(hwaddr))
222  ifr = struct.pack("%dsH6s" % IFNAMSIZ, ifname, scapy.ARPHDR_ETHER, hwaddr)
223  fcntl.ioctl(s, SIOCSIFHWADDR, ifr)
224
225
226def SetInterfaceState(ifname, up):
227  s = UDPSocket(AF_INET)
228  ifr = struct.pack("%dsH" % IFNAMSIZ, ifname, 0)
229  ifr = fcntl.ioctl(s, scapy.SIOCGIFFLAGS, ifr)
230  _, flags = struct.unpack("%dsH" % IFNAMSIZ, ifr)
231  if up:
232    flags |= scapy.IFF_UP
233  else:
234    flags &= ~scapy.IFF_UP
235  ifr = struct.pack("%dsH" % IFNAMSIZ, ifname, flags)
236  ifr = fcntl.ioctl(s, scapy.SIOCSIFFLAGS, ifr)
237
238
239def SetInterfaceUp(ifname):
240  return SetInterfaceState(ifname, True)
241
242
243def SetInterfaceDown(ifname):
244  return SetInterfaceState(ifname, False)
245
246
247def CanonicalizeIPv6Address(addr):
248  return inet_ntop(AF_INET6, inet_pton(AF_INET6, addr))
249
250
251def FormatProcAddress(unformatted):
252  groups = []
253  for i in xrange(0, len(unformatted), 4):
254    groups.append(unformatted[i:i+4])
255  formatted = ":".join(groups)
256  # Compress the address.
257  address = CanonicalizeIPv6Address(formatted)
258  return address
259
260
261def FormatSockStatAddress(address):
262  if ":" in address:
263    family = AF_INET6
264  else:
265    family = AF_INET
266  binary = inet_pton(family, address)
267  out = ""
268  for i in xrange(0, len(binary), 4):
269    out += "%08X" % struct.unpack("=L", binary[i:i+4])
270  return out
271
272
273def GetLinkAddress(ifname, linklocal):
274  addresses = open("/proc/net/if_inet6").readlines()
275  for address in addresses:
276    address = [s for s in address.strip().split(" ") if s]
277    if address[5] == ifname:
278      if (linklocal and address[0].startswith("fe80")
279          or not linklocal and not address[0].startswith("fe80")):
280        # Convert the address from raw hex to something with colons in it.
281        return FormatProcAddress(address[0])
282  return None
283
284
285def GetDefaultRoute(version=6):
286  if version == 6:
287    routes = open("/proc/net/ipv6_route").readlines()
288    for route in routes:
289      route = [s for s in route.strip().split(" ") if s]
290      if (route[0] == "00000000000000000000000000000000" and route[1] == "00"
291          # Routes in non-default tables end up in /proc/net/ipv6_route!!!
292          and route[9] != "lo" and not route[9].startswith("nettest")):
293        return FormatProcAddress(route[4]), route[9]
294    raise ValueError("No IPv6 default route found")
295  elif version == 4:
296    routes = open("/proc/net/route").readlines()
297    for route in routes:
298      route = [s for s in route.strip().split("\t") if s]
299      if route[1] == "00000000" and route[7] == "00000000":
300        gw, iface = route[2], route[0]
301        gw = inet_ntop(AF_INET, gw.decode("hex")[::-1])
302        return gw, iface
303    raise ValueError("No IPv4 default route found")
304  else:
305    raise ValueError("Don't know about IPv%s" % version)
306
307
308def GetDefaultRouteInterface():
309  unused_gw, iface = GetDefaultRoute()
310  return iface
311
312
313def MakeFlowLabelOption(addr, label):
314  # struct in6_flowlabel_req {
315  #         struct in6_addr flr_dst;
316  #         __be32  flr_label;
317  #         __u8    flr_action;
318  #         __u8    flr_share;
319  #         __u16   flr_flags;
320  #         __u16   flr_expires;
321  #         __u16   flr_linger;
322  #         __u32   __flr_pad;
323  #         /* Options in format of IPV6_PKTOPTIONS */
324  # };
325  fmt = "16sIBBHHH4s"
326  assert struct.calcsize(fmt) == 32
327  addr = inet_pton(AF_INET6, addr)
328  assert len(addr) == 16
329  label = htonl(label & 0xfffff)
330  action = IPV6_FL_A_GET
331  share = IPV6_FL_S_ANY
332  flags = IPV6_FL_F_CREATE
333  pad = "\x00" * 4
334  return struct.pack(fmt, addr, label, action, share, flags, 0, 0, pad)
335
336
337def SetFlowLabel(s, addr, label):
338  opt = MakeFlowLabelOption(addr, label)
339  s.setsockopt(SOL_IPV6, IPV6_FLOWLABEL_MGR, opt)
340  # Caller also needs to do s.setsockopt(SOL_IPV6, IPV6_FLOWINFO_SEND, 1).
341
342
343def RunIptablesCommand(version, args):
344  iptables = {4: "iptables", 6: "ip6tables"}[version]
345  iptables_path = "/sbin/" + iptables
346  if not os.access(iptables_path, os.X_OK):
347    iptables_path = "/system/bin/" + iptables
348  return os.spawnvp(os.P_WAIT, iptables_path, [iptables_path] + args.split(" "))
349
350# Determine network configuration.
351try:
352  GetDefaultRoute(version=4)
353  HAVE_IPV4 = True
354except ValueError:
355  HAVE_IPV4 = False
356
357try:
358  GetDefaultRoute(version=6)
359  HAVE_IPV6 = True
360except ValueError:
361  HAVE_IPV6 = False
362
363class RunAsUidGid(object):
364  """Context guard to run a code block as a given UID."""
365
366  def __init__(self, uid, gid):
367    self.uid = uid
368    self.gid = gid
369
370  def __enter__(self):
371    if self.uid:
372      self.saved_uids = os.getresuid()
373      self.saved_groups = os.getgroups()
374      os.setgroups(self.saved_groups + [AID_INET])
375      os.setresuid(self.uid, self.uid, self.saved_uids[0])
376    if self.gid:
377      self.saved_gid = os.getgid()
378      os.setgid(self.gid)
379
380  def __exit__(self, unused_type, unused_value, unused_traceback):
381    if self.uid:
382      os.setresuid(*self.saved_uids)
383      os.setgroups(self.saved_groups)
384    if self.gid:
385      os.setgid(self.saved_gid)
386
387class RunAsUid(RunAsUidGid):
388  """Context guard to run a code block as a given GID and UID."""
389
390  def __init__(self, uid):
391    RunAsUidGid.__init__(self, uid, 0)
392
393class NetworkTest(unittest.TestCase):
394
395  def assertRaisesErrno(self, err_num, f=None, *args):
396    """Test that the system returns an errno error.
397
398    This works similarly to unittest.TestCase.assertRaises. You can call it as
399    an assertion, or use it as a context manager.
400    e.g.
401        self.assertRaisesErrno(errno.ENOENT, do_things, arg1, arg2)
402    or
403        with self.assertRaisesErrno(errno.ENOENT):
404          do_things(arg1, arg2)
405
406    Args:
407      err_num: an errno constant
408      f: (optional) A callable that should result in error
409      *args: arguments passed to f
410    """
411    msg = os.strerror(err_num)
412    if f is None:
413      return self.assertRaisesRegexp(EnvironmentError, msg)
414    else:
415      self.assertRaisesRegexp(EnvironmentError, msg, f, *args)
416
417  def ReadProcNetSocket(self, protocol):
418    # Read file.
419    filename = "/proc/net/%s" % protocol
420    lines = open(filename).readlines()
421
422    # Possibly check, and strip, header.
423    if protocol in ["icmp6", "raw6", "udp6"]:
424      self.assertEqual(IPV6_SEQ_DGRAM_HEADER, lines[0])
425    lines = lines[1:]
426
427    # Check contents.
428    if protocol.endswith("6"):
429      addrlen = 32
430    else:
431      addrlen = 8
432
433    if protocol.startswith("tcp"):
434      # Real sockets have 5 extra numbers, timewait sockets have none.
435      end_regexp = "(| +[0-9]+ [0-9]+ [0-9]+ [0-9]+ -?[0-9]+)$"
436    elif re.match("icmp|udp|raw", protocol):
437      # Drops.
438      end_regexp = " +([0-9]+) *$"
439    else:
440      raise ValueError("Don't know how to parse %s" % filename)
441
442    regexp = re.compile(r" *(\d+): "                    # bucket
443                        "([0-9A-F]{%d}:[0-9A-F]{4}) "   # srcaddr, port
444                        "([0-9A-F]{%d}:[0-9A-F]{4}) "   # dstaddr, port
445                        "([0-9A-F][0-9A-F]) "           # state
446                        "([0-9A-F]{8}:[0-9A-F]{8}) "    # mem
447                        "([0-9A-F]{2}:[0-9A-F]{8}) "    # ?
448                        "([0-9A-F]{8}) +"               # ?
449                        "([0-9]+) +"                    # uid
450                        "([0-9]+) +"                    # timeout
451                        "([0-9]+) +"                    # inode
452                        "([0-9]+) +"                    # refcnt
453                        "([0-9a-f]+)"                   # sp
454                        "%s"                            # icmp has spaces
455                        % (addrlen, addrlen, end_regexp))
456    # Return a list of lists with only source / dest addresses for now.
457    # TODO: consider returning a dict or namedtuple instead.
458    out = []
459    for line in lines:
460      m = regexp.match(line)
461      if m is None:
462        raise ValueError("Failed match on [%s]" % line)
463      (_, src, dst, state, mem,
464       _, _, uid, _, _, refcnt, _, extra) = m.groups()
465      out.append([src, dst, state, mem, uid, refcnt, extra])
466    return out
467
468  @staticmethod
469  def GetConsoleLogLevel():
470    return int(open("/proc/sys/kernel/printk").readline().split()[0])
471
472  @staticmethod
473  def SetConsoleLogLevel(level):
474    return open("/proc/sys/kernel/printk", "w").write("%s\n" % level)
475
476
477if __name__ == "__main__":
478  unittest.main()
479