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