1 | #!/usr/bin/env python |
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 SimpleHTTPServer |
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),SimpleHTTPServer.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, 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, |
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 | |
284 | |