blob: 1ab61aee86c270edb2864eaf8a4d6ceaa14612ff [file] [log] [blame]
Christopher Wiley2f48d952013-02-22 09:51:47 -08001#!/usr/bin/python
2
3# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6#
7# Implement a pseudo cdma modem.
8#
9# This modem mimics a CDMA modem and allows a user to experiment with
10# a modem which starts in a factory reset state and is gradually moved
11# into a fully activated state.
12#
13# To test you'll need to have a machine with at least 1 ethernet port
14# available to simulate the cellular connection. Assume that it is
15# called eth1.
16# 1. install flimflam-test on your DUT.
17# 2. sudo backchannel setup eth0 pseudo-modem0
18# 3. activation-server &
19# 4. sudo fake-cromo
20# 5. Use the UI to "Activate Test Network"
21#
22
23
24import dbus, glib, gobject, os, subprocess, sys, time
25from optparse import OptionParser
26
27import_path = os.environ.get('SYSROOT', '/usr/local') + '/usr/lib/flimflam/test'
28sys.path.append(import_path)
29
30import flimflam_test
31
32Modem = flimflam_test.Modem
33ModemManager = flimflam_test.ModemManager
34
35class IpPoolRestrictor:
36 def __init__(self, interface):
37 self.interface = interface
38 self.restricted = False
39
40 def enter(self):
41 # Reject all non local tcp traffic, but allow DNS lookups
42 if self.restricted:
43 return
44 subprocess.call(['iptables',
45 '-I', 'INPUT', '1',
46 '-p', 'tcp',
47 '-i', self.interface,
48 '-j', 'REJECT'])
49 self.restricted = True
50
51 def leave(self, force=None):
52 if self.restricted or force:
53 subprocess.call(['iptables',
54 '-D', 'INPUT',
55 '-p', 'tcp',
56 '-i', self.interface,
57 '-j', 'REJECT'])
58 self.restricted = False
59
60restrictor = IpPoolRestrictor('pseudo-modem0')
61
62
63class CarrierState(dbus.service.Object):
64 def __init__(self, bus, path):
65 self.payment_made = False
66 self.restricted = False
67 dbus.service.Object.__init__(self, bus, path)
68
69 @dbus.service.method('org.chromium.ModemManager.Carrier',
70 in_signature = '', out_signature = '')
71 def ProcessPayment(self, *_args, **_kwargs):
72 print "CarrierState: ProcessPayment"
73 self.payment_made = True
74 self.restricted = False
75
76 @dbus.service.method('org.chromium.ModemManager.Carrier',
77 in_signature = '', out_signature = '')
78 def ConsumePlan(self, *_args, **_kwargs):
79 print "CarrierState: ConsumePlan"
80 self.payment_made = False
81 self.restricted = True
82
83
84class FactoryResetModem(Modem):
85
86 def __init__(self, mm, name):
87 Modem.__init__(self, mm, name,
88 mdn='0000001234',
89 activation_state=Modem.NOT_ACTIVATED)
90
91 def Activate(self, s, *_args, **_kwargs):
92 print 'FactoryResetModem: Activate "%s"' % s
93 self.StartActivation(Modem.PARTIALLY_ACTIVATED,
94 self.manager.MakePartiallyActivatedModem,
95 '0015551212')
96
97 # Implement connect as a failure
98 def Connect(self, _props, *_args, **_kwargs):
99 print 'FactoryResetModem: Connect'
100 time.sleep(self.manager.options.connect_delay_ms / 1000.0)
101 self.state = flimflam_test.STATE_CONNECTING
102 glib.timeout_add(500, lambda: self.ConnectDone(
103 self.state,
104 flimflam_test.STATE_REGISTERED,
105 flimflam_test.REASON_USER_REQUESTED))
106 raise flimflam_test.ConnectError()
107
108 def ActivateImpl(self, _s, _args, _kwargs):
109 raise NotImplementedError('Unimplemented. Must implement in subclass.')
110
111
112class PartiallyActivatedModem(Modem):
113
114 def __init__(self, mm, name):
115 Modem.__init__(self, mm, name,
116 mdn='0015551212',
117 activation_state=Modem.PARTIALLY_ACTIVATED)
118
119 def Activate(self, s, *_args, **_kwargs):
120 print 'Partially_ActivatedModem: Activate "%s"' % s
121 carrier = self.manager.carrier
122 if self.manager.options.activatable and carrier.payment_made:
123 self.StartActivation(Modem.ACTIVATED,
124 self.manager.MakeActivatedModem,
125 '6175551212')
126 else:
127 # TODO(jglasgow): define carrier error codes
128 carrier_error = 1
129 self.StartFailedActivation(carrier_error)
130
131 def ConnectDone(self, old, new, why):
132 # Implement ConnectDone by manipulating the IP pool restrictor
133 if new == flimflam_test.STATE_CONNECTED:
134 restrictor.enter()
135 else:
136 restrictor.leave()
137 Modem.ConnectDone(self, old, new, why)
138
139 def ActivateImpl(self, _s, _args, _kwargs):
140 raise NotImplementedError('Unimplemented. Must implement in subclass.')
141
142
143class ActivatedModem(Modem):
144 def __init__(self, mm, name):
145 Modem.__init__(self, mm, name,
146 mdn='6175551212',
147 activation_state=Modem.ACTIVATED)
148
149 def ConnectDone(self, old, new, why):
150 carrier = self.manager.carrier
151 # Implement ConnectDone by manipulating the IP pool restrictor
152 if new == flimflam_test.STATE_CONNECTED and carrier.restricted:
153 restrictor.enter()
154 else:
155 restrictor.leave()
156 Modem.ConnectDone(self, old, new, why)
157
158 def Connect(self, props, *args, **kwargs):
159 print 'ActivatedModem: Connect'
160 kwargs['connect_delay_ms'] = (
161 self.manager.options.connect_delay_ms)
162 Modem.Connect(self, props, *args, **kwargs)
163
164 def ActivateImpl(self, _s, _args, _kwargs):
165 raise NotImplementedError('Unimplemented. Must implement in subclass.')
166
167
168class BrokenActivatedModem(Modem):
169 """ BrokenActivatedModem is a modem that although activated always
170 fails to connect to the network. This simulates errors in which the
171 carrier refuses to allow connections.
172 """
173 def __init__(self, mm, name):
174 Modem.__init__(self, mm, name,
175 mdn='6175551212',
176 activation_state=Modem.ACTIVATED)
177
178 # Implement connect by always failing
179 def Connect(self, _props, *_args, **_kwargs):
180 print 'BrokenActivatedModem: Connect'
181 time.sleep(self.manager.options.connect_delay_ms / 1000.0)
182 self.state = flimflam_test.STATE_CONNECTING
183 glib.timeout_add(500, lambda: self.ConnectDone(
184 self.state,
185 flimflam_test.STATE_REGISTERED,
186 flimflam_test.REASON_USER_REQUESTED))
187 raise flimflam_test.ConnectError()
188
189 def ActivateImpl(self, _s, _args, _kwargs):
190 raise NotImplementedError('Unimplemented. Must implement in subclass.')
191
192
193class Manager(ModemManager):
194 def __init__(self, bus, options):
195 ModemManager.__init__(self, bus, flimflam_test.OCMM)
196 self.modem_number = 1
197 self.options = options
198 self.carrier = CarrierState(bus,
199 '/org/chromium/ModemManager/Carrier')
200
201 def NewModem(self, classname):
202 # modem registeres itself with mm, so does not disappear
203 _ = classname(self, '/TestModem/%d' % self.modem_number)
204 self.modem_number += 1
205
206 def MakeFactoryResetModem(self):
207 self.NewModem(FactoryResetModem)
208
209 def MakePartiallyActivatedModem(self):
210 self.NewModem(PartiallyActivatedModem)
211
212 def MakeActivatedModem(self):
213 if not self.options.activatable:
214 self.NewModem(PartiallyActivatedModem)
215 elif self.options.connectable:
216 self.NewModem(ActivatedModem)
217 else:
218 self.NewModem(BrokenActivatedModem)
219
220
221def main():
222 usage = '''
223Run the fake cromo program to simulate different modem and carrier
224behaviors. By default with no arguments the program will simulate a
225factory reset modem which needs to be activated and requires the user
226to sign up for service.
227
228To test for error cases in which connections to the carrier network
229always fail, use the --unconnectable flag. This is particularly
230useful when the initial state is set to activated as in:
231
232 sudo fake-cromo -u -s activated
233
234This can be used to simulate the conditions of crosbug.com/11355
235
236Another simulation that corresponds to many field error conditions is
237a device that fails OTASP activation. This can be simulated by using
238the -a flag. The device should start in either the factory or partial
239state.
240
241 sudo fake-cromo -a
242
243To test the re-up process, start the modem in the restricted state with
244
245 sudo fake-cromo -s restricted
246
247One can leave the restricted state by fetching
248http://localhost:8080/payment_succeeded.html. This can be done via
249the UI by pressing "Buy Plan", or manually. On the next reconnect the
250user should be out of the restricted IP pool.
251
252If the program is interupted while a partially activated modem is in
253the connected state it may leave the iptables set up in a way that
254causes all tcp traffic to fail, even when using other network
255interfaces. Fix this by restarting fake cromo and tell it to leave
256the restricted IP pool
257
258 sudo fake-cromo -l
259
260'''
261 parser = OptionParser(usage=usage)
262 parser.add_option('-u', '--unconnectable',
263 action='store_false', dest='connectable',
264 default=True,
265 help='Do not allow modem to connect')
266 parser.add_option('-s', '--state', dest='initial_state',
267 type='choice',
268 choices=['factory', 'partial',
269 'activated', 'restricted'],
270 default='factory',
271 help=('Set initial state to factory,'
272 'partial, restricted or activated'))
273 parser.add_option('-a', '--activation_fails',
274 action='store_false', dest='activatable',
275 default=True,
276 help='Do not allow modem to activate')
277 parser.add_option('-l', '--leave-restricted-pool',
278 action='store_true', dest='leave_restricted_pool',
279 default=False,
280 help='Leave the restricted pool and exit')
281 parser.add_option('--connect-delay', type='int',
282 dest='connect_delay_ms',
283 default=flimflam_test.DEFAULT_CONNECT_DELAY_MS,
284 help='time in ms required to connnect')
285
286 (options, args) = parser.parse_args()
287 if len(args) != 0:
288 parser.error("incorrect number of arguments")
289
290 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
291 bus = dbus.SystemBus()
292 mm = Manager(bus, options)
293 mainloop = gobject.MainLoop()
294 print "Running test modemmanager."
295 _ = dbus.service.BusName(flimflam_test.CMM, bus)
296
297 if options.leave_restricted_pool:
298 restrictor.leave(force=True)
299 return 0
300
301 # Choose the type of modem to instantiate...
302 if options.initial_state == 'factory':
303 mm.MakeFactoryResetModem()
304 elif options.initial_state == 'partial':
305 mm.MakePartiallyActivatedModem()
306 elif options.initial_state == 'activated':
307 mm.MakeActivatedModem()
308 elif options.initial_state == 'restricted':
309 mm.carrier.ConsumePlan()
310 mm.MakeActivatedModem()
311 else:
312 print 'Invalid initial state: %s' % options.initial_state
313 return 1
314
315 try:
316 mainloop.run()
317 finally:
318 restrictor.leave(force=True)
319
320
321if __name__ == '__main__':
322 main()