b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # Copyright 2008, 2009 (C) Jose Vasconcellos <jvasco@verizon.net> |
| 4 | # |
| 5 | # A script that can communicate with jungo-based routers |
| 6 | # (such as MI424-WR, USR8200 and WRV54G) to backup the installed |
| 7 | # firmware and replace the boot loader. |
| 8 | # |
| 9 | # Tested with Python 2.5 on Linux and Windows |
| 10 | # |
| 11 | """Usage: %s [options] <IP_address> [image.bin | url] |
| 12 | Valid options: |
| 13 | \t-h | --help: usage statement |
| 14 | \t-d | --dump: create a flash dump |
| 15 | \t-f | --file: use <filename> to store dump contents |
| 16 | \t-u | --user: provide username (default admin) |
| 17 | \t-p | --pass: provide password (default password1) |
| 18 | \t --port: set port for http (default 8080) |
| 19 | \t-q | --quiet: don't display unnecessary information |
| 20 | \t-r | --reboot: reboot target on successful transfer |
| 21 | \t-V | --version: display version information |
| 22 | |
| 23 | If no image (or url) is given, a flash dump is created. |
| 24 | A built-in http server is used when an image file is provided. |
| 25 | """ |
| 26 | |
| 27 | import os |
| 28 | import sys |
| 29 | import getopt |
| 30 | import getpass |
| 31 | import telnetlib |
| 32 | import string |
| 33 | import binascii |
| 34 | import socket |
| 35 | import _thread |
| 36 | import socketserver |
| 37 | import http.server |
| 38 | |
| 39 | reboot = 0 |
| 40 | HOST = "192.168.1.1" |
| 41 | PORT = 8080 |
| 42 | user = "admin" |
| 43 | #password = getpass.getpass() |
| 44 | password = "password1" |
| 45 | proto = "http" |
| 46 | url = "" |
| 47 | imagefile = "" |
| 48 | dumpfile = "" |
| 49 | verbose = 1 |
| 50 | do_dump = 0 |
| 51 | dumplen = 0x10000 |
| 52 | flashsize=4*1024*1024 |
| 53 | #device="br0" |
| 54 | device="ixp0" |
| 55 | |
| 56 | #################### |
| 57 | |
| 58 | def start_server(server): |
| 59 | httpd = socketserver.TCPServer((server,PORT),http.server.SimpleHTTPRequestHandler) |
| 60 | _thread.start_new_thread(httpd.serve_forever,()) |
| 61 | |
| 62 | #################### |
| 63 | |
| 64 | def get_flash_size(): |
| 65 | # make sure we don't have an A0 stepping |
| 66 | tn.write("cat /proc/cpuinfo\n") |
| 67 | buf = tn.read_until("Returned 0", 3) |
| 68 | if not buf: |
| 69 | print("Unable to obtain CPU information; make sure to not use A0 stepping!") |
| 70 | elif buf.find('rev 0') > 0: |
| 71 | print("Warning: IXP42x stepping A0 detected!") |
| 72 | if imagefile or url: |
| 73 | print("Error: No linux support for A0 stepping!") |
| 74 | sys.exit(2) |
| 75 | |
| 76 | # now get flash size |
| 77 | tn.write("cat /proc/mtd\n") |
| 78 | buf = tn.read_until("Returned 0", 3) |
| 79 | if buf: |
| 80 | i = buf.find('mtd0:') |
| 81 | if i > 0: |
| 82 | return int(buf[i+6:].split()[0],16) |
| 83 | # use different command |
| 84 | tn.write("flash_layout\n") |
| 85 | buf = tn.read_until("Returned 0", 3) |
| 86 | i = buf.rfind('Range ') |
| 87 | if i > 0: |
| 88 | return int(buf[i+17:].split()[0],16) |
| 89 | print("Can't determine flash size!") |
| 90 | else: |
| 91 | print("Unable to obtain flash size!") |
| 92 | sys.exit(2) |
| 93 | |
| 94 | def image_dump(tn, dumpfile): |
| 95 | if not dumpfile: |
| 96 | tn.write("ver\n"); |
| 97 | buf = tn.read_until("Returned 0",2) |
| 98 | i = buf.find("Platform:") |
| 99 | if i < 0: |
| 100 | platform="jungo" |
| 101 | else: |
| 102 | line=buf[i+9:] |
| 103 | i=line.find('\n') |
| 104 | platform=line[:i].split()[-1] |
| 105 | |
| 106 | tn.write("rg_conf_print /dev/%s/mac\n" % device); |
| 107 | buf = tn.read_until("Returned 0",3) |
| 108 | |
| 109 | i = buf.find("mac(") |
| 110 | if i > 0: |
| 111 | i += 4 |
| 112 | else: |
| 113 | print("No MAC address found! (use -f option)") |
| 114 | sys.exit(1) |
| 115 | dumpfile = "%s-%s.bin" % (platform, buf[i:i+17].replace(':','')) |
| 116 | else: |
| 117 | tn.write("\n") |
| 118 | |
| 119 | print("Dumping flash contents (%dMB) to %s" % (flashsize/1048576, dumpfile)) |
| 120 | f = open(dumpfile, "wb") |
| 121 | |
| 122 | t=flashsize/dumplen |
| 123 | for addr in range(t): |
| 124 | if verbose: |
| 125 | sys.stdout.write('\r%d%%'%(100*addr/t)) |
| 126 | sys.stdout.flush() |
| 127 | |
| 128 | tn.write("flash_dump -r 0x%x -l %d -4\n" % (addr*dumplen, dumplen)) |
| 129 | tn.read_until("\n") |
| 130 | |
| 131 | count = addr*dumplen |
| 132 | while 1: |
| 133 | buf = tn.read_until("\n") |
| 134 | if buf.strip() == "Returned 0": |
| 135 | break |
| 136 | s = buf.split() |
| 137 | if s and s[0][-1] == ':': |
| 138 | a=int(s[0][:-1],16) |
| 139 | if a != count: |
| 140 | print("Format error: %x != %x"%(a,count)) |
| 141 | sys.exit(2) |
| 142 | count += 16 |
| 143 | f.write(binascii.a2b_hex(string.join(s[1:],''))) |
| 144 | tn.read_until(">",1) |
| 145 | |
| 146 | f.close() |
| 147 | if verbose: |
| 148 | print("") |
| 149 | |
| 150 | def telnet_option(sock,cmd,option): |
| 151 | #print "Option: %d %d" % (ord(cmd), ord(option)) |
| 152 | if cmd == telnetlib.DO: |
| 153 | c=telnetlib.WILL |
| 154 | elif cmd == telnetlib.WILL: |
| 155 | c=telnetlib.DO |
| 156 | sock.sendall(telnetlib.IAC + c + option) |
| 157 | |
| 158 | def telnet_timeout(): |
| 159 | print("Fatal error: telnet timeout!") |
| 160 | sys.exit(1) |
| 161 | |
| 162 | def usage(): |
| 163 | print(__doc__ % os.path.basename(sys.argv[0])) |
| 164 | |
| 165 | #################### |
| 166 | |
| 167 | try: |
| 168 | opts, args = getopt.getopt(sys.argv[1:], "hdf:qp:P:rvV", \ |
| 169 | ["help", "dump", "file=", "user=", "pass=", "port=", |
| 170 | "quiet=", "reboot", "verbose", "version"]) |
| 171 | except getopt.GetoptError: |
| 172 | # print help information and exit: |
| 173 | usage() |
| 174 | sys.exit(1) |
| 175 | |
| 176 | for o, a in opts: |
| 177 | if o in ("-h", "--help"): |
| 178 | usage() |
| 179 | sys.exit(1) |
| 180 | elif o in ("-V", "--version"): |
| 181 | print("%s: 0.11" % sys.argv[0]) |
| 182 | sys.exit(1) |
| 183 | elif o in ("-d", "--no-dump"): |
| 184 | do_dump = 1 |
| 185 | elif o in ("-f", "--file"): |
| 186 | dumpfile = a |
| 187 | elif o in ("-u", "--user"): |
| 188 | user = a |
| 189 | elif o in ("-p", "--pass"): |
| 190 | password = a |
| 191 | elif o == "--port": |
| 192 | PORT = int(a) |
| 193 | elif o in ("-q", "--quiet"): |
| 194 | verbose = 0 |
| 195 | elif o in ("-r", "--reboot"): |
| 196 | reboot = 1 |
| 197 | elif o in ("-v", "--verbose"): |
| 198 | verbose = 1 |
| 199 | |
| 200 | # make sure we have enough arguments |
| 201 | if len(args) > 0: |
| 202 | HOST = args[0] |
| 203 | |
| 204 | if len(args) == 2: |
| 205 | if args[1].split(':')[0] in ("tftp", "http", "ftp"): |
| 206 | url = args[1] |
| 207 | else: |
| 208 | imagefile = args[1] |
| 209 | else: |
| 210 | do_dump = 1; |
| 211 | |
| 212 | #################### |
| 213 | # create a telnet session to the router |
| 214 | try: |
| 215 | tn = telnetlib.Telnet(HOST) |
| 216 | except socket.error as msg: |
| 217 | print("Unable to establish telnet session to %s: %s" % (HOST, msg)) |
| 218 | sys.exit(1) |
| 219 | |
| 220 | tn.set_option_negotiation_callback(telnet_option) |
| 221 | |
| 222 | buf = tn.read_until("Username: ", 3) |
| 223 | if not buf: |
| 224 | telnet_timeout() |
| 225 | tn.write(user+"\n") |
| 226 | if password: |
| 227 | buf = tn.read_until("Password: ", 3) |
| 228 | if not buf: |
| 229 | telnet_timeout() |
| 230 | tn.write(password+"\n") |
| 231 | |
| 232 | # wait for prompt |
| 233 | buf = tn.read_until("> ", 3) |
| 234 | if not buf: |
| 235 | telnet_timeout() |
| 236 | |
| 237 | flashsize = get_flash_size() |
| 238 | |
| 239 | if do_dump: |
| 240 | image_dump(tn, dumpfile) |
| 241 | |
| 242 | if imagefile or url: |
| 243 | splitpath = os.path.split(imagefile) |
| 244 | |
| 245 | # create load command |
| 246 | if url: |
| 247 | cmd = "load -u %s -r 0\n" % (url) |
| 248 | else: |
| 249 | server = tn.get_socket().getsockname()[0] |
| 250 | cmd = "load -u http://%s:%d/%s -r 0\n" % (server, PORT, splitpath[1]) |
| 251 | |
| 252 | if not os.access(imagefile, os.R_OK): |
| 253 | print("File access error: %s" % (imagefile)) |
| 254 | sys.exit(3) |
| 255 | |
| 256 | # make sure we're in the directory where the image is located |
| 257 | if splitpath[0]: |
| 258 | os.chdir(splitpath[0]) |
| 259 | |
| 260 | start_server(server) |
| 261 | |
| 262 | if verbose: |
| 263 | print("Unlocking flash...") |
| 264 | tn.write("unlock 0 0x%x\n" % flashsize) |
| 265 | buf = tn.read_until("Returned 0",5) |
| 266 | |
| 267 | if verbose: |
| 268 | print("Writing new image...") |
| 269 | print(cmd, end=' ') |
| 270 | tn.write(cmd) |
| 271 | buf = tn.read_until("Returned 0",10) |
| 272 | |
| 273 | # wait till the transfer completed |
| 274 | buf = tn.read_until("Download completed successfully",20) |
| 275 | if buf: |
| 276 | print("Flash update complete!") |
| 277 | if reboot: |
| 278 | tn.write("reboot\n") |
| 279 | print("Rebooting...") |
| 280 | |
| 281 | tn.write("exit\n") |
| 282 | tn.close() |
| 283 | |