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