1 | # Example file for the dbg_interactive.fdx extension. |
---|
2 | # |
---|
3 | # This extension provides an interactive python interpreter console that allows |
---|
4 | # interacting with freeDiameter framework. |
---|
5 | # |
---|
6 | # The adaptation layer between Python and C is provided by SWIG (http://swig.org). |
---|
7 | # You may refer to SWIG documentation for more information on how the wrapper is generated and used. |
---|
8 | # The name of the module wrapping freeDiameter framework is: _fDpy |
---|
9 | # |
---|
10 | # Similar to all freeDiameter extensions, an optional filename can be specified in the |
---|
11 | # main freeDiameter.conf configuration file for the dbg_interactive.fdx extension. |
---|
12 | # If such file is provided, it will be passed to the python interpreter as a python script |
---|
13 | # to execute. Otherwise, the interpreter will be interactive. |
---|
14 | # |
---|
15 | # SWIG deals with structures as follow: |
---|
16 | # Given the structure: |
---|
17 | # struct foo { int a; } |
---|
18 | # The following functions are available to python (their C equivalent processing is given in [ ]): |
---|
19 | # s = new_foo() [ s = calloc(1, sizeof(struct foo)) ] |
---|
20 | # foo_a_set(s, 2) [ s->a = 2 ] |
---|
21 | # foo_a_get(s) [ returns s->a value ] |
---|
22 | # delete_foo(s) [ free(s) ] |
---|
23 | # |
---|
24 | # In addition, thanks to the proxy (aka shadow) class, we can also do the more user-friendly: |
---|
25 | # s = foo() |
---|
26 | # s.a = 2 |
---|
27 | # s.a |
---|
28 | # del s |
---|
29 | # |
---|
30 | |
---|
31 | # The remaining of this file gives some examples of how to use the python interpreter. |
---|
32 | # Note that at the moment not 100% of the framework is usable. |
---|
33 | # You may have to extend some classes or write some typemaps in the source code |
---|
34 | # of the extension to do what you want. |
---|
35 | |
---|
36 | |
---|
37 | ############# Compilation-time constants (from freeDiameter-host.h) ############ |
---|
38 | |
---|
39 | # Display current version |
---|
40 | print "%s %d.%d.%d" % (FD_PROJECT_NAME, FD_PROJECT_VERSION_MAJOR, FD_PROJECT_VERSION_MINOR, FD_PROJECT_VERSION_REV) |
---|
41 | |
---|
42 | |
---|
43 | ############# Debug ############ |
---|
44 | |
---|
45 | # Change the global debug level of the framework (cvar contains all global variables) |
---|
46 | cvar.fd_g_debug_lvl = 1 |
---|
47 | |
---|
48 | |
---|
49 | # Turn on debug for a specific function (if framework compiled with DEBUG support) |
---|
50 | cvar.fd_debug_one_function = "gc_th_fct" |
---|
51 | |
---|
52 | |
---|
53 | # Print messages to freeDiameter's debug facility |
---|
54 | # Note: the python version does not support printf-like argument list. The formating should be done in python. |
---|
55 | # See SWIG documentation about varargs functions for more information. |
---|
56 | fd_log(FD_LOG_NOTICE, "3 + 4 = %d" % (7)) |
---|
57 | |
---|
58 | |
---|
59 | # Display some framework state information |
---|
60 | conf = fd_conf_dump(); |
---|
61 | print conf; |
---|
62 | |
---|
63 | fd_peer_dump_list(0) |
---|
64 | fd_servers_dump(0) |
---|
65 | fd_ext_dump(0) |
---|
66 | |
---|
67 | |
---|
68 | ############# Global variables ############ |
---|
69 | |
---|
70 | # Display the local Diameter Identity: |
---|
71 | print "Local Diameter Identity:", cvar.fd_g_config.cnf_diamid |
---|
72 | |
---|
73 | # Display realm, using the low-level functions (skip proxy classe definitions): |
---|
74 | print "Realm:", _fDpy.fd_config_cnf_diamrlm_get(_fDpy.cvar.fd_g_config) |
---|
75 | |
---|
76 | |
---|
77 | |
---|
78 | ############# Lists ############ |
---|
79 | |
---|
80 | # Note: we use different names from the C API here, for usability. |
---|
81 | l1 = fd_list() # Will be our sentinel |
---|
82 | l2 = fd_list() |
---|
83 | l3 = fd_list() |
---|
84 | l1.isempty() |
---|
85 | l1.insert_next(l2) # l1 -> l2 |
---|
86 | l1.isempty() |
---|
87 | l1.insert_prev(l3) # l1 -> l2 -> l3 (circular list) |
---|
88 | l1.dump() |
---|
89 | l3.detach() # l1 -> l2 |
---|
90 | l4=fd_list() |
---|
91 | l5=fd_list() |
---|
92 | l3.insert_next(l4) # l3 -> l4 |
---|
93 | l3.insert_next(l5) # l3 -> l5 -> l4 |
---|
94 | l1.concat(l3) # l1 -> l2 -> l5 -> l4 |
---|
95 | |
---|
96 | elements = l1.enum_as() # default: enumerates as fd_list. Warning: this a copy, changing the python list has no effect on the underlying fd_list. |
---|
97 | for li in elements: |
---|
98 | li.dump() |
---|
99 | |
---|
100 | del elements |
---|
101 | del l2 |
---|
102 | del l3 |
---|
103 | del l4 |
---|
104 | del l5 |
---|
105 | l1.isempty() # The destructor has an implicit fd_list_unlink call |
---|
106 | del l1 |
---|
107 | |
---|
108 | |
---|
109 | ############# Hash ############ |
---|
110 | |
---|
111 | hex(fd_os_hash("hello world")) # It accepts binary data |
---|
112 | |
---|
113 | |
---|
114 | ############# Dictionary ############ |
---|
115 | |
---|
116 | ##### Create a dedicated dictionary for our tests |
---|
117 | d = dictionary() |
---|
118 | d.dump() |
---|
119 | |
---|
120 | # New vendor |
---|
121 | v = dict_vendor_data() |
---|
122 | v.vendor_id = 123 |
---|
123 | v.vendor_name = "My test vendor" |
---|
124 | my_vendor = d.new_obj(DICT_VENDOR, v) |
---|
125 | del v |
---|
126 | d.dump() |
---|
127 | d.vendors_list() |
---|
128 | |
---|
129 | # Compact invocation also possible: |
---|
130 | v2 = dict_vendor_data(124, "My test vendor 2") |
---|
131 | del v2 |
---|
132 | |
---|
133 | # New application |
---|
134 | a = dict_application_data() |
---|
135 | a.application_id = 99 |
---|
136 | a.application_name = "My test appl" |
---|
137 | my_appl = d.new_obj(DICT_APPLICATION, a, my_vendor) |
---|
138 | del a |
---|
139 | |
---|
140 | a2 = dict_application_data(99, "My test appl 2") |
---|
141 | del a2 |
---|
142 | |
---|
143 | # New type (callbacks are not supported yet...) |
---|
144 | t = dict_type_data() |
---|
145 | t.type_base = AVP_TYPE_INTEGER32 |
---|
146 | t.type_name = "My integer AVP" |
---|
147 | my_type_int = d.new_obj(DICT_TYPE, t, my_appl) |
---|
148 | t.type_base = AVP_TYPE_OCTETSTRING |
---|
149 | t.type_name = "My binary buffer AVP" |
---|
150 | my_type_os = d.new_obj(DICT_TYPE, t, my_appl) |
---|
151 | del t |
---|
152 | |
---|
153 | t2 = dict_type_data(AVP_TYPE_UNSIGNED32, "u32 type") |
---|
154 | del t2 |
---|
155 | |
---|
156 | # Constants |
---|
157 | c = dict_enumval_data() |
---|
158 | c.enum_name = "AVP_VALUE_TROIS" |
---|
159 | c.enum_value.i32 = 3 |
---|
160 | d.new_obj(DICT_ENUMVAL, c, my_type_int) |
---|
161 | |
---|
162 | c.enum_name = "A_BUFFER_CONSTANT" |
---|
163 | c.enum_value.os = "This is a very long AVP value that we prefer to represent as a constant" |
---|
164 | c.enum_value.os.dump() |
---|
165 | d.new_obj(DICT_ENUMVAL, c, my_type_os) |
---|
166 | del c |
---|
167 | |
---|
168 | c2 = dict_enumval_data("enum 23", 23) # The constructor only accepts unsigned32, for other values, set them afterwards |
---|
169 | c3 = dict_enumval_data("enum other") |
---|
170 | c3.os = "other value" |
---|
171 | del c2 |
---|
172 | del c3 |
---|
173 | |
---|
174 | # AVP |
---|
175 | a = dict_avp_data() |
---|
176 | a.avp_code = 234 |
---|
177 | a.avp_name = "my integer avp" |
---|
178 | a.avp_flag_mask = AVP_FLAG_MANDATORY |
---|
179 | a.avp_basetype = AVP_TYPE_INTEGER32 |
---|
180 | my_avp_int = d.new_obj(DICT_AVP, a, my_type_int) |
---|
181 | |
---|
182 | a.avp_vendor = 123 |
---|
183 | a.avp_name = "my OS avp" |
---|
184 | a.avp_flag_mask = AVP_FLAG_MANDATORY + AVP_FLAG_VENDOR |
---|
185 | a.avp_flag_val = AVP_FLAG_VENDOR |
---|
186 | a.avp_basetype = AVP_TYPE_OCTETSTRING |
---|
187 | my_avp_os = d.new_obj(DICT_AVP, a, my_type_os) |
---|
188 | del a |
---|
189 | |
---|
190 | a2 = dict_avp_data(235, "no vendor, not mandatory", AVP_TYPE_OCTETSTRING) |
---|
191 | a3 = dict_avp_data(236, "vendor 12, not mandatory", AVP_TYPE_OCTETSTRING, 12) |
---|
192 | a4 = dict_avp_data(237, "vendor 12, mandatory", AVP_TYPE_OCTETSTRING, 12, 1) |
---|
193 | a5 = dict_avp_data(238, "no vendor, mandatory", AVP_TYPE_OCTETSTRING, 0, 1) |
---|
194 | del a2 |
---|
195 | del a3 |
---|
196 | del a4 |
---|
197 | del a5 |
---|
198 | |
---|
199 | |
---|
200 | # Command |
---|
201 | c = dict_cmd_data() |
---|
202 | c.cmd_code = 345 |
---|
203 | c.cmd_name = "My-Python-Request" |
---|
204 | c.cmd_flag_mask = CMD_FLAG_REQUEST + CMD_FLAG_PROXIABLE |
---|
205 | c.cmd_flag_val = CMD_FLAG_REQUEST + CMD_FLAG_PROXIABLE |
---|
206 | my_req = d.new_obj(DICT_COMMAND, c, my_appl) |
---|
207 | c.cmd_name = "My-Python-Answer" |
---|
208 | c.cmd_flag_val = CMD_FLAG_PROXIABLE |
---|
209 | my_ans = d.new_obj(DICT_COMMAND, c, my_appl) |
---|
210 | del c |
---|
211 | |
---|
212 | c2 = dict_cmd_data(346, "Second-Request", 1) # Default created with PROXIABLE flag. |
---|
213 | c3 = dict_cmd_data(346, "Second-Answer", 0) |
---|
214 | del c2 |
---|
215 | del c3 |
---|
216 | |
---|
217 | # Rule |
---|
218 | r = dict_rule_data() |
---|
219 | r.rule_avp = my_avp_int |
---|
220 | r.rule_position = RULE_REQUIRED |
---|
221 | r.rule_min = -1 |
---|
222 | r.rule_max = -1 |
---|
223 | d.new_obj(DICT_RULE, r, my_req) |
---|
224 | d.new_obj(DICT_RULE, r, my_ans) |
---|
225 | r.rule_avp = my_avp_os |
---|
226 | d.new_obj(DICT_RULE, r, my_req) |
---|
227 | d.new_obj(DICT_RULE, r, my_ans) |
---|
228 | del r |
---|
229 | |
---|
230 | r2 = dict_rule_data(my_avp_int, RULE_REQUIRED) # min & max are optional parameters, default to -1 |
---|
231 | r3 = dict_rule_data(my_avp_int, RULE_REQUIRED, 2, 3) # min is 2, max is 3 |
---|
232 | r4 = dict_rule_data(my_avp_int, RULE_FIXED_HEAD) # The r4.rule_order = 1 by default, change afterwards if needed. |
---|
233 | del r2 |
---|
234 | del r3 |
---|
235 | del r4 |
---|
236 | |
---|
237 | d.dump() |
---|
238 | del d |
---|
239 | |
---|
240 | ####### Now play with the "real" dictionary |
---|
241 | |
---|
242 | gdict = cvar.fd_g_config.cnf_dict |
---|
243 | |
---|
244 | appl = gdict.search ( DICT_APPLICATION, APPLICATION_BY_ID, 3 ) |
---|
245 | appl.dump() |
---|
246 | avp = gdict.search ( DICT_AVP, AVP_BY_NAME, "Origin-Host") |
---|
247 | avp.dump() |
---|
248 | errcmd = gdict.error_cmd() |
---|
249 | |
---|
250 | v = avp.getval() |
---|
251 | print v.avp_code |
---|
252 | del v |
---|
253 | |
---|
254 | t = avp.gettype() |
---|
255 | print t |
---|
256 | del t |
---|
257 | |
---|
258 | dict = avp.getdict() |
---|
259 | del dict |
---|
260 | |
---|
261 | |
---|
262 | ############# Sessions ############ |
---|
263 | |
---|
264 | # handler |
---|
265 | def my_cleanup(state,sid): |
---|
266 | print "Cleaning up python state for session:", sid |
---|
267 | print "Received state:", state |
---|
268 | del state |
---|
269 | |
---|
270 | hdl = session_handler(my_cleanup) |
---|
271 | hdl.dump() |
---|
272 | del hdl |
---|
273 | |
---|
274 | # Session |
---|
275 | hdl = session_handler(my_cleanup) |
---|
276 | s1 = session() |
---|
277 | s1.getsid() |
---|
278 | s2 = session("this.is.a.full.session.id") |
---|
279 | r,s3,isnew = fd_sess_fromsid("this.is.a.full.session.id") # use this call if "isnew" is really needed... |
---|
280 | s4 = session("host.id", "optional.part") |
---|
281 | s4.settimeout(30) # the python wrapper takes a number of seconds as parameter for simplicity |
---|
282 | s4.dump() |
---|
283 | |
---|
284 | # states |
---|
285 | mystate = [ 34, "blah", [ 32, 12 ] ] |
---|
286 | s1.store(hdl, mystate) |
---|
287 | del mystate |
---|
288 | gotstate = s1.retrieve(hdl) |
---|
289 | print gotstate |
---|
290 | del gotstate |
---|
291 | |
---|
292 | |
---|
293 | ############# Routing ############ |
---|
294 | |
---|
295 | rd = rt_data() |
---|
296 | |
---|
297 | rd.add("p1.testbed.aaa", "testbed.aaa") |
---|
298 | rd.add("p2.testbed.aaa", "testbed.aaa") |
---|
299 | rd.add("p3.testbed.aaa", "testbed.aaa") |
---|
300 | rd.add("p4.testbed.aaa", "testbed.aaa") |
---|
301 | |
---|
302 | rd.remove("p2.testbed.aaa") |
---|
303 | |
---|
304 | rd.error("p3.testbed.aaa", "relay.testbed.aaa", 3002) |
---|
305 | |
---|
306 | list = rd.extract(-1) |
---|
307 | for c in list.enum_as("struct rtd_candidate *"): |
---|
308 | print "%s (%s): %s" % (c.diamid, c.realm, c.score) |
---|
309 | |
---|
310 | del rd |
---|
311 | |
---|
312 | |
---|
313 | # A rt_fwd callback has the following prototype: |
---|
314 | def my_rtfwd_cb(msg): |
---|
315 | print "Forwarding the following message:" |
---|
316 | msg.dump() |
---|
317 | return [ 0, msg ] # return None instead of msg to stop forwarding. |
---|
318 | |
---|
319 | fwdhdl = fd_rt_fwd_hdl( my_rtfwd_cb, RT_FWD_REQ ) |
---|
320 | |
---|
321 | |
---|
322 | # A rt_out cb has the following prototype: |
---|
323 | def my_rtout_cb(msg, list): |
---|
324 | print "Sending out the following message:" |
---|
325 | msg.dump() |
---|
326 | print "The possible candidates are:" |
---|
327 | for c in list.enum_as("struct rtd_candidate *"): |
---|
328 | print "%s (%s): %s" % (c.diamid, c.realm, c.score) |
---|
329 | return 0 # returns an error code (standard errno values) |
---|
330 | |
---|
331 | outhdl = fd_rt_out_hdl( my_rtout_cb ) # a priority can be specified as 2nd parameter, default is 0. |
---|
332 | |
---|
333 | |
---|
334 | |
---|
335 | |
---|
336 | |
---|
337 | ############# Messages, AVPs ############ |
---|
338 | |
---|
339 | ## AVP |
---|
340 | |
---|
341 | # Create empty |
---|
342 | blank_avp = avp() |
---|
343 | del blank_avp |
---|
344 | |
---|
345 | # Create from dictionary definitions |
---|
346 | oh = avp(cvar.fd_g_config.cnf_dict.search ( DICT_AVP, AVP_BY_NAME, "Origin-Host")) # Octet String |
---|
347 | vi = avp(cvar.fd_g_config.cnf_dict.search ( DICT_AVP, AVP_BY_NAME, "Vendor-Id")) # U32 |
---|
348 | vsai = avp(cvar.fd_g_config.cnf_dict.search ( DICT_AVP, AVP_BY_NAME, "Vendor-Specific-Application-Id")) # Grouped |
---|
349 | |
---|
350 | # Set values |
---|
351 | val = avp_value() |
---|
352 | val.u32 = 123 |
---|
353 | vi.setval(None) # this cleans a previous value (usually not needed) |
---|
354 | vi.setval(val) |
---|
355 | val.os = "my.origin.host" |
---|
356 | oh.setval(val) |
---|
357 | vsai.add_child(vi) # call as add_child(vi, 1) to add the new AVP at the beginning, default is at the end |
---|
358 | |
---|
359 | # It is possible to initialize the AVP with a blank value as follow: |
---|
360 | blank_with_value = avp(None, AVPFL_SET_BLANK_VALUE) |
---|
361 | # it enables this without doing the setval call: |
---|
362 | blank_with_value.header().avp_value.u32 = 12 |
---|
363 | |
---|
364 | |
---|
365 | ## Messages |
---|
366 | |
---|
367 | # Create empt (as for avps, pass None or a dictionary object as 1st param, and flags as optional 2nd param)y |
---|
368 | a_msg = msg() |
---|
369 | a_msg.dump() |
---|
370 | del a_msg |
---|
371 | |
---|
372 | # It is also possible to pass MSGFL_* flags in second parameter (ALLOC_ETEID is default) |
---|
373 | msg_no_eid = msg(None, 0) |
---|
374 | msg_no_eid.dump() |
---|
375 | del msg_no_eid |
---|
376 | |
---|
377 | # Create from dictionary |
---|
378 | dwr_dict = cvar.fd_g_config.cnf_dict.search ( DICT_COMMAND, CMD_BY_NAME, "Device-Watchdog-Request" ) |
---|
379 | dwr = msg(dwr_dict) |
---|
380 | dwr.dump() |
---|
381 | |
---|
382 | # Create msg from a binary buffer (then you should call parse_dict and parse_rules methods) |
---|
383 | dwr2 = msg("\x01\x00\x00\x14\x80\x00\x01\x18\x00\x00\x00\x00\x00\x00\x00\x00\x1b\xf0\x00\x01") |
---|
384 | |
---|
385 | # Create answer from request (optional parameters: dictionary to use, and flags): |
---|
386 | dwr3 = msg(cvar.fd_g_config.cnf_dict.search ( DICT_COMMAND, CMD_BY_NAME, "Device-Watchdog-Request" )) |
---|
387 | dwa3 = dwr3.create_answer() |
---|
388 | dwr3cpy = dwa3.get_query() |
---|
389 | |
---|
390 | |
---|
391 | ## Other functions with AVPs & messages |
---|
392 | |
---|
393 | # Add the AVPs in the message |
---|
394 | dwr.add_child(oh) |
---|
395 | oh.add_next(vsai) # equivalent to add_child on the parent |
---|
396 | |
---|
397 | # Create a network byte buffer from the message |
---|
398 | dwr.bufferize() |
---|
399 | |
---|
400 | # Get first child AVP (fast) |
---|
401 | avp = dwr.first_child() |
---|
402 | |
---|
403 | # then: |
---|
404 | avp = avp.get_next() # when last AVP, returns None |
---|
405 | |
---|
406 | |
---|
407 | # Get all 1st level children (slower) -- warning, changes to the python list will not be reflected on the underlying message. read-only use. |
---|
408 | dwr.children() |
---|
409 | # example use: |
---|
410 | for a in dwr.children(): |
---|
411 | a.dump(0) # 0 means: dump only this object, do not walk the tree |
---|
412 | |
---|
413 | |
---|
414 | # Search the first AVP of a given type |
---|
415 | oh_dict = cvar.fd_g_config.cnf_dict.search( DICT_AVP, AVP_BY_NAME, "Origin-Host") |
---|
416 | oh = dwr.search( oh_dict ) |
---|
417 | |
---|
418 | # After adding AVPs, the length in the message header is outdated, refresh as follow: |
---|
419 | dwr.update_length() |
---|
420 | |
---|
421 | # Get dictionary model for a message or avp |
---|
422 | dwr.model() |
---|
423 | oh.model().dump() |
---|
424 | |
---|
425 | # Retrieve the header of messages & avp: |
---|
426 | dwr_hdr = dwr.header() |
---|
427 | dwr_hdr.msg_version |
---|
428 | dwr_hdr.msg_hbhid |
---|
429 | |
---|
430 | oh_hdr = oh.header() |
---|
431 | hex(oh_hdr.avp_flags) |
---|
432 | oh_hdr.avp_vendor |
---|
433 | oh_hdr.avp_value.os.as_str() |
---|
434 | |
---|
435 | |
---|
436 | # Get or set the routing data |
---|
437 | rd = rt_data() |
---|
438 | dwr.set_rtd(rd) |
---|
439 | rd = dwr.get_rtd() |
---|
440 | |
---|
441 | # Test if message is routable |
---|
442 | dwr.is_routable() |
---|
443 | |
---|
444 | # Which peer the message was received from (when received from network) |
---|
445 | dwr.source() |
---|
446 | |
---|
447 | # The session corresponding to this message (returns None when no Session-Id AVP is included) |
---|
448 | dwr.get_session() |
---|
449 | |
---|
450 | |
---|
451 | # Parse a buffer |
---|
452 | buf = "\x01\x00\x00@\x80\x00\x01\x18\x00\x00\x00\x00\x00\x00\x00\x00N\x10\x00\x00\x00\x00\x01\x08@\x00\x00\x16my.origin.host\x00\x00\x00\x00\x01\x04@\x00\x00\x14\x00\x00\x01\n@\x00\x00\x0c\x00\x00\x00{" |
---|
453 | mydwr = msg(buf) |
---|
454 | # Resolve objects in the dictionary. Return value is None or a struct pei_error in case of problem. |
---|
455 | mydwr.parse_dict() # if not using the fD global dict, pass it as parameter |
---|
456 | err = mydwr.parse_rules() |
---|
457 | err.pei_errcode |
---|
458 | |
---|
459 | |
---|
460 | # Grouped AVPs are browsed with same methods as messages: |
---|
461 | gavp = dwr.children()[1] |
---|
462 | gavp.first_child().dump() |
---|
463 | gavp.children() |
---|
464 | |
---|
465 | |
---|
466 | # Send a message: |
---|
467 | mydwr = msg(buf) |
---|
468 | mydwr.send() |
---|
469 | |
---|
470 | # Optionally, a callback can be registered when a request is sent, with an optional object. |
---|
471 | # This callback takes the answer message as parameter and should return None or a message. (cf. fd_msg_send) |
---|
472 | def send_callback(msg, obj): |
---|
473 | print "Received answer:" |
---|
474 | msg.dump() |
---|
475 | print "Associated data:" |
---|
476 | obj |
---|
477 | return None |
---|
478 | |
---|
479 | mydwr = msg(buf) |
---|
480 | mydwr.send(send_callback, some_object) |
---|
481 | |
---|
482 | # Again optionally, a time limit can be specified in this case as follow: |
---|
483 | mydwr.send(send_callback, some_object, 10) |
---|
484 | # In that case, if no answer / error is received after 10 seconds (the value specified), |
---|
485 | # the callback is called with the request as parameter. |
---|
486 | # Testing for timeout case is done by using msg.is_request() |
---|
487 | def send_callback(msg, obj): |
---|
488 | if (msg.is_request()): |
---|
489 | print "Request timed out without answer:" |
---|
490 | else: |
---|
491 | print "Received answer:" |
---|
492 | msg.dump() |
---|
493 | print "Associated data:" |
---|
494 | obj |
---|
495 | return None |
---|
496 | |
---|
497 | |
---|
498 | # Set a result code in an answer message. |
---|
499 | mydwr = msg(buf) |
---|
500 | dwa = mydwr.create_answer() |
---|
501 | dwa.rescode_set() # This adds the DIAMETER_SUCCESS result code |
---|
502 | dwa.rescode_set("DIAMETER_LIMITED_SUCCESS" ) # This adds a different result code |
---|
503 | dwa.rescode_set("DIAMETER_LIMITED_SUCCESS", "Something went not so well" ) # This adds a different result code + specified Error-Message |
---|
504 | dwa.rescode_set("DIAMETER_INVALID_AVP", None, faulty_avp ) # This adds a Failed-AVP |
---|
505 | dwa.rescode_set("DIAMETER_SUCCESS", None, None, 1 ) # This adds origin information (see fd_msg_rescode_set's type_id for more info) |
---|
506 | |
---|
507 | # Set the origin to local host |
---|
508 | mydwr.add_origin() # adds Origin-Host & Origin-Realm |
---|
509 | mydwr.add_origin(1) # adds Origin-State-Id in addition. |
---|
510 | |
---|
511 | |
---|
512 | ############# DISPATCH (aka. server application) ############ |
---|
513 | |
---|
514 | # As for sessions, only one dispatch handler can be registered in this extension at the moment. |
---|
515 | # The callback for the handler has the following syntax: |
---|
516 | def dispatch_cb_model(inmsg, inavp, insession): |
---|
517 | print "Callback trigged on message: " |
---|
518 | inmsg.dump() |
---|
519 | # inavp is None or the AVP that trigged the callback, depending on how it was registered. |
---|
520 | if inavp: |
---|
521 | print "From the following AVP:" |
---|
522 | inavp.dump() |
---|
523 | else: |
---|
524 | print "No AVP" |
---|
525 | # Session is provided only if a Session-Id is in the message |
---|
526 | if insession: |
---|
527 | print "The session is: ", insession.getsid() |
---|
528 | else: |
---|
529 | print "No session" |
---|
530 | # Now, for the return value. |
---|
531 | # This callback must return 3 elements: |
---|
532 | # - an integer which is interpreted as an error code (errno.h) |
---|
533 | # - a message or None, depending on the next item |
---|
534 | # - an enum disp_action value, with the same meaning as in C (see libfreeDiameter.h) |
---|
535 | del inmsg |
---|
536 | return [ 0, None, DISP_ACT_CONT ] |
---|
537 | |
---|
538 | |
---|
539 | ### Example use: rebuild the server-side of test_app.fdx in python |
---|
540 | |
---|
541 | # The following block defines the dictionary objects from the test_app.fdx application that we use on the remote peer |
---|
542 | gdict = cvar.fd_g_config.cnf_dict |
---|
543 | d_si = gdict.search ( DICT_AVP, AVP_BY_NAME, "Session-Id" ) |
---|
544 | d_oh = gdict.search ( DICT_AVP, AVP_BY_NAME, "Origin-Host" ) |
---|
545 | d_or = gdict.search ( DICT_AVP, AVP_BY_NAME, "Origin-Realm" ) |
---|
546 | d_dh = gdict.search ( DICT_AVP, AVP_BY_NAME, "Destination-Host" ) |
---|
547 | d_dr = gdict.search ( DICT_AVP, AVP_BY_NAME, "Destination-Realm" ) |
---|
548 | d_rc = gdict.search ( DICT_AVP, AVP_BY_NAME, "Result-Code" ) |
---|
549 | d_vnd = gdict.new_obj(DICT_VENDOR, dict_vendor_data(999999, "app_test_py vendor") ) |
---|
550 | d_app = gdict.new_obj(DICT_APPLICATION, dict_application_data(0xffffff, "app_test_py appli"), d_vnd) |
---|
551 | d_req = gdict.new_obj(DICT_COMMAND, dict_cmd_data(0xfffffe, "Test_py-Request", 1), d_app) |
---|
552 | d_ans = gdict.new_obj(DICT_COMMAND, dict_cmd_data(0xfffffe, "Test_py-Answer", 0), d_app) |
---|
553 | d_avp = gdict.new_obj(DICT_AVP, dict_avp_data(0xffffff, "app_test_py avp", AVP_TYPE_INTEGER32, 999999 )) |
---|
554 | gdict.new_obj(DICT_RULE, dict_rule_data(d_si, RULE_FIXED_HEAD, 1, 1), d_req) |
---|
555 | gdict.new_obj(DICT_RULE, dict_rule_data(d_si, RULE_FIXED_HEAD, 1, 1), d_ans) |
---|
556 | gdict.new_obj(DICT_RULE, dict_rule_data(d_avp, RULE_REQUIRED, 1, 1), d_req) |
---|
557 | gdict.new_obj(DICT_RULE, dict_rule_data(d_avp, RULE_REQUIRED, 1, 1), d_ans) |
---|
558 | gdict.new_obj(DICT_RULE, dict_rule_data(d_oh, RULE_REQUIRED, 1, 1), d_req) |
---|
559 | gdict.new_obj(DICT_RULE, dict_rule_data(d_oh, RULE_REQUIRED, 1, 1), d_ans) |
---|
560 | gdict.new_obj(DICT_RULE, dict_rule_data(d_or, RULE_REQUIRED, 1, 1), d_req) |
---|
561 | gdict.new_obj(DICT_RULE, dict_rule_data(d_or, RULE_REQUIRED, 1, 1), d_ans) |
---|
562 | gdict.new_obj(DICT_RULE, dict_rule_data(d_dr, RULE_REQUIRED, 1, 1), d_req) |
---|
563 | gdict.new_obj(DICT_RULE, dict_rule_data(d_dh, RULE_OPTIONAL, 0, 1), d_req) |
---|
564 | gdict.new_obj(DICT_RULE, dict_rule_data(d_rc, RULE_REQUIRED, 1, 1), d_ans) |
---|
565 | |
---|
566 | # Now, create the Test_app server callback: |
---|
567 | def test_app_cb(inmsg, inavp, insession): |
---|
568 | tval = inmsg.search(d_avp).header().avp_value.u32 |
---|
569 | print "Py ECHO Test message from '%s' with test value %x, replying..." % (inmsg.search(d_oh).header().avp_value.os.as_str(), tval) |
---|
570 | answ = inmsg.create_answer() |
---|
571 | answ.rescode_set() |
---|
572 | answ.add_origin() |
---|
573 | ta = avp(d_avp, AVPFL_SET_BLANK_VALUE) |
---|
574 | ta.header().avp_value.u32 = tval |
---|
575 | answ.add_child(ta) |
---|
576 | return [ 0, answ, DISP_ACT_SEND ] |
---|
577 | |
---|
578 | # Register the callback for dispatch thread: |
---|
579 | hdl = disp_hdl(test_app_cb, DISP_HOW_CC, disp_when(d_app, d_req)) # disp_when() takes 0 to 4 arguments as follow: (app=NULL, cmd=NULL, avp=NULL, val=NULL) |
---|
580 | |
---|
581 | # Don't forget to register the application in the daemon for CER/CEA capabilities. |
---|
582 | fd_disp_app_support ( d_app, d_vnd, 1, 0 ) |
---|
583 | |
---|
584 | |
---|
585 | ### For the fun, the client part of the test_app: |
---|
586 | |
---|
587 | def receive_answer(ans, testval): |
---|
588 | try: |
---|
589 | tval = ans.search(d_avp).header().avp_value.u32 |
---|
590 | except: |
---|
591 | print "Error in receive_answer: no Test-AVP included" |
---|
592 | tval = 0 |
---|
593 | try: |
---|
594 | print "Py RECV %x (expected: %x) Status: %d From: '%s'" % (tval, testval, ans.search(d_rc).header().avp_value.u32, ans.search(d_oh).header().avp_value.os.as_str()) |
---|
595 | except: |
---|
596 | print "Error in receive_answer: Result-Code or Origin-Host are missing" |
---|
597 | del ans |
---|
598 | return None |
---|
599 | |
---|
600 | import random |
---|
601 | |
---|
602 | def send_query(destrealm="localdomain"): |
---|
603 | qry = msg(d_req) |
---|
604 | sess = session() |
---|
605 | tv = random.randint(1, 1<<32) |
---|
606 | # Session-Id |
---|
607 | a = avp(d_si, AVPFL_SET_BLANK_VALUE) |
---|
608 | a.header().avp_value.os = sess.getsid() |
---|
609 | qry.add_child(a) |
---|
610 | # Destination-Realm |
---|
611 | a = avp(d_dr, AVPFL_SET_BLANK_VALUE) |
---|
612 | a.header().avp_value.os = destrealm |
---|
613 | qry.add_child(a) |
---|
614 | # Origin-Host, Origin-Realm |
---|
615 | qry.add_origin() |
---|
616 | # Test-AVP |
---|
617 | a = avp(d_avp, AVPFL_SET_BLANK_VALUE) |
---|
618 | a.header().avp_value.u32 = tv |
---|
619 | qry.add_child(a) |
---|
620 | print "Py SEND %x to '%s'" % (tv, destrealm) |
---|
621 | qry.send(receive_answer, tv) |
---|
622 | |
---|
623 | send_query() |
---|
624 | |
---|
625 | |
---|
626 | ############# FIFO queues ############ |
---|
627 | |
---|
628 | myqueue = fifo() |
---|
629 | |
---|
630 | # enqueue any object |
---|
631 | myqueue.post(3) |
---|
632 | myqueue.post("blah") |
---|
633 | myqueue.post( [ 3, 2 ] ) |
---|
634 | |
---|
635 | # Simple get (blocks when the queue is empty) |
---|
636 | myqueue.get() |
---|
637 | |
---|
638 | # Try get: returns the next object, or None if the queue is empty |
---|
639 | myqueue.tryget() |
---|
640 | |
---|
641 | # timed get: like get, but returns None after x seconds |
---|
642 | myqueue.timedget(3) |
---|
643 | |
---|
644 | # Show the number of items in the queue |
---|
645 | myqueue.length() |
---|
646 | |
---|
647 | |
---|
648 | ## Variants: |
---|
649 | # All the previous calls are suitable to queue Python objects. |
---|
650 | # In order to interact with objects queued / poped by C counterpart, |
---|
651 | # a second parameter must be passed to specify the object type, |
---|
652 | # as follow: |
---|
653 | ev = fd_event() |
---|
654 | ev.code = FDEV_DUMP_EXT |
---|
655 | cvar.fd_g_config.cnf_main_ev.post(ev, "struct fd_event *") |
---|
656 | |
---|
657 | # Similarly, for *get, we can specify the structure that was queued: |
---|
658 | myqueue.get("struct fd_event *") |
---|
659 | myqueue.tryget("struct fd_event *") |
---|
660 | myqueue.timedget(3, "struct fd_event *") |
---|
661 | |
---|
662 | del myqueue |
---|
663 | |
---|
664 | |
---|
665 | ############# HOOKS ############ |
---|
666 | |
---|
667 | def my_hook_cb(type, msg, peer, other, oldpmd): |
---|
668 | print "callback type ", type, " called: ", msg, other, oldpmd |
---|
669 | return "this is the new pmd" |
---|
670 | |
---|
671 | # Create a wrapped fd_hook_data_hdl: |
---|
672 | datahdl = fd_hook_data_hdl() |
---|
673 | |
---|
674 | # Register the hook callback: |
---|
675 | hdl = fd_hook_hdl(1 << HOOK_MESSAGE_SENT, my_hook_cb, datahdl) |
---|
676 | |
---|
677 | |
---|
678 | |
---|
679 | |
---|
680 | ############# PEERS ############ |
---|
681 | |
---|
682 | # Get the list of peers defined in the system |
---|
683 | # (we are supposed to readlock fd_g_peers_rw before accessing this list) |
---|
684 | cvar.fd_g_peers_rw.rdlock() |
---|
685 | peers = cvar.fd_g_peers.enum_as("struct peer_hdr *") |
---|
686 | cvar.fd_g_peers_rw.unlock() |
---|
687 | for p in peers: |
---|
688 | print "Peer:", p.info.pi_diamid |
---|
689 | |
---|
690 | |
---|
691 | # Create a new peer |
---|
692 | np = peer_info() |
---|
693 | np.pi_diamid = "nas.localdomain" |
---|
694 | np.config.pic_flags.pro4 = PI_P4_TCP |
---|
695 | |
---|
696 | |
---|
697 | # Add this peer into the framework. |
---|
698 | np.add() |
---|
699 | |
---|
700 | # It is possible to specify a callback for when the connection completes or fails with this peer. |
---|
701 | # The prototype is as follow: |
---|
702 | def add_cb(peer): |
---|
703 | if peer: |
---|
704 | if peer.runtime.pir_state == STATE_OPEN: |
---|
705 | print "Connection to peer '%s' completed" % (peer.pi_diamid) |
---|
706 | # can find more information in peer.runtime.* |
---|
707 | else: |
---|
708 | print "Connection to peer '%s' failed (state:%d)" % (peer.pi_diamid, peer.runtime.pir_state) |
---|
709 | else: |
---|
710 | print "The peer has been destroyed before it completed the connection." |
---|
711 | |
---|
712 | # Then add the peer like this: |
---|
713 | np.add(add_cb) |
---|
714 | |
---|
715 | |
---|
716 | # Search a peer by its diameter id (returns a peer_hdr object if found) -- similar to fd_peer_getbyid |
---|
717 | p = peer_search("nas.domain.aaa") |
---|
718 | |
---|
719 | |
---|
720 | ## Validation callback (see fd_peer_validate_register documentation) |
---|
721 | |
---|
722 | # cb2 prototype: |
---|
723 | def my_validate_cb2(pinfo): |
---|
724 | print "Cb2 callback trigged for peer %s" % (pinfo.pi_diamid) |
---|
725 | # Usually, this would be used only to check some TLS properties, |
---|
726 | # which is not really possible yet through the python interpreter... |
---|
727 | return 0 # return an error code if the peer is not validated |
---|
728 | |
---|
729 | # cb prototype: |
---|
730 | def my_validate_cb(pinfo): |
---|
731 | print "Validate callback trigged for peer %s" % (pinfo.pi_diamid) |
---|
732 | # If the peer is not allowed to connect: |
---|
733 | #return -1 |
---|
734 | # If the peer is authorized: |
---|
735 | #return 1 |
---|
736 | # In addition, if IPsec is allowed, |
---|
737 | #pinfo.config.pic_flags.sec = PI_SEC_NONE |
---|
738 | # If no decision has been made: |
---|
739 | #return 0 |
---|
740 | # If the peer is temporarily authorized but a second callback must be called after TLS negociation: |
---|
741 | return my_validate_cb2 |
---|
742 | |
---|
743 | # Register the callback, it will be called on new incoming connections. |
---|
744 | peer_validate_register(my_validate_cb) |
---|
745 | |
---|
746 | |
---|
747 | |
---|
748 | ############# ENDPOINTS ############ |
---|
749 | |
---|
750 | ep = fd_endpoint("129.168.168.192") |
---|
751 | |
---|
752 | # with port: |
---|
753 | ep = fd_endpoint("129.168.168.192", 3868) |
---|
754 | |
---|
755 | # With different flags: |
---|
756 | ep = fd_endpoint("129.168.168.192", 3868, EP_FL_PRIMARY) |
---|
757 | |
---|
758 | # Add IP information for the peer |
---|
759 | np = peer_info() |
---|
760 | ep.add_merge(np.pi_endpoints) |
---|
761 | fd_ep_dump(0, np.pi_endpoints) |
---|
762 | |
---|
763 | |
---|
764 | |
---|
765 | ############# POSIX functions wrappers ############ |
---|
766 | |
---|
767 | # The interface also provides wrappers around base POSIX |
---|
768 | # synchronization functions: |
---|
769 | |
---|
770 | m = pthread_mutex_t() |
---|
771 | m.lock() |
---|
772 | m.unlock() |
---|
773 | |
---|
774 | c = pthread_cond_t() |
---|
775 | c.signal() |
---|
776 | c.broadcast() |
---|
777 | c.wait(m) |
---|
778 | c.timedwait(m, 5) # it takes a relative time |
---|
779 | |
---|
780 | r = pthread_rwlock_t() |
---|
781 | r.rdlock() |
---|
782 | r.unlock() |
---|
783 | r.wrlock() |
---|