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