From b06ebda0110aaa466b4f7ba6cacac7a26b29f434 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Thu, 26 Jun 2008 19:34:33 +0000 Subject: [PATCH] Vendor import of netgraph from FreeBSD-current 20080626 --- sys/netgraph7/NOTES | 103 + sys/netgraph7/atm/ccatm/ng_ccatm.c | 1201 ++++++ sys/netgraph7/atm/ccatm/ng_ccatm_cust.h | 54 + sys/netgraph7/atm/ng_atm.c | 1433 +++++++ sys/netgraph7/atm/ng_atm.h | 248 ++ sys/netgraph7/atm/ng_ccatm.h | 172 + sys/netgraph7/atm/ng_sscfu.h | 68 + sys/netgraph7/atm/ng_sscop.h | 110 + sys/netgraph7/atm/ng_uni.h | 119 + sys/netgraph7/atm/ngatmbase.c | 501 +++ sys/netgraph7/atm/ngatmbase.h | 64 + sys/netgraph7/atm/sscfu/ng_sscfu.c | 609 +++ sys/netgraph7/atm/sscfu/ng_sscfu_cust.h | 131 + sys/netgraph7/atm/sscop/ng_sscop.c | 883 ++++ sys/netgraph7/atm/sscop/ng_sscop_cust.h | 344 ++ sys/netgraph7/atm/uni/ng_uni.c | 931 +++++ sys/netgraph7/atm/uni/ng_uni_cust.h | 150 + sys/netgraph7/bluetooth/common/ng_bluetooth.c | 253 ++ .../bluetooth/drivers/bt3c/ng_bt3c_pccard.c | 1228 ++++++ .../bluetooth/drivers/bt3c/ng_bt3c_var.h | 107 + sys/netgraph7/bluetooth/drivers/h4/TODO | 13 + sys/netgraph7/bluetooth/drivers/h4/ng_h4.c | 1020 +++++ .../bluetooth/drivers/h4/ng_h4_prse.h | 124 + .../bluetooth/drivers/h4/ng_h4_var.h | 103 + sys/netgraph7/bluetooth/drivers/ubt/TODO | 30 + sys/netgraph7/bluetooth/drivers/ubt/ng_ubt.c | 2234 ++++++++++ .../bluetooth/drivers/ubt/ng_ubt_var.h | 147 + .../bluetooth/drivers/ubtbcmfw/ubtbcmfw.c | 577 +++ sys/netgraph7/bluetooth/hci/TODO | 29 + sys/netgraph7/bluetooth/hci/ng_hci_cmds.c | 900 ++++ sys/netgraph7/bluetooth/hci/ng_hci_cmds.h | 47 + sys/netgraph7/bluetooth/hci/ng_hci_evnt.c | 1144 +++++ sys/netgraph7/bluetooth/hci/ng_hci_evnt.h | 45 + sys/netgraph7/bluetooth/hci/ng_hci_main.c | 1088 +++++ sys/netgraph7/bluetooth/hci/ng_hci_misc.c | 498 +++ sys/netgraph7/bluetooth/hci/ng_hci_misc.h | 58 + sys/netgraph7/bluetooth/hci/ng_hci_prse.h | 219 + sys/netgraph7/bluetooth/hci/ng_hci_ulpi.c | 1212 ++++++ sys/netgraph7/bluetooth/hci/ng_hci_ulpi.h | 54 + sys/netgraph7/bluetooth/hci/ng_hci_var.h | 218 + .../bluetooth/include/ng_bluetooth.h | 226 + sys/netgraph7/bluetooth/include/ng_bt3c.h | 111 + sys/netgraph7/bluetooth/include/ng_btsocket.h | 342 ++ .../bluetooth/include/ng_btsocket_hci_raw.h | 90 + .../bluetooth/include/ng_btsocket_l2cap.h | 210 + .../bluetooth/include/ng_btsocket_rfcomm.h | 340 ++ sys/netgraph7/bluetooth/include/ng_h4.h | 113 + sys/netgraph7/bluetooth/include/ng_hci.h | 1662 ++++++++ sys/netgraph7/bluetooth/include/ng_l2cap.h | 665 +++ sys/netgraph7/bluetooth/include/ng_ubt.h | 91 + sys/netgraph7/bluetooth/l2cap/TODO | 43 + sys/netgraph7/bluetooth/l2cap/ng_l2cap_cmds.c | 398 ++ sys/netgraph7/bluetooth/l2cap/ng_l2cap_cmds.h | 409 ++ sys/netgraph7/bluetooth/l2cap/ng_l2cap_evnt.c | 1327 ++++++ sys/netgraph7/bluetooth/l2cap/ng_l2cap_evnt.h | 40 + sys/netgraph7/bluetooth/l2cap/ng_l2cap_llpi.c | 906 ++++ sys/netgraph7/bluetooth/l2cap/ng_l2cap_llpi.h | 51 + sys/netgraph7/bluetooth/l2cap/ng_l2cap_main.c | 759 ++++ sys/netgraph7/bluetooth/l2cap/ng_l2cap_misc.c | 643 +++ sys/netgraph7/bluetooth/l2cap/ng_l2cap_misc.h | 106 + sys/netgraph7/bluetooth/l2cap/ng_l2cap_prse.h | 87 + sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.c | 1642 ++++++++ sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.h | 79 + sys/netgraph7/bluetooth/l2cap/ng_l2cap_var.h | 188 + sys/netgraph7/bluetooth/socket/TODO | 15 + sys/netgraph7/bluetooth/socket/ng_btsocket.c | 254 ++ .../bluetooth/socket/ng_btsocket_hci_raw.c | 1639 ++++++++ .../bluetooth/socket/ng_btsocket_l2cap.c | 2814 +++++++++++++ .../bluetooth/socket/ng_btsocket_l2cap_raw.c | 1306 ++++++ .../bluetooth/socket/ng_btsocket_rfcomm.c | 3576 ++++++++++++++++ sys/netgraph7/netflow/netflow.c | 718 ++++ sys/netgraph7/netflow/netflow.h | 129 + sys/netgraph7/netflow/ng_netflow.c | 664 +++ sys/netgraph7/netflow/ng_netflow.h | 275 ++ sys/netgraph7/netgraph.h | 1186 ++++++ sys/netgraph7/ng_UI.c | 248 ++ sys/netgraph7/ng_UI.h | 56 + sys/netgraph7/ng_async.c | 640 +++ sys/netgraph7/ng_async.h | 110 + sys/netgraph7/ng_atmllc.c | 274 ++ sys/netgraph7/ng_atmllc.h | 45 + sys/netgraph7/ng_base.c | 3711 +++++++++++++++++ sys/netgraph7/ng_bpf.c | 590 +++ sys/netgraph7/ng_bpf.h | 103 + sys/netgraph7/ng_bridge.c | 1042 +++++ sys/netgraph7/ng_bridge.h | 155 + sys/netgraph7/ng_car.c | 766 ++++ sys/netgraph7/ng_car.h | 140 + sys/netgraph7/ng_cisco.c | 652 +++ sys/netgraph7/ng_cisco.h | 91 + sys/netgraph7/ng_deflate.c | 684 +++ sys/netgraph7/ng_deflate.h | 85 + sys/netgraph7/ng_device.c | 492 +++ sys/netgraph7/ng_device.h | 49 + sys/netgraph7/ng_echo.c | 121 + sys/netgraph7/ng_echo.h | 51 + sys/netgraph7/ng_eiface.c | 591 +++ sys/netgraph7/ng_eiface.h | 59 + sys/netgraph7/ng_etf.c | 489 +++ sys/netgraph7/ng_etf.h | 90 + sys/netgraph7/ng_ether.c | 789 ++++ sys/netgraph7/ng_ether.h | 73 + sys/netgraph7/ng_fec.c | 1366 ++++++ sys/netgraph7/ng_fec.h | 113 + sys/netgraph7/ng_frame_relay.c | 515 +++ sys/netgraph7/ng_frame_relay.h | 56 + sys/netgraph7/ng_gif.c | 596 +++ sys/netgraph7/ng_gif.h | 86 + sys/netgraph7/ng_gif_demux.c | 399 ++ sys/netgraph7/ng_gif_demux.h | 51 + sys/netgraph7/ng_hole.c | 227 + sys/netgraph7/ng_hole.h | 71 + sys/netgraph7/ng_hub.c | 100 + sys/netgraph7/ng_hub.h | 36 + sys/netgraph7/ng_iface.c | 817 ++++ sys/netgraph7/ng_iface.h | 75 + sys/netgraph7/ng_ip_input.c | 136 + sys/netgraph7/ng_ip_input.h | 77 + sys/netgraph7/ng_ipfw.c | 338 ++ sys/netgraph7/ng_ipfw.h | 49 + sys/netgraph7/ng_ksocket.c | 1305 ++++++ sys/netgraph7/ng_ksocket.h | 111 + sys/netgraph7/ng_l2tp.c | 1619 +++++++ sys/netgraph7/ng_l2tp.h | 196 + sys/netgraph7/ng_lmi.c | 1082 +++++ sys/netgraph7/ng_lmi.h | 81 + sys/netgraph7/ng_message.h | 446 ++ sys/netgraph7/ng_mppc.c | 852 ++++ sys/netgraph7/ng_mppc.h | 85 + sys/netgraph7/ng_nat.c | 837 ++++ sys/netgraph7/ng_nat.h | 187 + sys/netgraph7/ng_one2many.c | 609 +++ sys/netgraph7/ng_one2many.h | 113 + sys/netgraph7/ng_parse.c | 1912 +++++++++ sys/netgraph7/ng_parse.h | 540 +++ sys/netgraph7/ng_ppp.c | 2604 ++++++++++++ sys/netgraph7/ng_ppp.h | 245 ++ sys/netgraph7/ng_pppoe.c | 1915 +++++++++ sys/netgraph7/ng_pppoe.h | 257 ++ sys/netgraph7/ng_pptpgre.c | 985 +++++ sys/netgraph7/ng_pptpgre.h | 135 + sys/netgraph7/ng_pred1.c | 698 ++++ sys/netgraph7/ng_pred1.h | 83 + sys/netgraph7/ng_rfc1490.c | 491 +++ sys/netgraph7/ng_rfc1490.h | 63 + sys/netgraph7/ng_sample.c | 499 +++ sys/netgraph7/ng_sample.h | 89 + sys/netgraph7/ng_socket.c | 1140 +++++ sys/netgraph7/ng_socket.h | 69 + sys/netgraph7/ng_socketvar.h | 68 + sys/netgraph7/ng_source.c | 920 ++++ sys/netgraph7/ng_source.h | 139 + sys/netgraph7/ng_split.c | 179 + sys/netgraph7/ng_split.h | 45 + sys/netgraph7/ng_sppp.c | 422 ++ sys/netgraph7/ng_sppp.h | 39 + sys/netgraph7/ng_tag.c | 717 ++++ sys/netgraph7/ng_tag.h | 130 + sys/netgraph7/ng_tcpmss.c | 443 ++ sys/netgraph7/ng_tcpmss.h | 82 + sys/netgraph7/ng_tee.c | 397 ++ sys/netgraph7/ng_tee.h | 99 + sys/netgraph7/ng_tty.c | 702 ++++ sys/netgraph7/ng_tty.h | 64 + sys/netgraph7/ng_vjc.c | 614 +++ sys/netgraph7/ng_vjc.h | 88 + sys/netgraph7/ng_vlan.c | 468 +++ sys/netgraph7/ng_vlan.h | 75 + 168 files changed, 83816 insertions(+) create mode 100644 sys/netgraph7/NOTES create mode 100644 sys/netgraph7/atm/ccatm/ng_ccatm.c create mode 100644 sys/netgraph7/atm/ccatm/ng_ccatm_cust.h create mode 100644 sys/netgraph7/atm/ng_atm.c create mode 100644 sys/netgraph7/atm/ng_atm.h create mode 100644 sys/netgraph7/atm/ng_ccatm.h create mode 100644 sys/netgraph7/atm/ng_sscfu.h create mode 100644 sys/netgraph7/atm/ng_sscop.h create mode 100644 sys/netgraph7/atm/ng_uni.h create mode 100644 sys/netgraph7/atm/ngatmbase.c create mode 100644 sys/netgraph7/atm/ngatmbase.h create mode 100644 sys/netgraph7/atm/sscfu/ng_sscfu.c create mode 100644 sys/netgraph7/atm/sscfu/ng_sscfu_cust.h create mode 100644 sys/netgraph7/atm/sscop/ng_sscop.c create mode 100644 sys/netgraph7/atm/sscop/ng_sscop_cust.h create mode 100644 sys/netgraph7/atm/uni/ng_uni.c create mode 100644 sys/netgraph7/atm/uni/ng_uni_cust.h create mode 100644 sys/netgraph7/bluetooth/common/ng_bluetooth.c create mode 100644 sys/netgraph7/bluetooth/drivers/bt3c/ng_bt3c_pccard.c create mode 100644 sys/netgraph7/bluetooth/drivers/bt3c/ng_bt3c_var.h create mode 100644 sys/netgraph7/bluetooth/drivers/h4/TODO create mode 100644 sys/netgraph7/bluetooth/drivers/h4/ng_h4.c create mode 100644 sys/netgraph7/bluetooth/drivers/h4/ng_h4_prse.h create mode 100644 sys/netgraph7/bluetooth/drivers/h4/ng_h4_var.h create mode 100644 sys/netgraph7/bluetooth/drivers/ubt/TODO create mode 100644 sys/netgraph7/bluetooth/drivers/ubt/ng_ubt.c create mode 100644 sys/netgraph7/bluetooth/drivers/ubt/ng_ubt_var.h create mode 100644 sys/netgraph7/bluetooth/drivers/ubtbcmfw/ubtbcmfw.c create mode 100644 sys/netgraph7/bluetooth/hci/TODO create mode 100644 sys/netgraph7/bluetooth/hci/ng_hci_cmds.c create mode 100644 sys/netgraph7/bluetooth/hci/ng_hci_cmds.h create mode 100644 sys/netgraph7/bluetooth/hci/ng_hci_evnt.c create mode 100644 sys/netgraph7/bluetooth/hci/ng_hci_evnt.h create mode 100644 sys/netgraph7/bluetooth/hci/ng_hci_main.c create mode 100644 sys/netgraph7/bluetooth/hci/ng_hci_misc.c create mode 100644 sys/netgraph7/bluetooth/hci/ng_hci_misc.h create mode 100644 sys/netgraph7/bluetooth/hci/ng_hci_prse.h create mode 100644 sys/netgraph7/bluetooth/hci/ng_hci_ulpi.c create mode 100644 sys/netgraph7/bluetooth/hci/ng_hci_ulpi.h create mode 100644 sys/netgraph7/bluetooth/hci/ng_hci_var.h create mode 100644 sys/netgraph7/bluetooth/include/ng_bluetooth.h create mode 100644 sys/netgraph7/bluetooth/include/ng_bt3c.h create mode 100644 sys/netgraph7/bluetooth/include/ng_btsocket.h create mode 100644 sys/netgraph7/bluetooth/include/ng_btsocket_hci_raw.h create mode 100644 sys/netgraph7/bluetooth/include/ng_btsocket_l2cap.h create mode 100644 sys/netgraph7/bluetooth/include/ng_btsocket_rfcomm.h create mode 100644 sys/netgraph7/bluetooth/include/ng_h4.h create mode 100644 sys/netgraph7/bluetooth/include/ng_hci.h create mode 100644 sys/netgraph7/bluetooth/include/ng_l2cap.h create mode 100644 sys/netgraph7/bluetooth/include/ng_ubt.h create mode 100644 sys/netgraph7/bluetooth/l2cap/TODO create mode 100644 sys/netgraph7/bluetooth/l2cap/ng_l2cap_cmds.c create mode 100644 sys/netgraph7/bluetooth/l2cap/ng_l2cap_cmds.h create mode 100644 sys/netgraph7/bluetooth/l2cap/ng_l2cap_evnt.c create mode 100644 sys/netgraph7/bluetooth/l2cap/ng_l2cap_evnt.h create mode 100644 sys/netgraph7/bluetooth/l2cap/ng_l2cap_llpi.c create mode 100644 sys/netgraph7/bluetooth/l2cap/ng_l2cap_llpi.h create mode 100644 sys/netgraph7/bluetooth/l2cap/ng_l2cap_main.c create mode 100644 sys/netgraph7/bluetooth/l2cap/ng_l2cap_misc.c create mode 100644 sys/netgraph7/bluetooth/l2cap/ng_l2cap_misc.h create mode 100644 sys/netgraph7/bluetooth/l2cap/ng_l2cap_prse.h create mode 100644 sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.c create mode 100644 sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.h create mode 100644 sys/netgraph7/bluetooth/l2cap/ng_l2cap_var.h create mode 100644 sys/netgraph7/bluetooth/socket/TODO create mode 100644 sys/netgraph7/bluetooth/socket/ng_btsocket.c create mode 100644 sys/netgraph7/bluetooth/socket/ng_btsocket_hci_raw.c create mode 100644 sys/netgraph7/bluetooth/socket/ng_btsocket_l2cap.c create mode 100644 sys/netgraph7/bluetooth/socket/ng_btsocket_l2cap_raw.c create mode 100644 sys/netgraph7/bluetooth/socket/ng_btsocket_rfcomm.c create mode 100644 sys/netgraph7/netflow/netflow.c create mode 100644 sys/netgraph7/netflow/netflow.h create mode 100644 sys/netgraph7/netflow/ng_netflow.c create mode 100644 sys/netgraph7/netflow/ng_netflow.h create mode 100644 sys/netgraph7/netgraph.h create mode 100644 sys/netgraph7/ng_UI.c create mode 100644 sys/netgraph7/ng_UI.h create mode 100644 sys/netgraph7/ng_async.c create mode 100644 sys/netgraph7/ng_async.h create mode 100644 sys/netgraph7/ng_atmllc.c create mode 100644 sys/netgraph7/ng_atmllc.h create mode 100644 sys/netgraph7/ng_base.c create mode 100644 sys/netgraph7/ng_bpf.c create mode 100644 sys/netgraph7/ng_bpf.h create mode 100644 sys/netgraph7/ng_bridge.c create mode 100644 sys/netgraph7/ng_bridge.h create mode 100644 sys/netgraph7/ng_car.c create mode 100644 sys/netgraph7/ng_car.h create mode 100644 sys/netgraph7/ng_cisco.c create mode 100644 sys/netgraph7/ng_cisco.h create mode 100644 sys/netgraph7/ng_deflate.c create mode 100644 sys/netgraph7/ng_deflate.h create mode 100644 sys/netgraph7/ng_device.c create mode 100644 sys/netgraph7/ng_device.h create mode 100644 sys/netgraph7/ng_echo.c create mode 100644 sys/netgraph7/ng_echo.h create mode 100644 sys/netgraph7/ng_eiface.c create mode 100644 sys/netgraph7/ng_eiface.h create mode 100644 sys/netgraph7/ng_etf.c create mode 100644 sys/netgraph7/ng_etf.h create mode 100644 sys/netgraph7/ng_ether.c create mode 100644 sys/netgraph7/ng_ether.h create mode 100644 sys/netgraph7/ng_fec.c create mode 100644 sys/netgraph7/ng_fec.h create mode 100644 sys/netgraph7/ng_frame_relay.c create mode 100644 sys/netgraph7/ng_frame_relay.h create mode 100644 sys/netgraph7/ng_gif.c create mode 100644 sys/netgraph7/ng_gif.h create mode 100644 sys/netgraph7/ng_gif_demux.c create mode 100644 sys/netgraph7/ng_gif_demux.h create mode 100644 sys/netgraph7/ng_hole.c create mode 100644 sys/netgraph7/ng_hole.h create mode 100644 sys/netgraph7/ng_hub.c create mode 100644 sys/netgraph7/ng_hub.h create mode 100644 sys/netgraph7/ng_iface.c create mode 100644 sys/netgraph7/ng_iface.h create mode 100644 sys/netgraph7/ng_ip_input.c create mode 100644 sys/netgraph7/ng_ip_input.h create mode 100644 sys/netgraph7/ng_ipfw.c create mode 100644 sys/netgraph7/ng_ipfw.h create mode 100644 sys/netgraph7/ng_ksocket.c create mode 100644 sys/netgraph7/ng_ksocket.h create mode 100644 sys/netgraph7/ng_l2tp.c create mode 100644 sys/netgraph7/ng_l2tp.h create mode 100644 sys/netgraph7/ng_lmi.c create mode 100644 sys/netgraph7/ng_lmi.h create mode 100644 sys/netgraph7/ng_message.h create mode 100644 sys/netgraph7/ng_mppc.c create mode 100644 sys/netgraph7/ng_mppc.h create mode 100644 sys/netgraph7/ng_nat.c create mode 100644 sys/netgraph7/ng_nat.h create mode 100644 sys/netgraph7/ng_one2many.c create mode 100644 sys/netgraph7/ng_one2many.h create mode 100644 sys/netgraph7/ng_parse.c create mode 100644 sys/netgraph7/ng_parse.h create mode 100644 sys/netgraph7/ng_ppp.c create mode 100644 sys/netgraph7/ng_ppp.h create mode 100644 sys/netgraph7/ng_pppoe.c create mode 100644 sys/netgraph7/ng_pppoe.h create mode 100644 sys/netgraph7/ng_pptpgre.c create mode 100644 sys/netgraph7/ng_pptpgre.h create mode 100644 sys/netgraph7/ng_pred1.c create mode 100644 sys/netgraph7/ng_pred1.h create mode 100644 sys/netgraph7/ng_rfc1490.c create mode 100644 sys/netgraph7/ng_rfc1490.h create mode 100644 sys/netgraph7/ng_sample.c create mode 100644 sys/netgraph7/ng_sample.h create mode 100644 sys/netgraph7/ng_socket.c create mode 100644 sys/netgraph7/ng_socket.h create mode 100644 sys/netgraph7/ng_socketvar.h create mode 100644 sys/netgraph7/ng_source.c create mode 100644 sys/netgraph7/ng_source.h create mode 100644 sys/netgraph7/ng_split.c create mode 100644 sys/netgraph7/ng_split.h create mode 100644 sys/netgraph7/ng_sppp.c create mode 100644 sys/netgraph7/ng_sppp.h create mode 100644 sys/netgraph7/ng_tag.c create mode 100644 sys/netgraph7/ng_tag.h create mode 100644 sys/netgraph7/ng_tcpmss.c create mode 100644 sys/netgraph7/ng_tcpmss.h create mode 100644 sys/netgraph7/ng_tee.c create mode 100644 sys/netgraph7/ng_tee.h create mode 100644 sys/netgraph7/ng_tty.c create mode 100644 sys/netgraph7/ng_tty.h create mode 100644 sys/netgraph7/ng_vjc.c create mode 100644 sys/netgraph7/ng_vjc.h create mode 100644 sys/netgraph7/ng_vlan.c create mode 100644 sys/netgraph7/ng_vlan.h diff --git a/sys/netgraph7/NOTES b/sys/netgraph7/NOTES new file mode 100644 index 0000000000..2050cc4151 --- /dev/null +++ b/sys/netgraph7/NOTES @@ -0,0 +1,103 @@ +$FreeBSD: src/sys/netgraph/NOTES,v 1.2 2001/01/06 00:46:46 julian Exp $ +Development ideas.. + +Archie's suggestions... :-) + + - There should be a new malloc type: M_NETGRAPH + [DONE] + - all mallocs/frees now changed to use this.. JRE + - might further split them out some time. + + - Use MALLOC and FREE macros instead of direct function calls + [DONE] + - They allow conditional compilation which keeps + statistics & counters on various memory allocation + (or so it seems) + - Now tend to have NG_FREE_XX() macros. they + allow better debugging + + - In struct ng_mesg: at least make "header" into "hdr", if not + getting rid of it altogether. It doesn't seem necessary and + makes all my C code lines too long. + + - I understand.. one thought however.. consider.. + if char data[0] were not legal, so that data[1] needed to be + used instead, then the only way to get the size of the header + would be sizeof(msg.header) as sizeof(msg) would include the dummy + following bytes. this is a portability issue and I hope + it will be ported eventually :) + + - Baloney! you can use sizeof(msg) - 1 then.. or just + make it a macro, then its always portable: + + #ifdef __GNU_C__ + #define NG_MSG_HDR_SIZE (sizeof(struct ng_message)) + #else + #define NG_MSG_HDR_SIZE (sizeof(struct ng_message) - 1) + #endif + + - inertia rules :-b + + + - Have a user level program to print out and manipulate nodes, etc. + - [DONE] + see ngctl, nghook + + - "Netgraph global" flags to turn on tracing, etc. + + - ngctl needs to be rewritten using libnetgraph. Also it needs a + command to list all existing nodes (in case you don't know the + name of what you're looking for). + [DONE] + + - Need a way to get a list of ALL nodes. + [DONE] + - see NGM_LISTNODES + + - Enhance "netstat" to display all netgraph nodes -- or at least + all netgraph socket nodes. + [DONE] + + - BUG FIX: bind() on a socket should neither require nor allow a + colon character at the end of the name. Note ngctl allows you + to do it either way! + [DONE] (I think) + - bind on a control socket has been disabled + it was a bad idea. + + - Need to implement passing meta information through socket nodes + using sendmsg() and recvmsg(). + + - Stuff needing to be added to manual: + + - Awareness of SPL level, use ng_queue*() functions when necessary. + - Malloc all memory with type M_NETGRAPH. -DONE + - Write code so it can be an LKM or built into the kernel.. this means + be careful with things like #ifdef INET. + - All nodes assume that all data mbufs have the M_PKTHDR flag set! + The ng_send_data() and related functions should have an + #ifdef DIAGNOSTICS check to check this assumption for every mbuf. + -DONE with INVARIANTS. Framework should test this more. + - More generally, netgraph code should make liberal use of the + #ifdef DIAGNOSTICS definition. + -INVARIANTS. + - Since data and messages are sent functionally, programmers need + to watch out for infinite feedback loops. Should ng_base.c detect + this automatically? + - I've been thinking about this. each node could have a 'colour' + which is set to the colour of the packet as you pass through. + hitting a node already of your colour would abort. Each packet + has another (incremented) colour. + -new 'item' type can hold a hopcount... + +NEW in 2001 +All piggyback responses have gone away. +use the node ID in the return address field for quick response delivery. + +Every node has a queue, plus there is a list of nodes that have queued work. +Extensive use of Mutexes. Getting in shape for SMP. + +Messages and data are deliverd in a new form. An Item now has +all information needed to queue such a request and deliver it later, so +it is now the basis of all data transfer since any transfer may need to +be queued. diff --git a/sys/netgraph7/atm/ccatm/ng_ccatm.c b/sys/netgraph7/atm/ccatm/ng_ccatm.c new file mode 100644 index 0000000000..404f54e7c8 --- /dev/null +++ b/sys/netgraph7/atm/ccatm/ng_ccatm.c @@ -0,0 +1,1201 @@ +/*- + * Copyright (c) 2001-2002 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * Copyright (c) 2003-2004 + * Hartmut Brandt + * All rights reserved. + * + * Author: Harti Brandt + * + * Redistribution of this software and documentation and use in source and + * binary forms, with or without modification, are permitted provided that + * the following conditions are met: + * + * 1. Redistributions of source code or documentation must retain the above + * copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE AUTHOR + * AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/atm/ccatm/ng_ccatm.c,v 1.3 2006/09/30 12:37:43 netchild Exp $ + * + * ATM call control and API + */ + +#include +__FBSDID("$FreeBSD: src/sys/netgraph/atm/ccatm/ng_ccatm.c,v 1.3 2006/09/30 12:37:43 netchild Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DEPEND(ng_ccatm, ngatmbase, 1, 1, 1); + +MALLOC_DEFINE(M_NG_CCATM, "ng_ccatm", "netgraph uni api node"); + +/* + * Command structure parsing + */ + +/* ESI */ +static const struct ng_parse_fixedarray_info ng_ccatm_esi_type_info = + NGM_CCATM_ESI_INFO; +static const struct ng_parse_type ng_ccatm_esi_type = { + &ng_parse_fixedarray_type, + &ng_ccatm_esi_type_info +}; + +/* PORT PARAMETERS */ +static const struct ng_parse_struct_field ng_ccatm_atm_port_type_info[] = + NGM_CCATM_ATM_PORT_INFO; +static const struct ng_parse_type ng_ccatm_atm_port_type = { + &ng_parse_struct_type, + ng_ccatm_atm_port_type_info +}; + +/* PORT structure */ +static const struct ng_parse_struct_field ng_ccatm_port_type_info[] = + NGM_CCATM_PORT_INFO; +static const struct ng_parse_type ng_ccatm_port_type = { + &ng_parse_struct_type, + ng_ccatm_port_type_info +}; + +/* the ADDRESS array itself */ +static const struct ng_parse_fixedarray_info ng_ccatm_addr_array_type_info = + NGM_CCATM_ADDR_ARRAY_INFO; +static const struct ng_parse_type ng_ccatm_addr_array_type = { + &ng_parse_fixedarray_type, + &ng_ccatm_addr_array_type_info +}; + +/* one ADDRESS */ +static const struct ng_parse_struct_field ng_ccatm_uni_addr_type_info[] = + NGM_CCATM_UNI_ADDR_INFO; +static const struct ng_parse_type ng_ccatm_uni_addr_type = { + &ng_parse_struct_type, + ng_ccatm_uni_addr_type_info +}; + +/* ADDRESS request */ +static const struct ng_parse_struct_field ng_ccatm_addr_req_type_info[] = + NGM_CCATM_ADDR_REQ_INFO; +static const struct ng_parse_type ng_ccatm_addr_req_type = { + &ng_parse_struct_type, + ng_ccatm_addr_req_type_info +}; + +/* ADDRESS var-array */ +static int +ng_ccatm_addr_req_array_getlen(const struct ng_parse_type *type, + const u_char *start, const u_char *buf) +{ + const struct ngm_ccatm_get_addresses *p; + + p = (const struct ngm_ccatm_get_addresses *) + (buf - offsetof(struct ngm_ccatm_get_addresses, addr)); + return (p->count); +} +static const struct ng_parse_array_info ng_ccatm_addr_req_array_type_info = + NGM_CCATM_ADDR_REQ_ARRAY_INFO; +static const struct ng_parse_type ng_ccatm_addr_req_array_type = { + &ng_parse_array_type, + &ng_ccatm_addr_req_array_type_info +}; + +/* Outer get_ADDRESSes structure */ +static const struct ng_parse_struct_field ng_ccatm_get_addresses_type_info[] = + NGM_CCATM_GET_ADDRESSES_INFO; +static const struct ng_parse_type ng_ccatm_get_addresses_type = { + &ng_parse_struct_type, + ng_ccatm_get_addresses_type_info +}; + +/* Port array */ +static int +ng_ccatm_port_array_getlen(const struct ng_parse_type *type, + const u_char *start, const u_char *buf) +{ + const struct ngm_ccatm_portlist *p; + + p = (const struct ngm_ccatm_portlist *) + (buf - offsetof(struct ngm_ccatm_portlist, ports)); + return (p->nports); +} +static const struct ng_parse_array_info ng_ccatm_port_array_type_info = + NGM_CCATM_PORT_ARRAY_INFO; +static const struct ng_parse_type ng_ccatm_port_array_type = { + &ng_parse_array_type, + &ng_ccatm_port_array_type_info +}; + +/* Portlist structure */ +static const struct ng_parse_struct_field ng_ccatm_portlist_type_info[] = + NGM_CCATM_PORTLIST_INFO; +static const struct ng_parse_type ng_ccatm_portlist_type = { + &ng_parse_struct_type, + ng_ccatm_portlist_type_info +}; + +/* + * Command list + */ +static const struct ng_cmdlist ng_ccatm_cmdlist[] = { + { + NGM_CCATM_COOKIE, + NGM_CCATM_DUMP, + "dump", + NULL, + NULL + }, + { + NGM_CCATM_COOKIE, + NGM_CCATM_STOP, + "stop", + &ng_ccatm_port_type, + NULL + }, + { + NGM_CCATM_COOKIE, + NGM_CCATM_START, + "start", + &ng_ccatm_port_type, + NULL + }, + { + NGM_CCATM_COOKIE, + NGM_CCATM_GETSTATE, + "getstate", + &ng_ccatm_port_type, + &ng_parse_uint32_type + }, + { + NGM_CCATM_COOKIE, + NGM_CCATM_GET_ADDRESSES, + "get_addresses", + &ng_ccatm_port_type, + &ng_ccatm_get_addresses_type + }, + { + NGM_CCATM_COOKIE, + NGM_CCATM_CLEAR, + "clear", + &ng_ccatm_port_type, + NULL + }, + { + NGM_CCATM_COOKIE, + NGM_CCATM_ADDRESS_REGISTERED, + "address_reg", + &ng_ccatm_addr_req_type, + NULL + }, + { + NGM_CCATM_COOKIE, + NGM_CCATM_ADDRESS_UNREGISTERED, + "address_unreg", + &ng_ccatm_addr_req_type, + NULL + }, + { + NGM_CCATM_COOKIE, + NGM_CCATM_SET_PORT_PARAM, + "set_port_param", + &ng_ccatm_atm_port_type, + NULL + }, + { + NGM_CCATM_COOKIE, + NGM_CCATM_GET_PORT_PARAM, + "get_port_param", + &ng_ccatm_port_type, + &ng_ccatm_atm_port_type, + }, + { + NGM_CCATM_COOKIE, + NGM_CCATM_GET_PORTLIST, + "get_portlist", + NULL, + &ng_ccatm_portlist_type, + }, + { + NGM_CCATM_COOKIE, + NGM_CCATM_SETLOG, + "setlog", + &ng_parse_hint32_type, + &ng_parse_hint32_type, + }, + { + NGM_CCATM_COOKIE, + NGM_CCATM_RESET, + "reset", + NULL, + NULL, + }, + { 0 } +}; + +/* + * Module data + */ +static ng_constructor_t ng_ccatm_constructor; +static ng_rcvmsg_t ng_ccatm_rcvmsg; +static ng_shutdown_t ng_ccatm_shutdown; +static ng_newhook_t ng_ccatm_newhook; +static ng_rcvdata_t ng_ccatm_rcvdata; +static ng_disconnect_t ng_ccatm_disconnect; +static int ng_ccatm_mod_event(module_t, int, void *); + +static struct ng_type ng_ccatm_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_CCATM_NODE_TYPE, + .mod_event = ng_ccatm_mod_event, + .constructor = ng_ccatm_constructor, /* Node constructor */ + .rcvmsg = ng_ccatm_rcvmsg, /* Control messages */ + .shutdown = ng_ccatm_shutdown, /* Node destructor */ + .newhook = ng_ccatm_newhook, /* Arrival of new hook */ + .rcvdata = ng_ccatm_rcvdata, /* receive data */ + .disconnect = ng_ccatm_disconnect, /* disconnect a hook */ + .cmdlist = ng_ccatm_cmdlist, +}; +NETGRAPH_INIT(ccatm, &ng_ccatm_typestruct); + +static ng_rcvdata_t ng_ccatm_rcvuni; +static ng_rcvdata_t ng_ccatm_rcvdump; +static ng_rcvdata_t ng_ccatm_rcvmanage; + +/* + * Private node data. + */ +struct ccnode { + node_p node; /* the owning node */ + hook_p dump; /* dump hook */ + hook_p manage; /* hook to ILMI */ + + struct ccdata *data; + struct mbuf *dump_first; + struct mbuf *dump_last; /* first and last mbuf when dumping */ + + u_int hook_cnt; /* count user and port hooks */ +}; + +/* + * Private UNI hook data + */ +struct cchook { + int is_uni; /* true if uni hook, user otherwise */ + struct ccnode *node; /* the owning node */ + hook_p hook; + void *inst; /* port or user */ +}; + +static void ng_ccatm_send_user(struct ccuser *, void *, u_int, void *, size_t); +static void ng_ccatm_respond_user(struct ccuser *, void *, int, u_int, + void *, size_t); +static void ng_ccatm_send_uni(struct ccconn *, void *, u_int, u_int, + struct uni_msg *); +static void ng_ccatm_send_uni_glob(struct ccport *, void *, u_int, u_int, + struct uni_msg *); +static void ng_ccatm_log(const char *, ...) __printflike(1, 2); + +static const struct cc_funcs cc_funcs = { + .send_user = ng_ccatm_send_user, + .respond_user = ng_ccatm_respond_user, + .send_uni = ng_ccatm_send_uni, + .send_uni_glob = ng_ccatm_send_uni_glob, + .log = ng_ccatm_log, +}; + +/************************************************************ + * + * Create a new node + */ +static int +ng_ccatm_constructor(node_p node) +{ + struct ccnode *priv; + + priv = malloc(sizeof(*priv), M_NG_CCATM, M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + + priv->node = node; + priv->data = cc_create(&cc_funcs); + if (priv->data == NULL) { + free(priv, M_NG_CCATM); + return (ENOMEM); + } + + NG_NODE_SET_PRIVATE(node, priv); + + return (0); +} + +/* + * Destroy a node. The user list is empty here, because all hooks are + * previously disconnected. The connection lists may not be empty, because + * connections may be waiting for responses from the stack. This also means, + * that no orphaned connections will be made by the port_destroy routine. + */ +static int +ng_ccatm_shutdown(node_p node) +{ + struct ccnode *priv = NG_NODE_PRIVATE(node); + + cc_destroy(priv->data); + + free(priv, M_NG_CCATM); + NG_NODE_SET_PRIVATE(node, NULL); + + NG_NODE_UNREF(node); + + return (0); +} + +/* + * Retrieve the registered addresses for one port or all ports. + * Returns an error code or 0 on success. + */ +static int +ng_ccatm_get_addresses(node_p node, uint32_t portno, struct ng_mesg *msg, + struct ng_mesg **resp) +{ + struct ccnode *priv = NG_NODE_PRIVATE(node); + struct uni_addr *addrs; + u_int *ports; + struct ngm_ccatm_get_addresses *list; + u_int count, i; + size_t len; + int err; + + err = cc_get_addrs(priv->data, portno, &addrs, &ports, &count); + if (err != 0) + return (err); + + len = sizeof(*list) + count * sizeof(list->addr[0]); + NG_MKRESPONSE(*resp, msg, len, M_NOWAIT); + if (*resp == NULL) { + free(addrs, M_NG_CCATM); + free(ports, M_NG_CCATM); + return (ENOMEM); + } + list = (struct ngm_ccatm_get_addresses *)(*resp)->data; + + list->count = count; + for (i = 0; i < count; i++) { + list->addr[i].port = ports[i]; + list->addr[i].addr = addrs[i]; + } + + free(addrs, M_NG_CCATM); + free(ports, M_NG_CCATM); + + return (0); +} + +/* + * Dumper function. Pack the data into an mbuf chain. + */ +static int +send_dump(struct ccdata *data, void *uarg, const char *buf) +{ + struct mbuf *m; + struct ccnode *priv = uarg; + + if (priv->dump == NULL) { + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (m == NULL) + return (ENOBUFS); + priv->dump_first = priv->dump_last = m; + m->m_pkthdr.len = 0; + } else { + m = m_getcl(M_DONTWAIT, MT_DATA, 0); + if (m == 0) { + m_freem(priv->dump_first); + return (ENOBUFS); + } + priv->dump_last->m_next = m; + priv->dump_last = m; + } + + strcpy(m->m_data, buf); + priv->dump_first->m_pkthdr.len += (m->m_len = strlen(buf)); + + return (0); +} + +/* + * Dump current status to dump hook + */ +static int +ng_ccatm_dump(node_p node) +{ + struct ccnode *priv = NG_NODE_PRIVATE(node); + struct mbuf *m; + int error; + + priv->dump_first = priv->dump_last = NULL; + error = cc_dump(priv->data, MCLBYTES, send_dump, priv); + if (error != 0) + return (error); + + if ((m = priv->dump_first) != NULL) { + priv->dump_first = priv->dump_last = NULL; + NG_SEND_DATA_ONLY(error, priv->dump, m); + return (error); + } + return (0); +} + +/* + * Control message + */ +static int +ng_ccatm_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct ng_mesg *resp = NULL; + struct ng_mesg *msg; + struct ccnode *priv = NG_NODE_PRIVATE(node); + int error = 0; + + NGI_GET_MSG(item, msg); + + switch (msg->header.typecookie) { + + case NGM_CCATM_COOKIE: + switch (msg->header.cmd) { + + case NGM_CCATM_DUMP: + if (priv->dump) + error = ng_ccatm_dump(node); + else + error = ENOTCONN; + break; + + case NGM_CCATM_STOP: + { + struct ngm_ccatm_port *arg; + + if (msg->header.arglen != sizeof(*arg)) { + error = EINVAL; + break; + } + arg = (struct ngm_ccatm_port *)msg->data; + error = cc_port_stop(priv->data, arg->port); + break; + } + + case NGM_CCATM_START: + { + struct ngm_ccatm_port *arg; + + if (msg->header.arglen != sizeof(*arg)) { + error = EINVAL; + break; + } + arg = (struct ngm_ccatm_port *)msg->data; + error = cc_port_start(priv->data, arg->port); + break; + } + + case NGM_CCATM_GETSTATE: + { + struct ngm_ccatm_port *arg; + int state; + + if (msg->header.arglen != sizeof(*arg)) { + error = EINVAL; + break; + } + arg = (struct ngm_ccatm_port *)msg->data; + error = cc_port_isrunning(priv->data, arg->port, + &state); + if (error == 0) { + NG_MKRESPONSE(resp, msg, sizeof(uint32_t), + M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + *(uint32_t *)resp->data = state; + } + break; + } + + case NGM_CCATM_GET_ADDRESSES: + { + struct ngm_ccatm_port *arg; + + if (msg->header.arglen != sizeof(*arg)) { + error = EINVAL; + break; + } + arg = (struct ngm_ccatm_port *)msg->data; + error = ng_ccatm_get_addresses(node, arg->port, msg, + &resp); + break; + } + + case NGM_CCATM_CLEAR: + { + struct ngm_ccatm_port *arg; + + if (msg->header.arglen != sizeof(*arg)) { + error = EINVAL; + break; + } + arg = (struct ngm_ccatm_port *)msg->data; + error = cc_port_clear(priv->data, arg->port); + break; + } + + case NGM_CCATM_ADDRESS_REGISTERED: + { + struct ngm_ccatm_addr_req *arg; + + if (msg->header.arglen != sizeof(*arg)) { + error = EINVAL; + break; + } + arg = (struct ngm_ccatm_addr_req *)msg->data; + error = cc_addr_register(priv->data, arg->port, + &arg->addr); + break; + } + + case NGM_CCATM_ADDRESS_UNREGISTERED: + { + struct ngm_ccatm_addr_req *arg; + + if (msg->header.arglen != sizeof(*arg)) { + error = EINVAL; + break; + } + arg = (struct ngm_ccatm_addr_req *)msg->data; + error = cc_addr_unregister(priv->data, arg->port, + &arg->addr); + break; + } + + case NGM_CCATM_GET_PORT_PARAM: + { + struct ngm_ccatm_port *arg; + + if (msg->header.arglen != sizeof(*arg)) { + error = EINVAL; + break; + } + arg = (struct ngm_ccatm_port *)msg->data; + NG_MKRESPONSE(resp, msg, sizeof(struct atm_port_info), + M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + error = cc_port_get_param(priv->data, arg->port, + (struct atm_port_info *)resp->data); + if (error != 0) { + free(resp, M_NETGRAPH_MSG); + resp = NULL; + } + break; + } + + case NGM_CCATM_SET_PORT_PARAM: + { + struct atm_port_info *arg; + + if (msg->header.arglen != sizeof(*arg)) { + error = EINVAL; + break; + } + arg = (struct atm_port_info *)msg->data; + error = cc_port_set_param(priv->data, arg); + break; + } + + case NGM_CCATM_GET_PORTLIST: + { + struct ngm_ccatm_portlist *arg; + u_int n, *ports; + + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + error = cc_port_getlist(priv->data, &n, &ports); + if (error != 0) + break; + + NG_MKRESPONSE(resp, msg, sizeof(*arg) + + n * sizeof(arg->ports[0]), M_NOWAIT); + if (resp == NULL) { + free(ports, M_NG_CCATM); + error = ENOMEM; + break; + } + arg = (struct ngm_ccatm_portlist *)resp->data; + + arg->nports = 0; + for (arg->nports = 0; arg->nports < n; arg->nports++) + arg->ports[arg->nports] = ports[arg->nports]; + free(ports, M_NG_CCATM); + break; + } + + case NGM_CCATM_SETLOG: + { + uint32_t log_level; + + log_level = cc_get_log(priv->data); + if (msg->header.arglen != 0) { + if (msg->header.arglen != sizeof(log_level)) { + error = EINVAL; + break; + } + cc_set_log(priv->data, *(uint32_t *)msg->data); + } + + NG_MKRESPONSE(resp, msg, sizeof(uint32_t), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + if (msg->header.arglen != 0) + cc_set_log(priv->data, log_level); + break; + } + *(uint32_t *)resp->data = log_level; + break; + } + + case NGM_CCATM_RESET: + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + + if (priv->hook_cnt != 0) { + error = EBUSY; + break; + } + cc_reset(priv->data); + break; + + case NGM_CCATM_GET_EXSTAT: + { + struct atm_exstatus s; + struct atm_exstatus_ep *eps; + struct atm_exstatus_port *ports; + struct atm_exstatus_conn *conns; + struct atm_exstatus_party *parties; + size_t offs; + + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + error = cc_get_extended_status(priv->data, + &s, &eps, &ports, &conns, &parties); + if (error != 0) + break; + + offs = sizeof(s) + s.neps * sizeof(*eps) + + s.nports * sizeof(*ports) + + s.nconns * sizeof(*conns) + + s.nparties * sizeof(*parties); + + NG_MKRESPONSE(resp, msg, offs, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + + memcpy(resp->data, &s, sizeof(s)); + offs = sizeof(s); + + memcpy(resp->data + offs, eps, + sizeof(*eps) * s.neps); + offs += sizeof(*eps) * s.neps; + + memcpy(resp->data + offs, ports, + sizeof(*ports) * s.nports); + offs += sizeof(*ports) * s.nports; + + memcpy(resp->data + offs, conns, + sizeof(*conns) * s.nconns); + offs += sizeof(*conns) * s.nconns; + + memcpy(resp->data + offs, parties, + sizeof(*parties) * s.nparties); + offs += sizeof(*parties) * s.nparties; + + free(eps, M_NG_CCATM); + free(ports, M_NG_CCATM); + free(conns, M_NG_CCATM); + free(parties, M_NG_CCATM); + break; + } + + default: + error = EINVAL; + break; + + } + break; + + default: + error = EINVAL; + break; + + } + + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/************************************************************ + * + * New hook arrival + */ +static int +ng_ccatm_newhook(node_p node, hook_p hook, const char *name) +{ + struct ccnode *priv = NG_NODE_PRIVATE(node); + struct ccport *port; + struct ccuser *user; + struct cchook *hd; + u_long lport; + char *end; + + if (strncmp(name, "uni", 3) == 0) { + /* + * This is a UNI hook. Should be a new port. + */ + if (name[3] == '\0') + return (EINVAL); + lport = strtoul(name + 3, &end, 10); + if (*end != '\0' || lport == 0 || lport > 0xffffffff) + return (EINVAL); + + hd = malloc(sizeof(*hd), M_NG_CCATM, M_NOWAIT); + if (hd == NULL) + return (ENOMEM); + hd->is_uni = 1; + hd->node = priv; + hd->hook = hook; + + port = cc_port_create(priv->data, hd, (u_int)lport); + if (port == NULL) { + free(hd, M_NG_CCATM); + return (ENOMEM); + } + hd->inst = port; + + NG_HOOK_SET_PRIVATE(hook, hd); + NG_HOOK_SET_RCVDATA(hook, ng_ccatm_rcvuni); + NG_HOOK_FORCE_QUEUE(hook); + + priv->hook_cnt++; + + return (0); + } + + if (strcmp(name, "dump") == 0) { + priv->dump = hook; + NG_HOOK_SET_RCVDATA(hook, ng_ccatm_rcvdump); + return (0); + } + + if (strcmp(name, "manage") == 0) { + priv->manage = hook; + NG_HOOK_SET_RCVDATA(hook, ng_ccatm_rcvmanage); + return (0); + } + + /* + * User hook + */ + hd = malloc(sizeof(*hd), M_NG_CCATM, M_NOWAIT); + if (hd == NULL) + return (ENOMEM); + hd->is_uni = 0; + hd->node = priv; + hd->hook = hook; + + user = cc_user_create(priv->data, hd, NG_HOOK_NAME(hook)); + if (user == NULL) { + free(hd, M_NG_CCATM); + return (ENOMEM); + } + + hd->inst = user; + NG_HOOK_SET_PRIVATE(hook, hd); + NG_HOOK_FORCE_QUEUE(hook); + + priv->hook_cnt++; + + return (0); +} + +/* + * Disconnect a hook + */ +static int +ng_ccatm_disconnect(hook_p hook) +{ + node_p node = NG_HOOK_NODE(hook); + struct ccnode *priv = NG_NODE_PRIVATE(node); + struct cchook *hd = NG_HOOK_PRIVATE(hook); + struct ccdata *cc; + + if (hook == priv->dump) { + priv->dump = NULL; + + } else if (hook == priv->manage) { + priv->manage = NULL; + cc_unmanage(priv->data); + + } else { + if (hd->is_uni) + cc_port_destroy(hd->inst, 0); + else + cc_user_destroy(hd->inst); + + cc = hd->node->data; + + free(hd, M_NG_CCATM); + NG_HOOK_SET_PRIVATE(hook, NULL); + + priv->hook_cnt--; + + cc_work(cc); + } + + /* + * When the number of hooks drops to zero, delete the node. + */ + if (NG_NODE_NUMHOOKS(node) == 0 && NG_NODE_IS_VALID(node)) + ng_rmnode_self(node); + + return (0); +} + +/************************************************************ + * + * Receive data from user hook + */ +static int +ng_ccatm_rcvdata(hook_p hook, item_p item) +{ + struct cchook *hd = NG_HOOK_PRIVATE(hook); + struct uni_msg *msg; + struct mbuf *m; + struct ccatm_op op; + int err; + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + if ((err = uni_msg_unpack_mbuf(m, &msg)) != 0) { + m_freem(m); + return (err); + } + m_freem(m); + + if (uni_msg_len(msg) < sizeof(op)) { + printf("%s: packet too short\n", __func__); + uni_msg_destroy(msg); + return (EINVAL); + } + + bcopy(msg->b_rptr, &op, sizeof(op)); + msg->b_rptr += sizeof(op); + + err = cc_user_signal(hd->inst, op.op, msg); + cc_work(hd->node->data); + return (err); +} + +/* + * Pack a header and a data area into an mbuf chain + */ +static struct mbuf * +pack_buf(void *h, size_t hlen, void *t, size_t tlen) +{ + struct mbuf *m, *m0, *last; + u_char *buf = (u_char *)t; + size_t n; + + /* header should fit into a normal mbuf */ + MGETHDR(m0, M_NOWAIT, MT_DATA); + if (m0 == NULL) + return NULL; + + KASSERT(hlen <= MHLEN, ("hlen > MHLEN")); + + bcopy(h, m0->m_data, hlen); + m0->m_len = hlen; + m0->m_pkthdr.len = hlen; + + last = m0; + while ((n = tlen) != 0) { + if (n > MLEN) { + m = m_getcl(M_NOWAIT, MT_DATA, 0); + if (n > MCLBYTES) + n = MCLBYTES; + } else + MGET(m, M_NOWAIT, MT_DATA); + + if(m == NULL) + goto drop; + + last->m_next = m; + last = m; + + bcopy(buf, m->m_data, n); + buf += n; + tlen -= n; + m->m_len = n; + m0->m_pkthdr.len += n; + } + + return (m0); + + drop: + m_freem(m0); + return NULL; +} + +/* + * Send an indication to the user. + */ +static void +ng_ccatm_send_user(struct ccuser *user, void *uarg, u_int op, + void *val, size_t len) +{ + struct cchook *hd = uarg; + struct mbuf *m; + struct ccatm_op h; + int error; + + h.op = op; + m = pack_buf(&h, sizeof(h), val, len); + if (m == NULL) + return; + + NG_SEND_DATA_ONLY(error, hd->hook, m); + if (error != 0) + printf("%s: error=%d\n", __func__, error); +} + +/* + * Send a response to the user. + */ +static void +ng_ccatm_respond_user(struct ccuser *user, void *uarg, int err, u_int data, + void *val, size_t len) +{ + struct cchook *hd = uarg; + struct mbuf *m; + struct { + struct ccatm_op op; + struct atm_resp resp; + } resp; + int error; + + resp.op.op = ATMOP_RESP; + resp.resp.resp = err; + resp.resp.data = data; + m = pack_buf(&resp, sizeof(resp), val, len); + if (m == NULL) + return; + + NG_SEND_DATA_ONLY(error, hd->hook, m); + if (error != 0) + printf("%s: error=%d\n", __func__, error); +} + +/* + * Receive data from UNI. + */ +static int +ng_ccatm_rcvuni(hook_p hook, item_p item) +{ + struct cchook *hd = NG_HOOK_PRIVATE(hook); + struct uni_msg *msg; + struct uni_arg arg; + struct mbuf *m; + int err; + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + if ((err = uni_msg_unpack_mbuf(m, &msg)) != 0) { + m_freem(m); + return (err); + } + m_freem(m); + + if (uni_msg_len(msg) < sizeof(arg)) { + printf("%s: packet too short\n", __func__); + uni_msg_destroy(msg); + return (EINVAL); + } + + bcopy(msg->b_rptr, &arg, sizeof(arg)); + msg->b_rptr += sizeof(arg); + + if (arg.sig == UNIAPI_ERROR) { + if (uni_msg_len(msg) != sizeof(struct uniapi_error)) { + printf("%s: bad UNIAPI_ERROR size %zu\n", __func__, + uni_msg_len(msg)); + uni_msg_destroy(msg); + return (EINVAL); + } + err = cc_uni_response(hd->inst, arg.cookie, + ((struct uniapi_error *)msg->b_rptr)->reason, + ((struct uniapi_error *)msg->b_rptr)->state); + uni_msg_destroy(msg); + } else + err = cc_uni_signal(hd->inst, arg.cookie, arg.sig, msg); + + cc_work(hd->node->data); + return (err); +} + +/* + * Uarg is the port's uarg. + */ +static void +ng_ccatm_send_uni(struct ccconn *conn, void *uarg, u_int op, u_int cookie, + struct uni_msg *msg) +{ + struct cchook *hd = uarg; + struct uni_arg arg; + struct mbuf *m; + int error; + + arg.sig = op; + arg.cookie = cookie; + + m = uni_msg_pack_mbuf(msg, &arg, sizeof(arg)); + uni_msg_destroy(msg); + if (m == NULL) + return; + + NG_SEND_DATA_ONLY(error, hd->hook, m); + if (error != 0) + printf("%s: error=%d\n", __func__, error); +} + +/* + * Send a global message to the UNI + */ +static void +ng_ccatm_send_uni_glob(struct ccport *port, void *uarg, u_int op, u_int cookie, + struct uni_msg *msg) +{ + struct cchook *hd = uarg; + struct uni_arg arg; + struct mbuf *m; + int error; + + arg.sig = op; + arg.cookie = cookie; + + m = uni_msg_pack_mbuf(msg, &arg, sizeof(arg)); + if (msg != NULL) + uni_msg_destroy(msg); + if (m == NULL) + return; + + NG_SEND_DATA_ONLY(error, hd->hook, m); + if (error != 0) + printf("%s: error=%d\n", __func__, error); +} +/* + * Receive from ILMID + */ +static int +ng_ccatm_rcvmanage(hook_p hook, item_p item) +{ + NG_FREE_ITEM(item); + return (0); +} + +static int +ng_ccatm_rcvdump(hook_p hook, item_p item) +{ + NG_FREE_ITEM(item); + return (0); +} + +static void +ng_ccatm_log(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vprintf(fmt, ap); + printf("\n"); + va_end(ap); +} + +/* + * Loading and unloading of node type + */ +static int +ng_ccatm_mod_event(module_t mod, int event, void *data) +{ + int s; + int error = 0; + + s = splnet(); + switch (event) { + + case MOD_LOAD: + break; + + case MOD_UNLOAD: + break; + + default: + error = EOPNOTSUPP; + break; + } + splx(s); + return (error); +} diff --git a/sys/netgraph7/atm/ccatm/ng_ccatm_cust.h b/sys/netgraph7/atm/ccatm/ng_ccatm_cust.h new file mode 100644 index 0000000000..0749a94998 --- /dev/null +++ b/sys/netgraph7/atm/ccatm/ng_ccatm_cust.h @@ -0,0 +1,54 @@ +/*- + * Copyright (c) 2003-2004 + * Hartmut Brandt + * All rights reserved. + * + * Author: Hartmut Brandt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Customisation of call control source to the NG environment. + * + * $FreeBSD: src/sys/netgraph/atm/ccatm/ng_ccatm_cust.h,v 1.2 2005/01/07 01:45:41 imp Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CCASSERT(E, M) KASSERT(E, M) + +MALLOC_DECLARE(M_NG_CCATM); + +#define CCMALLOC(S) (malloc((S), M_NG_CCATM, M_NOWAIT)) +#define CCZALLOC(S) (malloc((S), M_NG_CCATM, M_NOWAIT | M_ZERO)) +#define CCFREE(P) do { free((P), M_NG_CCATM); } while (0) + +#define CCGETERRNO() (ENOMEM) diff --git a/sys/netgraph7/atm/ng_atm.c b/sys/netgraph7/atm/ng_atm.c new file mode 100644 index 0000000000..9528ac54ba --- /dev/null +++ b/sys/netgraph7/atm/ng_atm.c @@ -0,0 +1,1433 @@ +/*- + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Author: Hartmut Brandt + */ + +/* + * Netgraph module to connect NATM interfaces to netgraph. + */ + +#include +__FBSDID("$FreeBSD: src/sys/netgraph/atm/ng_atm.c,v 1.15 2005/08/10 06:25:40 obrien Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* + * Hooks in the NATM code + */ +extern void (*ng_atm_attach_p)(struct ifnet *); +extern void (*ng_atm_detach_p)(struct ifnet *); +extern int (*ng_atm_output_p)(struct ifnet *, struct mbuf **); +extern void (*ng_atm_input_p)(struct ifnet *, struct mbuf **, + struct atm_pseudohdr *, void *); +extern void (*ng_atm_input_orphan_p)(struct ifnet *, struct mbuf *, + struct atm_pseudohdr *, void *); +extern void (*ng_atm_event_p)(struct ifnet *, uint32_t, void *); + +/* + * Sysctl stuff. + */ +SYSCTL_NODE(_net_graph, OID_AUTO, atm, CTLFLAG_RW, 0, "atm related stuff"); + +#ifdef NGATM_DEBUG +static int allow_shutdown; + +SYSCTL_INT(_net_graph_atm, OID_AUTO, allow_shutdown, CTLFLAG_RW, + &allow_shutdown, 0, "allow ng_atm nodes to shutdown"); +#endif + +/* + * Hook private data + */ +struct ngvcc { + uint16_t vpi; /* VPI of this hook */ + uint16_t vci; /* VCI of this hook, 0 if none */ + uint32_t flags; /* private flags */ + hook_p hook; /* the connected hook */ + + LIST_ENTRY(ngvcc) link; +}; +#define VCC_OPEN 0x0001 /* open */ + +/* + * Node private data + */ +struct priv { + struct ifnet *ifp; /* the ATM interface */ + hook_p input; /* raw input hook */ + hook_p orphans; /* packets to nowhere */ + hook_p output; /* catch output packets */ + hook_p manage; /* has also entry in vccs */ + uint64_t in_packets; + uint64_t in_errors; + uint64_t out_packets; + uint64_t out_errors; + + LIST_HEAD(, ngvcc) vccs; +}; + +/* + * Parse ifstate state + */ +static const struct ng_parse_struct_field ng_atm_if_change_info[] = + NGM_ATM_IF_CHANGE_INFO; +static const struct ng_parse_type ng_atm_if_change_type = { + &ng_parse_struct_type, + &ng_atm_if_change_info +}; + +/* + * Parse vcc state change + */ +static const struct ng_parse_struct_field ng_atm_vcc_change_info[] = + NGM_ATM_VCC_CHANGE_INFO; +static const struct ng_parse_type ng_atm_vcc_change_type = { + &ng_parse_struct_type, + &ng_atm_vcc_change_info +}; + +/* + * Parse acr change + */ +static const struct ng_parse_struct_field ng_atm_acr_change_info[] = + NGM_ATM_ACR_CHANGE_INFO; +static const struct ng_parse_type ng_atm_acr_change_type = { + &ng_parse_struct_type, + &ng_atm_acr_change_info +}; + +/* + * Parse the configuration structure ng_atm_config + */ +static const struct ng_parse_struct_field ng_atm_config_type_info[] = + NGM_ATM_CONFIG_INFO; + +static const struct ng_parse_type ng_atm_config_type = { + &ng_parse_struct_type, + &ng_atm_config_type_info +}; + +/* + * Parse a single vcc structure and a variable array of these ng_atm_vccs + */ +static const struct ng_parse_struct_field ng_atm_tparam_type_info[] = + NGM_ATM_TPARAM_INFO; +static const struct ng_parse_type ng_atm_tparam_type = { + &ng_parse_struct_type, + &ng_atm_tparam_type_info +}; +static const struct ng_parse_struct_field ng_atm_vcc_type_info[] = + NGM_ATM_VCC_INFO; +static const struct ng_parse_type ng_atm_vcc_type = { + &ng_parse_struct_type, + &ng_atm_vcc_type_info +}; + + +static int +ng_atm_vccarray_getlen(const struct ng_parse_type *type, + const u_char *start, const u_char *buf) +{ + const struct atmio_vcctable *vp; + + vp = (const struct atmio_vcctable *) + (buf - offsetof(struct atmio_vcctable, vccs)); + + return (vp->count); +} +static const struct ng_parse_array_info ng_atm_vccarray_info = + NGM_ATM_VCCARRAY_INFO; +static const struct ng_parse_type ng_atm_vccarray_type = { + &ng_parse_array_type, + &ng_atm_vccarray_info +}; + + +static const struct ng_parse_struct_field ng_atm_vcctable_type_info[] = + NGM_ATM_VCCTABLE_INFO; + +static const struct ng_parse_type ng_atm_vcctable_type = { + &ng_parse_struct_type, + &ng_atm_vcctable_type_info +}; + +/* + * Parse CPCS INIT structure ng_atm_cpcs_init + */ +static const struct ng_parse_struct_field ng_atm_cpcs_init_type_info[] = + NGM_ATM_CPCS_INIT_INFO; + +static const struct ng_parse_type ng_atm_cpcs_init_type = { + &ng_parse_struct_type, + &ng_atm_cpcs_init_type_info +}; + +/* + * Parse CPCS TERM structure ng_atm_cpcs_term + */ +static const struct ng_parse_struct_field ng_atm_cpcs_term_type_info[] = + NGM_ATM_CPCS_TERM_INFO; + +static const struct ng_parse_type ng_atm_cpcs_term_type = { + &ng_parse_struct_type, + &ng_atm_cpcs_term_type_info +}; + +/* + * Parse statistic struct + */ +static const struct ng_parse_struct_field ng_atm_stats_type_info[] = + NGM_ATM_STATS_INFO; + +static const struct ng_parse_type ng_atm_stats_type = { + &ng_parse_struct_type, + &ng_atm_stats_type_info +}; + +static const struct ng_cmdlist ng_atm_cmdlist[] = { + { + NGM_ATM_COOKIE, + NGM_ATM_GET_IFNAME, + "getifname", + NULL, + &ng_parse_string_type + }, + { + NGM_ATM_COOKIE, + NGM_ATM_GET_CONFIG, + "getconfig", + NULL, + &ng_atm_config_type + }, + { + NGM_ATM_COOKIE, + NGM_ATM_GET_VCCS, + "getvccs", + NULL, + &ng_atm_vcctable_type + }, + { + NGM_ATM_COOKIE, + NGM_ATM_CPCS_INIT, + "cpcsinit", + &ng_atm_cpcs_init_type, + NULL + }, + { + NGM_ATM_COOKIE, + NGM_ATM_CPCS_TERM, + "cpcsterm", + &ng_atm_cpcs_term_type, + NULL + }, + { + NGM_ATM_COOKIE, + NGM_ATM_GET_VCC, + "getvcc", + &ng_parse_hookbuf_type, + &ng_atm_vcc_type + }, + { + NGM_ATM_COOKIE, + NGM_ATM_GET_VCCID, + "getvccid", + &ng_atm_vcc_type, + &ng_atm_vcc_type + }, + { + NGM_ATM_COOKIE, + NGM_ATM_GET_STATS, + "getstats", + NULL, + &ng_atm_stats_type + }, + + /* events */ + { + NGM_ATM_COOKIE, + NGM_ATM_IF_CHANGE, + "if_change", + &ng_atm_if_change_type, + &ng_atm_if_change_type, + }, + { + NGM_ATM_COOKIE, + NGM_ATM_VCC_CHANGE, + "vcc_change", + &ng_atm_vcc_change_type, + &ng_atm_vcc_change_type, + }, + { + NGM_ATM_COOKIE, + NGM_ATM_ACR_CHANGE, + "acr_change", + &ng_atm_acr_change_type, + &ng_atm_acr_change_type, + }, + { 0 } +}; + +static int ng_atm_mod_event(module_t, int, void *); + +static ng_constructor_t ng_atm_constructor; +static ng_shutdown_t ng_atm_shutdown; +static ng_rcvmsg_t ng_atm_rcvmsg; +static ng_newhook_t ng_atm_newhook; +static ng_connect_t ng_atm_connect; +static ng_disconnect_t ng_atm_disconnect; +static ng_rcvdata_t ng_atm_rcvdata; +static ng_rcvdata_t ng_atm_rcvdrop; + +static struct ng_type ng_atm_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_ATM_NODE_TYPE, + .mod_event = ng_atm_mod_event, + .constructor = ng_atm_constructor, + .rcvmsg = ng_atm_rcvmsg, + .shutdown = ng_atm_shutdown, + .newhook = ng_atm_newhook, + .connect = ng_atm_connect, + .rcvdata = ng_atm_rcvdata, + .disconnect = ng_atm_disconnect, + .cmdlist = ng_atm_cmdlist, +}; +NETGRAPH_INIT(atm, &ng_atm_typestruct); + +static const struct { + u_int media; + const char *name; +} atmmedia[] = IFM_SUBTYPE_ATM_DESCRIPTIONS; + + +#define IFP2NG(IFP) ((node_p)((struct ifatm *)(IFP)->if_softc)->ngpriv) +#define IFP2NG_SET(IFP, val) (((struct ifatm *)(IFP)->if_softc)->ngpriv = (val)) + +#define IFFLAGS "\020\001UP\002BROADCAST\003DEBUG\004LOOPBACK" \ + "\005POINTOPOINT\006SMART\007RUNNING\010NOARP" \ + "\011PROMISC\012ALLMULTI\013OACTIVE\014SIMPLEX" \ + "\015LINK0\016LINK1\017LINK2\020MULTICAST" + + +/************************************************************/ +/* + * INPUT + */ +/* + * A packet is received from an interface. + * If we have an input hook, prepend the pseudoheader to the data and + * deliver it out to that hook. If not, look whether it is destined for + * use. If so locate the appropriate hook, deliver the packet without the + * header and we are done. If it is not for us, leave it alone. + */ +static void +ng_atm_input(struct ifnet *ifp, struct mbuf **mp, + struct atm_pseudohdr *ah, void *rxhand) +{ + node_p node = IFP2NG(ifp); + struct priv *priv; + const struct ngvcc *vcc; + int error; + + if (node == NULL) + return; + priv = NG_NODE_PRIVATE(node); + if (priv->input != NULL) { + /* + * Prepend the atm_pseudoheader. + */ + M_PREPEND(*mp, sizeof(*ah), M_DONTWAIT); + if (*mp == NULL) + return; + memcpy(mtod(*mp, struct atm_pseudohdr *), ah, sizeof(*ah)); + NG_SEND_DATA_ONLY(error, priv->input, *mp); + if (error == 0) { + priv->in_packets++; + *mp = NULL; + } else { +#ifdef NGATM_DEBUG + printf("%s: error=%d\n", __func__, error); +#endif + priv->in_errors++; + } + return; + } + if ((ATM_PH_FLAGS(ah) & ATMIO_FLAG_NG) == 0) + return; + + vcc = (struct ngvcc *)rxhand; + + NG_SEND_DATA_ONLY(error, vcc->hook, *mp); + if (error == 0) { + priv->in_packets++; + *mp = NULL; + } else { +#ifdef NGATM_DEBUG + printf("%s: error=%d\n", __func__, error); +#endif + priv->in_errors++; + } +} + +/* + * ATM packet is about to be output. The atm_pseudohdr is already prepended. + * If the hook is set, reroute the packet to the hook. + */ +static int +ng_atm_output(struct ifnet *ifp, struct mbuf **mp) +{ + const node_p node = IFP2NG(ifp); + const struct priv *priv; + int error = 0; + + if (node == NULL) + return (0); + priv = NG_NODE_PRIVATE(node); + if (priv->output) { + NG_SEND_DATA_ONLY(error, priv->output, *mp); + *mp = NULL; + } + + return (error); +} + +/* + * Well, this doesn't make much sense for ATM. + */ +static void +ng_atm_input_orphans(struct ifnet *ifp, struct mbuf *m, + struct atm_pseudohdr *ah, void *rxhand) +{ + node_p node = IFP2NG(ifp); + struct priv *priv; + int error; + + if (node == NULL) { + m_freem(m); + return; + } + priv = NG_NODE_PRIVATE(node); + if (priv->orphans == NULL) { + m_freem(m); + return; + } + /* + * Prepend the atm_pseudoheader. + */ + M_PREPEND(m, sizeof(*ah), M_DONTWAIT); + if (m == NULL) + return; + memcpy(mtod(m, struct atm_pseudohdr *), ah, sizeof(*ah)); + NG_SEND_DATA_ONLY(error, priv->orphans, m); + if (error == 0) + priv->in_packets++; + else { + priv->in_errors++; +#ifdef NGATM_DEBUG + printf("%s: error=%d\n", __func__, error); +#endif + } +} + +/************************************************************/ +/* + * OUTPUT + */ +static int +ng_atm_rcvdata(hook_p hook, item_p item) +{ + node_p node = NG_HOOK_NODE(hook); + struct priv *priv = NG_NODE_PRIVATE(node); + const struct ngvcc *vcc = NG_HOOK_PRIVATE(hook); + struct mbuf *m; + struct atm_pseudohdr *aph; + int error; + + if (vcc->vci == 0) { + NG_FREE_ITEM(item); + return (ENOTCONN); + } + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + /* + * Prepend pseudo-hdr. Drivers don't care about the flags. + */ + M_PREPEND(m, sizeof(*aph), M_DONTWAIT); + if (m == NULL) { + NG_FREE_M(m); + return (ENOMEM); + } + aph = mtod(m, struct atm_pseudohdr *); + ATM_PH_VPI(aph) = vcc->vpi; + ATM_PH_SETVCI(aph, vcc->vci); + ATM_PH_FLAGS(aph) = 0; + + if ((error = atm_output(priv->ifp, m, NULL, NULL)) == 0) + priv->out_packets++; + else + priv->out_errors++; + return (error); +} + +static int +ng_atm_rcvdrop(hook_p hook, item_p item) +{ + NG_FREE_ITEM(item); + return (0); +} + + +/************************************************************ + * + * Event from driver. + */ +static void +ng_atm_event_func(node_p node, hook_p hook, void *arg, int event) +{ + const struct priv *priv = NG_NODE_PRIVATE(node); + struct ngvcc *vcc; + struct ng_mesg *mesg; + int error; + + switch (event) { + + case ATMEV_FLOW_CONTROL: + { + struct atmev_flow_control *ev = arg; + struct ngm_queue_state *qstate; + + /* find the connection */ + LIST_FOREACH(vcc, &priv->vccs, link) + if (vcc->vci == ev->vci && vcc->vpi == ev->vpi) + break; + if (vcc == NULL) + break; + + /* convert into a flow control message */ + NG_MKMESSAGE(mesg, NGM_FLOW_COOKIE, + ev->busy ? NGM_HIGH_WATER_PASSED : NGM_LOW_WATER_PASSED, + sizeof(struct ngm_queue_state), M_NOWAIT); + if (mesg == NULL) + break; + qstate = (struct ngm_queue_state *)mesg->data; + + /* XXX have to figure out how to get that info */ + + NG_SEND_MSG_HOOK(error, node, mesg, vcc->hook, 0); + break; + } + + case ATMEV_VCC_CHANGED: + { + struct atmev_vcc_changed *ev = arg; + struct ngm_atm_vcc_change *chg; + + if (priv->manage == NULL) + break; + NG_MKMESSAGE(mesg, NGM_ATM_COOKIE, NGM_ATM_VCC_CHANGE, + sizeof(struct ngm_atm_vcc_change), M_NOWAIT); + if (mesg == NULL) + break; + chg = (struct ngm_atm_vcc_change *)mesg->data; + chg->vci = ev->vci; + chg->vpi = ev->vpi; + chg->state = (ev->up != 0); + chg->node = NG_NODE_ID(node); + NG_SEND_MSG_HOOK(error, node, mesg, priv->manage, 0); + break; + } + + case ATMEV_IFSTATE_CHANGED: + { + struct atmev_ifstate_changed *ev = arg; + struct ngm_atm_if_change *chg; + + if (priv->manage == NULL) + break; + NG_MKMESSAGE(mesg, NGM_ATM_COOKIE, NGM_ATM_IF_CHANGE, + sizeof(struct ngm_atm_if_change), M_NOWAIT); + if (mesg == NULL) + break; + chg = (struct ngm_atm_if_change *)mesg->data; + chg->carrier = (ev->carrier != 0); + chg->running = (ev->running != 0); + chg->node = NG_NODE_ID(node); + NG_SEND_MSG_HOOK(error, node, mesg, priv->manage, 0); + break; + } + + case ATMEV_ACR_CHANGED: + { + struct atmev_acr_changed *ev = arg; + struct ngm_atm_acr_change *acr; + + /* find the connection */ + LIST_FOREACH(vcc, &priv->vccs, link) + if (vcc->vci == ev->vci && vcc->vpi == ev->vpi) + break; + if (vcc == NULL) + break; + + /* convert into a flow control message */ + NG_MKMESSAGE(mesg, NGM_ATM_COOKIE, NGM_ATM_ACR_CHANGE, + sizeof(struct ngm_atm_acr_change), M_NOWAIT); + if (mesg == NULL) + break; + acr = (struct ngm_atm_acr_change *)mesg->data; + acr->node = NG_NODE_ID(node); + acr->vci = ev->vci; + acr->vpi = ev->vpi; + acr->acr = ev->acr; + + NG_SEND_MSG_HOOK(error, node, mesg, vcc->hook, 0); + break; + } + } +} + +/* + * Use send_fn to get the right lock + */ +static void +ng_atm_event(struct ifnet *ifp, uint32_t event, void *arg) +{ + const node_p node = IFP2NG(ifp); + + if (node != NULL) + /* may happen during attach/detach */ + (void)ng_send_fn(node, NULL, ng_atm_event_func, arg, event); +} + +/************************************************************ + * + * CPCS + */ +/* + * Open a channel for the user + */ +static int +ng_atm_cpcs_init(node_p node, const struct ngm_atm_cpcs_init *arg) +{ + struct priv *priv = NG_NODE_PRIVATE(node); + const struct ifatm_mib *mib; + struct ngvcc *vcc; + struct atmio_openvcc data; + int err; + + if(priv->ifp->if_ioctl == NULL) + return (ENXIO); + + mib = (const struct ifatm_mib *)(priv->ifp->if_linkmib); + + LIST_FOREACH(vcc, &priv->vccs, link) + if (strcmp(arg->name, NG_HOOK_NAME(vcc->hook)) == 0) + break; + if (vcc == NULL) + return (ENOTCONN); + if (vcc->flags & VCC_OPEN) + return (EISCONN); + + /* + * Check user arguments and construct ioctl argument + */ + memset(&data, 0, sizeof(data)); + + data.rxhand = vcc; + + switch (data.param.aal = arg->aal) { + + case ATMIO_AAL_34: + case ATMIO_AAL_5: + case ATMIO_AAL_0: + case ATMIO_AAL_RAW: + break; + + default: + return (EINVAL); + } + + if (arg->vpi > 0xff) + return (EINVAL); + data.param.vpi = arg->vpi; + + /* allow 0.0 as catch all receive channel */ + if (arg->vci == 0 && (arg->vpi != 0 || !(arg->flags & ATMIO_FLAG_NOTX))) + return (EINVAL); + data.param.vci = arg->vci; + + data.param.tparam.pcr = arg->pcr; + + if (arg->mcr > arg->pcr) + return (EINVAL); + data.param.tparam.mcr = arg->mcr; + + if (!(arg->flags & ATMIO_FLAG_NOTX)) { + if (arg->tmtu == 0) + data.param.tmtu = priv->ifp->if_mtu; + else { + data.param.tmtu = arg->tmtu; + } + } + if (!(arg->flags & ATMIO_FLAG_NORX)) { + if (arg->rmtu == 0) + data.param.rmtu = priv->ifp->if_mtu; + else { + data.param.rmtu = arg->rmtu; + } + } + + switch (data.param.traffic = arg->traffic) { + + case ATMIO_TRAFFIC_UBR: + case ATMIO_TRAFFIC_CBR: + break; + + case ATMIO_TRAFFIC_VBR: + if (arg->scr > arg->pcr) + return (EINVAL); + data.param.tparam.scr = arg->scr; + + if (arg->mbs > (1 << 24)) + return (EINVAL); + data.param.tparam.mbs = arg->mbs; + break; + + case ATMIO_TRAFFIC_ABR: + if (arg->icr > arg->pcr || arg->icr < arg->mcr) + return (EINVAL); + data.param.tparam.icr = arg->icr; + + if (arg->tbe == 0 || arg->tbe > (1 << 24)) + return (EINVAL); + data.param.tparam.tbe = arg->tbe; + + if (arg->nrm > 0x7) + return (EINVAL); + data.param.tparam.nrm = arg->nrm; + + if (arg->trm > 0x7) + return (EINVAL); + data.param.tparam.trm = arg->trm; + + if (arg->adtf > 0x3ff) + return (EINVAL); + data.param.tparam.adtf = arg->adtf; + + if (arg->rif > 0xf) + return (EINVAL); + data.param.tparam.rif = arg->rif; + + if (arg->rdf > 0xf) + return (EINVAL); + data.param.tparam.rdf = arg->rdf; + + if (arg->cdf > 0x7) + return (EINVAL); + data.param.tparam.cdf = arg->cdf; + + break; + + default: + return (EINVAL); + } + + if ((arg->flags & ATMIO_FLAG_NORX) && (arg->flags & ATMIO_FLAG_NOTX)) + return (EINVAL); + + data.param.flags = arg->flags & ~(ATM_PH_AAL5 | ATM_PH_LLCSNAP); + data.param.flags |= ATMIO_FLAG_NG; + + err = (*priv->ifp->if_ioctl)(priv->ifp, SIOCATMOPENVCC, (caddr_t)&data); + + if (err == 0) { + vcc->vci = data.param.vci; + vcc->vpi = data.param.vpi; + vcc->flags = VCC_OPEN; + } + + return (err); +} + +/* + * Issue the close command to the driver + */ +static int +cpcs_term(const struct priv *priv, u_int vpi, u_int vci) +{ + struct atmio_closevcc data; + + if (priv->ifp->if_ioctl == NULL) + return ENXIO; + + data.vpi = vpi; + data.vci = vci; + + return ((*priv->ifp->if_ioctl)(priv->ifp, + SIOCATMCLOSEVCC, (caddr_t)&data)); +} + + +/* + * Close a channel by request of the user + */ +static int +ng_atm_cpcs_term(node_p node, const struct ngm_atm_cpcs_term *arg) +{ + struct priv *priv = NG_NODE_PRIVATE(node); + struct ngvcc *vcc; + int error; + + LIST_FOREACH(vcc, &priv->vccs, link) + if(strcmp(arg->name, NG_HOOK_NAME(vcc->hook)) == 0) + break; + if (vcc == NULL) + return (ENOTCONN); + if (!(vcc->flags & VCC_OPEN)) + return (ENOTCONN); + + error = cpcs_term(priv, vcc->vpi, vcc->vci); + + vcc->vci = 0; + vcc->vpi = 0; + vcc->flags = 0; + + return (error); +} + +/************************************************************/ +/* + * CONTROL MESSAGES + */ + +/* + * Produce a textual description of the current status + */ +static int +text_status(node_p node, char *arg, u_int len) +{ + const struct priv *priv = NG_NODE_PRIVATE(node); + const struct ifatm_mib *mib; + struct sbuf sbuf; + u_int i; + + static const struct { + const char *name; + const char *vendor; + } devices[] = { + ATM_DEVICE_NAMES + }; + + mib = (const struct ifatm_mib *)(priv->ifp->if_linkmib); + + sbuf_new(&sbuf, arg, len, SBUF_FIXEDLEN); + sbuf_printf(&sbuf, "interface: %s\n", priv->ifp->if_xname); + + if (mib->device >= sizeof(devices) / sizeof(devices[0])) + sbuf_printf(&sbuf, "device=unknown\nvendor=unknown\n"); + else + sbuf_printf(&sbuf, "device=%s\nvendor=%s\n", + devices[mib->device].name, devices[mib->device].vendor); + + for (i = 0; atmmedia[i].name; i++) + if(mib->media == atmmedia[i].media) { + sbuf_printf(&sbuf, "media=%s\n", atmmedia[i].name); + break; + } + if(atmmedia[i].name == NULL) + sbuf_printf(&sbuf, "media=unknown\n"); + + sbuf_printf(&sbuf, "serial=%u esi=%6D hardware=%u software=%u\n", + mib->serial, mib->esi, ":", mib->hw_version, mib->sw_version); + sbuf_printf(&sbuf, "pcr=%u vpi_bits=%u vci_bits=%u max_vpcs=%u " + "max_vccs=%u\n", mib->pcr, mib->vpi_bits, mib->vci_bits, + mib->max_vpcs, mib->max_vccs); + sbuf_printf(&sbuf, "ifflags=%b\n", priv->ifp->if_flags, IFFLAGS); + + sbuf_finish(&sbuf); + + return (sbuf_len(&sbuf)); +} + +/* + * Get control message + */ +static int +ng_atm_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const struct priv *priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + struct ng_mesg *msg; + struct ifatm_mib *mib = (struct ifatm_mib *)(priv->ifp->if_linkmib); + int error = 0; + + NGI_GET_MSG(item, msg); + + switch (msg->header.typecookie) { + + case NGM_GENERIC_COOKIE: + switch (msg->header.cmd) { + + case NGM_TEXT_STATUS: + NG_MKRESPONSE(resp, msg, NG_TEXTRESPONSE, M_NOWAIT); + if(resp == NULL) { + error = ENOMEM; + break; + } + + resp->header.arglen = text_status(node, + (char *)resp->data, resp->header.arglen) + 1; + break; + + default: + error = EINVAL; + break; + } + break; + + case NGM_ATM_COOKIE: + switch (msg->header.cmd) { + + case NGM_ATM_GET_IFNAME: + NG_MKRESPONSE(resp, msg, IFNAMSIZ, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + strlcpy(resp->data, priv->ifp->if_xname, IFNAMSIZ); + break; + + case NGM_ATM_GET_CONFIG: + { + struct ngm_atm_config *config; + + NG_MKRESPONSE(resp, msg, sizeof(*config), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + config = (struct ngm_atm_config *)resp->data; + config->pcr = mib->pcr; + config->vpi_bits = mib->vpi_bits; + config->vci_bits = mib->vci_bits; + config->max_vpcs = mib->max_vpcs; + config->max_vccs = mib->max_vccs; + break; + } + + case NGM_ATM_GET_VCCS: + { + struct atmio_vcctable *vccs; + size_t len; + + if (priv->ifp->if_ioctl == NULL) { + error = ENXIO; + break; + } + error = (*priv->ifp->if_ioctl)(priv->ifp, + SIOCATMGETVCCS, (caddr_t)&vccs); + if (error) + break; + + len = sizeof(*vccs) + + vccs->count * sizeof(vccs->vccs[0]); + NG_MKRESPONSE(resp, msg, len, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + free(vccs, M_DEVBUF); + break; + } + + (void)memcpy(resp->data, vccs, len); + free(vccs, M_DEVBUF); + + break; + } + + case NGM_ATM_GET_VCC: + { + char hook[NG_HOOKSIZ]; + struct atmio_vcctable *vccs; + struct ngvcc *vcc; + u_int i; + + if (priv->ifp->if_ioctl == NULL) { + error = ENXIO; + break; + } + if (msg->header.arglen != NG_HOOKSIZ) { + error = EINVAL; + break; + } + strncpy(hook, msg->data, NG_HOOKSIZ); + hook[NG_HOOKSIZ - 1] = '\0'; + LIST_FOREACH(vcc, &priv->vccs, link) + if (strcmp(NG_HOOK_NAME(vcc->hook), hook) == 0) + break; + if (vcc == NULL) { + error = ENOTCONN; + break; + } + error = (*priv->ifp->if_ioctl)(priv->ifp, + SIOCATMGETVCCS, (caddr_t)&vccs); + if (error) + break; + + for (i = 0; i < vccs->count; i++) + if (vccs->vccs[i].vpi == vcc->vpi && + vccs->vccs[i].vci == vcc->vci) + break; + if (i == vccs->count) { + error = ENOTCONN; + free(vccs, M_DEVBUF); + break; + } + + NG_MKRESPONSE(resp, msg, sizeof(vccs->vccs[0]), + M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + free(vccs, M_DEVBUF); + break; + } + + *(struct atmio_vcc *)resp->data = vccs->vccs[i]; + free(vccs, M_DEVBUF); + break; + } + + case NGM_ATM_GET_VCCID: + { + struct atmio_vcc *arg; + struct atmio_vcctable *vccs; + u_int i; + + if (priv->ifp->if_ioctl == NULL) { + error = ENXIO; + break; + } + if (msg->header.arglen != sizeof(*arg)) { + error = EINVAL; + break; + } + arg = (struct atmio_vcc *)msg->data; + + error = (*priv->ifp->if_ioctl)(priv->ifp, + SIOCATMGETVCCS, (caddr_t)&vccs); + if (error) + break; + + for (i = 0; i < vccs->count; i++) + if (vccs->vccs[i].vpi == arg->vpi && + vccs->vccs[i].vci == arg->vci) + break; + if (i == vccs->count) { + error = ENOTCONN; + free(vccs, M_DEVBUF); + break; + } + + NG_MKRESPONSE(resp, msg, sizeof(vccs->vccs[0]), + M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + free(vccs, M_DEVBUF); + break; + } + + *(struct atmio_vcc *)resp->data = vccs->vccs[i]; + free(vccs, M_DEVBUF); + break; + } + + case NGM_ATM_CPCS_INIT: + if (msg->header.arglen != + sizeof(struct ngm_atm_cpcs_init)) { + error = EINVAL; + break; + } + error = ng_atm_cpcs_init(node, + (struct ngm_atm_cpcs_init *)msg->data); + break; + + case NGM_ATM_CPCS_TERM: + if (msg->header.arglen != + sizeof(struct ngm_atm_cpcs_term)) { + error = EINVAL; + break; + } + error = ng_atm_cpcs_term(node, + (struct ngm_atm_cpcs_term *)msg->data); + break; + + case NGM_ATM_GET_STATS: + { + struct ngm_atm_stats *p; + + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + NG_MKRESPONSE(resp, msg, sizeof(*p), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + p = (struct ngm_atm_stats *)resp->data; + p->in_packets = priv->in_packets; + p->out_packets = priv->out_packets; + p->in_errors = priv->in_errors; + p->out_errors = priv->out_errors; + + break; + } + + default: + error = EINVAL; + break; + } + break; + + default: + error = EINVAL; + break; + } + + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/************************************************************/ +/* + * HOOK MANAGEMENT + */ + +/* + * A new hook is create that will be connected to the node. + * Check, whether the name is one of the predefined ones. + * If not, create a new entry into the vcc list. + */ +static int +ng_atm_newhook(node_p node, hook_p hook, const char *name) +{ + struct priv *priv = NG_NODE_PRIVATE(node); + struct ngvcc *vcc; + + if (strcmp(name, "input") == 0) { + priv->input = hook; + NG_HOOK_SET_RCVDATA(hook, ng_atm_rcvdrop); + return (0); + } + if (strcmp(name, "output") == 0) { + priv->output = hook; + NG_HOOK_SET_RCVDATA(hook, ng_atm_rcvdrop); + return (0); + } + if (strcmp(name, "orphans") == 0) { + priv->orphans = hook; + NG_HOOK_SET_RCVDATA(hook, ng_atm_rcvdrop); + return (0); + } + + /* + * Allocate a new entry + */ + vcc = malloc(sizeof(*vcc), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (vcc == NULL) + return (ENOMEM); + + vcc->hook = hook; + NG_HOOK_SET_PRIVATE(hook, vcc); + + LIST_INSERT_HEAD(&priv->vccs, vcc, link); + + if (strcmp(name, "manage") == 0) + priv->manage = hook; + + return (0); +} + +/* + * Connect. Set the peer to queuing. + */ +static int +ng_atm_connect(hook_p hook) +{ + if (NG_HOOK_PRIVATE(hook) != NULL) + NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); + + return (0); +} + +/* + * Disconnect a HOOK + */ +static int +ng_atm_disconnect(hook_p hook) +{ + struct priv *priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct ngvcc *vcc = NG_HOOK_PRIVATE(hook); + + if (vcc == NULL) { + if (hook == priv->output) { + priv->output = NULL; + return (0); + } + if (hook == priv->input) { + priv->input = NULL; + return (0); + } + if (hook == priv->orphans) { + priv->orphans = NULL; + return (0); + } + log(LOG_ERR, "ng_atm: bad hook '%s'", NG_HOOK_NAME(hook)); + return (0); + } + + /* don't terminate if we are detaching from the interface */ + if ((vcc->flags & VCC_OPEN) && priv->ifp != NULL) + (void)cpcs_term(priv, vcc->vpi, vcc->vci); + + NG_HOOK_SET_PRIVATE(hook, NULL); + + LIST_REMOVE(vcc, link); + free(vcc, M_NETGRAPH); + + if (hook == priv->manage) + priv->manage = NULL; + + return (0); +} + +/************************************************************/ +/* + * NODE MANAGEMENT + */ + +/* + * ATM interface attached - create a node and name it like the interface. + */ +static void +ng_atm_attach(struct ifnet *ifp) +{ + node_p node; + struct priv *priv; + + KASSERT(IFP2NG(ifp) == 0, ("%s: node alreay exists?", __func__)); + + if (ng_make_node_common(&ng_atm_typestruct, &node) != 0) { + log(LOG_ERR, "%s: can't create node for %s\n", + __func__, ifp->if_xname); + return; + } + + priv = malloc(sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (priv == NULL) { + log(LOG_ERR, "%s: can't allocate memory for %s\n", + __func__, ifp->if_xname); + NG_NODE_UNREF(node); + return; + } + NG_NODE_SET_PRIVATE(node, priv); + priv->ifp = ifp; + LIST_INIT(&priv->vccs); + IFP2NG_SET(ifp, node); + + if (ng_name_node(node, ifp->if_xname) != 0) { + log(LOG_WARNING, "%s: can't name node %s\n", + __func__, ifp->if_xname); + } +} + +/* + * ATM interface detached - destroy node. + */ +static void +ng_atm_detach(struct ifnet *ifp) +{ + const node_p node = IFP2NG(ifp); + struct priv *priv; + + if(node == NULL) + return; + + NG_NODE_REALLY_DIE(node); + + priv = NG_NODE_PRIVATE(node); + IFP2NG_SET(priv->ifp, NULL); + priv->ifp = NULL; + + ng_rmnode_self(node); +} + +/* + * Shutdown the node. This is called from the shutdown message processing. + */ +static int +ng_atm_shutdown(node_p node) +{ + struct priv *priv = NG_NODE_PRIVATE(node); + + if (node->nd_flags & NGF_REALLY_DIE) { + /* + * We are called from unloading the ATM driver. Really, + * really need to shutdown this node. The ifp was + * already handled in the detach routine. + */ + NG_NODE_SET_PRIVATE(node, NULL); + free(priv, M_NETGRAPH); + + NG_NODE_UNREF(node); + return (0); + } + +#ifdef NGATM_DEBUG + if (!allow_shutdown) + NG_NODE_REVIVE(node); /* we persist */ + else { + IFP2NG_SET(priv->ifp, NULL); + NG_NODE_SET_PRIVATE(node, NULL); + free(priv, M_NETGRAPH); + NG_NODE_UNREF(node); + } +#else + /* + * We are persistant - reinitialize + */ + NG_NODE_REVIVE(node); +#endif + return (0); +} + +/* + * Nodes are constructed only via interface attaches. + */ +static int +ng_atm_constructor(node_p nodep) +{ + return (EINVAL); +} + +/************************************************************/ +/* + * INITIALISATION + */ +/* + * Loading and unloading of node type + * + * The assignments to the globals for the hooks should be ok without + * a special hook. The use pattern is generally: check that the pointer + * is not NULL, call the function. In the attach case this is no problem. + * In the detach case we can detach only when no ATM node exists. That + * means that there is no ATM interface anymore. So we are sure that + * we are not in the code path in if_atmsubr.c. To prevent someone + * from adding an interface after we have started to unload the node, we + * take the iflist lock so an if_attach will be blocked until we are done. + * XXX: perhaps the function pointers should be 'volatile' for this to work + * properly. + */ +static int +ng_atm_mod_event(module_t mod, int event, void *data) +{ + struct ifnet *ifp; + int error = 0; + + switch (event) { + + case MOD_LOAD: + /* + * Register function hooks + */ + if (ng_atm_attach_p != NULL) { + error = EEXIST; + break; + } + IFNET_RLOCK(); + + ng_atm_attach_p = ng_atm_attach; + ng_atm_detach_p = ng_atm_detach; + ng_atm_output_p = ng_atm_output; + ng_atm_input_p = ng_atm_input; + ng_atm_input_orphan_p = ng_atm_input_orphans; + ng_atm_event_p = ng_atm_event; + + /* Create nodes for existing ATM interfaces */ + TAILQ_FOREACH(ifp, &ifnet, if_link) { + if (ifp->if_type == IFT_ATM) + ng_atm_attach(ifp); + } + IFNET_RUNLOCK(); + break; + + case MOD_UNLOAD: + IFNET_RLOCK(); + + ng_atm_attach_p = NULL; + ng_atm_detach_p = NULL; + ng_atm_output_p = NULL; + ng_atm_input_p = NULL; + ng_atm_input_orphan_p = NULL; + ng_atm_event_p = NULL; + + TAILQ_FOREACH(ifp, &ifnet, if_link) { + if (ifp->if_type == IFT_ATM) + ng_atm_detach(ifp); + } + IFNET_RUNLOCK(); + break; + + default: + error = EOPNOTSUPP; + break; + } + return (error); +} diff --git a/sys/netgraph7/atm/ng_atm.h b/sys/netgraph7/atm/ng_atm.h new file mode 100644 index 0000000000..4b40657ada --- /dev/null +++ b/sys/netgraph7/atm/ng_atm.h @@ -0,0 +1,248 @@ +/*- + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Author: Harti Brandt + */ + +/* + * Netgraph module to connect NATM interfaces to netgraph. + * + * $FreeBSD: src/sys/netgraph/atm/ng_atm.h,v 1.5 2005/01/07 01:45:40 imp Exp $ + */ +#ifndef _NETGRAPH_ATM_NG_ATM_H_ +#define _NETGRAPH_ATM_NG_ATM_H_ + +#define NG_ATM_NODE_TYPE "atm" +#define NGM_ATM_COOKIE 960802260 + +/* Netgraph control messages */ +enum { + NGM_ATM_GET_IFNAME = 1, /* get the interface name */ + NGM_ATM_GET_CONFIG, /* get configuration */ + NGM_ATM_GET_VCCS, /* get a list of all active vccs */ + NGM_ATM_CPCS_INIT, /* start the channel */ + NGM_ATM_CPCS_TERM, /* stop the channel */ + NGM_ATM_GET_VCC, /* get VCC config */ + NGM_ATM_GET_VCCID, /* get VCC by VCI/VPI */ + NGM_ATM_GET_STATS, /* get global statistics */ + + /* messages from the node */ + NGM_ATM_CARRIER_CHANGE = 1000, /* UNUSED: carrier changed */ + NGM_ATM_VCC_CHANGE, /* permanent VCC changed */ + NGM_ATM_ACR_CHANGE, /* ABR ACR has changed */ + NGM_ATM_IF_CHANGE, /* interface state change */ +}; + +/* + * Hardware interface configuration + */ +struct ngm_atm_config { + uint32_t pcr; /* peak cell rate */ + uint32_t vpi_bits; /* number of active VPI bits */ + uint32_t vci_bits; /* number of active VCI bits */ + uint32_t max_vpcs; /* maximum number of VPCs */ + uint32_t max_vccs; /* maximum number of VCCs */ +}; +#define NGM_ATM_CONFIG_INFO \ + { \ + { "pcr", &ng_parse_uint32_type }, \ + { "vpi_bits", &ng_parse_uint32_type }, \ + { "vci_bits", &ng_parse_uint32_type }, \ + { "max_vpcs", &ng_parse_uint32_type }, \ + { "max_vccs", &ng_parse_uint32_type }, \ + { NULL } \ + } + +/* + * Information about an open VCC + * See net/if_atm.h. Keep in sync. + */ +#define NGM_ATM_TPARAM_INFO \ + { \ + { "pcr", &ng_parse_uint32_type }, \ + { "scr", &ng_parse_uint32_type }, \ + { "mbs", &ng_parse_uint32_type }, \ + { "mcr", &ng_parse_uint32_type }, \ + { "icr", &ng_parse_uint32_type }, \ + { "tbe", &ng_parse_uint32_type }, \ + { "nrm", &ng_parse_uint8_type }, \ + { "trm", &ng_parse_uint8_type }, \ + { "adtf", &ng_parse_uint16_type }, \ + { "rif", &ng_parse_uint8_type }, \ + { "rdf", &ng_parse_uint8_type }, \ + { "cdf", &ng_parse_uint8_type }, \ + { NULL } \ + } + +#define NGM_ATM_VCC_INFO \ + { \ + { "flags", &ng_parse_hint16_type }, \ + { "vpi", &ng_parse_uint16_type }, \ + { "vci", &ng_parse_uint16_type }, \ + { "rmtu", &ng_parse_uint16_type }, \ + { "tmtu", &ng_parse_uint16_type }, \ + { "aal", &ng_parse_uint8_type }, \ + { "traffic", &ng_parse_uint8_type }, \ + { "tparam", &ng_atm_tparam_type }, \ + { NULL } \ + } + +#define NGM_ATM_VCCARRAY_INFO \ + { \ + &ng_atm_vcc_type, \ + ng_atm_vccarray_getlen, \ + NULL \ + } + +#define NGM_ATM_VCCTABLE_INFO \ + { \ + { "count", &ng_parse_uint32_type }, \ + { "vccs", &ng_atm_vccarray_type }, \ + { NULL } \ + } + +/* + * Structure to open a VCC. + */ +struct ngm_atm_cpcs_init { + char name[NG_HOOKSIZ]; + uint32_t flags; /* flags. (if_atm.h) */ + uint16_t vci; /* VCI to open */ + uint16_t vpi; /* VPI to open */ + uint16_t rmtu; /* Receive maximum CPCS size */ + uint16_t tmtu; /* Transmit maximum CPCS size */ + uint8_t aal; /* AAL type (if_atm.h) */ + uint8_t traffic; /* traffic type (if_atm.h) */ + uint32_t pcr; /* Peak cell rate */ + uint32_t scr; /* VBR: Sustainable cell rate */ + uint32_t mbs; /* VBR: Maximum burst rate */ + uint32_t mcr; /* UBR+: Minimum cell rate */ + uint32_t icr; /* ABR: Initial cell rate */ + uint32_t tbe; /* ABR: Transmit buffer exposure */ + uint8_t nrm; /* ABR: Nrm */ + uint8_t trm; /* ABR: Trm */ + uint16_t adtf; /* ABR: ADTF */ + uint8_t rif; /* ABR: RIF */ + uint8_t rdf; /* ABR: RDF */ + uint8_t cdf; /* ABR: CDF */ +}; + +#define NGM_ATM_CPCS_INIT_INFO \ + { \ + { "name", &ng_parse_hookbuf_type }, \ + { "flags", &ng_parse_hint32_type }, \ + { "vci", &ng_parse_uint16_type }, \ + { "vpi", &ng_parse_uint16_type }, \ + { "rmtu", &ng_parse_uint16_type }, \ + { "tmtu", &ng_parse_uint16_type }, \ + { "aal", &ng_parse_uint8_type }, \ + { "traffic", &ng_parse_uint8_type }, \ + { "pcr", &ng_parse_uint32_type }, \ + { "scr", &ng_parse_uint32_type }, \ + { "mbs", &ng_parse_uint32_type }, \ + { "mcr", &ng_parse_uint32_type }, \ + { "icr", &ng_parse_uint32_type }, \ + { "tbe", &ng_parse_uint32_type }, \ + { "nrm", &ng_parse_uint8_type }, \ + { "trm", &ng_parse_uint8_type }, \ + { "adtf", &ng_parse_uint16_type }, \ + { "rif", &ng_parse_uint8_type }, \ + { "rdf", &ng_parse_uint8_type }, \ + { "cdf", &ng_parse_uint8_type }, \ + { NULL } \ + } + +/* + * Structure to close a VCI without disconnecting the hook + */ +struct ngm_atm_cpcs_term { + char name[NG_HOOKSIZ]; +}; +#define NGM_ATM_CPCS_TERM_INFO \ + { \ + { "name", &ng_parse_hookbuf_type }, \ + { NULL } \ + } + +struct ngm_atm_stats { + uint64_t in_packets; + uint64_t in_errors; + uint64_t out_packets; + uint64_t out_errors; +}; +#define NGM_ATM_STATS_INFO \ + { \ + { "in_packets", &ng_parse_uint64_type }, \ + { "in_errors", &ng_parse_uint64_type }, \ + { "out_packets", &ng_parse_uint64_type }, \ + { "out_errors", &ng_parse_uint64_type }, \ + { NULL } \ + } + +struct ngm_atm_if_change { + uint32_t node; + uint8_t carrier; + uint8_t running; +}; +#define NGM_ATM_IF_CHANGE_INFO \ + { \ + { "node", &ng_parse_hint32_type }, \ + { "carrier", &ng_parse_uint8_type }, \ + { "running", &ng_parse_uint8_type }, \ + { NULL } \ + } + +struct ngm_atm_vcc_change { + uint32_t node; + uint16_t vci; + uint8_t vpi; + uint8_t state; +}; +#define NGM_ATM_VCC_CHANGE_INFO \ + { \ + { "node", &ng_parse_hint32_type }, \ + { "vci", &ng_parse_uint16_type }, \ + { "vpi", &ng_parse_uint8_type }, \ + { "state", &ng_parse_uint8_type }, \ + { NULL } \ + } + +struct ngm_atm_acr_change { + uint32_t node; + uint16_t vci; + uint8_t vpi; + uint32_t acr; +}; +#define NGM_ATM_ACR_CHANGE_INFO \ + { \ + { "node", &ng_parse_hint32_type }, \ + { "vci", &ng_parse_uint16_type }, \ + { "vpi", &ng_parse_uint8_type }, \ + { "acr", &ng_parse_uint32_type }, \ + { NULL } \ + } + +#endif /* _NETGRAPH_ATM_NG_ATM_H */ diff --git a/sys/netgraph7/atm/ng_ccatm.h b/sys/netgraph7/atm/ng_ccatm.h new file mode 100644 index 0000000000..b0ab97a59f --- /dev/null +++ b/sys/netgraph7/atm/ng_ccatm.h @@ -0,0 +1,172 @@ +/*- + * Copyright (c) 2001-2002 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * Copyright (c) 2003-2004 + * Hartmut Brandt + * All rights reserved. + * + * Author: Harti Brandt + * + * Redistribution of this software and documentation and use in source and + * binary forms, with or without modification, are permitted provided that + * the following conditions are met: + * + * 1. Redistributions of source code or documentation must retain the above + * copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/atm/ng_ccatm.h,v 1.2 2005/01/07 01:45:40 imp Exp $ + */ + +/* + * Interface to ng_ccatm + */ +#ifndef _NETGRAPH_ATM_NG_CCATM_H_ +#define _NETGRAPH_ATM_NG_CCATM_H_ + +#define NG_CCATM_NODE_TYPE "ccatm" +#define NGM_CCATM_COOKIE 984046139 + +enum { + NGM_CCATM_DUMP, /* dump internal status */ + NGM_CCATM_STOP, /* stop all processing, close all */ + NGM_CCATM_START, /* start processing */ + NGM_CCATM_CLEAR, /* clear prefix/address table */ + NGM_CCATM_GET_ADDRESSES, /* get list of all addresses */ + NGM_CCATM_ADDRESS_REGISTERED, /* registration ok */ + NGM_CCATM_ADDRESS_UNREGISTERED, /* unregistration ok */ + NGM_CCATM_SET_PORT_PARAM, /* set port parameters */ + NGM_CCATM_GET_PORT_PARAM, /* get port parameters */ + NGM_CCATM_GET_PORTLIST, /* get list of port numbers */ + NGM_CCATM_GETSTATE, /* get port status */ + NGM_CCATM_SETLOG, /* set/get loglevel */ + NGM_CCATM_RESET, /* reset everything */ + NGM_CCATM_GET_EXSTAT, /* get extended status */ +}; + +/* + * This must be synchronized with unistruct.h::struct uni_addr + */ +#define NGM_CCATM_ADDR_ARRAY_INFO \ + { \ + &ng_parse_hint8_type, \ + UNI_ADDR_MAXLEN \ + } + +#define NGM_CCATM_UNI_ADDR_INFO \ + { \ + { "type", &ng_parse_uint32_type }, \ + { "plan", &ng_parse_uint32_type }, \ + { "len", &ng_parse_uint32_type }, \ + { "addr", &ng_ccatm_addr_array_type }, \ + { NULL } \ + } + +/* + * Address request + */ +struct ngm_ccatm_addr_req { + uint32_t port; + struct uni_addr addr; +}; +#define NGM_CCATM_ADDR_REQ_INFO \ + { \ + { "port", &ng_parse_uint32_type }, \ + { "addr", &ng_ccatm_uni_addr_type }, \ + { NULL }, \ + } + +/* + * Get current address list + */ +struct ngm_ccatm_get_addresses { + uint32_t count; + struct ngm_ccatm_addr_req addr[]; +}; +#define NGM_CCATM_ADDR_REQ_ARRAY_INFO \ + { \ + &ng_ccatm_addr_req_type, \ + ng_ccatm_addr_req_array_getlen \ + } +#define NGM_CCATM_GET_ADDRESSES_INFO \ + { \ + { "count", &ng_parse_uint32_type }, \ + { "addr", &ng_ccatm_addr_req_array_type }, \ + { NULL } \ + } + +/* + * Port as parameter + */ +struct ngm_ccatm_port { + uint32_t port; +}; +#define NGM_CCATM_PORT_INFO \ + { \ + { "port", &ng_parse_uint32_type }, \ + { NULL } \ + } + +/* + * Port parameters. + * This must be synchronized with atmapi.h::struct atm_port_info. + */ +#define NGM_CCATM_ESI_INFO \ + { \ + &ng_parse_hint8_type, \ + 6 \ + } +#define NGM_CCATM_ATM_PORT_INFO \ + { \ + { "port", &ng_parse_uint32_type }, \ + { "pcr", &ng_parse_uint32_type }, \ + { "max_vpi_bits", &ng_parse_uint32_type }, \ + { "max_vci_bits", &ng_parse_uint32_type }, \ + { "max_svpc_vpi", &ng_parse_uint32_type }, \ + { "max_svcc_vpi", &ng_parse_uint32_type }, \ + { "min_svcc_vci", &ng_parse_uint32_type }, \ + { "esi", &ng_ccatm_esi_type }, \ + { "num_addr", &ng_parse_uint32_type }, \ + { NULL } \ + } + +/* + * List of port numbers + */ +struct ngm_ccatm_portlist { + uint32_t nports; + uint32_t ports[]; +}; +#define NGM_CCATM_PORT_ARRAY_INFO \ + { \ + &ng_parse_uint32_type, \ + ng_ccatm_port_array_getlen \ + } +#define NGM_CCATM_PORTLIST_INFO \ + { \ + { "nports", &ng_parse_uint32_type }, \ + { "ports", &ng_ccatm_port_array_type }, \ + { NULL } \ + } + +struct ccatm_op { + uint32_t op; /* request code */ + u_char data[]; +}; + +#endif diff --git a/sys/netgraph7/atm/ng_sscfu.h b/sys/netgraph7/atm/ng_sscfu.h new file mode 100644 index 0000000000..5a8950a70d --- /dev/null +++ b/sys/netgraph7/atm/ng_sscfu.h @@ -0,0 +1,68 @@ +/*- + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/atm/ng_sscfu.h,v 1.2 2005/01/07 01:45:40 imp Exp $ + * + * Netgraph module for ITU-T Q.2120 UNI SSCF. + */ +#ifndef _NETGRAPH_ATM_NG_SSCFU_H_ +#define _NETGRAPH_ATM_NG_SSCFU_H_ + +#define NG_SSCFU_NODE_TYPE "sscfu" +#define NGM_SSCFU_COOKIE 980517963 + +/* Netgraph control messages */ +enum { + NGM_SSCFU_GETDEFPARAM = 1, /* get default SSCOP parameters */ + NGM_SSCFU_ENABLE, /* enable processing */ + NGM_SSCFU_DISABLE, /* disable processing */ + NGM_SSCFU_GETDEBUG, /* get debug flags */ + NGM_SSCFU_SETDEBUG, /* set debug flags */ + NGM_SSCFU_GETSTATE, /* get current state */ +}; + +/* getdefparam return */ +struct ng_sscfu_getdefparam { + struct sscop_param param; + uint32_t mask; +}; +#define NG_SSCFU_GETDEFPARAM_INFO \ + { \ + { "param", &ng_sscop_param_type }, \ + { "mask", &ng_parse_uint32_type }, \ + { NULL } \ + } + +/* + * Upper interface + */ +struct sscfu_arg { + uint32_t sig; + u_char data[]; +}; +#endif diff --git a/sys/netgraph7/atm/ng_sscop.h b/sys/netgraph7/atm/ng_sscop.h new file mode 100644 index 0000000000..b4b2b3e6ad --- /dev/null +++ b/sys/netgraph7/atm/ng_sscop.h @@ -0,0 +1,110 @@ +/*- + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/atm/ng_sscop.h,v 1.3 2005/01/07 01:45:40 imp Exp $ + * + * Netgraph module for Q.2110 SSCOP + */ +#ifndef _NETGRAPH_ATM_NG_SSCOP_H_ +#define _NETGRAPH_ATM_NG_SSCOP_H_ + +#define NG_SSCOP_NODE_TYPE "sscop" +#define NGM_SSCOP_COOKIE 980175044 + +/* Netgraph control messages */ +enum { + NGM_SSCOP_GETPARAM = 1, /* get parameters */ + NGM_SSCOP_SETPARAM, /* set parameters */ + NGM_SSCOP_ENABLE, /* enable processing */ + NGM_SSCOP_DISABLE, /* disable and reset */ + NGM_SSCOP_GETDEBUG, /* get debugging flags */ + NGM_SSCOP_SETDEBUG, /* set debugging flags */ + NGM_SSCOP_GETSTATE, /* get current SSCOP state */ +}; + +/* This must be in-sync with the definition in sscopdef.h */ +#define NG_SSCOP_PARAM_INFO \ + { \ + { "timer_cc", &ng_parse_uint32_type }, \ + { "timer_poll", &ng_parse_uint32_type }, \ + { "timer_keep_alive", &ng_parse_uint32_type }, \ + { "timer_no_response",&ng_parse_uint32_type }, \ + { "timer_idle", &ng_parse_uint32_type }, \ + { "maxk", &ng_parse_uint32_type }, \ + { "maxj", &ng_parse_uint32_type }, \ + { "maxcc", &ng_parse_uint32_type }, \ + { "maxpd", &ng_parse_uint32_type }, \ + { "maxstat", &ng_parse_uint32_type }, \ + { "mr", &ng_parse_uint32_type }, \ + { "flags", &ng_parse_uint32_type }, \ + { NULL } \ + } + + +struct ng_sscop_setparam { + uint32_t mask; + struct sscop_param param; +}; +#define NG_SSCOP_SETPARAM_INFO \ + { \ + { "mask", &ng_parse_uint32_type }, \ + { "param", &ng_sscop_param_type }, \ + { NULL } \ + } + +struct ng_sscop_setparam_resp { + uint32_t mask; + int32_t error; +}; +#define NG_SSCOP_SETPARAM_RESP_INFO \ + { \ + { "mask", &ng_parse_uint32_type }, \ + { "error", &ng_parse_int32_type }, \ + { NULL } \ + } + +/* + * Upper interface + */ +struct sscop_arg { + uint32_t sig; + uint32_t arg; /* opt. sequence number or clear-buff */ + u_char data[]; +}; + +struct sscop_marg { + uint32_t sig; + u_char data[]; +}; +struct sscop_merr { + uint32_t sig; + uint32_t err; /* error code */ + uint32_t cnt; /* error count */ +}; + +#endif diff --git a/sys/netgraph7/atm/ng_uni.h b/sys/netgraph7/atm/ng_uni.h new file mode 100644 index 0000000000..b2dc2c5c33 --- /dev/null +++ b/sys/netgraph7/atm/ng_uni.h @@ -0,0 +1,119 @@ +/*- + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Hartmut Brandt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/atm/ng_uni.h,v 1.2 2005/01/07 01:45:40 imp Exp $ + * + * Netgraph module for UNI 4.0 + */ +#ifndef _NETGRAPH_ATM_NG_UNI_H_ +#define _NETGRAPH_ATM_NG_UNI_H_ + +#define NG_UNI_NODE_TYPE "uni" +#define NGM_UNI_COOKIE 981112392 + +enum { + NGM_UNI_GETDEBUG, /* get debug flags */ + NGM_UNI_SETDEBUG, /* set debug flags */ + NGM_UNI_GET_CONFIG, /* get configuration */ + NGM_UNI_SET_CONFIG, /* set configuration */ + NGM_UNI_ENABLE, /* enable processing */ + NGM_UNI_DISABLE, /* free resources and disable */ + NGM_UNI_GETSTATE, /* retrieve coord state */ +}; + +struct ngm_uni_debug { + uint32_t level[UNI_MAXFACILITY]; +}; +#define NGM_UNI_DEBUGLEVEL_INFO { \ + &ng_parse_uint32_type, \ + UNI_MAXFACILITY \ +} +#define NGM_UNI_DEBUG_INFO \ + { \ + { "level", &ng_uni_debuglevel_type }, \ + { NULL } \ + } + +#define NGM_UNI_CONFIG_INFO \ + { \ + { "proto", &ng_parse_uint32_type }, \ + { "popt", &ng_parse_uint32_type }, \ + { "option", &ng_parse_uint32_type }, \ + { "timer301", &ng_parse_uint32_type }, \ + { "timer303", &ng_parse_uint32_type }, \ + { "init303", &ng_parse_uint32_type }, \ + { "timer308", &ng_parse_uint32_type }, \ + { "init308", &ng_parse_uint32_type }, \ + { "timer309", &ng_parse_uint32_type }, \ + { "timer310", &ng_parse_uint32_type }, \ + { "timer313", &ng_parse_uint32_type }, \ + { "timer316", &ng_parse_uint32_type }, \ + { "init316", &ng_parse_uint32_type }, \ + { "timer317", &ng_parse_uint32_type }, \ + { "timer322", &ng_parse_uint32_type }, \ + { "init322", &ng_parse_uint32_type }, \ + { "timer397", &ng_parse_uint32_type }, \ + { "timer398", &ng_parse_uint32_type }, \ + { "timer399", &ng_parse_uint32_type }, \ + { NULL } \ + } + +struct ngm_uni_config_mask { + uint32_t mask; + uint32_t popt_mask; + uint32_t option_mask; +}; +#define NGM_UNI_CONFIG_MASK_INFO \ + { \ + { "mask", &ng_parse_hint32_type }, \ + { "popt_mask", &ng_parse_hint32_type }, \ + { "option_mask", &ng_parse_hint32_type }, \ + { NULL } \ + } + +struct ngm_uni_set_config { + struct uni_config config; + struct ngm_uni_config_mask mask; +}; +#define NGM_UNI_SET_CONFIG_INFO \ + { \ + { "config", &ng_uni_config_type }, \ + { "mask", &ng_uni_config_mask_type }, \ + { NULL } \ + } + +/* + * API message + */ +struct uni_arg { + uint32_t sig; + uint32_t cookie; + u_char data[]; +}; + +#endif diff --git a/sys/netgraph7/atm/ngatmbase.c b/sys/netgraph7/atm/ngatmbase.c new file mode 100644 index 0000000000..47fa5bf71f --- /dev/null +++ b/sys/netgraph7/atm/ngatmbase.c @@ -0,0 +1,501 @@ +/*- + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Hartmut Brandt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * In-kernel UNI stack message functions. + */ + +#include +__FBSDID("$FreeBSD: src/sys/netgraph/atm/ngatmbase.c,v 1.3 2005/01/07 01:45:40 imp Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NGATMBASE_VERSION 1 + +static int ngatm_handler(module_t, int, void *); + +static moduledata_t ngatm_data = { + "ngatmbase", + ngatm_handler, + 0 +}; + +MODULE_VERSION(ngatmbase, NGATMBASE_VERSION); +DECLARE_MODULE(ngatmbase, ngatm_data, SI_SUB_EXEC, SI_ORDER_ANY); + +/*********************************************************************/ +/* + * UNI Stack message handling functions + */ +MALLOC_DEFINE(M_UNIMSG, "unimsg", "uni message buffers"); +MALLOC_DEFINE(M_UNIMSGHDR, "unimsghdr", "uni message headers"); + +#define EXTRA 128 + +/* mutex to protect the free list (and the used list if debugging) */ +static struct mtx ngatm_unilist_mtx; + +/* + * Initialize UNI message subsystem + */ +static void +uni_msg_init(void) +{ + mtx_init(&ngatm_unilist_mtx, "netgraph UNI msg header lists", NULL, + MTX_DEF); +} + +/* + * Ensure, that the message can be extended by at least s bytes. + * Re-allocate the message (not the header). If that failes, + * free the entire message and return ENOMEM. Free space at the start of + * the message is retained. + */ +int +uni_msg_extend(struct uni_msg *m, size_t s) +{ + u_char *b; + size_t len, lead; + + lead = uni_msg_leading(m); + len = uni_msg_len(m); + s += lead + len + EXTRA; + if ((b = malloc(s, M_UNIMSG, M_NOWAIT)) == NULL) { + uni_msg_destroy(m); + return (ENOMEM); + } + + bcopy(m->b_rptr, b + lead, len); + free(m->b_buf, M_UNIMSG); + + m->b_buf = b; + m->b_rptr = m->b_buf + lead; + m->b_wptr = m->b_rptr + len; + m->b_lim = m->b_buf + s; + + return (0); +} + +/* + * Append a buffer to the message, making space if needed. + * If reallocation files, ENOMEM is returned and the message freed. + */ +int +uni_msg_append(struct uni_msg *m, void *buf, size_t size) +{ + int error; + + if ((error = uni_msg_ensure(m, size))) + return (error); + bcopy(buf, m->b_wptr, size); + m->b_wptr += size; + + return (0); +} + +/* + * Pack/unpack data from/into mbufs. Assume, that the (optional) header + * fits into the first mbuf, ie. hdrlen < MHLEN. Note, that the message + * can be NULL, but hdrlen should not be 0 in this case. + */ +struct mbuf * +uni_msg_pack_mbuf(struct uni_msg *msg, void *hdr, size_t hdrlen) +{ + struct mbuf *m, *m0, *last; + size_t n; + + MGETHDR(m0, M_NOWAIT, MT_DATA); + if (m0 == NULL) + return (NULL); + + KASSERT(hdrlen <= MHLEN, ("uni_msg_pack_mbuf: hdrlen > MHLEN")); + + if (hdrlen != 0) { + bcopy(hdr, m0->m_data, hdrlen); + m0->m_len = hdrlen; + m0->m_pkthdr.len = hdrlen; + + } else { + if ((n = uni_msg_len(msg)) > MHLEN) { + MCLGET(m0, M_NOWAIT); + if (!(m0->m_flags & M_EXT)) + goto drop; + if (n > MCLBYTES) + n = MCLBYTES; + } + + bcopy(msg->b_rptr, m0->m_data, n); + msg->b_rptr += n; + m0->m_len = n; + m0->m_pkthdr.len = n; + } + + last = m0; + while (msg != NULL && (n = uni_msg_len(msg)) != 0) { + MGET(m, M_NOWAIT, MT_DATA); + if (m == NULL) + goto drop; + last->m_next = m; + last = m; + + if (n > MLEN) { + MCLGET(m, M_NOWAIT); + if (!(m->m_flags & M_EXT)) + goto drop; + if (n > MCLBYTES) + n = MCLBYTES; + } + + bcopy(msg->b_rptr, m->m_data, n); + msg->b_rptr += n; + m->m_len = n; + m0->m_pkthdr.len += n; + } + + return (m0); + + drop: + m_freem(m0); + return (NULL); +} + +#ifdef NGATM_DEBUG + +/* + * Prepend a debugging header to each message + */ +struct ngatm_msg { + LIST_ENTRY(ngatm_msg) link; + const char *file; + int line; + struct uni_msg msg; +}; + +/* + * These are the lists of free and used message headers. + */ +static LIST_HEAD(, ngatm_msg) ngatm_freeuni = + LIST_HEAD_INITIALIZER(ngatm_freeuni); +static LIST_HEAD(, ngatm_msg) ngatm_useduni = + LIST_HEAD_INITIALIZER(ngatm_useduni); + +/* + * Clean-up UNI message subsystem + */ +static void +uni_msg_fini(void) +{ + struct ngatm_msg *h; + + /* free all free message headers */ + while ((h = LIST_FIRST(&ngatm_freeuni)) != NULL) { + LIST_REMOVE(h, link); + free(h, M_UNIMSGHDR); + } + + /* forget about still used messages */ + LIST_FOREACH(h, &ngatm_useduni, link) + printf("unimsg header in use: %p (%s, %d)\n", + &h->msg, h->file, h->line); + + mtx_destroy(&ngatm_unilist_mtx); +} + +/* + * Allocate a message, that can hold at least s bytes. + */ +struct uni_msg * +_uni_msg_alloc(size_t s, const char *file, int line) +{ + struct ngatm_msg *m; + + mtx_lock(&ngatm_unilist_mtx); + if ((m = LIST_FIRST(&ngatm_freeuni)) != NULL) + LIST_REMOVE(m, link); + mtx_unlock(&ngatm_unilist_mtx); + + if (m == NULL && + (m = malloc(sizeof(*m), M_UNIMSGHDR, M_NOWAIT)) == NULL) + return (NULL); + + s += EXTRA; + if((m->msg.b_buf = malloc(s, M_UNIMSG, M_NOWAIT | M_ZERO)) == NULL) { + mtx_lock(&ngatm_unilist_mtx); + LIST_INSERT_HEAD(&ngatm_freeuni, m, link); + mtx_unlock(&ngatm_unilist_mtx); + return (NULL); + } + m->msg.b_rptr = m->msg.b_wptr = m->msg.b_buf; + m->msg.b_lim = m->msg.b_buf + s; + m->file = file; + m->line = line; + + mtx_lock(&ngatm_unilist_mtx); + LIST_INSERT_HEAD(&ngatm_useduni, m, link); + mtx_unlock(&ngatm_unilist_mtx); + return (&m->msg); +} + +/* + * Destroy a UNI message. + * The header is inserted into the free header list. + */ +void +_uni_msg_destroy(struct uni_msg *m, const char *file, int line) +{ + struct ngatm_msg *h, *d; + + d = (struct ngatm_msg *)((char *)m - offsetof(struct ngatm_msg, msg)); + + mtx_lock(&ngatm_unilist_mtx); + LIST_FOREACH(h, &ngatm_useduni, link) + if (h == d) + break; + + if (h == NULL) { + /* + * Not on used list. Ups. + */ + LIST_FOREACH(h, &ngatm_freeuni, link) + if (h == d) + break; + + if (h == NULL) + printf("uni_msg %p was never allocated; found " + "in %s:%u\n", m, file, line); + else + printf("uni_msg %p was already destroyed in %s,%d; " + "found in %s:%u\n", m, h->file, h->line, + file, line); + } else { + free(m->b_buf, M_UNIMSG); + + LIST_REMOVE(d, link); + LIST_INSERT_HEAD(&ngatm_freeuni, d, link); + + d->file = file; + d->line = line; + } + + mtx_unlock(&ngatm_unilist_mtx); +} + +#else /* !NGATM_DEBUG */ + +/* + * This assumes, that sizeof(struct uni_msg) >= sizeof(struct ngatm_msg) + * and the alignment requirements of are the same. + */ +struct ngatm_msg { + LIST_ENTRY(ngatm_msg) link; +}; + +/* Lists of free message headers. */ +static LIST_HEAD(, ngatm_msg) ngatm_freeuni = + LIST_HEAD_INITIALIZER(ngatm_freeuni); + +/* + * Clean-up UNI message subsystem + */ +static void +uni_msg_fini(void) +{ + struct ngatm_msg *h; + + /* free all free message headers */ + while ((h = LIST_FIRST(&ngatm_freeuni)) != NULL) { + LIST_REMOVE(h, link); + free(h, M_UNIMSGHDR); + } + + mtx_destroy(&ngatm_unilist_mtx); +} + +/* + * Allocate a message, that can hold at least s bytes. + */ +struct uni_msg * +uni_msg_alloc(size_t s) +{ + struct ngatm_msg *a; + struct uni_msg *m; + + mtx_lock(&ngatm_unilist_mtx); + if ((a = LIST_FIRST(&ngatm_freeuni)) != NULL) + LIST_REMOVE(a, link); + mtx_unlock(&ngatm_unilist_mtx); + + if (a == NULL) { + if ((m = malloc(sizeof(*m), M_UNIMSGHDR, M_NOWAIT)) == NULL) + return (NULL); + a = (struct ngatm_msg *)m; + } else + m = (struct uni_msg *)a; + + s += EXTRA; + if((m->b_buf = malloc(s, M_UNIMSG, M_NOWAIT | M_ZERO)) == NULL) { + mtx_lock(&ngatm_unilist_mtx); + LIST_INSERT_HEAD(&ngatm_freeuni, a, link); + mtx_unlock(&ngatm_unilist_mtx); + return (NULL); + } + m->b_rptr = m->b_wptr = m->b_buf; + m->b_lim = m->b_buf + s; + + return (m); +} + +/* + * Destroy a UNI message. + * The header is inserted into the free header list. + */ +void +uni_msg_destroy(struct uni_msg *m) +{ + struct ngatm_msg *a; + + a = (struct ngatm_msg *)m; + + free(m->b_buf, M_UNIMSG); + + mtx_lock(&ngatm_unilist_mtx); + LIST_INSERT_HEAD(&ngatm_freeuni, a, link); + mtx_unlock(&ngatm_unilist_mtx); +} + +#endif + +/* + * Build a message from a number of buffers. Arguments are pairs + * of (void *, size_t) ending with a NULL pointer. + */ +#ifdef NGATM_DEBUG +struct uni_msg * +_uni_msg_build(const char *file, int line, void *ptr, ...) +#else +struct uni_msg * +uni_msg_build(void *ptr, ...) +#endif +{ + va_list ap; + struct uni_msg *m; + size_t len, n; + void *p1; + + len = 0; + va_start(ap, ptr); + p1 = ptr; + while (p1 != NULL) { + n = va_arg(ap, size_t); + len += n; + p1 = va_arg(ap, void *); + } + va_end(ap); + +#ifdef NGATM_DEBUG + if ((m = _uni_msg_alloc(len, file, line)) == NULL) +#else + if ((m = uni_msg_alloc(len)) == NULL) +#endif + return (NULL); + + va_start(ap, ptr); + p1 = ptr; + while (p1 != NULL) { + n = va_arg(ap, size_t); + bcopy(p1, m->b_wptr, n); + m->b_wptr += n; + p1 = va_arg(ap, void *); + } + va_end(ap); + + return (m); +} + +/* + * Unpack an mbuf chain into a uni_msg buffer. + */ +#ifdef NGATM_DEBUG +int +_uni_msg_unpack_mbuf(struct mbuf *m, struct uni_msg **pmsg, const char *file, + int line) +#else +int +uni_msg_unpack_mbuf(struct mbuf *m, struct uni_msg **pmsg) +#endif +{ + if (!(m->m_flags & M_PKTHDR)) { + printf("%s: bogus packet %p\n", __func__, m); + return (EINVAL); + } +#ifdef NGATM_DEBUG + if ((*pmsg = _uni_msg_alloc(m->m_pkthdr.len, file, line)) == NULL) +#else + if ((*pmsg = uni_msg_alloc(m->m_pkthdr.len)) == NULL) +#endif + return (ENOMEM); + + m_copydata(m, 0, m->m_pkthdr.len, (*pmsg)->b_wptr); + (*pmsg)->b_wptr += m->m_pkthdr.len; + + return (0); +} + +/*********************************************************************/ + +static int +ngatm_handler(module_t mod, int what, void *arg) +{ + int error = 0; + + switch (what) { + + case MOD_LOAD: + uni_msg_init(); + break; + + case MOD_UNLOAD: + uni_msg_fini(); + break; + + default: + error = EOPNOTSUPP; + break; + } + + return (error); +} diff --git a/sys/netgraph7/atm/ngatmbase.h b/sys/netgraph7/atm/ngatmbase.h new file mode 100644 index 0000000000..126cb0b0ec --- /dev/null +++ b/sys/netgraph7/atm/ngatmbase.h @@ -0,0 +1,64 @@ +/*- + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt + * + * Redistribution of this software and documentation and use in source and + * binary forms, with or without modification, are permitted provided that + * the following conditions are met: + * + * 1. Redistributions of source code or documentation must retain the above + * copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS + * AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * FRAUNHOFER FOKUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/atm/ngatmbase.h,v 1.3 2005/01/07 01:45:40 imp Exp $ + * + * In-kernel UNI stack message functions. + */ +#ifndef _NETGRAPH_ATM_NGATMBASE_H_ +#define _NETGRAPH_ATM_NGATMBASE_H_ + +/* forward declarations */ +struct mbuf; +struct uni_msg; + +struct mbuf *uni_msg_pack_mbuf(struct uni_msg *, void *, size_t); + +#ifdef NGATM_DEBUG + +struct uni_msg *_uni_msg_alloc(size_t, const char *, int); +struct uni_msg *_uni_msg_build(const char *, int, void *, ...); +void _uni_msg_destroy(struct uni_msg *, const char *, int); +int _uni_msg_unpack_mbuf(struct mbuf *, struct uni_msg **, const char *, int); + +#define uni_msg_alloc(S) _uni_msg_alloc((S), __FILE__, __LINE__) +#define uni_msg_build(P...) _uni_msg_build(__FILE__, __LINE__, P) +#define uni_msg_destroy(M) _uni_msg_destroy((M), __FILE__, __LINE__) +#define uni_msg_unpack_mbuf(M, PP) \ + _uni_msg_unpack_mbuf((M), (PP), __FILE__, __LINE__) + +#else /* !NGATM_DEBUG */ + +struct uni_msg *uni_msg_alloc(size_t); +struct uni_msg *uni_msg_build(void *, ...); +void uni_msg_destroy(struct uni_msg *); +int uni_msg_unpack_mbuf(struct mbuf *, struct uni_msg **); + +#endif +#endif diff --git a/sys/netgraph7/atm/sscfu/ng_sscfu.c b/sys/netgraph7/atm/sscfu/ng_sscfu.c new file mode 100644 index 0000000000..1cf1f5d51b --- /dev/null +++ b/sys/netgraph7/atm/sscfu/ng_sscfu.c @@ -0,0 +1,609 @@ +/*- + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Author: Hartmut Brandt + * + * Netgraph module for ITU-T Q.2120 UNI SSCF. + */ + +#include +__FBSDID("$FreeBSD: src/sys/netgraph/atm/sscfu/ng_sscfu.c,v 1.4 2005/01/07 01:45:41 imp Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MALLOC_DEFINE(M_NG_SSCFU, "netgraph_sscfu", "netgraph uni sscf node"); + +MODULE_DEPEND(ng_sscfu, ngatmbase, 1, 1, 1); + +/* + * Private data + */ +struct priv { + hook_p upper; /* SAAL interface */ + hook_p lower; /* SSCOP interface */ + struct sscfu *sscf; /* the instance */ + int enabled; +}; + +/* + * PARSING + */ +/* + * Parse PARAM type + */ +static const struct ng_parse_struct_field ng_sscop_param_type_info[] = + NG_SSCOP_PARAM_INFO; + +static const struct ng_parse_type ng_sscop_param_type = { + &ng_parse_struct_type, + ng_sscop_param_type_info +}; + +static const struct ng_parse_struct_field ng_sscfu_getdefparam_type_info[] = + NG_SSCFU_GETDEFPARAM_INFO; + +static const struct ng_parse_type ng_sscfu_getdefparam_type = { + &ng_parse_struct_type, + ng_sscfu_getdefparam_type_info +}; + + +static const struct ng_cmdlist ng_sscfu_cmdlist[] = { + { + NGM_SSCFU_COOKIE, + NGM_SSCFU_GETDEFPARAM, + "getdefparam", + NULL, + &ng_sscfu_getdefparam_type + }, + { + NGM_SSCFU_COOKIE, + NGM_SSCFU_ENABLE, + "enable", + NULL, + NULL + }, + { + NGM_SSCFU_COOKIE, + NGM_SSCFU_DISABLE, + "disable", + NULL, + NULL + }, + { + NGM_SSCFU_COOKIE, + NGM_SSCFU_GETDEBUG, + "getdebug", + NULL, + &ng_parse_hint32_type + }, + { + NGM_SSCFU_COOKIE, + NGM_SSCFU_SETDEBUG, + "setdebug", + &ng_parse_hint32_type, + NULL + }, + { + NGM_SSCFU_COOKIE, + NGM_SSCFU_GETSTATE, + "getstate", + NULL, + &ng_parse_uint32_type + }, + { 0 } +}; + +static ng_constructor_t ng_sscfu_constructor; +static ng_shutdown_t ng_sscfu_shutdown; +static ng_rcvmsg_t ng_sscfu_rcvmsg; +static ng_newhook_t ng_sscfu_newhook; +static ng_disconnect_t ng_sscfu_disconnect; +static ng_rcvdata_t ng_sscfu_rcvupper; +static ng_rcvdata_t ng_sscfu_rcvlower; + +static int ng_sscfu_mod_event(module_t, int, void *); + +static struct ng_type ng_sscfu_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_SSCFU_NODE_TYPE, + .mod_event = ng_sscfu_mod_event, + .constructor = ng_sscfu_constructor, + .rcvmsg = ng_sscfu_rcvmsg, + .shutdown = ng_sscfu_shutdown, + .newhook = ng_sscfu_newhook, + .rcvdata = ng_sscfu_rcvupper, + .disconnect = ng_sscfu_disconnect, + .cmdlist = ng_sscfu_cmdlist, +}; +NETGRAPH_INIT(sscfu, &ng_sscfu_typestruct); + +static void sscfu_send_upper(struct sscfu *, void *, enum saal_sig, + struct mbuf *); +static void sscfu_send_lower(struct sscfu *, void *, enum sscop_aasig, + struct mbuf *, u_int); +static void sscfu_window(struct sscfu *, void *, u_int); +static void sscfu_verbose(struct sscfu *, void *, const char *, ...) + __printflike(3, 4); + +static const struct sscfu_funcs sscfu_funcs = { + sscfu_send_upper, + sscfu_send_lower, + sscfu_window, + sscfu_verbose +}; + +/************************************************************/ +/* + * CONTROL MESSAGES + */ +static int +text_status(node_p node, struct priv *priv, char *arg, u_int len) +{ + struct sbuf sbuf; + + sbuf_new(&sbuf, arg, len, 0); + + if (priv->upper) + sbuf_printf(&sbuf, "upper hook: %s connected to %s:%s\n", + NG_HOOK_NAME(priv->upper), + NG_NODE_NAME(NG_HOOK_NODE(NG_HOOK_PEER(priv->upper))), + NG_HOOK_NAME(NG_HOOK_PEER(priv->upper))); + else + sbuf_printf(&sbuf, "upper hook: \n"); + + if (priv->lower) + sbuf_printf(&sbuf, "lower hook: %s connected to %s:%s\n", + NG_HOOK_NAME(priv->lower), + NG_NODE_NAME(NG_HOOK_NODE(NG_HOOK_PEER(priv->lower))), + NG_HOOK_NAME(NG_HOOK_PEER(priv->lower))); + else + sbuf_printf(&sbuf, "lower hook: \n"); + + sbuf_printf(&sbuf, "sscf state: %s\n", + priv->enabled == 0 ? "" : + sscfu_statename(sscfu_getstate(priv->sscf))); + + sbuf_finish(&sbuf); + return (sbuf_len(&sbuf)); +} + +static int +ng_sscfu_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct priv *priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + struct ng_mesg *msg; + int error = 0; + + NGI_GET_MSG(item, msg); + + switch (msg->header.typecookie) { + + case NGM_GENERIC_COOKIE: + switch (msg->header.cmd) { + + case NGM_TEXT_STATUS: + NG_MKRESPONSE(resp, msg, NG_TEXTRESPONSE, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + resp->header.arglen = text_status(node, priv, + (char *)resp->data, resp->header.arglen) + 1; + break; + + default: + error = EINVAL; + break; + } + break; + + case NGM_SSCFU_COOKIE: + switch (msg->header.cmd) { + + case NGM_SSCFU_GETDEFPARAM: + { + struct ng_sscfu_getdefparam *p; + + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + NG_MKRESPONSE(resp, msg, sizeof(*p), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + p = (struct ng_sscfu_getdefparam *)resp->data; + p->mask = sscfu_getdefparam(&p->param); + break; + } + + case NGM_SSCFU_ENABLE: + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + if (priv->enabled) { + error = EISCONN; + break; + } + priv->enabled = 1; + break; + + case NGM_SSCFU_DISABLE: + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + if (!priv->enabled) { + error = ENOTCONN; + break; + } + priv->enabled = 0; + sscfu_reset(priv->sscf); + break; + + case NGM_SSCFU_GETSTATE: + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + NG_MKRESPONSE(resp, msg, sizeof(uint32_t), M_NOWAIT); + if(resp == NULL) { + error = ENOMEM; + break; + } + *(uint32_t *)resp->data = + priv->enabled ? (sscfu_getstate(priv->sscf) + 1) + : 0; + break; + + case NGM_SSCFU_GETDEBUG: + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + NG_MKRESPONSE(resp, msg, sizeof(uint32_t), M_NOWAIT); + if(resp == NULL) { + error = ENOMEM; + break; + } + *(uint32_t *)resp->data = sscfu_getdebug(priv->sscf); + break; + + case NGM_SSCFU_SETDEBUG: + if (msg->header.arglen != sizeof(uint32_t)) { + error = EINVAL; + break; + } + sscfu_setdebug(priv->sscf, *(uint32_t *)msg->data); + break; + + default: + error = EINVAL; + break; + } + break; + + default: + error = EINVAL; + break; + } + + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + + return (error); +} + +/************************************************************/ +/* + * HOOK MANAGEMENT + */ +static int +ng_sscfu_newhook(node_p node, hook_p hook, const char *name) +{ + struct priv *priv = NG_NODE_PRIVATE(node); + + if (strcmp(name, "upper") == 0) + priv->upper = hook; + else if (strcmp(name, "lower") == 0) { + priv->lower = hook; + NG_HOOK_SET_RCVDATA(hook, ng_sscfu_rcvlower); + } else + return (EINVAL); + return (0); +} + +static int +ng_sscfu_disconnect(hook_p hook) +{ + node_p node = NG_HOOK_NODE(hook); + struct priv *priv = NG_NODE_PRIVATE(node); + + if (hook == priv->upper) + priv->upper = NULL; + else if (hook == priv->lower) + priv->lower = NULL; + else { + log(LOG_ERR, "bogus hook"); + return (EINVAL); + } + + if (NG_NODE_NUMHOOKS(node) == 0) { + if (NG_NODE_IS_VALID(node)) + ng_rmnode_self(node); + } else { + /* + * Because there are no timeouts reset the protocol + * if the lower layer is disconnected. + */ + if (priv->lower == NULL && + priv->enabled && + sscfu_getstate(priv->sscf) != SSCFU_RELEASED) + sscfu_reset(priv->sscf); + } + return (0); +} + +/************************************************************/ +/* + * DATA + */ +static int +ng_sscfu_rcvupper(hook_p hook, item_p item) +{ + node_p node = NG_HOOK_NODE(hook); + struct priv *priv = NG_NODE_PRIVATE(node); + struct mbuf *m; + struct sscfu_arg a; + + if (!priv->enabled || priv->lower == NULL) { + NG_FREE_ITEM(item); + return (0); + } + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + if (!(m->m_flags & M_PKTHDR)) { + printf("no pkthdr\n"); + m_freem(m); + return (EINVAL); + } + if (m->m_len < (int)sizeof(a) && (m = m_pullup(m, sizeof(a))) == NULL) + return (ENOMEM); + bcopy((caddr_t)mtod(m, struct sscfu_arg *), &a, sizeof(a)); + m_adj(m, sizeof(a)); + + return (sscfu_saalsig(priv->sscf, a.sig, m)); +} + +static void +sscfu_send_upper(struct sscfu *sscf, void *p, enum saal_sig sig, struct mbuf *m) +{ + node_p node = (node_p)p; + struct priv *priv = NG_NODE_PRIVATE(node); + int error; + struct sscfu_arg *a; + + if (priv->upper == NULL) { + if (m != NULL) + m_freem(m); + return; + } + if (m == NULL) { + MGETHDR(m, M_NOWAIT, MT_DATA); + if (m == NULL) + return; + m->m_len = sizeof(struct sscfu_arg); + m->m_pkthdr.len = m->m_len; + } else { + M_PREPEND(m, sizeof(struct sscfu_arg), M_NOWAIT); + if (m == NULL) + return; + } + a = mtod(m, struct sscfu_arg *); + a->sig = sig; + + NG_SEND_DATA_ONLY(error, priv->upper, m); +} + +static int +ng_sscfu_rcvlower(hook_p hook, item_p item) +{ + node_p node = NG_HOOK_NODE(hook); + struct priv *priv = NG_NODE_PRIVATE(node); + struct mbuf *m; + struct sscop_arg a; + + if (!priv->enabled || priv->upper == NULL) { + NG_FREE_ITEM(item); + return (0); + } + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + if (!(m->m_flags & M_PKTHDR)) { + printf("no pkthdr\n"); + m_freem(m); + return (EINVAL); + } + + /* + * Strip of the SSCOP header. + */ + if (m->m_len < (int)sizeof(a) && (m = m_pullup(m, sizeof(a))) == NULL) + return (ENOMEM); + bcopy((caddr_t)mtod(m, struct sscop_arg *), &a, sizeof(a)); + m_adj(m, sizeof(a)); + + sscfu_input(priv->sscf, a.sig, m, a.arg); + + return (0); +} + +static void +sscfu_send_lower(struct sscfu *sscf, void *p, enum sscop_aasig sig, + struct mbuf *m, u_int arg) +{ + node_p node = (node_p)p; + struct priv *priv = NG_NODE_PRIVATE(node); + int error; + struct sscop_arg *a; + + if (priv->lower == NULL) { + if (m != NULL) + m_freem(m); + return; + } + if (m == NULL) { + MGETHDR(m, M_NOWAIT, MT_DATA); + if (m == NULL) + return; + m->m_len = sizeof(struct sscop_arg); + m->m_pkthdr.len = m->m_len; + } else { + M_PREPEND(m, sizeof(struct sscop_arg), M_NOWAIT); + if (m == NULL) + return; + } + a = mtod(m, struct sscop_arg *); + a->sig = sig; + a->arg = arg; + + NG_SEND_DATA_ONLY(error, priv->lower, m); +} + +/* + * Window is handled by ng_sscop so make this a NOP. + */ +static void +sscfu_window(struct sscfu *sscfu, void *arg, u_int w) +{ +} + +/************************************************************/ +/* + * NODE MANAGEMENT + */ +static int +ng_sscfu_constructor(node_p node) +{ + struct priv *priv; + + if ((priv = malloc(sizeof(*priv), M_NG_SSCFU, M_NOWAIT|M_ZERO)) == NULL) + return (ENOMEM); + + if ((priv->sscf = sscfu_create(node, &sscfu_funcs)) == NULL) { + free(priv, M_NG_SSCFU); + return (ENOMEM); + } + + NG_NODE_SET_PRIVATE(node, priv); + + return (0); +} + +static int +ng_sscfu_shutdown(node_p node) +{ + struct priv *priv = NG_NODE_PRIVATE(node); + + sscfu_destroy(priv->sscf); + + free(priv, M_NG_SSCFU); + NG_NODE_SET_PRIVATE(node, NULL); + + NG_NODE_UNREF(node); + + return (0); +} + +static void +sscfu_verbose(struct sscfu *sscfu, void *arg, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + printf("sscfu(%p): ", sscfu); + vprintf(fmt, ap); + va_end(ap); + printf("\n"); +} + +/************************************************************/ +/* + * INITIALISATION + */ +/* + * Loading and unloading of node type + */ +static int +ng_sscfu_mod_event(module_t mod, int event, void *data) +{ + int s; + int error = 0; + + s = splnet(); + switch (event) { + + case MOD_LOAD: + break; + + case MOD_UNLOAD: + break; + + default: + error = EOPNOTSUPP; + break; + } + splx(s); + return (error); +} diff --git a/sys/netgraph7/atm/sscfu/ng_sscfu_cust.h b/sys/netgraph7/atm/sscfu/ng_sscfu_cust.h new file mode 100644 index 0000000000..4605ad53f3 --- /dev/null +++ b/sys/netgraph7/atm/sscfu/ng_sscfu_cust.h @@ -0,0 +1,131 @@ +/*- + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Customisation of the SSCFU code to ng_sscfu. + * + * $FreeBSD: src/sys/netgraph/atm/sscfu/ng_sscfu_cust.h,v 1.2 2005/01/07 01:45:41 imp Exp $ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Allocate zeroed or non-zeroed memory of some size and cast it. + * Return NULL on failure. + */ +#ifndef SSCFU_DEBUG + +#define MEMINIT() \ + MALLOC_DECLARE(M_NG_SSCFU); \ + DECL_SIGQ_GET + +#define MEMZALLOC(PTR, CAST, SIZE) \ + ((PTR) = (CAST)malloc((SIZE), M_NG_SSCFU, M_NOWAIT | M_ZERO)) +#define MEMFREE(PTR) \ + free(PTR, M_NG_SSCFU) + +#define SIG_ALLOC(PTR) \ + MEMZALLOC(PTR, struct sscfu_sig *, sizeof(struct sscfu_sig)) +#define SIG_FREE(PTR) \ + MEMFREE(PTR) + +#else + +#define MEMINIT() \ + MALLOC_DEFINE(M_NG_SSCFU_INS, "sscfu_ins", "SSCFU instances"); \ + MALLOC_DEFINE(M_NG_SSCFU_SIG, "sscfu_sig", "SSCFU signals"); \ + DECL_SIGQ_GET + +#define MEMZALLOC(PTR, CAST, SIZE) \ + ((PTR) = (CAST)malloc((SIZE), M_NG_SSCFU_INS, M_NOWAIT | M_ZERO)) +#define MEMFREE(PTR) \ + FREE(PTR, M_NG_SSCFU_INS) + +#define SIG_ALLOC(PTR) \ + ((PTR) = malloc(sizeof(struct sscfu_sig), \ + M_NG_SSCFU_SIG, M_NOWAIT | M_ZERO)) +#define SIG_FREE(PTR) \ + FREE(PTR, M_NG_SSCFU_SIG) + +#endif + + +/* + * Signal queues + */ +typedef TAILQ_ENTRY(sscfu_sig) sscfu_sigq_link_t; +typedef TAILQ_HEAD(sscfu_sigq, sscfu_sig) sscfu_sigq_head_t; +#define SIGQ_INIT(Q) TAILQ_INIT(Q) +#define SIGQ_APPEND(Q, S) TAILQ_INSERT_TAIL(Q, S, link) + +#define SIGQ_GET(Q) ng_sscfu_sigq_get((Q)) + +#define DECL_SIGQ_GET \ +static __inline struct sscfu_sig * \ +ng_sscfu_sigq_get(struct sscfu_sigq *q) \ +{ \ + struct sscfu_sig *s; \ + \ + s = TAILQ_FIRST(q); \ + if (s != NULL) \ + TAILQ_REMOVE(q, s, link); \ + return (s); \ +} + +#define SIGQ_CLEAR(Q) \ + do { \ + struct sscfu_sig *_s1, *_s2; \ + \ + _s1 = TAILQ_FIRST(Q); \ + while (_s1 != NULL) { \ + _s2 = TAILQ_NEXT(_s1, link); \ + if (_s1->m) \ + MBUF_FREE(_s1->m); \ + SIG_FREE(_s1); \ + _s1 = _s2; \ + } \ + TAILQ_INIT(Q); \ + } while (0) + + +/* + * Message buffers + */ +#define MBUF_FREE(M) m_freem(M) + +#ifdef SSCFU_DEBUG +#define ASSERT(S) KASSERT(S, (#S)) +#else +#define ASSERT(S) +#endif diff --git a/sys/netgraph7/atm/sscop/ng_sscop.c b/sys/netgraph7/atm/sscop/ng_sscop.c new file mode 100644 index 0000000000..3b7e1c86bf --- /dev/null +++ b/sys/netgraph7/atm/sscop/ng_sscop.c @@ -0,0 +1,883 @@ +/*- + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt + * + * Redistribution of this software and documentation and use in source and + * binary forms, with or without modification, are permitted provided that + * the following conditions are met: + * + * 1. Redistributions of source code or documentation must retain the above + * copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS + * AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * FRAUNHOFER FOKUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Netgraph module for ITU-T Q.2110 SSCOP. + */ + +#include +__FBSDID("$FreeBSD: src/sys/netgraph/atm/sscop/ng_sscop.c,v 1.4 2005/08/10 06:25:40 obrien Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define DDD printf("%s: %d\n", __func__, __LINE__) + +#ifdef SSCOP_DEBUG +#define VERBOSE(P,M,F) \ + do { \ + if (sscop_getdebug((P)->sscop) & (M)) \ + sscop_verbose F ; \ + } while(0) +#else +#define VERBOSE(P,M,F) +#endif + +MALLOC_DEFINE(M_NG_SSCOP, "netgraph_sscop", "netgraph sscop node"); + +MODULE_DEPEND(ng_sscop, ngatmbase, 1, 1, 1); + +struct stats { + uint64_t in_packets; + uint64_t out_packets; + uint64_t aa_signals; + uint64_t errors; + uint64_t data_delivered; + uint64_t aa_dropped; + uint64_t maa_dropped; + uint64_t maa_signals; + uint64_t in_dropped; + uint64_t out_dropped; +}; + +/* + * Private data + */ +struct priv { + hook_p upper; /* SAAL interface */ + hook_p lower; /* AAL5 interface */ + hook_p manage; /* management interface */ + + struct sscop *sscop; /* sscop state */ + int enabled; /* whether the protocol is enabled */ + int flow; /* flow control states */ + struct stats stats; /* sadistics */ +}; + +/* + * Parse PARAM type + */ +static const struct ng_parse_struct_field ng_sscop_param_type_info[] = + NG_SSCOP_PARAM_INFO; + +static const struct ng_parse_type ng_sscop_param_type = { + &ng_parse_struct_type, + ng_sscop_param_type_info +}; + +/* + * Parse a SET PARAM type. + */ +static const struct ng_parse_struct_field ng_sscop_setparam_type_info[] = + NG_SSCOP_SETPARAM_INFO; + +static const struct ng_parse_type ng_sscop_setparam_type = { + &ng_parse_struct_type, + ng_sscop_setparam_type_info, +}; + +/* + * Parse a SET PARAM response + */ +static const struct ng_parse_struct_field ng_sscop_setparam_resp_type_info[] = + NG_SSCOP_SETPARAM_RESP_INFO; + +static const struct ng_parse_type ng_sscop_setparam_resp_type = { + &ng_parse_struct_type, + ng_sscop_setparam_resp_type_info, +}; + +static const struct ng_cmdlist ng_sscop_cmdlist[] = { + { + NGM_SSCOP_COOKIE, + NGM_SSCOP_GETPARAM, + "getparam", + NULL, + &ng_sscop_param_type + }, + { + NGM_SSCOP_COOKIE, + NGM_SSCOP_SETPARAM, + "setparam", + &ng_sscop_setparam_type, + &ng_sscop_setparam_resp_type + }, + { + NGM_SSCOP_COOKIE, + NGM_SSCOP_ENABLE, + "enable", + NULL, + NULL + }, + { + NGM_SSCOP_COOKIE, + NGM_SSCOP_DISABLE, + "disable", + NULL, + NULL + }, + { + NGM_SSCOP_COOKIE, + NGM_SSCOP_GETDEBUG, + "getdebug", + NULL, + &ng_parse_hint32_type + }, + { + NGM_SSCOP_COOKIE, + NGM_SSCOP_SETDEBUG, + "setdebug", + &ng_parse_hint32_type, + NULL + }, + { + NGM_SSCOP_COOKIE, + NGM_SSCOP_GETSTATE, + "getstate", + NULL, + &ng_parse_uint32_type + }, + { 0 } +}; + +static ng_constructor_t ng_sscop_constructor; +static ng_shutdown_t ng_sscop_shutdown; +static ng_rcvmsg_t ng_sscop_rcvmsg; +static ng_newhook_t ng_sscop_newhook; +static ng_disconnect_t ng_sscop_disconnect; +static ng_rcvdata_t ng_sscop_rcvlower; +static ng_rcvdata_t ng_sscop_rcvupper; +static ng_rcvdata_t ng_sscop_rcvmanage; + +static int ng_sscop_mod_event(module_t, int, void *); + +static struct ng_type ng_sscop_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_SSCOP_NODE_TYPE, + .mod_event = ng_sscop_mod_event, + .constructor = ng_sscop_constructor, + .rcvmsg = ng_sscop_rcvmsg, + .shutdown = ng_sscop_shutdown, + .newhook = ng_sscop_newhook, + .rcvdata = ng_sscop_rcvlower, + .disconnect = ng_sscop_disconnect, + .cmdlist = ng_sscop_cmdlist, +}; +NETGRAPH_INIT(sscop, &ng_sscop_typestruct); + +static void sscop_send_manage(struct sscop *, void *, enum sscop_maasig, + struct SSCOP_MBUF_T *, u_int, u_int); +static void sscop_send_upper(struct sscop *, void *, enum sscop_aasig, + struct SSCOP_MBUF_T *, u_int); +static void sscop_send_lower(struct sscop *, void *, + struct SSCOP_MBUF_T *); +static void sscop_verbose(struct sscop *, void *, const char *, ...) + __printflike(3, 4); + +static const struct sscop_funcs sscop_funcs = { + sscop_send_manage, + sscop_send_upper, + sscop_send_lower, + sscop_verbose +}; + +static void +sscop_verbose(struct sscop *sscop, void *arg, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + printf("sscop(%p): ", sscop); + vprintf(fmt, ap); + va_end(ap); + printf("\n"); +} + +/************************************************************/ +/* + * NODE MANAGEMENT + */ +static int +ng_sscop_constructor(node_p node) +{ + struct priv *p; + + if ((p = malloc(sizeof(*p), M_NG_SSCOP, M_NOWAIT | M_ZERO)) == NULL) + return (ENOMEM); + + if ((p->sscop = sscop_create(node, &sscop_funcs)) == NULL) { + free(p, M_NG_SSCOP); + return (ENOMEM); + } + NG_NODE_SET_PRIVATE(node, p); + + /* All data message received by the node are expected to change the + * node's state. Therefor we must ensure, that we have a writer lock. */ + NG_NODE_FORCE_WRITER(node); + + return (0); +} +static int +ng_sscop_shutdown(node_p node) +{ + struct priv *priv = NG_NODE_PRIVATE(node); + + sscop_destroy(priv->sscop); + + free(priv, M_NG_SSCOP); + NG_NODE_SET_PRIVATE(node, NULL); + + NG_NODE_UNREF(node); + + return (0); +} + +/************************************************************/ +/* + * CONTROL MESSAGES + */ +/* + * Flow control message from upper layer. + * This is very experimental: + * If we get a message from the upper layer, that somebody has passed its + * high water mark, we stop updating the receive window. + * If we get a low watermark passed, then we raise the window up + * to max - current. + * If we get a queue status and it indicates a current below the + * high watermark, we unstop window updates (if they are stopped) and + * raise the window to highwater - current. + */ +static int +flow_upper(node_p node, struct ng_mesg *msg) +{ + struct ngm_queue_state *q; + struct priv *priv = NG_NODE_PRIVATE(node); + u_int window, space; + + if (msg->header.arglen != sizeof(struct ngm_queue_state)) + return (EINVAL); + q = (struct ngm_queue_state *)msg->data; + + switch (msg->header.cmd) { + + case NGM_HIGH_WATER_PASSED: + if (priv->flow) { + VERBOSE(priv, SSCOP_DBG_FLOW, (priv->sscop, priv, + "flow control stopped")); + priv->flow = 0; + } + break; + + case NGM_LOW_WATER_PASSED: + window = sscop_window(priv->sscop, 0); + space = q->max_queuelen_packets - q->current; + if (space > window) { + VERBOSE(priv, SSCOP_DBG_FLOW, (priv->sscop, priv, + "flow control opened window by %u messages", + space - window)); + (void)sscop_window(priv->sscop, space - window); + } + priv->flow = 1; + break; + + case NGM_SYNC_QUEUE_STATE: + if (q->high_watermark <= q->current) + break; + window = sscop_window(priv->sscop, 0); + if (priv->flow) + space = q->max_queuelen_packets - q->current; + else + space = q->high_watermark - q->current; + if (space > window) { + VERBOSE(priv, SSCOP_DBG_FLOW, (priv->sscop, priv, + "flow control opened window by %u messages", + space - window)); + (void)sscop_window(priv->sscop, space - window); + } + priv->flow = 1; + break; + + default: + return (EINVAL); + } + return (0); +} + +static int +flow_lower(node_p node, struct ng_mesg *msg) +{ + struct priv *priv = NG_NODE_PRIVATE(node); + + if (msg->header.arglen != sizeof(struct ngm_queue_state)) + return (EINVAL); + + switch (msg->header.cmd) { + + case NGM_HIGH_WATER_PASSED: + sscop_setbusy(priv->sscop, 1); + break; + + case NGM_LOW_WATER_PASSED: + sscop_setbusy(priv->sscop, 1); + break; + + default: + return (EINVAL); + } + return (0); +} + +/* + * Produce a readable status description + */ +static int +text_status(node_p node, struct priv *priv, char *arg, u_int len) +{ + struct sbuf sbuf; + + sbuf_new(&sbuf, arg, len, 0); + + if (priv->upper) + sbuf_printf(&sbuf, "upper hook: %s connected to %s:%s\n", + NG_HOOK_NAME(priv->upper), + NG_NODE_NAME(NG_HOOK_NODE(NG_HOOK_PEER(priv->upper))), + NG_HOOK_NAME(NG_HOOK_PEER(priv->upper))); + else + sbuf_printf(&sbuf, "upper hook: \n"); + + if (priv->lower) + sbuf_printf(&sbuf, "lower hook: %s connected to %s:%s\n", + NG_HOOK_NAME(priv->lower), + NG_NODE_NAME(NG_HOOK_NODE(NG_HOOK_PEER(priv->lower))), + NG_HOOK_NAME(NG_HOOK_PEER(priv->lower))); + else + sbuf_printf(&sbuf, "lower hook: \n"); + + if (priv->manage) + sbuf_printf(&sbuf, "manage hook: %s connected to %s:%s\n", + NG_HOOK_NAME(priv->manage), + NG_NODE_NAME(NG_HOOK_NODE(NG_HOOK_PEER(priv->manage))), + NG_HOOK_NAME(NG_HOOK_PEER(priv->manage))); + else + sbuf_printf(&sbuf, "manage hook: \n"); + + sbuf_printf(&sbuf, "sscop state: %s\n", + !priv->enabled ? "" : + sscop_statename(sscop_getstate(priv->sscop))); + + sbuf_printf(&sbuf, "input packets: %ju\n", + (uintmax_t)priv->stats.in_packets); + sbuf_printf(&sbuf, "input dropped: %ju\n", + (uintmax_t)priv->stats.in_dropped); + sbuf_printf(&sbuf, "output packets: %ju\n", + (uintmax_t)priv->stats.out_packets); + sbuf_printf(&sbuf, "output dropped: %ju\n", + (uintmax_t)priv->stats.out_dropped); + sbuf_printf(&sbuf, "aa signals: %ju\n", + (uintmax_t)priv->stats.aa_signals); + sbuf_printf(&sbuf, "aa dropped: %ju\n", + (uintmax_t)priv->stats.aa_dropped); + sbuf_printf(&sbuf, "maa signals: %ju\n", + (uintmax_t)priv->stats.maa_signals); + sbuf_printf(&sbuf, "maa dropped: %ju\n", + (uintmax_t)priv->stats.maa_dropped); + sbuf_printf(&sbuf, "errors: %ju\n", + (uintmax_t)priv->stats.errors); + sbuf_printf(&sbuf, "data delivered: %ju\n", + (uintmax_t)priv->stats.data_delivered); + sbuf_printf(&sbuf, "window: %u\n", + sscop_window(priv->sscop, 0)); + + sbuf_finish(&sbuf); + return (sbuf_len(&sbuf)); +} + + +/* + * Control message received. + */ +static int +ng_sscop_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct priv *priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + struct ng_mesg *msg; + int error = 0; + + NGI_GET_MSG(item, msg); + + switch (msg->header.typecookie) { + + case NGM_GENERIC_COOKIE: + switch (msg->header.cmd) { + + case NGM_TEXT_STATUS: + NG_MKRESPONSE(resp, msg, NG_TEXTRESPONSE, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + + resp->header.arglen = text_status(node, priv, + (char *)resp->data, resp->header.arglen) + 1; + break; + + default: + error = EINVAL; + break; + } + break; + + case NGM_FLOW_COOKIE: + if (priv->enabled && lasthook != NULL) { + if (lasthook == priv->upper) + error = flow_upper(node, msg); + else if (lasthook == priv->lower) + error = flow_lower(node, msg); + } + break; + + case NGM_SSCOP_COOKIE: + switch (msg->header.cmd) { + + case NGM_SSCOP_GETPARAM: + { + struct sscop_param *p; + + NG_MKRESPONSE(resp, msg, sizeof(*p), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + p = (struct sscop_param *)resp->data; + sscop_getparam(priv->sscop, p); + break; + } + + case NGM_SSCOP_SETPARAM: + { + struct ng_sscop_setparam *arg; + struct ng_sscop_setparam_resp *p; + + if (msg->header.arglen != sizeof(*arg)) { + error = EINVAL; + break; + } + if (priv->enabled) { + error = EISCONN; + break; + } + arg = (struct ng_sscop_setparam *)msg->data; + NG_MKRESPONSE(resp, msg, sizeof(*p), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + p = (struct ng_sscop_setparam_resp *)resp->data; + p->mask = arg->mask; + p->error = sscop_setparam(priv->sscop, + &arg->param, &p->mask); + break; + } + + case NGM_SSCOP_ENABLE: + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + if (priv->enabled) { + error = EBUSY; + break; + } + priv->enabled = 1; + priv->flow = 1; + memset(&priv->stats, 0, sizeof(priv->stats)); + break; + + case NGM_SSCOP_DISABLE: + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + if (!priv->enabled) { + error = ENOTCONN; + break; + } + priv->enabled = 0; + sscop_reset(priv->sscop); + break; + + case NGM_SSCOP_GETDEBUG: + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); + if(resp == NULL) { + error = ENOMEM; + break; + } + *(u_int32_t *)resp->data = sscop_getdebug(priv->sscop); + break; + + case NGM_SSCOP_SETDEBUG: + if (msg->header.arglen != sizeof(u_int32_t)) { + error = EINVAL; + break; + } + sscop_setdebug(priv->sscop, *(u_int32_t *)msg->data); + break; + + case NGM_SSCOP_GETSTATE: + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); + if(resp == NULL) { + error = ENOMEM; + break; + } + *(u_int32_t *)resp->data = + priv->enabled ? (sscop_getstate(priv->sscop) + 1) + : 0; + break; + + default: + error = EINVAL; + break; + } + break; + + default: + error = EINVAL; + break; + } + + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + + return (error); +} + +/************************************************************/ +/* + * HOOK MANAGEMENT + */ +static int +ng_sscop_newhook(node_p node, hook_p hook, const char *name) +{ + struct priv *priv = NG_NODE_PRIVATE(node); + + if(strcmp(name, "upper") == 0) { + priv->upper = hook; + NG_HOOK_SET_RCVDATA(hook, ng_sscop_rcvupper); + } else if(strcmp(name, "lower") == 0) { + priv->lower = hook; + } else if(strcmp(name, "manage") == 0) { + priv->manage = hook; + NG_HOOK_SET_RCVDATA(hook, ng_sscop_rcvmanage); + } else + return EINVAL; + + return 0; +} +static int +ng_sscop_disconnect(hook_p hook) +{ + node_p node = NG_HOOK_NODE(hook); + struct priv *priv = NG_NODE_PRIVATE(node); + + if(hook == priv->upper) + priv->upper = NULL; + else if(hook == priv->lower) + priv->lower = NULL; + else if(hook == priv->manage) + priv->manage = NULL; + + if(NG_NODE_NUMHOOKS(node) == 0) { + if(NG_NODE_IS_VALID(node)) + ng_rmnode_self(node); + } else { + /* + * Imply a release request, if the upper layer is + * disconnected. + */ + if(priv->upper == NULL && priv->lower != NULL && + priv->enabled && + sscop_getstate(priv->sscop) != SSCOP_IDLE) { + sscop_aasig(priv->sscop, SSCOP_RELEASE_request, + NULL, 0); + } + } + return 0; +} + +/************************************************************/ +/* + * DATA + */ +static int +ng_sscop_rcvlower(hook_p hook, item_p item) +{ + struct priv *priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct mbuf *m; + + if (!priv->enabled) { + NG_FREE_ITEM(item); + return EINVAL; + } + + /* + * If we are disconnected at the upper layer and in the IDLE + * state, drop any incoming packet. + */ + if (priv->upper != NULL || sscop_getstate(priv->sscop) != SSCOP_IDLE) { + NGI_GET_M(item, m); + priv->stats.in_packets++; + sscop_input(priv->sscop, m); + } else { + priv->stats.in_dropped++; + } + NG_FREE_ITEM(item); + + return (0); +} + +static void +sscop_send_lower(struct sscop *sscop, void *p, struct mbuf *m) +{ + node_p node = (node_p)p; + struct priv *priv = NG_NODE_PRIVATE(node); + int error; + + if (priv->lower == NULL) { + m_freem(m); + priv->stats.out_dropped++; + return; + } + + priv->stats.out_packets++; + NG_SEND_DATA_ONLY(error, priv->lower, m); +} + +static int +ng_sscop_rcvupper(hook_p hook, item_p item) +{ + struct priv *priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct sscop_arg a; + struct mbuf *m; + + if (!priv->enabled) { + NG_FREE_ITEM(item); + return (EINVAL); + } + + /* + * If the lower layer is not connected allow to proceed. + * The lower layer sending function will drop outgoing frames, + * and the sscop will timeout any establish requests. + */ + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + if (!(m->m_flags & M_PKTHDR)) { + printf("no pkthdr\n"); + m_freem(m); + return (EINVAL); + } + if (m->m_len < (int)sizeof(a) && (m = m_pullup(m, sizeof(a))) == NULL) + return (ENOBUFS); + bcopy((caddr_t)mtod(m, struct sscop_arg *), &a, sizeof(a)); + m_adj(m, sizeof(a)); + + return (sscop_aasig(priv->sscop, a.sig, m, a.arg)); +} + +static void +sscop_send_upper(struct sscop *sscop, void *p, enum sscop_aasig sig, + struct SSCOP_MBUF_T *m, u_int arg) +{ + node_p node = (node_p)p; + struct priv *priv = NG_NODE_PRIVATE(node); + int error; + struct sscop_arg *a; + + if (sig == SSCOP_DATA_indication && priv->flow) + sscop_window(priv->sscop, 1); + + if (priv->upper == NULL) { + if (m != NULL) + m_freem(m); + priv->stats.aa_dropped++; + return; + } + + priv->stats.aa_signals++; + if (sig == SSCOP_DATA_indication) + priv->stats.data_delivered++; + + if (m == NULL) { + MGETHDR(m, M_NOWAIT, MT_DATA); + if (m == NULL) + return; + m->m_len = sizeof(struct sscop_arg); + m->m_pkthdr.len = m->m_len; + } else { + M_PREPEND(m, sizeof(struct sscop_arg), M_NOWAIT); + if (m == NULL) + return; + } + a = mtod(m, struct sscop_arg *); + a->sig = sig; + a->arg = arg; + + NG_SEND_DATA_ONLY(error, priv->upper, m); +} + +static int +ng_sscop_rcvmanage(hook_p hook, item_p item) +{ + struct priv *priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct sscop_marg a; + struct mbuf *m; + + if (!priv->enabled) { + NG_FREE_ITEM(item); + return (EINVAL); + } + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + if (m->m_len < (int)sizeof(a) && (m = m_pullup(m, sizeof(a))) == NULL) + return (ENOBUFS); + bcopy((caddr_t)mtod(m, struct sscop_arg *), &a, sizeof(a)); + m_adj(m, sizeof(a)); + + return (sscop_maasig(priv->sscop, a.sig, m)); +} + +static void +sscop_send_manage(struct sscop *sscop, void *p, enum sscop_maasig sig, + struct SSCOP_MBUF_T *m, u_int err, u_int cnt) +{ + node_p node = (node_p)p; + struct priv *priv = NG_NODE_PRIVATE(node); + int error; + struct sscop_merr *e; + struct sscop_marg *a; + + if (priv->manage == NULL) { + if (m != NULL) + m_freem(m); + priv->stats.maa_dropped++; + return; + } + + if (sig == SSCOP_MERROR_indication) { + MGETHDR(m, M_NOWAIT, MT_DATA); + if (m == NULL) + return; + m->m_len = sizeof(*e); + m->m_pkthdr.len = m->m_len; + e = mtod(m, struct sscop_merr *); + e->sig = sig; + e->err = err; + e->cnt = cnt; + priv->stats.errors++; + } else if (m == NULL) { + MGETHDR(m, M_NOWAIT, MT_DATA); + if (m == NULL) + return; + m->m_len = sizeof(*a); + m->m_pkthdr.len = m->m_len; + a = mtod(m, struct sscop_marg *); + a->sig = sig; + priv->stats.maa_signals++; + } else { + M_PREPEND(m, sizeof(*a), M_NOWAIT); + if (m == NULL) + return; + a = mtod(m, struct sscop_marg *); + a->sig = sig; + priv->stats.maa_signals++; + } + + NG_SEND_DATA_ONLY(error, priv->manage, m); +} + +/************************************************************/ +/* + * INITIALISATION + */ + +/* + * Loading and unloading of node type + */ +static int +ng_sscop_mod_event(module_t mod, int event, void *data) +{ + int s; + int error = 0; + + s = splnet(); + switch (event) { + + case MOD_LOAD: + break; + + case MOD_UNLOAD: + break; + + default: + error = EOPNOTSUPP; + break; + } + splx(s); + return (error); +} diff --git a/sys/netgraph7/atm/sscop/ng_sscop_cust.h b/sys/netgraph7/atm/sscop/ng_sscop_cust.h new file mode 100644 index 0000000000..d8f073b41d --- /dev/null +++ b/sys/netgraph7/atm/sscop/ng_sscop_cust.h @@ -0,0 +1,344 @@ +/*- + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/atm/sscop/ng_sscop_cust.h,v 1.5 2005/01/07 01:45:41 imp Exp $ + * + * Customisation of the SSCOP code to ng_sscop. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * Allocate zeroed or non-zeroed memory of some size and cast it. + * Return NULL on failure. + */ +#ifndef SSCOP_DEBUG + +#define MEMINIT() \ + MALLOC_DECLARE(M_NG_SSCOP); \ + DECL_MSGQ_GET \ + DECL_SIGQ_GET \ + DECL_MBUF_ALLOC + +#define MEMZALLOC(PTR, CAST, SIZE) \ + ((PTR) = (CAST)malloc((SIZE), M_NG_SSCOP, M_NOWAIT | M_ZERO)) +#define MEMFREE(PTR) \ + free((PTR), M_NG_SSCOP) + +#define MSG_ALLOC(PTR) \ + MEMZALLOC(PTR, struct sscop_msg *, sizeof(struct sscop_msg)) +#define MSG_FREE(PTR) \ + MEMFREE(PTR) + +#define SIG_ALLOC(PTR) \ + MEMZALLOC(PTR, struct sscop_sig *, sizeof(struct sscop_sig)) +#define SIG_FREE(PTR) \ + MEMFREE(PTR) + +#else + +#define MEMINIT() \ + MALLOC_DEFINE(M_NG_SSCOP_INS, "sscop_ins", "SSCOP instances"); \ + MALLOC_DEFINE(M_NG_SSCOP_MSG, "sscop_msg", "SSCOP buffers"); \ + MALLOC_DEFINE(M_NG_SSCOP_SIG, "sscop_sig", "SSCOP signals"); \ + DECL_MSGQ_GET \ + DECL_SIGQ_GET \ + DECL_MBUF_ALLOC + +#define MEMZALLOC(PTR, CAST, SIZE) \ + ((PTR) = (CAST)malloc((SIZE), M_NG_SSCOP_INS, M_NOWAIT | M_ZERO)) +#define MEMFREE(PTR) \ + free((PTR), M_NG_SSCOP_INS) + +#define MSG_ALLOC(PTR) \ + ((PTR) = malloc(sizeof(struct sscop_msg), \ + M_NG_SSCOP_MSG, M_NOWAIT | M_ZERO)) +#define MSG_FREE(PTR) \ + free((PTR), M_NG_SSCOP_MSG) + +#define SIG_ALLOC(PTR) \ + ((PTR) = malloc(sizeof(struct sscop_sig), \ + M_NG_SSCOP_SIG, M_NOWAIT | M_ZERO)) +#define SIG_FREE(PTR) \ + free((PTR), M_NG_SSCOP_SIG) + +#endif + +/* + * Timer support. + */ +typedef struct callout sscop_timer_t; +#define TIMER_INIT(S, T) ng_callout_init(&(S)->t_##T) +#define TIMER_STOP(S,T) do { \ + ng_uncallout(&(S)->t_##T, (S)->aarg); \ + } while (0) +#define TIMER_RESTART(S, T) do { \ + TIMER_STOP(S, T); \ + ng_callout(&(S)->t_##T, (S)->aarg, NULL, \ + hz * (S)->timer##T / 1000, T##_func, (S), 0); \ + } while (0) +#define TIMER_ISACT(S, T) ((S)->t_##T.c_flags & (CALLOUT_PENDING)) + +/* + * This assumes, that the user argument is the node pointer. + */ +#define TIMER_FUNC(T,N) \ +static void \ +T##_func(node_p node, hook_p hook, void *arg1, int arg2) \ +{ \ + struct sscop *sscop = arg1; \ + \ + VERBOSE(sscop, SSCOP_DBG_TIMER, (sscop, sscop->aarg, \ + "timer_" #T " expired")); \ + sscop_signal(sscop, SIG_T_##N, NULL); \ +} + + +/* + * Message queues + */ +typedef TAILQ_ENTRY(sscop_msg) sscop_msgq_link_t; +typedef TAILQ_HEAD(sscop_msgq, sscop_msg) sscop_msgq_head_t; +#define MSGQ_EMPTY(Q) TAILQ_EMPTY(Q) +#define MSGQ_INIT(Q) TAILQ_INIT(Q) +#define MSGQ_FOREACH(P, Q) TAILQ_FOREACH(P, Q, link) +#define MSGQ_REMOVE(Q, M) TAILQ_REMOVE(Q, M, link) +#define MSGQ_INSERT_BEFORE(B, M) TAILQ_INSERT_BEFORE(B, M, link) +#define MSGQ_APPEND(Q, M) TAILQ_INSERT_TAIL(Q, M, link) +#define MSGQ_PEEK(Q) TAILQ_FIRST((Q)) + +#define MSGQ_GET(Q) ng_sscop_msgq_get((Q)) + +#define DECL_MSGQ_GET \ +static __inline struct sscop_msg * \ +ng_sscop_msgq_get(struct sscop_msgq *q) \ +{ \ + struct sscop_msg *m; \ + \ + m = TAILQ_FIRST(q); \ + if (m != NULL) \ + TAILQ_REMOVE(q, m, link); \ + return (m); \ +} + +#define MSGQ_CLEAR(Q) \ + do { \ + struct sscop_msg *_m1, *_m2; \ + \ + _m1 = TAILQ_FIRST(Q); \ + while (_m1 != NULL) { \ + _m2 = TAILQ_NEXT(_m1, link); \ + SSCOP_MSG_FREE(_m1); \ + _m1 = _m2; \ + } \ + TAILQ_INIT((Q)); \ + } while (0) + +/* + * Signal queues + */ +typedef TAILQ_ENTRY(sscop_sig) sscop_sigq_link_t; +typedef TAILQ_HEAD(sscop_sigq, sscop_sig) sscop_sigq_head_t; +#define SIGQ_INIT(Q) TAILQ_INIT(Q) +#define SIGQ_APPEND(Q, S) TAILQ_INSERT_TAIL(Q, S, link) +#define SIGQ_EMPTY(Q) TAILQ_EMPTY(Q) + +#define SIGQ_GET(Q) ng_sscop_sigq_get((Q)) +#define DECL_SIGQ_GET \ +static __inline struct sscop_sig * \ +ng_sscop_sigq_get(struct sscop_sigq *q) \ +{ \ + struct sscop_sig *s; \ + \ + s = TAILQ_FIRST(q); \ + if (s != NULL) \ + TAILQ_REMOVE(q, s, link); \ + return (s); \ +} + +#define SIGQ_MOVE(F, T) \ + do { \ + struct sscop_sig *_s; \ + \ + while (!TAILQ_EMPTY(F)) { \ + _s = TAILQ_FIRST(F); \ + TAILQ_REMOVE(F, _s, link); \ + TAILQ_INSERT_TAIL(T, _s, link); \ + } \ + } while (0) + +#define SIGQ_PREPEND(F, T) \ + do { \ + struct sscop_sig *_s; \ + \ + while (!TAILQ_EMPTY(F)) { \ + _s = TAILQ_LAST(F, sscop_sigq); \ + TAILQ_REMOVE(F, _s, link); \ + TAILQ_INSERT_HEAD(T, _s, link); \ + } \ + } while (0) + +#define SIGQ_CLEAR(Q) \ + do { \ + struct sscop_sig *_s1, *_s2; \ + \ + _s1 = TAILQ_FIRST(Q); \ + while (_s1 != NULL) { \ + _s2 = TAILQ_NEXT(_s1, link); \ + SSCOP_MSG_FREE(_s1->msg); \ + SIG_FREE(_s1); \ + _s1 = _s2; \ + } \ + TAILQ_INIT(Q); \ + } while (0) + +/* + * Message buffers + */ +#define MBUF_FREE(M) do { if ((M)) m_freem((M)); } while(0) +#define MBUF_DUP(M) m_copypacket((M), M_NOWAIT) +#define MBUF_LEN(M) ((size_t)(M)->m_pkthdr.len) + +/* + * Return the i-th word counted from the end of the buffer. + * i=-1 will return the last 32bit word, i=-2 the 2nd last. + * Assumes that there is enough space. + */ +#define MBUF_TRAIL32(M ,I) ng_sscop_mbuf_trail32((M), (I)) + +static uint32_t __inline +ng_sscop_mbuf_trail32(const struct mbuf *m, int i) +{ + uint32_t w; + + m_copydata(m, m->m_pkthdr.len + 4 * i, 4, (caddr_t)&w); + return (ntohl(w)); +} + +/* + * Strip 32bit value from the end + */ +#define MBUF_STRIP32(M) ng_sscop_mbuf_strip32((M)) + +static uint32_t __inline +ng_sscop_mbuf_strip32(struct mbuf *m) +{ + uint32_t w; + + m_copydata(m, m->m_pkthdr.len - 4, 4, (caddr_t)&w); + m_adj(m, -4); + return (ntohl(w)); +} + +#define MBUF_GET32(M) ng_sscop_mbuf_get32((M)) + +static uint32_t __inline +ng_sscop_mbuf_get32(struct mbuf *m) +{ + uint32_t w; + + m_copydata(m, 0, 4, (caddr_t)&w); + m_adj(m, 4); + return (ntohl(w)); +} + +/* + * Append a 32bit value to an mbuf. Failures are ignored. + */ +#define MBUF_APPEND32(M, W) \ + do { \ + uint32_t _w = (W); \ + \ + _w = htonl(_w); \ + m_copyback((M), (M)->m_pkthdr.len, 4, (caddr_t)&_w); \ + } while (0) + +/* + * Pad a message to a multiple of four byte and return the amount of padding + * Failures are ignored. + */ +#define MBUF_PAD4(M) ng_sscop_mbuf_pad4((M)) + +static u_int __inline +ng_sscop_mbuf_pad4(struct mbuf *m) +{ + static u_char pad[4] = { 0, 0, 0, 0 }; + int len = m->m_pkthdr.len; + int npad = 3 - ((len + 3) & 3); + + if (npad != 0) + m_copyback(m, len, npad, (caddr_t)pad); + return (npad); +} + +#define MBUF_UNPAD(M, P) do { if( (P) > 0) m_adj((M), -(P)); } while (0) + +/* + * Allocate a message that will probably hold N bytes. + */ +#define MBUF_ALLOC(N) ng_sscop_mbuf_alloc((N)) + +#define DECL_MBUF_ALLOC \ +static __inline struct mbuf * \ +ng_sscop_mbuf_alloc(size_t n) \ +{ \ + struct mbuf *m; \ + \ + MGETHDR(m, M_NOWAIT, MT_DATA); \ + if (m != NULL) { \ + m->m_len = 0; \ + m->m_pkthdr.len = 0; \ + if (n > MHLEN) { \ + MCLGET(m, M_NOWAIT); \ + if (!(m->m_flags & M_EXT)){ \ + m_free(m); \ + m = NULL; \ + } \ + } \ + } \ + return (m); \ +} + +#ifdef SSCOP_DEBUG +#define ASSERT(X) KASSERT(X, (#X)) +#else +#define ASSERT(X) +#endif diff --git a/sys/netgraph7/atm/uni/ng_uni.c b/sys/netgraph7/atm/uni/ng_uni.c new file mode 100644 index 0000000000..3eb0584ce1 --- /dev/null +++ b/sys/netgraph7/atm/uni/ng_uni.c @@ -0,0 +1,931 @@ +/*- + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Hartmut Brandt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Netgraph module for ATM-Forum UNI 4.0 signalling + */ + +#include +__FBSDID("$FreeBSD: src/sys/netgraph/atm/uni/ng_uni.c,v 1.6 2005/10/31 15:41:26 rwatson Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MALLOC_DEFINE(M_NG_UNI, "netgraph_uni_node", "netgraph uni node"); +MALLOC_DEFINE(M_UNI, "netgraph_uni_data", "uni protocol data"); + +MODULE_DEPEND(ng_uni, ngatmbase, 1, 1, 1); + +/* + * Private node data + */ +struct priv { + hook_p upper; + hook_p lower; + struct uni *uni; + int enabled; +}; + +/* UNI CONFIG MASK */ +static const struct ng_parse_struct_field ng_uni_config_mask_type_info[] = + NGM_UNI_CONFIG_MASK_INFO; +static const struct ng_parse_type ng_uni_config_mask_type = { + &ng_parse_struct_type, + ng_uni_config_mask_type_info +}; + +/* UNI_CONFIG */ +static const struct ng_parse_struct_field ng_uni_config_type_info[] = + NGM_UNI_CONFIG_INFO; +static const struct ng_parse_type ng_uni_config_type = { + &ng_parse_struct_type, + ng_uni_config_type_info +}; + +/* SET CONFIG */ +static const struct ng_parse_struct_field ng_uni_set_config_type_info[] = + NGM_UNI_SET_CONFIG_INFO; +static const struct ng_parse_type ng_uni_set_config_type = { + &ng_parse_struct_type, + ng_uni_set_config_type_info +}; + +/* + * Parse DEBUG + */ +static const struct ng_parse_fixedarray_info ng_uni_debuglevel_type_info = + NGM_UNI_DEBUGLEVEL_INFO; +static const struct ng_parse_type ng_uni_debuglevel_type = { + &ng_parse_fixedarray_type, + &ng_uni_debuglevel_type_info +}; +static const struct ng_parse_struct_field ng_uni_debug_type_info[] = + NGM_UNI_DEBUG_INFO; +static const struct ng_parse_type ng_uni_debug_type = { + &ng_parse_struct_type, + ng_uni_debug_type_info +}; + +/* + * Command list + */ +static const struct ng_cmdlist ng_uni_cmdlist[] = { + { + NGM_UNI_COOKIE, + NGM_UNI_GETDEBUG, + "getdebug", + NULL, + &ng_uni_debug_type + }, + { + NGM_UNI_COOKIE, + NGM_UNI_SETDEBUG, + "setdebug", + &ng_uni_debug_type, + NULL + }, + { + NGM_UNI_COOKIE, + NGM_UNI_GET_CONFIG, + "get_config", + NULL, + &ng_uni_config_type + }, + { + NGM_UNI_COOKIE, + NGM_UNI_SET_CONFIG, + "set_config", + &ng_uni_set_config_type, + &ng_uni_config_mask_type, + }, + { + NGM_UNI_COOKIE, + NGM_UNI_ENABLE, + "enable", + NULL, + NULL, + }, + { + NGM_UNI_COOKIE, + NGM_UNI_DISABLE, + "disable", + NULL, + NULL, + }, + { + NGM_UNI_COOKIE, + NGM_UNI_GETSTATE, + "getstate", + NULL, + &ng_parse_uint32_type + }, + { 0 } +}; + +/* + * Netgraph module data + */ +static ng_constructor_t ng_uni_constructor; +static ng_shutdown_t ng_uni_shutdown; +static ng_rcvmsg_t ng_uni_rcvmsg; +static ng_newhook_t ng_uni_newhook; +static ng_disconnect_t ng_uni_disconnect; +static ng_rcvdata_t ng_uni_rcvlower; +static ng_rcvdata_t ng_uni_rcvupper; + +static int ng_uni_mod_event(module_t, int, void *); + +static struct ng_type ng_uni_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_UNI_NODE_TYPE, + .mod_event = ng_uni_mod_event, + .constructor = ng_uni_constructor, + .rcvmsg = ng_uni_rcvmsg, + .shutdown = ng_uni_shutdown, + .newhook = ng_uni_newhook, + .rcvdata = ng_uni_rcvlower, + .disconnect = ng_uni_disconnect, + .cmdlist = ng_uni_cmdlist, +}; +NETGRAPH_INIT(uni, &ng_uni_typestruct); + +static void uni_uni_output(struct uni *, void *, enum uni_sig, u_int32_t, + struct uni_msg *); +static void uni_saal_output(struct uni *, void *, enum saal_sig, + struct uni_msg *); +static void uni_verbose(struct uni *, void *, u_int, const char *, ...) + __printflike(4, 5); +static void uni_do_status(struct uni *, void *, void *, const char *, ...) + __printflike(4, 5); + +static const struct uni_funcs uni_funcs = { + uni_uni_output, + uni_saal_output, + uni_verbose, + uni_do_status +}; + +/************************************************************/ +/* + * NODE MANAGEMENT + */ +static int +ng_uni_constructor(node_p node) +{ + struct priv *priv; + + if ((priv = malloc(sizeof(*priv), M_NG_UNI, M_NOWAIT | M_ZERO)) == NULL) + return (ENOMEM); + + if ((priv->uni = uni_create(node, &uni_funcs)) == NULL) { + free(priv, M_NG_UNI); + return (ENOMEM); + } + + NG_NODE_SET_PRIVATE(node, priv); + NG_NODE_FORCE_WRITER(node); + + return (0); +} + +static int +ng_uni_shutdown(node_p node) +{ + struct priv *priv = NG_NODE_PRIVATE(node); + + uni_destroy(priv->uni); + + free(priv, M_NG_UNI); + NG_NODE_SET_PRIVATE(node, NULL); + + NG_NODE_UNREF(node); + + return (0); +} + +/************************************************************/ +/* + * CONTROL MESSAGES + */ +static void +uni_do_status(struct uni *uni, void *uarg, void *sbuf, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + sbuf_printf(sbuf, fmt, ap); + va_end(ap); +} + +static int +text_status(node_p node, struct priv *priv, char *buf, u_int len) +{ + struct sbuf sbuf; + u_int f; + + sbuf_new(&sbuf, buf, len, 0); + + if (priv->lower != NULL) + sbuf_printf(&sbuf, "lower hook: connected to %s:%s\n", + NG_NODE_NAME(NG_HOOK_NODE(NG_HOOK_PEER(priv->lower))), + NG_HOOK_NAME(NG_HOOK_PEER(priv->lower))); + else + sbuf_printf(&sbuf, "lower hook: \n"); + + if (priv->upper != NULL) + sbuf_printf(&sbuf, "upper hook: connected to %s:%s\n", + NG_NODE_NAME(NG_HOOK_NODE(NG_HOOK_PEER(priv->upper))), + NG_HOOK_NAME(NG_HOOK_PEER(priv->upper))); + else + sbuf_printf(&sbuf, "upper hook: \n"); + + sbuf_printf(&sbuf, "debugging:"); + for (f = 0; f < UNI_MAXFACILITY; f++) + if (uni_get_debug(priv->uni, f) != 0) + sbuf_printf(&sbuf, " %s=%u", uni_facname(f), + uni_get_debug(priv->uni, f)); + sbuf_printf(&sbuf, "\n"); + + if (priv->uni) + uni_status(priv->uni, &sbuf); + + sbuf_finish(&sbuf); + return (sbuf_len(&sbuf)); +} + +static int +ng_uni_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct priv *priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + struct ng_mesg *msg; + int error = 0; + u_int i; + + NGI_GET_MSG(item, msg); + + switch (msg->header.typecookie) { + + case NGM_GENERIC_COOKIE: + switch (msg->header.cmd) { + + case NGM_TEXT_STATUS: + NG_MKRESPONSE(resp, msg, NG_TEXTRESPONSE, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + + resp->header.arglen = text_status(node, priv, + (char *)resp->data, resp->header.arglen) + 1; + break; + + default: + error = EINVAL; + break; + } + break; + + case NGM_UNI_COOKIE: + switch (msg->header.cmd) { + + case NGM_UNI_SETDEBUG: + { + struct ngm_uni_debug *arg; + + if (msg->header.arglen > sizeof(*arg)) { + error = EINVAL; + break; + } + arg = (struct ngm_uni_debug *)msg->data; + for (i = 0; i < UNI_MAXFACILITY; i++) + uni_set_debug(priv->uni, i, arg->level[i]); + break; + } + + case NGM_UNI_GETDEBUG: + { + struct ngm_uni_debug *arg; + + NG_MKRESPONSE(resp, msg, sizeof(*arg), M_NOWAIT); + if(resp == NULL) { + error = ENOMEM; + break; + } + arg = (struct ngm_uni_debug *)resp->data; + for (i = 0; i < UNI_MAXFACILITY; i++) + arg->level[i] = uni_get_debug(priv->uni, i); + break; + } + + case NGM_UNI_GET_CONFIG: + { + struct uni_config *config; + + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + NG_MKRESPONSE(resp, msg, sizeof(*config), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + config = (struct uni_config *)resp->data; + uni_get_config(priv->uni, config); + + break; + } + + case NGM_UNI_SET_CONFIG: + { + struct ngm_uni_set_config *arg; + struct ngm_uni_config_mask *mask; + + if (msg->header.arglen != sizeof(*arg)) { + error = EINVAL; + break; + } + arg = (struct ngm_uni_set_config *)msg->data; + + NG_MKRESPONSE(resp, msg, sizeof(*mask), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + mask = (struct ngm_uni_config_mask *)resp->data; + + *mask = arg->mask; + + uni_set_config(priv->uni, &arg->config, + &mask->mask, &mask->popt_mask, &mask->option_mask); + + break; + } + + case NGM_UNI_ENABLE: + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + if (priv->enabled) { + error = EISCONN; + break; + } + priv->enabled = 1; + break; + + case NGM_UNI_DISABLE: + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + if (!priv->enabled) { + error = ENOTCONN; + break; + } + priv->enabled = 0; + uni_reset(priv->uni); + break; + + case NGM_UNI_GETSTATE: + if (msg->header.arglen != 0) { + error = EINVAL; + break; + } + NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); + if(resp == NULL) { + error = ENOMEM; + break; + } + *(u_int32_t *)resp->data = + priv->enabled ? (uni_getcustate(priv->uni) + 1) + : 0; + break; + + default: + error = EINVAL; + break; + } + break; + + default: + error = EINVAL; + break; + } + + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/************************************************************/ +/* + * HOOK MANAGEMENT + */ +static int +ng_uni_newhook(node_p node, hook_p hook, const char *name) +{ + struct priv *priv = NG_NODE_PRIVATE(node); + + if (strcmp(name, "lower") == 0) { + priv->lower = hook; + } else if(strcmp(name, "upper") == 0) { + priv->upper = hook; + NG_HOOK_SET_RCVDATA(hook, ng_uni_rcvupper); + } else + return EINVAL; + + return 0; +} + +static int +ng_uni_disconnect(hook_p hook) +{ + node_p node = NG_HOOK_NODE(hook); + struct priv *priv = NG_NODE_PRIVATE(node); + + if(hook == priv->lower) + priv->lower = NULL; + else if(hook == priv->upper) + priv->upper = NULL; + else + printf("%s: bogus hook %s\n", __func__, NG_HOOK_NAME(hook)); + + if (NG_NODE_NUMHOOKS(node) == 0) { + if (NG_NODE_IS_VALID(node)) + ng_rmnode_self(node); + } + + return (0); +} + +/************************************************************/ +/* + * DATA + */ +/* + * Receive signal from USER. + * + * Repackage the data into one large buffer. + */ +static int +ng_uni_rcvupper(hook_p hook, item_p item) +{ + node_p node = NG_HOOK_NODE(hook); + struct priv *priv = NG_NODE_PRIVATE(node); + struct mbuf *m; + struct uni_arg arg; + struct uni_msg *msg; + int error; + + if (!priv->enabled) { + NG_FREE_ITEM(item); + return (ENOTCONN); + } + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + if ((error = uni_msg_unpack_mbuf(m, &msg)) != 0) { + m_freem(m); + return (error); + } + m_freem(m); + + if (uni_msg_len(msg) < sizeof(arg)) { + printf("%s: packet too short\n", __func__); + uni_msg_destroy(msg); + return (EINVAL); + } + + bcopy(msg->b_rptr, &arg, sizeof(arg)); + msg->b_rptr += sizeof(arg); + + if (arg.sig >= UNIAPI_MAXSIG) { + printf("%s: bogus signal\n", __func__); + uni_msg_destroy(msg); + return (EINVAL); + } + uni_uni_input(priv->uni, arg.sig, arg.cookie, msg); + uni_work(priv->uni); + + return (0); +} + + +/* + * Upper layer signal from UNI + */ +static void +uni_uni_output(struct uni *uni, void *varg, enum uni_sig sig, u_int32_t cookie, + struct uni_msg *msg) +{ + node_p node = (node_p)varg; + struct priv *priv = NG_NODE_PRIVATE(node); + struct mbuf *m; + struct uni_arg arg; + int error; + + if (priv->upper == NULL) { + if (msg != NULL) + uni_msg_destroy(msg); + return; + } + arg.sig = sig; + arg.cookie = cookie; + + m = uni_msg_pack_mbuf(msg, &arg, sizeof(arg)); + if (msg != NULL) + uni_msg_destroy(msg); + if (m == NULL) + return; + + NG_SEND_DATA_ONLY(error, priv->upper, m); +} + + +static void +dump_uni_msg(struct uni_msg *msg) +{ + u_int pos; + + for (pos = 0; pos < uni_msg_len(msg); pos++) { + if (pos % 16 == 0) + printf("%06o ", pos); + if (pos % 16 == 8) + printf(" "); + printf(" %02x", msg->b_rptr[pos]); + if (pos % 16 == 15) + printf("\n"); + } + if (pos % 16 != 0) + printf("\n"); +} + + +/* + * Dump a SAAL signal in either direction + */ +static void +dump_saal_signal(node_p node, enum saal_sig sig, struct uni_msg *msg, int to) +{ + struct priv *priv = NG_NODE_PRIVATE(node); + + printf("signal %s SAAL: ", to ? "to" : "from"); + + switch (sig) { + +#define D(S) case S: printf("%s", #S); break + + D(SAAL_ESTABLISH_request); + D(SAAL_ESTABLISH_indication); + D(SAAL_ESTABLISH_confirm); + D(SAAL_RELEASE_request); + D(SAAL_RELEASE_confirm); + D(SAAL_RELEASE_indication); + D(SAAL_DATA_request); + D(SAAL_DATA_indication); + D(SAAL_UDATA_request); + D(SAAL_UDATA_indication); + +#undef D + default: + printf("sig=%d", sig); break; + } + if (msg != NULL) { + printf(" data=%zu\n", uni_msg_len(msg)); + if (uni_get_debug(priv->uni, UNI_FAC_SAAL) > 1) + dump_uni_msg(msg); + } else + printf("\n"); +} + +/* + * Receive signal from SSCOP. + * + * If this is a data signal, repackage the data into one large buffer. + * UNI shouldn't be the bottleneck in a system and this greatly simplifies + * parsing in UNI. + */ +static int +ng_uni_rcvlower(hook_p hook __unused, item_p item) +{ + node_p node = NG_HOOK_NODE(hook); + struct priv *priv = NG_NODE_PRIVATE(node); + struct mbuf *m; + struct sscfu_arg arg; + struct uni_msg *msg; + int error; + + if (!priv->enabled) { + NG_FREE_ITEM(item); + return (ENOTCONN); + } + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + if ((error = uni_msg_unpack_mbuf(m, &msg)) != 0) { + m_freem(m); + return (error); + } + m_freem(m); + + if (uni_msg_len(msg) < sizeof(arg)) { + uni_msg_destroy(msg); + printf("%s: packet too short\n", __func__); + return (EINVAL); + } + bcopy(msg->b_rptr, &arg, sizeof(arg)); + msg->b_rptr += sizeof(arg); + + if (arg.sig > SAAL_UDATA_indication) { + uni_msg_destroy(msg); + printf("%s: bogus signal\n", __func__); + return (EINVAL); + } + + if (uni_get_debug(priv->uni, UNI_FAC_SAAL) > 0) + dump_saal_signal(node, arg.sig, msg, 0); + + uni_saal_input(priv->uni, arg.sig, msg); + uni_work(priv->uni); + + return (0); +} + +/* + * Send signal to sscop. + * Pack the message into an mbuf chain. + */ +static void +uni_saal_output(struct uni *uni, void *varg, enum saal_sig sig, struct uni_msg *msg) +{ + node_p node = (node_p)varg; + struct priv *priv = NG_NODE_PRIVATE(node); + struct mbuf *m; + struct sscfu_arg arg; + int error; + + if (uni_get_debug(priv->uni, UNI_FAC_SAAL) > 0) + dump_saal_signal(node, sig, msg, 1); + + if (priv->lower == NULL) { + if (msg != NULL) + uni_msg_destroy(msg); + return; + } + + arg.sig = sig; + + m = uni_msg_pack_mbuf(msg, &arg, sizeof(arg)); + if (msg != NULL) + uni_msg_destroy(msg); + if (m == NULL) + return; + + NG_SEND_DATA_ONLY(error, priv->lower, m); +} + +static void +uni_verbose(struct uni *uni, void *varg, u_int fac, const char *fmt, ...) +{ + va_list ap; + + static char *facnames[] = { +#define UNI_DEBUG_DEFINE(D) [UNI_FAC_##D] #D, + UNI_DEBUG_FACILITIES +#undef UNI_DEBUG_DEFINE + }; + + printf("%s: ", facnames[fac]); + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + + printf("\n"); +} + + +/************************************************************/ +/* + * Memory debugging + */ +struct unimem_debug { + const char *file; + u_int lno; + LIST_ENTRY(unimem_debug) link; + char data[0]; +}; +LIST_HEAD(unimem_debug_list, unimem_debug); + +static struct unimem_debug_list nguni_freemem[UNIMEM_TYPES] = { + LIST_HEAD_INITIALIZER(unimem_debug), + LIST_HEAD_INITIALIZER(unimem_debug), + LIST_HEAD_INITIALIZER(unimem_debug), + LIST_HEAD_INITIALIZER(unimem_debug), + LIST_HEAD_INITIALIZER(unimem_debug), +}; +static struct unimem_debug_list nguni_usedmem[UNIMEM_TYPES] = { + LIST_HEAD_INITIALIZER(unimem_debug), + LIST_HEAD_INITIALIZER(unimem_debug), + LIST_HEAD_INITIALIZER(unimem_debug), + LIST_HEAD_INITIALIZER(unimem_debug), + LIST_HEAD_INITIALIZER(unimem_debug), +}; + +static struct mtx nguni_unilist_mtx; + +static const char *unimem_names[UNIMEM_TYPES] = { + "instance", + "all", + "signal", + "call", + "party" +}; + +static void +uni_init(void) +{ + mtx_init(&nguni_unilist_mtx, "netgraph UNI structure lists", NULL, + MTX_DEF); +} + +static void +uni_fini(void) +{ + u_int type; + struct unimem_debug *h; + + for (type = 0; type < UNIMEM_TYPES; type++) { + while ((h = LIST_FIRST(&nguni_freemem[type])) != NULL) { + LIST_REMOVE(h, link); + free(h, M_UNI); + } + + while ((h = LIST_FIRST(&nguni_usedmem[type])) != NULL) { + LIST_REMOVE(h, link); + printf("ng_uni: %s in use: %p (%s,%u)\n", + unimem_names[type], (caddr_t)h->data, + h->file, h->lno); + free(h, M_UNI); + } + } + + mtx_destroy(&nguni_unilist_mtx); +} + +/* + * Allocate a chunk of memory from a given type. + */ +void * +ng_uni_malloc(enum unimem type, const char *file, u_int lno) +{ + struct unimem_debug *d; + size_t full; + + /* + * Try to allocate + */ + mtx_lock(&nguni_unilist_mtx); + if ((d = LIST_FIRST(&nguni_freemem[type])) != NULL) + LIST_REMOVE(d, link); + mtx_unlock(&nguni_unilist_mtx); + + if (d == NULL) { + /* + * allocate + */ + full = unimem_sizes[type] + offsetof(struct unimem_debug, data); + if ((d = malloc(full, M_UNI, M_NOWAIT | M_ZERO)) == NULL) + return (NULL); + } else { + bzero(d->data, unimem_sizes[type]); + } + d->file = file; + d->lno = lno; + + mtx_lock(&nguni_unilist_mtx); + LIST_INSERT_HEAD(&nguni_usedmem[type], d, link); + mtx_unlock(&nguni_unilist_mtx); + return (d->data); +} + +void +ng_uni_free(enum unimem type, void *ptr, const char *file, u_int lno) +{ + struct unimem_debug *d, *h; + + d = (struct unimem_debug *) + ((char *)ptr - offsetof(struct unimem_debug, data)); + + mtx_lock(&nguni_unilist_mtx); + + LIST_FOREACH(h, &nguni_usedmem[type], link) + if (d == h) + break; + + if (h != NULL) { + LIST_REMOVE(d, link); + LIST_INSERT_HEAD(&nguni_freemem[type], d, link); + } else { + /* + * Not on used list - try free list. + */ + LIST_FOREACH(h, &nguni_freemem[type], link) + if (d == h) + break; + if (h == NULL) + printf("ng_uni: %s,%u: %p(%s) was never allocated\n", + file, lno, ptr, unimem_names[type]); + else + printf("ng_uni: %s,%u: %p(%s) was already destroyed " + "in %s,%u\n", + file, lno, ptr, unimem_names[type], + h->file, h->lno); + } + mtx_unlock(&nguni_unilist_mtx); +} +/************************************************************/ +/* + * INITIALISATION + */ + +/* + * Loading and unloading of node type + */ +static int +ng_uni_mod_event(module_t mod, int event, void *data) +{ + int s; + int error = 0; + + s = splnet(); + switch(event) { + + case MOD_LOAD: + uni_init(); + break; + + case MOD_UNLOAD: + uni_fini(); + break; + + default: + error = EOPNOTSUPP; + break; + } + splx(s); + return (error); +} diff --git a/sys/netgraph7/atm/uni/ng_uni_cust.h b/sys/netgraph7/atm/uni/ng_uni_cust.h new file mode 100644 index 0000000000..603c419bf7 --- /dev/null +++ b/sys/netgraph7/atm/uni/ng_uni_cust.h @@ -0,0 +1,150 @@ +/*- + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Hartmut Brandt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Customisation of signalling source to the NG environment. + * + * $FreeBSD: src/sys/netgraph/atm/uni/ng_uni_cust.h,v 1.6 2006/06/02 09:08:51 dds Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ASSERT(E, M) KASSERT(E,M) + +/* + * Memory + */ +enum unimem { + UNIMEM_INS = 0, + UNIMEM_ALL, + UNIMEM_SIG, + UNIMEM_CALL, + UNIMEM_PARTY, +}; +#define UNIMEM_TYPES 5 + +void *ng_uni_malloc(enum unimem, const char *, u_int); +void ng_uni_free(enum unimem, void *, const char *, u_int); + +#define INS_ALLOC() ng_uni_malloc(UNIMEM_INS, __FILE__, __LINE__) +#define INS_FREE(P) ng_uni_free(UNIMEM_INS, P, __FILE__, __LINE__) + +#define UNI_ALLOC() ng_uni_malloc(UNIMEM_ALL, __FILE__, __LINE__) +#define UNI_FREE(P) ng_uni_free(UNIMEM_ALL, P, __FILE__, __LINE__) + +#define SIG_ALLOC() ng_uni_malloc(UNIMEM_SIG, __FILE__, __LINE__) +#define SIG_FREE(P) ng_uni_free(UNIMEM_SIG, P, __FILE__, __LINE__) + +#define CALL_ALLOC() ng_uni_malloc(UNIMEM_CALL, __FILE__, __LINE__) +#define CALL_FREE(P) ng_uni_free(UNIMEM_CALL, P, __FILE__, __LINE__) + +#define PARTY_ALLOC() ng_uni_malloc(UNIMEM_PARTY, __FILE__, __LINE__) +#define PARTY_FREE(P) ng_uni_free(UNIMEM_PARTY, P, __FILE__, __LINE__) + +/* + * Timers + */ +struct uni_timer { + struct callout c; +}; + +#define _TIMER_INIT(X,T) ng_callout_init(&(X)->T.c) +#define _TIMER_DESTROY(UNI,FIELD) _TIMER_STOP(UNI,FIELD) +#define _TIMER_STOP(UNI,FIELD) do { \ + ng_uncallout(&FIELD.c, (UNI)->arg); \ + } while (0) +#define TIMER_ISACT(UNI,T) ((UNI)->T.c.c_flags & (CALLOUT_ACTIVE | \ + CALLOUT_PENDING)) +#define _TIMER_START(UNI,ARG,FIELD,DUE,FUNC) do { \ + _TIMER_STOP(UNI, FIELD); \ + ng_callout(&FIELD.c, (UNI)->arg, NULL, \ + hz * (DUE) / 1000, FUNC, (ARG), 0); \ + } while (0) + +#define TIMER_FUNC_UNI(T,F) \ +static void F(struct uni *); \ +static void \ +_##T##_func(node_p node, hook_p hook, void *arg1, int arg2) \ +{ \ + struct uni *uni = (struct uni *)arg1; \ + \ + (F)(uni); \ + uni_work(uni); \ +} + +/* + * Be careful: call may be invalid after the call to F + */ +#define TIMER_FUNC_CALL(T,F) \ +static void F(struct call *); \ +static void \ +_##T##_func(node_p node, hook_p hook, void *arg1, int arg2) \ +{ \ + struct call *call = (struct call *)arg1; \ + struct uni *uni = call->uni; \ + \ + (F)(call); \ + uni_work(uni); \ +} + +/* + * Be careful: call/party may be invalid after the call to F + */ +#define TIMER_FUNC_PARTY(T,F) \ +static void F(struct party *); \ +static void \ +_##T##_func(node_p node, hook_p hook, void *arg1, int arg2) \ +{ \ + struct party *party = (struct party *)arg1; \ + struct uni *uni = party->call->uni; \ + \ + (F)(party); \ + uni_work(uni); \ +} + +extern size_t unimem_sizes[UNIMEM_TYPES]; + +#define UNICORE \ +size_t unimem_sizes[UNIMEM_TYPES] = { \ + [UNIMEM_INS] = sizeof(struct uni), \ + [UNIMEM_ALL] = sizeof(struct uni_all), \ + [UNIMEM_SIG] = sizeof(struct sig), \ + [UNIMEM_CALL] = sizeof(struct call), \ + [UNIMEM_PARTY] = sizeof(struct party) \ +}; + +#define memmove(T, F, L) bcopy((F), (T), (L)) diff --git a/sys/netgraph7/bluetooth/common/ng_bluetooth.c b/sys/netgraph7/bluetooth/common/ng_bluetooth.c new file mode 100644 index 0000000000..aa1bad379a --- /dev/null +++ b/sys/netgraph7/bluetooth/common/ng_bluetooth.c @@ -0,0 +1,253 @@ +/* + * bluetooth.c + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_bluetooth.c,v 1.3 2003/04/26 22:37:31 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/common/ng_bluetooth.c,v 1.7 2007/06/04 18:25:07 dwmalone Exp $ + */ + +#include +#include +#include +#include +#include +#include + +#include + +/* + * Bluetooth stack sysctl globals + */ + +static u_int32_t bluetooth_hci_command_timeout_value = 5; /* sec */ +static u_int32_t bluetooth_hci_connect_timeout_value = 60; /* sec */ +static u_int32_t bluetooth_hci_max_neighbor_age_value = 600; /* sec */ +static u_int32_t bluetooth_l2cap_rtx_timeout_value = 60; /* sec */ +static u_int32_t bluetooth_l2cap_ertx_timeout_value = 300; /* sec */ + +/* + * Define sysctl tree that shared by other parts of Bluetooth stack + */ + +SYSCTL_NODE(_net, OID_AUTO, bluetooth, CTLFLAG_RW, 0, "Bluetooth family"); +SYSCTL_INT(_net_bluetooth, OID_AUTO, version, + CTLFLAG_RD, 0, NG_BLUETOOTH_VERSION, ""); + +/* + * HCI + */ + +SYSCTL_NODE(_net_bluetooth, OID_AUTO, hci, CTLFLAG_RW, + 0, "Bluetooth HCI family"); + +static int +bluetooth_set_hci_command_timeout_value(SYSCTL_HANDLER_ARGS) +{ + u_int32_t value; + int error; + + value = bluetooth_hci_command_timeout_value; + error = sysctl_handle_int(oidp, &value, 0, req); + if (error == 0 && req->newptr != NULL) { + if (value > 0) + bluetooth_hci_command_timeout_value = value; + else + error = EINVAL; + } + + return (error); +} /* bluetooth_set_hci_command_timeout_value */ + +SYSCTL_PROC(_net_bluetooth_hci, OID_AUTO, command_timeout, + CTLTYPE_INT | CTLFLAG_RW, + &bluetooth_hci_command_timeout_value, 5, + bluetooth_set_hci_command_timeout_value, + "I", "HCI command timeout (sec)"); + +static int +bluetooth_set_hci_connect_timeout_value(SYSCTL_HANDLER_ARGS) +{ + u_int32_t value; + int error; + + value = bluetooth_hci_connect_timeout_value; + error = sysctl_handle_int(oidp, &value, 0, req); + if (error == 0 && req->newptr != NULL) { + if (0 < value && value <= bluetooth_l2cap_rtx_timeout_value) + bluetooth_hci_connect_timeout_value = value; + else + error = EINVAL; + } + + return (error); +} /* bluetooth_set_hci_connect_timeout_value */ + +SYSCTL_PROC(_net_bluetooth_hci, OID_AUTO, connection_timeout, + CTLTYPE_INT | CTLFLAG_RW, + &bluetooth_hci_connect_timeout_value, 60, + bluetooth_set_hci_connect_timeout_value, + "I", "HCI connect timeout (sec)"); + +SYSCTL_INT(_net_bluetooth_hci, OID_AUTO, max_neighbor_age, CTLFLAG_RW, + &bluetooth_hci_max_neighbor_age_value, 600, + "Maximal HCI neighbor cache entry age (sec)"); + +/* + * L2CAP + */ + +SYSCTL_NODE(_net_bluetooth, OID_AUTO, l2cap, CTLFLAG_RW, + 0, "Bluetooth L2CAP family"); + +static int +bluetooth_set_l2cap_rtx_timeout_value(SYSCTL_HANDLER_ARGS) +{ + u_int32_t value; + int error; + + value = bluetooth_l2cap_rtx_timeout_value; + error = sysctl_handle_int(oidp, &value, 0, req); + if (error == 0 && req->newptr != NULL) { + if (bluetooth_hci_connect_timeout_value <= value && + value <= bluetooth_l2cap_ertx_timeout_value) + bluetooth_l2cap_rtx_timeout_value = value; + else + error = EINVAL; + } + + return (error); +} /* bluetooth_set_l2cap_rtx_timeout_value */ + +SYSCTL_PROC(_net_bluetooth_l2cap, OID_AUTO, rtx_timeout, + CTLTYPE_INT | CTLFLAG_RW, + &bluetooth_l2cap_rtx_timeout_value, 60, + bluetooth_set_l2cap_rtx_timeout_value, + "I", "L2CAP RTX timeout (sec)"); + +static int +bluetooth_set_l2cap_ertx_timeout_value(SYSCTL_HANDLER_ARGS) +{ + u_int32_t value; + int error; + + value = bluetooth_l2cap_ertx_timeout_value; + error = sysctl_handle_int(oidp, &value, 0, req); + if (error == 0 && req->newptr != NULL) { + if (value >= bluetooth_l2cap_rtx_timeout_value) + bluetooth_l2cap_ertx_timeout_value = value; + else + error = EINVAL; + } + + return (error); +} /* bluetooth_set_l2cap_ertx_timeout_value */ + +SYSCTL_PROC(_net_bluetooth_l2cap, OID_AUTO, ertx_timeout, + CTLTYPE_INT | CTLFLAG_RW, + &bluetooth_l2cap_ertx_timeout_value, 300, + bluetooth_set_l2cap_ertx_timeout_value, + "I", "L2CAP ERTX timeout (sec)"); + +/* + * Return various sysctl values + */ + +u_int32_t +bluetooth_hci_command_timeout(void) +{ + return (bluetooth_hci_command_timeout_value * hz); +} /* bluetooth_hci_command_timeout */ + +u_int32_t +bluetooth_hci_connect_timeout(void) +{ + return (bluetooth_hci_connect_timeout_value * hz); +} /* bluetooth_hci_connect_timeout */ + +u_int32_t +bluetooth_hci_max_neighbor_age(void) +{ + return (bluetooth_hci_max_neighbor_age_value); +} /* bluetooth_hci_max_neighbor_age */ + +u_int32_t +bluetooth_l2cap_rtx_timeout(void) +{ + return (bluetooth_l2cap_rtx_timeout_value * hz); +} /* bluetooth_l2cap_rtx_timeout */ + +u_int32_t +bluetooth_l2cap_ertx_timeout(void) +{ + return (bluetooth_l2cap_ertx_timeout_value * hz); +} /* bluetooth_l2cap_ertx_timeout */ + +/* + * RFCOMM + */ + +SYSCTL_NODE(_net_bluetooth, OID_AUTO, rfcomm, CTLFLAG_RW, + 0, "Bluetooth RFCOMM family"); + +/* + * Handle loading and unloading for this code. + */ + +static int +bluetooth_modevent(module_t mod, int event, void *data) +{ + int error = 0; + + switch (event) { + case MOD_LOAD: + break; + + case MOD_UNLOAD: + break; + + default: + error = EOPNOTSUPP; + break; + } + + return (error); +} /* bluetooth_modevent */ + +/* + * Module + */ + +static moduledata_t bluetooth_mod = { + "ng_bluetooth", + bluetooth_modevent, + NULL +}; + +DECLARE_MODULE(ng_bluetooth, bluetooth_mod, SI_SUB_PSEUDO, SI_ORDER_ANY); +MODULE_VERSION(ng_bluetooth, NG_BLUETOOTH_VERSION); + diff --git a/sys/netgraph7/bluetooth/drivers/bt3c/ng_bt3c_pccard.c b/sys/netgraph7/bluetooth/drivers/bt3c/ng_bt3c_pccard.c new file mode 100644 index 0000000000..a9b971661f --- /dev/null +++ b/sys/netgraph7/bluetooth/drivers/bt3c/ng_bt3c_pccard.c @@ -0,0 +1,1228 @@ +/* + * ng_bt3c_pccard.c + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_bt3c_pccard.c,v 1.5 2003/04/01 18:15:21 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/drivers/bt3c/ng_bt3c_pccard.c,v 1.20 2007/02/23 12:19:02 piso Exp $ + * + * XXX XXX XX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX + * + * Based on information obrained from: Jose Orlando Pereira + * and disassembled w2k driver. + * + * XXX XXX XX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX + * + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include "pccarddevs.h" + +#include +#include +#include +#include +#include +#include +#include + +/* Netgraph methods */ +static ng_constructor_t ng_bt3c_constructor; +static ng_shutdown_t ng_bt3c_shutdown; +static ng_newhook_t ng_bt3c_newhook; +static ng_connect_t ng_bt3c_connect; +static ng_disconnect_t ng_bt3c_disconnect; +static ng_rcvmsg_t ng_bt3c_rcvmsg; +static ng_rcvdata_t ng_bt3c_rcvdata; + +/* PCMCIA driver methods */ +static int bt3c_pccard_probe (device_t); +static int bt3c_pccard_attach (device_t); +static int bt3c_pccard_detach (device_t); + +static void bt3c_intr (void *); +static void bt3c_receive (bt3c_softc_p); + +static void bt3c_swi_intr (void *); +static void bt3c_forward (node_p, hook_p, void *, int); +static void bt3c_send (node_p, hook_p, void *, int); + +static void bt3c_download_firmware (bt3c_softc_p, char const *, int); + +#define bt3c_set_address(sc, address) \ +do { \ + bus_space_write_1((sc)->iot, (sc)->ioh, BT3C_ADDR_L, ((address) & 0xff)); \ + bus_space_write_1((sc)->iot, (sc)->ioh, BT3C_ADDR_H, (((address) >> 8) & 0xff)); \ +} while (0) + +#define bt3c_read_data(sc, data) \ +do { \ + (data) = bus_space_read_1((sc)->iot, (sc)->ioh, BT3C_DATA_L); \ + (data) |= ((bus_space_read_1((sc)->iot, (sc)->ioh, BT3C_DATA_H) & 0xff) << 8); \ +} while (0) + +#define bt3c_write_data(sc, data) \ +do { \ + bus_space_write_1((sc)->iot, (sc)->ioh, BT3C_DATA_L, ((data) & 0xff)); \ + bus_space_write_1((sc)->iot, (sc)->ioh, BT3C_DATA_H, (((data) >> 8) & 0xff)); \ +} while (0) + +#define bt3c_read_control(sc, data) \ +do { \ + (data) = bus_space_read_1((sc)->iot, (sc)->ioh, BT3C_CONTROL); \ +} while (0) + +#define bt3c_write_control(sc, data) \ +do { \ + bus_space_write_1((sc)->iot, (sc)->ioh, BT3C_CONTROL, (data)); \ +} while (0) + +#define bt3c_read(sc, address, data) \ +do { \ + bt3c_set_address((sc), (address)); \ + bt3c_read_data((sc), (data)); \ +} while(0) + +#define bt3c_write(sc, address, data) \ +do { \ + bt3c_set_address((sc), (address)); \ + bt3c_write_data((sc), (data)); \ +} while(0) + +static MALLOC_DEFINE(M_BT3C, "bt3c", "bt3c data structures"); + +/**************************************************************************** + **************************************************************************** + ** Netgraph specific + **************************************************************************** + ****************************************************************************/ + +/* + * Netgraph node type + */ + +/* Queue length */ +static const struct ng_parse_struct_field ng_bt3c_node_qlen_type_fields[] = +{ + { "queue", &ng_parse_int32_type, }, + { "qlen", &ng_parse_int32_type, }, + { NULL, } +}; +static const struct ng_parse_type ng_bt3c_node_qlen_type = { + &ng_parse_struct_type, + &ng_bt3c_node_qlen_type_fields +}; + +/* Stat info */ +static const struct ng_parse_struct_field ng_bt3c_node_stat_type_fields[] = +{ + { "pckts_recv", &ng_parse_uint32_type, }, + { "bytes_recv", &ng_parse_uint32_type, }, + { "pckts_sent", &ng_parse_uint32_type, }, + { "bytes_sent", &ng_parse_uint32_type, }, + { "oerrors", &ng_parse_uint32_type, }, + { "ierrors", &ng_parse_uint32_type, }, + { NULL, } +}; +static const struct ng_parse_type ng_bt3c_node_stat_type = { + &ng_parse_struct_type, + &ng_bt3c_node_stat_type_fields +}; + +static const struct ng_cmdlist ng_bt3c_cmdlist[] = { +{ + NGM_BT3C_COOKIE, + NGM_BT3C_NODE_GET_STATE, + "get_state", + NULL, + &ng_parse_uint16_type +}, +{ + NGM_BT3C_COOKIE, + NGM_BT3C_NODE_SET_DEBUG, + "set_debug", + &ng_parse_uint16_type, + NULL +}, +{ + NGM_BT3C_COOKIE, + NGM_BT3C_NODE_GET_DEBUG, + "get_debug", + NULL, + &ng_parse_uint16_type +}, +{ + NGM_BT3C_COOKIE, + NGM_BT3C_NODE_GET_QLEN, + "get_qlen", + NULL, + &ng_bt3c_node_qlen_type +}, +{ + NGM_BT3C_COOKIE, + NGM_BT3C_NODE_SET_QLEN, + "set_qlen", + &ng_bt3c_node_qlen_type, + NULL +}, +{ + NGM_BT3C_COOKIE, + NGM_BT3C_NODE_GET_STAT, + "get_stat", + NULL, + &ng_bt3c_node_stat_type +}, +{ + NGM_BT3C_COOKIE, + NGM_BT3C_NODE_RESET_STAT, + "reset_stat", + NULL, + NULL +}, +{ 0, } +}; + +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_BT3C_NODE_TYPE, + .constructor = ng_bt3c_constructor, + .rcvmsg = ng_bt3c_rcvmsg, + .shutdown = ng_bt3c_shutdown, + .newhook = ng_bt3c_newhook, + .connect = ng_bt3c_connect, + .rcvdata = ng_bt3c_rcvdata, + .disconnect = ng_bt3c_disconnect, + .cmdlist = ng_bt3c_cmdlist +}; + +/* + * Netgraph node constructor. Do not allow to create node of this type. + */ + +static int +ng_bt3c_constructor(node_p node) +{ + return (EINVAL); +} /* ng_bt3c_constructor */ + +/* + * Netgraph node destructor. Destroy node only when device has been detached + */ + +static int +ng_bt3c_shutdown(node_p node) +{ + bt3c_softc_p sc = (bt3c_softc_p) NG_NODE_PRIVATE(node); + + /* Let old node go */ + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + + /* Create new fresh one if we are not going down */ + if (sc == NULL) + goto out; + + /* Create new Netgraph node */ + if (ng_make_node_common(&typestruct, &sc->node) != 0) { + device_printf(sc->dev, "Could not create Netgraph node\n"); + sc->node = NULL; + goto out; + } + + /* Name new Netgraph node */ + if (ng_name_node(sc->node, device_get_nameunit(sc->dev)) != 0) { + device_printf(sc->dev, "Could not name Netgraph node\n"); + NG_NODE_UNREF(sc->node); + sc->node = NULL; + goto out; + } + + NG_NODE_SET_PRIVATE(sc->node, sc); +out: + return (0); +} /* ng_bt3c_shutdown */ + +/* + * Create new hook. There can only be one. + */ + +static int +ng_bt3c_newhook(node_p node, hook_p hook, char const *name) +{ + bt3c_softc_p sc = (bt3c_softc_p) NG_NODE_PRIVATE(node); + + if (strcmp(name, NG_BT3C_HOOK) != 0) + return (EINVAL); + + if (sc->hook != NULL) + return (EISCONN); + + sc->hook = hook; + + return (0); +} /* ng_bt3c_newhook */ + +/* + * Connect hook. Say YEP, that's OK with me. + */ + +static int +ng_bt3c_connect(hook_p hook) +{ + bt3c_softc_p sc = (bt3c_softc_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + if (hook != sc->hook) { + sc->hook = NULL; + return (EINVAL); + } + + /* set the hook into queueing mode (for incoming (from wire) packets) */ + NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); + + return (0); +} /* ng_bt3c_connect */ + +/* + * Disconnect hook + */ + +static int +ng_bt3c_disconnect(hook_p hook) +{ + bt3c_softc_p sc = (bt3c_softc_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + /* + * We need to check for sc != NULL because we can be called from + * bt3c_pccard_detach() via ng_rmnode_self() + */ + + if (sc != NULL) { + if (hook != sc->hook) + return (EINVAL); + + IF_DRAIN(&sc->inq); + IF_DRAIN(&sc->outq); + + sc->hook = NULL; + } + + return (0); +} /* ng_bt3c_disconnect */ + +/* + * Process control message + */ + +static int +ng_bt3c_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + bt3c_softc_p sc = (bt3c_softc_p) NG_NODE_PRIVATE(node); + struct ng_mesg *msg = NULL, *rsp = NULL; + int error = 0; + + if (sc == NULL) { + NG_FREE_ITEM(item); + return (EHOSTDOWN); + } + + NGI_GET_MSG(item, msg); + + switch (msg->header.typecookie) { + case NGM_GENERIC_COOKIE: + switch (msg->header.cmd) { + case NGM_TEXT_STATUS: + NG_MKRESPONSE(rsp, msg, NG_TEXTRESPONSE, M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + snprintf(rsp->data, NG_TEXTRESPONSE, + "Hook: %s\n" \ + "Flags: %#x\n" \ + "Debug: %d\n" \ + "State: %d\n" \ + "IncmQ: [len:%d,max:%d]\n" \ + "OutgQ: [len:%d,max:%d]\n", + (sc->hook != NULL)? NG_BT3C_HOOK : "", + sc->flags, + sc->debug, + sc->state, + _IF_QLEN(&sc->inq), /* XXX */ + sc->inq.ifq_maxlen, /* XXX */ + _IF_QLEN(&sc->outq), /* XXX */ + sc->outq.ifq_maxlen /* XXX */ + ); + break; + + default: + error = EINVAL; + break; + } + break; + + case NGM_BT3C_COOKIE: + switch (msg->header.cmd) { + case NGM_BT3C_NODE_GET_STATE: + NG_MKRESPONSE(rsp, msg, sizeof(ng_bt3c_node_state_ep), + M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + *((ng_bt3c_node_state_ep *)(rsp->data)) = + sc->state; + break; + + case NGM_BT3C_NODE_SET_DEBUG: + if (msg->header.arglen != sizeof(ng_bt3c_node_debug_ep)) + error = EMSGSIZE; + else + sc->debug = + *((ng_bt3c_node_debug_ep *)(msg->data)); + break; + + case NGM_BT3C_NODE_GET_DEBUG: + NG_MKRESPONSE(rsp, msg, sizeof(ng_bt3c_node_debug_ep), + M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + *((ng_bt3c_node_debug_ep *)(rsp->data)) = + sc->debug; + break; + + case NGM_BT3C_NODE_GET_QLEN: + NG_MKRESPONSE(rsp, msg, sizeof(ng_bt3c_node_qlen_ep), + M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + switch (((ng_bt3c_node_qlen_ep *)(msg->data))->queue) { + case NGM_BT3C_NODE_IN_QUEUE: + ((ng_bt3c_node_qlen_ep *)(rsp->data))->queue = + NGM_BT3C_NODE_IN_QUEUE; + ((ng_bt3c_node_qlen_ep *)(rsp->data))->qlen = + sc->inq.ifq_maxlen; + break; + + case NGM_BT3C_NODE_OUT_QUEUE: + ((ng_bt3c_node_qlen_ep *)(rsp->data))->queue = + NGM_BT3C_NODE_OUT_QUEUE; + ((ng_bt3c_node_qlen_ep *)(rsp->data))->qlen = + sc->outq.ifq_maxlen; + break; + + default: + NG_FREE_MSG(rsp); + error = EINVAL; + break; + } + break; + + case NGM_BT3C_NODE_SET_QLEN: + if (msg->header.arglen != sizeof(ng_bt3c_node_qlen_ep)){ + error = EMSGSIZE; + break; + } + + if (((ng_bt3c_node_qlen_ep *)(msg->data))->qlen <= 0) { + error = EINVAL; + break; + } + + switch (((ng_bt3c_node_qlen_ep *)(msg->data))->queue) { + case NGM_BT3C_NODE_IN_QUEUE: + sc->inq.ifq_maxlen = ((ng_bt3c_node_qlen_ep *) + (msg->data))->qlen; /* XXX */ + break; + + case NGM_BT3C_NODE_OUT_QUEUE: + sc->outq.ifq_maxlen = ((ng_bt3c_node_qlen_ep *) + (msg->data))->qlen; /* XXX */ + break; + + default: + error = EINVAL; + break; + } + break; + + case NGM_BT3C_NODE_GET_STAT: + NG_MKRESPONSE(rsp, msg, sizeof(ng_bt3c_node_stat_ep), + M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + bcopy(&sc->stat, rsp->data, + sizeof(ng_bt3c_node_stat_ep)); + break; + + case NGM_BT3C_NODE_RESET_STAT: + NG_BT3C_STAT_RESET(sc->stat); + break; + + case NGM_BT3C_NODE_DOWNLOAD_FIRMWARE: + if (msg->header.arglen < + sizeof(ng_bt3c_firmware_block_ep)) + error = EMSGSIZE; + else + bt3c_download_firmware(sc, msg->data, + msg->header.arglen); + break; + + default: + error = EINVAL; + break; + } + break; + + default: + error = EINVAL; + break; + } + + NG_RESPOND_MSG(error, node, item, rsp); + NG_FREE_MSG(msg); + + return (error); +} /* ng_bt3c_rcvmsg */ + +/* + * Process data + */ + +static int +ng_bt3c_rcvdata(hook_p hook, item_p item) +{ + bt3c_softc_p sc = (bt3c_softc_p)NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct mbuf *m = NULL; + int error = 0; + + if (sc == NULL) { + error = EHOSTDOWN; + goto out; + } + + if (hook != sc->hook) { + error = EINVAL; + goto out; + } + + NGI_GET_M(item, m); + + IF_LOCK(&sc->outq); + if (_IF_QFULL(&sc->outq)) { + NG_BT3C_ERR(sc->dev, +"Outgoing queue is full. Dropping mbuf, len=%d\n", m->m_pkthdr.len); + + _IF_DROP(&sc->outq); + NG_BT3C_STAT_OERROR(sc->stat); + + NG_FREE_M(m); + } else + _IF_ENQUEUE(&sc->outq, m); + IF_UNLOCK(&sc->outq); + + error = ng_send_fn(sc->node, NULL, bt3c_send, NULL, 0 /* new send */); +out: + NG_FREE_ITEM(item); + + return (error); +} /* ng_bt3c_rcvdata */ + +/**************************************************************************** + **************************************************************************** + ** PCMCIA driver specific + **************************************************************************** + ****************************************************************************/ + +/* + * PC Card (PCMCIA) probe routine + */ + +static int +bt3c_pccard_probe(device_t dev) +{ + static struct pccard_product const bt3c_pccard_products[] = { + PCMCIA_CARD(3COM, 3CRWB609), + { NULL, } + }; + + struct pccard_product const *pp = NULL; + + pp = pccard_product_lookup(dev, bt3c_pccard_products, + sizeof(bt3c_pccard_products[0]), NULL); + if (pp == NULL) + return (ENXIO); + + device_set_desc(dev, pp->pp_name); + + return (0); +} /* bt3c_pccard_probe */ + +/* + * PC Card (PCMCIA) attach routine + */ + +static int +bt3c_pccard_attach(device_t dev) +{ + bt3c_softc_p sc = (bt3c_softc_p) device_get_softc(dev); + + /* Allocate I/O ports */ + sc->iobase_rid = 0; + sc->iobase = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->iobase_rid, + 0, ~0, 8, RF_ACTIVE); + if (sc->iobase == NULL) { + device_printf(dev, "Could not allocate I/O ports\n"); + goto bad; + } + sc->iot = rman_get_bustag(sc->iobase); + sc->ioh = rman_get_bushandle(sc->iobase); + + /* Allocate IRQ */ + sc->irq_rid = 0; + sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irq_rid, + RF_ACTIVE); + if (sc->irq == NULL) { + device_printf(dev, "Could not allocate IRQ\n"); + goto bad; + } + + sc->irq_cookie = NULL; + if (bus_setup_intr(dev, sc->irq, INTR_TYPE_TTY, NULL, bt3c_intr, sc, + &sc->irq_cookie) != 0) { + device_printf(dev, "Could not setup ISR\n"); + goto bad; + } + + /* Attach handler to TTY SWI thread */ + sc->ith = NULL; + if (swi_add(&tty_intr_event, device_get_nameunit(dev), + bt3c_swi_intr, sc, SWI_TTY, 0, &sc->ith) < 0) { + device_printf(dev, "Could not setup SWI ISR\n"); + goto bad; + } + + /* Create Netgraph node */ + if (ng_make_node_common(&typestruct, &sc->node) != 0) { + device_printf(dev, "Could not create Netgraph node\n"); + sc->node = NULL; + goto bad; + } + + /* Name Netgraph node */ + if (ng_name_node(sc->node, device_get_nameunit(dev)) != 0) { + device_printf(dev, "Could not name Netgraph node\n"); + NG_NODE_UNREF(sc->node); + sc->node = NULL; + goto bad; + } + + sc->dev = dev; + sc->debug = NG_BT3C_WARN_LEVEL; + + sc->inq.ifq_maxlen = sc->outq.ifq_maxlen = BT3C_DEFAULTQLEN; + mtx_init(&sc->inq.ifq_mtx, "BT3C inq", NULL, MTX_DEF); + mtx_init(&sc->outq.ifq_mtx, "BT3C outq", NULL, MTX_DEF); + + sc->state = NG_BT3C_W4_PKT_IND; + sc->want = 1; + + NG_NODE_SET_PRIVATE(sc->node, sc); + + return (0); +bad: + if (sc->ith != NULL) { + swi_remove(sc->ith); + sc->ith = NULL; + } + + if (sc->irq != NULL) { + if (sc->irq_cookie != NULL) + bus_teardown_intr(dev, sc->irq, sc->irq_cookie); + + bus_release_resource(dev, SYS_RES_IRQ, + sc->irq_rid, sc->irq); + + sc->irq = NULL; + sc->irq_rid = 0; + } + + if (sc->iobase != NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, + sc->iobase_rid, sc->iobase); + + sc->iobase = NULL; + sc->iobase_rid = 0; + } + + return (ENXIO); +} /* bt3c_pccacd_attach */ + +/* + * PC Card (PCMCIA) detach routine + */ + +static int +bt3c_pccard_detach(device_t dev) +{ + bt3c_softc_p sc = (bt3c_softc_p) device_get_softc(dev); + + if (sc == NULL) + return (0); + + swi_remove(sc->ith); + sc->ith = NULL; + + bus_teardown_intr(dev, sc->irq, sc->irq_cookie); + bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq); + sc->irq_cookie = NULL; + sc->irq = NULL; + sc->irq_rid = 0; + + bus_release_resource(dev, SYS_RES_IOPORT, sc->iobase_rid, sc->iobase); + sc->iobase = NULL; + sc->iobase_rid = 0; + + if (sc->node != NULL) { + NG_NODE_SET_PRIVATE(sc->node, NULL); + ng_rmnode_self(sc->node); + sc->node = NULL; + } + + NG_FREE_M(sc->m); + IF_DRAIN(&sc->inq); + IF_DRAIN(&sc->outq); + + mtx_destroy(&sc->inq.ifq_mtx); + mtx_destroy(&sc->outq.ifq_mtx); + + return (0); +} /* bt3c_pccacd_detach */ + +/* + * Interrupt service routine's + */ + +static void +bt3c_intr(void *context) +{ + bt3c_softc_p sc = (bt3c_softc_p) context; + u_int16_t control, status; + + if (sc == NULL || sc->ith == NULL) { + printf("%s: bogus interrupt\n", NG_BT3C_NODE_TYPE); + return; + } + + bt3c_read_control(sc, control); + if ((control & 0x80) == 0) + return; + + bt3c_read(sc, 0x7001, status); + NG_BT3C_INFO(sc->dev, "control=%#x, status=%#x\n", control, status); + + if ((status & 0xff) == 0x7f || (status & 0xff) == 0xff) { + NG_BT3C_WARN(sc->dev, "Strange status=%#x\n", status); + return; + } + + /* Receive complete */ + if (status & 0x0001) + bt3c_receive(sc); + + /* Record status and schedule SWI */ + sc->status |= status; + swi_sched(sc->ith, 0); + + /* Complete interrupt */ + bt3c_write(sc, 0x7001, 0x0000); + bt3c_write_control(sc, control); +} /* bt3c_intr */ + +/* + * Receive data + */ + +static void +bt3c_receive(bt3c_softc_p sc) +{ + u_int16_t i, count, c; + + /* Receive data from the card */ + bt3c_read(sc, 0x7006, count); + NG_BT3C_INFO(sc->dev, "The card has %d characters\n", count); + + bt3c_set_address(sc, 0x7480); + + for (i = 0; i < count; i++) { + /* Allocate new mbuf if needed */ + if (sc->m == NULL) { + sc->state = NG_BT3C_W4_PKT_IND; + sc->want = 1; + + MGETHDR(sc->m, M_DONTWAIT, MT_DATA); + if (sc->m == NULL) { + NG_BT3C_ERR(sc->dev, "Could not get mbuf\n"); + NG_BT3C_STAT_IERROR(sc->stat); + + break; /* XXX lost of sync */ + } + + MCLGET(sc->m, M_DONTWAIT); + if (!(sc->m->m_flags & M_EXT)) { + NG_FREE_M(sc->m); + + NG_BT3C_ERR(sc->dev, "Could not get cluster\n"); + NG_BT3C_STAT_IERROR(sc->stat); + + break; /* XXX lost of sync */ + } + + sc->m->m_len = sc->m->m_pkthdr.len = 0; + } + + /* Read and append character to mbuf */ + bt3c_read_data(sc, c); + if (sc->m->m_pkthdr.len >= MCLBYTES) { + NG_BT3C_ERR(sc->dev, "Oversized frame\n"); + + NG_FREE_M(sc->m); + sc->state = NG_BT3C_W4_PKT_IND; + sc->want = 1; + + break; /* XXX lost of sync */ + } + + mtod(sc->m, u_int8_t *)[sc->m->m_len ++] = (u_int8_t) c; + sc->m->m_pkthdr.len ++; + + NG_BT3C_INFO(sc->dev, +"Got char %#x, want=%d, got=%d\n", c, sc->want, sc->m->m_pkthdr.len); + + if (sc->m->m_pkthdr.len < sc->want) + continue; /* wait for more */ + + switch (sc->state) { + /* Got packet indicator */ + case NG_BT3C_W4_PKT_IND: + NG_BT3C_INFO(sc->dev, +"Got packet indicator %#x\n", *mtod(sc->m, u_int8_t *)); + + sc->state = NG_BT3C_W4_PKT_HDR; + + /* + * Since packet indicator included in the packet + * header just set sc->want to sizeof(packet header). + */ + + switch (*mtod(sc->m, u_int8_t *)) { + case NG_HCI_ACL_DATA_PKT: + sc->want = sizeof(ng_hci_acldata_pkt_t); + break; + + case NG_HCI_SCO_DATA_PKT: + sc->want = sizeof(ng_hci_scodata_pkt_t); + break; + + case NG_HCI_EVENT_PKT: + sc->want = sizeof(ng_hci_event_pkt_t); + break; + + default: + NG_BT3C_ERR(sc->dev, +"Ignoring unknown packet type=%#x\n", *mtod(sc->m, u_int8_t *)); + + NG_BT3C_STAT_IERROR(sc->stat); + + NG_FREE_M(sc->m); + sc->state = NG_BT3C_W4_PKT_IND; + sc->want = 1; + break; + } + break; + + /* Got packet header */ + case NG_BT3C_W4_PKT_HDR: + sc->state = NG_BT3C_W4_PKT_DATA; + + switch (*mtod(sc->m, u_int8_t *)) { + case NG_HCI_ACL_DATA_PKT: + c = le16toh(mtod(sc->m, + ng_hci_acldata_pkt_t *)->length); + break; + + case NG_HCI_SCO_DATA_PKT: + c = mtod(sc->m, ng_hci_scodata_pkt_t*)->length; + break; + + case NG_HCI_EVENT_PKT: + c = mtod(sc->m, ng_hci_event_pkt_t *)->length; + break; + + default: + KASSERT(0, +("Invalid packet type=%#x\n", *mtod(sc->m, u_int8_t *))); + break; + } + + NG_BT3C_INFO(sc->dev, +"Got packet header, packet type=%#x, got so far %d, payload size=%d\n", + *mtod(sc->m, u_int8_t *), sc->m->m_pkthdr.len, + c); + + if (c > 0) { + sc->want += c; + break; + } + + /* else FALLTHROUGH and deliver frame */ + /* XXX is this true? should we deliver empty frame? */ + + /* Got packet data */ + case NG_BT3C_W4_PKT_DATA: + NG_BT3C_INFO(sc->dev, +"Got full packet, packet type=%#x, packet size=%d\n", + *mtod(sc->m, u_int8_t *), sc->m->m_pkthdr.len); + + NG_BT3C_STAT_BYTES_RECV(sc->stat, sc->m->m_pkthdr.len); + NG_BT3C_STAT_PCKTS_RECV(sc->stat); + + IF_LOCK(&sc->inq); + if (_IF_QFULL(&sc->inq)) { + NG_BT3C_ERR(sc->dev, +"Incoming queue is full. Dropping mbuf, len=%d\n", sc->m->m_pkthdr.len); + + _IF_DROP(&sc->inq); + NG_BT3C_STAT_IERROR(sc->stat); + + NG_FREE_M(sc->m); + } else { + _IF_ENQUEUE(&sc->inq, sc->m); + sc->m = NULL; + } + IF_UNLOCK(&sc->inq); + + sc->state = NG_BT3C_W4_PKT_IND; + sc->want = 1; + break; + + default: + KASSERT(0, +("Invalid node state=%d", sc->state)); + break; + } + } + + bt3c_write(sc, 0x7006, 0x0000); +} /* bt3c_receive */ + +/* + * SWI interrupt handler + * Netgraph part is handled via ng_send_fn() to avoid race with hook + * connection/disconnection + */ + +static void +bt3c_swi_intr(void *context) +{ + bt3c_softc_p sc = (bt3c_softc_p) context; + u_int16_t data; + + /* Receive complete */ + if (sc->status & 0x0001) { + sc->status &= ~0x0001; /* XXX is it safe? */ + + if (ng_send_fn(sc->node, NULL, &bt3c_forward, NULL, 0) != 0) + NG_BT3C_ALERT(sc->dev, "Could not forward frames!\n"); + } + + /* Send complete */ + if (sc->status & 0x0002) { + sc->status &= ~0x0002; /* XXX is it safe */ + + if (ng_send_fn(sc->node, NULL, &bt3c_send, NULL, 1) != 0) + NG_BT3C_ALERT(sc->dev, "Could not send frames!\n"); + } + + /* Antenna position */ + if (sc->status & 0x0020) { + sc->status &= ~0x0020; /* XXX is it safe */ + + bt3c_read(sc, 0x7002, data); + data &= 0x10; + + if (data) + sc->flags |= BT3C_ANTENNA_OUT; + else + sc->flags &= ~BT3C_ANTENNA_OUT; + + NG_BT3C_INFO(sc->dev, "Antenna %s\n", data? "OUT" : "IN"); + } +} /* bt3c_swi_intr */ + +/* + * Send all incoming frames to the upper layer + */ + +static void +bt3c_forward(node_p node, hook_p hook, void *arg1, int arg2) +{ + bt3c_softc_p sc = (bt3c_softc_p) NG_NODE_PRIVATE(node); + struct mbuf *m = NULL; + int error; + + if (sc == NULL) + return; + + if (sc->hook != NULL && NG_HOOK_IS_VALID(sc->hook)) { + for (;;) { + IF_DEQUEUE(&sc->inq, m); + if (m == NULL) + break; + + NG_SEND_DATA_ONLY(error, sc->hook, m); + if (error != 0) + NG_BT3C_STAT_IERROR(sc->stat); + } + } else { + IF_LOCK(&sc->inq); + for (;;) { + _IF_DEQUEUE(&sc->inq, m); + if (m == NULL) + break; + + NG_BT3C_STAT_IERROR(sc->stat); + NG_FREE_M(m); + } + IF_UNLOCK(&sc->inq); + } +} /* bt3c_forward */ + +/* + * Send more data to the device. Must be called when node is locked + */ + +static void +bt3c_send(node_p node, hook_p hook, void *arg, int completed) +{ + bt3c_softc_p sc = (bt3c_softc_p) NG_NODE_PRIVATE(node); + struct mbuf *m = NULL; + int i, wrote, len; + + if (sc == NULL) + return; + + if (completed) + sc->flags &= ~BT3C_XMIT; + + if (sc->flags & BT3C_XMIT) + return; + + bt3c_set_address(sc, 0x7080); + + for (wrote = 0; wrote < BT3C_FIFO_SIZE; ) { + IF_DEQUEUE(&sc->outq, m); + if (m == NULL) + break; + + while (m != NULL) { + len = min((BT3C_FIFO_SIZE - wrote), m->m_len); + + for (i = 0; i < len; i++) + bt3c_write_data(sc, m->m_data[i]); + + wrote += len; + m->m_data += len; + m->m_len -= len; + + if (m->m_len > 0) + break; + + m = m_free(m); + } + + if (m != NULL) { + IF_PREPEND(&sc->outq, m); + break; + } + + NG_BT3C_STAT_PCKTS_SENT(sc->stat); + } + + if (wrote > 0) { + NG_BT3C_INFO(sc->dev, "Wrote %d bytes\n", wrote); + NG_BT3C_STAT_BYTES_SENT(sc->stat, wrote); + + bt3c_write(sc, 0x7005, wrote); + sc->flags |= BT3C_XMIT; + } +} /* bt3c_send */ + +/* + * Download chip firmware + */ + +static void +bt3c_download_firmware(bt3c_softc_p sc, char const *firmware, int firmware_size) +{ + ng_bt3c_firmware_block_ep const *block = NULL; + u_int16_t const *data = NULL; + int i, size; + u_int8_t c; + + /* Reset */ + device_printf(sc->dev, "Reseting the card...\n"); + bt3c_write(sc, 0x8040, 0x0404); + bt3c_write(sc, 0x8040, 0x0400); + DELAY(1); + + bt3c_write(sc, 0x8040, 0x0404); + DELAY(17); + + /* Download firmware */ + device_printf(sc->dev, "Starting firmware download process...\n"); + + for (size = 0; size < firmware_size; ) { + block = (ng_bt3c_firmware_block_ep const *)(firmware + size); + data = (u_int16_t const *)(block + 1); + + if (bootverbose) + device_printf(sc->dev, "Download firmware block, " \ + "address=%#08x, size=%d words, aligment=%d\n", + block->block_address, block->block_size, + block->block_alignment); + + bt3c_set_address(sc, block->block_address); + for (i = 0; i < block->block_size; i++) + bt3c_write_data(sc, data[i]); + + size += (sizeof(*block) + (block->block_size * 2) + + block->block_alignment); + } + + DELAY(17); + device_printf(sc->dev, "Firmware download process complete\n"); + + /* Boot */ + device_printf(sc->dev, "Starting the card...\n"); + bt3c_set_address(sc, 0x3000); + bt3c_read_control(sc, c); + bt3c_write_control(sc, (c | 0x40)); + DELAY(17); + + /* Clear registers */ + device_printf(sc->dev, "Clearing card registers...\n"); + bt3c_write(sc, 0x7006, 0x0000); + bt3c_write(sc, 0x7005, 0x0000); + bt3c_write(sc, 0x7001, 0x0000); + DELAY(1000); +} /* bt3c_download_firmware */ + +/**************************************************************************** + **************************************************************************** + ** Driver module + **************************************************************************** + ****************************************************************************/ + +/* + * PC Card (PCMCIA) driver + */ + +static device_method_t bt3c_pccard_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, bt3c_pccard_probe), + DEVMETHOD(device_attach, bt3c_pccard_attach), + DEVMETHOD(device_detach, bt3c_pccard_detach), + + { 0, 0 } +}; + +static driver_t bt3c_pccard_driver = { + NG_BT3C_NODE_TYPE, + bt3c_pccard_methods, + sizeof(bt3c_softc_t) +}; + +static devclass_t bt3c_devclass; + + +/* + * Load/Unload the driver module + */ + +static int +bt3c_modevent(module_t mod, int event, void *data) +{ + int error; + + switch (event) { + case MOD_LOAD: + error = ng_newtype(&typestruct); + if (error != 0) + printf("%s: Could not register Netgraph node type, " \ + "error=%d\n", NG_BT3C_NODE_TYPE, error); + break; + + case MOD_UNLOAD: + error = ng_rmtype(&typestruct); + break; + + default: + error = EOPNOTSUPP; + break; + } + + return (error); +} /* bt3c_modevent */ + +DRIVER_MODULE(bt3c, pccard, bt3c_pccard_driver, bt3c_devclass, bt3c_modevent,0); +MODULE_VERSION(ng_bt3c, NG_BLUETOOTH_VERSION); +MODULE_DEPEND(ng_bt3c, netgraph, NG_ABI_VERSION, NG_ABI_VERSION,NG_ABI_VERSION); + diff --git a/sys/netgraph7/bluetooth/drivers/bt3c/ng_bt3c_var.h b/sys/netgraph7/bluetooth/drivers/bt3c/ng_bt3c_var.h new file mode 100644 index 0000000000..a3bca3cabf --- /dev/null +++ b/sys/netgraph7/bluetooth/drivers/bt3c/ng_bt3c_var.h @@ -0,0 +1,107 @@ +/* + * ng_bt3c_var.h + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_bt3c_var.h,v 1.1 2002/11/24 19:46:54 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/drivers/bt3c/ng_bt3c_var.h,v 1.4 2006/07/05 17:18:47 emax Exp $ + * + * XXX XXX XX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX + * + * Based on information obrained from: Jose Orlando Pereira + * and disassembled w2k driver. + * + * XXX XXX XX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX + * + */ + +#ifndef _NG_BT3C_VAR_H_ +#define _NG_BT3C_VAR_H_ + +/* Debug printf's */ +#define NG_BT3C_ALERT if (sc->debug >= NG_BT3C_ALERT_LEVEL) device_printf +#define NG_BT3C_ERR if (sc->debug >= NG_BT3C_ERR_LEVEL) device_printf +#define NG_BT3C_WARN if (sc->debug >= NG_BT3C_WARN_LEVEL) device_printf +#define NG_BT3C_INFO if (sc->debug >= NG_BT3C_INFO_LEVEL) device_printf + +/* Device registers */ +#define BT3C_DATA_L 0x00 /* data low byte */ +#define BT3C_DATA_H 0x01 /* high byte */ +#define BT3C_ADDR_L 0x02 /* address low byte */ +#define BT3C_ADDR_H 0x03 /* high byte */ +#define BT3C_CONTROL 0x04 /* control */ + +#define BT3C_FIFO_SIZE 256 + +/* Device softc structure */ +struct bt3c_softc { + /* Device specific */ + device_t dev; /* pointer back to device */ + int iobase_rid; /* iobase RID */ + struct resource *iobase; /* iobase */ + bus_space_tag_t iot; /* I/O tag */ + bus_space_handle_t ioh; /* I/O handle */ + int irq_rid; /* irq RID */ + struct resource *irq; /* irq */ + void *irq_cookie; /* irq cookie */ + + /* Netgraph specific */ + node_p node; /* pointer back to node */ + hook_p hook; /* hook */ + + ng_bt3c_node_debug_ep debug; /* debug level */ + u_int16_t flags; /* device flags */ +#define BT3C_ANTENNA_OUT (1 << 0) /* antena is out */ +#define BT3C_XMIT (1 << 1) /* xmit in progress */ + + ng_bt3c_node_state_ep state; /* receiving state */ + + ng_bt3c_node_stat_ep stat; /* statistic */ +#define NG_BT3C_STAT_PCKTS_SENT(s) (s).pckts_sent ++ +#define NG_BT3C_STAT_BYTES_SENT(s, n) (s).bytes_sent += (n) +#define NG_BT3C_STAT_PCKTS_RECV(s) (s).pckts_recv ++ +#define NG_BT3C_STAT_BYTES_RECV(s, n) (s).bytes_recv += (n) +#define NG_BT3C_STAT_OERROR(s) (s).oerrors ++ +#define NG_BT3C_STAT_IERROR(s) (s).ierrors ++ +#define NG_BT3C_STAT_RESET(s) bzero(&(s), sizeof((s))) + + u_int32_t status; /* from ISR */ + void *ith; /* ithread handler */ + + struct mbuf *m; /* current frame */ + u_int32_t want; /* # of chars we want */ + + struct ifqueue inq; /* queue of incoming mbuf's */ + struct ifqueue outq; /* queue of outgoing mbuf's */ +#define BT3C_DEFAULTQLEN 12 /* XXX max. size of out queue */ +}; + +typedef struct bt3c_softc bt3c_softc_t; +typedef struct bt3c_softc * bt3c_softc_p; + +#endif /* ndef _NG_BT3C_VAR_H_ */ + diff --git a/sys/netgraph7/bluetooth/drivers/h4/TODO b/sys/netgraph7/bluetooth/drivers/h4/TODO new file mode 100644 index 0000000000..ef18188750 --- /dev/null +++ b/sys/netgraph7/bluetooth/drivers/h4/TODO @@ -0,0 +1,13 @@ +$Id: TODO,v 1.2 2004/08/23 18:08:15 max Exp $ +$FreeBSD: src/sys/netgraph/bluetooth/drivers/h4/TODO,v 1.3 2007/08/13 17:19:28 emax Exp $ + +FIXME/TODO list + +This is a list of open issues for H4 node + +1) Locking/SMP + + External code now uses ng_send_fn to inject data into Netgraph, but + i still use splXXX to lock tty level. this is wrong and should be + fixed! + diff --git a/sys/netgraph7/bluetooth/drivers/h4/ng_h4.c b/sys/netgraph7/bluetooth/drivers/h4/ng_h4.c new file mode 100644 index 0000000000..a6c8316b75 --- /dev/null +++ b/sys/netgraph7/bluetooth/drivers/h4/ng_h4.c @@ -0,0 +1,1020 @@ +/* + * ng_h4.c + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_h4.c,v 1.10 2005/10/31 17:57:43 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/drivers/h4/ng_h4.c,v 1.17 2007/08/13 17:19:28 emax Exp $ + * + * Based on: + * --------- + * + * FreeBSD: src/sys/netgraph/ng_tty.c + * Author: Archie Cobbs + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/***************************************************************************** + ***************************************************************************** + ** This node implements a Bluetooth HCI UART transport layer as per chapter + ** H4 of the Bluetooth Specification Book v1.1. It is a terminal line + ** discipline that is also a netgraph node. Installing this line discipline + ** on a terminal device instantiates a new netgraph node of this type, which + ** allows access to the device via the "hook" hook of the node. + ** + ** Once the line discipline is installed, you can find out the name of the + ** corresponding netgraph node via a NGIOCGINFO ioctl(). + ***************************************************************************** + *****************************************************************************/ + +/* MALLOC define */ +#ifndef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_H4, "netgraph_h4", "Netgraph Bluetooth H4 node"); +#else +#define M_NETGRAPH_H4 M_NETGRAPH +#endif /* NG_SEPARATE_MALLOC */ + +/* Line discipline methods */ +static int ng_h4_open (struct cdev *, struct tty *); +static int ng_h4_close (struct tty *, int); +static int ng_h4_read (struct tty *, struct uio *, int); +static int ng_h4_write (struct tty *, struct uio *, int); +static int ng_h4_input (int, struct tty *); +static int ng_h4_start (struct tty *); +static int ng_h4_ioctl (struct tty *, u_long, caddr_t, + int, struct thread *); + +/* Line discipline descriptor */ +static struct linesw ng_h4_disc = { + ng_h4_open, /* open */ + ng_h4_close, /* close */ + ng_h4_read, /* read */ + ng_h4_write, /* write */ + ng_h4_ioctl, /* ioctl */ + ng_h4_input, /* input */ + ng_h4_start, /* start */ + ttymodem /* modem */ +}; + +/* Netgraph methods */ +static ng_constructor_t ng_h4_constructor; +static ng_rcvmsg_t ng_h4_rcvmsg; +static ng_shutdown_t ng_h4_shutdown; +static ng_newhook_t ng_h4_newhook; +static ng_connect_t ng_h4_connect; +static ng_rcvdata_t ng_h4_rcvdata; +static ng_disconnect_t ng_h4_disconnect; + +/* Other stuff */ +static void ng_h4_process_timeout (node_p, hook_p, void *, int); +static int ng_h4_mod_event (module_t, int, void *); + +/* Netgraph node type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_H4_NODE_TYPE, + .mod_event = ng_h4_mod_event, + .constructor = ng_h4_constructor, + .rcvmsg = ng_h4_rcvmsg, + .shutdown = ng_h4_shutdown, + .newhook = ng_h4_newhook, + .connect = ng_h4_connect, + .rcvdata = ng_h4_rcvdata, + .disconnect = ng_h4_disconnect, + .cmdlist = ng_h4_cmdlist +}; +NETGRAPH_INIT(h4, &typestruct); +MODULE_VERSION(ng_h4, NG_BLUETOOTH_VERSION); + +static int ng_h4_node = 0; + +/***************************************************************************** + ***************************************************************************** + ** Line discipline methods + ***************************************************************************** + *****************************************************************************/ + +/* + * Set our line discipline on the tty. + */ + +static int +ng_h4_open(struct cdev *dev, struct tty *tp) +{ + struct thread *td = curthread; + char name[NG_NODESIZ]; + ng_h4_info_p sc = NULL; + int error; + + /* Super-user only */ + error = priv_check(td, PRIV_NETGRAPH_TTY); /* XXX */ + if (error != 0) + return (error); + + /* Initialize private struct */ + MALLOC(sc, ng_h4_info_p, sizeof(*sc), M_NETGRAPH_H4, M_NOWAIT|M_ZERO); + if (sc == NULL) + return (ENOMEM); + + sc->tp = tp; + sc->debug = NG_H4_WARN_LEVEL; + + sc->state = NG_H4_W4_PKT_IND; + sc->want = 1; + sc->got = 0; + + mtx_init(&sc->outq.ifq_mtx, "ng_h4 node+queue", NULL, MTX_DEF); + IFQ_SET_MAXLEN(&sc->outq, NG_H4_DEFAULTQLEN); + ng_callout_init(&sc->timo); + + NG_H4_LOCK(sc); + + /* Setup netgraph node */ + error = ng_make_node_common(&typestruct, &sc->node); + if (error != 0) { + NG_H4_UNLOCK(sc); + + printf("%s: Unable to create new node!\n", __func__); + + mtx_destroy(&sc->outq.ifq_mtx); + bzero(sc, sizeof(*sc)); + FREE(sc, M_NETGRAPH_H4); + + return (error); + } + + /* Assign node its name */ + snprintf(name, sizeof(name), "%s%d", typestruct.name, ng_h4_node ++); + + error = ng_name_node(sc->node, name); + if (error != 0) { + NG_H4_UNLOCK(sc); + + printf("%s: %s - node name exists?\n", __func__, name); + + NG_NODE_UNREF(sc->node); + mtx_destroy(&sc->outq.ifq_mtx); + bzero(sc, sizeof(*sc)); + FREE(sc, M_NETGRAPH_H4); + + return (error); + } + + /* Set back pointers */ + NG_NODE_SET_PRIVATE(sc->node, sc); + tp->t_lsc = (caddr_t) sc; + + /* The node has to be a WRITER because data can change node status */ + NG_NODE_FORCE_WRITER(sc->node); + + /* + * Pre-allocate cblocks to the an appropriate amount. + * I'm not sure what is appropriate. + */ + + ttyflush(tp, FREAD | FWRITE); + clist_alloc_cblocks(&tp->t_canq, 0, 0); + clist_alloc_cblocks(&tp->t_rawq, 0, 0); + clist_alloc_cblocks(&tp->t_outq, + MLEN + NG_H4_HIWATER, MLEN + NG_H4_HIWATER); + + NG_H4_UNLOCK(sc); + + return (error); +} /* ng_h4_open */ + +/* + * Line specific close routine, called from device close routine + * and from ttioctl. This causes the node to be destroyed as well. + */ + +static int +ng_h4_close(struct tty *tp, int flag) +{ + ng_h4_info_p sc = (ng_h4_info_p) tp->t_lsc; + + ttyflush(tp, FREAD | FWRITE); + clist_free_cblocks(&tp->t_outq); + + if (sc != NULL) { + NG_H4_LOCK(sc); + + if (callout_pending(&sc->timo)) + ng_uncallout(&sc->timo, sc->node); + + tp->t_lsc = NULL; + sc->dying = 1; + + NG_H4_UNLOCK(sc); + + ng_rmnode_self(sc->node); + } + + return (0); +} /* ng_h4_close */ + +/* + * Once the device has been turned into a node, we don't allow reading. + */ + +static int +ng_h4_read(struct tty *tp, struct uio *uio, int flag) +{ + return (EIO); +} /* ng_h4_read */ + +/* + * Once the device has been turned into a node, we don't allow writing. + */ + +static int +ng_h4_write(struct tty *tp, struct uio *uio, int flag) +{ + return (EIO); +} /* ng_h4_write */ + +/* + * We implement the NGIOCGINFO ioctl() defined in ng_message.h. + */ + +static int +ng_h4_ioctl(struct tty *tp, u_long cmd, caddr_t data, int flag, + struct thread *td) +{ + ng_h4_info_p sc = (ng_h4_info_p) tp->t_lsc; + int error = 0; + + if (sc == NULL) + return (ENXIO); + + NG_H4_LOCK(sc); + + switch (cmd) { + case NGIOCGINFO: +#undef NI +#define NI(x) ((struct nodeinfo *)(x)) + + bzero(data, sizeof(*NI(data))); + + if (NG_NODE_HAS_NAME(sc->node)) + strncpy(NI(data)->name, NG_NODE_NAME(sc->node), + sizeof(NI(data)->name) - 1); + + strncpy(NI(data)->type, sc->node->nd_type->name, + sizeof(NI(data)->type) - 1); + + NI(data)->id = (u_int32_t) ng_node2ID(sc->node); + NI(data)->hooks = NG_NODE_NUMHOOKS(sc->node); + break; + + default: + error = ENOIOCTL; + break; + } + + NG_H4_UNLOCK(sc); + + return (error); +} /* ng_h4_ioctl */ + +/* + * Receive data coming from the device. We get one character at a time, which + * is kindof silly. + */ + +static int +ng_h4_input(int c, struct tty *tp) +{ + ng_h4_info_p sc = (ng_h4_info_p) tp->t_lsc; + + if (sc == NULL || tp != sc->tp || + sc->node == NULL || NG_NODE_NOT_VALID(sc->node)) + return (0); + + NG_H4_LOCK(sc); + + /* Check for error conditions */ + if ((tp->t_state & TS_CONNECTED) == 0) { + NG_H4_INFO("%s: %s - no carrier\n", __func__, + NG_NODE_NAME(sc->node)); + + sc->state = NG_H4_W4_PKT_IND; + sc->want = 1; + sc->got = 0; + + NG_H4_UNLOCK(sc); + + return (0); /* XXX Loss of synchronization here! */ + } + + /* Check for framing error or overrun on this char */ + if (c & TTY_ERRORMASK) { + NG_H4_ERR("%s: %s - line error %#x, c=%#x\n", __func__, + NG_NODE_NAME(sc->node), c & TTY_ERRORMASK, + c & TTY_CHARMASK); + + NG_H4_STAT_IERROR(sc->stat); + + sc->state = NG_H4_W4_PKT_IND; + sc->want = 1; + sc->got = 0; + + NG_H4_UNLOCK(sc); + + return (0); /* XXX Loss of synchronization here! */ + } + + NG_H4_STAT_BYTES_RECV(sc->stat, 1); + + /* Append char to mbuf */ + if (sc->got >= sizeof(sc->ibuf)) { + NG_H4_ALERT("%s: %s - input buffer overflow, c=%#x, got=%d\n", + __func__, NG_NODE_NAME(sc->node), c & TTY_CHARMASK, + sc->got); + + NG_H4_STAT_IERROR(sc->stat); + + sc->state = NG_H4_W4_PKT_IND; + sc->want = 1; + sc->got = 0; + + NG_H4_UNLOCK(sc); + + return (0); /* XXX Loss of synchronization here! */ + } + + sc->ibuf[sc->got ++] = (c & TTY_CHARMASK); + + NG_H4_INFO("%s: %s - got char %#x, want=%d, got=%d\n", __func__, + NG_NODE_NAME(sc->node), c, sc->want, sc->got); + + if (sc->got < sc->want) { + NG_H4_UNLOCK(sc); + + return (0); /* Wait for more */ + } + + switch (sc->state) { + /* Got packet indicator */ + case NG_H4_W4_PKT_IND: + NG_H4_INFO("%s: %s - got packet indicator %#x\n", __func__, + NG_NODE_NAME(sc->node), sc->ibuf[0]); + + sc->state = NG_H4_W4_PKT_HDR; + + /* + * Since packet indicator included in the packet header + * just set sc->want to sizeof(packet header). + */ + + switch (sc->ibuf[0]) { + case NG_HCI_ACL_DATA_PKT: + sc->want = sizeof(ng_hci_acldata_pkt_t); + break; + + case NG_HCI_SCO_DATA_PKT: + sc->want = sizeof(ng_hci_scodata_pkt_t); + break; + + case NG_HCI_EVENT_PKT: + sc->want = sizeof(ng_hci_event_pkt_t); + break; + + default: + NG_H4_WARN("%s: %s - ignoring unknown packet " \ + "type=%#x\n", __func__, NG_NODE_NAME(sc->node), + sc->ibuf[0]); + + NG_H4_STAT_IERROR(sc->stat); + + sc->state = NG_H4_W4_PKT_IND; + sc->want = 1; + sc->got = 0; + break; + } + break; + + /* Got packet header */ + case NG_H4_W4_PKT_HDR: + sc->state = NG_H4_W4_PKT_DATA; + + switch (sc->ibuf[0]) { + case NG_HCI_ACL_DATA_PKT: + c = le16toh(((ng_hci_acldata_pkt_t *) + (sc->ibuf))->length); + break; + + case NG_HCI_SCO_DATA_PKT: + c = ((ng_hci_scodata_pkt_t *)(sc->ibuf))->length; + break; + + case NG_HCI_EVENT_PKT: + c = ((ng_hci_event_pkt_t *)(sc->ibuf))->length; + break; + + default: + KASSERT((0), ("Invalid packet type=%#x\n", + sc->ibuf[0])); + break; + } + + NG_H4_INFO("%s: %s - got packet header, packet type=%#x, " \ + "packet size=%d, payload size=%d\n", __func__, + NG_NODE_NAME(sc->node), sc->ibuf[0], sc->got, c); + + if (c > 0) { + sc->want += c; + + /* + * Try to prevent possible buffer overrun + * + * XXX I'm *really* confused here. It turns out + * that Xircom card sends us packets with length + * greater then 512 bytes! This is greater then + * our old receive buffer (ibuf) size. In the same + * time the card demands from us *not* to send + * packets greater then 192 bytes. Weird! How the + * hell i should know how big *receive* buffer + * should be? For now increase receiving buffer + * size to 1K and add the following check. + */ + + if (sc->want >= sizeof(sc->ibuf)) { + int b; + + NG_H4_ALERT("%s: %s - packet too big for " \ + "buffer, type=%#x, got=%d, want=%d, " \ + "length=%d\n", __func__, + NG_NODE_NAME(sc->node), sc->ibuf[0], + sc->got, sc->want, c); + + NG_H4_ALERT("Packet header:\n"); + for (b = 0; b < sc->got; b++) + NG_H4_ALERT("%#x ", sc->ibuf[b]); + NG_H4_ALERT("\n"); + + /* Reset state */ + NG_H4_STAT_IERROR(sc->stat); + + sc->state = NG_H4_W4_PKT_IND; + sc->want = 1; + sc->got = 0; + } + + break; + } + + /* else FALLTHROUGH and deliver frame */ + /* XXX Is this true? Should we deliver empty frame? */ + + /* Got packet data */ + case NG_H4_W4_PKT_DATA: + NG_H4_INFO("%s: %s - got full packet, packet type=%#x, " \ + "packet size=%d\n", __func__, + NG_NODE_NAME(sc->node), sc->ibuf[0], sc->got); + + if (sc->hook != NULL && NG_HOOK_IS_VALID(sc->hook)) { + struct mbuf *m = NULL; + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m != NULL) { + m->m_pkthdr.len = 0; + + /* XXX m_copyback() is stupid */ + m->m_len = min(MHLEN, sc->got); + + m_copyback(m, 0, sc->got, sc->ibuf); + NG_SEND_DATA_ONLY(c, sc->hook, m); + } else { + NG_H4_ERR("%s: %s - could not get mbuf\n", + __func__, NG_NODE_NAME(sc->node)); + + NG_H4_STAT_IERROR(sc->stat); + } + } + + sc->state = NG_H4_W4_PKT_IND; + sc->want = 1; + sc->got = 0; + + NG_H4_STAT_PCKTS_RECV(sc->stat); + break; + + default: + KASSERT((0), ("Invalid H4 node state=%d", sc->state)); + break; + } + + NG_H4_UNLOCK(sc); + + return (0); +} /* ng_h4_input */ + +/* + * This is called when the device driver is ready for more output. Called from + * tty system. + */ + +static int +ng_h4_start(struct tty *tp) +{ + ng_h4_info_p sc = (ng_h4_info_p) tp->t_lsc; + struct mbuf *m = NULL; + int size; + + if (sc == NULL || tp != sc->tp || + sc->node == NULL || NG_NODE_NOT_VALID(sc->node)) + return (0); + +#if 0 + while (tp->t_outq.c_cc < NG_H4_HIWATER) { /* XXX 2.2 specific ? */ +#else + while (1) { +#endif + /* Remove first mbuf from queue */ + IF_DEQUEUE(&sc->outq, m); + if (m == NULL) + break; + + /* Send as much of it as possible */ + while (m != NULL) { + size = m->m_len - b_to_q(mtod(m, u_char *), + m->m_len, &tp->t_outq); + + NG_H4_LOCK(sc); + NG_H4_STAT_BYTES_SENT(sc->stat, size); + NG_H4_UNLOCK(sc); + + m->m_data += size; + m->m_len -= size; + if (m->m_len > 0) + break; /* device can't take no more */ + + m = m_free(m); + } + + /* Put remainder of mbuf chain (if any) back on queue */ + if (m != NULL) { + IF_PREPEND(&sc->outq, m); + break; + } + + /* Full packet has been sent */ + NG_H4_LOCK(sc); + NG_H4_STAT_PCKTS_SENT(sc->stat); + NG_H4_UNLOCK(sc); + } + + /* + * Call output process whether or not there is any output. We are + * being called in lieu of ttstart and must do what it would. + */ + + tt_oproc(sc->tp); + + /* + * This timeout is needed for operation on a pseudo-tty, because the + * pty code doesn't call pppstart after it has drained the t_outq. + */ + + NG_H4_LOCK(sc); + + if (!IFQ_IS_EMPTY(&sc->outq) && !callout_pending(&sc->timo)) + ng_callout(&sc->timo, sc->node, NULL, 1, + ng_h4_process_timeout, NULL, 0); + + NG_H4_UNLOCK(sc); + + return (0); +} /* ng_h4_start */ + +/***************************************************************************** + ***************************************************************************** + ** Netgraph node methods + ***************************************************************************** + *****************************************************************************/ + +/* + * Initialize a new node of this type. We only allow nodes to be created as + * a result of setting the line discipline on a tty, so always return an error + * if not. + */ + +static int +ng_h4_constructor(node_p node) +{ + return (EOPNOTSUPP); +} /* ng_h4_constructor */ + +/* + * Add a new hook. There can only be one. + */ + +static int +ng_h4_newhook(node_p node, hook_p hook, const char *name) +{ + ng_h4_info_p sc = (ng_h4_info_p) NG_NODE_PRIVATE(node); + + if (strcmp(name, NG_H4_HOOK) != 0) + return (EINVAL); + + NG_H4_LOCK(sc); + + if (sc->hook != NULL) { + NG_H4_UNLOCK(sc); + return (EISCONN); + } + sc->hook = hook; + + NG_H4_UNLOCK(sc); + + return (0); +} /* ng_h4_newhook */ + +/* + * Connect hook. Just say yes. + */ + +static int +ng_h4_connect(hook_p hook) +{ + ng_h4_info_p sc = (ng_h4_info_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + if (hook != sc->hook) + panic("%s: hook != sc->hook\n", __func__); + + NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); + NG_HOOK_FORCE_QUEUE(hook); + + return (0); +} /* ng_h4_connect */ + +/* + * Disconnect the hook + */ + +static int +ng_h4_disconnect(hook_p hook) +{ + ng_h4_info_p sc = (ng_h4_info_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + /* + * We need to check for sc != NULL because we can be called from + * ng_h4_close() via ng_rmnode_self() + */ + + if (sc != NULL) { + if (hook != sc->hook) + panic("%s: hook != sc->hook\n", __func__); + + NG_H4_LOCK(sc); + + /* XXX do we have to untimeout and drain out queue? */ + if (callout_pending(&sc->timo)) + ng_uncallout(&sc->timo, sc->node); + + _IF_DRAIN(&sc->outq); + + sc->state = NG_H4_W4_PKT_IND; + sc->want = 1; + sc->got = 0; + + sc->hook = NULL; + + NG_H4_UNLOCK(sc); + } + + return (0); +} /* ng_h4_disconnect */ + +/* + * Remove this node. The does the netgraph portion of the shutdown. + * This should only be called indirectly from ng_h4_close(). + */ + +static int +ng_h4_shutdown(node_p node) +{ + ng_h4_info_p sc = (ng_h4_info_p) NG_NODE_PRIVATE(node); + + NG_H4_LOCK(sc); + + if (!sc->dying) { + NG_H4_UNLOCK(sc); + + NG_NODE_REVIVE(node); /* we will persist */ + + return (EOPNOTSUPP); + } + + NG_H4_UNLOCK(sc); + + NG_NODE_SET_PRIVATE(node, NULL); + + _IF_DRAIN(&sc->outq); + + NG_NODE_UNREF(node); + mtx_destroy(&sc->outq.ifq_mtx); + bzero(sc, sizeof(*sc)); + FREE(sc, M_NETGRAPH_H4); + + return (0); +} /* ng_h4_shutdown */ + +/* + * Receive incoming data from Netgraph system. Put it on our + * output queue and start output if necessary. + */ + +static int +ng_h4_rcvdata(hook_p hook, item_p item) +{ + ng_h4_info_p sc = (ng_h4_info_p)NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct mbuf *m = NULL; + int qlen; + + if (sc == NULL) + return (EHOSTDOWN); + + if (hook != sc->hook) + panic("%s: hook != sc->hook\n", __func__); + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + NG_H4_LOCK(sc); + + if (_IF_QFULL(&sc->outq)) { + NG_H4_ERR("%s: %s - dropping mbuf, len=%d\n", __func__, + NG_NODE_NAME(sc->node), m->m_pkthdr.len); + + NG_H4_STAT_OERROR(sc->stat); + _IF_DROP(&sc->outq); + + NG_H4_UNLOCK(sc); + + NG_FREE_M(m); + + return (ENOBUFS); + } + + NG_H4_INFO("%s: %s - queue mbuf, len=%d\n", __func__, + NG_NODE_NAME(sc->node), m->m_pkthdr.len); + + _IF_ENQUEUE(&sc->outq, m); + qlen = _IF_QLEN(&sc->outq); + + NG_H4_UNLOCK(sc); + + /* + * If qlen > 1, then we should already have a scheduled callout + */ + + if (qlen == 1) { + mtx_lock(&Giant); + ng_h4_start(sc->tp); + mtx_unlock(&Giant); + } + + return (0); +} /* ng_h4_rcvdata */ + +/* + * Receive control message + */ + +static int +ng_h4_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + ng_h4_info_p sc = (ng_h4_info_p) NG_NODE_PRIVATE(node); + struct ng_mesg *msg = NULL, *resp = NULL; + int error = 0; + + if (sc == NULL) + return (EHOSTDOWN); + + NGI_GET_MSG(item, msg); + NG_H4_LOCK(sc); + + switch (msg->header.typecookie) { + case NGM_GENERIC_COOKIE: + switch (msg->header.cmd) { + case NGM_TEXT_STATUS: + NG_MKRESPONSE(resp, msg, NG_TEXTRESPONSE, M_NOWAIT); + if (resp == NULL) + error = ENOMEM; + else + snprintf(resp->data, NG_TEXTRESPONSE, + "Hook: %s\n" \ + "Debug: %d\n" \ + "State: %d\n" \ + "Queue: [have:%d,max:%d]\n" \ + "Input: [got:%d,want:%d]", + (sc->hook != NULL)? NG_H4_HOOK : "", + sc->debug, + sc->state, + _IF_QLEN(&sc->outq), + sc->outq.ifq_maxlen, + sc->got, + sc->want); + break; + + default: + error = EINVAL; + break; + } + break; + + case NGM_H4_COOKIE: + switch (msg->header.cmd) { + case NGM_H4_NODE_RESET: + _IF_DRAIN(&sc->outq); + sc->state = NG_H4_W4_PKT_IND; + sc->want = 1; + sc->got = 0; + break; + + case NGM_H4_NODE_GET_STATE: + NG_MKRESPONSE(resp, msg, sizeof(ng_h4_node_state_ep), + M_NOWAIT); + if (resp == NULL) + error = ENOMEM; + else + *((ng_h4_node_state_ep *)(resp->data)) = + sc->state; + break; + + case NGM_H4_NODE_GET_DEBUG: + NG_MKRESPONSE(resp, msg, sizeof(ng_h4_node_debug_ep), + M_NOWAIT); + if (resp == NULL) + error = ENOMEM; + else + *((ng_h4_node_debug_ep *)(resp->data)) = + sc->debug; + break; + + case NGM_H4_NODE_SET_DEBUG: + if (msg->header.arglen != sizeof(ng_h4_node_debug_ep)) + error = EMSGSIZE; + else + sc->debug = + *((ng_h4_node_debug_ep *)(msg->data)); + break; + + case NGM_H4_NODE_GET_QLEN: + NG_MKRESPONSE(resp, msg, sizeof(ng_h4_node_qlen_ep), + M_NOWAIT); + if (resp == NULL) + error = ENOMEM; + else + *((ng_h4_node_qlen_ep *)(resp->data)) = + sc->outq.ifq_maxlen; + break; + + case NGM_H4_NODE_SET_QLEN: + if (msg->header.arglen != sizeof(ng_h4_node_qlen_ep)) + error = EMSGSIZE; + else if (*((ng_h4_node_qlen_ep *)(msg->data)) <= 0) + error = EINVAL; + else + IFQ_SET_MAXLEN(&sc->outq, + *((ng_h4_node_qlen_ep *)(msg->data))); + break; + + case NGM_H4_NODE_GET_STAT: + NG_MKRESPONSE(resp, msg, sizeof(ng_h4_node_stat_ep), + M_NOWAIT); + if (resp == NULL) + error = ENOMEM; + else + bcopy(&sc->stat, resp->data, + sizeof(ng_h4_node_stat_ep)); + break; + + case NGM_H4_NODE_RESET_STAT: + NG_H4_STAT_RESET(sc->stat); + break; + + default: + error = EINVAL; + break; + } + break; + + default: + error = EINVAL; + break; + } + + NG_H4_UNLOCK(sc); + + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + + return (error); +} /* ng_h4_rcvmsg */ + +/* + * Timeout processing function. + * We still have data to output to the device, so try sending more. + */ + +static void +ng_h4_process_timeout(node_p node, hook_p hook, void *arg1, int arg2) +{ + ng_h4_info_p sc = (ng_h4_info_p) NG_NODE_PRIVATE(node); + + mtx_lock(&Giant); + ng_h4_start(sc->tp); + mtx_unlock(&Giant); +} /* ng_h4_process_timeout */ + +/* + * Handle loading and unloading for this node type + */ + +static int +ng_h4_mod_event(module_t mod, int event, void *data) +{ + static int ng_h4_ldisc; + int error = 0; + + switch (event) { + case MOD_LOAD: + /* Register line discipline */ + mtx_lock(&Giant); + ng_h4_ldisc = ldisc_register(H4DISC, &ng_h4_disc); + mtx_unlock(&Giant); + + if (ng_h4_ldisc < 0) { + printf("%s: can't register H4 line discipline\n", + __func__); + error = EIO; + } + break; + + case MOD_UNLOAD: + /* Unregister line discipline */ + mtx_lock(&Giant); + ldisc_deregister(ng_h4_ldisc); + mtx_unlock(&Giant); + break; + + default: + error = EOPNOTSUPP; + break; + } + + return (error); +} /* ng_h4_mod_event */ + diff --git a/sys/netgraph7/bluetooth/drivers/h4/ng_h4_prse.h b/sys/netgraph7/bluetooth/drivers/h4/ng_h4_prse.h new file mode 100644 index 0000000000..fbf39310e7 --- /dev/null +++ b/sys/netgraph7/bluetooth/drivers/h4/ng_h4_prse.h @@ -0,0 +1,124 @@ +/* + * ng_h4_prse.h + */ + +/*- + * Copyright (c) 2001 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_h4_prse.h,v 1.4 2005/10/31 17:57:43 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/drivers/h4/ng_h4_prse.h,v 1.5 2007/08/13 17:19:28 emax Exp $ + */ + +/*************************************************************************** + *************************************************************************** + ** ng_parse definitions for the H4 node + *************************************************************************** + ***************************************************************************/ + +#ifndef _NETGRAPH_H4_PRSE_H_ +#define _NETGRAPH_H4_PRSE_H_ + +/* + * H4 node command list + */ + +/* Stat info */ +static const struct ng_parse_struct_field ng_h4_stat_type_fields[] = +{ + { "pckts_recv", &ng_parse_uint32_type, }, + { "bytes_recv", &ng_parse_uint32_type, }, + { "pckts_sent", &ng_parse_uint32_type, }, + { "bytes_sent", &ng_parse_uint32_type, }, + { "oerrors", &ng_parse_uint32_type, }, + { "ierrors", &ng_parse_uint32_type, }, + { NULL, } +}; +static const struct ng_parse_type ng_h4_stat_type = { + &ng_parse_struct_type, + &ng_h4_stat_type_fields +}; + +static const struct ng_cmdlist ng_h4_cmdlist[] = { + { + NGM_H4_COOKIE, + NGM_H4_NODE_RESET, + "reset", + NULL, + NULL + }, + { + NGM_H4_COOKIE, + NGM_H4_NODE_GET_STATE, + "get_state", + NULL, + &ng_parse_uint16_type + }, + { + NGM_H4_COOKIE, + NGM_H4_NODE_GET_DEBUG, + "get_debug", + NULL, + &ng_parse_uint16_type + }, + { + NGM_H4_COOKIE, + NGM_H4_NODE_SET_DEBUG, + "set_debug", + &ng_parse_uint16_type, + NULL + }, + { + NGM_H4_COOKIE, + NGM_H4_NODE_GET_QLEN, + "get_qlen", + NULL, + &ng_parse_int32_type + }, + { + NGM_H4_COOKIE, + NGM_H4_NODE_SET_QLEN, + "set_qlen", + &ng_parse_int32_type, + NULL + }, + { + NGM_H4_COOKIE, + NGM_H4_NODE_GET_STAT, + "get_stat", + NULL, + &ng_h4_stat_type + }, + { + NGM_H4_COOKIE, + NGM_H4_NODE_RESET_STAT, + "reset_stat", + NULL, + NULL + }, + { 0, } +}; + +#endif /* ndef _NETGRAPH_H4_PRSE_H_ */ + diff --git a/sys/netgraph7/bluetooth/drivers/h4/ng_h4_var.h b/sys/netgraph7/bluetooth/drivers/h4/ng_h4_var.h new file mode 100644 index 0000000000..e79c3b2402 --- /dev/null +++ b/sys/netgraph7/bluetooth/drivers/h4/ng_h4_var.h @@ -0,0 +1,103 @@ +/* + * ng_h4_var.h + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_h4_var.h,v 1.5 2005/10/31 17:57:43 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/drivers/h4/ng_h4_var.h,v 1.6 2007/08/13 17:19:28 emax Exp $ + * + * Based on: + * --------- + * + * FreeBSD: src/sys/netgraph/ng_tty.h + * Author: Archie Cobbs + */ + +#ifndef _NETGRAPH_H4_VAR_H_ +#define _NETGRAPH_H4_VAR_H_ + +/* + * Malloc declaration + */ + +#ifndef NG_SEPARATE_MALLOC +MALLOC_DECLARE(M_NETGRAPH_H4); +#else +#define M_NETGRAPH_H4 M_NETGRAPH +#endif /* NG_SEPARATE_MALLOC */ + +/* + * Debug + */ + +#define NG_H4_ALERT if (sc->debug >= NG_H4_ALERT_LEVEL) printf +#define NG_H4_ERR if (sc->debug >= NG_H4_ERR_LEVEL) printf +#define NG_H4_WARN if (sc->debug >= NG_H4_WARN_LEVEL) printf +#define NG_H4_INFO if (sc->debug >= NG_H4_INFO_LEVEL) printf + +#define NG_H4_HIWATER 256 /* High water mark on output */ + +/* + * Per-node private info + */ + +typedef struct ng_h4_info { + struct tty *tp; /* Terminal device */ + node_p node; /* Netgraph node */ + + ng_h4_node_debug_ep debug; /* Debug level */ + ng_h4_node_state_ep state; /* State */ + + ng_h4_node_stat_ep stat; +#define NG_H4_STAT_PCKTS_SENT(s) (s).pckts_sent ++ +#define NG_H4_STAT_BYTES_SENT(s, n) (s).bytes_sent += (n) +#define NG_H4_STAT_PCKTS_RECV(s) (s).pckts_recv ++ +#define NG_H4_STAT_BYTES_RECV(s, n) (s).bytes_recv += (n) +#define NG_H4_STAT_OERROR(s) (s).oerrors ++ +#define NG_H4_STAT_IERROR(s) (s).ierrors ++ +#define NG_H4_STAT_RESET(s) bzero(&(s), sizeof((s))) + + struct ifqueue outq; /* Queue of outgoing mbuf's */ +#define NG_H4_DEFAULTQLEN 12 /* XXX max number of mbuf's in outq */ +#define NG_H4_LOCK(sc) IF_LOCK(&sc->outq) +#define NG_H4_UNLOCK(sc) IF_UNLOCK(&sc->outq) + +#define NG_H4_IBUF_SIZE 1024 /* XXX must be big enough to hold full + frame */ + u_int8_t ibuf[NG_H4_IBUF_SIZE]; /* Incoming data */ + u_int32_t got; /* Number of bytes we have received */ + u_int32_t want; /* Number of bytes we want to receive */ + + hook_p hook; /* Upstream hook */ + struct callout timo; /* See man timeout(9) */ + + u_int8_t dying; /* are we dying? */ +} ng_h4_info_t; +typedef ng_h4_info_t * ng_h4_info_p; + +#endif /* _NETGRAPH_H4_VAR_H_ */ + diff --git a/sys/netgraph7/bluetooth/drivers/ubt/TODO b/sys/netgraph7/bluetooth/drivers/ubt/TODO new file mode 100644 index 0000000000..608c3b0a09 --- /dev/null +++ b/sys/netgraph7/bluetooth/drivers/ubt/TODO @@ -0,0 +1,30 @@ +$Id: TODO,v 1.1 2002/11/24 19:46:56 max Exp $ +$FreeBSD: src/sys/netgraph/bluetooth/drivers/ubt/TODO,v 1.2 2003/05/10 21:44:39 julian Exp $ + +1) SMP/Locking + + The code makes use of ng_send_fn() whenever possible. Just + need to verify and make sure i did it right + +2) Review USB ATTACH function + + It is a bit ugly now. Probably need a better way to discover + USB device configuration. + +2) Firmware upgrade + + According to Bluetooth spec device may present third interface + to perform firmware upgrade. 3Com USB Bluetooth dongle has + such interface. Need to implement set of Netgraph messages. + +3) Understand and fix isoc. USB transfers (SCO data) + + Currenty device reports that is got zero bytes and calls + isoc_in_complete callback over and over again. Why? + Also might need to setup at least two isoc. transfers in + both directions and switch them on the fly. Just to ensure + there at least one transfer at any time ready to run. + +4) Currently interrupt transfers are done as bulk-in transfers + + Need to check if that is allowed. diff --git a/sys/netgraph7/bluetooth/drivers/ubt/ng_ubt.c b/sys/netgraph7/bluetooth/drivers/ubt/ng_ubt.c new file mode 100644 index 0000000000..b8f17be83b --- /dev/null +++ b/sys/netgraph7/bluetooth/drivers/ubt/ng_ubt.c @@ -0,0 +1,2234 @@ +/* + * ng_ubt.c + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_ubt.c,v 1.16 2003/10/10 19:15:06 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/drivers/ubt/ng_ubt.c,v 1.33 2007/06/23 04:34:38 imp Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "usbdevs.h" + +/* + * USB methods + */ + +static device_probe_t ubt_match; +static device_attach_t ubt_attach; +static device_detach_t ubt_detach; + +static device_method_t ubt_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ubt_match), + DEVMETHOD(device_attach, ubt_attach), + DEVMETHOD(device_detach, ubt_detach), + + { 0, 0 } +}; + +static driver_t ubt_driver = { + "ubt", + ubt_methods, + sizeof(struct ubt_softc) +}; + +static devclass_t ubt_devclass; + +static int ubt_modevent (module_t, int, void *); + +static usbd_status ubt_request_start (ubt_softc_p); +static void ubt_request_complete (usbd_xfer_handle, + usbd_private_handle, usbd_status); +static void ubt_request_complete2 (node_p, hook_p, void *, int); + +static usbd_status ubt_intr_start (ubt_softc_p); +static void ubt_intr_complete (usbd_xfer_handle, + usbd_private_handle, usbd_status); +static void ubt_intr_complete2 (node_p, hook_p, void *, int); + +static usbd_status ubt_bulk_in_start (ubt_softc_p); +static void ubt_bulk_in_complete (usbd_xfer_handle, + usbd_private_handle, usbd_status); +static void ubt_bulk_in_complete2 (node_p, hook_p, void *, int); + +static usbd_status ubt_bulk_out_start (ubt_softc_p); +static void ubt_bulk_out_complete (usbd_xfer_handle, + usbd_private_handle, usbd_status); +static void ubt_bulk_out_complete2 (node_p, hook_p, void *, int); + +static usbd_status ubt_isoc_in_start (ubt_softc_p); +static void ubt_isoc_in_complete (usbd_xfer_handle, + usbd_private_handle, usbd_status); +static void ubt_isoc_in_complete2 (node_p, hook_p, void *, int); + +static usbd_status ubt_isoc_out_start (ubt_softc_p); +static void ubt_isoc_out_complete (usbd_xfer_handle, + usbd_private_handle, usbd_status); +static void ubt_isoc_out_complete2 (node_p, hook_p, void *, int); + +static void ubt_reset (ubt_softc_p); + +/* + * Netgraph methods + */ + +static ng_constructor_t ng_ubt_constructor; +static ng_shutdown_t ng_ubt_shutdown; +static ng_newhook_t ng_ubt_newhook; +static ng_connect_t ng_ubt_connect; +static ng_disconnect_t ng_ubt_disconnect; +static ng_rcvmsg_t ng_ubt_rcvmsg; +static ng_rcvdata_t ng_ubt_rcvdata; + +/* Queue length */ +static const struct ng_parse_struct_field ng_ubt_node_qlen_type_fields[] = +{ + { "queue", &ng_parse_int32_type, }, + { "qlen", &ng_parse_int32_type, }, + { NULL, } +}; +static const struct ng_parse_type ng_ubt_node_qlen_type = { + &ng_parse_struct_type, + &ng_ubt_node_qlen_type_fields +}; + +/* Stat info */ +static const struct ng_parse_struct_field ng_ubt_node_stat_type_fields[] = +{ + { "pckts_recv", &ng_parse_uint32_type, }, + { "bytes_recv", &ng_parse_uint32_type, }, + { "pckts_sent", &ng_parse_uint32_type, }, + { "bytes_sent", &ng_parse_uint32_type, }, + { "oerrors", &ng_parse_uint32_type, }, + { "ierrors", &ng_parse_uint32_type, }, + { NULL, } +}; +static const struct ng_parse_type ng_ubt_node_stat_type = { + &ng_parse_struct_type, + &ng_ubt_node_stat_type_fields +}; + +/* Netgraph node command list */ +static const struct ng_cmdlist ng_ubt_cmdlist[] = { +{ + NGM_UBT_COOKIE, + NGM_UBT_NODE_SET_DEBUG, + "set_debug", + &ng_parse_uint16_type, + NULL +}, +{ + NGM_UBT_COOKIE, + NGM_UBT_NODE_GET_DEBUG, + "get_debug", + NULL, + &ng_parse_uint16_type +}, +{ + NGM_UBT_COOKIE, + NGM_UBT_NODE_SET_QLEN, + "set_qlen", + &ng_ubt_node_qlen_type, + NULL +}, +{ + NGM_UBT_COOKIE, + NGM_UBT_NODE_GET_QLEN, + "get_qlen", + &ng_ubt_node_qlen_type, + &ng_ubt_node_qlen_type +}, +{ + NGM_UBT_COOKIE, + NGM_UBT_NODE_GET_STAT, + "get_stat", + NULL, + &ng_ubt_node_stat_type +}, +{ + NGM_UBT_COOKIE, + NGM_UBT_NODE_RESET_STAT, + "reset_stat", + NULL, + NULL +}, +{ 0, } +}; + +/* Netgraph node type */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_UBT_NODE_TYPE, + .constructor = ng_ubt_constructor, + .rcvmsg = ng_ubt_rcvmsg, + .shutdown = ng_ubt_shutdown, + .newhook = ng_ubt_newhook, + .connect = ng_ubt_connect, + .rcvdata = ng_ubt_rcvdata, + .disconnect = ng_ubt_disconnect, + .cmdlist = ng_ubt_cmdlist +}; + +/* + * Module + */ + +DRIVER_MODULE(ubt, uhub, ubt_driver, ubt_devclass, ubt_modevent, 0); +MODULE_VERSION(ng_ubt, NG_BLUETOOTH_VERSION); +MODULE_DEPEND(ng_ubt, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION); +MODULE_DEPEND(ubt, usb, 1, 1, 1); + + +/**************************************************************************** + **************************************************************************** + ** USB specific + **************************************************************************** + ****************************************************************************/ + +/* + * Load/Unload the driver module + */ + +static int +ubt_modevent(module_t mod, int event, void *data) +{ + int error; + + switch (event) { + case MOD_LOAD: + error = ng_newtype(&typestruct); + if (error != 0) + printf( +"%s: Could not register Netgraph node type, error=%d\n", + NG_UBT_NODE_TYPE, error); + else + error = usbd_driver_load(mod, event, data); + break; + + case MOD_UNLOAD: + error = ng_rmtype(&typestruct); + if (error == 0) + error = usbd_driver_load(mod, event, data); + break; + + default: + error = EOPNOTSUPP; + break; + } + + return (error); +} /* ubt_modevent */ + +/* + * Probe for a USB Bluetooth device + */ + +static int +ubt_match(device_t self) +{ + /* + * If for some reason device should not be attached then put + * VendorID/ProductID pair into the list below. The format is + * as follows: + * + * { VENDOR_ID, PRODUCT_ID }, + * + * where VENDOR_ID and PRODUCT_ID are hex numbers. + */ + + static struct usb_devno const ubt_ignored_devices[] = { + { USB_VENDOR_AVM, 0x2200 }, /* AVM USB Bluetooth-Adapter BlueFritz! v1.0 */ + { 0, 0 } /* This should be the last item in the list */ + }; + + /* + * If device violates Bluetooth specification and has bDeviceClass, + * bDeviceSubClass and bDeviceProtocol set to wrong values then you + * could try to put VendorID/ProductID pair into the list below. + * Adding VendorID/ProductID pair into this list forces ng_ubt(4) + * to attach to the broken device. + */ + + static struct usb_devno const ubt_broken_devices[] = { + { USB_VENDOR_AVM, 0x3800 }, /* AVM USB Bluetooth-Adapter BlueFritz! v2.0 */ + { 0, 0 } /* This should be the last item in the list */ + }; + + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_device_descriptor_t *dd = usbd_get_device_descriptor(uaa->device); + + if (uaa->iface == NULL || + usb_lookup(ubt_ignored_devices, uaa->vendor, uaa->product)) + return (UMATCH_NONE); + + if (dd->bDeviceClass == UDCLASS_WIRELESS && + dd->bDeviceSubClass == UDSUBCLASS_RF && + dd->bDeviceProtocol == UDPROTO_BLUETOOTH) + return (UMATCH_DEVCLASS_DEVSUBCLASS); + + if (usb_lookup(ubt_broken_devices, uaa->vendor, uaa->product)) + return (UMATCH_VENDOR_PRODUCT); + + return (UMATCH_NONE); +} /* ubt_match */ + +/* + * Attach the device + */ + +static int +ubt_attach(device_t self) +{ + struct ubt_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_config_descriptor_t *cd = NULL; + usb_interface_descriptor_t *id = NULL; + usb_endpoint_descriptor_t *ed = NULL; + usbd_status error; + int i, ai, alt_no, isoc_in, isoc_out, + isoc_isize, isoc_osize; + + /* Get USB device info */ + sc->sc_dev = self; + sc->sc_udev = uaa->device; + + /* + * Initialize device softc structure + */ + + /* State */ + sc->sc_debug = NG_UBT_WARN_LEVEL; + sc->sc_flags = 0; + NG_UBT_STAT_RESET(sc->sc_stat); + + /* Interfaces */ + sc->sc_iface0 = sc->sc_iface1 = NULL; + + /* Interrupt pipe */ + sc->sc_intr_ep = -1; + sc->sc_intr_pipe = NULL; + sc->sc_intr_xfer = NULL; + sc->sc_intr_buffer = NULL; + + /* Control pipe */ + sc->sc_ctrl_xfer = NULL; + sc->sc_ctrl_buffer = NULL; + NG_BT_MBUFQ_INIT(&sc->sc_cmdq, UBT_DEFAULT_QLEN); + + /* Bulk-in pipe */ + sc->sc_bulk_in_ep = -1; + sc->sc_bulk_in_pipe = NULL; + sc->sc_bulk_in_xfer = NULL; + sc->sc_bulk_in_buffer = NULL; + + /* Bulk-out pipe */ + sc->sc_bulk_out_ep = -1; + sc->sc_bulk_out_pipe = NULL; + sc->sc_bulk_out_xfer = NULL; + sc->sc_bulk_out_buffer = NULL; + NG_BT_MBUFQ_INIT(&sc->sc_aclq, UBT_DEFAULT_QLEN); + + /* Isoc-in pipe */ + sc->sc_isoc_in_ep = -1; + sc->sc_isoc_in_pipe = NULL; + sc->sc_isoc_in_xfer = NULL; + + /* Isoc-out pipe */ + sc->sc_isoc_out_ep = -1; + sc->sc_isoc_out_pipe = NULL; + sc->sc_isoc_out_xfer = NULL; + sc->sc_isoc_size = -1; + NG_BT_MBUFQ_INIT(&sc->sc_scoq, UBT_DEFAULT_QLEN); + + /* Netgraph part */ + sc->sc_node = NULL; + sc->sc_hook = NULL; + + /* + * XXX set configuration? + * + * Configure Bluetooth USB device. Discover all required USB interfaces + * and endpoints. + * + * USB device must present two interfaces: + * 1) Interface 0 that has 3 endpoints + * 1) Interrupt endpoint to receive HCI events + * 2) Bulk IN endpoint to receive ACL data + * 3) Bulk OUT endpoint to send ACL data + * + * 2) Interface 1 then has 2 endpoints + * 1) Isochronous IN endpoint to receive SCO data + * 2) Isochronous OUT endpoint to send SCO data + * + * Interface 1 (with isochronous endpoints) has several alternate + * configurations with different packet size. + */ + + /* + * Interface 0 + */ + + error = usbd_device2interface_handle(sc->sc_udev, 0, &sc->sc_iface0); + if (error || sc->sc_iface0 == NULL) { + printf("%s: Could not get interface 0 handle. %s (%d), " \ + "handle=%p\n", device_get_nameunit(sc->sc_dev), + usbd_errstr(error), error, sc->sc_iface0); + goto bad; + } + + id = usbd_get_interface_descriptor(sc->sc_iface0); + if (id == NULL) { + printf("%s: Could not get interface 0 descriptor\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + + for (i = 0; i < id->bNumEndpoints; i ++) { + ed = usbd_interface2endpoint_descriptor(sc->sc_iface0, i); + if (ed == NULL) { + printf("%s: Could not read endpoint descriptor for " \ + "interface 0, i=%d\n", device_get_nameunit(sc->sc_dev), + i); + goto bad; + } + + switch (UE_GET_XFERTYPE(ed->bmAttributes)) { + case UE_BULK: + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN) + sc->sc_bulk_in_ep = ed->bEndpointAddress; + else + sc->sc_bulk_out_ep = ed->bEndpointAddress; + break; + + case UE_INTERRUPT: + sc->sc_intr_ep = ed->bEndpointAddress; + break; + } + } + + /* Check if we got everything we wanted on Interface 0 */ + if (sc->sc_intr_ep == -1) { + printf("%s: Could not detect interrupt endpoint\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + if (sc->sc_bulk_in_ep == -1) { + printf("%s: Could not detect bulk-in endpoint\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + if (sc->sc_bulk_out_ep == -1) { + printf("%s: Could not detect bulk-out endpoint\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + + printf("%s: Interface 0 endpoints: interrupt=%#x, bulk-in=%#x, " \ + "bulk-out=%#x\n", device_get_nameunit(sc->sc_dev), + sc->sc_intr_ep, sc->sc_bulk_in_ep, sc->sc_bulk_out_ep); + + /* + * Interface 1 + */ + + cd = usbd_get_config_descriptor(sc->sc_udev); + if (cd == NULL) { + printf("%s: Could not get device configuration descriptor\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + + error = usbd_device2interface_handle(sc->sc_udev, 1, &sc->sc_iface1); + if (error || sc->sc_iface1 == NULL) { + printf("%s: Could not get interface 1 handle. %s (%d), " \ + "handle=%p\n", device_get_nameunit(sc->sc_dev), + usbd_errstr(error), error, sc->sc_iface1); + goto bad; + } + + id = usbd_get_interface_descriptor(sc->sc_iface1); + if (id == NULL) { + printf("%s: Could not get interface 1 descriptor\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + + /* + * Scan all alternate configurations for interface 1 + */ + + alt_no = -1; + + for (ai = 0; ai < usbd_get_no_alts(cd, 1); ai++) { + error = usbd_set_interface(sc->sc_iface1, ai); + if (error) { + printf("%s: [SCAN] Could not set alternate " \ + "configuration %d for interface 1. %s (%d)\n", + device_get_nameunit(sc->sc_dev), ai, usbd_errstr(error), + error); + goto bad; + } + id = usbd_get_interface_descriptor(sc->sc_iface1); + if (id == NULL) { + printf("%s: Could not get interface 1 descriptor for " \ + "alternate configuration %d\n", + device_get_nameunit(sc->sc_dev), ai); + goto bad; + } + + isoc_in = isoc_out = -1; + isoc_isize = isoc_osize = 0; + + for (i = 0; i < id->bNumEndpoints; i ++) { + ed = usbd_interface2endpoint_descriptor(sc->sc_iface1, i); + if (ed == NULL) { + printf("%s: Could not read endpoint " \ + "descriptor for interface 1, " \ + "alternate configuration %d, i=%d\n", + device_get_nameunit(sc->sc_dev), ai, i); + goto bad; + } + + if (UE_GET_XFERTYPE(ed->bmAttributes) != UE_ISOCHRONOUS) + continue; + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN) { + isoc_in = ed->bEndpointAddress; + isoc_isize = UGETW(ed->wMaxPacketSize); + } else { + isoc_out = ed->bEndpointAddress; + isoc_osize = UGETW(ed->wMaxPacketSize); + } + } + + /* + * Make sure that configuration looks sane and if so + * update current settings + */ + + if (isoc_in != -1 && isoc_out != -1 && + isoc_isize > 0 && isoc_osize > 0 && + isoc_isize == isoc_osize && isoc_isize > sc->sc_isoc_size) { + sc->sc_isoc_in_ep = isoc_in; + sc->sc_isoc_out_ep = isoc_out; + sc->sc_isoc_size = isoc_isize; + alt_no = ai; + } + } + + /* Check if we got everything we wanted on Interface 0 */ + if (sc->sc_isoc_in_ep == -1) { + printf("%s: Could not detect isoc-in endpoint\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + if (sc->sc_isoc_out_ep == -1) { + printf("%s: Could not detect isoc-out endpoint\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + if (sc->sc_isoc_size <= 0) { + printf("%s: Invalid isoc. packet size=%d\n", + device_get_nameunit(sc->sc_dev), sc->sc_isoc_size); + goto bad; + } + + error = usbd_set_interface(sc->sc_iface1, alt_no); + if (error) { + printf("%s: Could not set alternate configuration " \ + "%d for interface 1. %s (%d)\n", device_get_nameunit(sc->sc_dev), + alt_no, usbd_errstr(error), error); + goto bad; + } + + /* Allocate USB transfer handles and buffers */ + sc->sc_ctrl_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_ctrl_xfer == NULL) { + printf("%s: Could not allocate control xfer handle\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + sc->sc_ctrl_buffer = usbd_alloc_buffer(sc->sc_ctrl_xfer, + UBT_CTRL_BUFFER_SIZE); + if (sc->sc_ctrl_buffer == NULL) { + printf("%s: Could not allocate control buffer\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + + sc->sc_intr_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_intr_xfer == NULL) { + printf("%s: Could not allocate interrupt xfer handle\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + + sc->sc_bulk_in_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_bulk_in_xfer == NULL) { + printf("%s: Could not allocate bulk-in xfer handle\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + + sc->sc_bulk_out_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_bulk_out_xfer == NULL) { + printf("%s: Could not allocate bulk-out xfer handle\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + sc->sc_bulk_out_buffer = usbd_alloc_buffer(sc->sc_bulk_out_xfer, + UBT_BULK_BUFFER_SIZE); + if (sc->sc_bulk_out_buffer == NULL) { + printf("%s: Could not allocate bulk-out buffer\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + + /* + * Allocate buffers for isoc. transfers + */ + + sc->sc_isoc_nframes = (UBT_ISOC_BUFFER_SIZE / sc->sc_isoc_size) + 1; + + sc->sc_isoc_in_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_isoc_in_xfer == NULL) { + printf("%s: Could not allocate isoc-in xfer handle\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + sc->sc_isoc_in_buffer = usbd_alloc_buffer(sc->sc_isoc_in_xfer, + sc->sc_isoc_nframes * sc->sc_isoc_size); + if (sc->sc_isoc_in_buffer == NULL) { + printf("%s: Could not allocate isoc-in buffer\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + sc->sc_isoc_in_frlen = malloc(sizeof(u_int16_t) * sc->sc_isoc_nframes, + M_USBDEV, M_NOWAIT); + if (sc->sc_isoc_in_frlen == NULL) { + printf("%s: Could not allocate isoc-in frame sizes buffer\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + + sc->sc_isoc_out_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_isoc_out_xfer == NULL) { + printf("%s: Could not allocate isoc-out xfer handle\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + sc->sc_isoc_out_buffer = usbd_alloc_buffer(sc->sc_isoc_out_xfer, + sc->sc_isoc_nframes * sc->sc_isoc_size); + if (sc->sc_isoc_out_buffer == NULL) { + printf("%s: Could not allocate isoc-out buffer\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + sc->sc_isoc_out_frlen = malloc(sizeof(u_int16_t) * sc->sc_isoc_nframes, + M_USBDEV, M_NOWAIT); + if (sc->sc_isoc_out_frlen == NULL) { + printf("%s: Could not allocate isoc-out frame sizes buffer\n", + device_get_nameunit(sc->sc_dev)); + goto bad; + } + + printf("%s: Interface 1 (alt.config %d) endpoints: isoc-in=%#x, " \ + "isoc-out=%#x; wMaxPacketSize=%d; nframes=%d, buffer size=%d\n", + device_get_nameunit(sc->sc_dev), alt_no, sc->sc_isoc_in_ep, + sc->sc_isoc_out_ep, sc->sc_isoc_size, sc->sc_isoc_nframes, + (sc->sc_isoc_nframes * sc->sc_isoc_size)); + + /* + * Open pipes + */ + + /* Interrupt */ + error = usbd_open_pipe(sc->sc_iface0, sc->sc_intr_ep, + USBD_EXCLUSIVE_USE, &sc->sc_intr_pipe); + if (error != USBD_NORMAL_COMPLETION) { + printf("%s: %s - Could not open interrupt pipe. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(error), + error); + goto bad; + } + + /* Bulk-in */ + error = usbd_open_pipe(sc->sc_iface0, sc->sc_bulk_in_ep, + USBD_EXCLUSIVE_USE, &sc->sc_bulk_in_pipe); + if (error != USBD_NORMAL_COMPLETION) { + printf("%s: %s - Could not open bulk-in pipe. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(error), + error); + goto bad; + } + + /* Bulk-out */ + error = usbd_open_pipe(sc->sc_iface0, sc->sc_bulk_out_ep, + USBD_EXCLUSIVE_USE, &sc->sc_bulk_out_pipe); + if (error != USBD_NORMAL_COMPLETION) { + printf("%s: %s - Could not open bulk-out pipe. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(error), + error); + goto bad; + } + +#if 0 /* XXX FIXME */ + /* Isoc-in */ + error = usbd_open_pipe(sc->sc_iface1, sc->sc_isoc_in_ep, + USBD_EXCLUSIVE_USE, &sc->sc_isoc_in_pipe); + if (error != USBD_NORMAL_COMPLETION) { + printf("%s: %s - Could not open isoc-in pipe. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(error), + error); + goto bad; + } + + /* Isoc-out */ + error = usbd_open_pipe(sc->sc_iface1, sc->sc_isoc_out_ep, + USBD_EXCLUSIVE_USE, &sc->sc_isoc_out_pipe); + if (error != USBD_NORMAL_COMPLETION) { + printf("%s: %s - Could not open isoc-out pipe. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(error), + error); + goto bad; + } +#endif + + /* Create Netgraph node */ + if (ng_make_node_common(&typestruct, &sc->sc_node) != 0) { + printf("%s: Could not create Netgraph node\n", + device_get_nameunit(sc->sc_dev)); + sc->sc_node = NULL; + goto bad; + } + + /* Name node */ + if (ng_name_node(sc->sc_node, device_get_nameunit(sc->sc_dev)) != 0) { + printf("%s: Could not name Netgraph node\n", + device_get_nameunit(sc->sc_dev)); + NG_NODE_UNREF(sc->sc_node); + sc->sc_node = NULL; + goto bad; + } + + NG_NODE_SET_PRIVATE(sc->sc_node, sc); + NG_NODE_FORCE_WRITER(sc->sc_node); + + /* Claim all interfaces on the device */ + for (i = 0; i < uaa->nifaces; i++) + uaa->ifaces[i] = NULL; + + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, + sc->sc_dev); + + return 0; +bad: + ubt_detach(self); + + return ENXIO; +} /* ubt_attach */ + +/* + * Detach the device + */ + +static int +ubt_detach(device_t self) +{ + struct ubt_softc *sc = device_get_softc(self); + + /* Destroy Netgraph node */ + if (sc->sc_node != NULL) { + NG_NODE_SET_PRIVATE(sc->sc_node, NULL); + ng_rmnode_self(sc->sc_node); + sc->sc_node = NULL; + } + + /* Close pipes */ + if (sc->sc_intr_pipe != NULL) { + usbd_close_pipe(sc->sc_intr_pipe); + sc->sc_intr_pipe = NULL; + } + + if (sc->sc_bulk_in_pipe != NULL) { + usbd_close_pipe(sc->sc_bulk_in_pipe); + sc->sc_bulk_in_pipe = NULL; + } + if (sc->sc_bulk_out_pipe != NULL) { + usbd_close_pipe(sc->sc_bulk_out_pipe); + sc->sc_bulk_out_pipe = NULL; + } + + if (sc->sc_isoc_in_pipe != NULL) { + usbd_close_pipe(sc->sc_isoc_in_pipe); + sc->sc_isoc_in_pipe = NULL; + } + if (sc->sc_isoc_out_pipe != NULL) { + usbd_close_pipe(sc->sc_isoc_out_pipe); + sc->sc_isoc_out_pipe = NULL; + } + + /* Destroy USB transfer handles */ + if (sc->sc_ctrl_xfer != NULL) { + usbd_free_xfer(sc->sc_ctrl_xfer); + sc->sc_ctrl_xfer = NULL; + } + + if (sc->sc_intr_xfer != NULL) { + usbd_free_xfer(sc->sc_intr_xfer); + sc->sc_intr_xfer = NULL; + } + + if (sc->sc_bulk_in_xfer != NULL) { + usbd_free_xfer(sc->sc_bulk_in_xfer); + sc->sc_bulk_in_xfer = NULL; + } + if (sc->sc_bulk_out_xfer != NULL) { + usbd_free_xfer(sc->sc_bulk_out_xfer); + sc->sc_bulk_out_xfer = NULL; + } + + if (sc->sc_isoc_in_xfer != NULL) { + usbd_free_xfer(sc->sc_isoc_in_xfer); + sc->sc_isoc_in_xfer = NULL; + } + if (sc->sc_isoc_out_xfer != NULL) { + usbd_free_xfer(sc->sc_isoc_out_xfer); + sc->sc_isoc_out_xfer = NULL; + } + + /* Destroy isoc. frame size buffers */ + if (sc->sc_isoc_in_frlen != NULL) { + free(sc->sc_isoc_in_frlen, M_USBDEV); + sc->sc_isoc_in_frlen = NULL; + } + if (sc->sc_isoc_out_frlen != NULL) { + free(sc->sc_isoc_out_frlen, M_USBDEV); + sc->sc_isoc_out_frlen = NULL; + } + + /* Destroy queues */ + NG_BT_MBUFQ_DRAIN(&sc->sc_cmdq); + NG_BT_MBUFQ_DRAIN(&sc->sc_aclq); + NG_BT_MBUFQ_DRAIN(&sc->sc_scoq); + + usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, + sc->sc_dev); + + return (0); +} /* ubt_detach */ + +/* + * Start USB control request (HCI command). Must be called with node locked + */ + +static usbd_status +ubt_request_start(ubt_softc_p sc) +{ + usb_device_request_t req; + struct mbuf *m = NULL; + usbd_status status; + + KASSERT(!(sc->sc_flags & UBT_CMD_XMIT), ( +"%s: %s - Another control request is pending\n", + __func__, device_get_nameunit(sc->sc_dev))); + + NG_BT_MBUFQ_DEQUEUE(&sc->sc_cmdq, m); + if (m == NULL) { + NG_UBT_INFO( +"%s: %s - HCI command queue is empty\n", __func__, device_get_nameunit(sc->sc_dev)); + + return (USBD_NORMAL_COMPLETION); + } + + /* + * Check HCI command frame size and copy it back to + * linear USB transfer buffer. + */ + + if (m->m_pkthdr.len > UBT_CTRL_BUFFER_SIZE) + panic( +"%s: %s - HCI command frame too big, size=%zd, len=%d\n", + __func__, device_get_nameunit(sc->sc_dev), UBT_CTRL_BUFFER_SIZE, + m->m_pkthdr.len); + + m_copydata(m, 0, m->m_pkthdr.len, sc->sc_ctrl_buffer); + + /* Initialize a USB control request and then schedule it */ + bzero(&req, sizeof(req)); + req.bmRequestType = UBT_HCI_REQUEST; + USETW(req.wLength, m->m_pkthdr.len); + + NG_UBT_INFO( +"%s: %s - Sending control request, bmRequestType=%#x, wLength=%d\n", + __func__, device_get_nameunit(sc->sc_dev), req.bmRequestType, + UGETW(req.wLength)); + + usbd_setup_default_xfer( + sc->sc_ctrl_xfer, + sc->sc_udev, + (usbd_private_handle) sc->sc_node, + USBD_DEFAULT_TIMEOUT, /* XXX */ + &req, + sc->sc_ctrl_buffer, + m->m_pkthdr.len, + USBD_NO_COPY, + ubt_request_complete); + + NG_NODE_REF(sc->sc_node); + + status = usbd_transfer(sc->sc_ctrl_xfer); + if (status != USBD_NORMAL_COMPLETION && status != USBD_IN_PROGRESS) { + NG_UBT_ERR( +"%s: %s - Could not start control request. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), + usbd_errstr(status), status); + + NG_NODE_UNREF(sc->sc_node); + + NG_BT_MBUFQ_DROP(&sc->sc_cmdq); + NG_UBT_STAT_OERROR(sc->sc_stat); + + /* XXX FIXME should we try to resubmit another request? */ + } else { + NG_UBT_INFO( +"%s: %s - Control request has been started\n", + __func__, device_get_nameunit(sc->sc_dev)); + + sc->sc_flags |= UBT_CMD_XMIT; + status = USBD_NORMAL_COMPLETION; + } + + NG_FREE_M(m); + + return (status); +} /* ubt_request_start */ + +/* + * USB control request callback + */ + +static void +ubt_request_complete(usbd_xfer_handle h, usbd_private_handle p, usbd_status s) +{ + ng_send_fn((node_p) p, NULL, ubt_request_complete2, (void *) h, s); + NG_NODE_UNREF((node_p) p); +} /* ubt_request_complete */ + +static void +ubt_request_complete2(node_p node, hook_p hook, void *arg1, int arg2) +{ + ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); + usbd_xfer_handle h = (usbd_xfer_handle) arg1; + usbd_status s = (usbd_status) arg2; + + if (sc == NULL) + return; + + KASSERT((sc->sc_flags & UBT_CMD_XMIT), ( +"%s: %s - No control request is pending\n", __func__, device_get_nameunit(sc->sc_dev))); + + sc->sc_flags &= ~UBT_CMD_XMIT; + + if (s == USBD_CANCELLED) { + NG_UBT_INFO( +"%s: %s - Control request cancelled\n", __func__, device_get_nameunit(sc->sc_dev)); + + return; + } + + if (s != USBD_NORMAL_COMPLETION) { + NG_UBT_ERR( +"%s: %s - Control request failed. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(s), s); + + if (s == USBD_STALLED) + usbd_clear_endpoint_stall_async(h->pipe); + + NG_UBT_STAT_OERROR(sc->sc_stat); + } else { + NG_UBT_INFO( +"%s: %s - Sent %d bytes to control pipe\n", + __func__, device_get_nameunit(sc->sc_dev), h->actlen); + + NG_UBT_STAT_BYTES_SENT(sc->sc_stat, h->actlen); + NG_UBT_STAT_PCKTS_SENT(sc->sc_stat); + } + + if (NG_BT_MBUFQ_LEN(&sc->sc_cmdq) > 0) + ubt_request_start(sc); +} /* ubt_request_complete2 */ + +/* + * Start interrupt transfer. Must be called when node is locked + */ + +static usbd_status +ubt_intr_start(ubt_softc_p sc) +{ + struct mbuf *m = NULL; + usbd_status status; + + KASSERT(!(sc->sc_flags & UBT_EVT_RECV), ( +"%s: %s - Another interrupt request is pending\n", + __func__, device_get_nameunit(sc->sc_dev))); + + /* Allocate new mbuf cluster */ + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) + return (USBD_NOMEM); + + MCLGET(m, M_DONTWAIT); + if (!(m->m_flags & M_EXT)) { + NG_FREE_M(m); + return (USBD_NOMEM); + } + + if (!(sc->sc_flags & UBT_HAVE_FRAME_TYPE)) { + *mtod(m, u_int8_t *) = NG_HCI_EVENT_PKT; + m->m_pkthdr.len = m->m_len = 1; + } else + m->m_pkthdr.len = m->m_len = 0; + + /* Initialize a USB transfer and then schedule it */ + usbd_setup_xfer( + sc->sc_intr_xfer, + sc->sc_intr_pipe, + (usbd_private_handle) sc->sc_node, + (void *)(mtod(m, u_int8_t *) + m->m_len), + MCLBYTES - m->m_len, + USBD_SHORT_XFER_OK, + USBD_NO_TIMEOUT, + ubt_intr_complete); + + NG_NODE_REF(sc->sc_node); + + status = usbd_transfer(sc->sc_intr_xfer); + if (status != USBD_NORMAL_COMPLETION && status != USBD_IN_PROGRESS) { + NG_UBT_ERR( +"%s: %s - Failed to start intrerrupt transfer. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(status), + status); + + NG_NODE_UNREF(sc->sc_node); + + NG_FREE_M(m); + + return (status); + } + + sc->sc_flags |= UBT_EVT_RECV; + sc->sc_intr_buffer = m; + + return (USBD_NORMAL_COMPLETION); +} /* ubt_intr_start */ + +/* + * Process interrupt from USB device (We got data from interrupt pipe) + */ + +static void +ubt_intr_complete(usbd_xfer_handle h, usbd_private_handle p, usbd_status s) +{ + ng_send_fn((node_p) p, NULL, ubt_intr_complete2, (void *) h, s); + NG_NODE_UNREF((node_p) p); +} /* ubt_intr_complete */ + +static void +ubt_intr_complete2(node_p node, hook_p hook, void *arg1, int arg2) +{ + ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); + usbd_xfer_handle h = (usbd_xfer_handle) arg1; + usbd_status s = (usbd_status) arg2; + struct mbuf *m = NULL; + ng_hci_event_pkt_t *hdr = NULL; + int error; + + if (sc == NULL) + return; + + KASSERT((sc->sc_flags & UBT_EVT_RECV), ( +"%s: %s - No interrupt request is pending\n", + __func__, device_get_nameunit(sc->sc_dev))); + + sc->sc_flags &= ~UBT_EVT_RECV; + + m = sc->sc_intr_buffer; + sc->sc_intr_buffer = NULL; + + hdr = mtod(m, ng_hci_event_pkt_t *); + + if (sc->sc_hook == NULL || NG_HOOK_NOT_VALID(sc->sc_hook)) { + NG_UBT_INFO( +"%s: %s - No upstream hook\n", __func__, device_get_nameunit(sc->sc_dev)); + + NG_FREE_M(m); + return; + } + + if (s == USBD_CANCELLED) { + NG_UBT_INFO( +"%s: %s - Interrupt xfer cancelled\n", __func__, device_get_nameunit(sc->sc_dev)); + + NG_FREE_M(m); + return; + } + + if (s != USBD_NORMAL_COMPLETION) { + NG_UBT_WARN( +"%s: %s - Interrupt xfer failed, %s (%d). No new xfer will be submitted!\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(s), s); + + if (s == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_intr_pipe); + + NG_UBT_STAT_IERROR(sc->sc_stat); + NG_FREE_M(m); + + return; /* XXX FIXME we should restart after some delay */ + } + + NG_UBT_STAT_BYTES_RECV(sc->sc_stat, h->actlen); + m->m_pkthdr.len += h->actlen; + m->m_len += h->actlen; + + NG_UBT_INFO( +"%s: %s - Got %d bytes from interrupt pipe\n", + __func__, device_get_nameunit(sc->sc_dev), h->actlen); + + if (m->m_pkthdr.len < sizeof(*hdr)) { + NG_FREE_M(m); + goto done; + } + + if (hdr->length == m->m_pkthdr.len - sizeof(*hdr)) { + NG_UBT_INFO( +"%s: %s - Got complete HCI event frame, pktlen=%d, length=%d\n", + __func__, device_get_nameunit(sc->sc_dev), m->m_pkthdr.len, + hdr->length); + + NG_UBT_STAT_PCKTS_RECV(sc->sc_stat); + + NG_SEND_DATA_ONLY(error, sc->sc_hook, m); + if (error != 0) + NG_UBT_STAT_IERROR(sc->sc_stat); + } else { + NG_UBT_ERR( +"%s: %s - Invalid HCI event frame size, length=%d, pktlen=%d\n", + __func__, device_get_nameunit(sc->sc_dev), hdr->length, + m->m_pkthdr.len); + + NG_UBT_STAT_IERROR(sc->sc_stat); + NG_FREE_M(m); + } +done: + ubt_intr_start(sc); +} /* ubt_intr_complete2 */ + +/* + * Start bulk-in USB transfer (ACL data). Must be called when node is locked + */ + +static usbd_status +ubt_bulk_in_start(ubt_softc_p sc) +{ + struct mbuf *m = NULL; + usbd_status status; + + KASSERT(!(sc->sc_flags & UBT_ACL_RECV), ( +"%s: %s - Another bulk-in request is pending\n", + __func__, device_get_nameunit(sc->sc_dev))); + + /* Allocate new mbuf cluster */ + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) + return (USBD_NOMEM); + + MCLGET(m, M_DONTWAIT); + if (!(m->m_flags & M_EXT)) { + NG_FREE_M(m); + return (USBD_NOMEM); + } + + if (!(sc->sc_flags & UBT_HAVE_FRAME_TYPE)) { + *mtod(m, u_int8_t *) = NG_HCI_ACL_DATA_PKT; + m->m_pkthdr.len = m->m_len = 1; + } else + m->m_pkthdr.len = m->m_len = 0; + + /* Initialize a bulk-in USB transfer and then schedule it */ + usbd_setup_xfer( + sc->sc_bulk_in_xfer, + sc->sc_bulk_in_pipe, + (usbd_private_handle) sc->sc_node, + (void *)(mtod(m, u_int8_t *) + m->m_len), + MCLBYTES - m->m_len, + USBD_SHORT_XFER_OK, + USBD_NO_TIMEOUT, + ubt_bulk_in_complete); + + NG_NODE_REF(sc->sc_node); + + status = usbd_transfer(sc->sc_bulk_in_xfer); + if (status != USBD_NORMAL_COMPLETION && status != USBD_IN_PROGRESS) { + NG_UBT_ERR( +"%s: %s - Failed to start bulk-in transfer. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(status), + status); + + NG_NODE_UNREF(sc->sc_node); + + NG_FREE_M(m); + + return (status); + } + + sc->sc_flags |= UBT_ACL_RECV; + sc->sc_bulk_in_buffer = m; + + return (USBD_NORMAL_COMPLETION); +} /* ubt_bulk_in_start */ + +/* + * USB bulk-in transfer callback + */ + +static void +ubt_bulk_in_complete(usbd_xfer_handle h, usbd_private_handle p, usbd_status s) +{ + ng_send_fn((node_p) p, NULL, ubt_bulk_in_complete2, (void *) h, s); + NG_NODE_UNREF((node_p) p); +} /* ubt_bulk_in_complete */ + +static void +ubt_bulk_in_complete2(node_p node, hook_p hook, void *arg1, int arg2) +{ + ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); + usbd_xfer_handle h = (usbd_xfer_handle) arg1; + usbd_status s = (usbd_status) arg2; + struct mbuf *m = NULL; + ng_hci_acldata_pkt_t *hdr = NULL; + int len; + + if (sc == NULL) + return; + + KASSERT((sc->sc_flags & UBT_ACL_RECV), ( +"%s: %s - No bulk-in request is pending\n", __func__, device_get_nameunit(sc->sc_dev))); + + sc->sc_flags &= ~UBT_ACL_RECV; + + m = sc->sc_bulk_in_buffer; + sc->sc_bulk_in_buffer = NULL; + + hdr = mtod(m, ng_hci_acldata_pkt_t *); + + if (sc->sc_hook == NULL || NG_HOOK_NOT_VALID(sc->sc_hook)) { + NG_UBT_INFO( +"%s: %s - No upstream hook\n", __func__, device_get_nameunit(sc->sc_dev)); + + NG_FREE_M(m); + return; + } + + if (s == USBD_CANCELLED) { + NG_UBT_INFO( +"%s: %s - Bulk-in xfer cancelled, pipe=%p\n", + __func__, device_get_nameunit(sc->sc_dev), sc->sc_bulk_in_pipe); + + NG_FREE_M(m); + return; + } + + if (s != USBD_NORMAL_COMPLETION) { + NG_UBT_WARN( +"%s: %s - Bulk-in xfer failed, %s (%d). No new xfer will be submitted!\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(s), s); + + if (s == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_bulk_in_pipe); + + NG_UBT_STAT_IERROR(sc->sc_stat); + NG_FREE_M(m); + + return; /* XXX FIXME we should restart after some delay */ + } + + NG_UBT_STAT_BYTES_RECV(sc->sc_stat, h->actlen); + m->m_pkthdr.len += h->actlen; + m->m_len += h->actlen; + + NG_UBT_INFO( +"%s: %s - Got %d bytes from bulk-in pipe\n", + __func__, device_get_nameunit(sc->sc_dev), h->actlen); + + if (m->m_pkthdr.len < sizeof(*hdr)) { + NG_FREE_M(m); + goto done; + } + + len = le16toh(hdr->length); + if (len == m->m_pkthdr.len - sizeof(*hdr)) { + NG_UBT_INFO( +"%s: %s - Got complete ACL data frame, pktlen=%d, length=%d\n", + __func__, device_get_nameunit(sc->sc_dev), m->m_pkthdr.len, len); + + NG_UBT_STAT_PCKTS_RECV(sc->sc_stat); + + NG_SEND_DATA_ONLY(len, sc->sc_hook, m); + if (len != 0) + NG_UBT_STAT_IERROR(sc->sc_stat); + } else { + NG_UBT_ERR( +"%s: %s - Invalid ACL frame size, length=%d, pktlen=%d\n", + __func__, device_get_nameunit(sc->sc_dev), len, + m->m_pkthdr.len); + + NG_UBT_STAT_IERROR(sc->sc_stat); + NG_FREE_M(m); + } +done: + ubt_bulk_in_start(sc); +} /* ubt_bulk_in_complete2 */ + +/* + * Start bulk-out USB transfer. Must be called with node locked + */ + +static usbd_status +ubt_bulk_out_start(ubt_softc_p sc) +{ + struct mbuf *m = NULL; + usbd_status status; + + KASSERT(!(sc->sc_flags & UBT_ACL_XMIT), ( +"%s: %s - Another bulk-out request is pending\n", + __func__, device_get_nameunit(sc->sc_dev))); + + NG_BT_MBUFQ_DEQUEUE(&sc->sc_aclq, m); + if (m == NULL) { + NG_UBT_INFO( +"%s: %s - ACL data queue is empty\n", __func__, device_get_nameunit(sc->sc_dev)); + + return (USBD_NORMAL_COMPLETION); + } + + /* + * Check ACL data frame size and copy it back to linear USB + * transfer buffer. + */ + + if (m->m_pkthdr.len > UBT_BULK_BUFFER_SIZE) + panic( +"%s: %s - ACL data frame too big, size=%d, len=%d\n", + __func__, device_get_nameunit(sc->sc_dev), UBT_BULK_BUFFER_SIZE, + m->m_pkthdr.len); + + m_copydata(m, 0, m->m_pkthdr.len, sc->sc_bulk_out_buffer); + + /* Initialize a bulk-out USB transfer and then schedule it */ + usbd_setup_xfer( + sc->sc_bulk_out_xfer, + sc->sc_bulk_out_pipe, + (usbd_private_handle) sc->sc_node, + sc->sc_bulk_out_buffer, + m->m_pkthdr.len, + USBD_NO_COPY, + USBD_DEFAULT_TIMEOUT, /* XXX */ + ubt_bulk_out_complete); + + NG_NODE_REF(sc->sc_node); + + status = usbd_transfer(sc->sc_bulk_out_xfer); + if (status != USBD_NORMAL_COMPLETION && status != USBD_IN_PROGRESS) { + NG_UBT_ERR( +"%s: %s - Could not start bulk-out transfer. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(status), + status); + + NG_NODE_UNREF(sc->sc_node); + + NG_BT_MBUFQ_DROP(&sc->sc_aclq); + NG_UBT_STAT_OERROR(sc->sc_stat); + + /* XXX FIXME should we try to start another transfer? */ + } else { + NG_UBT_INFO( +"%s: %s - Bulk-out transfer has been started, len=%d\n", + __func__, device_get_nameunit(sc->sc_dev), m->m_pkthdr.len); + + sc->sc_flags |= UBT_ACL_XMIT; + status = USBD_NORMAL_COMPLETION; + } + + NG_FREE_M(m); + + return (status); +} /* ubt_bulk_out_start */ + +/* + * USB bulk-out transfer callback + */ + +static void +ubt_bulk_out_complete(usbd_xfer_handle h, usbd_private_handle p, usbd_status s) +{ + ng_send_fn((node_p) p, NULL, ubt_bulk_out_complete2, (void *) h, s); + NG_NODE_UNREF((node_p) p); +} /* ubt_bulk_out_complete */ + +static void +ubt_bulk_out_complete2(node_p node, hook_p hook, void *arg1, int arg2) +{ + ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); + usbd_xfer_handle h = (usbd_xfer_handle) arg1; + usbd_status s = (usbd_status) arg2; + + if (sc == NULL) + return; + + KASSERT((sc->sc_flags & UBT_ACL_XMIT), ( +"%s: %s - No bulk-out request is pending\n", __func__, device_get_nameunit(sc->sc_dev))); + + sc->sc_flags &= ~UBT_ACL_XMIT; + + if (s == USBD_CANCELLED) { + NG_UBT_INFO( +"%s: %s - Bulk-out xfer cancelled, pipe=%p\n", + __func__, device_get_nameunit(sc->sc_dev), sc->sc_bulk_out_pipe); + + return; + } + + if (s != USBD_NORMAL_COMPLETION) { + NG_UBT_WARN( +"%s: %s - Bulk-out xfer failed. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(s), s); + + if (s == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_bulk_out_pipe); + + NG_UBT_STAT_OERROR(sc->sc_stat); + } else { + NG_UBT_INFO( +"%s: %s - Sent %d bytes to bulk-out pipe\n", + __func__, device_get_nameunit(sc->sc_dev), h->actlen); + + NG_UBT_STAT_BYTES_SENT(sc->sc_stat, h->actlen); + NG_UBT_STAT_PCKTS_SENT(sc->sc_stat); + } + + if (NG_BT_MBUFQ_LEN(&sc->sc_aclq) > 0) + ubt_bulk_out_start(sc); +} /* ubt_bulk_out_complete2 */ + +/* + * Start Isochronous-in USB transfer. Must be called with node locked + */ + +static usbd_status +ubt_isoc_in_start(ubt_softc_p sc) +{ + usbd_status status; + int i; + + KASSERT(!(sc->sc_flags & UBT_SCO_RECV), ( +"%s: %s - Another isoc-in request is pending\n", + __func__, device_get_nameunit(sc->sc_dev))); + + /* Initialize a isoc-in USB transfer and then schedule it */ + for (i = 0; i < sc->sc_isoc_nframes; i++) + sc->sc_isoc_in_frlen[i] = sc->sc_isoc_size; + + usbd_setup_isoc_xfer( + sc->sc_isoc_in_xfer, + sc->sc_isoc_in_pipe, + (usbd_private_handle) sc->sc_node, + sc->sc_isoc_in_frlen, + sc->sc_isoc_nframes, + USBD_NO_COPY, /* XXX flags */ + ubt_isoc_in_complete); + + NG_NODE_REF(sc->sc_node); + + status = usbd_transfer(sc->sc_isoc_in_xfer); + if (status != USBD_NORMAL_COMPLETION && status != USBD_IN_PROGRESS) { + NG_UBT_ERR( +"%s: %s - Failed to start isoc-in transfer. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), + usbd_errstr(status), status); + + NG_NODE_UNREF(sc->sc_node); + + return (status); + } + + sc->sc_flags |= UBT_SCO_RECV; + + return (USBD_NORMAL_COMPLETION); +} /* ubt_isoc_in_start */ + +/* + * USB isochronous transfer callback + */ + +static void +ubt_isoc_in_complete(usbd_xfer_handle h, usbd_private_handle p, usbd_status s) +{ + ng_send_fn((node_p) p, NULL, ubt_isoc_in_complete2, (void *) h, s); + NG_NODE_UNREF((node_p) p); +} /* ubt_isoc_in_complete */ + +static void +ubt_isoc_in_complete2(node_p node, hook_p hook, void *arg1, int arg2) +{ + ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); + usbd_xfer_handle h = (usbd_xfer_handle) arg1; + usbd_status s = (usbd_status) arg2; + struct mbuf *m = NULL; + ng_hci_scodata_pkt_t *hdr = NULL; + u_int8_t *b = NULL; + int i; + + if (sc == NULL) + return; + + KASSERT((sc->sc_flags & UBT_SCO_RECV), ( +"%s: %s - No isoc-in request is pending\n", __func__, device_get_nameunit(sc->sc_dev))); + + sc->sc_flags &= ~UBT_SCO_RECV; + + if (sc->sc_hook == NULL || NG_HOOK_NOT_VALID(sc->sc_hook)) { + NG_UBT_INFO( +"%s: %s - No upstream hook\n", __func__, device_get_nameunit(sc->sc_dev)); + + return; + } + + if (s == USBD_CANCELLED) { + NG_UBT_INFO( +"%s: %s - Isoc-in xfer cancelled, pipe=%p\n", + __func__, device_get_nameunit(sc->sc_dev), sc->sc_isoc_in_pipe); + + return; + } + + if (s != USBD_NORMAL_COMPLETION) { + NG_UBT_WARN( +"%s: %s - Isoc-in xfer failed, %s (%d). No new xfer will be submitted!\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(s), s); + + if (s == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_isoc_in_pipe); + + NG_UBT_STAT_IERROR(sc->sc_stat); + + return; /* XXX FIXME we should restart after some delay */ + } + + NG_UBT_STAT_BYTES_RECV(sc->sc_stat, h->actlen); + + NG_UBT_INFO( +"%s: %s - Got %d bytes from isoc-in pipe\n", + __func__, device_get_nameunit(sc->sc_dev), h->actlen); + + /* Copy SCO data frame to mbuf */ + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + NG_UBT_ALERT( +"%s: %s - Could not allocate mbuf\n", + __func__, device_get_nameunit(sc->sc_dev)); + + NG_UBT_STAT_IERROR(sc->sc_stat); + goto done; + } + + /* Fix SCO data frame header if required */ + if (!(sc->sc_flags & UBT_HAVE_FRAME_TYPE)) { + *mtod(m, u_int8_t *) = NG_HCI_SCO_DATA_PKT; + m->m_pkthdr.len = 1; + m->m_len = min(MHLEN, h->actlen + 1); /* XXX m_copyback */ + } else { + m->m_pkthdr.len = 0; + m->m_len = min(MHLEN, h->actlen); /* XXX m_copyback */ + } + + /* + * XXX FIXME how do we know how many frames we have received? + * XXX use frlen for now. is that correct? + */ + + b = (u_int8_t *) sc->sc_isoc_in_buffer; + + for (i = 0; i < sc->sc_isoc_nframes; i++) { + b += (i * sc->sc_isoc_size); + + if (sc->sc_isoc_in_frlen[i] > 0) + m_copyback(m, m->m_pkthdr.len, + sc->sc_isoc_in_frlen[i], b); + } + + if (m->m_pkthdr.len < sizeof(*hdr)) + goto done; + + hdr = mtod(m, ng_hci_scodata_pkt_t *); + + if (hdr->length == m->m_pkthdr.len - sizeof(*hdr)) { + NG_UBT_INFO( +"%s: %s - Got complete SCO data frame, pktlen=%d, length=%d\n", + __func__, device_get_nameunit(sc->sc_dev), m->m_pkthdr.len, + hdr->length); + + NG_UBT_STAT_PCKTS_RECV(sc->sc_stat); + + NG_SEND_DATA_ONLY(i, sc->sc_hook, m); + if (i != 0) + NG_UBT_STAT_IERROR(sc->sc_stat); + } else { + NG_UBT_ERR( +"%s: %s - Invalid SCO frame size, length=%d, pktlen=%d\n", + __func__, device_get_nameunit(sc->sc_dev), hdr->length, + m->m_pkthdr.len); + + NG_UBT_STAT_IERROR(sc->sc_stat); + NG_FREE_M(m); + } +done: + ubt_isoc_in_start(sc); +} /* ubt_isoc_in_complete2 */ + +/* + * Start isochronous-out USB transfer. Must be called with node locked + */ + +static usbd_status +ubt_isoc_out_start(ubt_softc_p sc) +{ + struct mbuf *m = NULL; + u_int8_t *b = NULL; + int i, len, nframes; + usbd_status status; + + KASSERT(!(sc->sc_flags & UBT_SCO_XMIT), ( +"%s: %s - Another isoc-out request is pending\n", + __func__, device_get_nameunit(sc->sc_dev))); + + NG_BT_MBUFQ_DEQUEUE(&sc->sc_scoq, m); + if (m == NULL) { + NG_UBT_INFO( +"%s: %s - SCO data queue is empty\n", __func__, device_get_nameunit(sc->sc_dev)); + + return (USBD_NORMAL_COMPLETION); + } + + /* Copy entire SCO frame into USB transfer buffer and start transfer */ + b = (u_int8_t *) sc->sc_isoc_out_buffer; + nframes = 0; + + for (i = 0; i < sc->sc_isoc_nframes; i++) { + b += (i * sc->sc_isoc_size); + + len = min(m->m_pkthdr.len, sc->sc_isoc_size); + if (len > 0) { + m_copydata(m, 0, len, b); + m_adj(m, len); + nframes ++; + } + + sc->sc_isoc_out_frlen[i] = len; + } + + if (m->m_pkthdr.len > 0) + panic( +"%s: %s - SCO data frame is too big, nframes=%d, size=%d, len=%d\n", + __func__, device_get_nameunit(sc->sc_dev), sc->sc_isoc_nframes, + sc->sc_isoc_size, m->m_pkthdr.len); + + NG_FREE_M(m); + + /* Initialize a isoc-out USB transfer and then schedule it */ + usbd_setup_isoc_xfer( + sc->sc_isoc_out_xfer, + sc->sc_isoc_out_pipe, + (usbd_private_handle) sc->sc_node, + sc->sc_isoc_out_frlen, + nframes, + USBD_NO_COPY, + ubt_isoc_out_complete); + + NG_NODE_REF(sc->sc_node); + + status = usbd_transfer(sc->sc_isoc_out_xfer); + if (status != USBD_NORMAL_COMPLETION && status != USBD_IN_PROGRESS) { + NG_UBT_ERR( +"%s: %s - Could not start isoc-out transfer. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(status), + status); + + NG_NODE_UNREF(sc->sc_node); + + NG_BT_MBUFQ_DROP(&sc->sc_scoq); + NG_UBT_STAT_OERROR(sc->sc_stat); + } else { + NG_UBT_INFO( +"%s: %s - Isoc-out transfer has been started, nframes=%d, size=%d\n", + __func__, device_get_nameunit(sc->sc_dev), nframes, + sc->sc_isoc_size); + + sc->sc_flags |= UBT_SCO_XMIT; + status = USBD_NORMAL_COMPLETION; + } + + return (status); +} /* ubt_isoc_out_start */ + +/* + * USB isoc-out. transfer callback + */ + +static void +ubt_isoc_out_complete(usbd_xfer_handle h, usbd_private_handle p, usbd_status s) +{ + ng_send_fn((node_p) p, NULL, ubt_isoc_out_complete2, (void *) h, s); + NG_NODE_UNREF((node_p) p); +} /* ubt_isoc_out_complete */ + +static void +ubt_isoc_out_complete2(node_p node, hook_p hook, void *arg1, int arg2) +{ + ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); + usbd_xfer_handle h = (usbd_xfer_handle) arg1; + usbd_status s = (usbd_status) arg2; + + if (sc == NULL) + return; + + KASSERT((sc->sc_flags & UBT_SCO_XMIT), ( +"%s: %s - No isoc-out request is pending\n", __func__, device_get_nameunit(sc->sc_dev))); + + sc->sc_flags &= ~UBT_SCO_XMIT; + + if (s == USBD_CANCELLED) { + NG_UBT_INFO( +"%s: %s - Isoc-out xfer cancelled, pipe=%p\n", + __func__, device_get_nameunit(sc->sc_dev), + sc->sc_isoc_out_pipe); + + return; + } + + if (s != USBD_NORMAL_COMPLETION) { + NG_UBT_WARN( +"%s: %s - Isoc-out xfer failed. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(s), s); + + if (s == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_isoc_out_pipe); + + NG_UBT_STAT_OERROR(sc->sc_stat); + } else { + NG_UBT_INFO( +"%s: %s - Sent %d bytes to isoc-out pipe\n", + __func__, device_get_nameunit(sc->sc_dev), h->actlen); + + NG_UBT_STAT_BYTES_SENT(sc->sc_stat, h->actlen); + NG_UBT_STAT_PCKTS_SENT(sc->sc_stat); + } + + if (NG_BT_MBUFQ_LEN(&sc->sc_scoq) > 0) + ubt_isoc_out_start(sc); +} /* ubt_isoc_out_complete2 */ + +/* + * Abort transfers on all USB pipes + */ + +static void +ubt_reset(ubt_softc_p sc) +{ + /* Interrupt */ + if (sc->sc_intr_pipe != NULL) + usbd_abort_pipe(sc->sc_intr_pipe); + + /* Bulk-in/out */ + if (sc->sc_bulk_in_pipe != NULL) + usbd_abort_pipe(sc->sc_bulk_in_pipe); + if (sc->sc_bulk_out_pipe != NULL) + usbd_abort_pipe(sc->sc_bulk_out_pipe); + + /* Isoc-in/out */ + if (sc->sc_isoc_in_pipe != NULL) + usbd_abort_pipe(sc->sc_isoc_in_pipe); + if (sc->sc_isoc_out_pipe != NULL) + usbd_abort_pipe(sc->sc_isoc_out_pipe); + + /* Cleanup queues */ + NG_BT_MBUFQ_DRAIN(&sc->sc_cmdq); + NG_BT_MBUFQ_DRAIN(&sc->sc_aclq); + NG_BT_MBUFQ_DRAIN(&sc->sc_scoq); +} /* ubt_reset */ + +/**************************************************************************** + **************************************************************************** + ** Netgraph specific + **************************************************************************** + ****************************************************************************/ + +/* + * Netgraph node constructor. Do not allow to create node of this type. + */ + +static int +ng_ubt_constructor(node_p node) +{ + return (EINVAL); +} /* ng_ubt_constructor */ + +/* + * Netgraph node destructor. Destroy node only when device has been detached + */ + +static int +ng_ubt_shutdown(node_p node) +{ + ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); + + /* Let old node go */ + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + + if (sc == NULL) + goto done; + + /* Create Netgraph node */ + if (ng_make_node_common(&typestruct, &sc->sc_node) != 0) { + printf("%s: Could not create Netgraph node\n", + device_get_nameunit(sc->sc_dev)); + sc->sc_node = NULL; + goto done; + } + + /* Name node */ + if (ng_name_node(sc->sc_node, device_get_nameunit(sc->sc_dev)) != 0) { + printf("%s: Could not name Netgraph node\n", + device_get_nameunit(sc->sc_dev)); + NG_NODE_UNREF(sc->sc_node); + sc->sc_node = NULL; + goto done; + } + + NG_NODE_SET_PRIVATE(sc->sc_node, sc); + NG_NODE_FORCE_WRITER(sc->sc_node); +done: + return (0); +} /* ng_ubt_shutdown */ + +/* + * Create new hook. There can only be one. + */ + +static int +ng_ubt_newhook(node_p node, hook_p hook, char const *name) +{ + ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); + + if (strcmp(name, NG_UBT_HOOK) != 0) + return (EINVAL); + + if (sc->sc_hook != NULL) + return (EISCONN); + + sc->sc_hook = hook; + + return (0); +} /* ng_ubt_newhook */ + +/* + * Connect hook. Start incoming USB transfers + */ + +static int +ng_ubt_connect(hook_p hook) +{ + ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + usbd_status status; + + NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); + + /* Start intr transfer */ + status = ubt_intr_start(sc); + if (status != USBD_NORMAL_COMPLETION) { + NG_UBT_ALERT( +"%s: %s - Could not start interrupt transfer. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(status), + status); + goto fail; + } + + /* Start bulk-in transfer */ + status = ubt_bulk_in_start(sc); + if (status != USBD_NORMAL_COMPLETION) { + NG_UBT_ALERT( +"%s: %s - Could not start bulk-in transfer. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(status), + status); + goto fail; + } + +#if 0 /* XXX FIXME */ + /* Start isoc-in transfer */ + status = ubt_isoc_in_start(sc); + if (status != USBD_NORMAL_COMPLETION) { + NG_UBT_ALERT( +"%s: %s - Could not start isoc-in transfer. %s (%d)\n", + __func__, device_get_nameunit(sc->sc_dev), usbd_errstr(status), + status); + goto fail; + } +#endif + + return (0); +fail: + ubt_reset(sc); + sc->sc_hook = NULL; + + return (ENXIO); +} /* ng_ubt_connect */ + +/* + * Disconnect hook + */ + +static int +ng_ubt_disconnect(hook_p hook) +{ + ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + if (sc != NULL) { + if (hook != sc->sc_hook) + return (EINVAL); + + ubt_reset(sc); + sc->sc_hook = NULL; + } + + return (0); +} /* ng_ubt_disconnect */ + +/* + * Process control message + */ + +static int +ng_ubt_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); + struct ng_mesg *msg = NULL, *rsp = NULL; + struct ng_bt_mbufq *q = NULL; + int error = 0, queue, qlen; + + if (sc == NULL) { + NG_FREE_ITEM(item); + return (EHOSTDOWN); + } + + NGI_GET_MSG(item, msg); + + switch (msg->header.typecookie) { + case NGM_GENERIC_COOKIE: + switch (msg->header.cmd) { + case NGM_TEXT_STATUS: + NG_MKRESPONSE(rsp, msg, NG_TEXTRESPONSE, M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + snprintf(rsp->data, NG_TEXTRESPONSE, + "Hook: %s\n" \ + "Flags: %#x\n" \ + "Debug: %d\n" \ + "CMD queue: [have:%d,max:%d]\n" \ + "ACL queue: [have:%d,max:%d]\n" \ + "SCO queue: [have:%d,max:%d]", + (sc->sc_hook != NULL)? NG_UBT_HOOK : "", + sc->sc_flags, + sc->sc_debug, + NG_BT_MBUFQ_LEN(&sc->sc_cmdq), + sc->sc_cmdq.maxlen, + NG_BT_MBUFQ_LEN(&sc->sc_aclq), + sc->sc_aclq.maxlen, + NG_BT_MBUFQ_LEN(&sc->sc_scoq), + sc->sc_scoq.maxlen); + break; + + default: + error = EINVAL; + break; + } + break; + + case NGM_UBT_COOKIE: + switch (msg->header.cmd) { + case NGM_UBT_NODE_SET_DEBUG: + if (msg->header.arglen != sizeof(ng_ubt_node_debug_ep)) + error = EMSGSIZE; + else + sc->sc_debug = + *((ng_ubt_node_debug_ep *)(msg->data)); + break; + + case NGM_UBT_NODE_GET_DEBUG: + NG_MKRESPONSE(rsp, msg, sizeof(ng_ubt_node_debug_ep), + M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + *((ng_ubt_node_debug_ep *)(rsp->data)) = + sc->sc_debug; + break; + + case NGM_UBT_NODE_SET_QLEN: + if (msg->header.arglen != sizeof(ng_ubt_node_qlen_ep)) + error = EMSGSIZE; + else { + queue = ((ng_ubt_node_qlen_ep *) + (msg->data))->queue; + qlen = ((ng_ubt_node_qlen_ep *) + (msg->data))->qlen; + + if (qlen <= 0) { + error = EINVAL; + break; + } + + switch (queue) { + case NGM_UBT_NODE_QUEUE_CMD: + q = &sc->sc_cmdq; + break; + + case NGM_UBT_NODE_QUEUE_ACL: + q = &sc->sc_aclq; + break; + + case NGM_UBT_NODE_QUEUE_SCO: + q = &sc->sc_scoq; + break; + + default: + q = NULL; + error = EINVAL; + break; + } + + if (q != NULL) + q->maxlen = qlen; + } + break; + + case NGM_UBT_NODE_GET_QLEN: + if (msg->header.arglen != sizeof(ng_ubt_node_qlen_ep)) { + error = EMSGSIZE; + break; + } + + queue = ((ng_ubt_node_qlen_ep *)(msg->data))->queue; + switch (queue) { + case NGM_UBT_NODE_QUEUE_CMD: + q = &sc->sc_cmdq; + break; + + case NGM_UBT_NODE_QUEUE_ACL: + q = &sc->sc_aclq; + break; + + case NGM_UBT_NODE_QUEUE_SCO: + q = &sc->sc_scoq; + break; + + default: + q = NULL; + error = EINVAL; + break; + } + + if (q != NULL) { + NG_MKRESPONSE(rsp, msg, + sizeof(ng_ubt_node_qlen_ep), M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + ((ng_ubt_node_qlen_ep *)(rsp->data))->queue = + queue; + ((ng_ubt_node_qlen_ep *)(rsp->data))->qlen = + q->maxlen; + } + break; + + case NGM_UBT_NODE_GET_STAT: + NG_MKRESPONSE(rsp, msg, sizeof(ng_ubt_node_stat_ep), + M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + bcopy(&sc->sc_stat, rsp->data, + sizeof(ng_ubt_node_stat_ep)); + break; + + case NGM_UBT_NODE_RESET_STAT: + NG_UBT_STAT_RESET(sc->sc_stat); + break; + + default: + error = EINVAL; + break; + } + break; + + default: + error = EINVAL; + break; + } + + NG_RESPOND_MSG(error, node, item, rsp); + NG_FREE_MSG(msg); + + return (error); +} /* ng_ubt_rcvmsg */ + +/* + * Process data + */ + +static int +ng_ubt_rcvdata(hook_p hook, item_p item) +{ + ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct mbuf *m = NULL; + usbd_status (*f)(ubt_softc_p) = NULL; + struct ng_bt_mbufq *q = NULL; + int b, error = 0; + + if (sc == NULL) { + error = EHOSTDOWN; + goto done; + } + + if (hook != sc->sc_hook) { + error = EINVAL; + goto done; + } + + /* Deatch mbuf and get HCI frame type */ + NGI_GET_M(item, m); + + /* Process HCI frame */ + switch (*mtod(m, u_int8_t *)) { /* XXX call m_pullup ? */ + case NG_HCI_CMD_PKT: + f = ubt_request_start; + q = &sc->sc_cmdq; + b = UBT_CMD_XMIT; + break; + + case NG_HCI_ACL_DATA_PKT: + f = ubt_bulk_out_start; + q = &sc->sc_aclq; + b = UBT_ACL_XMIT; + break; + +#if 0 /* XXX FIXME */ + case NG_HCI_SCO_DATA_PKT: + f = ubt_isoc_out_start; + q = &sc->sc_scoq; + b = UBT_SCO_XMIT; + break; +#endif + + default: + NG_UBT_ERR( +"%s: %s - Dropping unknown/unsupported HCI frame, type=%d, pktlen=%d\n", + __func__, device_get_nameunit(sc->sc_dev), *mtod(m, u_int8_t *), + m->m_pkthdr.len); + + NG_FREE_M(m); + error = EINVAL; + + goto done; + /* NOT REACHED */ + } + + /* Loose frame type, if required */ + if (!(sc->sc_flags & UBT_NEED_FRAME_TYPE)) + m_adj(m, sizeof(u_int8_t)); + + if (NG_BT_MBUFQ_FULL(q)) { + NG_UBT_ERR( +"%s: %s - Dropping HCI frame %#x, len=%d. Queue full\n", + __func__, device_get_nameunit(sc->sc_dev), + *mtod(m, u_int8_t *), m->m_pkthdr.len); + + NG_FREE_M(m); + } else + NG_BT_MBUFQ_ENQUEUE(q, m); + + if (!(sc->sc_flags & b)) + if ((*f)(sc) != USBD_NORMAL_COMPLETION) + error = EIO; +done: + NG_FREE_ITEM(item); + + return (error); +} /* ng_ubt_rcvdata */ + diff --git a/sys/netgraph7/bluetooth/drivers/ubt/ng_ubt_var.h b/sys/netgraph7/bluetooth/drivers/ubt/ng_ubt_var.h new file mode 100644 index 0000000000..ec8f15d9c1 --- /dev/null +++ b/sys/netgraph7/bluetooth/drivers/ubt/ng_ubt_var.h @@ -0,0 +1,147 @@ +/* + * ng_ubt_var.h + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_ubt_var.h,v 1.2 2003/03/22 23:44:36 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/drivers/ubt/ng_ubt_var.h,v 1.7 2006/09/07 23:38:09 emax Exp $ + */ + +#ifndef _NG_UBT_VAR_H_ +#define _NG_UBT_VAR_H_ + +/* pullup wrapper */ +#define NG_UBT_M_PULLUP(m, s) \ + do { \ + if ((m)->m_len < (s)) \ + (m) = m_pullup((m), (s)); \ + if ((m) == NULL) \ + NG_UBT_ALERT("%s: %s - m_pullup(%d) failed\n", \ + __func__, device_get_nameunit(sc->sc_dev), (s)); \ + } while (0) + +/* Debug printf's */ +#define NG_UBT_ALERT if (sc->sc_debug >= NG_UBT_ALERT_LEVEL) printf +#define NG_UBT_ERR if (sc->sc_debug >= NG_UBT_ERR_LEVEL) printf +#define NG_UBT_WARN if (sc->sc_debug >= NG_UBT_WARN_LEVEL) printf +#define NG_UBT_INFO if (sc->sc_debug >= NG_UBT_INFO_LEVEL) printf + +/* Bluetooth USB control request type */ +#define UBT_HCI_REQUEST 0x20 +#define UBT_DEFAULT_QLEN 12 + +/* USB device softc structure */ +struct ubt_softc { + /* State */ + ng_ubt_node_debug_ep sc_debug; /* debug level */ + u_int32_t sc_flags; /* device flags */ +#define UBT_NEED_FRAME_TYPE (1 << 0) /* device required frame type */ +#define UBT_HAVE_FRAME_TYPE UBT_NEED_FRAME_TYPE +#define UBT_CMD_XMIT (1 << 1) /* CMD xmit in progress */ +#define UBT_ACL_XMIT (1 << 2) /* ACL xmit in progress */ +#define UBT_SCO_XMIT (1 << 3) /* SCO xmit in progress */ +#define UBT_EVT_RECV (1 << 4) /* EVN recv in progress */ +#define UBT_ACL_RECV (1 << 5) /* ACL recv in progress */ +#define UBT_SCO_RECV (1 << 6) /* SCO recv in progress */ +#define UBT_CTRL_DEV (1 << 7) /* ctrl device is open */ +#define UBT_INTR_DEV (1 << 8) /* intr device is open */ +#define UBT_BULK_DEV (1 << 9) /* bulk device is open */ +#define UBT_ANY_DEV (UBT_CTRL_DEV|UBT_INTR_DEV|UBT_BULK_DEV) + + ng_ubt_node_stat_ep sc_stat; /* statistic */ +#define NG_UBT_STAT_PCKTS_SENT(s) (s).pckts_sent ++ +#define NG_UBT_STAT_BYTES_SENT(s, n) (s).bytes_sent += (n) +#define NG_UBT_STAT_PCKTS_RECV(s) (s).pckts_recv ++ +#define NG_UBT_STAT_BYTES_RECV(s, n) (s).bytes_recv += (n) +#define NG_UBT_STAT_OERROR(s) (s).oerrors ++ +#define NG_UBT_STAT_IERROR(s) (s).ierrors ++ +#define NG_UBT_STAT_RESET(s) bzero(&(s), sizeof((s))) + + /* USB device specific */ + device_t sc_dev; /* pointer back to USB device */ + usbd_device_handle sc_udev; /* USB device handle */ + + usbd_interface_handle sc_iface0; /* USB interface 0 */ + usbd_interface_handle sc_iface1; /* USB interface 1 */ + + /* Interrupt pipe (HCI events) */ + int sc_intr_ep; /* interrupt endpoint */ + usbd_pipe_handle sc_intr_pipe; /* interrupt pipe handle */ + usbd_xfer_handle sc_intr_xfer; /* intr xfer */ + struct mbuf *sc_intr_buffer; /* interrupt buffer */ + + /* Control pipe (HCI commands) */ + usbd_xfer_handle sc_ctrl_xfer; /* control xfer handle */ + void *sc_ctrl_buffer; /* control buffer */ + struct ng_bt_mbufq sc_cmdq; /* HCI command queue */ +#define UBT_CTRL_BUFFER_SIZE \ + (sizeof(ng_hci_cmd_pkt_t) + NG_HCI_CMD_PKT_SIZE) + + /* Bulk in pipe (ACL data) */ + int sc_bulk_in_ep; /* bulk-in enpoint */ + usbd_pipe_handle sc_bulk_in_pipe; /* bulk-in pipe */ + usbd_xfer_handle sc_bulk_in_xfer; /* bulk-in xfer */ + struct mbuf *sc_bulk_in_buffer; /* bulk-in buffer */ + + /* Bulk out pipe (ACL data) */ + int sc_bulk_out_ep; /* bulk-out endpoint */ + usbd_pipe_handle sc_bulk_out_pipe; /* bulk-out pipe */ + usbd_xfer_handle sc_bulk_out_xfer; /* bulk-out xfer */ + void *sc_bulk_out_buffer; /* bulk-out buffer */ + struct ng_bt_mbufq sc_aclq; /* ACL data queue */ +#define UBT_BULK_BUFFER_SIZE \ + MCLBYTES /* XXX should be big enough to hold one frame */ + + /* Isoc. in pipe (SCO data) */ + int sc_isoc_in_ep; /* isoc-in endpoint */ + usbd_pipe_handle sc_isoc_in_pipe; /* isoc-in pipe */ + usbd_xfer_handle sc_isoc_in_xfer; /* isoc-in xfer */ + void *sc_isoc_in_buffer; /* isoc-in buffer */ + u_int16_t *sc_isoc_in_frlen; /* isoc-in. frame length */ + + /* Isoc. out pipe (ACL data) */ + int sc_isoc_out_ep; /* isoc-out endpoint */ + usbd_pipe_handle sc_isoc_out_pipe; /* isoc-out pipe */ + usbd_xfer_handle sc_isoc_out_xfer; /* isoc-out xfer */ + void *sc_isoc_out_buffer; /* isoc-in buffer */ + u_int16_t *sc_isoc_out_frlen; /* isoc-out. frame length */ + struct ng_bt_mbufq sc_scoq; /* SCO data queue */ + + int sc_isoc_size; /* max. size of isoc. packet */ + u_int32_t sc_isoc_nframes; /* num. isoc. frames */ +#define UBT_ISOC_BUFFER_SIZE \ + (sizeof(ng_hci_scodata_pkt_t) + NG_HCI_SCO_PKT_SIZE) + + /* Netgraph specific */ + node_p sc_node; /* pointer back to node */ + hook_p sc_hook; /* upstream hook */ +}; +typedef struct ubt_softc ubt_softc_t; +typedef struct ubt_softc * ubt_softc_p; + +#endif /* ndef _NG_UBT_VAR_H_ */ + diff --git a/sys/netgraph7/bluetooth/drivers/ubtbcmfw/ubtbcmfw.c b/sys/netgraph7/bluetooth/drivers/ubtbcmfw/ubtbcmfw.c new file mode 100644 index 0000000000..a85b6ebeec --- /dev/null +++ b/sys/netgraph7/bluetooth/drivers/ubtbcmfw/ubtbcmfw.c @@ -0,0 +1,577 @@ +/* + * ubtbcmfw.c + */ + +/*- + * Copyright (c) 2003 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ubtbcmfw.c,v 1.3 2003/10/10 19:15:08 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/drivers/ubtbcmfw/ubtbcmfw.c,v 1.18 2007/06/23 04:34:38 imp Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "usbdevs.h" + +/* + * Download firmware to BCM2033. + */ + +#define UBTBCMFW_CONFIG_NO 1 /* Config number */ +#define UBTBCMFW_IFACE_IDX 0 /* Control interface */ +#define UBTBCMFW_INTR_IN_EP 0x81 /* Fixed endpoint */ +#define UBTBCMFW_BULK_OUT_EP 0x02 /* Fixed endpoint */ +#define UBTBCMFW_INTR_IN UE_GET_ADDR(UBTBCMFW_INTR_IN_EP) +#define UBTBCMFW_BULK_OUT UE_GET_ADDR(UBTBCMFW_BULK_OUT_EP) + +struct ubtbcmfw_softc { + device_t sc_dev; /* base device */ + usbd_device_handle sc_udev; /* USB device handle */ + struct cdev *sc_ctrl_dev; /* control device */ + struct cdev *sc_intr_in_dev; /* interrupt device */ + struct cdev *sc_bulk_out_dev; /* bulk device */ + usbd_pipe_handle sc_intr_in_pipe; /* interrupt pipe */ + usbd_pipe_handle sc_bulk_out_pipe; /* bulk out pipe */ + int sc_flags; +#define UBTBCMFW_CTRL_DEV (1 << 0) +#define UBTBCMFW_INTR_IN_DEV (1 << 1) +#define UBTBCMFW_BULK_OUT_DEV (1 << 2) + int sc_refcnt; + int sc_dying; +}; + +typedef struct ubtbcmfw_softc *ubtbcmfw_softc_p; + +/* + * Device methods + */ + +#define UBTBCMFW_UNIT(n) ((minor(n) >> 4) & 0xf) +#define UBTBCMFW_ENDPOINT(n) (minor(n) & 0xf) +#define UBTBCMFW_MINOR(u, e) (((u) << 4) | (e)) +#define UBTBCMFW_BSIZE 1024 + +static d_open_t ubtbcmfw_open; +static d_close_t ubtbcmfw_close; +static d_read_t ubtbcmfw_read; +static d_write_t ubtbcmfw_write; +static d_ioctl_t ubtbcmfw_ioctl; +static d_poll_t ubtbcmfw_poll; + +static struct cdevsw ubtbcmfw_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_NEEDGIANT, + .d_open = ubtbcmfw_open, + .d_close = ubtbcmfw_close, + .d_read = ubtbcmfw_read, + .d_write = ubtbcmfw_write, + .d_ioctl = ubtbcmfw_ioctl, + .d_poll = ubtbcmfw_poll, + .d_name = "ubtbcmfw", +}; + +/* + * Module + */ + +static device_probe_t ubtbcmfw_match; +static device_attach_t ubtbcmfw_attach; +static device_detach_t ubtbcmfw_detach; + +static device_method_t ubtbcmfw_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ubtbcmfw_match), + DEVMETHOD(device_attach, ubtbcmfw_attach), + DEVMETHOD(device_detach, ubtbcmfw_detach), + + { 0, 0 } +}; + +static driver_t ubtbcmfw_driver = { + "ubtbcmfw", + ubtbcmfw_methods, + sizeof(struct ubtbcmfw_softc) +}; + +static devclass_t ubtbcmfw_devclass; + +MODULE_DEPEND(ubtbcmfw, usb, 1, 1, 1); +DRIVER_MODULE(ubtbcmfw, uhub, ubtbcmfw_driver, ubtbcmfw_devclass, + usbd_driver_load, 0); + +/* + * Probe for a USB Bluetooth device + */ + +static int +ubtbcmfw_match(device_t self) +{ +#define USB_PRODUCT_BROADCOM_BCM2033NF 0x2033 + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->iface != NULL) + return (UMATCH_NONE); + + /* Match the boot device. */ + if (uaa->vendor == USB_VENDOR_BROADCOM && + uaa->product == USB_PRODUCT_BROADCOM_BCM2033NF) + return (UMATCH_VENDOR_PRODUCT); + + return (UMATCH_NONE); +} + +/* + * Attach the device + */ + +static int +ubtbcmfw_attach(device_t self) +{ + struct ubtbcmfw_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_interface_handle iface; + usbd_status err; + + sc->sc_dev = self; + sc->sc_udev = uaa->device; + + sc->sc_ctrl_dev = sc->sc_intr_in_dev = sc->sc_bulk_out_dev = NULL; + sc->sc_intr_in_pipe = sc->sc_bulk_out_pipe = NULL; + sc->sc_flags = sc->sc_refcnt = sc->sc_dying = 0; + + err = usbd_set_config_no(sc->sc_udev, UBTBCMFW_CONFIG_NO, 1); + if (err) { + printf("%s: setting config no failed. %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + goto bad; + } + + err = usbd_device2interface_handle(sc->sc_udev, UBTBCMFW_IFACE_IDX, + &iface); + if (err) { + printf("%s: getting interface handle failed. %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + goto bad; + } + + /* Will be used as a bulk pipe */ + err = usbd_open_pipe(iface, UBTBCMFW_INTR_IN_EP, 0, + &sc->sc_intr_in_pipe); + if (err) { + printf("%s: open intr in failed. %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + goto bad; + } + + err = usbd_open_pipe(iface, UBTBCMFW_BULK_OUT_EP, 0, + &sc->sc_bulk_out_pipe); + if (err) { + printf("%s: open bulk out failed. %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + goto bad; + } + + /* Create device nodes */ + sc->sc_ctrl_dev = make_dev(&ubtbcmfw_cdevsw, + UBTBCMFW_MINOR(device_get_unit(sc->sc_dev), 0), + UID_ROOT, GID_OPERATOR, 0644, + "%s", device_get_nameunit(sc->sc_dev)); + + sc->sc_intr_in_dev = make_dev(&ubtbcmfw_cdevsw, + UBTBCMFW_MINOR(device_get_unit(sc->sc_dev), UBTBCMFW_INTR_IN), + UID_ROOT, GID_OPERATOR, 0644, + "%s.%d", device_get_nameunit(sc->sc_dev), UBTBCMFW_INTR_IN); + + sc->sc_bulk_out_dev = make_dev(&ubtbcmfw_cdevsw, + UBTBCMFW_MINOR(device_get_unit(sc->sc_dev), UBTBCMFW_BULK_OUT), + UID_ROOT, GID_OPERATOR, 0644, + "%s.%d", device_get_nameunit(sc->sc_dev), UBTBCMFW_BULK_OUT); + + return 0; +bad: + ubtbcmfw_detach(self); + return ENXIO; +} + +/* + * Detach the device + */ + +static int +ubtbcmfw_detach(device_t self) +{ + struct ubtbcmfw_softc *sc = device_get_softc(self); + + sc->sc_dying = 1; + if (-- sc->sc_refcnt >= 0) { + if (sc->sc_intr_in_pipe != NULL) + usbd_abort_pipe(sc->sc_intr_in_pipe); + + if (sc->sc_bulk_out_pipe != NULL) + usbd_abort_pipe(sc->sc_bulk_out_pipe); + + usb_detach_wait(sc->sc_dev); + } + + /* Destroy device nodes */ + if (sc->sc_bulk_out_dev != NULL) { + destroy_dev(sc->sc_bulk_out_dev); + sc->sc_bulk_out_dev = NULL; + } + + if (sc->sc_intr_in_dev != NULL) { + destroy_dev(sc->sc_intr_in_dev); + sc->sc_intr_in_dev = NULL; + } + + if (sc->sc_ctrl_dev != NULL) { + destroy_dev(sc->sc_ctrl_dev); + sc->sc_ctrl_dev = NULL; + } + + /* Close pipes */ + if (sc->sc_intr_in_pipe != NULL) { + usbd_close_pipe(sc->sc_intr_in_pipe); + sc->sc_intr_in_pipe = NULL; + } + + if (sc->sc_bulk_out_pipe != NULL) { + usbd_close_pipe(sc->sc_bulk_out_pipe); + sc->sc_intr_in_pipe = NULL; + } + + return (0); +} + +/* + * Open endpoint device + * XXX FIXME softc locking + */ + +static int +ubtbcmfw_open(struct cdev *dev, int flag, int mode, struct thread *p) +{ + ubtbcmfw_softc_p sc = NULL; + int error = 0; + + /* checks for sc != NULL */ + sc = devclass_get_softc(ubtbcmfw_devclass, UBTBCMFW_UNIT(dev)); + if (sc == NULL) + return (ENXIO); + if (sc->sc_dying) + return (ENXIO); + + switch (UBTBCMFW_ENDPOINT(dev)) { + case USB_CONTROL_ENDPOINT: + if (!(sc->sc_flags & UBTBCMFW_CTRL_DEV)) + sc->sc_flags |= UBTBCMFW_CTRL_DEV; + else + error = EBUSY; + break; + + case UBTBCMFW_INTR_IN: + if (!(sc->sc_flags & UBTBCMFW_INTR_IN_DEV)) { + if (sc->sc_intr_in_pipe != NULL) + sc->sc_flags |= UBTBCMFW_INTR_IN_DEV; + else + error = ENXIO; + } else + error = EBUSY; + break; + + case UBTBCMFW_BULK_OUT: + if (!(sc->sc_flags & UBTBCMFW_BULK_OUT_DEV)) { + if (sc->sc_bulk_out_pipe != NULL) + sc->sc_flags |= UBTBCMFW_BULK_OUT_DEV; + else + error = ENXIO; + } else + error = EBUSY; + break; + + default: + error = ENXIO; + break; + } + + return (error); +} + +/* + * Close endpoint device + * XXX FIXME softc locking + */ + +static int +ubtbcmfw_close(struct cdev *dev, int flag, int mode, struct thread *p) +{ + ubtbcmfw_softc_p sc = NULL; + + sc = devclass_get_softc(ubtbcmfw_devclass, UBTBCMFW_UNIT(dev)); + if (sc == NULL) + return (ENXIO); + + switch (UBTBCMFW_ENDPOINT(dev)) { + case USB_CONTROL_ENDPOINT: + sc->sc_flags &= ~UBTBCMFW_CTRL_DEV; + break; + + case UBTBCMFW_INTR_IN: + if (sc->sc_intr_in_pipe != NULL) + usbd_abort_pipe(sc->sc_intr_in_pipe); + + sc->sc_flags &= ~UBTBCMFW_INTR_IN_DEV; + break; + + case UBTBCMFW_BULK_OUT: + if (sc->sc_bulk_out_pipe != NULL) + usbd_abort_pipe(sc->sc_bulk_out_pipe); + + sc->sc_flags &= ~UBTBCMFW_BULK_OUT_DEV; + break; + } + + return (0); +} + +/* + * Read from the endpoint device + * XXX FIXME softc locking + */ + +static int +ubtbcmfw_read(struct cdev *dev, struct uio *uio, int flag) +{ + ubtbcmfw_softc_p sc = NULL; + u_int8_t buf[UBTBCMFW_BSIZE]; + usbd_xfer_handle xfer; + usbd_status err; + int n, tn, error = 0; + + sc = devclass_get_softc(ubtbcmfw_devclass, UBTBCMFW_UNIT(dev)); + if (sc == NULL || sc->sc_dying) + return (ENXIO); + + if (UBTBCMFW_ENDPOINT(dev) != UBTBCMFW_INTR_IN) + return (EOPNOTSUPP); + if (sc->sc_intr_in_pipe == NULL) + return (ENXIO); + + xfer = usbd_alloc_xfer(sc->sc_udev); + if (xfer == NULL) + return (ENOMEM); + + sc->sc_refcnt ++; + + while ((n = min(sizeof(buf), uio->uio_resid)) != 0) { + tn = n; + err = usbd_bulk_transfer(xfer, sc->sc_intr_in_pipe, + USBD_SHORT_XFER_OK, USBD_DEFAULT_TIMEOUT, + buf, &tn, "bcmrd"); + switch (err) { + case USBD_NORMAL_COMPLETION: + error = uiomove(buf, tn, uio); + break; + + case USBD_INTERRUPTED: + error = EINTR; + break; + + case USBD_TIMEOUT: + error = ETIMEDOUT; + break; + + default: + error = EIO; + break; + } + + if (error != 0 || tn < n) + break; + } + + usbd_free_xfer(xfer); + + if (-- sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + return (error); +} + +/* + * Write into the endpoint device + * XXX FIXME softc locking + */ + +static int +ubtbcmfw_write(struct cdev *dev, struct uio *uio, int flag) +{ + ubtbcmfw_softc_p sc = NULL; + u_int8_t buf[UBTBCMFW_BSIZE]; + usbd_xfer_handle xfer; + usbd_status err; + int n, error = 0; + + sc = devclass_get_softc(ubtbcmfw_devclass, UBTBCMFW_UNIT(dev)); + if (sc == NULL || sc->sc_dying) + return (ENXIO); + + if (UBTBCMFW_ENDPOINT(dev) != UBTBCMFW_BULK_OUT) + return (EOPNOTSUPP); + if (sc->sc_bulk_out_pipe == NULL) + return (ENXIO); + + xfer = usbd_alloc_xfer(sc->sc_udev); + if (xfer == NULL) + return (ENOMEM); + + sc->sc_refcnt ++; + + while ((n = min(sizeof(buf), uio->uio_resid)) != 0) { + error = uiomove(buf, n, uio); + if (error != 0) + break; + + err = usbd_bulk_transfer(xfer, sc->sc_bulk_out_pipe, + 0, USBD_DEFAULT_TIMEOUT, buf, &n, "bcmwr"); + switch (err) { + case USBD_NORMAL_COMPLETION: + break; + + case USBD_INTERRUPTED: + error = EINTR; + break; + + case USBD_TIMEOUT: + error = ETIMEDOUT; + break; + + default: + error = EIO; + break; + } + + if (error != 0) + break; + } + + usbd_free_xfer(xfer); + + if (-- sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + return (error); +} + +/* + * Process ioctl on the endpoint device + * XXX FIXME softc locking + */ + +static int +ubtbcmfw_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, + struct thread *p) +{ + ubtbcmfw_softc_p sc = NULL; + int error = 0; + + sc = devclass_get_softc(ubtbcmfw_devclass, UBTBCMFW_UNIT(dev)); + if (sc == NULL || sc->sc_dying) + return (ENXIO); + + if (UBTBCMFW_ENDPOINT(dev) != USB_CONTROL_ENDPOINT) + return (EOPNOTSUPP); + + sc->sc_refcnt ++; + + switch (cmd) { + case USB_GET_DEVICE_DESC: + *(usb_device_descriptor_t *) data = + *usbd_get_device_descriptor(sc->sc_udev); + break; + + default: + error = EINVAL; + break; + } + + if (-- sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + return (error); +} + +/* + * Poll the endpoint device + * XXX FIXME softc locking + */ + +static int +ubtbcmfw_poll(struct cdev *dev, int events, struct thread *p) +{ + ubtbcmfw_softc_p sc = NULL; + int revents = 0; + + sc = devclass_get_softc(ubtbcmfw_devclass, UBTBCMFW_UNIT(dev)); + if (sc == NULL) + return (ENXIO); + + switch (UBTBCMFW_ENDPOINT(dev)) { + case UBTBCMFW_INTR_IN: + if (sc->sc_intr_in_pipe != NULL) + revents |= events & (POLLIN | POLLRDNORM); + else + revents = ENXIO; + break; + + case UBTBCMFW_BULK_OUT: + if (sc->sc_bulk_out_pipe != NULL) + revents |= events & (POLLOUT | POLLWRNORM); + else + revents = ENXIO; + break; + + default: + revents = EOPNOTSUPP; + break; + } + + return (revents); +} diff --git a/sys/netgraph7/bluetooth/hci/TODO b/sys/netgraph7/bluetooth/hci/TODO new file mode 100644 index 0000000000..0cc8f74184 --- /dev/null +++ b/sys/netgraph7/bluetooth/hci/TODO @@ -0,0 +1,29 @@ +$Id: TODO,v 1.2 2003/04/26 22:36:29 max Exp $ +$FreeBSD: src/sys/netgraph/bluetooth/hci/TODO,v 1.2 2003/05/10 21:44:40 julian Exp $ + +FIXME/TODO list + +This is a list of open issues for HCI node + +1) Locking/SMP + + External code now uses ng_send_fn to inject data into Netgraph, so + it should be fine as long as Netgraph is SMP safe. Just need to + verify it. + +2) HCI QoS handling + + Some code exists, but i have no idea how it should work. Will + understand and fix later. I only have CSR based hardware and + it does not support QoS. + +3) Add proper handling for some HCI commands + + HCI testing commands is one example. Also might implement Host to + Host Controller flow control (not sure if it is required). + +4) Code cleanup + + Verify return codes from functions + Remove some waringns/errors + diff --git a/sys/netgraph7/bluetooth/hci/ng_hci_cmds.c b/sys/netgraph7/bluetooth/hci/ng_hci_cmds.c new file mode 100644 index 0000000000..aa31c007fa --- /dev/null +++ b/sys/netgraph7/bluetooth/hci/ng_hci_cmds.c @@ -0,0 +1,900 @@ +/* + * ng_hci_cmds.c + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_hci_cmds.c,v 1.4 2003/09/08 18:57:51 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/hci/ng_hci_cmds.c,v 1.7 2005/01/07 01:45:43 imp Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/****************************************************************************** + ****************************************************************************** + ** HCI commands processing module + ****************************************************************************** + ******************************************************************************/ + +#undef min +#define min(a, b) ((a) < (b))? (a) : (b) + +static int complete_command (ng_hci_unit_p, int, struct mbuf **); + +static int process_link_control_params + (ng_hci_unit_p, u_int16_t, struct mbuf *, struct mbuf *); +static int process_link_policy_params + (ng_hci_unit_p, u_int16_t, struct mbuf *, struct mbuf *); +static int process_hc_baseband_params + (ng_hci_unit_p, u_int16_t, struct mbuf *, struct mbuf *); +static int process_info_params + (ng_hci_unit_p, u_int16_t, struct mbuf *, struct mbuf *); +static int process_status_params + (ng_hci_unit_p, u_int16_t, struct mbuf *, struct mbuf *); +static int process_testing_params + (ng_hci_unit_p, u_int16_t, struct mbuf *, struct mbuf *); + +static int process_link_control_status + (ng_hci_unit_p, ng_hci_command_status_ep *, struct mbuf *); +static int process_link_policy_status + (ng_hci_unit_p, ng_hci_command_status_ep *, struct mbuf *); + +/* + * Send HCI command to the driver. + */ + +int +ng_hci_send_command(ng_hci_unit_p unit) +{ + struct mbuf *m0 = NULL, *m = NULL; + int free, error = 0; + + /* Check if other command is pending */ + if (unit->state & NG_HCI_UNIT_COMMAND_PENDING) + return (0); + + /* Check if unit can accept our command */ + NG_HCI_BUFF_CMD_GET(unit->buffer, free); + if (free == 0) + return (0); + + /* Check if driver hook is still ok */ + if (unit->drv == NULL || NG_HOOK_NOT_VALID(unit->drv)) { + NG_HCI_WARN( +"%s: %s - hook \"%s\" is not connected or valid\n", + __func__, NG_NODE_NAME(unit->node), NG_HCI_HOOK_DRV); + + NG_BT_MBUFQ_DRAIN(&unit->cmdq); + + return (ENOTCONN); + } + + /* + * Get first command from queue, give it to RAW hook then + * make copy of it and send it to the driver + */ + + m0 = NG_BT_MBUFQ_FIRST(&unit->cmdq); + if (m0 == NULL) + return (0); + + ng_hci_mtap(unit, m0); + + m = m_dup(m0, M_DONTWAIT); + if (m != NULL) + NG_SEND_DATA_ONLY(error, unit->drv, m); + else + error = ENOBUFS; + + if (error != 0) + NG_HCI_ERR( +"%s: %s - could not send HCI command, error=%d\n", + __func__, NG_NODE_NAME(unit->node), error); + + /* + * Even if we were not able to send command we still pretend + * that everything is OK and let timeout handle that. + */ + + NG_HCI_BUFF_CMD_USE(unit->buffer, 1); + NG_HCI_STAT_CMD_SENT(unit->stat); + NG_HCI_STAT_BYTES_SENT(unit->stat, m0->m_pkthdr.len); + + /* + * Note: ng_hci_command_timeout() will set + * NG_HCI_UNIT_COMMAND_PENDING flag + */ + + ng_hci_command_timeout(unit); + + return (0); +} /* ng_hci_send_command */ + +/* + * Process HCI Command_Compete event. Complete HCI command, and do post + * processing on the command parameters (cp) and command return parameters + * (e) if required (for example adjust state). + */ + +int +ng_hci_process_command_complete(ng_hci_unit_p unit, struct mbuf *e) +{ + ng_hci_command_compl_ep *ep = NULL; + struct mbuf *cp = NULL; + int error = 0; + + /* Get event packet and update command buffer info */ + NG_HCI_M_PULLUP(e, sizeof(*ep)); + if (e == NULL) + return (ENOBUFS); /* XXX this is bad */ + + ep = mtod(e, ng_hci_command_compl_ep *); + NG_HCI_BUFF_CMD_SET(unit->buffer, ep->num_cmd_pkts); + + /* Check for special NOOP command */ + if (ep->opcode == 0x0000) { + NG_FREE_M(e); + goto out; + } + + /* Try to match first command item in the queue */ + error = complete_command(unit, ep->opcode, &cp); + if (error != 0) { + NG_FREE_M(e); + goto out; + } + + /* + * Perform post processing on command parameters and return parameters + * do it only if status is OK (status == 0). Status is the first byte + * of any command return parameters. + */ + + ep->opcode = le16toh(ep->opcode); + m_adj(e, sizeof(*ep)); + + if (*mtod(e, u_int8_t *) == 0) { /* XXX m_pullup here? */ + switch (NG_HCI_OGF(ep->opcode)) { + case NG_HCI_OGF_LINK_CONTROL: + error = process_link_control_params(unit, + NG_HCI_OCF(ep->opcode), cp, e); + break; + + case NG_HCI_OGF_LINK_POLICY: + error = process_link_policy_params(unit, + NG_HCI_OCF(ep->opcode), cp, e); + break; + + case NG_HCI_OGF_HC_BASEBAND: + error = process_hc_baseband_params(unit, + NG_HCI_OCF(ep->opcode), cp, e); + break; + + case NG_HCI_OGF_INFO: + error = process_info_params(unit, + NG_HCI_OCF(ep->opcode), cp, e); + break; + + case NG_HCI_OGF_STATUS: + error = process_status_params(unit, + NG_HCI_OCF(ep->opcode), cp, e); + break; + + case NG_HCI_OGF_TESTING: + error = process_testing_params(unit, + NG_HCI_OCF(ep->opcode), cp, e); + break; + + case NG_HCI_OGF_BT_LOGO: + case NG_HCI_OGF_VENDOR: + NG_FREE_M(cp); + NG_FREE_M(e); + break; + + default: + NG_FREE_M(cp); + NG_FREE_M(e); + error = EINVAL; + break; + } + } else { + NG_HCI_ERR( +"%s: %s - HCI command failed, OGF=%#x, OCF=%#x, status=%#x\n", + __func__, NG_NODE_NAME(unit->node), + NG_HCI_OGF(ep->opcode), NG_HCI_OCF(ep->opcode), + *mtod(e, u_int8_t *)); + + NG_FREE_M(cp); + NG_FREE_M(e); + } +out: + ng_hci_send_command(unit); + + return (error); +} /* ng_hci_process_command_complete */ + +/* + * Process HCI Command_Status event. Check the status (mst) and do post + * processing (if required). + */ + +int +ng_hci_process_command_status(ng_hci_unit_p unit, struct mbuf *e) +{ + ng_hci_command_status_ep *ep = NULL; + struct mbuf *cp = NULL; + int error = 0; + + /* Update command buffer info */ + NG_HCI_M_PULLUP(e, sizeof(*ep)); + if (e == NULL) + return (ENOBUFS); /* XXX this is bad */ + + ep = mtod(e, ng_hci_command_status_ep *); + NG_HCI_BUFF_CMD_SET(unit->buffer, ep->num_cmd_pkts); + + /* Check for special NOOP command */ + if (ep->opcode == 0x0000) + goto out; + + /* Try to match first command item in the queue */ + error = complete_command(unit, ep->opcode, &cp); + if (error != 0) + goto out; + + /* + * Perform post processing on HCI Command_Status event + */ + + ep->opcode = le16toh(ep->opcode); + + switch (NG_HCI_OGF(ep->opcode)) { + case NG_HCI_OGF_LINK_CONTROL: + error = process_link_control_status(unit, ep, cp); + break; + + case NG_HCI_OGF_LINK_POLICY: + error = process_link_policy_status(unit, ep, cp); + break; + + case NG_HCI_OGF_BT_LOGO: + case NG_HCI_OGF_VENDOR: + NG_FREE_M(cp); + break; + + case NG_HCI_OGF_HC_BASEBAND: + case NG_HCI_OGF_INFO: + case NG_HCI_OGF_STATUS: + case NG_HCI_OGF_TESTING: + default: + NG_FREE_M(cp); + error = EINVAL; + break; + } +out: + NG_FREE_M(e); + ng_hci_send_command(unit); + + return (error); +} /* ng_hci_process_command_status */ + +/* + * Complete queued HCI command. + */ + +static int +complete_command(ng_hci_unit_p unit, int opcode, struct mbuf **cp) +{ + struct mbuf *m = NULL; + + /* Check unit state */ + if (!(unit->state & NG_HCI_UNIT_COMMAND_PENDING)) { + NG_HCI_ALERT( +"%s: %s - no pending command, state=%#x\n", + __func__, NG_NODE_NAME(unit->node), unit->state); + + return (EINVAL); + } + + /* Get first command in the queue */ + m = NG_BT_MBUFQ_FIRST(&unit->cmdq); + if (m == NULL) { + NG_HCI_ALERT( +"%s: %s - empty command queue?!\n", __func__, NG_NODE_NAME(unit->node)); + + return (EINVAL); + } + + /* + * Match command opcode, if does not match - do nothing and + * let timeout handle that. + */ + + if (mtod(m, ng_hci_cmd_pkt_t *)->opcode != opcode) { + NG_HCI_ALERT( +"%s: %s - command queue is out of sync\n", __func__, NG_NODE_NAME(unit->node)); + + return (EINVAL); + } + + /* + * Now we can remove command timeout, dequeue completed command + * and return command parameters. ng_hci_command_untimeout will + * drop NG_HCI_UNIT_COMMAND_PENDING flag. + * Note: if ng_hci_command_untimeout() fails (returns non-zero) + * then timeout aready happened and timeout message went info node + * queue. In this case we ignore command completion and pretend + * there is a timeout. + */ + + if (ng_hci_command_untimeout(unit) != 0) + return (ETIMEDOUT); + + NG_BT_MBUFQ_DEQUEUE(&unit->cmdq, *cp); + m_adj(*cp, sizeof(ng_hci_cmd_pkt_t)); + + return (0); +} /* complete_command */ + +/* + * Process HCI command timeout + */ + +void +ng_hci_process_command_timeout(node_p node, hook_p hook, void *arg1, int arg2) +{ + ng_hci_unit_p unit = NULL; + struct mbuf *m = NULL; + u_int16_t opcode; + + if (NG_NODE_NOT_VALID(node)) { + printf("%s: Netgraph node is not valid\n", __func__); + return; + } + + unit = (ng_hci_unit_p) NG_NODE_PRIVATE(node); + + if (unit->state & NG_HCI_UNIT_COMMAND_PENDING) { + unit->state &= ~NG_HCI_UNIT_COMMAND_PENDING; + + NG_BT_MBUFQ_DEQUEUE(&unit->cmdq, m); + if (m == NULL) { + NG_HCI_ALERT( +"%s: %s - command queue is out of sync!\n", __func__, NG_NODE_NAME(unit->node)); + + return; + } + + opcode = le16toh(mtod(m, ng_hci_cmd_pkt_t *)->opcode); + NG_FREE_M(m); + + NG_HCI_ERR( +"%s: %s - unable to complete HCI command OGF=%#x, OCF=%#x. Timeout\n", + __func__, NG_NODE_NAME(unit->node), NG_HCI_OGF(opcode), + NG_HCI_OCF(opcode)); + + /* Try to send more commands */ + NG_HCI_BUFF_CMD_SET(unit->buffer, 1); + ng_hci_send_command(unit); + } else + NG_HCI_ALERT( +"%s: %s - no pending command\n", __func__, NG_NODE_NAME(unit->node)); +} /* ng_hci_process_command_timeout */ + +/* + * Process link command return parameters + */ + +static int +process_link_control_params(ng_hci_unit_p unit, u_int16_t ocf, + struct mbuf *mcp, struct mbuf *mrp) +{ + int error = 0; + + switch (ocf) { + case NG_HCI_OCF_INQUIRY_CANCEL: + case NG_HCI_OCF_PERIODIC_INQUIRY: + case NG_HCI_OCF_EXIT_PERIODIC_INQUIRY: + case NG_HCI_OCF_LINK_KEY_REP: + case NG_HCI_OCF_LINK_KEY_NEG_REP: + case NG_HCI_OCF_PIN_CODE_REP: + case NG_HCI_OCF_PIN_CODE_NEG_REP: + /* These do not need post processing */ + break; + + case NG_HCI_OCF_INQUIRY: + case NG_HCI_OCF_CREATE_CON: + case NG_HCI_OCF_DISCON: + case NG_HCI_OCF_ADD_SCO_CON: + case NG_HCI_OCF_ACCEPT_CON: + case NG_HCI_OCF_REJECT_CON: + case NG_HCI_OCF_CHANGE_CON_PKT_TYPE: + case NG_HCI_OCF_AUTH_REQ: + case NG_HCI_OCF_SET_CON_ENCRYPTION: + case NG_HCI_OCF_CHANGE_CON_LINK_KEY: + case NG_HCI_OCF_MASTER_LINK_KEY: + case NG_HCI_OCF_REMOTE_NAME_REQ: + case NG_HCI_OCF_READ_REMOTE_FEATURES: + case NG_HCI_OCF_READ_REMOTE_VER_INFO: + case NG_HCI_OCF_READ_CLOCK_OFFSET: + default: + + /* + * None of these command was supposed to generate + * Command_Complete event. Instead Command_Status event + * should have been generated and then appropriate event + * should have been sent to indicate the final result. + */ + + error = EINVAL; + break; + } + + NG_FREE_M(mcp); + NG_FREE_M(mrp); + + return (error); +} /* process_link_control_params */ + +/* + * Process link policy command return parameters + */ + +static int +process_link_policy_params(ng_hci_unit_p unit, u_int16_t ocf, + struct mbuf *mcp, struct mbuf *mrp) +{ + int error = 0; + + switch (ocf){ + case NG_HCI_OCF_ROLE_DISCOVERY: { + ng_hci_role_discovery_rp *rp = NULL; + ng_hci_unit_con_t *con = NULL; + u_int16_t h; + + NG_HCI_M_PULLUP(mrp, sizeof(*rp)); + if (mrp != NULL) { + rp = mtod(mrp, ng_hci_role_discovery_rp *); + + h = NG_HCI_CON_HANDLE(le16toh(rp->con_handle)); + con = ng_hci_con_by_handle(unit, h); + if (con == NULL) { + NG_HCI_ALERT( +"%s: %s - invalid connection handle=%d\n", + __func__, NG_NODE_NAME(unit->node), h); + error = ENOENT; + } else if (con->link_type != NG_HCI_LINK_ACL) { + NG_HCI_ALERT( +"%s: %s - invalid link type=%d\n", __func__, NG_NODE_NAME(unit->node), + con->link_type); + error = EINVAL; + } else + con->role = rp->role; + } else + error = ENOBUFS; + } break; + + case NG_HCI_OCF_READ_LINK_POLICY_SETTINGS: + case NG_HCI_OCF_WRITE_LINK_POLICY_SETTINGS: + /* These do not need post processing */ + break; + + case NG_HCI_OCF_HOLD_MODE: + case NG_HCI_OCF_SNIFF_MODE: + case NG_HCI_OCF_EXIT_SNIFF_MODE: + case NG_HCI_OCF_PARK_MODE: + case NG_HCI_OCF_EXIT_PARK_MODE: + case NG_HCI_OCF_QOS_SETUP: + case NG_HCI_OCF_SWITCH_ROLE: + default: + + /* + * None of these command was supposed to generate + * Command_Complete event. Instead Command_Status event + * should have been generated and then appropriate event + * should have been sent to indicate the final result. + */ + + error = EINVAL; + break; + } + + NG_FREE_M(mcp); + NG_FREE_M(mrp); + + return (error); +} /* process_link_policy_params */ + +/* + * Process HC and baseband command return parameters + */ + +int +process_hc_baseband_params(ng_hci_unit_p unit, u_int16_t ocf, + struct mbuf *mcp, struct mbuf *mrp) +{ + int error = 0; + + switch (ocf) { + case NG_HCI_OCF_SET_EVENT_MASK: + case NG_HCI_OCF_SET_EVENT_FILTER: + case NG_HCI_OCF_FLUSH: /* XXX Do we need to handle that? */ + case NG_HCI_OCF_READ_PIN_TYPE: + case NG_HCI_OCF_WRITE_PIN_TYPE: + case NG_HCI_OCF_CREATE_NEW_UNIT_KEY: + case NG_HCI_OCF_WRITE_STORED_LINK_KEY: + case NG_HCI_OCF_WRITE_CON_ACCEPT_TIMO: + case NG_HCI_OCF_WRITE_PAGE_TIMO: + case NG_HCI_OCF_READ_SCAN_ENABLE: + case NG_HCI_OCF_WRITE_SCAN_ENABLE: + case NG_HCI_OCF_WRITE_PAGE_SCAN_ACTIVITY: + case NG_HCI_OCF_WRITE_INQUIRY_SCAN_ACTIVITY: + case NG_HCI_OCF_READ_AUTH_ENABLE: + case NG_HCI_OCF_WRITE_AUTH_ENABLE: + case NG_HCI_OCF_READ_ENCRYPTION_MODE: + case NG_HCI_OCF_WRITE_ENCRYPTION_MODE: + case NG_HCI_OCF_WRITE_VOICE_SETTINGS: + case NG_HCI_OCF_READ_NUM_BROADCAST_RETRANS: + case NG_HCI_OCF_WRITE_NUM_BROADCAST_RETRANS: + case NG_HCI_OCF_READ_HOLD_MODE_ACTIVITY: + case NG_HCI_OCF_WRITE_HOLD_MODE_ACTIVITY: + case NG_HCI_OCF_READ_SCO_FLOW_CONTROL: + case NG_HCI_OCF_WRITE_SCO_FLOW_CONTROL: + case NG_HCI_OCF_H2HC_FLOW_CONTROL: /* XXX Not supported this time */ + case NG_HCI_OCF_HOST_BUFFER_SIZE: + case NG_HCI_OCF_READ_IAC_LAP: + case NG_HCI_OCF_WRITE_IAC_LAP: + case NG_HCI_OCF_READ_PAGE_SCAN_PERIOD: + case NG_HCI_OCF_WRITE_PAGE_SCAN_PERIOD: + case NG_HCI_OCF_READ_PAGE_SCAN: + case NG_HCI_OCF_WRITE_PAGE_SCAN: + case NG_HCI_OCF_READ_LINK_SUPERVISION_TIMO: + case NG_HCI_OCF_WRITE_LINK_SUPERVISION_TIMO: + case NG_HCI_OCF_READ_SUPPORTED_IAC_NUM: + case NG_HCI_OCF_READ_STORED_LINK_KEY: + case NG_HCI_OCF_DELETE_STORED_LINK_KEY: + case NG_HCI_OCF_READ_CON_ACCEPT_TIMO: + case NG_HCI_OCF_READ_PAGE_TIMO: + case NG_HCI_OCF_READ_PAGE_SCAN_ACTIVITY: + case NG_HCI_OCF_READ_INQUIRY_SCAN_ACTIVITY: + case NG_HCI_OCF_READ_VOICE_SETTINGS: + case NG_HCI_OCF_READ_AUTO_FLUSH_TIMO: + case NG_HCI_OCF_WRITE_AUTO_FLUSH_TIMO: + case NG_HCI_OCF_READ_XMIT_LEVEL: + case NG_HCI_OCF_HOST_NUM_COMPL_PKTS: /* XXX Can get here? */ + case NG_HCI_OCF_CHANGE_LOCAL_NAME: + case NG_HCI_OCF_READ_LOCAL_NAME: + case NG_HCI_OCF_READ_UNIT_CLASS: + case NG_HCI_OCF_WRITE_UNIT_CLASS: + /* These do not need post processing */ + break; + + case NG_HCI_OCF_RESET: { + ng_hci_unit_con_p con = NULL; + int size; + + /* + * XXX + * + * After RESET command unit goes into standby mode + * and all operational state is lost. Host controller + * will revert to default values for all parameters. + * + * For now we shall terminate all connections and drop + * inited bit. After RESET unit must be re-initialized. + */ + + while (!LIST_EMPTY(&unit->con_list)) { + con = LIST_FIRST(&unit->con_list); + + /* Remove all timeouts (if any) */ + if (con->flags & NG_HCI_CON_TIMEOUT_PENDING) + ng_hci_con_untimeout(con); + + /* Connection terminated by local host */ + ng_hci_lp_discon_ind(con, 0x16); + ng_hci_free_con(con); + } + + NG_HCI_BUFF_ACL_TOTAL(unit->buffer, size); + NG_HCI_BUFF_ACL_FREE(unit->buffer, size); + + NG_HCI_BUFF_SCO_TOTAL(unit->buffer, size); + NG_HCI_BUFF_SCO_FREE(unit->buffer, size); + + unit->state &= ~NG_HCI_UNIT_INITED; + } break; + + default: + error = EINVAL; + break; + } + + NG_FREE_M(mcp); + NG_FREE_M(mrp); + + return (error); +} /* process_hc_baseband_params */ + +/* + * Process info command return parameters + */ + +static int +process_info_params(ng_hci_unit_p unit, u_int16_t ocf, struct mbuf *mcp, + struct mbuf *mrp) +{ + int error = 0, len; + + switch (ocf) { + case NG_HCI_OCF_READ_LOCAL_VER: + case NG_HCI_OCF_READ_COUNTRY_CODE: + break; + + case NG_HCI_OCF_READ_LOCAL_FEATURES: + m_adj(mrp, sizeof(u_int8_t)); + len = min(mrp->m_pkthdr.len, sizeof(unit->features)); + m_copydata(mrp, 0, len, (caddr_t) unit->features); + break; + + case NG_HCI_OCF_READ_BUFFER_SIZE: { + ng_hci_read_buffer_size_rp *rp = NULL; + + /* Do not update buffer descriptor if node was initialized */ + if ((unit->state & NG_HCI_UNIT_READY) == NG_HCI_UNIT_READY) + break; + + NG_HCI_M_PULLUP(mrp, sizeof(*rp)); + if (mrp != NULL) { + rp = mtod(mrp, ng_hci_read_buffer_size_rp *); + + NG_HCI_BUFF_ACL_SET( + unit->buffer, + le16toh(rp->num_acl_pkt), /* number */ + le16toh(rp->max_acl_size), /* size */ + le16toh(rp->num_acl_pkt) /* free */ + ); + + NG_HCI_BUFF_SCO_SET( + unit->buffer, + le16toh(rp->num_sco_pkt), /* number */ + rp->max_sco_size, /* size */ + le16toh(rp->num_sco_pkt) /* free */ + ); + + /* Let upper layers know */ + ng_hci_node_is_up(unit->node, unit->acl, NULL, 0); + ng_hci_node_is_up(unit->node, unit->sco, NULL, 0); + } else + error = ENOBUFS; + } break; + + case NG_HCI_OCF_READ_BDADDR: + /* Do not update BD_ADDR if node was initialized */ + if ((unit->state & NG_HCI_UNIT_READY) == NG_HCI_UNIT_READY) + break; + + m_adj(mrp, sizeof(u_int8_t)); + len = min(mrp->m_pkthdr.len, sizeof(unit->bdaddr)); + m_copydata(mrp, 0, len, (caddr_t) &unit->bdaddr); + + /* Let upper layers know */ + ng_hci_node_is_up(unit->node, unit->acl, NULL, 0); + ng_hci_node_is_up(unit->node, unit->sco, NULL, 0); + break; + + default: + error = EINVAL; + break; + } + + NG_FREE_M(mcp); + NG_FREE_M(mrp); + + return (error); +} /* process_info_params */ + +/* + * Process status command return parameters + */ + +static int +process_status_params(ng_hci_unit_p unit, u_int16_t ocf, struct mbuf *mcp, + struct mbuf *mrp) +{ + int error = 0; + + switch (ocf) { + case NG_HCI_OCF_READ_FAILED_CONTACT_CNTR: + case NG_HCI_OCF_RESET_FAILED_CONTACT_CNTR: + case NG_HCI_OCF_GET_LINK_QUALITY: + case NG_HCI_OCF_READ_RSSI: + /* These do not need post processing */ + break; + + default: + error = EINVAL; + break; + } + + NG_FREE_M(mcp); + NG_FREE_M(mrp); + + return (error); +} /* process_status_params */ + +/* + * Process testing command return parameters + */ + +int +process_testing_params(ng_hci_unit_p unit, u_int16_t ocf, struct mbuf *mcp, + struct mbuf *mrp) +{ + int error = 0; + + switch (ocf) { + + /* + * XXX FIXME + * We do not support these features at this time. However, + * HCI node could support this and do something smart. At least + * node can change unit state. + */ + + case NG_HCI_OCF_READ_LOOPBACK_MODE: + case NG_HCI_OCF_WRITE_LOOPBACK_MODE: + case NG_HCI_OCF_ENABLE_UNIT_UNDER_TEST: + break; + + default: + error = EINVAL; + break; + } + + NG_FREE_M(mcp); + NG_FREE_M(mrp); + + return (error); +} /* process_testing_params */ + +/* + * Process link control command status + */ + +static int +process_link_control_status(ng_hci_unit_p unit, ng_hci_command_status_ep *ep, + struct mbuf *mcp) +{ + int error = 0; + + switch (NG_HCI_OCF(ep->opcode)) { + case NG_HCI_OCF_INQUIRY: + case NG_HCI_OCF_DISCON: /* XXX */ + case NG_HCI_OCF_REJECT_CON: /* XXX */ + case NG_HCI_OCF_CHANGE_CON_PKT_TYPE: + case NG_HCI_OCF_AUTH_REQ: + case NG_HCI_OCF_SET_CON_ENCRYPTION: + case NG_HCI_OCF_CHANGE_CON_LINK_KEY: + case NG_HCI_OCF_MASTER_LINK_KEY: + case NG_HCI_OCF_REMOTE_NAME_REQ: + case NG_HCI_OCF_READ_REMOTE_FEATURES: + case NG_HCI_OCF_READ_REMOTE_VER_INFO: + case NG_HCI_OCF_READ_CLOCK_OFFSET: + /* These do not need post processing */ + break; + + case NG_HCI_OCF_CREATE_CON: + break; + + case NG_HCI_OCF_ADD_SCO_CON: + break; + + case NG_HCI_OCF_ACCEPT_CON: + break; + + case NG_HCI_OCF_INQUIRY_CANCEL: + case NG_HCI_OCF_PERIODIC_INQUIRY: + case NG_HCI_OCF_EXIT_PERIODIC_INQUIRY: + case NG_HCI_OCF_LINK_KEY_REP: + case NG_HCI_OCF_LINK_KEY_NEG_REP: + case NG_HCI_OCF_PIN_CODE_REP: + case NG_HCI_OCF_PIN_CODE_NEG_REP: + default: + + /* + * None of these command was supposed to generate + * Command_Status event. Instead Command_Complete event + * should have been sent. + */ + + error = EINVAL; + break; + } + + NG_FREE_M(mcp); + + return (error); +} /* process_link_control_status */ + +/* + * Process link policy command status + */ + +static int +process_link_policy_status(ng_hci_unit_p unit, ng_hci_command_status_ep *ep, + struct mbuf *mcp) +{ + int error = 0; + + switch (NG_HCI_OCF(ep->opcode)) { + case NG_HCI_OCF_HOLD_MODE: + case NG_HCI_OCF_SNIFF_MODE: + case NG_HCI_OCF_EXIT_SNIFF_MODE: + case NG_HCI_OCF_PARK_MODE: + case NG_HCI_OCF_EXIT_PARK_MODE: + case NG_HCI_OCF_SWITCH_ROLE: + /* These do not need post processing */ + break; + + case NG_HCI_OCF_QOS_SETUP: + break; + + case NG_HCI_OCF_ROLE_DISCOVERY: + case NG_HCI_OCF_READ_LINK_POLICY_SETTINGS: + case NG_HCI_OCF_WRITE_LINK_POLICY_SETTINGS: + default: + + /* + * None of these command was supposed to generate + * Command_Status event. Instead Command_Complete event + * should have been sent. + */ + + error = EINVAL; + break; + } + + NG_FREE_M(mcp); + + return (error); +} /* process_link_policy_status */ + diff --git a/sys/netgraph7/bluetooth/hci/ng_hci_cmds.h b/sys/netgraph7/bluetooth/hci/ng_hci_cmds.h new file mode 100644 index 0000000000..f802aaca2a --- /dev/null +++ b/sys/netgraph7/bluetooth/hci/ng_hci_cmds.h @@ -0,0 +1,47 @@ +/* + * ng_hci_cmds.h + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_hci_cmds.h,v 1.1 2002/11/24 19:46:58 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/hci/ng_hci_cmds.h,v 1.4 2005/01/07 01:45:43 imp Exp $ + */ + +#ifndef _NETGRAPH_HCI_CMDS_H_ +#define _NETGRAPH_HCI_CMDS_H_ + +/* + * HCI command return parameters processing routines + */ + +int ng_hci_send_command (ng_hci_unit_p); +int ng_hci_process_command_complete (ng_hci_unit_p, struct mbuf *); +int ng_hci_process_command_status (ng_hci_unit_p, struct mbuf *); +void ng_hci_process_command_timeout (node_p, hook_p, void *, int); + +#endif /* ndef _NETGRAPH_HCI_CMDS_H_ */ + diff --git a/sys/netgraph7/bluetooth/hci/ng_hci_evnt.c b/sys/netgraph7/bluetooth/hci/ng_hci_evnt.c new file mode 100644 index 0000000000..6cab3eaf73 --- /dev/null +++ b/sys/netgraph7/bluetooth/hci/ng_hci_evnt.c @@ -0,0 +1,1144 @@ +/* + * ng_hci_evnt.c + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_hci_evnt.c,v 1.6 2003/09/08 18:57:51 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/hci/ng_hci_evnt.c,v 1.8 2005/01/07 01:45:43 imp Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/****************************************************************************** + ****************************************************************************** + ** HCI event processing module + ****************************************************************************** + ******************************************************************************/ + +/* + * Event processing routines + */ + +static int inquiry_result (ng_hci_unit_p, struct mbuf *); +static int con_compl (ng_hci_unit_p, struct mbuf *); +static int con_req (ng_hci_unit_p, struct mbuf *); +static int discon_compl (ng_hci_unit_p, struct mbuf *); +static int encryption_change (ng_hci_unit_p, struct mbuf *); +static int read_remote_features_compl (ng_hci_unit_p, struct mbuf *); +static int qos_setup_compl (ng_hci_unit_p, struct mbuf *); +static int hardware_error (ng_hci_unit_p, struct mbuf *); +static int role_change (ng_hci_unit_p, struct mbuf *); +static int num_compl_pkts (ng_hci_unit_p, struct mbuf *); +static int mode_change (ng_hci_unit_p, struct mbuf *); +static int data_buffer_overflow (ng_hci_unit_p, struct mbuf *); +static int read_clock_offset_compl (ng_hci_unit_p, struct mbuf *); +static int qos_violation (ng_hci_unit_p, struct mbuf *); +static int page_scan_mode_change (ng_hci_unit_p, struct mbuf *); +static int page_scan_rep_mode_change (ng_hci_unit_p, struct mbuf *); +static int sync_con_queue (ng_hci_unit_p, ng_hci_unit_con_p, int); +static int send_data_packets (ng_hci_unit_p, int, int); + +/* + * Process HCI event packet + */ + +int +ng_hci_process_event(ng_hci_unit_p unit, struct mbuf *event) +{ + ng_hci_event_pkt_t *hdr = NULL; + int error = 0; + + /* Get event packet header */ + NG_HCI_M_PULLUP(event, sizeof(*hdr)); + if (event == NULL) + return (ENOBUFS); + + hdr = mtod(event, ng_hci_event_pkt_t *); + + NG_HCI_INFO( +"%s: %s - got HCI event=%#x, length=%d\n", + __func__, NG_NODE_NAME(unit->node), hdr->event, hdr->length); + + /* Get rid of event header and process event */ + m_adj(event, sizeof(*hdr)); + + switch (hdr->event) { + case NG_HCI_EVENT_INQUIRY_COMPL: + case NG_HCI_EVENT_RETURN_LINK_KEYS: + case NG_HCI_EVENT_PIN_CODE_REQ: + case NG_HCI_EVENT_LINK_KEY_REQ: + case NG_HCI_EVENT_LINK_KEY_NOTIFICATION: + case NG_HCI_EVENT_LOOPBACK_COMMAND: + case NG_HCI_EVENT_AUTH_COMPL: + case NG_HCI_EVENT_CHANGE_CON_LINK_KEY_COMPL: + case NG_HCI_EVENT_MASTER_LINK_KEY_COMPL: + case NG_HCI_EVENT_FLUSH_OCCUR: /* XXX Do we have to handle it? */ + case NG_HCI_EVENT_MAX_SLOT_CHANGE: + case NG_HCI_EVENT_CON_PKT_TYPE_CHANGED: + case NG_HCI_EVENT_BT_LOGO: + case NG_HCI_EVENT_VENDOR: + case NG_HCI_EVENT_REMOTE_NAME_REQ_COMPL: + case NG_HCI_EVENT_READ_REMOTE_VER_INFO_COMPL: + /* These do not need post processing */ + NG_FREE_M(event); + break; + + case NG_HCI_EVENT_INQUIRY_RESULT: + error = inquiry_result(unit, event); + break; + + case NG_HCI_EVENT_CON_COMPL: + error = con_compl(unit, event); + break; + + case NG_HCI_EVENT_CON_REQ: + error = con_req(unit, event); + break; + + case NG_HCI_EVENT_DISCON_COMPL: + error = discon_compl(unit, event); + break; + + case NG_HCI_EVENT_ENCRYPTION_CHANGE: + error = encryption_change(unit, event); + break; + + case NG_HCI_EVENT_READ_REMOTE_FEATURES_COMPL: + error = read_remote_features_compl(unit, event); + break; + + case NG_HCI_EVENT_QOS_SETUP_COMPL: + error = qos_setup_compl(unit, event); + break; + + case NG_HCI_EVENT_COMMAND_COMPL: + error = ng_hci_process_command_complete(unit, event); + break; + + case NG_HCI_EVENT_COMMAND_STATUS: + error = ng_hci_process_command_status(unit, event); + break; + + case NG_HCI_EVENT_HARDWARE_ERROR: + error = hardware_error(unit, event); + break; + + case NG_HCI_EVENT_ROLE_CHANGE: + error = role_change(unit, event); + break; + + case NG_HCI_EVENT_NUM_COMPL_PKTS: + error = num_compl_pkts(unit, event); + break; + + case NG_HCI_EVENT_MODE_CHANGE: + error = mode_change(unit, event); + break; + + case NG_HCI_EVENT_DATA_BUFFER_OVERFLOW: + error = data_buffer_overflow(unit, event); + break; + + case NG_HCI_EVENT_READ_CLOCK_OFFSET_COMPL: + error = read_clock_offset_compl(unit, event); + break; + + case NG_HCI_EVENT_QOS_VIOLATION: + error = qos_violation(unit, event); + break; + + case NG_HCI_EVENT_PAGE_SCAN_MODE_CHANGE: + error = page_scan_mode_change(unit, event); + break; + + case NG_HCI_EVENT_PAGE_SCAN_REP_MODE_CHANGE: + error = page_scan_rep_mode_change(unit, event); + break; + + default: + NG_FREE_M(event); + error = EINVAL; + break; + } + + return (error); +} /* ng_hci_process_event */ + +/* + * Send ACL and/or SCO data to the unit driver + */ + +void +ng_hci_send_data(ng_hci_unit_p unit) +{ + int count; + + /* Send ACL data */ + NG_HCI_BUFF_ACL_AVAIL(unit->buffer, count); + + NG_HCI_INFO( +"%s: %s - sending ACL data packets, count=%d\n", + __func__, NG_NODE_NAME(unit->node), count); + + if (count > 0) { + count = send_data_packets(unit, NG_HCI_LINK_ACL, count); + NG_HCI_STAT_ACL_SENT(unit->stat, count); + NG_HCI_BUFF_ACL_USE(unit->buffer, count); + } + + /* Send SCO data */ + NG_HCI_BUFF_SCO_AVAIL(unit->buffer, count); + + NG_HCI_INFO( +"%s: %s - sending SCO data packets, count=%d\n", + __func__, NG_NODE_NAME(unit->node), count); + + if (count > 0) { + count = send_data_packets(unit, NG_HCI_LINK_SCO, count); + NG_HCI_STAT_SCO_SENT(unit->stat, count); + NG_HCI_BUFF_SCO_USE(unit->buffer, count); + } +} /* ng_hci_send_data */ + +/* + * Send data packets to the lower layer. + */ + +static int +send_data_packets(ng_hci_unit_p unit, int link_type, int limit) +{ + ng_hci_unit_con_p con = NULL, winner = NULL; + item_p item = NULL; + int min_pending, total_sent, sent, error, v; + + for (total_sent = 0; limit > 0; ) { + min_pending = 0x0fffffff; + winner = NULL; + + /* + * Find the connection that has has data to send + * and the smallest number of pending packets + */ + + LIST_FOREACH(con, &unit->con_list, next) { + if (con->link_type != link_type) + continue; + if (NG_BT_ITEMQ_LEN(&con->conq) == 0) + continue; + + if (con->pending < min_pending) { + winner = con; + min_pending = con->pending; + } + } + + if (winner == NULL) + break; + + /* + * OK, we have a winner now send as much packets as we can + * Count the number of packets we have sent and then sync + * winner connection queue. + */ + + for (sent = 0; limit > 0; limit --, total_sent ++, sent ++) { + NG_BT_ITEMQ_DEQUEUE(&winner->conq, item); + if (item == NULL) + break; + + NG_HCI_INFO( +"%s: %s - sending data packet, handle=%d, len=%d\n", + __func__, NG_NODE_NAME(unit->node), + winner->con_handle, NGI_M(item)->m_pkthdr.len); + + /* Check if driver hook still there */ + v = (unit->drv != NULL && NG_HOOK_IS_VALID(unit->drv)); + if (!v || (unit->state & NG_HCI_UNIT_READY) != + NG_HCI_UNIT_READY) { + NG_HCI_ERR( +"%s: %s - could not send data. Hook \"%s\" is %svalid, state=%#x\n", + __func__, NG_NODE_NAME(unit->node), + NG_HCI_HOOK_DRV, ((v)? "" : "not "), + unit->state); + + NG_FREE_ITEM(item); + error = ENOTCONN; + } else { + v = NGI_M(item)->m_pkthdr.len; + + /* Give packet to raw hook */ + ng_hci_mtap(unit, NGI_M(item)); + + /* ... and forward item to the driver */ + NG_FWD_ITEM_HOOK(error, item, unit->drv); + } + + if (error != 0) { + NG_HCI_ERR( +"%s: %s - could not send data packet, handle=%d, error=%d\n", + __func__, NG_NODE_NAME(unit->node), + winner->con_handle, error); + break; + } + + winner->pending ++; + NG_HCI_STAT_BYTES_SENT(unit->stat, v); + } + + /* + * Sync connection queue for the winner + */ + + sync_con_queue(unit, winner, sent); + } + + return (total_sent); +} /* send_data_packets */ + +/* + * Send flow control messages to the upper layer + */ + +static int +sync_con_queue(ng_hci_unit_p unit, ng_hci_unit_con_p con, int completed) +{ + hook_p hook = NULL; + struct ng_mesg *msg = NULL; + ng_hci_sync_con_queue_ep *state = NULL; + int error; + + hook = (con->link_type == NG_HCI_LINK_ACL)? unit->acl : unit->sco; + if (hook == NULL || NG_HOOK_NOT_VALID(hook)) + return (ENOTCONN); + + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_SYNC_CON_QUEUE, + sizeof(*state), M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + state = (ng_hci_sync_con_queue_ep *)(msg->data); + state->con_handle = con->con_handle; + state->completed = completed; + + NG_SEND_MSG_HOOK(error, unit->node, msg, hook, 0); + + return (error); +} /* sync_con_queue */ + +/* Inquiry result event */ +static int +inquiry_result(ng_hci_unit_p unit, struct mbuf *event) +{ + ng_hci_inquiry_result_ep *ep = NULL; + ng_hci_neighbor_p n = NULL; + bdaddr_t bdaddr; + int error = 0; + + NG_HCI_M_PULLUP(event, sizeof(*ep)); + if (event == NULL) + return (ENOBUFS); + + ep = mtod(event, ng_hci_inquiry_result_ep *); + m_adj(event, sizeof(*ep)); + + for (; ep->num_responses > 0; ep->num_responses --) { + /* Get remote unit address */ + m_copydata(event, 0, sizeof(bdaddr), (caddr_t) &bdaddr); + m_adj(event, sizeof(bdaddr)); + + /* Lookup entry in the cache */ + n = ng_hci_get_neighbor(unit, &bdaddr); + if (n == NULL) { + /* Create new entry */ + n = ng_hci_new_neighbor(unit); + if (n == NULL) { + error = ENOMEM; + break; + } + } else + getmicrotime(&n->updated); + + bcopy(&bdaddr, &n->bdaddr, sizeof(n->bdaddr)); + + /* XXX call m_pullup here? */ + + n->page_scan_rep_mode = *mtod(event, u_int8_t *); + m_adj(event, sizeof(u_int8_t)); + + /* page_scan_period_mode */ + m_adj(event, sizeof(u_int8_t)); + + n->page_scan_mode = *mtod(event, u_int8_t *); + m_adj(event, sizeof(u_int8_t)); + + /* class */ + m_adj(event, NG_HCI_CLASS_SIZE); + + /* clock offset */ + m_copydata(event, 0, sizeof(n->clock_offset), + (caddr_t) &n->clock_offset); + n->clock_offset = le16toh(n->clock_offset); + } + + NG_FREE_M(event); + + return (error); +} /* inquiry_result */ + +/* Connection complete event */ +static int +con_compl(ng_hci_unit_p unit, struct mbuf *event) +{ + ng_hci_con_compl_ep *ep = NULL; + ng_hci_unit_con_p con = NULL; + int error = 0; + + NG_HCI_M_PULLUP(event, sizeof(*ep)); + if (event == NULL) + return (ENOBUFS); + + ep = mtod(event, ng_hci_con_compl_ep *); + + /* + * Find the first connection descriptor that matches the following: + * + * 1) con->link_type == ep->link_type + * 2) con->state == NG_HCI_CON_W4_CONN_COMPLETE + * 3) con->bdaddr == ep->bdaddr + */ + + LIST_FOREACH(con, &unit->con_list, next) + if (con->link_type == ep->link_type && + con->state == NG_HCI_CON_W4_CONN_COMPLETE && + bcmp(&con->bdaddr, &ep->bdaddr, sizeof(bdaddr_t)) == 0) + break; + + /* + * Two possible cases: + * + * 1) We have found connection descriptor. That means upper layer has + * requested this connection via LP_CON_REQ message. In this case + * connection must have timeout set. If ng_hci_con_untimeout() fails + * then timeout message already went into node's queue. In this case + * ignore Connection_Complete event and let timeout deal with it. + * + * 2) We do not have connection descriptor. That means upper layer + * nas not requested this connection or (less likely) we gave up + * on this connection (timeout). The most likely scenario is that + * we have received Create_Connection/Add_SCO_Connection command + * from the RAW hook + */ + + if (con == NULL) { + if (ep->status != 0) + goto out; + + con = ng_hci_new_con(unit, ep->link_type); + if (con == NULL) { + error = ENOMEM; + goto out; + } + + bcopy(&ep->bdaddr, &con->bdaddr, sizeof(con->bdaddr)); + } else if ((error = ng_hci_con_untimeout(con)) != 0) + goto out; + + /* + * Update connection descriptor and send notification + * to the upper layers. + */ + + con->con_handle = NG_HCI_CON_HANDLE(le16toh(ep->con_handle)); + con->encryption_mode = ep->encryption_mode; + + ng_hci_lp_con_cfm(con, ep->status); + + /* Adjust connection state */ + if (ep->status != 0) + ng_hci_free_con(con); + else { + con->state = NG_HCI_CON_OPEN; + + /* + * Change link policy for the ACL connections. Enable all + * supported link modes. Enable Role switch as well if + * device supports it. + */ + + if (ep->link_type == NG_HCI_LINK_ACL) { + struct __link_policy { + ng_hci_cmd_pkt_t hdr; + ng_hci_write_link_policy_settings_cp cp; + } __attribute__ ((packed)) *lp; + struct mbuf *m; + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m != NULL) { + m->m_pkthdr.len = m->m_len = sizeof(*lp); + lp = mtod(m, struct __link_policy *); + + lp->hdr.type = NG_HCI_CMD_PKT; + lp->hdr.opcode = htole16(NG_HCI_OPCODE( + NG_HCI_OGF_LINK_POLICY, + NG_HCI_OCF_WRITE_LINK_POLICY_SETTINGS)); + lp->hdr.length = sizeof(lp->cp); + + lp->cp.con_handle = ep->con_handle; + + lp->cp.settings = 0; + if ((unit->features[0] & NG_HCI_LMP_SWITCH) && + unit->role_switch) + lp->cp.settings |= 0x1; + if (unit->features[0] & NG_HCI_LMP_HOLD_MODE) + lp->cp.settings |= 0x2; + if (unit->features[0] & NG_HCI_LMP_SNIFF_MODE) + lp->cp.settings |= 0x4; + if (unit->features[1] & NG_HCI_LMP_PARK_MODE) + lp->cp.settings |= 0x8; + + lp->cp.settings &= unit->link_policy_mask; + lp->cp.settings = htole16(lp->cp.settings); + + NG_BT_MBUFQ_ENQUEUE(&unit->cmdq, m); + if (!(unit->state & NG_HCI_UNIT_COMMAND_PENDING)) + ng_hci_send_command(unit); + } + } + } +out: + NG_FREE_M(event); + + return (error); +} /* con_compl */ + +/* Connection request event */ +static int +con_req(ng_hci_unit_p unit, struct mbuf *event) +{ + ng_hci_con_req_ep *ep = NULL; + ng_hci_unit_con_p con = NULL; + int error = 0; + + NG_HCI_M_PULLUP(event, sizeof(*ep)); + if (event == NULL) + return (ENOBUFS); + + ep = mtod(event, ng_hci_con_req_ep *); + + /* + * Find the first connection descriptor that matches the following: + * + * 1) con->link_type == ep->link_type + * + * 2) con->state == NG_HCI_CON_W4_LP_CON_RSP || + * con->state == NG_HCI_CON_W4_CONN_COMPL + * + * 3) con->bdaddr == ep->bdaddr + * + * Possible cases: + * + * 1) We do not have connection descriptor. This is simple. Create + * new fresh connection descriptor and send notification to the + * appropriate upstream hook (based on link_type). + * + * 2) We found connection handle. This is more complicated. + * + * 2.1) ACL links + * + * Since only one ACL link can exist between each pair of + * units then we have a race. Our upper layer has requested + * an ACL connection to the remote unit, but we did not send + * command yet. At the same time the remote unit has requested + * an ACL connection from us. In this case we will ignore + * Connection_Request event. This probably will cause connect + * failure on both units. + * + * 2.2) SCO links + * + * The spec on page 45 says : + * + * "The master can support up to three SCO links to the same + * slave or to different slaves. A slave can support up to + * three SCO links from the same master, or two SCO links if + * the links originate from different masters." + * + * The only problem is how to handle multiple SCO links between + * matster and slave. For now we will assume that multiple SCO + * links MUST be opened one after another. + */ + + LIST_FOREACH(con, &unit->con_list, next) + if (con->link_type == ep->link_type && + (con->state == NG_HCI_CON_W4_LP_CON_RSP || + con->state == NG_HCI_CON_W4_CONN_COMPLETE) && + bcmp(&con->bdaddr, &ep->bdaddr, sizeof(bdaddr_t)) == 0) + break; + + if (con == NULL) { + con = ng_hci_new_con(unit, ep->link_type); + if (con != NULL) { + bcopy(&ep->bdaddr, &con->bdaddr, sizeof(con->bdaddr)); + + con->state = NG_HCI_CON_W4_LP_CON_RSP; + ng_hci_con_timeout(con); + + error = ng_hci_lp_con_ind(con, ep->uclass); + if (error != 0) { + ng_hci_con_untimeout(con); + ng_hci_free_con(con); + } + } else + error = ENOMEM; + } + + NG_FREE_M(event); + + return (error); +} /* con_req */ + +/* Disconnect complete event */ +static int +discon_compl(ng_hci_unit_p unit, struct mbuf *event) +{ + ng_hci_discon_compl_ep *ep = NULL; + ng_hci_unit_con_p con = NULL; + int error = 0; + u_int16_t h; + + NG_HCI_M_PULLUP(event, sizeof(*ep)); + if (event == NULL) + return (ENOBUFS); + + ep = mtod(event, ng_hci_discon_compl_ep *); + + /* + * XXX + * Do we have to send notification if ep->status != 0? + * For now we will send notification for both ACL and SCO connections + * ONLY if ep->status == 0. + */ + + if (ep->status == 0) { + h = NG_HCI_CON_HANDLE(le16toh(ep->con_handle)); + con = ng_hci_con_by_handle(unit, h); + if (con != NULL) { + error = ng_hci_lp_discon_ind(con, ep->reason); + + /* Remove all timeouts (if any) */ + if (con->flags & NG_HCI_CON_TIMEOUT_PENDING) + ng_hci_con_untimeout(con); + + ng_hci_free_con(con); + } else { + NG_HCI_ALERT( +"%s: %s - invalid connection handle=%d\n", + __func__, NG_NODE_NAME(unit->node), h); + error = ENOENT; + } + } + + NG_FREE_M(event); + + return (error); +} /* discon_compl */ + +/* Encryption change event */ +static int +encryption_change(ng_hci_unit_p unit, struct mbuf *event) +{ + ng_hci_encryption_change_ep *ep = NULL; + ng_hci_unit_con_p con = NULL; + int error = 0; + + NG_HCI_M_PULLUP(event, sizeof(*ep)); + if (event == NULL) + return (ENOBUFS); + + ep = mtod(event, ng_hci_encryption_change_ep *); + + if (ep->status == 0) { + u_int16_t h = NG_HCI_CON_HANDLE(le16toh(ep->con_handle)); + + con = ng_hci_con_by_handle(unit, h); + if (con == NULL) { + NG_HCI_ALERT( +"%s: %s - invalid connection handle=%d\n", + __func__, NG_NODE_NAME(unit->node), h); + error = ENOENT; + } else if (con->link_type != NG_HCI_LINK_ACL) { + NG_HCI_ALERT( +"%s: %s - invalid link type=%d\n", + __func__, NG_NODE_NAME(unit->node), + con->link_type); + error = EINVAL; + } else if (ep->encryption_enable) + /* XXX is that true? */ + con->encryption_mode = NG_HCI_ENCRYPTION_MODE_P2P; + else + con->encryption_mode = NG_HCI_ENCRYPTION_MODE_NONE; + } else + NG_HCI_ERR( +"%s: %s - failed to change encryption mode, status=%d\n", + __func__, NG_NODE_NAME(unit->node), ep->status); + + NG_FREE_M(event); + + return (error); +} /* encryption_change */ + +/* Read remote feature complete event */ +static int +read_remote_features_compl(ng_hci_unit_p unit, struct mbuf *event) +{ + ng_hci_read_remote_features_compl_ep *ep = NULL; + ng_hci_unit_con_p con = NULL; + ng_hci_neighbor_p n = NULL; + u_int16_t h; + int error = 0; + + NG_HCI_M_PULLUP(event, sizeof(*ep)); + if (event == NULL) + return (ENOBUFS); + + ep = mtod(event, ng_hci_read_remote_features_compl_ep *); + + if (ep->status == 0) { + /* Check if we have this connection handle */ + h = NG_HCI_CON_HANDLE(le16toh(ep->con_handle)); + con = ng_hci_con_by_handle(unit, h); + if (con == NULL) { + NG_HCI_ALERT( +"%s: %s - invalid connection handle=%d\n", + __func__, NG_NODE_NAME(unit->node), h); + error = ENOENT; + goto out; + } + + /* Update cache entry */ + n = ng_hci_get_neighbor(unit, &con->bdaddr); + if (n == NULL) { + n = ng_hci_new_neighbor(unit); + if (n == NULL) { + error = ENOMEM; + goto out; + } + + bcopy(&con->bdaddr, &n->bdaddr, sizeof(n->bdaddr)); + } else + getmicrotime(&n->updated); + + bcopy(ep->features, n->features, sizeof(n->features)); + } else + NG_HCI_ERR( +"%s: %s - failed to read remote unit features, status=%d\n", + __func__, NG_NODE_NAME(unit->node), ep->status); +out: + NG_FREE_M(event); + + return (error); +} /* read_remote_features_compl */ + +/* QoS setup complete event */ +static int +qos_setup_compl(ng_hci_unit_p unit, struct mbuf *event) +{ + ng_hci_qos_setup_compl_ep *ep = NULL; + ng_hci_unit_con_p con = NULL; + u_int16_t h; + int error = 0; + + NG_HCI_M_PULLUP(event, sizeof(*ep)); + if (event == NULL) + return (ENOBUFS); + + ep = mtod(event, ng_hci_qos_setup_compl_ep *); + + /* Check if we have this connection handle */ + h = NG_HCI_CON_HANDLE(le16toh(ep->con_handle)); + con = ng_hci_con_by_handle(unit, h); + if (con == NULL) { + NG_HCI_ALERT( +"%s: %s - invalid connection handle=%d\n", + __func__, NG_NODE_NAME(unit->node), h); + error = ENOENT; + } else if (con->link_type != NG_HCI_LINK_ACL) { + NG_HCI_ALERT( +"%s: %s - invalid link type=%d, handle=%d\n", + __func__, NG_NODE_NAME(unit->node), con->link_type, h); + error = EINVAL; + } else if (con->state != NG_HCI_CON_OPEN) { + NG_HCI_ALERT( +"%s: %s - invalid connection state=%d, handle=%d\n", + __func__, NG_NODE_NAME(unit->node), + con->state, h); + error = EINVAL; + } else /* Notify upper layer */ + error = ng_hci_lp_qos_cfm(con, ep->status); + + NG_FREE_M(event); + + return (error); +} /* qos_setup_compl */ + +/* Hardware error event */ +static int +hardware_error(ng_hci_unit_p unit, struct mbuf *event) +{ + NG_HCI_ALERT( +"%s: %s - hardware error %#x\n", + __func__, NG_NODE_NAME(unit->node), *mtod(event, u_int8_t *)); + + NG_FREE_M(event); + + return (0); +} /* hardware_error */ + +/* Role change event */ +static int +role_change(ng_hci_unit_p unit, struct mbuf *event) +{ + ng_hci_role_change_ep *ep = NULL; + ng_hci_unit_con_p con = NULL; + + NG_HCI_M_PULLUP(event, sizeof(*ep)); + if (event == NULL) + return (ENOBUFS); + + ep = mtod(event, ng_hci_role_change_ep *); + + if (ep->status == 0) { + /* XXX shoud we also change "role" for SCO connections? */ + con = ng_hci_con_by_bdaddr(unit, &ep->bdaddr, NG_HCI_LINK_ACL); + if (con != NULL) + con->role = ep->role; + else + NG_HCI_ALERT( +"%s: %s - ACL connection does not exist, bdaddr=%x:%x:%x:%x:%x:%x\n", + __func__, NG_NODE_NAME(unit->node), + ep->bdaddr.b[5], ep->bdaddr.b[4], + ep->bdaddr.b[3], ep->bdaddr.b[2], + ep->bdaddr.b[1], ep->bdaddr.b[0]); + } else + NG_HCI_ERR( +"%s: %s - failed to change role, status=%d, bdaddr=%x:%x:%x:%x:%x:%x\n", + __func__, NG_NODE_NAME(unit->node), ep->status, + ep->bdaddr.b[5], ep->bdaddr.b[4], ep->bdaddr.b[3], + ep->bdaddr.b[2], ep->bdaddr.b[1], ep->bdaddr.b[0]); + + NG_FREE_M(event); + + return (0); +} /* role_change */ + +/* Number of completed packets event */ +static int +num_compl_pkts(ng_hci_unit_p unit, struct mbuf *event) +{ + ng_hci_num_compl_pkts_ep *ep = NULL; + ng_hci_unit_con_p con = NULL; + u_int16_t h, p; + + NG_HCI_M_PULLUP(event, sizeof(*ep)); + if (event == NULL) + return (ENOBUFS); + + ep = mtod(event, ng_hci_num_compl_pkts_ep *); + m_adj(event, sizeof(*ep)); + + for (; ep->num_con_handles > 0; ep->num_con_handles --) { + /* Get connection handle */ + m_copydata(event, 0, sizeof(h), (caddr_t) &h); + m_adj(event, sizeof(h)); + h = NG_HCI_CON_HANDLE(le16toh(h)); + + /* Get number of completed packets */ + m_copydata(event, 0, sizeof(p), (caddr_t) &p); + m_adj(event, sizeof(p)); + p = le16toh(p); + + /* Check if we have this connection handle */ + con = ng_hci_con_by_handle(unit, h); + if (con != NULL) { + con->pending -= p; + if (con->pending < 0) { + NG_HCI_WARN( +"%s: %s - pending packet counter is out of sync! " \ +"handle=%d, pending=%d, ncp=%d\n", __func__, NG_NODE_NAME(unit->node), + con->con_handle, con->pending, p); + + con->pending = 0; + } + + /* Update buffer descriptor */ + if (con->link_type == NG_HCI_LINK_ACL) + NG_HCI_BUFF_ACL_FREE(unit->buffer, p); + else + NG_HCI_BUFF_SCO_FREE(unit->buffer, p); + } else + NG_HCI_ALERT( +"%s: %s - invalid connection handle=%d\n", + __func__, NG_NODE_NAME(unit->node), h); + } + + NG_FREE_M(event); + + /* Send more data */ + ng_hci_send_data(unit); + + return (0); +} /* num_compl_pkts */ + +/* Mode change event */ +static int +mode_change(ng_hci_unit_p unit, struct mbuf *event) +{ + ng_hci_mode_change_ep *ep = NULL; + ng_hci_unit_con_p con = NULL; + int error = 0; + + NG_HCI_M_PULLUP(event, sizeof(*ep)); + if (event == NULL) + return (ENOBUFS); + + ep = mtod(event, ng_hci_mode_change_ep *); + + if (ep->status == 0) { + u_int16_t h = NG_HCI_CON_HANDLE(le16toh(ep->con_handle)); + + con = ng_hci_con_by_handle(unit, h); + if (con == NULL) { + NG_HCI_ALERT( +"%s: %s - invalid connection handle=%d\n", + __func__, NG_NODE_NAME(unit->node), h); + error = ENOENT; + } else if (con->link_type != NG_HCI_LINK_ACL) { + NG_HCI_ALERT( +"%s: %s - invalid link type=%d\n", + __func__, NG_NODE_NAME(unit->node), + con->link_type); + error = EINVAL; + } else + con->mode = ep->unit_mode; + } else + NG_HCI_ERR( +"%s: %s - failed to change mode, status=%d\n", + __func__, NG_NODE_NAME(unit->node), ep->status); + + NG_FREE_M(event); + + return (error); +} /* mode_change */ + +/* Data buffer overflow event */ +static int +data_buffer_overflow(ng_hci_unit_p unit, struct mbuf *event) +{ + NG_HCI_ALERT( +"%s: %s - %s data buffer overflow\n", + __func__, NG_NODE_NAME(unit->node), + (*mtod(event, u_int8_t *) == NG_HCI_LINK_ACL)? "ACL" : "SCO"); + + NG_FREE_M(event); + + return (0); +} /* data_buffer_overflow */ + +/* Read clock offset complete event */ +static int +read_clock_offset_compl(ng_hci_unit_p unit, struct mbuf *event) +{ + ng_hci_read_clock_offset_compl_ep *ep = NULL; + ng_hci_unit_con_p con = NULL; + ng_hci_neighbor_p n = NULL; + int error = 0; + + NG_HCI_M_PULLUP(event, sizeof(*ep)); + if (event == NULL) + return (ENOBUFS); + + ep = mtod(event, ng_hci_read_clock_offset_compl_ep *); + + if (ep->status == 0) { + u_int16_t h = NG_HCI_CON_HANDLE(le16toh(ep->con_handle)); + + con = ng_hci_con_by_handle(unit, h); + if (con == NULL) { + NG_HCI_ALERT( +"%s: %s - invalid connection handle=%d\n", + __func__, NG_NODE_NAME(unit->node), h); + error = ENOENT; + goto out; + } + + /* Update cache entry */ + n = ng_hci_get_neighbor(unit, &con->bdaddr); + if (n == NULL) { + n = ng_hci_new_neighbor(unit); + if (n == NULL) { + error = ENOMEM; + goto out; + } + + bcopy(&con->bdaddr, &n->bdaddr, sizeof(n->bdaddr)); + } else + getmicrotime(&n->updated); + + n->clock_offset = le16toh(ep->clock_offset); + } else + NG_HCI_ERR( +"%s: %s - failed to Read Remote Clock Offset, status=%d\n", + __func__, NG_NODE_NAME(unit->node), ep->status); +out: + NG_FREE_M(event); + + return (error); +} /* read_clock_offset_compl */ + +/* QoS violation event */ +static int +qos_violation(ng_hci_unit_p unit, struct mbuf *event) +{ + ng_hci_qos_violation_ep *ep = NULL; + ng_hci_unit_con_p con = NULL; + u_int16_t h; + int error = 0; + + NG_HCI_M_PULLUP(event, sizeof(*ep)); + if (event == NULL) + return (ENOBUFS); + + ep = mtod(event, ng_hci_qos_violation_ep *); + + /* Check if we have this connection handle */ + h = NG_HCI_CON_HANDLE(le16toh(ep->con_handle)); + con = ng_hci_con_by_handle(unit, h); + if (con == NULL) { + NG_HCI_ALERT( +"%s: %s - invalid connection handle=%d\n", + __func__, NG_NODE_NAME(unit->node), h); + error = ENOENT; + } else if (con->link_type != NG_HCI_LINK_ACL) { + NG_HCI_ALERT( +"%s: %s - invalid link type=%d\n", + __func__, NG_NODE_NAME(unit->node), con->link_type); + error = EINVAL; + } else if (con->state != NG_HCI_CON_OPEN) { + NG_HCI_ALERT( +"%s: %s - invalid connection state=%d, handle=%d\n", + __func__, NG_NODE_NAME(unit->node), con->state, h); + error = EINVAL; + } else /* Notify upper layer */ + error = ng_hci_lp_qos_ind(con); + + NG_FREE_M(event); + + return (error); +} /* qos_violation */ + +/* Page scan mode change event */ +static int +page_scan_mode_change(ng_hci_unit_p unit, struct mbuf *event) +{ + ng_hci_page_scan_mode_change_ep *ep = NULL; + ng_hci_neighbor_p n = NULL; + int error = 0; + + NG_HCI_M_PULLUP(event, sizeof(*ep)); + if (event == NULL) + return (ENOBUFS); + + ep = mtod(event, ng_hci_page_scan_mode_change_ep *); + + /* Update cache entry */ + n = ng_hci_get_neighbor(unit, &ep->bdaddr); + if (n == NULL) { + n = ng_hci_new_neighbor(unit); + if (n == NULL) { + error = ENOMEM; + goto out; + } + + bcopy(&ep->bdaddr, &n->bdaddr, sizeof(n->bdaddr)); + } else + getmicrotime(&n->updated); + + n->page_scan_mode = ep->page_scan_mode; +out: + NG_FREE_M(event); + + return (error); +} /* page_scan_mode_change */ + +/* Page scan repetition mode change event */ +static int +page_scan_rep_mode_change(ng_hci_unit_p unit, struct mbuf *event) +{ + ng_hci_page_scan_rep_mode_change_ep *ep = NULL; + ng_hci_neighbor_p n = NULL; + int error = 0; + + NG_HCI_M_PULLUP(event, sizeof(*ep)); + if (event == NULL) + return (ENOBUFS); + + ep = mtod(event, ng_hci_page_scan_rep_mode_change_ep *); + + /* Update cache entry */ + n = ng_hci_get_neighbor(unit, &ep->bdaddr); + if (n == NULL) { + n = ng_hci_new_neighbor(unit); + if (n == NULL) { + error = ENOMEM; + goto out; + } + + bcopy(&ep->bdaddr, &n->bdaddr, sizeof(n->bdaddr)); + } else + getmicrotime(&n->updated); + + n->page_scan_rep_mode = ep->page_scan_rep_mode; +out: + NG_FREE_M(event); + + return (error); +} /* page_scan_rep_mode_change */ + diff --git a/sys/netgraph7/bluetooth/hci/ng_hci_evnt.h b/sys/netgraph7/bluetooth/hci/ng_hci_evnt.h new file mode 100644 index 0000000000..e6949f6ea1 --- /dev/null +++ b/sys/netgraph7/bluetooth/hci/ng_hci_evnt.h @@ -0,0 +1,45 @@ +/* + * ng_hci_evnt.h + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_hci_evnt.h,v 1.1 2002/11/24 19:46:58 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/hci/ng_hci_evnt.h,v 1.4 2005/01/07 01:45:43 imp Exp $ + */ + +#ifndef _NETGRAPH_HCI_EVNT_H_ +#define _NETGRAPH_HCI_EVNT_H_ + +/* + * HCI events processing routines + */ + +int ng_hci_process_event (ng_hci_unit_p, struct mbuf *); +void ng_hci_send_data (ng_hci_unit_p); + +#endif /* ndef _NETGRAPH_HCI_EVNT_H_ */ + diff --git a/sys/netgraph7/bluetooth/hci/ng_hci_main.c b/sys/netgraph7/bluetooth/hci/ng_hci_main.c new file mode 100644 index 0000000000..c7eb336a1c --- /dev/null +++ b/sys/netgraph7/bluetooth/hci/ng_hci_main.c @@ -0,0 +1,1088 @@ +/* + * ng_hci_main.c + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_hci_main.c,v 1.2 2003/03/18 00:09:36 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/hci/ng_hci_main.c,v 1.6 2005/01/07 01:45:43 imp Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/****************************************************************************** + ****************************************************************************** + ** This node implements Bluetooth Host Controller Interface (HCI) + ****************************************************************************** + ******************************************************************************/ + +/* MALLOC define */ +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_HCI, "netgraph_hci", "Netgraph Bluetooth HCI node"); +#else +#define M_NETGRAPH_HCI M_NETGRAPH +#endif /* NG_SEPARATE_MALLOC */ + +/* Netgraph node methods */ +static ng_constructor_t ng_hci_constructor; +static ng_shutdown_t ng_hci_shutdown; +static ng_newhook_t ng_hci_newhook; +static ng_connect_t ng_hci_connect; +static ng_disconnect_t ng_hci_disconnect; +static ng_rcvmsg_t ng_hci_default_rcvmsg; +static ng_rcvmsg_t ng_hci_upper_rcvmsg; +static ng_rcvdata_t ng_hci_drv_rcvdata; +static ng_rcvdata_t ng_hci_acl_rcvdata; +static ng_rcvdata_t ng_hci_sco_rcvdata; +static ng_rcvdata_t ng_hci_raw_rcvdata; + +/* Netgraph node type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_HCI_NODE_TYPE, + .constructor = ng_hci_constructor, + .rcvmsg = ng_hci_default_rcvmsg, + .shutdown = ng_hci_shutdown, + .newhook = ng_hci_newhook, + .connect = ng_hci_connect, + .rcvdata = ng_hci_drv_rcvdata, + .disconnect = ng_hci_disconnect, + .cmdlist = ng_hci_cmdlist, +}; +NETGRAPH_INIT(hci, &typestruct); +MODULE_VERSION(ng_hci, NG_BLUETOOTH_VERSION); +MODULE_DEPEND(ng_hci, ng_bluetooth, NG_BLUETOOTH_VERSION, + NG_BLUETOOTH_VERSION, NG_BLUETOOTH_VERSION); + +/***************************************************************************** + ***************************************************************************** + ** Netgraph methods implementation + ***************************************************************************** + *****************************************************************************/ + +/* + * Create new instance of HCI node (new unit) + */ + +static int +ng_hci_constructor(node_p node) +{ + ng_hci_unit_p unit = NULL; + + MALLOC(unit, ng_hci_unit_p, sizeof(*unit), M_NETGRAPH_HCI, + M_NOWAIT | M_ZERO); + if (unit == NULL) + return (ENOMEM); + + unit->node = node; + unit->debug = NG_HCI_WARN_LEVEL; + + unit->link_policy_mask = 0xffff; /* Enable all supported modes */ + unit->packet_mask = 0xffff; /* Enable all packet types */ + unit->role_switch = 1; /* Enable role switch (if device supports it) */ + + /* + * Set default buffer info + * + * One HCI command + * One ACL packet with max. size of 17 bytes (1 DM1 packet) + * One SCO packet with max. size of 10 bytes (1 HV1 packet) + */ + + NG_HCI_BUFF_CMD_SET(unit->buffer, 1); + NG_HCI_BUFF_ACL_SET(unit->buffer, 1, 17, 1); + NG_HCI_BUFF_SCO_SET(unit->buffer, 1, 10, 1); + + /* Init command queue & command timeout handler */ + ng_callout_init(&unit->cmd_timo); + NG_BT_MBUFQ_INIT(&unit->cmdq, NG_HCI_CMD_QUEUE_LEN); + + /* Init lists */ + LIST_INIT(&unit->con_list); + LIST_INIT(&unit->neighbors); + + /* + * This node has to be a WRITER because both data and messages + * can change node state. + */ + + NG_NODE_FORCE_WRITER(node); + NG_NODE_SET_PRIVATE(node, unit); + + return (0); +} /* ng_hci_constructor */ + +/* + * Destroy the node + */ + +static int +ng_hci_shutdown(node_p node) +{ + ng_hci_unit_p unit = (ng_hci_unit_p) NG_NODE_PRIVATE(node); + + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + + unit->node = NULL; + ng_hci_unit_clean(unit, 0x16 /* Connection terminated by local host */); + + NG_BT_MBUFQ_DESTROY(&unit->cmdq); + + bzero(unit, sizeof(*unit)); + FREE(unit, M_NETGRAPH_HCI); + + return (0); +} /* ng_hci_shutdown */ + +/* + * Give our OK for a hook to be added. Unit driver is connected to the driver + * (NG_HCI_HOOK_DRV) hook. Upper layer protocols are connected to appropriate + * (NG_HCI_HOOK_ACL or NG_HCI_HOOK_SCO) hooks. + */ + +static int +ng_hci_newhook(node_p node, hook_p hook, char const *name) +{ + ng_hci_unit_p unit = (ng_hci_unit_p) NG_NODE_PRIVATE(node); + hook_p *h = NULL; + + if (strcmp(name, NG_HCI_HOOK_DRV) == 0) + h = &unit->drv; + else if (strcmp(name, NG_HCI_HOOK_ACL) == 0) + h = &unit->acl; + else if (strcmp(name, NG_HCI_HOOK_SCO) == 0) + h = &unit->sco; + else if (strcmp(name, NG_HCI_HOOK_RAW) == 0) + h = &unit->raw; + else + return (EINVAL); + + if (*h != NULL) + return (EISCONN); + + *h = hook; + + return (0); +} /* ng_hci_newhook */ + +/* + * Give our final OK to connect hook + */ + +static int +ng_hci_connect(hook_p hook) +{ + ng_hci_unit_p unit = (ng_hci_unit_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + if (hook != unit->drv) { + if (hook == unit->acl) { + NG_HOOK_SET_RCVMSG(hook, ng_hci_upper_rcvmsg); + NG_HOOK_SET_RCVDATA(hook, ng_hci_acl_rcvdata); + } else if (hook == unit->sco) { + NG_HOOK_SET_RCVMSG(hook, ng_hci_upper_rcvmsg); + NG_HOOK_SET_RCVDATA(hook, ng_hci_sco_rcvdata); + } else + NG_HOOK_SET_RCVDATA(hook, ng_hci_raw_rcvdata); + + /* Send delayed notification to the upper layers */ + if (hook != unit->raw) + ng_send_fn(unit->node, hook, ng_hci_node_is_up, NULL,0); + } else + unit->state |= NG_HCI_UNIT_CONNECTED; + + return (0); +} /* ng_hci_connect */ + +/* + * Disconnect the hook + */ + +static int +ng_hci_disconnect(hook_p hook) +{ + ng_hci_unit_p unit = (ng_hci_unit_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + if (hook == unit->acl) + unit->acl = NULL; + else if (hook == unit->sco) + unit->sco = NULL; + else if (hook == unit->raw) + unit->raw = NULL; + else if (hook == unit->drv) { + unit->drv = NULL; + + /* Connection terminated by local host */ + ng_hci_unit_clean(unit, 0x16); + unit->state &= ~(NG_HCI_UNIT_CONNECTED|NG_HCI_UNIT_INITED); + } else + return (EINVAL); + + /* Shutdown when all hooks are disconnected */ + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && + (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) + ng_rmnode_self(NG_HOOK_NODE(hook)); + + return (0); +} /* ng_hci_disconnect */ + +/* + * Default control message processing routine. Control message could be: + * + * 1) GENERIC Netgraph messages + * + * 2) Control message directed to the node itself. + */ + +static int +ng_hci_default_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + ng_hci_unit_p unit = (ng_hci_unit_p) NG_NODE_PRIVATE(node); + struct ng_mesg *msg = NULL, *rsp = NULL; + int error = 0; + + NGI_GET_MSG(item, msg); + + switch (msg->header.typecookie) { + case NGM_GENERIC_COOKIE: + switch (msg->header.cmd) { + case NGM_TEXT_STATUS: { + int cmd_avail, + acl_total, acl_avail, acl_size, + sco_total, sco_avail, sco_size; + + NG_MKRESPONSE(rsp, msg, NG_TEXTRESPONSE, M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + NG_HCI_BUFF_CMD_GET(unit->buffer, cmd_avail); + + NG_HCI_BUFF_ACL_AVAIL(unit->buffer, acl_avail); + NG_HCI_BUFF_ACL_TOTAL(unit->buffer, acl_total); + NG_HCI_BUFF_ACL_SIZE(unit->buffer, acl_size); + + NG_HCI_BUFF_SCO_AVAIL(unit->buffer, sco_avail); + NG_HCI_BUFF_SCO_TOTAL(unit->buffer, sco_total); + NG_HCI_BUFF_SCO_SIZE(unit->buffer, sco_size); + + snprintf(rsp->data, NG_TEXTRESPONSE, + "bdaddr %x:%x:%x:%x:%x:%x\n" \ + "Hooks %s %s %s %s\n" \ + "State %#x\n" \ + "Queue cmd:%d\n" \ + "Buffer cmd:%d,acl:%d,%d,%d,sco:%d,%d,%d", + unit->bdaddr.b[5], unit->bdaddr.b[4], + unit->bdaddr.b[3], unit->bdaddr.b[2], + unit->bdaddr.b[1], unit->bdaddr.b[0], + (unit->drv != NULL)? NG_HCI_HOOK_DRV : "", + (unit->acl != NULL)? NG_HCI_HOOK_ACL : "", + (unit->sco != NULL)? NG_HCI_HOOK_SCO : "", + (unit->raw != NULL)? NG_HCI_HOOK_RAW : "", + unit->state, + NG_BT_MBUFQ_LEN(&unit->cmdq), + cmd_avail, + acl_avail, acl_total, acl_size, + sco_avail, sco_total, sco_size); + } break; + + default: + error = EINVAL; + break; + } + break; + + case NGM_HCI_COOKIE: + switch (msg->header.cmd) { + /* Get current node state */ + case NGM_HCI_NODE_GET_STATE: + NG_MKRESPONSE(rsp, msg, sizeof(unit->state), M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + *((ng_hci_node_state_ep *)(rsp->data)) = unit->state; + break; + + /* Turn INITED bit - node initialized */ + case NGM_HCI_NODE_INIT: + if (bcmp(&unit->bdaddr, NG_HCI_BDADDR_ANY, + sizeof(bdaddr_t)) == 0) { + error = ENXIO; + break; + } + + unit->state |= NG_HCI_UNIT_INITED; + + ng_hci_node_is_up(unit->node, unit->acl, NULL, 0); + ng_hci_node_is_up(unit->node, unit->sco, NULL, 0); + break; + + /* Get node debug level */ + case NGM_HCI_NODE_GET_DEBUG: + NG_MKRESPONSE(rsp, msg, sizeof(unit->debug), M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + *((ng_hci_node_debug_ep *)(rsp->data)) = unit->debug; + break; + + /* Set node debug level */ + case NGM_HCI_NODE_SET_DEBUG: + if (msg->header.arglen != sizeof(ng_hci_node_debug_ep)){ + error = EMSGSIZE; + break; + } + + unit->debug = *((ng_hci_node_debug_ep *)(msg->data)); + break; + + /* Get buffer info */ + case NGM_HCI_NODE_GET_BUFFER: { + ng_hci_node_buffer_ep *ep = NULL; + + NG_MKRESPONSE(rsp, msg, sizeof(ng_hci_node_buffer_ep), + M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + ep = (ng_hci_node_buffer_ep *)(rsp->data); + + NG_HCI_BUFF_CMD_GET(unit->buffer, ep->cmd_free); + NG_HCI_BUFF_ACL_AVAIL(unit->buffer, ep->acl_free); + NG_HCI_BUFF_ACL_TOTAL(unit->buffer, ep->acl_pkts); + NG_HCI_BUFF_ACL_SIZE(unit->buffer, ep->acl_size); + NG_HCI_BUFF_SCO_AVAIL(unit->buffer, ep->sco_free); + NG_HCI_BUFF_SCO_TOTAL(unit->buffer, ep->sco_pkts); + NG_HCI_BUFF_SCO_SIZE(unit->buffer, ep->sco_size); + } break; + + /* Get BDADDR */ + case NGM_HCI_NODE_GET_BDADDR: + NG_MKRESPONSE(rsp, msg, sizeof(bdaddr_t), M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + bcopy(&unit->bdaddr, rsp->data, sizeof(bdaddr_t)); + break; + + /* Get features */ + case NGM_HCI_NODE_GET_FEATURES: + NG_MKRESPONSE(rsp,msg,sizeof(unit->features),M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + bcopy(&unit->features,rsp->data,sizeof(unit->features)); + break; + + /* Get stat */ + case NGM_HCI_NODE_GET_STAT: + NG_MKRESPONSE(rsp, msg, sizeof(unit->stat), M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + bcopy(&unit->stat, rsp->data, sizeof(unit->stat)); + break; + + /* Reset stat */ + case NGM_HCI_NODE_RESET_STAT: + NG_HCI_STAT_RESET(unit->stat); + break; + + /* Clean up neighbors list */ + case NGM_HCI_NODE_FLUSH_NEIGHBOR_CACHE: + ng_hci_flush_neighbor_cache(unit); + break; + + /* Get neighbor cache entries */ + case NGM_HCI_NODE_GET_NEIGHBOR_CACHE: { + ng_hci_neighbor_p n = NULL; + ng_hci_node_get_neighbor_cache_ep *e1 = NULL; + ng_hci_node_neighbor_cache_entry_ep *e2 = NULL; + int s = 0; + + /* Look for the fresh entries in the cache */ + for (n = LIST_FIRST(&unit->neighbors); n != NULL; ) { + ng_hci_neighbor_p nn = LIST_NEXT(n, next); + + if (ng_hci_neighbor_stale(n)) + ng_hci_free_neighbor(n); + else + s ++; + + n = nn; + } + if (s > NG_HCI_MAX_NEIGHBOR_NUM) + s = NG_HCI_MAX_NEIGHBOR_NUM; + + /* Prepare response */ + NG_MKRESPONSE(rsp, msg, sizeof(*e1) + s * sizeof(*e2), + M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + e1 = (ng_hci_node_get_neighbor_cache_ep *)(rsp->data); + e2 = (ng_hci_node_neighbor_cache_entry_ep *)(e1 + 1); + + e1->num_entries = s; + + LIST_FOREACH(n, &unit->neighbors, next) { + e2->page_scan_rep_mode = n->page_scan_rep_mode; + e2->page_scan_mode = n->page_scan_mode; + e2->clock_offset = n->clock_offset; + bcopy(&n->bdaddr, &e2->bdaddr, + sizeof(e2->bdaddr)); + bcopy(&n->features, &e2->features, + sizeof(e2->features)); + + e2 ++; + if (--s <= 0) + break; + } + } break; + + /* Get connection list */ + case NGM_HCI_NODE_GET_CON_LIST: { + ng_hci_unit_con_p c = NULL; + ng_hci_node_con_list_ep *e1 = NULL; + ng_hci_node_con_ep *e2 = NULL; + int s = 0; + + /* Count number of connections in the list */ + LIST_FOREACH(c, &unit->con_list, next) + s ++; + if (s > NG_HCI_MAX_CON_NUM) + s = NG_HCI_MAX_CON_NUM; + + /* Prepare response */ + NG_MKRESPONSE(rsp, msg, sizeof(*e1) + s * sizeof(*e2), + M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + e1 = (ng_hci_node_con_list_ep *)(rsp->data); + e2 = (ng_hci_node_con_ep *)(e1 + 1); + + e1->num_connections = s; + + LIST_FOREACH(c, &unit->con_list, next) { + e2->link_type = c->link_type; + e2->encryption_mode= c->encryption_mode; + e2->mode = c->mode; + e2->role = c->role; + + e2->state = c->state; + + e2->pending = c->pending; + e2->queue_len = NG_BT_ITEMQ_LEN(&c->conq); + + e2->con_handle = c->con_handle; + bcopy(&c->bdaddr, &e2->bdaddr, + sizeof(e2->bdaddr)); + + e2 ++; + if (--s <= 0) + break; + } + } break; + + /* Get link policy settings mask */ + case NGM_HCI_NODE_GET_LINK_POLICY_SETTINGS_MASK: + NG_MKRESPONSE(rsp, msg, sizeof(unit->link_policy_mask), + M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + *((ng_hci_node_link_policy_mask_ep *)(rsp->data)) = + unit->link_policy_mask; + break; + + /* Set link policy settings mask */ + case NGM_HCI_NODE_SET_LINK_POLICY_SETTINGS_MASK: + if (msg->header.arglen != + sizeof(ng_hci_node_link_policy_mask_ep)) { + error = EMSGSIZE; + break; + } + + unit->link_policy_mask = + *((ng_hci_node_link_policy_mask_ep *) + (msg->data)); + break; + + /* Get packet mask */ + case NGM_HCI_NODE_GET_PACKET_MASK: + NG_MKRESPONSE(rsp, msg, sizeof(unit->packet_mask), + M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + *((ng_hci_node_packet_mask_ep *)(rsp->data)) = + unit->packet_mask; + break; + + /* Set packet mask */ + case NGM_HCI_NODE_SET_PACKET_MASK: + if (msg->header.arglen != + sizeof(ng_hci_node_packet_mask_ep)) { + error = EMSGSIZE; + break; + } + + unit->packet_mask = + *((ng_hci_node_packet_mask_ep *)(msg->data)); + break; + + /* Get role switch */ + case NGM_HCI_NODE_GET_ROLE_SWITCH: + NG_MKRESPONSE(rsp, msg, sizeof(unit->role_switch), + M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + *((ng_hci_node_role_switch_ep *)(rsp->data)) = + unit->role_switch; + break; + + /* Set role switch */ + case NGM_HCI_NODE_SET_ROLE_SWITCH: + if (msg->header.arglen != + sizeof(ng_hci_node_role_switch_ep)) { + error = EMSGSIZE; + break; + } + + unit->role_switch = + *((ng_hci_node_role_switch_ep *)(msg->data)); + break; + + default: + error = EINVAL; + break; + } + break; + + default: + error = EINVAL; + break; + } + + /* NG_RESPOND_MSG should take care of "item" and "rsp" */ + NG_RESPOND_MSG(error, node, item, rsp); + NG_FREE_MSG(msg); + + return (error); +} /* ng_hci_default_rcvmsg */ + +/* + * Process control message from upstream hooks (ACL and SCO). + * Handle LP_xxx messages here, give everything else to default routine. + */ + +static int +ng_hci_upper_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + ng_hci_unit_p unit = (ng_hci_unit_p) NG_NODE_PRIVATE(node); + int error = 0; + + switch (NGI_MSG(item)->header.typecookie) { + case NGM_HCI_COOKIE: + switch (NGI_MSG(item)->header.cmd) { + case NGM_HCI_LP_CON_REQ: + error = ng_hci_lp_con_req(unit, item, lasthook); + break; + + case NGM_HCI_LP_DISCON_REQ: /* XXX not defined by specs */ + error = ng_hci_lp_discon_req(unit, item, lasthook); + break; + + case NGM_HCI_LP_CON_RSP: + error = ng_hci_lp_con_rsp(unit, item, lasthook); + break; + + case NGM_HCI_LP_QOS_REQ: + error = ng_hci_lp_qos_req(unit, item, lasthook); + break; + + default: + error = ng_hci_default_rcvmsg(node, item, lasthook); + break; + } + break; + + default: + error = ng_hci_default_rcvmsg(node, item, lasthook); + break; + } + + return (error); +} /* ng_hci_upper_rcvmsg */ + +/* + * Process data packet from the driver hook. + * We expect HCI events, ACL or SCO data packets. + */ + +static int +ng_hci_drv_rcvdata(hook_p hook, item_p item) +{ + ng_hci_unit_p unit = (ng_hci_unit_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct mbuf *m = NULL; + int error = 0; + + /* Process packet */ + m = NGI_M(item); /* item still has mbuf, just peeking */ + m->m_flags |= M_PROTO1; /* mark as incoming packet */ + + NG_HCI_STAT_BYTES_RECV(unit->stat, m->m_pkthdr.len); + + /* Give copy packet to RAW hook */ + ng_hci_mtap(unit, m); + + /* + * XXX XXX XXX + * Lower layer drivers MUST NOT send mbuf chain with empty mbuf at + * the beginning of the chain. HCI layer WILL NOT call m_pullup() here. + */ + + switch (*mtod(m, u_int8_t *)) { + case NG_HCI_ACL_DATA_PKT: + NG_HCI_STAT_ACL_RECV(unit->stat); + + if ((unit->state & NG_HCI_UNIT_READY) != NG_HCI_UNIT_READY || + unit->acl == NULL || NG_HOOK_NOT_VALID(unit->acl)) { + NG_HCI_WARN( +"%s: %s - could not forward HCI ACL data packet, state=%#x, hook=%p\n", + __func__, NG_NODE_NAME(unit->node), + unit->state, unit->acl); + + NG_FREE_ITEM(item); + } else + NG_FWD_ITEM_HOOK(error, item, unit->acl); + break; + + case NG_HCI_SCO_DATA_PKT: + NG_HCI_STAT_SCO_RECV(unit->stat); + + if ((unit->state & NG_HCI_UNIT_READY) != NG_HCI_UNIT_READY || + unit->sco == NULL || NG_HOOK_NOT_VALID(unit->sco)) { + NG_HCI_WARN( +"%s: %s - could not forward HCI SCO data packet, state=%#x, hook=%p\n", + __func__, NG_NODE_NAME(unit->node), + unit->state, unit->sco); + + NG_FREE_ITEM(item); + } else + NG_FWD_ITEM_HOOK(error, item, unit->sco); + break; + + case NG_HCI_EVENT_PKT: + NG_HCI_STAT_EVNT_RECV(unit->stat); + + /* Detach mbuf, discard item and process event */ + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + error = ng_hci_process_event(unit, m); + break; + + default: + NG_HCI_ALERT( +"%s: %s - got unknown HCI packet type=%#x\n", + __func__, NG_NODE_NAME(unit->node), + *mtod(m, u_int8_t *)); + + NG_FREE_ITEM(item); + + error = EINVAL; + break; + } + + return (error); +} /* ng_hci_drv_rcvdata */ + +/* + * Process data packet from ACL upstream hook. + * We expect valid HCI ACL data packets. + */ + +static int +ng_hci_acl_rcvdata(hook_p hook, item_p item) +{ + ng_hci_unit_p unit = (ng_hci_unit_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct mbuf *m = NULL; + ng_hci_unit_con_p con = NULL; + u_int16_t con_handle; + int size, error = 0; + + NG_HCI_BUFF_ACL_SIZE(unit->buffer, size); + + /* Check packet */ + NGI_GET_M(item, m); + + if (*mtod(m, u_int8_t *) != NG_HCI_ACL_DATA_PKT) { + NG_HCI_ALERT( +"%s: %s - invalid HCI data packet type=%#x\n", + __func__, NG_NODE_NAME(unit->node), + *mtod(m, u_int8_t *)); + + error = EINVAL; + goto drop; + } + + if (m->m_pkthdr.len < sizeof(ng_hci_acldata_pkt_t) || + m->m_pkthdr.len > sizeof(ng_hci_acldata_pkt_t) + size) { + NG_HCI_ALERT( +"%s: %s - invalid HCI ACL data packet, len=%d, mtu=%d\n", + __func__, NG_NODE_NAME(unit->node), + m->m_pkthdr.len, size); + + error = EMSGSIZE; + goto drop; + } + + NG_HCI_M_PULLUP(m, sizeof(ng_hci_acldata_pkt_t)); + if (m == NULL) { + error = ENOBUFS; + goto drop; + } + + con_handle = NG_HCI_CON_HANDLE(le16toh( + mtod(m, ng_hci_acldata_pkt_t *)->con_handle)); + size = le16toh(mtod(m, ng_hci_acldata_pkt_t *)->length); + + if (m->m_pkthdr.len != sizeof(ng_hci_acldata_pkt_t) + size) { + NG_HCI_ALERT( +"%s: %s - invalid HCI ACL data packet size, len=%d, length=%d\n", + __func__, NG_NODE_NAME(unit->node), + m->m_pkthdr.len, size); + + error = EMSGSIZE; + goto drop; + } + + /* Queue packet */ + con = ng_hci_con_by_handle(unit, con_handle); + if (con == NULL) { + NG_HCI_ERR( +"%s: %s - unexpected HCI ACL data packet. Connection does not exists, " \ +"con_handle=%d\n", __func__, NG_NODE_NAME(unit->node), con_handle); + + error = ENOENT; + goto drop; + } + + if (con->link_type != NG_HCI_LINK_ACL) { + NG_HCI_ERR( +"%s: %s - unexpected HCI ACL data packet. Not ACL link, con_handle=%d, " \ +"link_type=%d\n", __func__, NG_NODE_NAME(unit->node), + con_handle, con->link_type); + + error = EINVAL; + goto drop; + } + + if (con->state != NG_HCI_CON_OPEN) { + NG_HCI_ERR( +"%s: %s - unexpected HCI ACL data packet. Invalid connection state=%d, " \ +"con_handle=%d\n", __func__, NG_NODE_NAME(unit->node), + con->state, con_handle); + + error = EHOSTDOWN; + goto drop; + } + + if (NG_BT_ITEMQ_FULL(&con->conq)) { + NG_HCI_ALERT( +"%s: %s - dropping HCI ACL data packet, con_handle=%d, len=%d, queue_len=%d\n", + __func__, NG_NODE_NAME(unit->node), con_handle, + m->m_pkthdr.len, NG_BT_ITEMQ_LEN(&con->conq)); + + NG_BT_ITEMQ_DROP(&con->conq); + + error = ENOBUFS; + goto drop; + } + + /* Queue item and schedule data transfer */ + NGI_M(item) = m; + NG_BT_ITEMQ_ENQUEUE(&con->conq, item); + item = NULL; + m = NULL; + + ng_hci_send_data(unit); +drop: + if (item != NULL) + NG_FREE_ITEM(item); + + NG_FREE_M(m); /* NG_FREE_M() checks for m != NULL */ + + return (error); +} /* ng_hci_acl_rcvdata */ + +/* + * Process data packet from SCO upstream hook. + * We expect valid HCI SCO data packets + */ + +static int +ng_hci_sco_rcvdata(hook_p hook, item_p item) +{ + ng_hci_unit_p unit = (ng_hci_unit_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct mbuf *m = NULL; + ng_hci_unit_con_p con = NULL; + u_int16_t con_handle; + int size, error = 0; + + NG_HCI_BUFF_SCO_SIZE(unit->buffer, size); + + /* Check packet */ + NGI_GET_M(item, m); + + if (*mtod(m, u_int8_t *) != NG_HCI_SCO_DATA_PKT) { + NG_HCI_ALERT( +"%s: %s - invalid HCI data packet type=%#x\n", + __func__, NG_NODE_NAME(unit->node), + *mtod(m, u_int8_t *)); + + error = EINVAL; + goto drop; + } + + if (m->m_pkthdr.len < sizeof(ng_hci_scodata_pkt_t) || + m->m_pkthdr.len > sizeof(ng_hci_scodata_pkt_t) + size) { + NG_HCI_ALERT( +"%s: %s - invalid HCI SCO data packet, len=%d, mtu=%d\n", + __func__, NG_NODE_NAME(unit->node), + m->m_pkthdr.len, size); + + error = EMSGSIZE; + goto drop; + } + + NG_HCI_M_PULLUP(m, sizeof(ng_hci_scodata_pkt_t)); + if (m == NULL) { + error = ENOBUFS; + goto drop; + } + + con_handle = NG_HCI_CON_HANDLE(le16toh( + mtod(m, ng_hci_scodata_pkt_t *)->con_handle)); + size = mtod(m, ng_hci_scodata_pkt_t *)->length; + + if (m->m_pkthdr.len != sizeof(ng_hci_scodata_pkt_t) + size) { + NG_HCI_ALERT( +"%s: %s - invalid HCI SCO data packet size, len=%d, length=%d\n", + __func__, NG_NODE_NAME(unit->node), + m->m_pkthdr.len, size); + + error = EMSGSIZE; + goto drop; + } + + /* Queue packet */ + con = ng_hci_con_by_handle(unit, con_handle); + if (con == NULL) { + NG_HCI_ERR( +"%s: %s - unexpected HCI SCO data packet. Connection does not exists, " \ +"con_handle=%d\n", __func__, NG_NODE_NAME(unit->node), con_handle); + + error = ENOENT; + goto drop; + } + + if (con->link_type != NG_HCI_LINK_SCO) { + NG_HCI_ERR( +"%s: %s - unexpected HCI SCO data packet. Not SCO link, con_handle=%d, " \ +"link_type=%d\n", __func__, NG_NODE_NAME(unit->node), + con_handle, con->link_type); + + error = EINVAL; + goto drop; + } + + if (con->state != NG_HCI_CON_OPEN) { + NG_HCI_ERR( +"%s: %s - unexpected HCI SCO data packet. Invalid connection state=%d, " \ +"con_handle=%d\n", __func__, NG_NODE_NAME(unit->node), + con->state, con_handle); + + error = EHOSTDOWN; + goto drop; + } + + if (NG_BT_ITEMQ_FULL(&con->conq)) { + NG_HCI_ALERT( +"%s: %s - dropping HCI SCO data packet, con_handle=%d, len=%d, queue_len=%d\n", + __func__, NG_NODE_NAME(unit->node), con_handle, + m->m_pkthdr.len, NG_BT_ITEMQ_LEN(&con->conq)); + + NG_BT_ITEMQ_DROP(&con->conq); + + error = ENOBUFS; + goto drop; + } + + /* Queue item and schedule data transfer */ + NGI_M(item) = m; + NG_BT_ITEMQ_ENQUEUE(&con->conq, item); + item = NULL; + m = NULL; + + ng_hci_send_data(unit); +drop: + if (item != NULL) + NG_FREE_ITEM(item); + + NG_FREE_M(m); /* NG_FREE_M() checks for m != NULL */ + + return (error); +} /* ng_hci_sco_rcvdata */ + +/* + * Process data packet from uptream RAW hook. + * We expect valid HCI command packets. + */ + +static int +ng_hci_raw_rcvdata(hook_p hook, item_p item) +{ + ng_hci_unit_p unit = (ng_hci_unit_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct mbuf *m = NULL; + int error = 0; + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + /* Check packet */ + if (*mtod(m, u_int8_t *) != NG_HCI_CMD_PKT) { + NG_HCI_ALERT( +"%s: %s - invalid HCI command packet type=%#x\n", + __func__, NG_NODE_NAME(unit->node), + *mtod(m, u_int8_t *)); + + error = EINVAL; + goto drop; + } + + if (m->m_pkthdr.len < sizeof(ng_hci_cmd_pkt_t)) { + NG_HCI_ALERT( +"%s: %s - invalid HCI command packet len=%d\n", + __func__, NG_NODE_NAME(unit->node), m->m_pkthdr.len); + + error = EMSGSIZE; + goto drop; + } + + NG_HCI_M_PULLUP(m, sizeof(ng_hci_cmd_pkt_t)); + if (m == NULL) { + error = ENOBUFS; + goto drop; + } + + if (m->m_pkthdr.len != + mtod(m, ng_hci_cmd_pkt_t *)->length + sizeof(ng_hci_cmd_pkt_t)) { + NG_HCI_ALERT( +"%s: %s - invalid HCI command packet size, len=%d, length=%d\n", + __func__, NG_NODE_NAME(unit->node), m->m_pkthdr.len, + mtod(m, ng_hci_cmd_pkt_t *)->length); + + error = EMSGSIZE; + goto drop; + } + + if (mtod(m, ng_hci_cmd_pkt_t *)->opcode == 0) { + NG_HCI_ALERT( +"%s: %s - invalid HCI command opcode\n", + __func__, NG_NODE_NAME(unit->node)); + + error = EINVAL; + goto drop; + } + + if (NG_BT_MBUFQ_FULL(&unit->cmdq)) { + NG_HCI_ALERT( +"%s: %s - dropping HCI command packet, len=%d, queue_len=%d\n", + __func__, NG_NODE_NAME(unit->node), m->m_pkthdr.len, + NG_BT_MBUFQ_LEN(&unit->cmdq)); + + NG_BT_MBUFQ_DROP(&unit->cmdq); + + error = ENOBUFS; + goto drop; + } + + /* Queue and send command */ + NG_BT_MBUFQ_ENQUEUE(&unit->cmdq, m); + m = NULL; + + if (!(unit->state & NG_HCI_UNIT_COMMAND_PENDING)) + error = ng_hci_send_command(unit); +drop: + NG_FREE_M(m); /* NG_FREE_M() checks for m != NULL */ + + return (error); +} /* ng_hci_raw_rcvdata */ + diff --git a/sys/netgraph7/bluetooth/hci/ng_hci_misc.c b/sys/netgraph7/bluetooth/hci/ng_hci_misc.c new file mode 100644 index 0000000000..c4b6caf70b --- /dev/null +++ b/sys/netgraph7/bluetooth/hci/ng_hci_misc.c @@ -0,0 +1,498 @@ +/* + * ng_hci_misc.c + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_hci_misc.c,v 1.5 2003/09/08 18:57:51 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/hci/ng_hci_misc.c,v 1.10 2005/01/07 01:45:43 imp Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/****************************************************************************** + ****************************************************************************** + ** Utility routines + ****************************************************************************** + ******************************************************************************/ + +/* + * Give packet to RAW hook + * Assumes input mbuf is read only. + */ + +void +ng_hci_mtap(ng_hci_unit_p unit, struct mbuf *m0) +{ + struct mbuf *m = NULL; + int error = 0; + + if (unit->raw != NULL && NG_HOOK_IS_VALID(unit->raw)) { + m = m_dup(m0, M_DONTWAIT); + if (m != NULL) + NG_SEND_DATA_ONLY(error, unit->raw, m); + + if (error != 0) + NG_HCI_INFO( +"%s: %s - Could not forward packet, error=%d\n", + __func__, NG_NODE_NAME(unit->node), error); + } +} /* ng_hci_mtap */ + +/* + * Send notification to the upper layer's + */ + +void +ng_hci_node_is_up(node_p node, hook_p hook, void *arg1, int arg2) +{ + ng_hci_unit_p unit = NULL; + struct ng_mesg *msg = NULL; + ng_hci_node_up_ep *ep = NULL; + int error; + + if (node == NULL || NG_NODE_NOT_VALID(node) || + hook == NULL || NG_HOOK_NOT_VALID(hook)) + return; + + unit = (ng_hci_unit_p) NG_NODE_PRIVATE(node); + if ((unit->state & NG_HCI_UNIT_READY) != NG_HCI_UNIT_READY) + return; + + if (hook != unit->acl && hook != unit->sco) + return; + + NG_MKMESSAGE(msg,NGM_HCI_COOKIE,NGM_HCI_NODE_UP,sizeof(*ep),M_NOWAIT); + if (msg != NULL) { + ep = (ng_hci_node_up_ep *)(msg->data); + + if (hook == unit->acl) { + NG_HCI_BUFF_ACL_SIZE(unit->buffer, ep->pkt_size); + NG_HCI_BUFF_ACL_TOTAL(unit->buffer, ep->num_pkts); + } else { + NG_HCI_BUFF_SCO_SIZE(unit->buffer, ep->pkt_size); + NG_HCI_BUFF_SCO_TOTAL(unit->buffer, ep->num_pkts); + } + + bcopy(&unit->bdaddr, &ep->bdaddr, sizeof(ep->bdaddr)); + + NG_SEND_MSG_HOOK(error, node, msg, hook, 0); + } else + error = ENOMEM; + + if (error != 0) + NG_HCI_INFO( +"%s: %s - failed to send NODE_UP message to hook \"%s\", error=%d\n", + __func__, NG_NODE_NAME(unit->node), + NG_HOOK_NAME(hook), error); +} /* ng_hci_node_is_up */ + +/* + * Clean unit (helper) + */ + +void +ng_hci_unit_clean(ng_hci_unit_p unit, int reason) +{ + int size; + + /* Drain command queue */ + if (unit->state & NG_HCI_UNIT_COMMAND_PENDING) + ng_hci_command_untimeout(unit); + + NG_BT_MBUFQ_DRAIN(&unit->cmdq); + NG_HCI_BUFF_CMD_SET(unit->buffer, 1); + + /* Clean up connection list */ + while (!LIST_EMPTY(&unit->con_list)) { + ng_hci_unit_con_p con = LIST_FIRST(&unit->con_list); + + /* Remove all timeouts (if any) */ + if (con->flags & NG_HCI_CON_TIMEOUT_PENDING) + ng_hci_con_untimeout(con); + + /* + * Notify upper layer protocol and destroy connection + * descriptor. Do not really care about the result. + */ + + ng_hci_lp_discon_ind(con, reason); + ng_hci_free_con(con); + } + + NG_HCI_BUFF_ACL_TOTAL(unit->buffer, size); + NG_HCI_BUFF_ACL_FREE(unit->buffer, size); + + NG_HCI_BUFF_SCO_TOTAL(unit->buffer, size); + NG_HCI_BUFF_SCO_FREE(unit->buffer, size); + + /* Clean up neighbors list */ + ng_hci_flush_neighbor_cache(unit); +} /* ng_hci_unit_clean */ + +/* + * Allocate and link new unit neighbor cache entry + */ + +ng_hci_neighbor_p +ng_hci_new_neighbor(ng_hci_unit_p unit) +{ + ng_hci_neighbor_p n = NULL; + + MALLOC(n, ng_hci_neighbor_p, sizeof(*n), M_NETGRAPH_HCI, + M_NOWAIT | M_ZERO); + if (n != NULL) { + getmicrotime(&n->updated); + LIST_INSERT_HEAD(&unit->neighbors, n, next); + } + + return (n); +} /* ng_hci_new_neighbor */ + +/* + * Free unit neighbor cache entry + */ + +void +ng_hci_free_neighbor(ng_hci_neighbor_p n) +{ + LIST_REMOVE(n, next); + bzero(n, sizeof(*n)); + FREE(n, M_NETGRAPH_HCI); +} /* ng_hci_free_neighbor */ + +/* + * Flush neighbor cache + */ + +void +ng_hci_flush_neighbor_cache(ng_hci_unit_p unit) +{ + while (!LIST_EMPTY(&unit->neighbors)) + ng_hci_free_neighbor(LIST_FIRST(&unit->neighbors)); +} /* ng_hci_flush_neighbor_cache */ + +/* + * Lookup unit in neighbor cache + */ + +ng_hci_neighbor_p +ng_hci_get_neighbor(ng_hci_unit_p unit, bdaddr_p bdaddr) +{ + ng_hci_neighbor_p n = NULL; + + for (n = LIST_FIRST(&unit->neighbors); n != NULL; ) { + ng_hci_neighbor_p nn = LIST_NEXT(n, next); + + if (!ng_hci_neighbor_stale(n)) { + if (bcmp(&n->bdaddr, bdaddr, sizeof(*bdaddr)) == 0) + break; + } else + ng_hci_free_neighbor(n); /* remove old entry */ + + n = nn; + } + + return (n); +} /* ng_hci_get_neighbor */ + +/* + * Check if neighbor entry is stale + */ + +int +ng_hci_neighbor_stale(ng_hci_neighbor_p n) +{ + struct timeval now; + + getmicrotime(&now); + + return (now.tv_sec - n->updated.tv_sec > bluetooth_hci_max_neighbor_age()); +} /* ng_hci_neighbor_stale */ + +/* + * Allocate and link new connection descriptor + */ + +ng_hci_unit_con_p +ng_hci_new_con(ng_hci_unit_p unit, int link_type) +{ + ng_hci_unit_con_p con = NULL; + int num_pkts; + static int fake_con_handle = 0x0f00; + + MALLOC(con, ng_hci_unit_con_p, sizeof(*con), M_NETGRAPH_HCI, + M_NOWAIT | M_ZERO); + if (con != NULL) { + con->unit = unit; + con->state = NG_HCI_CON_CLOSED; + + /* + * XXX + * + * Assign fake connection handle to the connection descriptor. + * Bluetooth specification marks 0x0f00 - 0x0fff connection + * handles as reserved. We need this fake connection handles + * for timeouts. Connection handle will be passed as argument + * to timeout so when timeout happens we can find the right + * connection descriptor. We can not pass pointers, because + * timeouts are external (to Netgraph) events and there might + * be a race when node/hook goes down and timeout event already + * went into node's queue + */ + + con->con_handle = fake_con_handle ++; + if (fake_con_handle > 0x0fff) + fake_con_handle = 0x0f00; + + con->link_type = link_type; + + if (con->link_type == NG_HCI_LINK_ACL) + NG_HCI_BUFF_ACL_TOTAL(unit->buffer, num_pkts); + else + NG_HCI_BUFF_SCO_TOTAL(unit->buffer, num_pkts); + + NG_BT_ITEMQ_INIT(&con->conq, num_pkts); + + ng_callout_init(&con->con_timo); + + LIST_INSERT_HEAD(&unit->con_list, con, next); + } + + return (con); +} /* ng_hci_new_con */ + +/* + * Free connection descriptor + */ + +void +ng_hci_free_con(ng_hci_unit_con_p con) +{ + LIST_REMOVE(con, next); + + /* + * If we have pending packets then assume that Host Controller has + * flushed these packets and we can free them too + */ + + if (con->link_type == NG_HCI_LINK_ACL) + NG_HCI_BUFF_ACL_FREE(con->unit->buffer, con->pending); + else + NG_HCI_BUFF_SCO_FREE(con->unit->buffer, con->pending); + + NG_BT_ITEMQ_DESTROY(&con->conq); + + bzero(con, sizeof(*con)); + FREE(con, M_NETGRAPH_HCI); +} /* ng_hci_free_con */ + +/* + * Lookup connection for given unit and connection handle. + */ + +ng_hci_unit_con_p +ng_hci_con_by_handle(ng_hci_unit_p unit, int con_handle) +{ + ng_hci_unit_con_p con = NULL; + + LIST_FOREACH(con, &unit->con_list, next) + if (con->con_handle == con_handle) + break; + + return (con); +} /* ng_hci_con_by_handle */ + +/* + * Lookup connection for given unit, link type and remove unit address + */ + +ng_hci_unit_con_p +ng_hci_con_by_bdaddr(ng_hci_unit_p unit, bdaddr_p bdaddr, int link_type) +{ + ng_hci_unit_con_p con = NULL; + + LIST_FOREACH(con, &unit->con_list, next) + if (con->link_type == link_type && + bcmp(&con->bdaddr, bdaddr, sizeof(bdaddr_t)) == 0) + break; + + return (con); +} /* ng_hci_con_by_bdaddr */ + +/* + * Set HCI command timeout + * XXX FIXME: check return code from ng_callout + */ + +int +ng_hci_command_timeout(ng_hci_unit_p unit) +{ + if (unit->state & NG_HCI_UNIT_COMMAND_PENDING) + panic( +"%s: %s - Duplicated command timeout!\n", __func__, NG_NODE_NAME(unit->node)); + + unit->state |= NG_HCI_UNIT_COMMAND_PENDING; + ng_callout(&unit->cmd_timo, unit->node, NULL, + bluetooth_hci_command_timeout(), + ng_hci_process_command_timeout, NULL, 0); + + return (0); +} /* ng_hci_command_timeout */ + +/* + * Unset HCI command timeout + */ + +int +ng_hci_command_untimeout(ng_hci_unit_p unit) +{ + if (!(unit->state & NG_HCI_UNIT_COMMAND_PENDING)) + panic( +"%s: %s - No command timeout!\n", __func__, NG_NODE_NAME(unit->node)); + + if (ng_uncallout(&unit->cmd_timo, unit->node) == 0) + return (ETIMEDOUT); + + unit->state &= ~NG_HCI_UNIT_COMMAND_PENDING; + + return (0); +} /* ng_hci_command_untimeout */ + +/* + * Set HCI connection timeout + * XXX FIXME: check return code from ng_callout + */ + +int +ng_hci_con_timeout(ng_hci_unit_con_p con) +{ + if (con->flags & NG_HCI_CON_TIMEOUT_PENDING) + panic( +"%s: %s - Duplicated connection timeout!\n", + __func__, NG_NODE_NAME(con->unit->node)); + + con->flags |= NG_HCI_CON_TIMEOUT_PENDING; + ng_callout(&con->con_timo, con->unit->node, NULL, + bluetooth_hci_connect_timeout(), + ng_hci_process_con_timeout, NULL, + con->con_handle); + + return (0); +} /* ng_hci_con_timeout */ + +/* + * Unset HCI connection timeout + */ + +int +ng_hci_con_untimeout(ng_hci_unit_con_p con) +{ + if (!(con->flags & NG_HCI_CON_TIMEOUT_PENDING)) + panic( +"%s: %s - No connection timeout!\n", __func__, NG_NODE_NAME(con->unit->node)); + + if (ng_uncallout(&con->con_timo, con->unit->node) == 0) + return (ETIMEDOUT); + + con->flags &= ~NG_HCI_CON_TIMEOUT_PENDING; + + return (0); +} /* ng_hci_con_untimeout */ + +#if 0 +/* + * Convert numeric error code/reason to a string + */ + +char const * const +ng_hci_str_error(u_int16_t code) +{ +#define LAST_ERROR_CODE ((sizeof(s)/sizeof(s[0]))-1) + static char const * const s[] = { + /* 0x00 */ "No error", + /* 0x01 */ "Unknown HCI command", + /* 0x02 */ "No connection", + /* 0x03 */ "Hardware failure", + /* 0x04 */ "Page timeout", + /* 0x05 */ "Authentication failure", + /* 0x06 */ "Key missing", + /* 0x07 */ "Memory full", + /* 0x08 */ "Connection timeout", + /* 0x09 */ "Max number of connections", + /* 0x0a */ "Max number of SCO connections to a unit", + /* 0x0b */ "ACL connection already exists", + /* 0x0c */ "Command disallowed", + /* 0x0d */ "Host rejected due to limited resources", + /* 0x0e */ "Host rejected due to securiity reasons", + /* 0x0f */ "Host rejected due to remote unit is a personal unit", + /* 0x10 */ "Host timeout", + /* 0x11 */ "Unsupported feature or parameter value", + /* 0x12 */ "Invalid HCI command parameter", + /* 0x13 */ "Other end terminated connection: User ended connection", + /* 0x14 */ "Other end terminated connection: Low resources", + /* 0x15 */ "Other end terminated connection: About to power off", + /* 0x16 */ "Connection terminated by local host", + /* 0x17 */ "Repeated attempts", + /* 0x18 */ "Pairing not allowed", + /* 0x19 */ "Unknown LMP PDU", + /* 0x1a */ "Unsupported remote feature", + /* 0x1b */ "SCO offset rejected", + /* 0x1c */ "SCO interval rejected", + /* 0x1d */ "SCO air mode rejected", + /* 0x1e */ "Invalid LMP parameters", + /* 0x1f */ "Unspecified error", + /* 0x20 */ "Unsupported LMP parameter value", + /* 0x21 */ "Role change not allowed", + /* 0x22 */ "LMP response timeout", + /* 0x23 */ "LMP error transaction collision", + /* 0x24 */ "LMP PSU not allowed", + /* 0x25 */ "Encryption mode not acceptable", + /* 0x26 */ "Unit key used", + /* 0x27 */ "QoS is not supported", + /* 0x28 */ "Instant passed", + /* 0x29 */ "Paring with unit key not supported", + /* SHOULD ALWAYS BE LAST */ "Unknown error" + }; + + return ((code >= LAST_ERROR_CODE)? s[LAST_ERROR_CODE] : s[code]); +} /* ng_hci_str_error */ +#endif + diff --git a/sys/netgraph7/bluetooth/hci/ng_hci_misc.h b/sys/netgraph7/bluetooth/hci/ng_hci_misc.h new file mode 100644 index 0000000000..7861b915fe --- /dev/null +++ b/sys/netgraph7/bluetooth/hci/ng_hci_misc.h @@ -0,0 +1,58 @@ +/* + * ng_hci_misc.h + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_hci_misc.h,v 1.3 2003/09/08 18:57:51 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/hci/ng_hci_misc.h,v 1.5 2005/01/07 01:45:43 imp Exp $ + */ + +#ifndef _NETGRAPH_HCI_MISC_H_ +#define _NETGRAPH_HCI_MISC_H_ + +void ng_hci_mtap (ng_hci_unit_p, struct mbuf *); +void ng_hci_node_is_up (node_p, hook_p, void *, int); +void ng_hci_unit_clean (ng_hci_unit_p, int); + +ng_hci_neighbor_p ng_hci_new_neighbor (ng_hci_unit_p); +void ng_hci_free_neighbor (ng_hci_neighbor_p); +void ng_hci_flush_neighbor_cache (ng_hci_unit_p); +ng_hci_neighbor_p ng_hci_get_neighbor (ng_hci_unit_p, bdaddr_p); +int ng_hci_neighbor_stale (ng_hci_neighbor_p); + +ng_hci_unit_con_p ng_hci_new_con (ng_hci_unit_p, int); +void ng_hci_free_con (ng_hci_unit_con_p); +ng_hci_unit_con_p ng_hci_con_by_handle (ng_hci_unit_p, int); +ng_hci_unit_con_p ng_hci_con_by_bdaddr (ng_hci_unit_p, bdaddr_p, int); + +int ng_hci_command_timeout (ng_hci_unit_p); +int ng_hci_command_untimeout (ng_hci_unit_p); +int ng_hci_con_timeout (ng_hci_unit_con_p); +int ng_hci_con_untimeout (ng_hci_unit_con_p); + +#endif /* ndef _NETGRAPH_HCI_MISC_H_ */ + diff --git a/sys/netgraph7/bluetooth/hci/ng_hci_prse.h b/sys/netgraph7/bluetooth/hci/ng_hci_prse.h new file mode 100644 index 0000000000..5856f171e4 --- /dev/null +++ b/sys/netgraph7/bluetooth/hci/ng_hci_prse.h @@ -0,0 +1,219 @@ +/* + * ng_hci_prse.h + */ + +/*- + * Copyright (c) 2001 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_hci_prse.h,v 1.2 2003/03/18 00:09:36 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/hci/ng_hci_prse.h,v 1.4 2005/01/07 01:45:43 imp Exp $ + */ + +/*************************************************************************** + *************************************************************************** + ** ng_parse definitions for the HCI node + *************************************************************************** + ***************************************************************************/ + +#ifndef _NETGRAPH_HCI_PRSE_H_ +#define _NETGRAPH_HCI_PRSE_H_ + +/* BDADDR */ +static const struct ng_parse_fixedarray_info ng_hci_bdaddr_type_info = { + &ng_parse_uint8_type, + NG_HCI_BDADDR_SIZE +}; +static const struct ng_parse_type ng_hci_bdaddr_type = { + &ng_parse_fixedarray_type, + &ng_hci_bdaddr_type_info +}; + +/* Features */ +static const struct ng_parse_fixedarray_info ng_hci_features_type_info = { + &ng_parse_uint8_type, + NG_HCI_FEATURES_SIZE +}; +static const struct ng_parse_type ng_hci_features_type = { + &ng_parse_fixedarray_type, + &ng_hci_features_type_info +}; + +/* Buffer info */ +static const struct ng_parse_struct_field ng_hci_buffer_type_fields[] = +{ + { "cmd_free", &ng_parse_uint8_type, }, + { "sco_size", &ng_parse_uint8_type, }, + { "sco_pkts", &ng_parse_uint16_type, }, + { "sco_free", &ng_parse_uint16_type, }, + { "acl_size", &ng_parse_uint16_type, }, + { "acl_pkts", &ng_parse_uint16_type, }, + { "acl_free", &ng_parse_uint16_type, }, + { NULL, } +}; +static const struct ng_parse_type ng_hci_buffer_type = { + &ng_parse_struct_type, + &ng_hci_buffer_type_fields +}; + +/* Stat info */ +static const struct ng_parse_struct_field ng_hci_stat_type_fields[] = +{ + { "cmd_sent", &ng_parse_uint32_type, }, + { "evnt_recv", &ng_parse_uint32_type, }, + { "acl_recv", &ng_parse_uint32_type, }, + { "acl_sent", &ng_parse_uint32_type, }, + { "sco_recv", &ng_parse_uint32_type, }, + { "sco_sent", &ng_parse_uint32_type, }, + { "bytes_recv", &ng_parse_uint32_type, }, + { "bytes_sent", &ng_parse_uint32_type, }, + { NULL, } +}; +static const struct ng_parse_type ng_hci_stat_type = { + &ng_parse_struct_type, + &ng_hci_stat_type_fields +}; + +/* + * HCI node command list + */ + +static const struct ng_cmdlist ng_hci_cmdlist[] = { + { + NGM_HCI_COOKIE, + NGM_HCI_NODE_GET_STATE, + "get_state", + NULL, + &ng_parse_uint16_type + }, + { + NGM_HCI_COOKIE, + NGM_HCI_NODE_INIT, + "init", + NULL, + NULL + }, + { + NGM_HCI_COOKIE, + NGM_HCI_NODE_GET_DEBUG, + "get_debug", + NULL, + &ng_parse_uint16_type + }, + { + NGM_HCI_COOKIE, + NGM_HCI_NODE_SET_DEBUG, + "set_debug", + &ng_parse_uint16_type, + NULL + }, + { + NGM_HCI_COOKIE, + NGM_HCI_NODE_GET_BUFFER, + "get_buff_info", + NULL, + &ng_hci_buffer_type + }, + { + NGM_HCI_COOKIE, + NGM_HCI_NODE_GET_BDADDR, + "get_bdaddr", + NULL, + &ng_hci_bdaddr_type + }, + { + NGM_HCI_COOKIE, + NGM_HCI_NODE_GET_FEATURES, + "get_features", + NULL, + &ng_hci_features_type + }, + { + NGM_HCI_COOKIE, + NGM_HCI_NODE_GET_STAT, + "get_stat", + NULL, + &ng_hci_stat_type + }, + { + NGM_HCI_COOKIE, + NGM_HCI_NODE_RESET_STAT, + "reset_stat", + NULL, + NULL + }, + { + NGM_HCI_COOKIE, + NGM_HCI_NODE_FLUSH_NEIGHBOR_CACHE, + "flush_ncache", + NULL, + NULL + }, + { + NGM_HCI_COOKIE, + NGM_HCI_NODE_GET_LINK_POLICY_SETTINGS_MASK, + "get_lm_mask", + NULL, + &ng_parse_uint16_type + }, + { + NGM_HCI_COOKIE, + NGM_HCI_NODE_SET_LINK_POLICY_SETTINGS_MASK, + "set_lm_mask", + &ng_parse_uint16_type, + NULL + }, + { + NGM_HCI_COOKIE, + NGM_HCI_NODE_GET_PACKET_MASK, + "get_pkt_mask", + NULL, + &ng_parse_uint16_type + }, + { + NGM_HCI_COOKIE, + NGM_HCI_NODE_SET_PACKET_MASK, + "set_pkt_mask", + &ng_parse_uint16_type, + NULL + }, + { + NGM_HCI_COOKIE, + NGM_HCI_NODE_GET_ROLE_SWITCH, + "get_role_sw", + NULL, + &ng_parse_uint16_type + }, + { + NGM_HCI_COOKIE, + NGM_HCI_NODE_SET_ROLE_SWITCH, + "set_role_sw", + &ng_parse_uint16_type, + NULL + }, + { 0, } +}; + +#endif /* ndef _NETGRAPH_HCI_PRSE_H_ */ + diff --git a/sys/netgraph7/bluetooth/hci/ng_hci_ulpi.c b/sys/netgraph7/bluetooth/hci/ng_hci_ulpi.c new file mode 100644 index 0000000000..1da18d4f6a --- /dev/null +++ b/sys/netgraph7/bluetooth/hci/ng_hci_ulpi.c @@ -0,0 +1,1212 @@ +/* + * ng_hci_ulpi.c + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_hci_ulpi.c,v 1.7 2003/09/08 18:57:51 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/hci/ng_hci_ulpi.c,v 1.8 2005/01/07 01:45:43 imp Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/****************************************************************************** + ****************************************************************************** + ** Upper Layer Protocol Interface module + ****************************************************************************** + ******************************************************************************/ + +static int ng_hci_lp_acl_con_req (ng_hci_unit_p, item_p, hook_p); +static int ng_hci_lp_sco_con_req (ng_hci_unit_p, item_p, hook_p); + +/* + * Process LP_ConnectReq event from the upper layer protocol + */ + +int +ng_hci_lp_con_req(ng_hci_unit_p unit, item_p item, hook_p hook) +{ + if ((unit->state & NG_HCI_UNIT_READY) != NG_HCI_UNIT_READY) { + NG_HCI_WARN( +"%s: %s - unit is not ready, state=%#x\n", + __func__, NG_NODE_NAME(unit->node), unit->state); + + NG_FREE_ITEM(item); + + return (ENXIO); + } + + if (NGI_MSG(item)->header.arglen != sizeof(ng_hci_lp_con_req_ep)) { + NG_HCI_ALERT( +"%s: %s - invalid LP_ConnectReq message size=%d\n", + __func__, NG_NODE_NAME(unit->node), + NGI_MSG(item)->header.arglen); + + NG_FREE_ITEM(item); + + return (EMSGSIZE); + } + + if (((ng_hci_lp_con_req_ep *)(NGI_MSG(item)->data))->link_type == NG_HCI_LINK_ACL) + return (ng_hci_lp_acl_con_req(unit, item, hook)); + + if (hook != unit->sco) { + NG_HCI_WARN( +"%s: %s - LP_ConnectReq for SCO connection came from wrong hook=%p\n", + __func__, NG_NODE_NAME(unit->node), hook); + + NG_FREE_ITEM(item); + + return (EINVAL); + } + + return (ng_hci_lp_sco_con_req(unit, item, hook)); +} /* ng_hci_lp_con_req */ + +/* + * Request to create new ACL connection + */ + +static int +ng_hci_lp_acl_con_req(ng_hci_unit_p unit, item_p item, hook_p hook) +{ + struct acl_con_req { + ng_hci_cmd_pkt_t hdr; + ng_hci_create_con_cp cp; + } __attribute__ ((packed)) *req = NULL; + ng_hci_lp_con_req_ep *ep = NULL; + ng_hci_unit_con_p con = NULL; + ng_hci_neighbor_t *n = NULL; + struct mbuf *m = NULL; + int error = 0; + + ep = (ng_hci_lp_con_req_ep *)(NGI_MSG(item)->data); + + /* + * Only one ACL connection can exist between each pair of units. + * So try to find ACL connection descriptor (in any state) that + * has requested remote BD_ADDR. + * + * Two cases: + * + * 1) We do not have connection to the remote unit. This is simple. + * Just create new connection descriptor and send HCI command to + * create new connection. + * + * 2) We do have connection descriptor. We need to check connection + * state: + * + * 2.1) NG_HCI_CON_W4_LP_CON_RSP means that we are in the middle of + * accepting connection from the remote unit. This is a race + * condition. We will ignore this message. + * + * 2.2) NG_HCI_CON_W4_CONN_COMPLETE means that upper layer already + * requested connection or we just accepted it. In any case + * all we need to do here is set appropriate notification bit + * and wait. + * + * 2.3) NG_HCI_CON_OPEN means connection is open. Just reply back + * and let upper layer know that we have connection already. + */ + + con = ng_hci_con_by_bdaddr(unit, &ep->bdaddr, NG_HCI_LINK_ACL); + if (con != NULL) { + switch (con->state) { + case NG_HCI_CON_W4_LP_CON_RSP: /* XXX */ + error = EALREADY; + break; + + case NG_HCI_CON_W4_CONN_COMPLETE: + if (hook == unit->acl) + con->flags |= NG_HCI_CON_NOTIFY_ACL; + else + con->flags |= NG_HCI_CON_NOTIFY_SCO; + break; + + case NG_HCI_CON_OPEN: { + struct ng_mesg *msg = NULL; + ng_hci_lp_con_cfm_ep *cfm = NULL; + + if (hook != NULL && NG_HOOK_IS_VALID(hook)) { + NGI_GET_MSG(item, msg); + NG_FREE_MSG(msg); + + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, + NGM_HCI_LP_CON_CFM, sizeof(*cfm), + M_NOWAIT); + if (msg != NULL) { + cfm = (ng_hci_lp_con_cfm_ep *)msg->data; + cfm->status = 0; + cfm->link_type = con->link_type; + cfm->con_handle = con->con_handle; + bcopy(&con->bdaddr, &cfm->bdaddr, + sizeof(cfm->bdaddr)); + + /* + * This will forward item back to + * sender and set item to NULL + */ + + _NGI_MSG(item) = msg; + NG_FWD_ITEM_HOOK(error, item, hook); + } else + error = ENOMEM; + } else + NG_HCI_INFO( +"%s: %s - Source hook is not valid, hook=%p\n", + __func__, NG_NODE_NAME(unit->node), + hook); + } break; + + default: + panic( +"%s: %s - Invalid connection state=%d\n", + __func__, NG_NODE_NAME(unit->node), con->state); + break; + } + + goto out; + } + + /* + * If we got here then we need to create new ACL connection descriptor + * and submit HCI command. First create new connection desriptor, set + * bdaddr and notification flags. + */ + + con = ng_hci_new_con(unit, NG_HCI_LINK_ACL); + if (con == NULL) { + error = ENOMEM; + goto out; + } + + bcopy(&ep->bdaddr, &con->bdaddr, sizeof(con->bdaddr)); + + /* + * Create HCI command + */ + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + ng_hci_free_con(con); + error = ENOBUFS; + goto out; + } + + m->m_pkthdr.len = m->m_len = sizeof(*req); + req = mtod(m, struct acl_con_req *); + req->hdr.type = NG_HCI_CMD_PKT; + req->hdr.length = sizeof(req->cp); + req->hdr.opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_CREATE_CON)); + + bcopy(&ep->bdaddr, &req->cp.bdaddr, sizeof(req->cp.bdaddr)); + + req->cp.pkt_type = (NG_HCI_PKT_DM1|NG_HCI_PKT_DH1); + if (unit->features[0] & NG_HCI_LMP_3SLOT) + req->cp.pkt_type |= (NG_HCI_PKT_DM3|NG_HCI_PKT_DH3); + if (unit->features[0] & NG_HCI_LMP_5SLOT) + req->cp.pkt_type |= (NG_HCI_PKT_DM5|NG_HCI_PKT_DH5); + + req->cp.pkt_type &= unit->packet_mask; + if ((req->cp.pkt_type & (NG_HCI_PKT_DM1|NG_HCI_PKT_DH1| + NG_HCI_PKT_DM3|NG_HCI_PKT_DH3| + NG_HCI_PKT_DM5|NG_HCI_PKT_DH5)) == 0) + req->cp.pkt_type = (NG_HCI_PKT_DM1|NG_HCI_PKT_DH1); + + req->cp.pkt_type = htole16(req->cp.pkt_type); + + if ((unit->features[0] & NG_HCI_LMP_SWITCH) && unit->role_switch) + req->cp.accept_role_switch = 1; + else + req->cp.accept_role_switch = 0; + + /* + * We may speed up connect by specifying valid parameters. + * So check the neighbor cache. + */ + + n = ng_hci_get_neighbor(unit, &ep->bdaddr); + if (n == NULL) { + req->cp.page_scan_rep_mode = 0; + req->cp.page_scan_mode = 0; + req->cp.clock_offset = 0; + } else { + req->cp.page_scan_rep_mode = n->page_scan_rep_mode; + req->cp.page_scan_mode = n->page_scan_mode; + req->cp.clock_offset = htole16(n->clock_offset); + } + + /* + * Adust connection state + */ + + if (hook == unit->acl) + con->flags |= NG_HCI_CON_NOTIFY_ACL; + else + con->flags |= NG_HCI_CON_NOTIFY_SCO; + + con->state = NG_HCI_CON_W4_CONN_COMPLETE; + ng_hci_con_timeout(con); + + /* + * Queue and send HCI command + */ + + NG_BT_MBUFQ_ENQUEUE(&unit->cmdq, m); + if (!(unit->state & NG_HCI_UNIT_COMMAND_PENDING)) + error = ng_hci_send_command(unit); +out: + if (item != NULL) + NG_FREE_ITEM(item); + + return (error); +} /* ng_hci_lp_acl_con_req */ + +/* + * Request to create new SCO connection + */ + +static int +ng_hci_lp_sco_con_req(ng_hci_unit_p unit, item_p item, hook_p hook) +{ + struct sco_con_req { + ng_hci_cmd_pkt_t hdr; + ng_hci_add_sco_con_cp cp; + } __attribute__ ((packed)) *req = NULL; + ng_hci_lp_con_req_ep *ep = NULL; + ng_hci_unit_con_p acl_con = NULL, sco_con = NULL; + struct mbuf *m = NULL; + int error = 0; + + ep = (ng_hci_lp_con_req_ep *)(NGI_MSG(item)->data); + + /* + * SCO connection without ACL link + * + * If upper layer requests SCO connection and there is no open ACL + * connection to the desired remote unit, we will reject the request. + */ + + LIST_FOREACH(acl_con, &unit->con_list, next) + if (acl_con->link_type == NG_HCI_LINK_ACL && + acl_con->state == NG_HCI_CON_OPEN && + bcmp(&acl_con->bdaddr, &ep->bdaddr, sizeof(bdaddr_t)) == 0) + break; + + if (acl_con == NULL) { + NG_HCI_INFO( +"%s: %s - No open ACL connection to bdaddr=%x:%x:%x:%x:%x:%x\n", + __func__, NG_NODE_NAME(unit->node), + ep->bdaddr.b[5], ep->bdaddr.b[4], ep->bdaddr.b[3], + ep->bdaddr.b[2], ep->bdaddr.b[1], ep->bdaddr.b[0]); + + error = ENOENT; + goto out; + } + + /* + * Multiple SCO connections can exist between the same pair of units. + * We assume that multiple SCO connections have to be opened one after + * another. + * + * Try to find SCO connection descriptor that matches the following: + * + * 1) sco_con->link_type == NG_HCI_LINK_SCO + * + * 2) sco_con->state == NG_HCI_CON_W4_LP_CON_RSP || + * sco_con->state == NG_HCI_CON_W4_CONN_COMPLETE + * + * 3) sco_con->bdaddr == ep->bdaddr + * + * Two cases: + * + * 1) We do not have connection descriptor. This is simple. Just + * create new connection and submit Add_SCO_Connection command. + * + * 2) We do have connection descriptor. We need to check the state. + * + * 2.1) NG_HCI_CON_W4_LP_CON_RSP means we in the middle of accepting + * connection from the remote unit. This is a race condition and + * we will ignore the request. + * + * 2.2) NG_HCI_CON_W4_CONN_COMPLETE means upper layer already requested + * connection or we just accepted it. + */ + + LIST_FOREACH(sco_con, &unit->con_list, next) + if (sco_con->link_type == NG_HCI_LINK_SCO && + (sco_con->state == NG_HCI_CON_W4_LP_CON_RSP || + sco_con->state == NG_HCI_CON_W4_CONN_COMPLETE) && + bcmp(&sco_con->bdaddr, &ep->bdaddr, sizeof(bdaddr_t)) == 0) + break; + + if (sco_con != NULL) { + switch (sco_con->state) { + case NG_HCI_CON_W4_LP_CON_RSP: /* XXX */ + error = EALREADY; + break; + + case NG_HCI_CON_W4_CONN_COMPLETE: + sco_con->flags |= NG_HCI_CON_NOTIFY_SCO; + break; + + default: + panic( +"%s: %s - Inavalid connection state=%d\n", + __func__, NG_NODE_NAME(unit->node), + sco_con->state); + break; + } + + goto out; + } + + /* + * If we got here then we need to create new SCO connection descriptor + * and submit HCI command. + */ + + sco_con = ng_hci_new_con(unit, NG_HCI_LINK_SCO); + if (sco_con == NULL) { + error = ENOMEM; + goto out; + } + + bcopy(&ep->bdaddr, &sco_con->bdaddr, sizeof(sco_con->bdaddr)); + + /* + * Create HCI command + */ + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + ng_hci_free_con(sco_con); + error = ENOBUFS; + goto out; + } + + m->m_pkthdr.len = m->m_len = sizeof(*req); + req = mtod(m, struct sco_con_req *); + req->hdr.type = NG_HCI_CMD_PKT; + req->hdr.length = sizeof(req->cp); + req->hdr.opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_ADD_SCO_CON)); + + req->cp.con_handle = htole16(acl_con->con_handle); + + req->cp.pkt_type = NG_HCI_PKT_HV1; + if (unit->features[1] & NG_HCI_LMP_HV2_PKT) + req->cp.pkt_type |= NG_HCI_PKT_HV2; + if (unit->features[1] & NG_HCI_LMP_HV3_PKT) + req->cp.pkt_type |= NG_HCI_PKT_HV3; + + req->cp.pkt_type &= unit->packet_mask; + if ((req->cp.pkt_type & (NG_HCI_PKT_HV1| + NG_HCI_PKT_HV2| + NG_HCI_PKT_HV3)) == 0) + req->cp.pkt_type = NG_HCI_PKT_HV1; + + req->cp.pkt_type = htole16(req->cp.pkt_type); + + /* + * Adust connection state + */ + + sco_con->flags |= NG_HCI_CON_NOTIFY_SCO; + + sco_con->state = NG_HCI_CON_W4_CONN_COMPLETE; + ng_hci_con_timeout(sco_con); + + /* + * Queue and send HCI command + */ + + NG_BT_MBUFQ_ENQUEUE(&unit->cmdq, m); + if (!(unit->state & NG_HCI_UNIT_COMMAND_PENDING)) + error = ng_hci_send_command(unit); +out: + NG_FREE_ITEM(item); + + return (error); +} /* ng_hci_lp_sco_con_req */ + +/* + * Process LP_DisconnectReq event from the upper layer protocol + */ + +int +ng_hci_lp_discon_req(ng_hci_unit_p unit, item_p item, hook_p hook) +{ + struct discon_req { + ng_hci_cmd_pkt_t hdr; + ng_hci_discon_cp cp; + } __attribute__ ((packed)) *req = NULL; + ng_hci_lp_discon_req_ep *ep = NULL; + ng_hci_unit_con_p con = NULL; + struct mbuf *m = NULL; + int error = 0; + + /* Check if unit is ready */ + if ((unit->state & NG_HCI_UNIT_READY) != NG_HCI_UNIT_READY) { + NG_HCI_WARN( +"%s: %s - unit is not ready, state=%#x\n", + __func__, NG_NODE_NAME(unit->node), unit->state); + + error = ENXIO; + goto out; + } + + if (NGI_MSG(item)->header.arglen != sizeof(*ep)) { + NG_HCI_ALERT( +"%s: %s - invalid LP_DisconnectReq message size=%d\n", + __func__, NG_NODE_NAME(unit->node), + NGI_MSG(item)->header.arglen); + + error = EMSGSIZE; + goto out; + } + + ep = (ng_hci_lp_discon_req_ep *)(NGI_MSG(item)->data); + + con = ng_hci_con_by_handle(unit, ep->con_handle); + if (con == NULL) { + NG_HCI_ERR( +"%s: %s - invalid connection handle=%d\n", + __func__, NG_NODE_NAME(unit->node), ep->con_handle); + + error = ENOENT; + goto out; + } + + if (con->state != NG_HCI_CON_OPEN) { + NG_HCI_ERR( +"%s: %s - invalid connection state=%d, handle=%d\n", + __func__, NG_NODE_NAME(unit->node), con->state, + ep->con_handle); + + error = EINVAL; + goto out; + } + + /* + * Create HCI command + */ + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + error = ENOBUFS; + goto out; + } + + m->m_pkthdr.len = m->m_len = sizeof(*req); + req = mtod(m, struct discon_req *); + req->hdr.type = NG_HCI_CMD_PKT; + req->hdr.length = sizeof(req->cp); + req->hdr.opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_DISCON)); + + req->cp.con_handle = htole16(ep->con_handle); + req->cp.reason = ep->reason; + + /* + * Queue and send HCI command + */ + + NG_BT_MBUFQ_ENQUEUE(&unit->cmdq, m); + if (!(unit->state & NG_HCI_UNIT_COMMAND_PENDING)) + error = ng_hci_send_command(unit); +out: + NG_FREE_ITEM(item); + + return (error); +} /* ng_hci_lp_discon_req */ + +/* + * Send LP_ConnectCfm event to the upper layer protocol + */ + +int +ng_hci_lp_con_cfm(ng_hci_unit_con_p con, int status) +{ + ng_hci_unit_p unit = con->unit; + struct ng_mesg *msg = NULL; + ng_hci_lp_con_cfm_ep *ep = NULL; + int error; + + /* + * Check who wants to be notified. For ACL links both ACL and SCO + * upstream hooks will be notified (if required). For SCO links + * only SCO upstream hook will receive notification + */ + + if (con->link_type == NG_HCI_LINK_ACL && + con->flags & NG_HCI_CON_NOTIFY_ACL) { + if (unit->acl != NULL && NG_HOOK_IS_VALID(unit->acl)) { + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_CON_CFM, + sizeof(*ep), M_NOWAIT); + if (msg != NULL) { + ep = (ng_hci_lp_con_cfm_ep *) msg->data; + ep->status = status; + ep->link_type = con->link_type; + ep->con_handle = con->con_handle; + bcopy(&con->bdaddr, &ep->bdaddr, + sizeof(ep->bdaddr)); + + NG_SEND_MSG_HOOK(error, unit->node, msg, + unit->acl, 0); + } + } else + NG_HCI_INFO( +"%s: %s - ACL hook not valid, hook=%p\n", + __func__, NG_NODE_NAME(unit->node), unit->acl); + + con->flags &= ~NG_HCI_CON_NOTIFY_ACL; + } + + if (con->flags & NG_HCI_CON_NOTIFY_SCO) { + if (unit->sco != NULL && NG_HOOK_IS_VALID(unit->sco)) { + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_CON_CFM, + sizeof(*ep), M_NOWAIT); + if (msg != NULL) { + ep = (ng_hci_lp_con_cfm_ep *) msg->data; + ep->status = status; + ep->link_type = con->link_type; + ep->con_handle = con->con_handle; + bcopy(&con->bdaddr, &ep->bdaddr, + sizeof(ep->bdaddr)); + + NG_SEND_MSG_HOOK(error, unit->node, msg, + unit->sco, 0); + } + } else + NG_HCI_INFO( +"%s: %s - SCO hook not valid, hook=%p\n", + __func__, NG_NODE_NAME(unit->node), unit->acl); + + con->flags &= ~NG_HCI_CON_NOTIFY_SCO; + } + + return (0); +} /* ng_hci_lp_con_cfm */ + +/* + * Send LP_ConnectInd event to the upper layer protocol + */ + +int +ng_hci_lp_con_ind(ng_hci_unit_con_p con, u_int8_t *uclass) +{ + ng_hci_unit_p unit = con->unit; + struct ng_mesg *msg = NULL; + ng_hci_lp_con_ind_ep *ep = NULL; + hook_p hook = NULL; + int error = 0; + + /* + * Connection_Request event is generated for specific link type. + * Use link_type to select upstream hook. + */ + + if (con->link_type == NG_HCI_LINK_ACL) + hook = unit->acl; + else + hook = unit->sco; + + if (hook != NULL && NG_HOOK_IS_VALID(hook)) { + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_CON_IND, + sizeof(*ep), M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + ep = (ng_hci_lp_con_ind_ep *)(msg->data); + ep->link_type = con->link_type; + bcopy(uclass, ep->uclass, sizeof(ep->uclass)); + bcopy(&con->bdaddr, &ep->bdaddr, sizeof(ep->bdaddr)); + + NG_SEND_MSG_HOOK(error, unit->node, msg, hook, 0); + } else { + NG_HCI_WARN( +"%s: %s - Upstream hook is not connected or not valid, hook=%p\n", + __func__, NG_NODE_NAME(unit->node), hook); + + error = ENOTCONN; + } + + return (error); +} /* ng_hci_lp_con_ind */ + +/* + * Process LP_ConnectRsp event from the upper layer protocol + */ + +int +ng_hci_lp_con_rsp(ng_hci_unit_p unit, item_p item, hook_p hook) +{ + struct con_rsp_req { + ng_hci_cmd_pkt_t hdr; + union { + ng_hci_accept_con_cp acc; + ng_hci_reject_con_cp rej; + } __attribute__ ((packed)) cp; + } __attribute__ ((packed)) *req = NULL; + ng_hci_lp_con_rsp_ep *ep = NULL; + ng_hci_unit_con_p con = NULL; + struct mbuf *m = NULL; + int error = 0; + + /* Check if unit is ready */ + if ((unit->state & NG_HCI_UNIT_READY) != NG_HCI_UNIT_READY) { + NG_HCI_WARN( +"%s: %s - unit is not ready, state=%#x\n", + __func__, NG_NODE_NAME(unit->node), unit->state); + + error = ENXIO; + goto out; + } + + if (NGI_MSG(item)->header.arglen != sizeof(*ep)) { + NG_HCI_ALERT( +"%s: %s - invalid LP_ConnectRsp message size=%d\n", + __func__, NG_NODE_NAME(unit->node), + NGI_MSG(item)->header.arglen); + + error = EMSGSIZE; + goto out; + } + + ep = (ng_hci_lp_con_rsp_ep *)(NGI_MSG(item)->data); + + /* + * Here we have to deal with race. Upper layers might send conflicting + * requests. One might send Accept and other Reject. We will not try + * to solve all the problems, so first request will always win. + * + * Try to find connection that matches the following: + * + * 1) con->link_type == ep->link_type + * + * 2) con->state == NG_HCI_CON_W4_LP_CON_RSP || + * con->state == NG_HCI_CON_W4_CONN_COMPLETE + * + * 3) con->bdaddr == ep->bdaddr + * + * Two cases: + * + * 1) We do not have connection descriptor. Could be bogus request or + * we have rejected connection already. + * + * 2) We do have connection descriptor. Then we need to check state: + * + * 2.1) NG_HCI_CON_W4_LP_CON_RSP means upper layer has requested + * connection and it is a first response from the upper layer. + * if "status == 0" (Accept) then we will send Accept_Connection + * command and change connection state to W4_CONN_COMPLETE, else + * send reject and delete connection. + * + * 2.2) NG_HCI_CON_W4_CONN_COMPLETE means that we already accepted + * connection. If "status == 0" we just need to link request + * and wait, else ignore Reject request. + */ + + LIST_FOREACH(con, &unit->con_list, next) + if (con->link_type == ep->link_type && + (con->state == NG_HCI_CON_W4_LP_CON_RSP || + con->state == NG_HCI_CON_W4_CONN_COMPLETE) && + bcmp(&con->bdaddr, &ep->bdaddr, sizeof(bdaddr_t)) == 0) + break; + + if (con == NULL) { + /* Reject for non-existing connection is fine */ + error = (ep->status == 0)? ENOENT : 0; + goto out; + } + + /* + * Remove connection timeout and check connection state. + * Note: if ng_hci_con_untimeout() fails (returns non-zero value) then + * timeout already happened and event went into node's queue. + */ + + if ((error = ng_hci_con_untimeout(con)) != 0) + goto out; + + switch (con->state) { + case NG_HCI_CON_W4_LP_CON_RSP: + + /* + * Create HCI command + */ + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + error = ENOBUFS; + goto out; + } + + req = mtod(m, struct con_rsp_req *); + req->hdr.type = NG_HCI_CMD_PKT; + + if (ep->status == 0) { + req->hdr.length = sizeof(req->cp.acc); + req->hdr.opcode = htole16(NG_HCI_OPCODE( + NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_ACCEPT_CON)); + + bcopy(&ep->bdaddr, &req->cp.acc.bdaddr, + sizeof(req->cp.acc.bdaddr)); + + /* + * We are accepting connection, so if we support role + * switch and role switch was enabled then set role to + * NG_HCI_ROLE_MASTER and let LM peform role switch. + * Otherwise we remain slave. In this case LM WILL NOT + * perform role switch. + */ + + if ((unit->features[0] & NG_HCI_LMP_SWITCH) && + unit->role_switch) + req->cp.acc.role = NG_HCI_ROLE_MASTER; + else + req->cp.acc.role = NG_HCI_ROLE_SLAVE; + + /* + * Adjust connection state + */ + + if (hook == unit->acl) + con->flags |= NG_HCI_CON_NOTIFY_ACL; + else + con->flags |= NG_HCI_CON_NOTIFY_SCO; + + con->state = NG_HCI_CON_W4_CONN_COMPLETE; + ng_hci_con_timeout(con); + } else { + req->hdr.length = sizeof(req->cp.rej); + req->hdr.opcode = htole16(NG_HCI_OPCODE( + NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_REJECT_CON)); + + bcopy(&ep->bdaddr, &req->cp.rej.bdaddr, + sizeof(req->cp.rej.bdaddr)); + + req->cp.rej.reason = ep->status; + + /* + * Free connection descritor + * Item will be deleted just before return. + */ + + ng_hci_free_con(con); + } + + m->m_pkthdr.len = m->m_len = sizeof(req->hdr) + req->hdr.length; + + /* Queue and send HCI command */ + NG_BT_MBUFQ_ENQUEUE(&unit->cmdq, m); + if (!(unit->state & NG_HCI_UNIT_COMMAND_PENDING)) + error = ng_hci_send_command(unit); + break; + + case NG_HCI_CON_W4_CONN_COMPLETE: + if (ep->status == 0) { + if (hook == unit->acl) + con->flags |= NG_HCI_CON_NOTIFY_ACL; + else + con->flags |= NG_HCI_CON_NOTIFY_SCO; + } else + error = EPERM; + break; + + default: + panic( +"%s: %s - Invalid connection state=%d\n", + __func__, NG_NODE_NAME(unit->node), con->state); + break; + } +out: + NG_FREE_ITEM(item); + + return (error); +} /* ng_hci_lp_con_rsp */ + +/* + * Send LP_DisconnectInd to the upper layer protocol + */ + +int +ng_hci_lp_discon_ind(ng_hci_unit_con_p con, int reason) +{ + ng_hci_unit_p unit = con->unit; + struct ng_mesg *msg = NULL; + ng_hci_lp_discon_ind_ep *ep = NULL; + int error = 0; + + /* + * Disconnect_Complete event is generated for specific connection + * handle. For ACL connection handles both ACL and SCO upstream + * hooks will receive notification. For SCO connection handles + * only SCO upstream hook will receive notification. + */ + + if (con->link_type == NG_HCI_LINK_ACL) { + if (unit->acl != NULL && NG_HOOK_IS_VALID(unit->acl)) { + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, + NGM_HCI_LP_DISCON_IND, sizeof(*ep), M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + ep = (ng_hci_lp_discon_ind_ep *) msg->data; + ep->reason = reason; + ep->link_type = con->link_type; + ep->con_handle = con->con_handle; + + NG_SEND_MSG_HOOK(error,unit->node,msg,unit->acl,0); + } else + NG_HCI_INFO( +"%s: %s - ACL hook is not connected or not valid, hook=%p\n", + __func__, NG_NODE_NAME(unit->node), unit->acl); + } + + if (unit->sco != NULL && NG_HOOK_IS_VALID(unit->sco)) { + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_DISCON_IND, + sizeof(*ep), M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + ep = (ng_hci_lp_discon_ind_ep *) msg->data; + ep->reason = reason; + ep->link_type = con->link_type; + ep->con_handle = con->con_handle; + + NG_SEND_MSG_HOOK(error, unit->node, msg, unit->sco, 0); + } else + NG_HCI_INFO( +"%s: %s - SCO hook is not connected or not valid, hook=%p\n", + __func__, NG_NODE_NAME(unit->node), unit->sco); + + return (0); +} /* ng_hci_lp_discon_ind */ + +/* + * Process LP_QoSReq action from the upper layer protocol + */ + +int +ng_hci_lp_qos_req(ng_hci_unit_p unit, item_p item, hook_p hook) +{ + struct qos_setup_req { + ng_hci_cmd_pkt_t hdr; + ng_hci_qos_setup_cp cp; + } __attribute__ ((packed)) *req = NULL; + ng_hci_lp_qos_req_ep *ep = NULL; + ng_hci_unit_con_p con = NULL; + struct mbuf *m = NULL; + int error = 0; + + /* Check if unit is ready */ + if ((unit->state & NG_HCI_UNIT_READY) != NG_HCI_UNIT_READY) { + NG_HCI_WARN( +"%s: %s - unit is not ready, state=%#x\n", + __func__, NG_NODE_NAME(unit->node), unit->state); + + error = ENXIO; + goto out; + } + + if (NGI_MSG(item)->header.arglen != sizeof(*ep)) { + NG_HCI_ALERT( +"%s: %s - invalid LP_QoSSetupReq message size=%d\n", + __func__, NG_NODE_NAME(unit->node), + NGI_MSG(item)->header.arglen); + + error = EMSGSIZE; + goto out; + } + + ep = (ng_hci_lp_qos_req_ep *)(NGI_MSG(item)->data); + + con = ng_hci_con_by_handle(unit, ep->con_handle); + if (con == NULL) { + NG_HCI_ERR( +"%s: %s - invalid connection handle=%d\n", + __func__, NG_NODE_NAME(unit->node), ep->con_handle); + + error = EINVAL; + goto out; + } + + if (con->link_type != NG_HCI_LINK_ACL) { + NG_HCI_ERR("%s: %s - invalid link type=%d\n", + __func__, NG_NODE_NAME(unit->node), con->link_type); + + error = EINVAL; + goto out; + } + + if (con->state != NG_HCI_CON_OPEN) { + NG_HCI_ERR( +"%s: %s - invalid connection state=%d, handle=%d\n", + __func__, NG_NODE_NAME(unit->node), con->state, + con->con_handle); + + error = EINVAL; + goto out; + } + + /* + * Create HCI command + */ + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + error = ENOBUFS; + goto out; + } + + m->m_pkthdr.len = m->m_len = sizeof(*req); + req = mtod(m, struct qos_setup_req *); + req->hdr.type = NG_HCI_CMD_PKT; + req->hdr.length = sizeof(req->cp); + req->hdr.opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_POLICY, + NG_HCI_OCF_QOS_SETUP)); + + req->cp.con_handle = htole16(ep->con_handle); + req->cp.flags = ep->flags; + req->cp.service_type = ep->service_type; + req->cp.token_rate = htole32(ep->token_rate); + req->cp.peak_bandwidth = htole32(ep->peak_bandwidth); + req->cp.latency = htole32(ep->latency); + req->cp.delay_variation = htole32(ep->delay_variation); + + /* + * Adjust connection state + */ + + if (hook == unit->acl) + con->flags |= NG_HCI_CON_NOTIFY_ACL; + else + con->flags |= NG_HCI_CON_NOTIFY_SCO; + + /* + * Queue and send HCI command + */ + + NG_BT_MBUFQ_ENQUEUE(&unit->cmdq, m); + if (!(unit->state & NG_HCI_UNIT_COMMAND_PENDING)) + error = ng_hci_send_command(unit); +out: + NG_FREE_ITEM(item); + + return (error); +} /* ng_hci_lp_qos_req */ + +/* + * Send LP_QoSCfm event to the upper layer protocol + */ + +int +ng_hci_lp_qos_cfm(ng_hci_unit_con_p con, int status) +{ + ng_hci_unit_p unit = con->unit; + struct ng_mesg *msg = NULL; + ng_hci_lp_qos_cfm_ep *ep = NULL; + int error; + + if (con->flags & NG_HCI_CON_NOTIFY_ACL) { + if (unit->acl != NULL && NG_HOOK_IS_VALID(unit->acl)) { + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_QOS_CFM, + sizeof(*ep), M_NOWAIT); + if (msg != NULL) { + ep = (ng_hci_lp_qos_cfm_ep *) msg->data; + ep->status = status; + ep->con_handle = con->con_handle; + + NG_SEND_MSG_HOOK(error, unit->node, msg, + unit->acl, 0); + } + } else + NG_HCI_INFO( +"%s: %s - ACL hook not valid, hook=%p\n", + __func__, NG_NODE_NAME(unit->node), unit->acl); + + con->flags &= ~NG_HCI_CON_NOTIFY_ACL; + } + + if (con->flags & NG_HCI_CON_NOTIFY_SCO) { + if (unit->sco != NULL && NG_HOOK_IS_VALID(unit->sco)) { + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_QOS_CFM, + sizeof(*ep), M_NOWAIT); + if (msg != NULL) { + ep = (ng_hci_lp_qos_cfm_ep *) msg->data; + ep->status = status; + ep->con_handle = con->con_handle; + + NG_SEND_MSG_HOOK(error, unit->node, msg, + unit->sco, 0); + } + } else + NG_HCI_INFO( +"%s: %s - SCO hook not valid, hook=%p\n", + __func__, NG_NODE_NAME(unit->node), unit->sco); + + con->flags &= ~NG_HCI_CON_NOTIFY_SCO; + } + + return (0); +} /* ng_hci_lp_qos_cfm */ + +/* + * Send LP_QoSViolationInd event to the upper layer protocol + */ + +int +ng_hci_lp_qos_ind(ng_hci_unit_con_p con) +{ + ng_hci_unit_p unit = con->unit; + struct ng_mesg *msg = NULL; + ng_hci_lp_qos_ind_ep *ep = NULL; + int error; + + /* + * QoS Violation can only be generated for ACL connection handles. + * Both ACL and SCO upstream hooks will receive notification. + */ + + if (unit->acl != NULL && NG_HOOK_IS_VALID(unit->acl)) { + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_QOS_IND, + sizeof(*ep), M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + ep = (ng_hci_lp_qos_ind_ep *) msg->data; + ep->con_handle = con->con_handle; + + NG_SEND_MSG_HOOK(error, unit->node, msg, unit->acl, 0); + } else + NG_HCI_INFO( +"%s: %s - ACL hook is not connected or not valid, hook=%p\n", + __func__, NG_NODE_NAME(unit->node), unit->acl); + + if (unit->sco != NULL && NG_HOOK_IS_VALID(unit->sco)) { + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_QOS_IND, + sizeof(*ep), M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + ep = (ng_hci_lp_qos_ind_ep *) msg->data; + ep->con_handle = con->con_handle; + + NG_SEND_MSG_HOOK(error, unit->node, msg, unit->sco, 0); + } else + NG_HCI_INFO( +"%s: %s - SCO hook is not connected or not valid, hook=%p\n", + __func__, NG_NODE_NAME(unit->node), unit->sco); + + return (0); +} /* ng_hci_lp_qos_ind */ + +/* + * Process connection timeout + */ + +void +ng_hci_process_con_timeout(node_p node, hook_p hook, void *arg1, int con_handle) +{ + ng_hci_unit_p unit = NULL; + ng_hci_unit_con_p con = NULL; + + if (NG_NODE_NOT_VALID(node)) { + printf("%s: Netgraph node is not valid\n", __func__); + return; + } + + unit = (ng_hci_unit_p) NG_NODE_PRIVATE(node); + con = ng_hci_con_by_handle(unit, con_handle); + + if (con == NULL) { + NG_HCI_ALERT( +"%s: %s - could not find connection, handle=%d\n", + __func__, NG_NODE_NAME(node), con_handle); + return; + } + + if (!(con->flags & NG_HCI_CON_TIMEOUT_PENDING)) { + NG_HCI_ALERT( +"%s: %s - no pending connection timeout, handle=%d, state=%d, flags=%#x\n", + __func__, NG_NODE_NAME(node), con_handle, con->state, + con->flags); + return; + } + + con->flags &= ~NG_HCI_CON_TIMEOUT_PENDING; + + /* + * We expect to receive connection timeout in one of the following + * states: + * + * 1) NG_HCI_CON_W4_LP_CON_RSP means that upper layer has not responded + * to our LP_CON_IND. Do nothing and destroy connection. Remote peer + * most likely already gave up on us. + * + * 2) NG_HCI_CON_W4_CONN_COMPLETE means upper layer requested connection + * (or we in the process of accepting it) and baseband has timedout + * on us. Inform upper layers and send LP_CON_CFM. + */ + + switch (con->state) { + case NG_HCI_CON_W4_LP_CON_RSP: + break; + + case NG_HCI_CON_W4_CONN_COMPLETE: + ng_hci_lp_con_cfm(con, 0xee); + break; + + default: + panic( +"%s: %s - Invalid connection state=%d\n", + __func__, NG_NODE_NAME(node), con->state); + break; + } + + ng_hci_free_con(con); +} /* ng_hci_process_con_timeout */ + diff --git a/sys/netgraph7/bluetooth/hci/ng_hci_ulpi.h b/sys/netgraph7/bluetooth/hci/ng_hci_ulpi.h new file mode 100644 index 0000000000..e3bfb4481b --- /dev/null +++ b/sys/netgraph7/bluetooth/hci/ng_hci_ulpi.h @@ -0,0 +1,54 @@ +/* + * ng_hci_ulpi.h + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_hci_ulpi.h,v 1.2 2003/04/26 22:35:21 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/hci/ng_hci_ulpi.h,v 1.3 2005/01/07 01:45:43 imp Exp $ + */ + +#ifndef _NETGRAPH_HCI_ULPI_H_ +#define _NETGRAPH_HCI_ULPI_H_ + +/* + * LP_xxx event handlers + */ + +int ng_hci_lp_con_req (ng_hci_unit_p, item_p, hook_p); +int ng_hci_lp_discon_req (ng_hci_unit_p, item_p, hook_p); +int ng_hci_lp_con_cfm (ng_hci_unit_con_p, int); +int ng_hci_lp_con_ind (ng_hci_unit_con_p, u_int8_t *); +int ng_hci_lp_con_rsp (ng_hci_unit_p, item_p, hook_p); +int ng_hci_lp_discon_ind (ng_hci_unit_con_p, int); +int ng_hci_lp_qos_req (ng_hci_unit_p, item_p, hook_p); +int ng_hci_lp_qos_cfm (ng_hci_unit_con_p, int); +int ng_hci_lp_qos_ind (ng_hci_unit_con_p); + +void ng_hci_process_con_timeout (node_p, hook_p, void *, int); + +#endif /* ndef _NETGRAPH_HCI_ULPI_H_ */ + diff --git a/sys/netgraph7/bluetooth/hci/ng_hci_var.h b/sys/netgraph7/bluetooth/hci/ng_hci_var.h new file mode 100644 index 0000000000..8df13a8cd8 --- /dev/null +++ b/sys/netgraph7/bluetooth/hci/ng_hci_var.h @@ -0,0 +1,218 @@ +/* + * ng_hci_var.h + */ + +/*- + * Copyright (c) 2001 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_hci_var.h,v 1.3 2003/04/26 22:35:21 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/hci/ng_hci_var.h,v 1.6 2005/01/07 01:45:43 imp Exp $ + */ + +#ifndef _NETGRAPH_HCI_VAR_H_ +#define _NETGRAPH_HCI_VAR_H_ + +/* MALLOC decalation */ +#ifdef NG_SEPARATE_MALLOC +MALLOC_DECLARE(M_NETGRAPH_HCI); +#else +#define M_NETGRAPH_HCI M_NETGRAPH +#endif /* NG_SEPARATE_MALLOC */ + +/* Debug */ +#define NG_HCI_ALERT if (unit->debug >= NG_HCI_ALERT_LEVEL) printf +#define NG_HCI_ERR if (unit->debug >= NG_HCI_ERR_LEVEL) printf +#define NG_HCI_WARN if (unit->debug >= NG_HCI_WARN_LEVEL) printf +#define NG_HCI_INFO if (unit->debug >= NG_HCI_INFO_LEVEL) printf + +/* Wrapper around m_pullup */ +#define NG_HCI_M_PULLUP(m, s) \ + do { \ + if ((m)->m_len < (s)) \ + (m) = m_pullup((m), (s)); \ + if ((m) == NULL) \ + NG_HCI_ALERT("%s: %s - m_pullup(%zd) failed\n", \ + __func__, NG_NODE_NAME(unit->node), (s)); \ + } while (0) + +/* + * Unit hardware buffer descriptor + */ + +typedef struct ng_hci_unit_buff { + u_int8_t cmd_free; /* space available (cmds) */ + + u_int8_t sco_size; /* max. size of one packet */ + u_int16_t sco_pkts; /* size of buffer (packets) */ + u_int16_t sco_free; /* space available (packets)*/ + + u_int16_t acl_size; /* max. size of one packet */ + u_int16_t acl_pkts; /* size of buffer (packets) */ + u_int16_t acl_free; /* space available (packets)*/ +} ng_hci_unit_buff_t; + +/* + * These macro's must be used everywhere in the code. So if extra locking + * is required later, it can be added without much troubles. + */ + +#define NG_HCI_BUFF_CMD_SET(b, v) (b).cmd_free = (v) +#define NG_HCI_BUFF_CMD_GET(b, v) (v) = (b).cmd_free +#define NG_HCI_BUFF_CMD_USE(b, v) (b).cmd_free -= (v) + +#define NG_HCI_BUFF_ACL_USE(b, v) (b).acl_free -= (v) +#define NG_HCI_BUFF_ACL_FREE(b, v) \ + do { \ + (b).acl_free += (v); \ + if ((b).acl_free > (b).acl_pkts) \ + (b).acl_free = (b).acl_pkts; \ + } while (0) +#define NG_HCI_BUFF_ACL_AVAIL(b, v) (v) = (b).acl_free +#define NG_HCI_BUFF_ACL_TOTAL(b, v) (v) = (b).acl_pkts +#define NG_HCI_BUFF_ACL_SIZE(b, v) (v) = (b).acl_size +#define NG_HCI_BUFF_ACL_SET(b, n, s, f) \ + do { \ + (b).acl_free = (f); \ + (b).acl_size = (s); \ + (b).acl_pkts = (n); \ + } while (0) + +#define NG_HCI_BUFF_SCO_USE(b, v) (b).sco_free -= (v) +#define NG_HCI_BUFF_SCO_FREE(b, v) \ + do { \ + (b).sco_free += (v); \ + if ((b).sco_free > (b).sco_pkts) \ + (b).sco_free = (b).sco_pkts; \ + } while (0) +#define NG_HCI_BUFF_SCO_AVAIL(b, v) (v) = (b).sco_free +#define NG_HCI_BUFF_SCO_TOTAL(b, v) (v) = (b).sco_pkts +#define NG_HCI_BUFF_SCO_SIZE(b, v) (v) = (b).sco_size +#define NG_HCI_BUFF_SCO_SET(b, n, s, f) \ + do { \ + (b).sco_free = (f); \ + (b).sco_size = (s); \ + (b).sco_pkts = (n); \ + } while (0) + +/* + * Unit (Node private) + */ + +struct ng_hci_unit_con; +struct ng_hci_neighbor; + +typedef struct ng_hci_unit { + node_p node; /* node ptr */ + + ng_hci_node_debug_ep debug; /* debug level */ + ng_hci_node_state_ep state; /* unit state */ + + bdaddr_t bdaddr; /* unit address */ + u_int8_t features[NG_HCI_FEATURES_SIZE]; + /* LMP features */ + + ng_hci_node_link_policy_mask_ep link_policy_mask; /* link policy mask */ + ng_hci_node_packet_mask_ep packet_mask; /* packet mask */ + ng_hci_node_role_switch_ep role_switch; /* role switch */ + + ng_hci_node_stat_ep stat; /* statistic */ +#define NG_HCI_STAT_CMD_SENT(s) (s).cmd_sent ++ +#define NG_HCI_STAT_EVNT_RECV(s) (s).evnt_recv ++ +#define NG_HCI_STAT_ACL_SENT(s, n) (s).acl_sent += (n) +#define NG_HCI_STAT_ACL_RECV(s) (s).acl_recv ++ +#define NG_HCI_STAT_SCO_SENT(s, n) (s).sco_sent += (n) +#define NG_HCI_STAT_SCO_RECV(s) (s).sco_recv ++ +#define NG_HCI_STAT_BYTES_SENT(s, b) (s).bytes_sent += (b) +#define NG_HCI_STAT_BYTES_RECV(s, b) (s).bytes_recv += (b) +#define NG_HCI_STAT_RESET(s) bzero(&(s), sizeof((s))) + + ng_hci_unit_buff_t buffer; /* buffer info */ + + struct callout cmd_timo; /* command timeout */ + ng_bt_mbufq_t cmdq; /* command queue */ +#define NG_HCI_CMD_QUEUE_LEN 12 /* max. size of cmd q */ + + hook_p drv; /* driver hook */ + hook_p acl; /* upstream hook */ + hook_p sco; /* upstream hook */ + hook_p raw; /* upstream hook */ + + LIST_HEAD(, ng_hci_unit_con) con_list; /* connections */ + LIST_HEAD(, ng_hci_neighbor) neighbors; /* unit neighbors */ +} ng_hci_unit_t; +typedef ng_hci_unit_t * ng_hci_unit_p; + +/* + * Unit connection descriptor + */ + +typedef struct ng_hci_unit_con { + ng_hci_unit_p unit; /* pointer back */ + + u_int16_t state; /* con. state */ + u_int16_t flags; /* con. flags */ +#define NG_HCI_CON_TIMEOUT_PENDING (1 << 0) +#define NG_HCI_CON_NOTIFY_ACL (1 << 1) +#define NG_HCI_CON_NOTIFY_SCO (1 << 2) + + bdaddr_t bdaddr; /* remote address */ + u_int16_t con_handle; /* con. handle */ + + u_int8_t link_type; /* ACL or SCO */ + u_int8_t encryption_mode; /* none, p2p, ... */ + u_int8_t mode; /* ACTIVE, HOLD ... */ + u_int8_t role; /* MASTER/SLAVE */ + + struct callout con_timo; /* con. timeout */ + + int pending; /* # of data pkts */ + ng_bt_itemq_t conq; /* con. queue */ + + LIST_ENTRY(ng_hci_unit_con) next; /* next */ +} ng_hci_unit_con_t; +typedef ng_hci_unit_con_t * ng_hci_unit_con_p; + +/* + * Unit's neighbor descriptor. + * Neighbor is a remote unit that responded to our inquiry. + */ + +typedef struct ng_hci_neighbor { + struct timeval updated; /* entry was updated */ + + bdaddr_t bdaddr; /* address */ + u_int8_t features[NG_HCI_FEATURES_SIZE]; + /* LMP features */ + + u_int8_t page_scan_rep_mode; /* PS rep. mode */ + u_int8_t page_scan_mode; /* page scan mode */ + u_int16_t clock_offset; /* clock offset */ + + LIST_ENTRY(ng_hci_neighbor) next; +} ng_hci_neighbor_t; +typedef ng_hci_neighbor_t * ng_hci_neighbor_p; + +#endif /* ndef _NETGRAPH_HCI_VAR_H_ */ + diff --git a/sys/netgraph7/bluetooth/include/ng_bluetooth.h b/sys/netgraph7/bluetooth/include/ng_bluetooth.h new file mode 100644 index 0000000000..c59812b9bf --- /dev/null +++ b/sys/netgraph7/bluetooth/include/ng_bluetooth.h @@ -0,0 +1,226 @@ +/* + * bluetooth.h + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_bluetooth.h,v 1.4 2003/04/26 22:32:34 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/include/ng_bluetooth.h,v 1.5 2008/04/15 21:15:32 mav Exp $ + */ + +#ifndef _NETGRAPH_BLUETOOTH_H_ +#define _NETGRAPH_BLUETOOTH_H_ + +#include + +/* + * Version of the stack + */ + +#define NG_BLUETOOTH_VERSION 1 + +/* + * Declare the base of the Bluetooth sysctl hierarchy, + * but only if this file cares about sysctl's + */ + +#ifdef SYSCTL_DECL +SYSCTL_DECL(_net_bluetooth); +SYSCTL_DECL(_net_bluetooth_hci); +SYSCTL_DECL(_net_bluetooth_l2cap); +SYSCTL_DECL(_net_bluetooth_rfcomm); +#endif /* SYSCTL_DECL */ + +/* + * Mbuf qeueue and useful mbufq macros. We do not use ifqueue because we + * do not need mutex and other locking stuff + */ + +struct mbuf; + +struct ng_bt_mbufq { + struct mbuf *head; /* first item in the queue */ + struct mbuf *tail; /* last item in the queue */ + u_int32_t len; /* number of items in the queue */ + u_int32_t maxlen; /* maximal number of items in the queue */ + u_int32_t drops; /* number if dropped items */ +}; +typedef struct ng_bt_mbufq ng_bt_mbufq_t; +typedef struct ng_bt_mbufq * ng_bt_mbufq_p; + +#define NG_BT_MBUFQ_INIT(q, _maxlen) \ + do { \ + (q)->head = NULL; \ + (q)->tail = NULL; \ + (q)->len = 0; \ + (q)->maxlen = (_maxlen); \ + (q)->drops = 0; \ + } while (0) + +#define NG_BT_MBUFQ_DESTROY(q) \ + do { \ + NG_BT_MBUFQ_DRAIN((q)); \ + } while (0) + +#define NG_BT_MBUFQ_FIRST(q) (q)->head + +#define NG_BT_MBUFQ_LEN(q) (q)->len + +#define NG_BT_MBUFQ_FULL(q) ((q)->len >= (q)->maxlen) + +#define NG_BT_MBUFQ_DROP(q) (q)->drops ++ + +#define NG_BT_MBUFQ_ENQUEUE(q, i) \ + do { \ + (i)->m_nextpkt = NULL; \ + \ + if ((q)->tail == NULL) \ + (q)->head = (i); \ + else \ + (q)->tail->m_nextpkt = (i); \ + \ + (q)->tail = (i); \ + (q)->len ++; \ + } while (0) + +#define NG_BT_MBUFQ_DEQUEUE(q, i) \ + do { \ + (i) = (q)->head; \ + if ((i) != NULL) { \ + (q)->head = (q)->head->m_nextpkt; \ + if ((q)->head == NULL) \ + (q)->tail = NULL; \ + \ + (q)->len --; \ + (i)->m_nextpkt = NULL; \ + } \ + } while (0) + +#define NG_BT_MBUFQ_PREPEND(q, i) \ + do { \ + (i)->m_nextpkt = (q)->head; \ + if ((q)->tail == NULL) \ + (q)->tail = (i); \ + \ + (q)->head = (i); \ + (q)->len ++; \ + } while (0) + +#define NG_BT_MBUFQ_DRAIN(q) \ + do { \ + struct mbuf *m = NULL; \ + \ + for (;;) { \ + NG_BT_MBUFQ_DEQUEUE((q), m); \ + if (m == NULL) \ + break; \ + \ + NG_FREE_M(m); \ + } \ + } while (0) + +/* + * Netgraph item queue and useful itemq macros + */ + +struct ng_item; + +struct ng_bt_itemq { + STAILQ_HEAD(, ng_item) queue; /* actually items queue */ + u_int32_t len; /* number of items in the queue */ + u_int32_t maxlen; /* maximal number of items in the queue */ + u_int32_t drops; /* number if dropped items */ +}; +typedef struct ng_bt_itemq ng_bt_itemq_t; +typedef struct ng_bt_itemq * ng_bt_itemq_p; + +#define NG_BT_ITEMQ_INIT(q, _maxlen) \ + do { \ + STAILQ_INIT(&(q)->queue); \ + (q)->len = 0; \ + (q)->maxlen = (_maxlen); \ + (q)->drops = 0; \ + } while (0) + +#define NG_BT_ITEMQ_DESTROY(q) \ + do { \ + NG_BT_ITEMQ_DRAIN((q)); \ + } while (0) + +#define NG_BT_ITEMQ_FIRST(q) STAILQ_FIRST(&(q)->queue) + +#define NG_BT_ITEMQ_LEN(q) NG_BT_MBUFQ_LEN((q)) + +#define NG_BT_ITEMQ_FULL(q) NG_BT_MBUFQ_FULL((q)) + +#define NG_BT_ITEMQ_DROP(q) NG_BT_MBUFQ_DROP((q)) + +#define NG_BT_ITEMQ_ENQUEUE(q, i) \ + do { \ + STAILQ_INSERT_TAIL(&(q)->queue, (i), el_next); \ + (q)->len ++; \ + } while (0) + +#define NG_BT_ITEMQ_DEQUEUE(q, i) \ + do { \ + (i) = STAILQ_FIRST(&(q)->queue); \ + if ((i) != NULL) { \ + STAILQ_REMOVE_HEAD(&(q)->queue, el_next); \ + (q)->len --; \ + } \ + } while (0) + +#define NG_BT_ITEMQ_PREPEND(q, i) \ + do { \ + STAILQ_INSERT_HEAD(&(q)->queue, (i), el_next); \ + (q)->len ++; \ + } while (0) + +#define NG_BT_ITEMQ_DRAIN(q) \ + do { \ + struct ng_item *i = NULL; \ + \ + for (;;) { \ + NG_BT_ITEMQ_DEQUEUE((q), i); \ + if (i == NULL) \ + break; \ + \ + NG_FREE_ITEM(i); \ + } \ + } while (0) + +/* + * Get Bluetooth stack sysctl globals + */ + +u_int32_t bluetooth_hci_command_timeout (void); +u_int32_t bluetooth_hci_connect_timeout (void); +u_int32_t bluetooth_hci_max_neighbor_age (void); +u_int32_t bluetooth_l2cap_rtx_timeout (void); +u_int32_t bluetooth_l2cap_ertx_timeout (void); + +#endif /* _NETGRAPH_BLUETOOTH_H_ */ + diff --git a/sys/netgraph7/bluetooth/include/ng_bt3c.h b/sys/netgraph7/bluetooth/include/ng_bt3c.h new file mode 100644 index 0000000000..0cf5d5d9b0 --- /dev/null +++ b/sys/netgraph7/bluetooth/include/ng_bt3c.h @@ -0,0 +1,111 @@ +/* + * ng_bt3c.h + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_bt3c.h,v 1.1 2002/11/24 19:47:05 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/include/ng_bt3c.h,v 1.4 2005/01/07 01:45:43 imp Exp $ + * + * XXX XXX XX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX + * + * Based on information obrained from: Jose Orlando Pereira + * and disassembled w2k driver. + * + * XXX XXX XX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX + * + */ + +#ifndef _NG_BT3C_H_ +#define _NG_BT3C_H_ + +/************************************************************************** + ************************************************************************** + ** Netgraph node hook name, type name and type cookie and commands + ************************************************************************** + **************************************************************************/ + +#define NG_BT3C_NODE_TYPE "btccc" /* XXX can't use bt3c in pccard.conf */ +#define NG_BT3C_HOOK "hook" + +#define NGM_BT3C_COOKIE 1014752016 + +/* Debug levels */ +#define NG_BT3C_ALERT_LEVEL 1 +#define NG_BT3C_ERR_LEVEL 2 +#define NG_BT3C_WARN_LEVEL 3 +#define NG_BT3C_INFO_LEVEL 4 + +/* Node states */ +#define NG_BT3C_W4_PKT_IND 1 /* wait for packet indicator */ +#define NG_BT3C_W4_PKT_HDR 2 /* wait for packet header */ +#define NG_BT3C_W4_PKT_DATA 3 /* wait for packet data */ + +/************************************************************************** + ************************************************************************** + ** BT3C node command/event parameters + ************************************************************************** + **************************************************************************/ + +#define NGM_BT3C_NODE_GET_STATE 1 /* get node state */ +typedef u_int16_t ng_bt3c_node_state_ep; + +#define NGM_BT3C_NODE_SET_DEBUG 2 /* set debug level */ +#define NGM_BT3C_NODE_GET_DEBUG 3 /* get debug level */ +typedef u_int16_t ng_bt3c_node_debug_ep; + +#define NGM_BT3C_NODE_GET_QLEN 4 /* get queue length */ +#define NGM_BT3C_NODE_SET_QLEN 5 /* set queue length */ +typedef struct { + int32_t queue; /* queue index */ +#define NGM_BT3C_NODE_IN_QUEUE 1 /* incoming queue */ +#define NGM_BT3C_NODE_OUT_QUEUE 2 /* outgoing queue */ + + int32_t qlen; /* queue length */ +} ng_bt3c_node_qlen_ep; + +#define NGM_BT3C_NODE_GET_STAT 6 /* get statistic */ +typedef struct { + u_int32_t pckts_recv; /* # of packets received */ + u_int32_t bytes_recv; /* # of bytes received */ + u_int32_t pckts_sent; /* # of packets sent */ + u_int32_t bytes_sent; /* # of bytes sent */ + u_int32_t oerrors; /* # of output errors */ + u_int32_t ierrors; /* # of input errors */ +} ng_bt3c_node_stat_ep; + +#define NGM_BT3C_NODE_RESET_STAT 7 /* reset statistic */ + +#define NGM_BT3C_NODE_DOWNLOAD_FIRMWARE 8 /* download firmware */ + +typedef struct { + u_int32_t block_address; + u_int16_t block_size; /* in words */ + u_int16_t block_alignment; /* in bytes */ +} ng_bt3c_firmware_block_ep; + +#endif /* ndef _NG_BT3C_H_ */ + diff --git a/sys/netgraph7/bluetooth/include/ng_btsocket.h b/sys/netgraph7/bluetooth/include/ng_btsocket.h new file mode 100644 index 0000000000..111d283460 --- /dev/null +++ b/sys/netgraph7/bluetooth/include/ng_btsocket.h @@ -0,0 +1,342 @@ +/* + * ng_btsocket.h + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_btsocket.h,v 1.8 2003/04/26 22:32:10 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/include/ng_btsocket.h,v 1.8 2006/05/17 00:13:06 emax Exp $ + */ + +#ifndef _NETGRAPH_BTSOCKET_H_ +#define _NETGRAPH_BTSOCKET_H_ + +/* + * Bluetooth protocols + */ + +#define BLUETOOTH_PROTO_HCI 134 /* HCI protocol number */ +#define BLUETOOTH_PROTO_L2CAP 135 /* L2CAP protocol number */ +#define BLUETOOTH_PROTO_RFCOMM 136 /* RFCOMM protocol number */ + +/* + * Bluetooth version of struct sockaddr for raw HCI sockets + */ + +struct sockaddr_hci { + u_char hci_len; /* total length */ + u_char hci_family; /* address family */ + char hci_node[32]; /* address (size == NG_NODESIZ ) */ +}; + +/* Raw HCI socket options */ +#define SOL_HCI_RAW 0x0802 /* socket options level */ + +#define SO_HCI_RAW_FILTER 1 /* get/set filter on socket */ +#define SO_HCI_RAW_DIRECTION 2 /* turn on/off direction info */ +#define SCM_HCI_RAW_DIRECTION SO_HCI_RAW_DIRECTION /* cmsg_type */ + +/* + * Raw HCI socket filter. + * + * For packet mask use (1 << (HCI packet indicator - 1)) + * For event mask use (1 << (Event - 1)) + */ + +struct ng_btsocket_hci_raw_filter { + bitstr_t bit_decl(packet_mask, 32); + bitstr_t bit_decl(event_mask, (NG_HCI_EVENT_MASK_SIZE * 8)); +}; + +/* + * Raw HCI sockets ioctl's + */ + +/* Get state */ +struct ng_btsocket_hci_raw_node_state { + ng_hci_node_state_ep state; +}; +#define SIOC_HCI_RAW_NODE_GET_STATE \ + _IOWR('b', NGM_HCI_NODE_GET_STATE, \ + struct ng_btsocket_hci_raw_node_state) + +/* Initialize */ +#define SIOC_HCI_RAW_NODE_INIT \ + _IO('b', NGM_HCI_NODE_INIT) + +/* Get/Set debug level */ +struct ng_btsocket_hci_raw_node_debug { + ng_hci_node_debug_ep debug; +}; +#define SIOC_HCI_RAW_NODE_GET_DEBUG \ + _IOWR('b', NGM_HCI_NODE_GET_DEBUG, \ + struct ng_btsocket_hci_raw_node_debug) +#define SIOC_HCI_RAW_NODE_SET_DEBUG \ + _IOWR('b', NGM_HCI_NODE_SET_DEBUG, \ + struct ng_btsocket_hci_raw_node_debug) + +/* Get buffer info */ +struct ng_btsocket_hci_raw_node_buffer { + ng_hci_node_buffer_ep buffer; +}; +#define SIOC_HCI_RAW_NODE_GET_BUFFER \ + _IOWR('b', NGM_HCI_NODE_GET_BUFFER, \ + struct ng_btsocket_hci_raw_node_buffer) + +/* Get BD_ADDR */ +struct ng_btsocket_hci_raw_node_bdaddr { + bdaddr_t bdaddr; +}; +#define SIOC_HCI_RAW_NODE_GET_BDADDR \ + _IOWR('b', NGM_HCI_NODE_GET_BDADDR, \ + struct ng_btsocket_hci_raw_node_bdaddr) + +/* Get features */ +struct ng_btsocket_hci_raw_node_features { + u_int8_t features[NG_HCI_FEATURES_SIZE]; +}; +#define SIOC_HCI_RAW_NODE_GET_FEATURES \ + _IOWR('b', NGM_HCI_NODE_GET_FEATURES, \ + struct ng_btsocket_hci_raw_node_features) + +/* Get stat */ +struct ng_btsocket_hci_raw_node_stat { + ng_hci_node_stat_ep stat; +}; +#define SIOC_HCI_RAW_NODE_GET_STAT \ + _IOWR('b', NGM_HCI_NODE_GET_STAT, \ + struct ng_btsocket_hci_raw_node_stat) + +/* Reset stat */ +#define SIOC_HCI_RAW_NODE_RESET_STAT \ + _IO('b', NGM_HCI_NODE_RESET_STAT) + +/* Flush neighbor cache */ +#define SIOC_HCI_RAW_NODE_FLUSH_NEIGHBOR_CACHE \ + _IO('b', NGM_HCI_NODE_FLUSH_NEIGHBOR_CACHE) + +/* Get neighbor cache */ +struct ng_btsocket_hci_raw_node_neighbor_cache { + u_int32_t num_entries; + ng_hci_node_neighbor_cache_entry_ep *entries; +}; +#define SIOC_HCI_RAW_NODE_GET_NEIGHBOR_CACHE \ + _IOWR('b', NGM_HCI_NODE_GET_NEIGHBOR_CACHE, \ + struct ng_btsocket_hci_raw_node_neighbor_cache) + +/* Get connection list */ +struct ng_btsocket_hci_raw_con_list { + u_int32_t num_connections; + ng_hci_node_con_ep *connections; +}; +#define SIOC_HCI_RAW_NODE_GET_CON_LIST \ + _IOWR('b', NGM_HCI_NODE_GET_CON_LIST, \ + struct ng_btsocket_hci_raw_con_list) + +/* Get/Set link policy settings mask */ +struct ng_btsocket_hci_raw_node_link_policy_mask { + ng_hci_node_link_policy_mask_ep policy_mask; +}; +#define SIOC_HCI_RAW_NODE_GET_LINK_POLICY_MASK \ + _IOWR('b', NGM_HCI_NODE_GET_LINK_POLICY_SETTINGS_MASK, \ + struct ng_btsocket_hci_raw_node_link_policy_mask) +#define SIOC_HCI_RAW_NODE_SET_LINK_POLICY_MASK \ + _IOWR('b', NGM_HCI_NODE_SET_LINK_POLICY_SETTINGS_MASK, \ + struct ng_btsocket_hci_raw_node_link_policy_mask) + +/* Get/Set packet mask */ +struct ng_btsocket_hci_raw_node_packet_mask { + ng_hci_node_packet_mask_ep packet_mask; +}; +#define SIOC_HCI_RAW_NODE_GET_PACKET_MASK \ + _IOWR('b', NGM_HCI_NODE_GET_PACKET_MASK, \ + struct ng_btsocket_hci_raw_node_packet_mask) +#define SIOC_HCI_RAW_NODE_SET_PACKET_MASK \ + _IOWR('b', NGM_HCI_NODE_SET_PACKET_MASK, \ + struct ng_btsocket_hci_raw_node_packet_mask) + +/* Get/Set role switch */ +struct ng_btsocket_hci_raw_node_role_switch { + ng_hci_node_role_switch_ep role_switch; +}; +#define SIOC_HCI_RAW_NODE_GET_ROLE_SWITCH \ + _IOWR('b', NGM_HCI_NODE_GET_ROLE_SWITCH, \ + struct ng_btsocket_hci_raw_node_role_switch) +#define SIOC_HCI_RAW_NODE_SET_ROLE_SWITCH \ + _IOWR('b', NGM_HCI_NODE_SET_ROLE_SWITCH, \ + struct ng_btsocket_hci_raw_node_role_switch) + +/* Get list of HCI node names */ +struct ng_btsocket_hci_raw_node_list_names { + u_int32_t num_names; + struct nodeinfo *names; +}; +#define SIOC_HCI_RAW_NODE_LIST_NAMES \ + _IOWR('b', NGM_HCI_NODE_LIST_NAMES, \ + struct ng_btsocket_hci_raw_node_list_names) + +/* + * XXX FIXME: probably does not belong here + * Bluetooth version of struct sockaddr for L2CAP sockets (RAW and SEQPACKET) + */ + +struct sockaddr_l2cap { + u_char l2cap_len; /* total length */ + u_char l2cap_family; /* address family */ + u_int16_t l2cap_psm; /* PSM (Protocol/Service Multiplexor) */ + bdaddr_t l2cap_bdaddr; /* address */ +}; + +/* L2CAP socket options */ +#define SOL_L2CAP 0x1609 /* socket option level */ + +#define SO_L2CAP_IMTU 1 /* get/set incoming MTU */ +#define SO_L2CAP_OMTU 2 /* get outgoing (peer incoming) MTU */ +#define SO_L2CAP_IFLOW 3 /* get incoming flow spec. */ +#define SO_L2CAP_OFLOW 4 /* get/set outgoing flow spec. */ +#define SO_L2CAP_FLUSH 5 /* get/set flush timeout */ + +/* + * Raw L2CAP sockets ioctl's + */ + +/* Ping */ +struct ng_btsocket_l2cap_raw_ping { + u_int32_t result; + u_int32_t echo_size; + u_int8_t *echo_data; +}; +#define SIOC_L2CAP_L2CA_PING \ + _IOWR('b', NGM_L2CAP_L2CA_PING, \ + struct ng_btsocket_l2cap_raw_ping) + +/* Get info */ +struct ng_btsocket_l2cap_raw_get_info { + u_int32_t result; + u_int32_t info_type; + u_int32_t info_size; + u_int8_t *info_data; +}; +#define SIOC_L2CAP_L2CA_GET_INFO \ + _IOWR('b', NGM_L2CAP_L2CA_GET_INFO, \ + struct ng_btsocket_l2cap_raw_get_info) + +/* Get flags */ +struct ng_btsocket_l2cap_raw_node_flags { + ng_l2cap_node_flags_ep flags; +}; +#define SIOC_L2CAP_NODE_GET_FLAGS \ + _IOWR('b', NGM_L2CAP_NODE_GET_FLAGS, \ + struct ng_btsocket_l2cap_raw_node_flags) + +/* Get/Set debug level */ +struct ng_btsocket_l2cap_raw_node_debug { + ng_l2cap_node_debug_ep debug; +}; +#define SIOC_L2CAP_NODE_GET_DEBUG \ + _IOWR('b', NGM_L2CAP_NODE_GET_DEBUG, \ + struct ng_btsocket_l2cap_raw_node_debug) +#define SIOC_L2CAP_NODE_SET_DEBUG \ + _IOWR('b', NGM_L2CAP_NODE_SET_DEBUG, \ + struct ng_btsocket_l2cap_raw_node_debug) + +/* Get connection list */ +struct ng_btsocket_l2cap_raw_con_list { + u_int32_t num_connections; + ng_l2cap_node_con_ep *connections; +}; +#define SIOC_L2CAP_NODE_GET_CON_LIST \ + _IOWR('b', NGM_L2CAP_NODE_GET_CON_LIST, \ + struct ng_btsocket_l2cap_raw_con_list) + +/* Get channel list */ +struct ng_btsocket_l2cap_raw_chan_list { + u_int32_t num_channels; + ng_l2cap_node_chan_ep *channels; +}; +#define SIOC_L2CAP_NODE_GET_CHAN_LIST \ + _IOWR('b', NGM_L2CAP_NODE_GET_CHAN_LIST, \ + struct ng_btsocket_l2cap_raw_chan_list) + +/* Get/Set auto disconnect timeout */ +struct ng_btsocket_l2cap_raw_auto_discon_timo +{ + ng_l2cap_node_auto_discon_ep timeout; +}; +#define SIOC_L2CAP_NODE_GET_AUTO_DISCON_TIMO \ + _IOWR('b', NGM_L2CAP_NODE_GET_AUTO_DISCON_TIMO, \ + struct ng_btsocket_l2cap_raw_auto_discon_timo) +#define SIOC_L2CAP_NODE_SET_AUTO_DISCON_TIMO \ + _IOWR('b', NGM_L2CAP_NODE_SET_AUTO_DISCON_TIMO, \ + struct ng_btsocket_l2cap_raw_auto_discon_timo) + +/* + * XXX FIXME: probably does not belong here + * Bluetooth version of struct sockaddr for RFCOMM sockets (STREAM) + */ + +struct sockaddr_rfcomm { + u_char rfcomm_len; /* total length */ + u_char rfcomm_family; /* address family */ + bdaddr_t rfcomm_bdaddr; /* address */ + u_int8_t rfcomm_channel; /* channel */ +}; + +/* Flow control information */ +struct ng_btsocket_rfcomm_fc_info { + u_int8_t lmodem; /* modem signals (local) */ + u_int8_t rmodem; /* modem signals (remote) */ + u_int8_t tx_cred; /* TX credits */ + u_int8_t rx_cred; /* RX credits */ + u_int8_t cfc; /* credit flow control */ + u_int8_t reserved; +}; + +/* STREAM RFCOMM socket options */ +#define SOL_RFCOMM 0x0816 /* socket options level */ + +#define SO_RFCOMM_MTU 1 /* get channel MTU */ +#define SO_RFCOMM_FC_INFO 2 /* get flow control information */ + +/* + * Netgraph node type name and cookie + */ + +#define NG_BTSOCKET_HCI_RAW_NODE_TYPE "btsock_hci_raw" +#define NG_BTSOCKET_L2CAP_RAW_NODE_TYPE "btsock_l2c_raw" +#define NG_BTSOCKET_L2CAP_NODE_TYPE "btsock_l2c" + +/* + * Debug levels + */ + +#define NG_BTSOCKET_ALERT_LEVEL 1 +#define NG_BTSOCKET_ERR_LEVEL 2 +#define NG_BTSOCKET_WARN_LEVEL 3 +#define NG_BTSOCKET_INFO_LEVEL 4 + +#endif /* _NETGRAPH_BTSOCKET_H_ */ + diff --git a/sys/netgraph7/bluetooth/include/ng_btsocket_hci_raw.h b/sys/netgraph7/bluetooth/include/ng_btsocket_hci_raw.h new file mode 100644 index 0000000000..359a990b8a --- /dev/null +++ b/sys/netgraph7/bluetooth/include/ng_btsocket_hci_raw.h @@ -0,0 +1,90 @@ +/* + * ng_btsocket_hci_raw.h + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_btsocket_hci_raw.h,v 1.3 2003/03/25 23:53:32 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/include/ng_btsocket_hci_raw.h,v 1.7 2006/07/21 17:11:13 rwatson Exp $ + */ + +#ifndef _NETGRAPH_BTSOCKET_HCI_RAW_H_ +#define _NETGRAPH_BTSOCKET_HCI_RAW_H_ + +#define NG_BTSOCKET_HCI_RAW_SENDSPACE (4 * 1024) +#define NG_BTSOCKET_HCI_RAW_RECVSPACE (4 * 1024) + +/* + * Bluetooth raw HCI socket PCB + */ + +struct ng_btsocket_hci_raw_pcb { + struct socket *so; /* socket */ + u_int32_t flags; /* flags */ +#define NG_BTSOCKET_HCI_RAW_DIRECTION (1 << 0) +#define NG_BTSOCKET_HCI_RAW_PRIVILEGED (1 << 1) + struct sockaddr_hci addr; /* local address */ + struct ng_btsocket_hci_raw_filter filter; /* filter */ + u_int32_t token; /* message token */ + struct ng_mesg *msg; /* message */ + LIST_ENTRY(ng_btsocket_hci_raw_pcb) next; /* link to next */ + struct mtx pcb_mtx; /* pcb mutex */ +}; +typedef struct ng_btsocket_hci_raw_pcb ng_btsocket_hci_raw_pcb_t; +typedef struct ng_btsocket_hci_raw_pcb * ng_btsocket_hci_raw_pcb_p; + +#define so2hci_raw_pcb(so) \ + ((struct ng_btsocket_hci_raw_pcb *)((so)->so_pcb)) + +/* + * Bluetooth raw HCI socket methods + */ + +#ifdef _KERNEL + +void ng_btsocket_hci_raw_init (void); +void ng_btsocket_hci_raw_abort (struct socket *); +void ng_btsocket_hci_raw_close (struct socket *); +int ng_btsocket_hci_raw_attach (struct socket *, int, struct thread *); +int ng_btsocket_hci_raw_bind (struct socket *, struct sockaddr *, + struct thread *); +int ng_btsocket_hci_raw_connect (struct socket *, struct sockaddr *, + struct thread *); +int ng_btsocket_hci_raw_control (struct socket *, u_long, caddr_t, + struct ifnet *, struct thread *); +int ng_btsocket_hci_raw_ctloutput (struct socket *, struct sockopt *); +void ng_btsocket_hci_raw_detach (struct socket *); +int ng_btsocket_hci_raw_disconnect (struct socket *); +int ng_btsocket_hci_raw_peeraddr (struct socket *, struct sockaddr **); +int ng_btsocket_hci_raw_send (struct socket *, int, struct mbuf *, + struct sockaddr *, struct mbuf *, + struct thread *); +int ng_btsocket_hci_raw_sockaddr (struct socket *, struct sockaddr **); + +#endif /* _KERNEL */ + +#endif /* ndef _NETGRAPH_BTSOCKET_HCI_RAW_H_ */ + diff --git a/sys/netgraph7/bluetooth/include/ng_btsocket_l2cap.h b/sys/netgraph7/bluetooth/include/ng_btsocket_l2cap.h new file mode 100644 index 0000000000..1597650a68 --- /dev/null +++ b/sys/netgraph7/bluetooth/include/ng_btsocket_l2cap.h @@ -0,0 +1,210 @@ +/* + * ng_btsocket_l2cap.h + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_btsocket_l2cap.h,v 1.4 2003/03/25 23:53:33 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/include/ng_btsocket_l2cap.h,v 1.8 2006/07/21 17:11:13 rwatson Exp $ + */ + +#ifndef _NETGRAPH_BTSOCKET_L2CAP_H_ +#define _NETGRAPH_BTSOCKET_L2CAP_H_ + +/* + * L2CAP routing entry + */ + +struct ng_hook; +struct ng_message; + +struct ng_btsocket_l2cap_rtentry { + bdaddr_t src; /* source BD_ADDR */ + struct ng_hook *hook; /* downstream hook */ + LIST_ENTRY(ng_btsocket_l2cap_rtentry) next; /* link to next */ +}; +typedef struct ng_btsocket_l2cap_rtentry ng_btsocket_l2cap_rtentry_t; +typedef struct ng_btsocket_l2cap_rtentry * ng_btsocket_l2cap_rtentry_p; + +/***************************************************************************** + ***************************************************************************** + ** SOCK_RAW L2CAP sockets ** + ***************************************************************************** + *****************************************************************************/ + +#define NG_BTSOCKET_L2CAP_RAW_SENDSPACE NG_L2CAP_MTU_DEFAULT +#define NG_BTSOCKET_L2CAP_RAW_RECVSPACE NG_L2CAP_MTU_DEFAULT + +/* + * Bluetooth raw L2CAP socket PCB + */ + +struct ng_btsocket_l2cap_raw_pcb { + struct socket *so; /* socket */ + + u_int32_t flags; /* flags */ +#define NG_BTSOCKET_L2CAP_RAW_PRIVILEGED (1 << 0) + + bdaddr_t src; /* source address */ + bdaddr_t dst; /* dest address */ + ng_btsocket_l2cap_rtentry_p rt; /* routing info */ + + u_int32_t token; /* message token */ + struct ng_mesg *msg; /* message */ + + struct mtx pcb_mtx; /* pcb mutex */ + + LIST_ENTRY(ng_btsocket_l2cap_raw_pcb) next; /* link to next PCB */ +}; +typedef struct ng_btsocket_l2cap_raw_pcb ng_btsocket_l2cap_raw_pcb_t; +typedef struct ng_btsocket_l2cap_raw_pcb * ng_btsocket_l2cap_raw_pcb_p; + +#define so2l2cap_raw_pcb(so) \ + ((struct ng_btsocket_l2cap_raw_pcb *)((so)->so_pcb)) + +/* + * Bluetooth raw L2CAP socket methods + */ + +#ifdef _KERNEL + +void ng_btsocket_l2cap_raw_init (void); +void ng_btsocket_l2cap_raw_abort (struct socket *); +void ng_btsocket_l2cap_raw_close (struct socket *); +int ng_btsocket_l2cap_raw_attach (struct socket *, int, struct thread *); +int ng_btsocket_l2cap_raw_bind (struct socket *, struct sockaddr *, + struct thread *); +int ng_btsocket_l2cap_raw_connect (struct socket *, struct sockaddr *, + struct thread *); +int ng_btsocket_l2cap_raw_control (struct socket *, u_long, caddr_t, + struct ifnet *, struct thread *); +void ng_btsocket_l2cap_raw_detach (struct socket *); +int ng_btsocket_l2cap_raw_disconnect (struct socket *); +int ng_btsocket_l2cap_raw_peeraddr (struct socket *, struct sockaddr **); +int ng_btsocket_l2cap_raw_send (struct socket *, int, struct mbuf *, + struct sockaddr *, struct mbuf *, + struct thread *); +int ng_btsocket_l2cap_raw_sockaddr (struct socket *, struct sockaddr **); + +#endif /* _KERNEL */ + +/***************************************************************************** + ***************************************************************************** + ** SOCK_SEQPACKET L2CAP sockets ** + ***************************************************************************** + *****************************************************************************/ + +#define NG_BTSOCKET_L2CAP_SENDSPACE NG_L2CAP_MTU_DEFAULT /* (64 * 1024) */ +#define NG_BTSOCKET_L2CAP_RECVSPACE (64 * 1024) + +/* + * Bluetooth L2CAP socket PCB + */ + +struct ng_btsocket_l2cap_pcb { + struct socket *so; /* Pointer to socket */ + + bdaddr_t src; /* Source address */ + bdaddr_t dst; /* Destination address */ + + u_int16_t psm; /* PSM */ + u_int16_t cid; /* Local channel ID */ + + u_int16_t flags; /* socket flags */ +#define NG_BTSOCKET_L2CAP_CLIENT (1 << 0) /* socket is client */ +#define NG_BTSOCKET_L2CAP_TIMO (1 << 1) /* timeout pending */ + + u_int8_t state; /* socket state */ +#define NG_BTSOCKET_L2CAP_CLOSED 0 /* socket closed */ +#define NG_BTSOCKET_L2CAP_CONNECTING 1 /* wait for connect */ +#define NG_BTSOCKET_L2CAP_CONFIGURING 2 /* wait for config */ +#define NG_BTSOCKET_L2CAP_OPEN 3 /* socket open */ +#define NG_BTSOCKET_L2CAP_DISCONNECTING 4 /* wait for disconnect */ + + u_int8_t cfg_state; /* config state */ +#define NG_BTSOCKET_L2CAP_CFG_IN (1 << 0) /* incoming path done */ +#define NG_BTSOCKET_L2CAP_CFG_OUT (1 << 1) /* outgoing path done */ +#define NG_BTSOCKET_L2CAP_CFG_BOTH \ + (NG_BTSOCKET_L2CAP_CFG_IN | NG_BTSOCKET_L2CAP_CFG_OUT) + +#define NG_BTSOCKET_L2CAP_CFG_IN_SENT (1 << 2) /* L2CAP ConfigReq sent */ +#define NG_BTSOCKET_L2CAP_CFG_OUT_SENT (1 << 3) /* ---/--- */ + + u_int16_t imtu; /* Incoming MTU */ + ng_l2cap_flow_t iflow; /* Input flow spec */ + + u_int16_t omtu; /* Outgoing MTU */ + ng_l2cap_flow_t oflow; /* Outgoing flow spec */ + + u_int16_t flush_timo; /* flush timeout */ + u_int16_t link_timo; /* link timeout */ + + struct callout_handle timo; /* timeout */ + + u_int32_t token; /* message token */ + ng_btsocket_l2cap_rtentry_p rt; /* routing info */ + + struct mtx pcb_mtx; /* pcb mutex */ + + LIST_ENTRY(ng_btsocket_l2cap_pcb) next; /* link to next PCB */ +}; +typedef struct ng_btsocket_l2cap_pcb ng_btsocket_l2cap_pcb_t; +typedef struct ng_btsocket_l2cap_pcb * ng_btsocket_l2cap_pcb_p; + +#define so2l2cap_pcb(so) \ + ((struct ng_btsocket_l2cap_pcb *)((so)->so_pcb)) + +/* + * Bluetooth L2CAP socket methods + */ + +#ifdef _KERNEL + +void ng_btsocket_l2cap_init (void); +void ng_btsocket_l2cap_abort (struct socket *); +void ng_btsocket_l2cap_close (struct socket *); +int ng_btsocket_l2cap_accept (struct socket *, struct sockaddr **); +int ng_btsocket_l2cap_attach (struct socket *, int, struct thread *); +int ng_btsocket_l2cap_bind (struct socket *, struct sockaddr *, + struct thread *); +int ng_btsocket_l2cap_connect (struct socket *, struct sockaddr *, + struct thread *); +int ng_btsocket_l2cap_control (struct socket *, u_long, caddr_t, + struct ifnet *, struct thread *); +int ng_btsocket_l2cap_ctloutput (struct socket *, struct sockopt *); +void ng_btsocket_l2cap_detach (struct socket *); +int ng_btsocket_l2cap_disconnect (struct socket *); +int ng_btsocket_l2cap_listen (struct socket *, int, struct thread *); +int ng_btsocket_l2cap_peeraddr (struct socket *, struct sockaddr **); +int ng_btsocket_l2cap_send (struct socket *, int, struct mbuf *, + struct sockaddr *, struct mbuf *, + struct thread *); +int ng_btsocket_l2cap_sockaddr (struct socket *, struct sockaddr **); + +#endif /* _KERNEL */ + +#endif /* _NETGRAPH_BTSOCKET_L2CAP_H_ */ + diff --git a/sys/netgraph7/bluetooth/include/ng_btsocket_rfcomm.h b/sys/netgraph7/bluetooth/include/ng_btsocket_rfcomm.h new file mode 100644 index 0000000000..6e20bfdcfa --- /dev/null +++ b/sys/netgraph7/bluetooth/include/ng_btsocket_rfcomm.h @@ -0,0 +1,340 @@ +/* + * ng_btsocket_rfcomm.h + */ + +/*- + * Copyright (c) 2001-2003 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_btsocket_rfcomm.h,v 1.10 2003/03/29 22:27:42 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/include/ng_btsocket_rfcomm.h,v 1.8 2006/07/21 17:11:13 rwatson Exp $ + */ + +#ifndef _NETGRAPH_BTSOCKET_RFCOMM_H_ +#define _NETGRAPH_BTSOCKET_RFCOMM_H_ + +/***************************************************************************** + ***************************************************************************** + ** RFCOMM ** + ***************************************************************************** + *****************************************************************************/ + +/* XXX FIXME this does not belong here */ + +#define RFCOMM_DEFAULT_MTU 667 +#define RFCOMM_MAX_MTU 1024 + +#define RFCOMM_DEFAULT_CREDITS 7 +#define RFCOMM_MAX_CREDITS 40 + +/* RFCOMM frame types */ +#define RFCOMM_FRAME_SABM 0x2f +#define RFCOMM_FRAME_DISC 0x43 +#define RFCOMM_FRAME_UA 0x63 +#define RFCOMM_FRAME_DM 0x0f +#define RFCOMM_FRAME_UIH 0xef + +/* RFCOMM MCC commands */ +#define RFCOMM_MCC_TEST 0x08 /* Test */ +#define RFCOMM_MCC_FCON 0x28 /* Flow Control on */ +#define RFCOMM_MCC_FCOFF 0x18 /* Flow Control off */ +#define RFCOMM_MCC_MSC 0x38 /* Modem Status Command */ +#define RFCOMM_MCC_RPN 0x24 /* Remote Port Negotiation */ +#define RFCOMM_MCC_RLS 0x14 /* Remote Line Status */ +#define RFCOMM_MCC_PN 0x20 /* Port Negotiation */ +#define RFCOMM_MCC_NSC 0x04 /* Non Supported Command */ + +/* RFCOMM modem signals */ +#define RFCOMM_MODEM_FC 0x02 /* Flow Control asserted */ +#define RFCOMM_MODEM_RTC 0x04 /* Ready To Communicate */ +#define RFCOMM_MODEM_RTR 0x08 /* Ready To Receive */ +#define RFCOMM_MODEM_IC 0x40 /* Incomming Call */ +#define RFCOMM_MODEM_DV 0x80 /* Data Valid */ + +/* RPN parameters - baud rate */ +#define RFCOMM_RPN_BR_2400 0x0 +#define RFCOMM_RPN_BR_4800 0x1 +#define RFCOMM_RPN_BR_7200 0x2 +#define RFCOMM_RPN_BR_9600 0x3 +#define RFCOMM_RPN_BR_19200 0x4 +#define RFCOMM_RPN_BR_38400 0x5 +#define RFCOMM_RPN_BR_57600 0x6 +#define RFCOMM_RPN_BR_115200 0x7 +#define RFCOMM_RPN_BR_230400 0x8 + +/* RPN parameters - data bits */ +#define RFCOMM_RPN_DATA_5 0x0 +#define RFCOMM_RPN_DATA_6 0x1 +#define RFCOMM_RPN_DATA_7 0x2 +#define RFCOMM_RPN_DATA_8 0x3 + +/* RPN parameters - stop bit */ +#define RFCOMM_RPN_STOP_1 0 +#define RFCOMM_RPN_STOP_15 1 + +/* RPN parameters - parity */ +#define RFCOMM_RPN_PARITY_NONE 0x0 +#define RFCOMM_RPN_PARITY_ODD 0x4 +#define RFCOMM_RPN_PARITY_EVEN 0x5 +#define RFCOMM_RPN_PARITY_MARK 0x6 +#define RFCOMM_RPN_PARITY_SPACE 0x7 + +/* RPN parameters - flow control */ +#define RFCOMM_RPN_FLOW_NONE 0x00 +#define RFCOMM_RPN_XON_CHAR 0x11 +#define RFCOMM_RPN_XOFF_CHAR 0x13 + +/* RPN parameters - mask */ +#define RFCOMM_RPN_PM_BITRATE 0x0001 +#define RFCOMM_RPN_PM_DATA 0x0002 +#define RFCOMM_RPN_PM_STOP 0x0004 +#define RFCOMM_RPN_PM_PARITY 0x0008 +#define RFCOMM_RPN_PM_PARITY_TYPE 0x0010 +#define RFCOMM_RPN_PM_XON 0x0020 +#define RFCOMM_RPN_PM_XOFF 0x0040 +#define RFCOMM_RPN_PM_FLOW 0x3F00 +#define RFCOMM_RPN_PM_ALL 0x3F7F + +/* RFCOMM frame header */ +struct rfcomm_frame_hdr +{ + u_int8_t address; + u_int8_t control; + u_int8_t length; /* Actual size could be 2 bytes */ +} __attribute__ ((packed)); + +/* RFCOMM command frame header */ +struct rfcomm_cmd_hdr +{ + u_int8_t address; + u_int8_t control; + u_int8_t length; + u_int8_t fcs; +} __attribute__ ((packed)); + +/* RFCOMM MCC command header */ +struct rfcomm_mcc_hdr +{ + u_int8_t type; + u_int8_t length; /* XXX FIXME Can actual size be 2 bytes?? */ +} __attribute__ ((packed)); + +/* RFCOMM MSC command */ +struct rfcomm_mcc_msc +{ + u_int8_t address; + u_int8_t modem; +} __attribute__ ((packed)); + +/* RFCOMM RPN command */ +struct rfcomm_mcc_rpn +{ + u_int8_t dlci; + u_int8_t bit_rate; + u_int8_t line_settings; + u_int8_t flow_control; + u_int8_t xon_char; + u_int8_t xoff_char; + u_int16_t param_mask; +} __attribute__ ((packed)); + +/* RFCOMM RLS command */ +struct rfcomm_mcc_rls +{ + u_int8_t address; + u_int8_t status; +} __attribute__ ((packed)); + +/* RFCOMM PN command */ +struct rfcomm_mcc_pn +{ + u_int8_t dlci; + u_int8_t flow_control; + u_int8_t priority; + u_int8_t ack_timer; + u_int16_t mtu; + u_int8_t max_retrans; + u_int8_t credits; +} __attribute__ ((packed)); + +/* RFCOMM frame parsing macros */ +#define RFCOMM_DLCI(b) (((b) & 0xfc) >> 2) +#define RFCOMM_CHANNEL(b) (((b) & 0xf8) >> 3) +#define RFCOMM_DIRECTION(b) (((b) & 0x04) >> 2) +#define RFCOMM_TYPE(b) (((b) & 0xef)) + +#define RFCOMM_EA(b) (((b) & 0x01)) +#define RFCOMM_CR(b) (((b) & 0x02) >> 1) +#define RFCOMM_PF(b) (((b) & 0x10) >> 4) + +#define RFCOMM_SRVCHANNEL(dlci) ((dlci) >> 1) + +#define RFCOMM_MKADDRESS(cr, dlci) \ + ((((dlci) & 0x3f) << 2) | ((cr) << 1) | 0x01) + +#define RFCOMM_MKCONTROL(type, pf) ((((type) & 0xef) | ((pf) << 4))) +#define RFCOMM_MKDLCI(dir, channel) ((((channel) & 0x1f) << 1) | (dir)) + +#define RFCOMM_MKLEN8(len) (((len) << 1) | 1) +#define RFCOMM_MKLEN16(len) ((len) << 1) + +/* RFCOMM MCC macros */ +#define RFCOMM_MCC_TYPE(b) (((b) & 0xfc) >> 2) +#define RFCOMM_MCC_LENGTH(b) (((b) & 0xfe) >> 1) +#define RFCOMM_MKMCC_TYPE(cr, type) ((((type) << 2) | ((cr) << 1) | 0x01)) + +/* RPN macros */ +#define RFCOMM_RPN_DATA_BITS(line) ((line) & 0x3) +#define RFCOMM_RPN_STOP_BITS(line) (((line) >> 2) & 0x1) +#define RFCOMM_RPN_PARITY(line) (((line) >> 3) & 0x3) +#define RFCOMM_MKRPN_LINE_SETTINGS(data, stop, parity) \ + (((data) & 0x3) | (((stop) & 0x1) << 2) | (((parity) & 0x3) << 3)) + +/***************************************************************************** + ***************************************************************************** + ** SOCK_STREAM RFCOMM sockets ** + ***************************************************************************** + *****************************************************************************/ + +#define NG_BTSOCKET_RFCOMM_SENDSPACE \ + (RFCOMM_MAX_CREDITS * RFCOMM_DEFAULT_MTU * 2) +#define NG_BTSOCKET_RFCOMM_RECVSPACE \ + (RFCOMM_MAX_CREDITS * RFCOMM_DEFAULT_MTU * 2) + +/* + * Bluetooth RFCOMM session. One L2CAP connection == one RFCOMM session + */ + +struct ng_btsocket_rfcomm_pcb; +struct ng_btsocket_rfcomm_session; + +struct ng_btsocket_rfcomm_session { + struct socket *l2so; /* L2CAP socket */ + + u_int16_t state; /* session state */ +#define NG_BTSOCKET_RFCOMM_SESSION_CLOSED 0 +#define NG_BTSOCKET_RFCOMM_SESSION_LISTENING 1 +#define NG_BTSOCKET_RFCOMM_SESSION_CONNECTING 2 +#define NG_BTSOCKET_RFCOMM_SESSION_CONNECTED 3 +#define NG_BTSOCKET_RFCOMM_SESSION_OPEN 4 +#define NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING 5 + + u_int16_t flags; /* session flags */ +#define NG_BTSOCKET_RFCOMM_SESSION_INITIATOR (1 << 0) /* initiator */ +#define NG_BTSOCKET_RFCOMM_SESSION_LFC (1 << 1) /* local flow */ +#define NG_BTSOCKET_RFCOMM_SESSION_RFC (1 << 2) /* remote flow */ + +#define INITIATOR(s) \ + (((s)->flags & NG_BTSOCKET_RFCOMM_SESSION_INITIATOR)? 1 : 0) + + u_int16_t mtu; /* default MTU */ + struct ng_bt_mbufq outq; /* outgoing queue */ + + struct mtx session_mtx; /* session lock */ + LIST_HEAD(, ng_btsocket_rfcomm_pcb) dlcs; /* active DLC */ + + LIST_ENTRY(ng_btsocket_rfcomm_session) next; /* link to next */ +}; +typedef struct ng_btsocket_rfcomm_session ng_btsocket_rfcomm_session_t; +typedef struct ng_btsocket_rfcomm_session * ng_btsocket_rfcomm_session_p; + +/* + * Bluetooth RFCOMM socket PCB (DLC) + */ + +struct ng_btsocket_rfcomm_pcb { + struct socket *so; /* RFCOMM socket */ + struct ng_btsocket_rfcomm_session *session; /* RFCOMM session */ + + u_int16_t flags; /* DLC flags */ +#define NG_BTSOCKET_RFCOMM_DLC_TIMO (1 << 0) /* timeout pending */ +#define NG_BTSOCKET_RFCOMM_DLC_CFC (1 << 1) /* credit flow ctrl */ +#define NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT (1 << 2) /* timeout happend */ +#define NG_BTSOCKET_RFCOMM_DLC_DETACHED (1 << 3) /* DLC detached */ +#define NG_BTSOCKET_RFCOMM_DLC_SENDING (1 << 4) /* send pending */ + + u_int16_t state; /* DLC state */ +#define NG_BTSOCKET_RFCOMM_DLC_CLOSED 0 +#define NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT 1 +#define NG_BTSOCKET_RFCOMM_DLC_CONFIGURING 2 +#define NG_BTSOCKET_RFCOMM_DLC_CONNECTING 3 +#define NG_BTSOCKET_RFCOMM_DLC_CONNECTED 4 +#define NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING 5 + + bdaddr_t src; /* source address */ + bdaddr_t dst; /* dest. address */ + + u_int8_t channel; /* RFCOMM channel */ + u_int8_t dlci; /* RFCOMM DLCI */ + + u_int8_t lmodem; /* local mdm signls */ + u_int8_t rmodem; /* remote -/- */ + + u_int16_t mtu; /* MTU */ + int16_t rx_cred; /* RX credits */ + int16_t tx_cred; /* TX credits */ + + struct mtx pcb_mtx; /* PCB lock */ + struct callout_handle timo; /* timeout */ + + LIST_ENTRY(ng_btsocket_rfcomm_pcb) session_next;/* link to next */ + LIST_ENTRY(ng_btsocket_rfcomm_pcb) next; /* link to next */ +}; +typedef struct ng_btsocket_rfcomm_pcb ng_btsocket_rfcomm_pcb_t; +typedef struct ng_btsocket_rfcomm_pcb * ng_btsocket_rfcomm_pcb_p; + +#define so2rfcomm_pcb(so) \ + ((struct ng_btsocket_rfcomm_pcb *)((so)->so_pcb)) + +/* + * Bluetooth RFCOMM socket methods + */ + +#ifdef _KERNEL + +void ng_btsocket_rfcomm_init (void); +void ng_btsocket_rfcomm_abort (struct socket *); +void ng_btsocket_rfcomm_close (struct socket *); +int ng_btsocket_rfcomm_accept (struct socket *, struct sockaddr **); +int ng_btsocket_rfcomm_attach (struct socket *, int, struct thread *); +int ng_btsocket_rfcomm_bind (struct socket *, struct sockaddr *, + struct thread *); +int ng_btsocket_rfcomm_connect (struct socket *, struct sockaddr *, + struct thread *); +int ng_btsocket_rfcomm_control (struct socket *, u_long, caddr_t, + struct ifnet *, struct thread *); +int ng_btsocket_rfcomm_ctloutput (struct socket *, struct sockopt *); +void ng_btsocket_rfcomm_detach (struct socket *); +int ng_btsocket_rfcomm_disconnect (struct socket *); +int ng_btsocket_rfcomm_listen (struct socket *, int, struct thread *); +int ng_btsocket_rfcomm_peeraddr (struct socket *, struct sockaddr **); +int ng_btsocket_rfcomm_send (struct socket *, int, struct mbuf *, + struct sockaddr *, struct mbuf *, + struct thread *); +int ng_btsocket_rfcomm_sockaddr (struct socket *, struct sockaddr **); + +#endif /* _KERNEL */ + +#endif /* _NETGRAPH_BTSOCKET_RFCOMM_H_ */ + diff --git a/sys/netgraph7/bluetooth/include/ng_h4.h b/sys/netgraph7/bluetooth/include/ng_h4.h new file mode 100644 index 0000000000..a8a8fdcdb8 --- /dev/null +++ b/sys/netgraph7/bluetooth/include/ng_h4.h @@ -0,0 +1,113 @@ +/* + * ng_h4.h + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_h4.h,v 1.1 2002/11/24 19:47:05 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/include/ng_h4.h,v 1.5 2005/01/07 01:45:43 imp Exp $ + * + * Based on: + * --------- + * + * FreeBSD: src/sys/netgraph/ng_tty.h + * Author: Archie Cobbs + */ + +/* + * This file contains everything that application needs to know about + * Bluetooth HCI UART transport layer as per chapter H4 of the Bluetooth + * Specification Book v1.1. + * + * This file can be included by both kernel and userland applications. + */ + +#ifndef _NETGRAPH_H4_H_ +#define _NETGRAPH_H4_H_ + +/************************************************************************** + ************************************************************************** + ** Netgraph node hook name, type name and type cookie and commands + ************************************************************************** + **************************************************************************/ + +/* Hook name */ +#define NG_H4_HOOK "hook" + +/* Node type name and magic cookie */ +#define NG_H4_NODE_TYPE "h4" +#define NGM_H4_COOKIE 1013899512 + +/* Node states */ +#define NG_H4_W4_PKT_IND 1 /* Waiting for packet indicator */ +#define NG_H4_W4_PKT_HDR 2 /* Waiting for packet header */ +#define NG_H4_W4_PKT_DATA 3 /* Waiting for packet data */ + +/* Debug levels */ +#define NG_H4_ALERT_LEVEL 1 +#define NG_H4_ERR_LEVEL 2 +#define NG_H4_WARN_LEVEL 3 +#define NG_H4_INFO_LEVEL 4 + +/************************************************************************** + ************************************************************************** + ** H4 node command/event parameters + ************************************************************************** + **************************************************************************/ + +/* Reset node */ +#define NGM_H4_NODE_RESET 1 + +/* Get node state (see states above) */ +#define NGM_H4_NODE_GET_STATE 2 +typedef u_int16_t ng_h4_node_state_ep; + +/* Get/Set node debug level (see levels above) */ +#define NGM_H4_NODE_GET_DEBUG 3 +#define NGM_H4_NODE_SET_DEBUG 4 +typedef u_int16_t ng_h4_node_debug_ep; + +/* Get/Set max queue length for the node */ +#define NGM_H4_NODE_GET_QLEN 5 +#define NGM_H4_NODE_SET_QLEN 6 +typedef int32_t ng_h4_node_qlen_ep; + +/* Get node statistic */ +#define NGM_H4_NODE_GET_STAT 7 +typedef struct { + u_int32_t pckts_recv; /* # of packets received */ + u_int32_t bytes_recv; /* # of bytes received */ + u_int32_t pckts_sent; /* # of packets sent */ + u_int32_t bytes_sent; /* # of bytes sent */ + u_int32_t oerrors; /* # of output errors */ + u_int32_t ierrors; /* # of input errors */ +} ng_h4_node_stat_ep; + +/* Reset node statistic */ +#define NGM_H4_NODE_RESET_STAT 8 + +#endif /* _NETGRAPH_H4_H_ */ + diff --git a/sys/netgraph7/bluetooth/include/ng_hci.h b/sys/netgraph7/bluetooth/include/ng_hci.h new file mode 100644 index 0000000000..da475551ab --- /dev/null +++ b/sys/netgraph7/bluetooth/include/ng_hci.h @@ -0,0 +1,1662 @@ +/* + * ng_hci.h + */ + +/*- + * Copyright (c) 2001 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_hci.h,v 1.2 2003/03/18 00:09:37 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/include/ng_hci.h,v 1.7 2006/05/17 00:13:06 emax Exp $ + */ + +/* + * This file contains everything that application needs to know about + * Host Controller Interface (HCI). All information was obtained from + * Bluetooth Specification Book v1.1. + * + * This file can be included by both kernel and userland applications. + * + * NOTE: Here and after Bluetooth device is called a "unit". Bluetooth + * specification refers to both devices and units. They are the + * same thing (i think), so to be consistent word "unit" will be + * used. + */ + +#ifndef _NETGRAPH_HCI_H_ +#define _NETGRAPH_HCI_H_ + +/************************************************************************** + ************************************************************************** + ** Netgraph node hook name, type name and type cookie and commands + ************************************************************************** + **************************************************************************/ + +/* Node type name and type cookie */ +#define NG_HCI_NODE_TYPE "hci" +#define NGM_HCI_COOKIE 1000774184 + +/* Netgraph node hook names */ +#define NG_HCI_HOOK_DRV "drv" /* Driver <-> HCI */ +#define NG_HCI_HOOK_ACL "acl" /* HCI <-> Upper */ +#define NG_HCI_HOOK_SCO "sco" /* HCI <-> Upper */ +#define NG_HCI_HOOK_RAW "raw" /* HCI <-> Upper */ + +/************************************************************************** + ************************************************************************** + ** Common defines and types (HCI) + ************************************************************************** + **************************************************************************/ + +/* All sizes are in bytes */ +#define NG_HCI_BDADDR_SIZE 6 /* unit address */ +#define NG_HCI_LAP_SIZE 3 /* unit LAP */ +#define NG_HCI_KEY_SIZE 16 /* link key */ +#define NG_HCI_PIN_SIZE 16 /* link PIN */ +#define NG_HCI_EVENT_MASK_SIZE 8 /* event mask */ +#define NG_HCI_CLASS_SIZE 3 /* unit class */ +#define NG_HCI_FEATURES_SIZE 8 /* LMP features */ +#define NG_HCI_UNIT_NAME_SIZE 248 /* unit name size */ + +/* HCI specification */ +#define NG_HCI_SPEC_V10 0x00 /* v1.0 */ +#define NG_HCI_SPEC_V11 0x01 /* v1.1 */ +/* 0x02 - 0xFF - reserved for future use */ + +/* LMP features */ +/* ------------------- byte 0 --------------------*/ +#define NG_HCI_LMP_3SLOT 0x01 +#define NG_HCI_LMP_5SLOT 0x02 +#define NG_HCI_LMP_ENCRYPTION 0x04 +#define NG_HCI_LMP_SLOT_OFFSET 0x08 +#define NG_HCI_LMP_TIMING_ACCURACY 0x10 +#define NG_HCI_LMP_SWITCH 0x20 +#define NG_HCI_LMP_HOLD_MODE 0x40 +#define NG_HCI_LMP_SNIFF_MODE 0x80 +/* ------------------- byte 1 --------------------*/ +#define NG_HCI_LMP_PARK_MODE 0x01 +#define NG_HCI_LMP_RSSI 0x02 +#define NG_HCI_LMP_CHANNEL_QUALITY 0x04 +#define NG_HCI_LMP_SCO_LINK 0x08 +#define NG_HCI_LMP_HV2_PKT 0x10 +#define NG_HCI_LMP_HV3_PKT 0x20 +#define NG_HCI_LMP_ULAW_LOG 0x40 +#define NG_HCI_LMP_ALAW_LOG 0x80 +/* ------------------- byte 2 --------------------*/ +#define NG_HCI_LMP_CVSD 0x01 +#define NG_HCI_LMP_PAGING_SCHEME 0x02 +#define NG_HCI_LMP_POWER_CONTROL 0x04 +#define NG_HCI_LMP_TRANSPARENT_SCO 0x08 +#define NG_HCI_LMP_FLOW_CONTROL_LAG0 0x10 +#define NG_HCI_LMP_FLOW_CONTROL_LAG1 0x20 +#define NG_HCI_LMP_FLOW_CONTROL_LAG2 0x40 + +/* Link types */ +#define NG_HCI_LINK_SCO 0x00 /* Voice */ +#define NG_HCI_LINK_ACL 0x01 /* Data */ +/* 0x02 - 0xFF - reserved for future use */ + +/* Packet types */ + /* 0x0001 - 0x0004 - reserved for future use */ +#define NG_HCI_PKT_DM1 0x0008 /* ACL link */ +#define NG_HCI_PKT_DH1 0x0010 /* ACL link */ +#define NG_HCI_PKT_HV1 0x0020 /* SCO link */ +#define NG_HCI_PKT_HV2 0x0040 /* SCO link */ +#define NG_HCI_PKT_HV3 0x0080 /* SCO link */ + /* 0x0100 - 0x0200 - reserved for future use */ +#define NG_HCI_PKT_DM3 0x0400 /* ACL link */ +#define NG_HCI_PKT_DH3 0x0800 /* ACL link */ + /* 0x1000 - 0x2000 - reserved for future use */ +#define NG_HCI_PKT_DM5 0x4000 /* ACL link */ +#define NG_HCI_PKT_DH5 0x8000 /* ACL link */ + +/* + * Connection modes/Unit modes + * + * This is confusing. It means that one of the units change its mode + * for the specific connection. For example one connection was put on + * hold (but i could be wrong :) + */ + +#define NG_HCI_UNIT_MODE_ACTIVE 0x00 +#define NG_HCI_UNIT_MODE_HOLD 0x01 +#define NG_HCI_UNIT_MODE_SNIFF 0x02 +#define NG_HCI_UNIT_MODE_PARK 0x03 +/* 0x04 - 0xFF - reserved for future use */ + +/* Page scan modes */ +#define NG_HCI_MANDATORY_PAGE_SCAN_MODE 0x00 +#define NG_HCI_OPTIONAL_PAGE_SCAN_MODE1 0x01 +#define NG_HCI_OPTIONAL_PAGE_SCAN_MODE2 0x02 +#define NG_HCI_OPTIONAL_PAGE_SCAN_MODE3 0x03 +/* 0x04 - 0xFF - reserved for future use */ + +/* Page scan repetition modes */ +#define NG_HCI_SCAN_REP_MODE0 0x00 +#define NG_HCI_SCAN_REP_MODE1 0x01 +#define NG_HCI_SCAN_REP_MODE2 0x02 +/* 0x03 - 0xFF - reserved for future use */ + +/* Page scan period modes */ +#define NG_HCI_PAGE_SCAN_PERIOD_MODE0 0x00 +#define NG_HCI_PAGE_SCAN_PERIOD_MODE1 0x01 +#define NG_HCI_PAGE_SCAN_PERIOD_MODE2 0x02 +/* 0x03 - 0xFF - reserved for future use */ + +/* Scan enable */ +#define NG_HCI_NO_SCAN_ENABLE 0x00 +#define NG_HCI_INQUIRY_ENABLE_PAGE_DISABLE 0x01 +#define NG_HCI_INQUIRY_DISABLE_PAGE_ENABLE 0x02 +#define NG_HCI_INQUIRY_ENABLE_PAGE_ENABLE 0x03 +/* 0x04 - 0xFF - reserved for future use */ + +/* Hold mode activities */ +#define NG_HCI_HOLD_MODE_NO_CHANGE 0x00 +#define NG_HCI_HOLD_MODE_SUSPEND_PAGE_SCAN 0x01 +#define NG_HCI_HOLD_MODE_SUSPEND_INQUIRY_SCAN 0x02 +#define NG_HCI_HOLD_MODE_SUSPEND_PERIOD_INQUIRY 0x04 +/* 0x08 - 0x80 - reserved for future use */ + +/* Connection roles */ +#define NG_HCI_ROLE_MASTER 0x00 +#define NG_HCI_ROLE_SLAVE 0x01 +/* 0x02 - 0xFF - reserved for future use */ + +/* Key flags */ +#define NG_HCI_USE_SEMI_PERMANENT_LINK_KEYS 0x00 +#define NG_HCI_USE_TEMPORARY_LINK_KEY 0x01 +/* 0x02 - 0xFF - reserved for future use */ + +/* Pin types */ +#define NG_HCI_PIN_TYPE_VARIABLE 0x00 +#define NG_HCI_PIN_TYPE_FIXED 0x01 + +/* Link key types */ +#define NG_HCI_LINK_KEY_TYPE_COMBINATION_KEY 0x00 +#define NG_HCI_LINK_KEY_TYPE_LOCAL_UNIT_KEY 0x01 +#define NG_HCI_LINK_KEY_TYPE_REMOTE_UNIT_KEY 0x02 +/* 0x03 - 0xFF - reserved for future use */ + +/* Encryption modes */ +#define NG_HCI_ENCRYPTION_MODE_NONE 0x00 +#define NG_HCI_ENCRYPTION_MODE_P2P 0x01 +#define NG_HCI_ENCRYPTION_MODE_ALL 0x02 +/* 0x03 - 0xFF - reserved for future use */ + +/* Quality of service types */ +#define NG_HCI_SERVICE_TYPE_NO_TRAFFIC 0x00 +#define NG_HCI_SERVICE_TYPE_BEST_EFFORT 0x01 +#define NG_HCI_SERVICE_TYPE_GUARANTEED 0x02 +/* 0x03 - 0xFF - reserved for future use */ + +/* Link policy settings */ +#define NG_HCI_LINK_POLICY_DISABLE_ALL_LM_MODES 0x0000 +#define NG_HCI_LINK_POLICY_ENABLE_ROLE_SWITCH 0x0001 /* Master/Slave switch */ +#define NG_HCI_LINK_POLICY_ENABLE_HOLD_MODE 0x0002 +#define NG_HCI_LINK_POLICY_ENABLE_SNIFF_MODE 0x0004 +#define NG_HCI_LINK_POLICY_ENABLE_PARK_MODE 0x0008 +/* 0x0010 - 0x8000 - reserved for future use */ + +/* Event masks */ +#define NG_HCI_EVMSK_ALL 0x00000000ffffffff +#define NG_HCI_EVMSK_NONE 0x0000000000000000 +#define NG_HCI_EVMSK_INQUIRY_COMPL 0x0000000000000001 +#define NG_HCI_EVMSK_INQUIRY_RESULT 0x0000000000000002 +#define NG_HCI_EVMSK_CON_COMPL 0x0000000000000004 +#define NG_HCI_EVMSK_CON_REQ 0x0000000000000008 +#define NG_HCI_EVMSK_DISCON_COMPL 0x0000000000000010 +#define NG_HCI_EVMSK_AUTH_COMPL 0x0000000000000020 +#define NG_HCI_EVMSK_REMOTE_NAME_REQ_COMPL 0x0000000000000040 +#define NG_HCI_EVMSK_ENCRYPTION_CHANGE 0x0000000000000080 +#define NG_HCI_EVMSK_CHANGE_CON_LINK_KEY_COMPL 0x0000000000000100 +#define NG_HCI_EVMSK_MASTER_LINK_KEY_COMPL 0x0000000000000200 +#define NG_HCI_EVMSK_READ_REMOTE_FEATURES_COMPL 0x0000000000000400 +#define NG_HCI_EVMSK_READ_REMOTE_VER_INFO_COMPL 0x0000000000000800 +#define NG_HCI_EVMSK_QOS_SETUP_COMPL 0x0000000000001000 +#define NG_HCI_EVMSK_COMMAND_COMPL 0x0000000000002000 +#define NG_HCI_EVMSK_COMMAND_STATUS 0x0000000000004000 +#define NG_HCI_EVMSK_HARDWARE_ERROR 0x0000000000008000 +#define NG_HCI_EVMSK_FLUSH_OCCUR 0x0000000000010000 +#define NG_HCI_EVMSK_ROLE_CHANGE 0x0000000000020000 +#define NG_HCI_EVMSK_NUM_COMPL_PKTS 0x0000000000040000 +#define NG_HCI_EVMSK_MODE_CHANGE 0x0000000000080000 +#define NG_HCI_EVMSK_RETURN_LINK_KEYS 0x0000000000100000 +#define NG_HCI_EVMSK_PIN_CODE_REQ 0x0000000000200000 +#define NG_HCI_EVMSK_LINK_KEY_REQ 0x0000000000400000 +#define NG_HCI_EVMSK_LINK_KEY_NOTIFICATION 0x0000000000800000 +#define NG_HCI_EVMSK_LOOPBACK_COMMAND 0x0000000001000000 +#define NG_HCI_EVMSK_DATA_BUFFER_OVERFLOW 0x0000000002000000 +#define NG_HCI_EVMSK_MAX_SLOT_CHANGE 0x0000000004000000 +#define NG_HCI_EVMSK_READ_CLOCK_OFFSET_COMLETE 0x0000000008000000 +#define NG_HCI_EVMSK_CON_PKT_TYPE_CHANGED 0x0000000010000000 +#define NG_HCI_EVMSK_QOS_VIOLATION 0x0000000020000000 +#define NG_HCI_EVMSK_PAGE_SCAN_MODE_CHANGE 0x0000000040000000 +#define NG_HCI_EVMSK_PAGE_SCAN_REP_MODE_CHANGE 0x0000000080000000 +/* 0x0000000100000000 - 0x8000000000000000 - reserved for future use */ + +/* Filter types */ +#define NG_HCI_FILTER_TYPE_NONE 0x00 +#define NG_HCI_FILTER_TYPE_INQUIRY_RESULT 0x01 +#define NG_HCI_FILTER_TYPE_CON_SETUP 0x02 +/* 0x03 - 0xFF - reserved for future use */ + +/* Filter condition types for NG_HCI_FILTER_TYPE_INQUIRY_RESULT */ +#define NG_HCI_FILTER_COND_INQUIRY_NEW_UNIT 0x00 +#define NG_HCI_FILTER_COND_INQUIRY_UNIT_CLASS 0x01 +#define NG_HCI_FILTER_COND_INQUIRY_BDADDR 0x02 +/* 0x03 - 0xFF - reserved for future use */ + +/* Filter condition types for NG_HCI_FILTER_TYPE_CON_SETUP */ +#define NG_HCI_FILTER_COND_CON_ANY_UNIT 0x00 +#define NG_HCI_FILTER_COND_CON_UNIT_CLASS 0x01 +#define NG_HCI_FILTER_COND_CON_BDADDR 0x02 +/* 0x03 - 0xFF - reserved for future use */ + +/* Xmit level types */ +#define NG_HCI_XMIT_LEVEL_CURRENT 0x00 +#define NG_HCI_XMIT_LEVEL_MAXIMUM 0x01 +/* 0x02 - 0xFF - reserved for future use */ + +/* Host to Host Controller flow control */ +#define NG_HCI_H2HC_FLOW_CONTROL_NONE 0x00 +#define NG_HCI_H2HC_FLOW_CONTROL_ACL 0x01 +#define NG_HCI_H2HC_FLOW_CONTROL_SCO 0x02 +#define NG_HCI_H2HC_FLOW_CONTROL_BOTH 0x03 /* ACL and SCO */ +/* 0x04 - 0xFF - reserved future use */ + +/* Country codes */ +#define NG_HCI_COUNTRY_CODE_NAM_EUR_JP 0x00 +#define NG_HCI_COUNTRY_CODE_FRANCE 0x01 +/* 0x02 - 0xFF - reserved future use */ + +/* Loopback modes */ +#define NG_HCI_LOOPBACK_NONE 0x00 +#define NG_HCI_LOOPBACK_LOCAL 0x01 +#define NG_HCI_LOOPBACK_REMOTE 0x02 +/* 0x03 - 0xFF - reserved future use */ + +/************************************************************************** + ************************************************************************** + ** Link level defines, headers and types + ************************************************************************** + **************************************************************************/ + +/* + * Macro(s) to combine OpCode and extract OGF (OpCode Group Field) + * and OCF (OpCode Command Field) from OpCode. + */ + +#define NG_HCI_OPCODE(gf,cf) ((((gf) & 0x3f) << 10) | ((cf) & 0x3ff)) +#define NG_HCI_OCF(op) ((op) & 0x3ff) +#define NG_HCI_OGF(op) (((op) >> 10) & 0x3f) + +/* + * Marco(s) to extract/combine connection handle, BC (Broadcast) and + * PB (Packet boundary) flags. + */ + +#define NG_HCI_CON_HANDLE(h) ((h) & 0x0fff) +#define NG_HCI_PB_FLAG(h) (((h) & 0x3000) >> 12) +#define NG_HCI_BC_FLAG(h) (((h) & 0xc000) >> 14) +#define NG_HCI_MK_CON_HANDLE(h, pb, bc) \ + (((h) & 0x0fff) | (((pb) & 3) << 12) | (((bc) & 3) << 14)) + +/* PB flag values */ + /* 00 - reserved for future use */ +#define NG_HCI_PACKET_FRAGMENT 0x1 +#define NG_HCI_PACKET_START 0x2 + /* 11 - reserved for future use */ + +/* BC flag values */ +#define NG_HCI_POINT2POINT 0x0 /* only Host controller to Host */ +#define NG_HCI_BROADCAST_ACTIVE 0x1 /* both directions */ +#define NG_HCI_BROADCAST_PICONET 0x2 /* both directions */ + /* 11 - reserved for future use */ + +/* HCI command packet header */ +#define NG_HCI_CMD_PKT 0x01 +#define NG_HCI_CMD_PKT_SIZE 0xff /* without header */ +typedef struct { + u_int8_t type; /* MUST be 0x1 */ + u_int16_t opcode; /* OpCode */ + u_int8_t length; /* parameter(s) length in bytes */ +} __attribute__ ((packed)) ng_hci_cmd_pkt_t; + +/* ACL data packet header */ +#define NG_HCI_ACL_DATA_PKT 0x02 +#define NG_HCI_ACL_PKT_SIZE 0xffff /* without header */ +typedef struct { + u_int8_t type; /* MUST be 0x2 */ + u_int16_t con_handle; /* connection handle + PB + BC flags */ + u_int16_t length; /* payload length in bytes */ +} __attribute__ ((packed)) ng_hci_acldata_pkt_t; + +/* SCO data packet header */ +#define NG_HCI_SCO_DATA_PKT 0x03 +#define NG_HCI_SCO_PKT_SIZE 0xff /* without header */ +typedef struct { + u_int8_t type; /* MUST be 0x3 */ + u_int16_t con_handle; /* connection handle + reserved bits */ + u_int8_t length; /* payload length in bytes */ +} __attribute__ ((packed)) ng_hci_scodata_pkt_t; + +/* HCI event packet header */ +#define NG_HCI_EVENT_PKT 0x04 +#define NG_HCI_EVENT_PKT_SIZE 0xff /* without header */ +typedef struct { + u_int8_t type; /* MUST be 0x4 */ + u_int8_t event; /* event */ + u_int8_t length; /* parameter(s) length in bytes */ +} __attribute__ ((packed)) ng_hci_event_pkt_t; + +/* Bluetooth unit address */ +typedef struct { + u_int8_t b[NG_HCI_BDADDR_SIZE]; +} __attribute__ ((packed)) bdaddr_t; +typedef bdaddr_t * bdaddr_p; + +/* Any BD_ADDR. Note: This is actually 7 bytes (count '\0' terminator) */ +#define NG_HCI_BDADDR_ANY ((bdaddr_p) "\000\000\000\000\000\000") + +/* HCI status return parameter */ +typedef struct { + u_int8_t status; /* 0x00 - success */ +} __attribute__ ((packed)) ng_hci_status_rp; + +/************************************************************************** + ************************************************************************** + ** Upper layer protocol interface. LP_xxx event parameters + ************************************************************************** + **************************************************************************/ + +/* Connection Request Event */ +#define NGM_HCI_LP_CON_REQ 1 /* Upper -> HCI */ +typedef struct { + u_int16_t link_type; /* type of connection */ + bdaddr_t bdaddr; /* remote unit address */ +} ng_hci_lp_con_req_ep; + +/* + * XXX XXX XXX + * + * NOTE: This request is not defined by Bluetooth specification, + * but i find it useful :) + */ +#define NGM_HCI_LP_DISCON_REQ 2 /* Upper -> HCI */ +typedef struct { + u_int16_t con_handle; /* connection handle */ + u_int16_t reason; /* reason to disconnect (only low byte) */ +} ng_hci_lp_discon_req_ep; + +/* Connection Confirmation Event */ +#define NGM_HCI_LP_CON_CFM 3 /* HCI -> Upper */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t link_type; /* link type */ + u_int16_t con_handle; /* con_handle */ + bdaddr_t bdaddr; /* remote unit address */ +} ng_hci_lp_con_cfm_ep; + +/* Connection Indication Event */ +#define NGM_HCI_LP_CON_IND 4 /* HCI -> Upper */ +typedef struct { + u_int8_t link_type; /* link type */ + u_int8_t uclass[NG_HCI_CLASS_SIZE]; /* unit class */ + bdaddr_t bdaddr; /* remote unit address */ +} ng_hci_lp_con_ind_ep; + +/* Connection Response Event */ +#define NGM_HCI_LP_CON_RSP 5 /* Upper -> HCI */ +typedef struct { + u_int8_t status; /* 0x00 - accept connection */ + u_int8_t link_type; /* link type */ + bdaddr_t bdaddr; /* remote unit address */ +} ng_hci_lp_con_rsp_ep; + +/* Disconnection Indication Event */ +#define NGM_HCI_LP_DISCON_IND 6 /* HCI -> Upper */ +typedef struct { + u_int8_t reason; /* reason to disconnect (only low byte) */ + u_int8_t link_type; /* link type */ + u_int16_t con_handle; /* connection handle */ +} ng_hci_lp_discon_ind_ep; + +/* QoS Setup Request Event */ +#define NGM_HCI_LP_QOS_REQ 7 /* Upper -> HCI */ +typedef struct { + u_int16_t con_handle; /* connection handle */ + u_int8_t flags; /* reserved */ + u_int8_t service_type; /* service type */ + u_int32_t token_rate; /* bytes/sec */ + u_int32_t peak_bandwidth; /* bytes/sec */ + u_int32_t latency; /* msec */ + u_int32_t delay_variation; /* msec */ +} ng_hci_lp_qos_req_ep; + +/* QoS Conformition Event */ +#define NGM_HCI_LP_QOS_CFM 8 /* HCI -> Upper */ +typedef struct { + u_int16_t status; /* 0x00 - success (only low byte) */ + u_int16_t con_handle; /* connection handle */ +} ng_hci_lp_qos_cfm_ep; + +/* QoS Violation Indication Event */ +#define NGM_HCI_LP_QOS_IND 9 /* HCI -> Upper */ +typedef struct { + u_int16_t con_handle; /* connection handle */ +} ng_hci_lp_qos_ind_ep; + +/************************************************************************** + ************************************************************************** + ** HCI node command/event parameters + ************************************************************************** + **************************************************************************/ + +/* Debug levels */ +#define NG_HCI_ALERT_LEVEL 1 +#define NG_HCI_ERR_LEVEL 2 +#define NG_HCI_WARN_LEVEL 3 +#define NG_HCI_INFO_LEVEL 4 + +/* Unit states */ +#define NG_HCI_UNIT_CONNECTED (1 << 0) +#define NG_HCI_UNIT_INITED (1 << 1) +#define NG_HCI_UNIT_READY (NG_HCI_UNIT_CONNECTED|NG_HCI_UNIT_INITED) +#define NG_HCI_UNIT_COMMAND_PENDING (1 << 2) + +/* Connection state */ +#define NG_HCI_CON_CLOSED 0 /* connection closed */ +#define NG_HCI_CON_W4_LP_CON_RSP 1 /* wait for LP_ConnectRsp */ +#define NG_HCI_CON_W4_CONN_COMPLETE 2 /* wait for Connection_Complete evt */ +#define NG_HCI_CON_OPEN 3 /* connection open */ + +/* Get HCI node (unit) state (see states above) */ +#define NGM_HCI_NODE_GET_STATE 100 /* HCI -> User */ +typedef u_int16_t ng_hci_node_state_ep; + +/* Turn on "inited" bit */ +#define NGM_HCI_NODE_INIT 101 /* User -> HCI */ +/* No parameters */ + +/* Get/Set node debug level (see debug levels above) */ +#define NGM_HCI_NODE_GET_DEBUG 102 /* HCI -> User */ +#define NGM_HCI_NODE_SET_DEBUG 103 /* User -> HCI */ +typedef u_int16_t ng_hci_node_debug_ep; + +/* Get node buffer info */ +#define NGM_HCI_NODE_GET_BUFFER 104 /* HCI -> User */ +typedef struct { + u_int8_t cmd_free; /* number of free command packets */ + u_int8_t sco_size; /* max. size of SCO packet */ + u_int16_t sco_pkts; /* number of SCO packets */ + u_int16_t sco_free; /* number of free SCO packets */ + u_int16_t acl_size; /* max. size of ACL packet */ + u_int16_t acl_pkts; /* number of ACL packets */ + u_int16_t acl_free; /* number of free ACL packets */ +} ng_hci_node_buffer_ep; + +/* Get BDADDR */ +#define NGM_HCI_NODE_GET_BDADDR 105 /* HCI -> User */ +/* bdaddr_t -- BDADDR */ + +/* Get features */ +#define NGM_HCI_NODE_GET_FEATURES 106 /* HCI -> User */ +/* features[NG_HCI_FEATURES_SIZE] -- features */ + +#define NGM_HCI_NODE_GET_STAT 107 /* HCI -> User */ +typedef struct { + u_int32_t cmd_sent; /* number of HCI commands sent */ + u_int32_t evnt_recv; /* number of HCI events received */ + u_int32_t acl_recv; /* number of ACL packets received */ + u_int32_t acl_sent; /* number of ACL packets sent */ + u_int32_t sco_recv; /* number of SCO packets received */ + u_int32_t sco_sent; /* number of SCO packets sent */ + u_int32_t bytes_recv; /* total number of bytes received */ + u_int32_t bytes_sent; /* total number of bytes sent */ +} ng_hci_node_stat_ep; + +#define NGM_HCI_NODE_RESET_STAT 108 /* User -> HCI */ +/* No parameters */ + +#define NGM_HCI_NODE_FLUSH_NEIGHBOR_CACHE 109 /* User -> HCI */ + +#define NGM_HCI_NODE_GET_NEIGHBOR_CACHE 110 /* HCI -> User */ +typedef struct { + u_int32_t num_entries; /* number of entries */ +} ng_hci_node_get_neighbor_cache_ep; + +typedef struct { + u_int16_t page_scan_rep_mode; /* page rep scan mode */ + u_int16_t page_scan_mode; /* page scan mode */ + u_int16_t clock_offset; /* clock offset */ + bdaddr_t bdaddr; /* bdaddr */ + u_int8_t features[NG_HCI_FEATURES_SIZE]; /* features */ +} ng_hci_node_neighbor_cache_entry_ep; + +#define NG_HCI_MAX_NEIGHBOR_NUM \ + ((0xffff - sizeof(ng_hci_node_get_neighbor_cache_ep))/sizeof(ng_hci_node_neighbor_cache_entry_ep)) + +#define NGM_HCI_NODE_GET_CON_LIST 111 /* HCI -> User */ +typedef struct { + u_int32_t num_connections; /* number of connections */ +} ng_hci_node_con_list_ep; + +typedef struct { + u_int8_t link_type; /* ACL or SCO */ + u_int8_t encryption_mode; /* none, p2p, ... */ + u_int8_t mode; /* ACTIVE, HOLD ... */ + u_int8_t role; /* MASTER/SLAVE */ + u_int16_t state; /* connection state */ + u_int16_t reserved; /* place holder */ + u_int16_t pending; /* number of pending packets */ + u_int16_t queue_len; /* number of packets in queue */ + u_int16_t con_handle; /* connection handle */ + bdaddr_t bdaddr; /* remote bdaddr */ +} ng_hci_node_con_ep; + +#define NG_HCI_MAX_CON_NUM \ + ((0xffff - sizeof(ng_hci_node_con_list_ep))/sizeof(ng_hci_node_con_ep)) + +#define NGM_HCI_NODE_UP 112 /* HCI -> Upper */ +typedef struct { + u_int16_t pkt_size; /* max. ACL/SCO packet size (w/out header) */ + u_int16_t num_pkts; /* ACL/SCO packet queue size */ + u_int16_t reserved; /* place holder */ + bdaddr_t bdaddr; /* bdaddr */ +} ng_hci_node_up_ep; + +#define NGM_HCI_SYNC_CON_QUEUE 113 /* HCI -> Upper */ +typedef struct { + u_int16_t con_handle; /* connection handle */ + u_int16_t completed; /* number of completed packets */ +} ng_hci_sync_con_queue_ep; + +#define NGM_HCI_NODE_GET_LINK_POLICY_SETTINGS_MASK 114 /* HCI -> User */ +#define NGM_HCI_NODE_SET_LINK_POLICY_SETTINGS_MASK 115 /* User -> HCI */ +typedef u_int16_t ng_hci_node_link_policy_mask_ep; + +#define NGM_HCI_NODE_GET_PACKET_MASK 116 /* HCI -> User */ +#define NGM_HCI_NODE_SET_PACKET_MASK 117 /* User -> HCI */ +typedef u_int16_t ng_hci_node_packet_mask_ep; + +#define NGM_HCI_NODE_GET_ROLE_SWITCH 118 /* HCI -> User */ +#define NGM_HCI_NODE_SET_ROLE_SWITCH 119 /* User -> HCI */ +typedef u_int16_t ng_hci_node_role_switch_ep; + +#define NGM_HCI_NODE_LIST_NAMES 200 /* HCI -> User */ + +/************************************************************************** + ************************************************************************** + ** Link control commands and return parameters + ************************************************************************** + **************************************************************************/ + +#define NG_HCI_OGF_LINK_CONTROL 0x01 /* OpCode Group Field */ + +#define NG_HCI_OCF_INQUIRY 0x0001 +typedef struct { + u_int8_t lap[NG_HCI_LAP_SIZE]; /* LAP */ + u_int8_t inquiry_length; /* (N x 1.28) sec */ + u_int8_t num_responses; /* Max. # of responses before halted */ +} __attribute__ ((packed)) ng_hci_inquiry_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_INQUIRY_CANCEL 0x0002 +/* No command parameter(s) */ +typedef ng_hci_status_rp ng_hci_inquiry_cancel_rp; + +#define NG_HCI_OCF_PERIODIC_INQUIRY 0x0003 +typedef struct { + u_int16_t max_period_length; /* Max. and min. amount of time */ + u_int16_t min_period_length; /* between consecutive inquiries */ + u_int8_t lap[NG_HCI_LAP_SIZE]; /* LAP */ + u_int8_t inquiry_length; /* (inquiry_length * 1.28) sec */ + u_int8_t num_responses; /* Max. # of responses */ +} __attribute__ ((packed)) ng_hci_periodic_inquiry_cp; + +typedef ng_hci_status_rp ng_hci_periodic_inquiry_rp; + +#define NG_HCI_OCF_EXIT_PERIODIC_INQUIRY 0x0004 +/* No command parameter(s) */ +typedef ng_hci_status_rp ng_hci_exit_periodic_inquiry_rp; + +#define NG_HCI_OCF_CREATE_CON 0x0005 +typedef struct { + bdaddr_t bdaddr; /* destination address */ + u_int16_t pkt_type; /* packet type */ + u_int8_t page_scan_rep_mode; /* page scan repetition mode */ + u_int8_t page_scan_mode; /* page scan mode */ + u_int16_t clock_offset; /* clock offset */ + u_int8_t accept_role_switch; /* accept role switch? 0x00 - no */ +} __attribute__ ((packed)) ng_hci_create_con_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_DISCON 0x0006 +typedef struct { + u_int16_t con_handle; /* connection handle */ + u_int8_t reason; /* reason to disconnect */ +} __attribute__ ((packed)) ng_hci_discon_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_ADD_SCO_CON 0x0007 +typedef struct { + u_int16_t con_handle; /* connection handle */ + u_int16_t pkt_type; /* packet type */ +} __attribute__ ((packed)) ng_hci_add_sco_con_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_ACCEPT_CON 0x0009 +typedef struct { + bdaddr_t bdaddr; /* address of unit to be connected */ + u_int8_t role; /* connection role */ +} __attribute__ ((packed)) ng_hci_accept_con_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_REJECT_CON 0x000a +typedef struct { + bdaddr_t bdaddr; /* remote address */ + u_int8_t reason; /* reason to reject */ +} __attribute__ ((packed)) ng_hci_reject_con_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_LINK_KEY_REP 0x000b +typedef struct { + bdaddr_t bdaddr; /* remote address */ + u_int8_t key[NG_HCI_KEY_SIZE]; /* key */ +} __attribute__ ((packed)) ng_hci_link_key_rep_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + bdaddr_t bdaddr; /* unit address */ +} __attribute__ ((packed)) ng_hci_link_key_rep_rp; + +#define NG_HCI_OCF_LINK_KEY_NEG_REP 0x000c +typedef struct { + bdaddr_t bdaddr; /* remote address */ +} __attribute__ ((packed)) ng_hci_link_key_neg_rep_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + bdaddr_t bdaddr; /* unit address */ +} __attribute__ ((packed)) ng_hci_link_key_neg_rep_rp; + +#define NG_HCI_OCF_PIN_CODE_REP 0x000d +typedef struct { + bdaddr_t bdaddr; /* remote address */ + u_int8_t pin_size; /* pin code length (in bytes) */ + u_int8_t pin[NG_HCI_PIN_SIZE]; /* pin code */ +} __attribute__ ((packed)) ng_hci_pin_code_rep_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + bdaddr_t bdaddr; /* unit address */ +} __attribute__ ((packed)) ng_hci_pin_code_rep_rp; + +#define NG_HCI_OCF_PIN_CODE_NEG_REP 0x000e +typedef struct { + bdaddr_t bdaddr; /* remote address */ +} __attribute__ ((packed)) ng_hci_pin_code_neg_rep_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + bdaddr_t bdaddr; /* unit address */ +} __attribute__ ((packed)) ng_hci_pin_code_neg_rep_rp; + +#define NG_HCI_OCF_CHANGE_CON_PKT_TYPE 0x000f +typedef struct { + u_int16_t con_handle; /* connection handle */ + u_int16_t pkt_type; /* packet type */ +} __attribute__ ((packed)) ng_hci_change_con_pkt_type_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_AUTH_REQ 0x0011 +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_auth_req_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_SET_CON_ENCRYPTION 0x0013 +typedef struct { + u_int16_t con_handle; /* connection handle */ + u_int8_t encryption_enable; /* 0x00 - disable, 0x01 - enable */ +} __attribute__ ((packed)) ng_hci_set_con_encryption_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_CHANGE_CON_LINK_KEY 0x0015 +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_change_con_link_key_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_MASTER_LINK_KEY 0x0017 +typedef struct { + u_int8_t key_flag; /* key flag */ +} __attribute__ ((packed)) ng_hci_master_link_key_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_REMOTE_NAME_REQ 0x0019 +typedef struct { + bdaddr_t bdaddr; /* remote address */ + u_int8_t page_scan_rep_mode; /* page scan repetition mode */ + u_int8_t page_scan_mode; /* page scan mode */ + u_int16_t clock_offset; /* clock offset */ +} __attribute__ ((packed)) ng_hci_remote_name_req_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_READ_REMOTE_FEATURES 0x001b +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_read_remote_features_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_READ_REMOTE_VER_INFO 0x001d +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_read_remote_ver_info_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_READ_CLOCK_OFFSET 0x001f +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_read_clock_offset_cp; +/* No return parameter(s) */ + +/************************************************************************** + ************************************************************************** + ** Link policy commands and return parameters + ************************************************************************** + **************************************************************************/ + +#define NG_HCI_OGF_LINK_POLICY 0x02 /* OpCode Group Field */ + +#define NG_HCI_OCF_HOLD_MODE 0x0001 +typedef struct { + u_int16_t con_handle; /* connection handle */ + u_int16_t max_interval; /* (max_interval * 0.625) msec */ + u_int16_t min_interval; /* (max_interval * 0.625) msec */ +} __attribute__ ((packed)) ng_hci_hold_mode_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_SNIFF_MODE 0x0003 +typedef struct { + u_int16_t con_handle; /* connection handle */ + u_int16_t max_interval; /* (max_interval * 0.625) msec */ + u_int16_t min_interval; /* (max_interval * 0.625) msec */ + u_int16_t attempt; /* (2 * attempt - 1) * 0.625 msec */ + u_int16_t timeout; /* (2 * attempt - 1) * 0.625 msec */ +} __attribute__ ((packed)) ng_hci_sniff_mode_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_EXIT_SNIFF_MODE 0x0004 +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_exit_sniff_mode_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_PARK_MODE 0x0005 +typedef struct { + u_int16_t con_handle; /* connection handle */ + u_int16_t max_interval; /* (max_interval * 0.625) msec */ + u_int16_t min_interval; /* (max_interval * 0.625) msec */ +} __attribute__ ((packed)) ng_hci_park_mode_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_EXIT_PARK_MODE 0x0006 +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_exit_park_mode_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_QOS_SETUP 0x0007 +typedef struct { + u_int16_t con_handle; /* connection handle */ + u_int8_t flags; /* reserved for future use */ + u_int8_t service_type; /* service type */ + u_int32_t token_rate; /* bytes per second */ + u_int32_t peak_bandwidth; /* bytes per second */ + u_int32_t latency; /* microseconds */ + u_int32_t delay_variation; /* microseconds */ +} __attribute__ ((packed)) ng_hci_qos_setup_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_ROLE_DISCOVERY 0x0009 +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_role_discovery_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ + u_int8_t role; /* role for the connection handle */ +} __attribute__ ((packed)) ng_hci_role_discovery_rp; + +#define NG_HCI_OCF_SWITCH_ROLE 0x000b +typedef struct { + bdaddr_t bdaddr; /* remote address */ + u_int8_t role; /* new local role */ +} __attribute__ ((packed)) ng_hci_switch_role_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_READ_LINK_POLICY_SETTINGS 0x000c +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_read_link_policy_settings_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ + u_int16_t settings; /* link policy settings */ +} __attribute__ ((packed)) ng_hci_read_link_policy_settings_rp; + +#define NG_HCI_OCF_WRITE_LINK_POLICY_SETTINGS 0x000d +typedef struct { + u_int16_t con_handle; /* connection handle */ + u_int16_t settings; /* link policy settings */ +} __attribute__ ((packed)) ng_hci_write_link_policy_settings_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_write_link_policy_settings_rp; + +/************************************************************************** + ************************************************************************** + ** Host controller and baseband commands and return parameters + ************************************************************************** + **************************************************************************/ + +#define NG_HCI_OGF_HC_BASEBAND 0x03 /* OpCode Group Field */ + +#define NG_HCI_OCF_SET_EVENT_MASK 0x0001 +typedef struct { + u_int8_t event_mask[NG_HCI_EVENT_MASK_SIZE]; /* event_mask */ +} __attribute__ ((packed)) ng_hci_set_event_mask_cp; + +typedef ng_hci_status_rp ng_hci_set_event_mask_rp; + +#define NG_HCI_OCF_RESET 0x0003 +/* No command parameter(s) */ +typedef ng_hci_status_rp ng_hci_reset_rp; + +#define NG_HCI_OCF_SET_EVENT_FILTER 0x0005 +typedef struct { + u_int8_t filter_type; /* filter type */ + u_int8_t filter_condition_type; /* filter condition type */ + u_int8_t condition[0]; /* conditions - variable size */ +} __attribute__ ((packed)) ng_hci_set_event_filter_cp; + +typedef ng_hci_status_rp ng_hci_set_event_filter_rp; + +#define NG_HCI_OCF_FLUSH 0x0008 +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_flush_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_flush_rp; + +#define NG_HCI_OCF_READ_PIN_TYPE 0x0009 +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t pin_type; /* PIN type */ +} __attribute__ ((packed)) ng_hci_read_pin_type_rp; + +#define NG_HCI_OCF_WRITE_PIN_TYPE 0x000a +typedef struct { + u_int8_t pin_type; /* PIN type */ +} __attribute__ ((packed)) ng_hci_write_pin_type_cp; + +typedef ng_hci_status_rp ng_hci_write_pin_type_rp; + +#define NG_HCI_OCF_CREATE_NEW_UNIT_KEY 0x000b +/* No command parameter(s) */ +typedef ng_hci_status_rp ng_hci_create_new_unit_key_rp; + +#define NG_HCI_OCF_READ_STORED_LINK_KEY 0x000d +typedef struct { + bdaddr_t bdaddr; /* address */ + u_int8_t read_all; /* read all keys? 0x01 - yes */ +} __attribute__ ((packed)) ng_hci_read_stored_link_key_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t max_num_keys; /* Max. number of keys */ + u_int16_t num_keys_read; /* Number of stored keys */ +} __attribute__ ((packed)) ng_hci_read_stored_link_key_rp; + +#define NG_HCI_OCF_WRITE_STORED_LINK_KEY 0x0011 +typedef struct { + u_int8_t num_keys_write; /* # of keys to write */ +/* these are repeated "num_keys_write" times + bdaddr_t bdaddr; --- remote address(es) + u_int8_t key[NG_HCI_KEY_SIZE]; --- key(s) */ +} __attribute__ ((packed)) ng_hci_write_stored_link_key_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t num_keys_written; /* # of keys successfully written */ +} __attribute__ ((packed)) ng_hci_write_stored_link_key_rp; + +#define NG_HCI_OCF_DELETE_STORED_LINK_KEY 0x0012 +typedef struct { + bdaddr_t bdaddr; /* address */ + u_int8_t delete_all; /* delete all keys? 0x01 - yes */ +} __attribute__ ((packed)) ng_hci_delete_stored_link_key_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t num_keys_deleted; /* Number of keys deleted */ +} __attribute__ ((packed)) ng_hci_delete_stored_link_key_rp; + +#define NG_HCI_OCF_CHANGE_LOCAL_NAME 0x0013 +typedef struct { + char name[NG_HCI_UNIT_NAME_SIZE]; /* new unit name */ +} __attribute__ ((packed)) ng_hci_change_local_name_cp; + +typedef ng_hci_status_rp ng_hci_change_local_name_rp; + +#define NG_HCI_OCF_READ_LOCAL_NAME 0x0014 +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + char name[NG_HCI_UNIT_NAME_SIZE]; /* unit name */ +} __attribute__ ((packed)) ng_hci_read_local_name_rp; + +#define NG_HCI_OCF_READ_CON_ACCEPT_TIMO 0x0015 +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t timeout; /* (timeout * 0.625) msec */ +} __attribute__ ((packed)) ng_hci_read_con_accept_timo_rp; + +#define NG_HCI_OCF_WRITE_CON_ACCEPT_TIMO 0x0016 +typedef struct { + u_int16_t timeout; /* (timeout * 0.625) msec */ +} __attribute__ ((packed)) ng_hci_write_con_accept_timo_cp; + +typedef ng_hci_status_rp ng_hci_write_con_accept_timo_rp; + +#define NG_HCI_OCF_READ_PAGE_TIMO 0x0017 +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t timeout; /* (timeout * 0.625) msec */ +} __attribute__ ((packed)) ng_hci_read_page_timo_rp; + +#define NG_HCI_OCF_WRITE_PAGE_TIMO 0x0018 +typedef struct { + u_int16_t timeout; /* (timeout * 0.625) msec */ +} __attribute__ ((packed)) ng_hci_write_page_timo_cp; + +typedef ng_hci_status_rp ng_hci_write_page_timo_rp; + +#define NG_HCI_OCF_READ_SCAN_ENABLE 0x0019 +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t scan_enable; /* Scan enable */ +} __attribute__ ((packed)) ng_hci_read_scan_enable_rp; + +#define NG_HCI_OCF_WRITE_SCAN_ENABLE 0x001a +typedef struct { + u_int8_t scan_enable; /* Scan enable */ +} __attribute__ ((packed)) ng_hci_write_scan_enable_cp; + +typedef ng_hci_status_rp ng_hci_write_scan_enable_rp; + +#define NG_HCI_OCF_READ_PAGE_SCAN_ACTIVITY 0x001b +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t page_scan_interval; /* interval * 0.625 msec */ + u_int16_t page_scan_window; /* window * 0.625 msec */ +} __attribute__ ((packed)) ng_hci_read_page_scan_activity_rp; + +#define NG_HCI_OCF_WRITE_PAGE_SCAN_ACTIVITY 0x001c +typedef struct { + u_int16_t page_scan_interval; /* interval * 0.625 msec */ + u_int16_t page_scan_window; /* window * 0.625 msec */ +} __attribute__ ((packed)) ng_hci_write_page_scan_activity_cp; + +typedef ng_hci_status_rp ng_hci_write_page_scan_activity_rp; + +#define NG_HCI_OCF_READ_INQUIRY_SCAN_ACTIVITY 0x001d +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t inquiry_scan_interval; /* interval * 0.625 msec */ + u_int16_t inquiry_scan_window; /* window * 0.625 msec */ +} __attribute__ ((packed)) ng_hci_read_inquiry_scan_activity_rp; + +#define NG_HCI_OCF_WRITE_INQUIRY_SCAN_ACTIVITY 0x001e +typedef struct { + u_int16_t inquiry_scan_interval; /* interval * 0.625 msec */ + u_int16_t inquiry_scan_window; /* window * 0.625 msec */ +} __attribute__ ((packed)) ng_hci_write_inquiry_scan_activity_cp; + +typedef ng_hci_status_rp ng_hci_write_inquiry_scan_activity_rp; + +#define NG_HCI_OCF_READ_AUTH_ENABLE 0x001f +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t auth_enable; /* 0x01 - enabled */ +} __attribute__ ((packed)) ng_hci_read_auth_enable_rp; + +#define NG_HCI_OCF_WRITE_AUTH_ENABLE 0x0020 +typedef struct { + u_int8_t auth_enable; /* 0x01 - enabled */ +} __attribute__ ((packed)) ng_hci_write_auth_enable_cp; + +typedef ng_hci_status_rp ng_hci_write_auth_enable_rp; + +#define NG_HCI_OCF_READ_ENCRYPTION_MODE 0x0021 +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t encryption_mode; /* encryption mode */ +} __attribute__ ((packed)) ng_hci_read_encryption_mode_rp; + +#define NG_HCI_OCF_WRITE_ENCRYPTION_MODE 0x0022 +typedef struct { + u_int8_t encryption_mode; /* encryption mode */ +} __attribute__ ((packed)) ng_hci_write_encryption_mode_cp; + +typedef ng_hci_status_rp ng_hci_write_encryption_mode_rp; + +#define NG_HCI_OCF_READ_UNIT_CLASS 0x0023 +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t uclass[NG_HCI_CLASS_SIZE]; /* unit class */ +} __attribute__ ((packed)) ng_hci_read_unit_class_rp; + +#define NG_HCI_OCF_WRITE_UNIT_CLASS 0x0024 +typedef struct { + u_int8_t uclass[NG_HCI_CLASS_SIZE]; /* unit class */ +} __attribute__ ((packed)) ng_hci_write_unit_class_cp; + +typedef ng_hci_status_rp ng_hci_write_unit_class_rp; + +#define NG_HCI_OCF_READ_VOICE_SETTINGS 0x0025 +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t settings; /* voice settings */ +} __attribute__ ((packed)) ng_hci_read_voice_settings_rp; + +#define NG_HCI_OCF_WRITE_VOICE_SETTINGS 0x0026 +typedef struct { + u_int16_t settings; /* voice settings */ +} __attribute__ ((packed)) ng_hci_write_voice_settings_cp; + +typedef ng_hci_status_rp ng_hci_write_voice_settings_rp; + +#define NG_HCI_OCF_READ_AUTO_FLUSH_TIMO 0x0027 +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_read_auto_flush_timo_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ + u_int16_t timeout; /* 0x00 - no flush, timeout * 0.625 msec */ +} __attribute__ ((packed)) ng_hci_read_auto_flush_timo_rp; + +#define NG_HCI_OCF_WRITE_AUTO_FLUSH_TIMO 0x0028 +typedef struct { + u_int16_t con_handle; /* connection handle */ + u_int16_t timeout; /* 0x00 - no flush, timeout * 0.625 msec */ +} __attribute__ ((packed)) ng_hci_write_auto_flush_timo_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_write_auto_flush_timo_rp; + +#define NG_HCI_OCF_READ_NUM_BROADCAST_RETRANS 0x0029 +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t counter; /* number of broadcast retransmissions */ +} __attribute__ ((packed)) ng_hci_read_num_broadcast_retrans_rp; + +#define NG_HCI_OCF_WRITE_NUM_BROADCAST_RETRANS 0x002a +typedef struct { + u_int8_t counter; /* number of broadcast retransmissions */ +} __attribute__ ((packed)) ng_hci_write_num_broadcast_retrans_cp; + +typedef ng_hci_status_rp ng_hci_write_num_broadcast_retrans_rp; + +#define NG_HCI_OCF_READ_HOLD_MODE_ACTIVITY 0x002b +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t hold_mode_activity; /* Hold mode activities */ +} __attribute__ ((packed)) ng_hci_read_hold_mode_activity_rp; + +#define NG_HCI_OCF_WRITE_HOLD_MODE_ACTIVITY 0x002c +typedef struct { + u_int8_t hold_mode_activity; /* Hold mode activities */ +} __attribute__ ((packed)) ng_hci_write_hold_mode_activity_cp; + +typedef ng_hci_status_rp ng_hci_write_hold_mode_activity_rp; + +#define NG_HCI_OCF_READ_XMIT_LEVEL 0x002d +typedef struct { + u_int16_t con_handle; /* connection handle */ + u_int8_t type; /* Xmit level type */ +} __attribute__ ((packed)) ng_hci_read_xmit_level_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ + char level; /* -30 <= level <= 30 dBm */ +} __attribute__ ((packed)) ng_hci_read_xmit_level_rp; + +#define NG_HCI_OCF_READ_SCO_FLOW_CONTROL 0x002e +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t flow_control; /* 0x00 - disabled */ +} __attribute__ ((packed)) ng_hci_read_sco_flow_control_rp; + +#define NG_HCI_OCF_WRITE_SCO_FLOW_CONTROL 0x002f +typedef struct { + u_int8_t flow_control; /* 0x00 - disabled */ +} __attribute__ ((packed)) ng_hci_write_sco_flow_control_cp; + +typedef ng_hci_status_rp ng_hci_write_sco_flow_control_rp; + +#define NG_HCI_OCF_H2HC_FLOW_CONTROL 0x0031 +typedef struct { + u_int8_t h2hc_flow; /* Host to Host controller flow control */ +} __attribute__ ((packed)) ng_hci_h2hc_flow_control_cp; + +typedef ng_hci_status_rp ng_hci_h2hc_flow_control_rp; + +#define NG_HCI_OCF_HOST_BUFFER_SIZE 0x0033 +typedef struct { + u_int16_t max_acl_size; /* Max. size of ACL packet (bytes) */ + u_int8_t max_sco_size; /* Max. size of SCO packet (bytes) */ + u_int16_t num_acl_pkt; /* Max. number of ACL packets */ + u_int16_t num_sco_pkt; /* Max. number of SCO packets */ +} __attribute__ ((packed)) ng_hci_host_buffer_size_cp; + +typedef ng_hci_status_rp ng_hci_host_buffer_size_rp; + +#define NG_HCI_OCF_HOST_NUM_COMPL_PKTS 0x0035 +typedef struct { + u_int8_t num_con_handles; /* # of connection handles */ +/* these are repeated "num_con_handles" times + u_int16_t con_handle; --- connection handle(s) + u_int16_t compl_pkt; --- # of completed packets */ +} __attribute__ ((packed)) ng_hci_host_num_compl_pkts_cp; +/* No return parameter(s) */ + +#define NG_HCI_OCF_READ_LINK_SUPERVISION_TIMO 0x0036 +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_read_link_supervision_timo_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ + u_int16_t timeout; /* Link supervision timeout * 0.625 msec */ +} __attribute__ ((packed)) ng_hci_read_link_supervision_timo_rp; + +#define NG_HCI_OCF_WRITE_LINK_SUPERVISION_TIMO 0x0037 +typedef struct { + u_int16_t con_handle; /* connection handle */ + u_int16_t timeout; /* Link supervision timeout * 0.625 msec */ +} __attribute__ ((packed)) ng_hci_write_link_supervision_timo_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_write_link_supervision_timo_rp; + +#define NG_HCI_OCF_READ_SUPPORTED_IAC_NUM 0x0038 +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t num_iac; /* # of supported IAC during scan */ +} __attribute__ ((packed)) ng_hci_read_supported_iac_num_rp; + +#define NG_HCI_OCF_READ_IAC_LAP 0x0039 +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t num_iac; /* # of IAC */ +/* these are repeated "num_iac" times + u_int8_t laps[NG_HCI_LAP_SIZE]; --- LAPs */ +} __attribute__ ((packed)) ng_hci_read_iac_lap_rp; + +#define NG_HCI_OCF_WRITE_IAC_LAP 0x003a +typedef struct { + u_int8_t num_iac; /* # of IAC */ +/* these are repeated "num_iac" times + u_int8_t laps[NG_HCI_LAP_SIZE]; --- LAPs */ +} __attribute__ ((packed)) ng_hci_write_iac_lap_cp; + +typedef ng_hci_status_rp ng_hci_write_iac_lap_rp; + +#define NG_HCI_OCF_READ_PAGE_SCAN_PERIOD 0x003b +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t page_scan_period_mode; /* Page scan period mode */ +} __attribute__ ((packed)) ng_hci_read_page_scan_period_rp; + +#define NG_HCI_OCF_WRITE_PAGE_SCAN_PERIOD 0x003c +typedef struct { + u_int8_t page_scan_period_mode; /* Page scan period mode */ +} __attribute__ ((packed)) ng_hci_write_page_scan_period_cp; + +typedef ng_hci_status_rp ng_hci_write_page_scan_period_rp; + +#define NG_HCI_OCF_READ_PAGE_SCAN 0x003d +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t page_scan_mode; /* Page scan mode */ +} __attribute__ ((packed)) ng_hci_read_page_scan_rp; + +#define NG_HCI_OCF_WRITE_PAGE_SCAN 0x003e +typedef struct { + u_int8_t page_scan_mode; /* Page scan mode */ +} __attribute__ ((packed)) ng_hci_write_page_scan_cp; + +typedef ng_hci_status_rp ng_hci_write_page_scan_rp; + +/************************************************************************** + ************************************************************************** + ** Informational commands and return parameters + ** All commands in this category do not accept any parameters + ************************************************************************** + **************************************************************************/ + +#define NG_HCI_OGF_INFO 0x04 /* OpCode Group Field */ + +#define NG_HCI_OCF_READ_LOCAL_VER 0x0001 +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t hci_version; /* HCI version */ + u_int16_t hci_revision; /* HCI revision */ + u_int8_t lmp_version; /* LMP version */ + u_int16_t manufacturer; /* Hardware manufacturer name */ + u_int16_t lmp_subversion; /* LMP sub-version */ +} __attribute__ ((packed)) ng_hci_read_local_ver_rp; + +#define NG_HCI_OCF_READ_LOCAL_FEATURES 0x0003 +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t features[NG_HCI_FEATURES_SIZE]; /* LMP features bitmsk*/ +} __attribute__ ((packed)) ng_hci_read_local_features_rp; + +#define NG_HCI_OCF_READ_BUFFER_SIZE 0x0005 +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t max_acl_size; /* Max. size of ACL packet (bytes) */ + u_int8_t max_sco_size; /* Max. size of SCO packet (bytes) */ + u_int16_t num_acl_pkt; /* Max. number of ACL packets */ + u_int16_t num_sco_pkt; /* Max. number of SCO packets */ +} __attribute__ ((packed)) ng_hci_read_buffer_size_rp; + +#define NG_HCI_OCF_READ_COUNTRY_CODE 0x0007 +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t country_code; /* 0x00 - NAM, EUR, JP; 0x01 - France */ +} __attribute__ ((packed)) ng_hci_read_country_code_rp; + +#define NG_HCI_OCF_READ_BDADDR 0x0009 +typedef struct { + u_int8_t status; /* 0x00 - success */ + bdaddr_t bdaddr; /* unit address */ +} __attribute__ ((packed)) ng_hci_read_bdaddr_rp; + +/************************************************************************** + ************************************************************************** + ** Status commands and return parameters + ************************************************************************** + **************************************************************************/ + +#define NG_HCI_OGF_STATUS 0x05 /* OpCode Group Field */ + +#define NG_HCI_OCF_READ_FAILED_CONTACT_CNTR 0x0001 +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_read_failed_contact_cntr_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ + u_int16_t counter; /* number of consecutive failed contacts */ +} __attribute__ ((packed)) ng_hci_read_failed_contact_cntr_rp; + +#define NG_HCI_OCF_RESET_FAILED_CONTACT_CNTR 0x0002 +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_reset_failed_contact_cntr_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_reset_failed_contact_cntr_rp; + +#define NG_HCI_OCF_GET_LINK_QUALITY 0x0003 +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_get_link_quality_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ + u_int8_t quality; /* higher value means better quality */ +} __attribute__ ((packed)) ng_hci_get_link_quality_rp; + +#define NG_HCI_OCF_READ_RSSI 0x0005 +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_read_rssi_cp; + +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ + char rssi; /* -127 <= rssi <= 127 dB */ +} __attribute__ ((packed)) ng_hci_read_rssi_rp; + +/************************************************************************** + ************************************************************************** + ** Testing commands and return parameters + ************************************************************************** + **************************************************************************/ + +#define NG_HCI_OGF_TESTING 0x06 /* OpCode Group Field */ + +#define NG_HCI_OCF_READ_LOOPBACK_MODE 0x0001 +/* No command parameter(s) */ +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int8_t lbmode; /* loopback mode */ +} __attribute__ ((packed)) ng_hci_read_loopback_mode_rp; + +#define NG_HCI_OCF_WRITE_LOOPBACK_MODE 0x0002 +typedef struct { + u_int8_t lbmode; /* loopback mode */ +} __attribute__ ((packed)) ng_hci_write_loopback_mode_cp; + +typedef ng_hci_status_rp ng_hci_write_loopback_mode_rp; + +#define NG_HCI_OCF_ENABLE_UNIT_UNDER_TEST 0x0003 +/* No command parameter(s) */ +typedef ng_hci_status_rp ng_hci_enable_unit_under_test_rp; + +/************************************************************************** + ************************************************************************** + ** Special HCI OpCode group field values + ************************************************************************** + **************************************************************************/ + +#define NG_HCI_OGF_BT_LOGO 0x3e + +#define NG_HCI_OGF_VENDOR 0x3f + +/************************************************************************** + ************************************************************************** + ** Events and event parameters + ************************************************************************** + **************************************************************************/ + +#define NG_HCI_EVENT_INQUIRY_COMPL 0x01 +typedef struct { + u_int8_t status; /* 0x00 - success */ +} __attribute__ ((packed)) ng_hci_inquiry_compl_ep; + +#define NG_HCI_EVENT_INQUIRY_RESULT 0x02 +typedef struct { + u_int8_t num_responses; /* number of responses */ +/* ng_hci_inquiry_response[num_responses] -- see below */ +} __attribute__ ((packed)) ng_hci_inquiry_result_ep; + +typedef struct { + bdaddr_t bdaddr; /* unit address */ + u_int8_t page_scan_rep_mode; /* page scan rep. mode */ + u_int8_t page_scan_period_mode; /* page scan period mode */ + u_int8_t page_scan_mode; /* page scan mode */ + u_int8_t uclass[NG_HCI_CLASS_SIZE];/* unit class */ + u_int16_t clock_offset; /* clock offset */ +} __attribute__ ((packed)) ng_hci_inquiry_response; + +#define NG_HCI_EVENT_CON_COMPL 0x03 +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* Connection handle */ + bdaddr_t bdaddr; /* remote unit address */ + u_int8_t link_type; /* Link type */ + u_int8_t encryption_mode; /* Encryption mode */ +} __attribute__ ((packed)) ng_hci_con_compl_ep; + +#define NG_HCI_EVENT_CON_REQ 0x04 +typedef struct { + bdaddr_t bdaddr; /* remote unit address */ + u_int8_t uclass[NG_HCI_CLASS_SIZE]; /* remote unit class */ + u_int8_t link_type; /* link type */ +} __attribute__ ((packed)) ng_hci_con_req_ep; + +#define NG_HCI_EVENT_DISCON_COMPL 0x05 +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ + u_int8_t reason; /* reason to disconnect */ +} __attribute__ ((packed)) ng_hci_discon_compl_ep; + +#define NG_HCI_EVENT_AUTH_COMPL 0x06 +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_auth_compl_ep; + +#define NG_HCI_EVENT_REMOTE_NAME_REQ_COMPL 0x7 +typedef struct { + u_int8_t status; /* 0x00 - success */ + bdaddr_t bdaddr; /* remote unit address */ + char name[NG_HCI_UNIT_NAME_SIZE]; /* remote unit name */ +} __attribute__ ((packed)) ng_hci_remote_name_req_compl_ep; + +#define NG_HCI_EVENT_ENCRYPTION_CHANGE 0x08 +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* Connection handle */ + u_int8_t encryption_enable; /* 0x00 - disable */ +} __attribute__ ((packed)) ng_hci_encryption_change_ep; + +#define NG_HCI_EVENT_CHANGE_CON_LINK_KEY_COMPL 0x09 +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* Connection handle */ +} __attribute__ ((packed)) ng_hci_change_con_link_key_compl_ep; + +#define NG_HCI_EVENT_MASTER_LINK_KEY_COMPL 0x0a +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* Connection handle */ + u_int8_t key_flag; /* Key flag */ +} __attribute__ ((packed)) ng_hci_master_link_key_compl_ep; + +#define NG_HCI_EVENT_READ_REMOTE_FEATURES_COMPL 0x0b +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* Connection handle */ + u_int8_t features[NG_HCI_FEATURES_SIZE]; /* LMP features bitmsk*/ +} __attribute__ ((packed)) ng_hci_read_remote_features_compl_ep; + +#define NG_HCI_EVENT_READ_REMOTE_VER_INFO_COMPL 0x0c +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* Connection handle */ + u_int8_t lmp_version; /* LMP version */ + u_int16_t manufacturer; /* Hardware manufacturer name */ + u_int16_t lmp_subversion; /* LMP sub-version */ +} __attribute__ ((packed)) ng_hci_read_remote_ver_info_compl_ep; + +#define NG_HCI_EVENT_QOS_SETUP_COMPL 0x0d +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ + u_int8_t flags; /* reserved for future use */ + u_int8_t service_type; /* service type */ + u_int32_t token_rate; /* bytes per second */ + u_int32_t peak_bandwidth; /* bytes per second */ + u_int32_t latency; /* microseconds */ + u_int32_t delay_variation; /* microseconds */ +} __attribute__ ((packed)) ng_hci_qos_setup_compl_ep; + +#define NG_HCI_EVENT_COMMAND_COMPL 0x0e +typedef struct { + u_int8_t num_cmd_pkts; /* # of HCI command packets */ + u_int16_t opcode; /* command OpCode */ + /* command return parameters (if any) */ +} __attribute__ ((packed)) ng_hci_command_compl_ep; + +#define NG_HCI_EVENT_COMMAND_STATUS 0x0f +typedef struct { + u_int8_t status; /* 0x00 - pending */ + u_int8_t num_cmd_pkts; /* # of HCI command packets */ + u_int16_t opcode; /* command OpCode */ +} __attribute__ ((packed)) ng_hci_command_status_ep; + +#define NG_HCI_EVENT_HARDWARE_ERROR 0x10 +typedef struct { + u_int8_t hardware_code; /* hardware error code */ +} __attribute__ ((packed)) ng_hci_hardware_error_ep; + +#define NG_HCI_EVENT_FLUSH_OCCUR 0x11 +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_flush_occur_ep; + +#define NG_HCI_EVENT_ROLE_CHANGE 0x12 +typedef struct { + u_int8_t status; /* 0x00 - success */ + bdaddr_t bdaddr; /* address of remote unit */ + u_int8_t role; /* new connection role */ +} __attribute__ ((packed)) ng_hci_role_change_ep; + +#define NG_HCI_EVENT_NUM_COMPL_PKTS 0x13 +typedef struct { + u_int8_t num_con_handles; /* # of connection handles */ +/* these are repeated "num_con_handles" times + u_int16_t con_handle; --- connection handle(s) + u_int16_t compl_pkt; --- # of completed packets */ +} __attribute__ ((packed)) ng_hci_num_compl_pkts_ep; + +#define NG_HCI_EVENT_MODE_CHANGE 0x14 +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ + u_int8_t unit_mode; /* remote unit mode */ + u_int16_t interval; /* interval * 0.625 msec */ +} __attribute__ ((packed)) ng_hci_mode_change_ep; + +#define NG_HCI_EVENT_RETURN_LINK_KEYS 0x15 +typedef struct { + u_int8_t num_keys; /* # of keys */ +/* these are repeated "num_keys" times + bdaddr_t bdaddr; --- remote address(es) + u_int8_t key[NG_HCI_KEY_SIZE]; --- key(s) */ +} __attribute__ ((packed)) ng_hci_return_link_keys_ep; + +#define NG_HCI_EVENT_PIN_CODE_REQ 0x16 +typedef struct { + bdaddr_t bdaddr; /* remote unit address */ +} __attribute__ ((packed)) ng_hci_pin_code_req_ep; + +#define NG_HCI_EVENT_LINK_KEY_REQ 0x17 +typedef struct { + bdaddr_t bdaddr; /* remote unit address */ +} __attribute__ ((packed)) ng_hci_link_key_req_ep; + +#define NG_HCI_EVENT_LINK_KEY_NOTIFICATION 0x18 +typedef struct { + bdaddr_t bdaddr; /* remote unit address */ + u_int8_t key[NG_HCI_KEY_SIZE]; /* link key */ + u_int8_t key_type; /* type of the key */ +} __attribute__ ((packed)) ng_hci_link_key_notification_ep; + +#define NG_HCI_EVENT_LOOPBACK_COMMAND 0x19 +typedef struct { + u_int8_t command[0]; /* Command packet */ +} __attribute__ ((packed)) ng_hci_loopback_command_ep; + +#define NG_HCI_EVENT_DATA_BUFFER_OVERFLOW 0x1a +typedef struct { + u_int8_t link_type; /* Link type */ +} __attribute__ ((packed)) ng_hci_data_buffer_overflow_ep; + +#define NG_HCI_EVENT_MAX_SLOT_CHANGE 0x1b +typedef struct { + u_int16_t con_handle; /* connection handle */ + u_int8_t lmp_max_slots; /* Max. # of slots allowed */ +} __attribute__ ((packed)) ng_hci_max_slot_change_ep; + +#define NG_HCI_EVENT_READ_CLOCK_OFFSET_COMPL 0x1c +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* Connection handle */ + u_int16_t clock_offset; /* Clock offset */ +} __attribute__ ((packed)) ng_hci_read_clock_offset_compl_ep; + +#define NG_HCI_EVENT_CON_PKT_TYPE_CHANGED 0x1d +typedef struct { + u_int8_t status; /* 0x00 - success */ + u_int16_t con_handle; /* connection handle */ + u_int16_t pkt_type; /* packet type */ +} __attribute__ ((packed)) ng_hci_con_pkt_type_changed_ep; + +#define NG_HCI_EVENT_QOS_VIOLATION 0x1e +typedef struct { + u_int16_t con_handle; /* connection handle */ +} __attribute__ ((packed)) ng_hci_qos_violation_ep; + +#define NG_HCI_EVENT_PAGE_SCAN_MODE_CHANGE 0x1f +typedef struct { + bdaddr_t bdaddr; /* destination address */ + u_int8_t page_scan_mode; /* page scan mode */ +} __attribute__ ((packed)) ng_hci_page_scan_mode_change_ep; + +#define NG_HCI_EVENT_PAGE_SCAN_REP_MODE_CHANGE 0x20 +typedef struct { + bdaddr_t bdaddr; /* destination address */ + u_int8_t page_scan_rep_mode; /* page scan repetition mode */ +} __attribute__ ((packed)) ng_hci_page_scan_rep_mode_change_ep; + +#define NG_HCI_EVENT_BT_LOGO 0xfe + +#define NG_HCI_EVENT_VENDOR 0xff + +#endif /* ndef _NETGRAPH_HCI_H_ */ diff --git a/sys/netgraph7/bluetooth/include/ng_l2cap.h b/sys/netgraph7/bluetooth/include/ng_l2cap.h new file mode 100644 index 0000000000..3be95d94aa --- /dev/null +++ b/sys/netgraph7/bluetooth/include/ng_l2cap.h @@ -0,0 +1,665 @@ +/* + * ng_l2cap.h + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_l2cap.h,v 1.2 2003/04/27 00:52:26 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/include/ng_l2cap.h,v 1.4 2005/08/31 18:13:23 emax Exp $ + */ + +/* + * This file contains everything that application needs to know about + * Link Layer Control and Adaptation Protocol (L2CAP). All information + * was obtained from Bluetooth Specification Book v1.1. + * + * This file can be included by both kernel and userland applications. + */ + +#ifndef _NETGRAPH_L2CAP_H_ +#define _NETGRAPH_L2CAP_H_ + +/************************************************************************** + ************************************************************************** + ** Netgraph node hook name, type name and type cookie and commands + ************************************************************************** + **************************************************************************/ + +/* Netgraph node hook names */ +#define NG_L2CAP_HOOK_HCI "hci" /* HCI <-> L2CAP */ +#define NG_L2CAP_HOOK_L2C "l2c" /* L2CAP <-> Upper */ +#define NG_L2CAP_HOOK_CTL "ctl" /* L2CAP <-> User */ + +/* Node type name and type cookie */ +#define NG_L2CAP_NODE_TYPE "l2cap" +#define NGM_L2CAP_COOKIE 1000774185 + +/************************************************************************** + ************************************************************************** + ** Common defines and types (L2CAP) + ************************************************************************** + **************************************************************************/ + +/* + * Channel IDs are assigned relative to the instance of L2CAP node, i.e. + * relative to the unit. So the total number of channels that unit can have + * open at the same time is 0xffff - 0x0040 = 0xffbf (65471). This number + * does not depend on number of connections. + */ + +#define NG_L2CAP_NULL_CID 0x0000 /* DO NOT USE THIS CID */ +#define NG_L2CAP_SIGNAL_CID 0x0001 /* signaling channel ID */ +#define NG_L2CAP_CLT_CID 0x0002 /* connectionless channel ID */ + /* 0x0003 - 0x003f Reserved */ +#define NG_L2CAP_FIRST_CID 0x0040 /* dynamically alloc. (start) */ +#define NG_L2CAP_LAST_CID 0xffff /* dynamically alloc. (end) */ + +/* L2CAP MTU */ +#define NG_L2CAP_MTU_MINIMUM 48 +#define NG_L2CAP_MTU_DEFAULT 672 +#define NG_L2CAP_MTU_MAXIMUM 0xffff + +/* L2CAP flush and link timeouts */ +#define NG_L2CAP_FLUSH_TIMO_DEFAULT 0xffff /* always retransmit */ +#define NG_L2CAP_LINK_TIMO_DEFAULT 0xffff + +/* L2CAP Command Reject reasons */ +#define NG_L2CAP_REJ_NOT_UNDERSTOOD 0x0000 +#define NG_L2CAP_REJ_MTU_EXCEEDED 0x0001 +#define NG_L2CAP_REJ_INVALID_CID 0x0002 +/* 0x0003 - 0xffff - reserved for future use */ + +/* Protocol/Service Multioplexor (PSM) values */ +#define NG_L2CAP_PSM_ANY 0x0000 /* Any/Invalid PSM */ +#define NG_L2CAP_PSM_SDP 0x0001 /* Service Discovery Protocol */ +#define NG_L2CAP_PSM_RFCOMM 0x0003 /* RFCOMM protocol */ +#define NG_L2CAP_PSM_TCP 0x0005 /* Telephony Control Protocol */ +/* 0x0006 - 0x1000 - reserved for future use */ + +/* L2CAP Connection response command result codes */ +#define NG_L2CAP_SUCCESS 0x0000 +#define NG_L2CAP_PENDING 0x0001 +#define NG_L2CAP_PSM_NOT_SUPPORTED 0x0002 +#define NG_L2CAP_SEQUIRY_BLOCK 0x0003 +#define NG_L2CAP_NO_RESOURCES 0x0004 +#define NG_L2CAP_TIMEOUT 0xeeee +#define NG_L2CAP_UNKNOWN 0xffff +/* 0x0005 - 0xffff - reserved for future use */ + +/* L2CAP Connection response status codes */ +#define NG_L2CAP_NO_INFO 0x0000 +#define NG_L2CAP_AUTH_PENDING 0x0001 +#define NG_L2CAP_AUTZ_PENDING 0x0002 +/* 0x0003 - 0xffff - reserved for future use */ + +/* L2CAP Configuration response result codes */ +#define NG_L2CAP_UNACCEPTABLE_PARAMS 0x0001 +#define NG_L2CAP_REJECT 0x0002 +#define NG_L2CAP_UNKNOWN_OPTION 0x0003 +/* 0x0003 - 0xffff - reserved for future use */ + +/* L2CAP Configuration options */ +#define NG_L2CAP_OPT_CFLAG_BIT 0x0001 +#define NG_L2CAP_OPT_CFLAG(flags) ((flags) & NG_L2CAP_OPT_CFLAG_BIT) +#define NG_L2CAP_OPT_HINT_BIT 0x80 +#define NG_L2CAP_OPT_HINT(type) ((type) & NG_L2CAP_OPT_HINT_BIT) +#define NG_L2CAP_OPT_HINT_MASK 0x7f +#define NG_L2CAP_OPT_MTU 0x01 +#define NG_L2CAP_OPT_MTU_SIZE sizeof(u_int16_t) +#define NG_L2CAP_OPT_FLUSH_TIMO 0x02 +#define NG_L2CAP_OPT_FLUSH_TIMO_SIZE sizeof(u_int16_t) +#define NG_L2CAP_OPT_QOS 0x03 +#define NG_L2CAP_OPT_QOS_SIZE sizeof(ng_l2cap_flow_t) +/* 0x4 - 0xff - reserved for future use */ + +/* L2CAP Information request type codes */ +#define NG_L2CAP_CONNLESS_MTU 0x0001 +/* 0x0002 - 0xffff - reserved for future use */ + +/* L2CAP Information response codes */ +#define NG_L2CAP_NOT_SUPPORTED 0x0001 +/* 0x0002 - 0xffff - reserved for future use */ + +/* L2CAP flow control */ +typedef struct { + u_int8_t flags; /* reserved for future use */ + u_int8_t service_type; /* service type */ + u_int32_t token_rate; /* bytes per second */ + u_int32_t token_bucket_size; /* bytes */ + u_int32_t peak_bandwidth; /* bytes per second */ + u_int32_t latency; /* microseconds */ + u_int32_t delay_variation; /* microseconds */ +} __attribute__ ((packed)) ng_l2cap_flow_t; +typedef ng_l2cap_flow_t * ng_l2cap_flow_p; + +/************************************************************************** + ************************************************************************** + ** Link level defines, headers and types + ************************************************************************** + **************************************************************************/ + +/* L2CAP header */ +typedef struct { + u_int16_t length; /* payload size */ + u_int16_t dcid; /* destination channel ID */ +} __attribute__ ((packed)) ng_l2cap_hdr_t; + +/* L2CAP ConnectionLess Traffic (CLT) (if destination cid == 0x2) */ +typedef struct { + u_int16_t psm; /* Protocol/Service Multiplexor */ +} __attribute__ ((packed)) ng_l2cap_clt_hdr_t; + +#define NG_L2CAP_CLT_MTU_MAXIMUM \ + (NG_L2CAP_MTU_MAXIMUM - sizeof(ng_l2cap_clt_hdr_t)) + +/* L2CAP command header */ +typedef struct { + u_int8_t code; /* command OpCode */ + u_int8_t ident; /* identifier to match request and response */ + u_int16_t length; /* command parameters length */ +} __attribute__ ((packed)) ng_l2cap_cmd_hdr_t; + +/* L2CAP Command Reject */ +#define NG_L2CAP_CMD_REJ 0x01 +typedef struct { + u_int16_t reason; /* reason to reject command */ +/* u_int8_t data[]; -- optional data (depends on reason) */ +} __attribute__ ((packed)) ng_l2cap_cmd_rej_cp; + +/* CommandReject data */ +typedef union { + /* NG_L2CAP_REJ_MTU_EXCEEDED */ + struct { + u_int16_t mtu; /* actual signaling MTU */ + } __attribute__ ((packed)) mtu; + /* NG_L2CAP_REJ_INVALID_CID */ + struct { + u_int16_t scid; /* local CID */ + u_int16_t dcid; /* remote CID */ + } __attribute__ ((packed)) cid; +} ng_l2cap_cmd_rej_data_t; +typedef ng_l2cap_cmd_rej_data_t * ng_l2cap_cmd_rej_data_p; + +/* L2CAP Connection Request */ +#define NG_L2CAP_CON_REQ 0x02 +typedef struct { + u_int16_t psm; /* Protocol/Service Multiplexor (PSM) */ + u_int16_t scid; /* source channel ID */ +} __attribute__ ((packed)) ng_l2cap_con_req_cp; + +/* L2CAP Connection Response */ +#define NG_L2CAP_CON_RSP 0x03 +typedef struct { + u_int16_t dcid; /* destination channel ID */ + u_int16_t scid; /* source channel ID */ + u_int16_t result; /* 0x00 - success */ + u_int16_t status; /* more info if result != 0x00 */ +} __attribute__ ((packed)) ng_l2cap_con_rsp_cp; + +/* L2CAP Configuration Request */ +#define NG_L2CAP_CFG_REQ 0x04 +typedef struct { + u_int16_t dcid; /* destination channel ID */ + u_int16_t flags; /* flags */ +/* u_int8_t options[] -- options */ +} __attribute__ ((packed)) ng_l2cap_cfg_req_cp; + +/* L2CAP Configuration Response */ +#define NG_L2CAP_CFG_RSP 0x05 +typedef struct { + u_int16_t scid; /* source channel ID */ + u_int16_t flags; /* flags */ + u_int16_t result; /* 0x00 - success */ +/* u_int8_t options[] -- options */ +} __attribute__ ((packed)) ng_l2cap_cfg_rsp_cp; + +/* L2CAP configuration option */ +typedef struct { + u_int8_t type; + u_int8_t length; +/* u_int8_t value[] -- option value (depends on type) */ +} __attribute__ ((packed)) ng_l2cap_cfg_opt_t; +typedef ng_l2cap_cfg_opt_t * ng_l2cap_cfg_opt_p; + +/* L2CAP configuration option value */ +typedef union { + u_int16_t mtu; /* NG_L2CAP_OPT_MTU */ + u_int16_t flush_timo; /* NG_L2CAP_OPT_FLUSH_TIMO */ + ng_l2cap_flow_t flow; /* NG_L2CAP_OPT_QOS */ +} ng_l2cap_cfg_opt_val_t; +typedef ng_l2cap_cfg_opt_val_t * ng_l2cap_cfg_opt_val_p; + +/* L2CAP Disconnect Request */ +#define NG_L2CAP_DISCON_REQ 0x06 +typedef struct { + u_int16_t dcid; /* destination channel ID */ + u_int16_t scid; /* source channel ID */ +} __attribute__ ((packed)) ng_l2cap_discon_req_cp; + +/* L2CAP Disconnect Response */ +#define NG_L2CAP_DISCON_RSP 0x07 +typedef ng_l2cap_discon_req_cp ng_l2cap_discon_rsp_cp; + +/* L2CAP Echo Request */ +#define NG_L2CAP_ECHO_REQ 0x08 +/* No command parameters, only optional data */ + +/* L2CAP Echo Response */ +#define NG_L2CAP_ECHO_RSP 0x09 +#define NG_L2CAP_MAX_ECHO_SIZE \ + (NG_L2CAP_MTU_MAXIMUM - sizeof(ng_l2cap_cmd_hdr_t)) +/* No command parameters, only optional data */ + +/* L2CAP Information Request */ +#define NG_L2CAP_INFO_REQ 0x0a +typedef struct { + u_int16_t type; /* requested information type */ +} __attribute__ ((packed)) ng_l2cap_info_req_cp; + +/* L2CAP Information Response */ +#define NG_L2CAP_INFO_RSP 0x0b +typedef struct { + u_int16_t type; /* requested information type */ + u_int16_t result; /* 0x00 - success */ +/* u_int8_t info[] -- info data (depends on type) + * + * NG_L2CAP_CONNLESS_MTU - 2 bytes connectionless MTU + */ +} __attribute__ ((packed)) ng_l2cap_info_rsp_cp; + +typedef union { + /* NG_L2CAP_CONNLESS_MTU */ + struct { + u_int16_t mtu; + } __attribute__ ((packed)) mtu; +} ng_l2cap_info_rsp_data_t; +typedef ng_l2cap_info_rsp_data_t * ng_l2cap_info_rsp_data_p; + +/************************************************************************** + ************************************************************************** + ** Upper layer protocol interface. L2CA_xxx messages + ************************************************************************** + **************************************************************************/ + +/* + * NOTE! NOTE! NOTE! + * + * Bluetooth specification says that L2CA_xxx request must block until + * response is ready. We are not allowed to block in Netgraph, so we + * need to queue request and save some information that can be used + * later and help match request and response. + * + * The idea is to use "token" field from Netgraph message header. The + * upper layer protocol _MUST_ populate "token". L2CAP will queue request + * (using L2CAP command descriptor) and start processing. Later, when + * response is ready or timeout has occur L2CAP layer will create new + * Netgraph message, set "token" and RESP flag and send the message to + * the upper layer protocol. + * + * L2CA_xxx_Ind messages _WILL_NOT_ populate "token" and _WILL_NOT_ + * set RESP flag. There is no reason for this, because they are just + * notifications and do not require acknowlegment. + * + * NOTE: This is _NOT_ what NG_MKRESPONSE and NG_RESPOND_MSG do, however + * it is somewhat similar. + */ + +/* L2CA data packet header */ +typedef struct { + u_int32_t token; /* token to use in L2CAP_L2CA_WRITE */ + u_int16_t length; /* length of the data */ + u_int16_t lcid; /* local channel ID */ +} __attribute__ ((packed)) ng_l2cap_l2ca_hdr_t; + +/* L2CA_Connect */ +#define NGM_L2CAP_L2CA_CON 0x80 +/* Upper -> L2CAP */ +typedef struct { + u_int16_t psm; /* Protocol/Service Multiplexor */ + bdaddr_t bdaddr; /* remote unit address */ +} ng_l2cap_l2ca_con_ip; + +/* L2CAP -> Upper */ +typedef struct { + u_int16_t lcid; /* local channel ID */ + u_int16_t result; /* 0x00 - success */ + u_int16_t status; /* if result != 0x00 */ +} ng_l2cap_l2ca_con_op; + +/* L2CA_ConnectInd */ +#define NGM_L2CAP_L2CA_CON_IND 0x81 +/* L2CAP -> Upper */ +typedef struct { + bdaddr_t bdaddr; /* remote unit address */ + u_int16_t lcid; /* local channel ID */ + u_int16_t psm; /* Procotol/Service Multiplexor */ + u_int8_t ident; /* indentifier */ + u_int8_t unused; /* place holder */ +} ng_l2cap_l2ca_con_ind_ip; +/* No output parameters */ + +/* L2CA_ConnectRsp */ +#define NGM_L2CAP_L2CA_CON_RSP 0x82 +/* Upper -> L2CAP */ +typedef struct { + bdaddr_t bdaddr; /* remote unit address */ + u_int8_t ident; /* "ident" from L2CAP_ConnectInd event */ + u_int8_t unused; /* place holder */ + u_int16_t lcid; /* local channel ID */ + u_int16_t result; /* 0x00 - success */ + u_int16_t status; /* if response != 0x00 */ +} ng_l2cap_l2ca_con_rsp_ip; + +/* L2CAP -> Upper */ +typedef struct { + u_int16_t result; /* 0x00 - success */ +} ng_l2cap_l2ca_con_rsp_op; + +/* L2CA_Config */ +#define NGM_L2CAP_L2CA_CFG 0x83 +/* Upper -> L2CAP */ +typedef struct { + u_int16_t lcid; /* local channel ID */ + u_int16_t imtu; /* receiving MTU for the local channel */ + ng_l2cap_flow_t oflow; /* out flow */ + u_int16_t flush_timo; /* flush timeout (msec) */ + u_int16_t link_timo; /* link timeout (msec) */ +} ng_l2cap_l2ca_cfg_ip; + +/* L2CAP -> Upper */ +typedef struct { + u_int16_t result; /* 0x00 - success */ + u_int16_t imtu; /* sending MTU for the remote channel */ + ng_l2cap_flow_t oflow; /* out flow */ + u_int16_t flush_timo; /* flush timeout (msec) */ +} ng_l2cap_l2ca_cfg_op; + +/* L2CA_ConfigRsp */ +#define NGM_L2CAP_L2CA_CFG_RSP 0x84 +/* Upper -> L2CAP */ +typedef struct { + u_int16_t lcid; /* local channel ID */ + u_int16_t omtu; /* sending MTU for the local channel */ + ng_l2cap_flow_t iflow; /* in FLOW */ +} ng_l2cap_l2ca_cfg_rsp_ip; + +/* L2CAP -> Upper */ +typedef struct { + u_int16_t result; /* 0x00 - sucsess */ +} ng_l2cap_l2ca_cfg_rsp_op; + +/* L2CA_ConfigInd */ +#define NGM_L2CAP_L2CA_CFG_IND 0x85 +/* L2CAP -> Upper */ +typedef struct { + u_int16_t lcid; /* local channel ID */ + u_int16_t omtu; /* outgoing MTU for the local channel */ + ng_l2cap_flow_t iflow; /* in flow */ + u_int16_t flush_timo; /* flush timeout (msec) */ +} ng_l2cap_l2ca_cfg_ind_ip; +/* No output parameters */ + +/* L2CA_QoSViolationInd */ +#define NGM_L2CAP_L2CA_QOS_IND 0x86 +/* L2CAP -> Upper */ +typedef struct { + bdaddr_t bdaddr; /* remote unit address */ +} ng_l2cap_l2ca_qos_ind_ip; +/* No output parameters */ + +/* L2CA_Disconnect */ +#define NGM_L2CAP_L2CA_DISCON 0x87 +/* Upper -> L2CAP */ +typedef struct { + u_int16_t lcid; /* local channel ID */ +} ng_l2cap_l2ca_discon_ip; + +/* L2CAP -> Upper */ +typedef struct { + u_int16_t result; /* 0x00 - sucsess */ +} ng_l2cap_l2ca_discon_op; + +/* L2CA_DisconnectInd */ +#define NGM_L2CAP_L2CA_DISCON_IND 0x88 +/* L2CAP -> Upper */ +typedef ng_l2cap_l2ca_discon_ip ng_l2cap_l2ca_discon_ind_ip; +/* No output parameters */ + +/* L2CA_Write response */ +#define NGM_L2CAP_L2CA_WRITE 0x89 +/* No input parameters */ + +/* L2CAP -> Upper */ +typedef struct { + int result; /* result (0x00 - success) */ + u_int16_t length; /* amount of data written */ + u_int16_t lcid; /* local channel ID */ +} ng_l2cap_l2ca_write_op; + +/* L2CA_GroupCreate */ +#define NGM_L2CAP_L2CA_GRP_CREATE 0x8a +/* Upper -> L2CAP */ +typedef struct { + u_int16_t psm; /* Protocol/Service Multiplexor */ +} ng_l2cap_l2ca_grp_create_ip; + +/* L2CAP -> Upper */ +typedef struct { + u_int16_t lcid; /* local group channel ID */ +} ng_l2cap_l2ca_grp_create_op; + +/* L2CA_GroupClose */ +#define NGM_L2CAP_L2CA_GRP_CLOSE 0x8b +/* Upper -> L2CAP */ +typedef struct { + u_int16_t lcid; /* local group channel ID */ +} ng_l2cap_l2ca_grp_close_ip; + +#if 0 +/* L2CAP -> Upper */ + * typedef struct { + * u_int16_t result; /* 0x00 - success */ + * } ng_l2cap_l2ca_grp_close_op; +#endif + +/* L2CA_GroupAddMember */ +#define NGM_L2CAP_L2CA_GRP_ADD_MEMBER 0x8c +/* Upper -> L2CAP */ +typedef struct { + u_int16_t lcid; /* local group channel ID */ + bdaddr_t bdaddr; /* remote unit address */ +} ng_l2cap_l2ca_grp_add_member_ip; + +/* L2CAP -> Upper */ +typedef struct { + u_int16_t result; /* 0x00 - success */ +} ng_l2cap_l2ca_grp_add_member_op; + +/* L2CA_GroupRemoveMember */ +#define NGM_L2CAP_L2CA_GRP_REM_MEMBER 0x8d +/* Upper -> L2CAP */ +typedef ng_l2cap_l2ca_grp_add_member_ip ng_l2cap_l2ca_grp_rem_member_ip; + +/* L2CAP -> Upper */ +#if 0 + * typedef ng_l2cap_l2ca_grp_add_member_op ng_l2cap_l2ca_grp_rem_member_op; +#endif + +/* L2CA_GroupMembeship */ +#define NGM_L2CAP_L2CA_GRP_MEMBERSHIP 0x8e +/* Upper -> L2CAP */ +typedef struct { + u_int16_t lcid; /* local group channel ID */ +} ng_l2cap_l2ca_grp_get_members_ip; + +/* L2CAP -> Upper */ +typedef struct { + u_int16_t result; /* 0x00 - success */ + u_int16_t nmembers; /* number of group members */ +/* bdaddr_t members[] -- group memebers */ +} ng_l2cap_l2ca_grp_get_members_op; + +/* L2CA_Ping */ +#define NGM_L2CAP_L2CA_PING 0x8f +/* Upper -> L2CAP */ +typedef struct { + bdaddr_t bdaddr; /* remote unit address */ + u_int16_t echo_size; /* size of echo data in bytes */ +/* u_int8_t echo_data[] -- echo data */ +} ng_l2cap_l2ca_ping_ip; + +/* L2CAP -> Upper */ +typedef struct { + u_int16_t result; /* 0x00 - success */ + bdaddr_t bdaddr; /* remote unit address */ + u_int16_t echo_size; /* size of echo data in bytes */ +/* u_int8_t echo_data[] -- echo data */ +} ng_l2cap_l2ca_ping_op; + +/* L2CA_GetInfo */ +#define NGM_L2CAP_L2CA_GET_INFO 0x90 +/* Upper -> L2CAP */ +typedef struct { + bdaddr_t bdaddr; /* remote unit address */ + u_int16_t info_type; /* info type */ +} ng_l2cap_l2ca_get_info_ip; + +/* L2CAP -> Upper */ +typedef struct { + u_int16_t result; /* 0x00 - success */ + u_int16_t info_size; /* size of info data in bytes */ +/* u_int8_t info_data[] -- info data */ +} ng_l2cap_l2ca_get_info_op; + +/* L2CA_EnableCLT/L2CA_DisableCLT */ +#define NGM_L2CAP_L2CA_ENABLE_CLT 0x91 +/* Upper -> L2CAP */ +typedef struct { + u_int16_t psm; /* Protocol/Service Multiplexor */ + u_int16_t enable; /* 0x00 - disable */ +} ng_l2cap_l2ca_enable_clt_ip; + +#if 0 +/* L2CAP -> Upper */ + * typedef struct { + * u_int16_t result; /* 0x00 - success */ + * } ng_l2cap_l2ca_enable_clt_op; +#endif + +/************************************************************************** + ************************************************************************** + ** L2CAP node messages + ************************************************************************** + **************************************************************************/ + +/* L2CAP connection states */ +#define NG_L2CAP_CON_CLOSED 0 /* connection closed */ +#define NG_L2CAP_W4_LP_CON_CFM 1 /* waiting... */ +#define NG_L2CAP_CON_OPEN 2 /* connection open */ + +/* L2CAP channel states */ +#define NG_L2CAP_CLOSED 0 /* channel closed */ +#define NG_L2CAP_W4_L2CAP_CON_RSP 1 /* wait for L2CAP resp. */ +#define NG_L2CAP_W4_L2CA_CON_RSP 2 /* wait for upper resp. */ +#define NG_L2CAP_CONFIG 3 /* L2CAP configuration */ +#define NG_L2CAP_OPEN 4 /* channel open */ +#define NG_L2CAP_W4_L2CAP_DISCON_RSP 5 /* wait for L2CAP discon. */ +#define NG_L2CAP_W4_L2CA_DISCON_RSP 6 /* wait for upper discon. */ + +/* Node flags */ +#define NG_L2CAP_CLT_SDP_DISABLED (1 << 0) /* disable SDP CLT */ +#define NG_L2CAP_CLT_RFCOMM_DISABLED (1 << 1) /* disable RFCOMM CLT */ +#define NG_L2CAP_CLT_TCP_DISABLED (1 << 2) /* disable TCP CLT */ + +/* Debug levels */ +#define NG_L2CAP_ALERT_LEVEL 1 +#define NG_L2CAP_ERR_LEVEL 2 +#define NG_L2CAP_WARN_LEVEL 3 +#define NG_L2CAP_INFO_LEVEL 4 + +/* Get node flags (see flags above) */ +#define NGM_L2CAP_NODE_GET_FLAGS 0x400 /* L2CAP -> User */ +typedef u_int16_t ng_l2cap_node_flags_ep; + +/* Get/Set debug level (see levels above) */ +#define NGM_L2CAP_NODE_GET_DEBUG 0x401 /* L2CAP -> User */ +#define NGM_L2CAP_NODE_SET_DEBUG 0x402 /* User -> L2CAP */ +typedef u_int16_t ng_l2cap_node_debug_ep; + +#define NGM_L2CAP_NODE_HOOK_INFO 0x409 /* L2CAP -> Upper */ +/* bdaddr_t bdaddr; -- local (source BDADDR) */ + +#define NGM_L2CAP_NODE_GET_CON_LIST 0x40a /* L2CAP -> User */ +typedef struct { + u_int32_t num_connections; /* number of connections */ +} ng_l2cap_node_con_list_ep; + +/* Connection flags */ +#define NG_L2CAP_CON_TX (1 << 0) /* sending data */ +#define NG_L2CAP_CON_RX (1 << 1) /* receiving data */ +#define NG_L2CAP_CON_OUTGOING (1 << 2) /* outgoing connection */ +#define NG_L2CAP_CON_LP_TIMO (1 << 3) /* LP timeout */ +#define NG_L2CAP_CON_AUTO_DISCON_TIMO (1 << 4) /* auto discon. timeout */ +#define NG_L2CAP_CON_DYING (1 << 5) /* connection is dying */ + +typedef struct { + u_int8_t state; /* connection state */ + u_int8_t flags; /* flags */ + int16_t pending; /* num. pending packets */ + u_int16_t con_handle; /* connection handle */ + bdaddr_t remote; /* remote bdaddr */ +} ng_l2cap_node_con_ep; + +#define NG_L2CAP_MAX_CON_NUM \ + ((0xffff - sizeof(ng_l2cap_node_con_list_ep))/sizeof(ng_l2cap_node_con_ep)) + +#define NGM_L2CAP_NODE_GET_CHAN_LIST 0x40b /* L2CAP -> User */ +typedef struct { + u_int32_t num_channels; /* number of channels */ +} ng_l2cap_node_chan_list_ep; + +typedef struct { + u_int32_t state; /* channel state */ + + u_int16_t scid; /* source (local) channel ID */ + u_int16_t dcid; /* destination (remote) channel ID */ + + u_int16_t imtu; /* incomming MTU */ + u_int16_t omtu; /* outgoing MTU */ + + u_int16_t psm; /* PSM */ + bdaddr_t remote; /* remote bdaddr */ +} ng_l2cap_node_chan_ep; + +#define NG_L2CAP_MAX_CHAN_NUM \ + ((0xffff - sizeof(ng_l2cap_node_chan_list_ep))/sizeof(ng_l2cap_node_chan_ep)) + +#define NGM_L2CAP_NODE_GET_AUTO_DISCON_TIMO 0x40c /* L2CAP -> User */ +#define NGM_L2CAP_NODE_SET_AUTO_DISCON_TIMO 0x40d /* User -> L2CAP */ +typedef u_int16_t ng_l2cap_node_auto_discon_ep; + +#endif /* ndef _NETGRAPH_L2CAP_H_ */ + diff --git a/sys/netgraph7/bluetooth/include/ng_ubt.h b/sys/netgraph7/bluetooth/include/ng_ubt.h new file mode 100644 index 0000000000..0233f08587 --- /dev/null +++ b/sys/netgraph7/bluetooth/include/ng_ubt.h @@ -0,0 +1,91 @@ +/* + * ng_ubt.h + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_ubt.h,v 1.6 2003/04/13 21:34:42 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/include/ng_ubt.h,v 1.3 2005/01/07 01:45:43 imp Exp $ + */ + +#ifndef _NG_UBT_H_ +#define _NG_UBT_H_ + +/************************************************************************** + ************************************************************************** + ** Netgraph node hook name, type name and type cookie and commands + ************************************************************************** + **************************************************************************/ + +#define NG_UBT_NODE_TYPE "ubt" +#define NG_UBT_HOOK "hook" + +#define NGM_UBT_COOKIE 1021837971 + +/* Debug levels */ +#define NG_UBT_ALERT_LEVEL 1 +#define NG_UBT_ERR_LEVEL 2 +#define NG_UBT_WARN_LEVEL 3 +#define NG_UBT_INFO_LEVEL 4 + +/************************************************************************** + ************************************************************************** + ** UBT node command/event parameters + ************************************************************************** + **************************************************************************/ + +#define NGM_UBT_NODE_SET_DEBUG 1 /* set debug level */ +#define NGM_UBT_NODE_GET_DEBUG 2 /* get debug level */ +typedef u_int16_t ng_ubt_node_debug_ep; + +#define NGM_UBT_NODE_SET_QLEN 3 /* set queue length */ +#define NGM_UBT_NODE_GET_QLEN 4 /* get queue length */ +typedef struct { + int32_t queue; /* queue index */ +#define NGM_UBT_NODE_QUEUE_CMD 1 /* commands */ +#define NGM_UBT_NODE_QUEUE_ACL 2 /* ACL data */ +#define NGM_UBT_NODE_QUEUE_SCO 3 /* SCO data */ + + int32_t qlen; /* queue length */ +} ng_ubt_node_qlen_ep; + +#define NGM_UBT_NODE_GET_STAT 5 /* get statistic */ +typedef struct { + u_int32_t pckts_recv; /* # of packets received */ + u_int32_t bytes_recv; /* # of bytes received */ + u_int32_t pckts_sent; /* # of packets sent */ + u_int32_t bytes_sent; /* # of bytes sent */ + u_int32_t oerrors; /* # of output errors */ + u_int32_t ierrors; /* # of input errors */ +} ng_ubt_node_stat_ep; + +#define NGM_UBT_NODE_RESET_STAT 6 /* reset statistic */ + +#define NGM_UBT_NODE_DEV_NODES 7 /* on/off device interface */ +typedef u_int16_t ng_ubt_node_dev_nodes_ep; + +#endif /* ndef _NG_UBT_H_ */ + diff --git a/sys/netgraph7/bluetooth/l2cap/TODO b/sys/netgraph7/bluetooth/l2cap/TODO new file mode 100644 index 0000000000..0d7aa29716 --- /dev/null +++ b/sys/netgraph7/bluetooth/l2cap/TODO @@ -0,0 +1,43 @@ +$Id: TODO,v 1.1 2002/11/24 19:47:06 max Exp $ +$FreeBSD: src/sys/netgraph/bluetooth/l2cap/TODO,v 1.2 2003/05/10 21:44:41 julian Exp $ + +FIXME/TODO list + +0) Ping itself. Should L2CAP layer loopback data? + +1) Locking/SMP + + External code now uses ng_send_fn to inject data into Netgraph, so + it should be fine as long as Netgraph is SMP safe. Just need to + verify it. + +2) Understand and implement L2CAP QoS + + Will fix later. I only have CSR based hardware and it does not + support QoS. + +3) Better functions to manage CIDs and command ident's. + + Resource manager is not good because it uses MTX_DEF mutexes, + (i.e. could block/sleep) + +4) Implement group channels (multicast) + + Will fix later + +5) Add bytes/packets counters and commands to get/reset them + + Will fix later. What to count? + +6) Better way to get information about channels + + L2CAP can support about 65000 channels. Need define some good way + to get data from kernel to user space. For example if we need to pass + 1K of information for every channel, then worst case is that we need + to pass 65Mbytes of data from kernel to user space. Not good. + +7) Deal properly with "shutdown"s and hook "disconnect"s + + For now we destroy all channels when upstream hook is disconnected. + Is there a better way to handle this? + diff --git a/sys/netgraph7/bluetooth/l2cap/ng_l2cap_cmds.c b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_cmds.c new file mode 100644 index 0000000000..44a037216e --- /dev/null +++ b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_cmds.c @@ -0,0 +1,398 @@ +/* + * ng_l2cap_cmds.c + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_l2cap_cmds.c,v 1.2 2003/09/08 19:11:45 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/l2cap/ng_l2cap_cmds.c,v 1.7 2007/03/28 21:25:56 emax Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/****************************************************************************** + ****************************************************************************** + ** L2CAP commands processing module + ****************************************************************************** + ******************************************************************************/ + +/* + * Process L2CAP command queue on connection + */ + +void +ng_l2cap_con_wakeup(ng_l2cap_con_p con) +{ + ng_l2cap_cmd_p cmd = NULL; + struct mbuf *m = NULL; + int error = 0; + + /* Find first non-pending command in the queue */ + TAILQ_FOREACH(cmd, &con->cmd_list, next) { + KASSERT((cmd->con == con), +("%s: %s - invalid connection pointer!\n", + __func__, NG_NODE_NAME(con->l2cap->node))); + + if (!(cmd->flags & NG_L2CAP_CMD_PENDING)) + break; + } + + if (cmd == NULL) + return; + + /* Detach command packet */ + m = cmd->aux; + cmd->aux = NULL; + + /* Process command */ + switch (cmd->code) { + case NG_L2CAP_CMD_REJ: + case NG_L2CAP_DISCON_RSP: + case NG_L2CAP_ECHO_RSP: + case NG_L2CAP_INFO_RSP: + /* + * Do not check return ng_l2cap_lp_send() value, because + * in these cases we do not really have a graceful way out. + * ECHO and INFO responses are internal to the stack and not + * visible to user. REJect is just being nice to remote end + * (otherwise remote end will timeout anyway). DISCON is + * probably most interesting here, however, if it fails + * there is nothing we can do anyway. + */ + + (void) ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m); + ng_l2cap_unlink_cmd(cmd); + ng_l2cap_free_cmd(cmd); + break; + + case NG_L2CAP_CON_REQ: + error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m); + if (error != 0) { + ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token, + NG_L2CAP_NO_RESOURCES, 0); + ng_l2cap_free_chan(cmd->ch); /* will free commands */ + } else + ng_l2cap_command_timeout(cmd, + bluetooth_l2cap_rtx_timeout()); + break; + + case NG_L2CAP_CON_RSP: + error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m); + ng_l2cap_unlink_cmd(cmd); + if (cmd->ch != NULL) { + ng_l2cap_l2ca_con_rsp_rsp(cmd->ch, cmd->token, + (error == 0)? NG_L2CAP_SUCCESS : + NG_L2CAP_NO_RESOURCES); + if (error != 0) + ng_l2cap_free_chan(cmd->ch); + } + ng_l2cap_free_cmd(cmd); + break; + + case NG_L2CAP_CFG_REQ: + error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m); + if (error != 0) { + ng_l2cap_l2ca_cfg_rsp(cmd->ch, cmd->token, + NG_L2CAP_NO_RESOURCES); + ng_l2cap_unlink_cmd(cmd); + ng_l2cap_free_cmd(cmd); + } else + ng_l2cap_command_timeout(cmd, + bluetooth_l2cap_rtx_timeout()); + break; + + case NG_L2CAP_CFG_RSP: + error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m); + ng_l2cap_unlink_cmd(cmd); + if (cmd->ch != NULL) + ng_l2cap_l2ca_cfg_rsp_rsp(cmd->ch, cmd->token, + (error == 0)? NG_L2CAP_SUCCESS : + NG_L2CAP_NO_RESOURCES); + ng_l2cap_free_cmd(cmd); + break; + + case NG_L2CAP_DISCON_REQ: + error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m); + ng_l2cap_l2ca_discon_rsp(cmd->ch, cmd->token, + (error == 0)? NG_L2CAP_SUCCESS : NG_L2CAP_NO_RESOURCES); + if (error != 0) + ng_l2cap_free_chan(cmd->ch); /* XXX free channel */ + else + ng_l2cap_command_timeout(cmd, + bluetooth_l2cap_rtx_timeout()); + break; + + case NG_L2CAP_ECHO_REQ: + error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m); + if (error != 0) { + ng_l2cap_l2ca_ping_rsp(con, cmd->token, + NG_L2CAP_NO_RESOURCES, NULL); + ng_l2cap_unlink_cmd(cmd); + ng_l2cap_free_cmd(cmd); + } else + ng_l2cap_command_timeout(cmd, + bluetooth_l2cap_rtx_timeout()); + break; + + case NG_L2CAP_INFO_REQ: + error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m); + if (error != 0) { + ng_l2cap_l2ca_get_info_rsp(con, cmd->token, + NG_L2CAP_NO_RESOURCES, NULL); + ng_l2cap_unlink_cmd(cmd); + ng_l2cap_free_cmd(cmd); + } else + ng_l2cap_command_timeout(cmd, + bluetooth_l2cap_rtx_timeout()); + break; + + case NGM_L2CAP_L2CA_WRITE: { + int length = m->m_pkthdr.len; + + if (cmd->ch->dcid == NG_L2CAP_CLT_CID) { + m = ng_l2cap_prepend(m, sizeof(ng_l2cap_clt_hdr_t)); + if (m == NULL) + error = ENOBUFS; + else + mtod(m, ng_l2cap_clt_hdr_t *)->psm = + htole16(cmd->ch->psm); + } + + if (error == 0) + error = ng_l2cap_lp_send(con, cmd->ch->dcid, m); + + ng_l2cap_l2ca_write_rsp(cmd->ch, cmd->token, + (error == 0)? NG_L2CAP_SUCCESS : NG_L2CAP_NO_RESOURCES, + length); + + ng_l2cap_unlink_cmd(cmd); + ng_l2cap_free_cmd(cmd); + } break; + + /* XXX FIXME add other commands */ + + default: + panic( +"%s: %s - unknown command code=%d\n", + __func__, NG_NODE_NAME(con->l2cap->node), cmd->code); + break; + } +} /* ng_l2cap_con_wakeup */ + +/* + * We have failed to open ACL connection to the remote unit. Could be negative + * confirmation or timeout. So fail any "delayed" commands, notify upper layer, + * remove all channels and remove connection descriptor. + */ + +void +ng_l2cap_con_fail(ng_l2cap_con_p con, u_int16_t result) +{ + ng_l2cap_p l2cap = con->l2cap; + ng_l2cap_cmd_p cmd = NULL; + ng_l2cap_chan_p ch = NULL; + + NG_L2CAP_INFO( +"%s: %s - ACL connection failed, result=%d\n", + __func__, NG_NODE_NAME(l2cap->node), result); + + /* Connection is dying */ + con->flags |= NG_L2CAP_CON_DYING; + + /* Clean command queue */ + while (!TAILQ_EMPTY(&con->cmd_list)) { + cmd = TAILQ_FIRST(&con->cmd_list); + + ng_l2cap_unlink_cmd(cmd); + if(cmd->flags & NG_L2CAP_CMD_PENDING) + ng_l2cap_command_untimeout(cmd); + + KASSERT((cmd->con == con), +("%s: %s - invalid connection pointer!\n", + __func__, NG_NODE_NAME(l2cap->node))); + + switch (cmd->code) { + case NG_L2CAP_CMD_REJ: + case NG_L2CAP_DISCON_RSP: + case NG_L2CAP_ECHO_RSP: + case NG_L2CAP_INFO_RSP: + break; + + case NG_L2CAP_CON_REQ: + ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token, result, 0); + break; + + case NG_L2CAP_CON_RSP: + if (cmd->ch != NULL) + ng_l2cap_l2ca_con_rsp_rsp(cmd->ch, cmd->token, + result); + break; + + case NG_L2CAP_CFG_REQ: + case NG_L2CAP_CFG_RSP: + case NGM_L2CAP_L2CA_WRITE: + ng_l2cap_l2ca_discon_ind(cmd->ch); + break; + + case NG_L2CAP_DISCON_REQ: + ng_l2cap_l2ca_discon_rsp(cmd->ch, cmd->token, + NG_L2CAP_SUCCESS); + break; + + case NG_L2CAP_ECHO_REQ: + ng_l2cap_l2ca_ping_rsp(cmd->con, cmd->token, + result, NULL); + break; + + case NG_L2CAP_INFO_REQ: + ng_l2cap_l2ca_get_info_rsp(cmd->con, cmd->token, + result, NULL); + break; + + /* XXX FIXME add other commands */ + + default: + panic( +"%s: %s - unexpected command code=%d\n", + __func__, NG_NODE_NAME(l2cap->node), cmd->code); + break; + } + + if (cmd->ch != NULL) + ng_l2cap_free_chan(cmd->ch); + + ng_l2cap_free_cmd(cmd); + } + + /* + * There still might be channels (in OPEN state?) that + * did not submit any commands, so diconnect them + */ + + LIST_FOREACH(ch, &l2cap->chan_list, next) + if (ch->con == con) + ng_l2cap_l2ca_discon_ind(ch); + + /* Free connection descriptor */ + ng_l2cap_free_con(con); +} /* ng_l2cap_con_fail */ + +/* + * Process L2CAP command timeout. In general - notify upper layer and destroy + * channel. Do not pay much attension to return code, just do our best. + */ + +void +ng_l2cap_process_command_timeout(node_p node, hook_p hook, void *arg1, int arg2) +{ + ng_l2cap_p l2cap = NULL; + ng_l2cap_con_p con = NULL; + ng_l2cap_cmd_p cmd = NULL; + u_int16_t con_handle = (arg2 & 0x0ffff); + u_int8_t ident = ((arg2 >> 16) & 0xff); + + if (NG_NODE_NOT_VALID(node)) { + printf("%s: Netgraph node is not valid\n", __func__); + return; + } + + l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(node); + + con = ng_l2cap_con_by_handle(l2cap, con_handle); + if (con == NULL) { + NG_L2CAP_ALERT( +"%s: %s - could not find connection, con_handle=%d\n", + __func__, NG_NODE_NAME(node), con_handle); + return; + } + + cmd = ng_l2cap_cmd_by_ident(con, ident); + if (cmd == NULL) { + NG_L2CAP_ALERT( +"%s: %s - could not find command, con_handle=%d, ident=%d\n", + __func__, NG_NODE_NAME(node), con_handle, ident); + return; + } + + cmd->flags &= ~NG_L2CAP_CMD_PENDING; + ng_l2cap_unlink_cmd(cmd); + + switch (cmd->code) { + case NG_L2CAP_CON_REQ: + ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token, NG_L2CAP_TIMEOUT, 0); + ng_l2cap_free_chan(cmd->ch); + break; + + case NG_L2CAP_CFG_REQ: + ng_l2cap_l2ca_cfg_rsp(cmd->ch, cmd->token, NG_L2CAP_TIMEOUT); + break; + + case NG_L2CAP_DISCON_REQ: + ng_l2cap_l2ca_discon_rsp(cmd->ch, cmd->token, NG_L2CAP_TIMEOUT); + ng_l2cap_free_chan(cmd->ch); /* XXX free channel */ + break; + + case NG_L2CAP_ECHO_REQ: + /* Echo request timed out. Let the upper layer know */ + ng_l2cap_l2ca_ping_rsp(cmd->con, cmd->token, + NG_L2CAP_TIMEOUT, NULL); + break; + + case NG_L2CAP_INFO_REQ: + /* Info request timed out. Let the upper layer know */ + ng_l2cap_l2ca_get_info_rsp(cmd->con, cmd->token, + NG_L2CAP_TIMEOUT, NULL); + break; + + /* XXX FIXME add other commands */ + + default: + panic( +"%s: %s - unexpected command code=%d\n", + __func__, NG_NODE_NAME(l2cap->node), cmd->code); + break; + } + + ng_l2cap_free_cmd(cmd); +} /* ng_l2cap_process_command_timeout */ + diff --git a/sys/netgraph7/bluetooth/l2cap/ng_l2cap_cmds.h b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_cmds.h new file mode 100644 index 0000000000..5c4f126d09 --- /dev/null +++ b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_cmds.h @@ -0,0 +1,409 @@ +/* + * ng_l2cap_cmds.h + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_l2cap_cmds.h,v 1.4 2003/04/01 18:15:26 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/l2cap/ng_l2cap_cmds.h,v 1.5 2005/01/07 01:45:43 imp Exp $ + */ + +#ifndef _NETGRAPH_L2CAP_CMDS_H_ +#define _NETGRAPH_L2CAP_CMDS_H_ + +/****************************************************************************** + ****************************************************************************** + ** L2CAP to L2CAP signaling command macros + ****************************************************************************** + ******************************************************************************/ + +/* + * Note: All L2CAP implementations are required to support minimal signaling + * MTU of 48 bytes. In order to simplify things we will send one command + * per one L2CAP packet. Given evrything above we can assume that one + * signaling packet will fit into single mbuf. + */ + +/* L2CAP_CommandRej */ +#define _ng_l2cap_cmd_rej(_m, _ident, _reason, _mtu, _scid, _dcid) \ +do { \ + struct _cmd_rej { \ + ng_l2cap_cmd_hdr_t hdr; \ + ng_l2cap_cmd_rej_cp param; \ + ng_l2cap_cmd_rej_data_t data; \ + } __attribute__ ((packed)) *c = NULL; \ + \ + MGETHDR((_m), M_DONTWAIT, MT_DATA); \ + if ((_m) == NULL) \ + break; \ + \ + c = mtod((_m), struct _cmd_rej *); \ + c->hdr.code = NG_L2CAP_CMD_REJ; \ + c->hdr.ident = (_ident); \ + c->hdr.length = sizeof(c->param); \ + \ + c->param.reason = htole16((_reason)); \ + \ + if ((_reason) == NG_L2CAP_REJ_MTU_EXCEEDED) { \ + c->data.mtu.mtu = htole16((_mtu)); \ + c->hdr.length += sizeof(c->data.mtu); \ + } else if ((_reason) == NG_L2CAP_REJ_INVALID_CID) { \ + c->data.cid.scid = htole16((_scid)); \ + c->data.cid.dcid = htole16((_dcid)); \ + c->hdr.length += sizeof(c->data.cid); \ + } \ + \ + (_m)->m_pkthdr.len = (_m)->m_len = sizeof(c->hdr) + \ + c->hdr.length; \ + \ + c->hdr.length = htole16(c->hdr.length); \ +} while (0) + +/* L2CAP_ConnectReq */ +#define _ng_l2cap_con_req(_m, _ident, _psm, _scid) \ +do { \ + struct _con_req { \ + ng_l2cap_cmd_hdr_t hdr; \ + ng_l2cap_con_req_cp param; \ + } __attribute__ ((packed)) *c = NULL; \ + \ + MGETHDR((_m), M_DONTWAIT, MT_DATA); \ + if ((_m) == NULL) \ + break; \ + \ + (_m)->m_pkthdr.len = (_m)->m_len = sizeof(*c); \ + \ + c = mtod((_m), struct _con_req *); \ + c->hdr.code = NG_L2CAP_CON_REQ; \ + c->hdr.ident = (_ident); \ + c->hdr.length = htole16(sizeof(c->param)); \ + \ + c->param.psm = htole16((_psm)); \ + c->param.scid = htole16((_scid)); \ +} while (0) + +/* L2CAP_ConnectRsp */ +#define _ng_l2cap_con_rsp(_m, _ident, _dcid, _scid, _result, _status) \ +do { \ + struct _con_rsp { \ + ng_l2cap_cmd_hdr_t hdr; \ + ng_l2cap_con_rsp_cp param; \ + } __attribute__ ((packed)) *c = NULL; \ + \ + MGETHDR((_m), M_DONTWAIT, MT_DATA); \ + if ((_m) == NULL) \ + break; \ + \ + (_m)->m_pkthdr.len = (_m)->m_len = sizeof(*c); \ + \ + c = mtod((_m), struct _con_rsp *); \ + c->hdr.code = NG_L2CAP_CON_RSP; \ + c->hdr.ident = (_ident); \ + c->hdr.length = htole16(sizeof(c->param)); \ + \ + c->param.dcid = htole16((_dcid)); \ + c->param.scid = htole16((_scid)); \ + c->param.result = htole16((_result)); \ + c->param.status = htole16((_status)); \ +} while (0) + +/* L2CAP_ConfigReq */ +#define _ng_l2cap_cfg_req(_m, _ident, _dcid, _flags, _data) \ +do { \ + struct _cfg_req { \ + ng_l2cap_cmd_hdr_t hdr; \ + ng_l2cap_cfg_req_cp param; \ + } __attribute__ ((packed)) *c = NULL; \ + \ + MGETHDR((_m), M_DONTWAIT, MT_DATA); \ + if ((_m) == NULL) { \ + NG_FREE_M((_data)); \ + break; \ + } \ + \ + (_m)->m_pkthdr.len = (_m)->m_len = sizeof(*c); \ + \ + c = mtod((_m), struct _cfg_req *); \ + c->hdr.code = NG_L2CAP_CFG_REQ; \ + c->hdr.ident = (_ident); \ + c->hdr.length = sizeof(c->param); \ + \ + c->param.dcid = htole16((_dcid)); \ + c->param.flags = htole16((_flags)); \ + if ((_data) != NULL) { \ + int l = (_data)->m_pkthdr.len; \ + \ + m_cat((_m), (_data)); \ + c->hdr.length += l; \ + (_m)->m_pkthdr.len += l; \ + } \ + \ + c->hdr.length = htole16(c->hdr.length); \ +} while (0) + +/* L2CAP_ConfigRsp */ +#define _ng_l2cap_cfg_rsp(_m, _ident, _scid, _flags, _result, _data) \ +do { \ + struct _cfg_rsp { \ + ng_l2cap_cmd_hdr_t hdr; \ + ng_l2cap_cfg_rsp_cp param; \ + } __attribute__ ((packed)) *c = NULL; \ + \ + MGETHDR((_m), M_DONTWAIT, MT_DATA); \ + if ((_m) == NULL) { \ + NG_FREE_M((_data)); \ + break; \ + } \ + \ + (_m)->m_pkthdr.len = (_m)->m_len = sizeof(*c); \ + \ + c = mtod((_m), struct _cfg_rsp *); \ + c->hdr.code = NG_L2CAP_CFG_RSP; \ + c->hdr.ident = (_ident); \ + c->hdr.length = sizeof(c->param); \ + \ + c->param.scid = htole16((_scid)); \ + c->param.flags = htole16((_flags)); \ + c->param.result = htole16((_result)); \ + if ((_data) != NULL) { \ + int l = (_data)->m_pkthdr.len; \ + \ + m_cat((_m), (_data)); \ + c->hdr.length += l; \ + (_m)->m_pkthdr.len += l; \ + } \ + \ + c->hdr.length = htole16(c->hdr.length); \ +} while (0) + +/* Build configuration options */ +#define _ng_l2cap_build_cfg_options(_m, _mtu, _flush_timo, _flow) \ +do { \ + u_int8_t *p = NULL; \ + \ + MGETHDR((_m), M_DONTWAIT, MT_DATA); \ + if ((_m) == NULL) \ + break; \ + \ + (_m)->m_pkthdr.len = (_m)->m_len = 0; \ + p = mtod((_m), u_int8_t *); \ + \ + if ((_mtu) != NULL) { \ + struct _cfg_opt_mtu { \ + ng_l2cap_cfg_opt_t hdr; \ + u_int16_t val; \ + } __attribute__ ((packed)) *o = NULL; \ + \ + o = (struct _cfg_opt_mtu *) p; \ + o->hdr.type = NG_L2CAP_OPT_MTU; \ + o->hdr.length = sizeof(o->val); \ + o->val = htole16(*(u_int16_t *)(_mtu)); \ + \ + (_m)->m_pkthdr.len += sizeof(*o); \ + p += sizeof(*o); \ + } \ + \ + if ((_flush_timo) != NULL) { \ + struct _cfg_opt_flush { \ + ng_l2cap_cfg_opt_t hdr; \ + u_int16_t val; \ + } __attribute__ ((packed)) *o = NULL; \ + \ + o = (struct _cfg_opt_flush *) p; \ + o->hdr.type = NG_L2CAP_OPT_FLUSH_TIMO; \ + o->hdr.length = sizeof(o->val); \ + o->val = htole16(*(u_int16_t *)(_flush_timo)); \ + \ + (_m)->m_pkthdr.len += sizeof(*o); \ + p += sizeof(*o); \ + } \ + \ + if ((_flow) != NULL) { \ + struct _cfg_opt_flow { \ + ng_l2cap_cfg_opt_t hdr; \ + ng_l2cap_flow_t val; \ + } __attribute__ ((packed)) *o = NULL; \ + \ + o = (struct _cfg_opt_flow *) p; \ + o->hdr.type = NG_L2CAP_OPT_QOS; \ + o->hdr.length = sizeof(o->val); \ + o->val.flags = ((ng_l2cap_flow_p)(_flow))->flags; \ + o->val.service_type = ((ng_l2cap_flow_p) \ + (_flow))->service_type; \ + o->val.token_rate = \ + htole32(((ng_l2cap_flow_p)(_flow))->token_rate);\ + o->val.token_bucket_size = \ + htole32(((ng_l2cap_flow_p) \ + (_flow))->token_bucket_size); \ + o->val.peak_bandwidth = \ + htole32(((ng_l2cap_flow_p) \ + (_flow))->peak_bandwidth); \ + o->val.latency = htole32(((ng_l2cap_flow_p) \ + (_flow))->latency); \ + o->val.delay_variation = \ + htole32(((ng_l2cap_flow_p) \ + (_flow))->delay_variation); \ + \ + (_m)->m_pkthdr.len += sizeof(*o); \ + } \ + \ + (_m)->m_len = (_m)->m_pkthdr.len; \ +} while (0) + +/* L2CAP_DisconnectReq */ +#define _ng_l2cap_discon_req(_m, _ident, _dcid, _scid) \ +do { \ + struct _discon_req { \ + ng_l2cap_cmd_hdr_t hdr; \ + ng_l2cap_discon_req_cp param; \ + } __attribute__ ((packed)) *c = NULL; \ + \ + MGETHDR((_m), M_DONTWAIT, MT_DATA); \ + if ((_m) == NULL) \ + break; \ + \ + (_m)->m_pkthdr.len = (_m)->m_len = sizeof(*c); \ + \ + c = mtod((_m), struct _discon_req *); \ + c->hdr.code = NG_L2CAP_DISCON_REQ; \ + c->hdr.ident = (_ident); \ + c->hdr.length = htole16(sizeof(c->param)); \ + \ + c->param.dcid = htole16((_dcid)); \ + c->param.scid = htole16((_scid)); \ +} while (0) + +/* L2CA_DisconnectRsp */ +#define _ng_l2cap_discon_rsp(_m, _ident, _dcid, _scid) \ +do { \ + struct _discon_rsp { \ + ng_l2cap_cmd_hdr_t hdr; \ + ng_l2cap_discon_rsp_cp param; \ + } __attribute__ ((packed)) *c = NULL; \ + \ + MGETHDR((_m), M_DONTWAIT, MT_DATA); \ + if ((_m) == NULL) \ + break; \ + \ + (_m)->m_pkthdr.len = (_m)->m_len = sizeof(*c); \ + \ + c = mtod((_m), struct _discon_rsp *); \ + c->hdr.code = NG_L2CAP_DISCON_RSP; \ + c->hdr.ident = (_ident); \ + c->hdr.length = htole16(sizeof(c->param)); \ + \ + c->param.dcid = htole16((_dcid)); \ + c->param.scid = htole16((_scid)); \ +} while (0) + +/* L2CAP_EchoReq */ +#define _ng_l2cap_echo_req(_m, _ident, _data, _size) \ +do { \ + ng_l2cap_cmd_hdr_t *c = NULL; \ + \ + MGETHDR((_m), M_DONTWAIT, MT_DATA); \ + if ((_m) == NULL) \ + break; \ + \ + (_m)->m_pkthdr.len = (_m)->m_len = sizeof(*c); \ + \ + c = mtod((_m), ng_l2cap_cmd_hdr_t *); \ + c->code = NG_L2CAP_ECHO_REQ; \ + c->ident = (_ident); \ + c->length = 0; \ + \ + if ((_data) != NULL) { \ + m_copyback((_m), sizeof(*c), (_size), (_data)); \ + c->length += (_size); \ + } \ + \ + c->length = htole16(c->length); \ +} while (0) + +/* L2CAP_InfoReq */ +#define _ng_l2cap_info_req(_m, _ident, _type) \ +do { \ + struct _info_req { \ + ng_l2cap_cmd_hdr_t hdr; \ + ng_l2cap_info_req_cp param; \ + } __attribute__ ((packed)) *c = NULL; \ + \ + MGETHDR((_m), M_DONTWAIT, MT_DATA); \ + if ((_m) == NULL) \ + break; \ + \ + (_m)->m_pkthdr.len = (_m)->m_len = sizeof(*c); \ + \ + c = mtod((_m), struct _info_req *); \ + c->hdr.code = NG_L2CAP_INFO_REQ; \ + c->hdr.ident = (_ident); \ + c->hdr.length = htole16(sizeof(c->param)); \ + \ + c->param.type = htole16((_type)); \ +} while (0) + +/* L2CAP_InfoRsp */ +#define _ng_l2cap_info_rsp(_m, _ident, _type, _result, _mtu) \ +do { \ + struct _info_rsp { \ + ng_l2cap_cmd_hdr_t hdr; \ + ng_l2cap_info_rsp_cp param; \ + ng_l2cap_info_rsp_data_t data; \ + } __attribute__ ((packed)) *c = NULL; \ + \ + MGETHDR((_m), M_DONTWAIT, MT_DATA); \ + if ((_m) == NULL) \ + break; \ + \ + c = mtod((_m), struct _info_rsp *); \ + c->hdr.code = NG_L2CAP_INFO_REQ; \ + c->hdr.ident = (_ident); \ + c->hdr.length = sizeof(c->param); \ + \ + c->param.type = htole16((_type)); \ + c->param.result = htole16((_result)); \ + \ + if ((_result) == NG_L2CAP_SUCCESS) { \ + switch ((_type)) { \ + case NG_L2CAP_CONNLESS_MTU: \ + c->data.mtu.mtu = htole16((_mtu)); \ + c->hdr.length += sizeof((c->data.mtu.mtu)); \ + break; \ + } \ + } \ + \ + (_m)->m_pkthdr.len = (_m)->m_len = sizeof(c->hdr) + \ + c->hdr.length; \ + \ + c->hdr.length = htole16(c->hdr.length); \ +} while (0) + +void ng_l2cap_con_wakeup (ng_l2cap_con_p); +void ng_l2cap_con_fail (ng_l2cap_con_p, u_int16_t); +void ng_l2cap_process_command_timeout (node_p, hook_p, void *, int); + +#endif /* ndef _NETGRAPH_L2CAP_CMDS_H_ */ + diff --git a/sys/netgraph7/bluetooth/l2cap/ng_l2cap_evnt.c b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_evnt.c new file mode 100644 index 0000000000..2a79976fbc --- /dev/null +++ b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_evnt.c @@ -0,0 +1,1327 @@ +/* + * ng_l2cap_evnt.c + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_l2cap_evnt.c,v 1.5 2003/09/08 19:11:45 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/l2cap/ng_l2cap_evnt.c,v 1.8 2005/01/07 01:45:43 imp Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/****************************************************************************** + ****************************************************************************** + ** L2CAP events processing module + ****************************************************************************** + ******************************************************************************/ + +static int ng_l2cap_process_signal_cmd (ng_l2cap_con_p); +static int ng_l2cap_process_cmd_rej (ng_l2cap_con_p, u_int8_t); +static int ng_l2cap_process_con_req (ng_l2cap_con_p, u_int8_t); +static int ng_l2cap_process_con_rsp (ng_l2cap_con_p, u_int8_t); +static int ng_l2cap_process_cfg_req (ng_l2cap_con_p, u_int8_t); +static int ng_l2cap_process_cfg_rsp (ng_l2cap_con_p, u_int8_t); +static int ng_l2cap_process_discon_req (ng_l2cap_con_p, u_int8_t); +static int ng_l2cap_process_discon_rsp (ng_l2cap_con_p, u_int8_t); +static int ng_l2cap_process_echo_req (ng_l2cap_con_p, u_int8_t); +static int ng_l2cap_process_echo_rsp (ng_l2cap_con_p, u_int8_t); +static int ng_l2cap_process_info_req (ng_l2cap_con_p, u_int8_t); +static int ng_l2cap_process_info_rsp (ng_l2cap_con_p, u_int8_t); +static int send_l2cap_reject + (ng_l2cap_con_p, u_int8_t, u_int16_t, u_int16_t, u_int16_t, u_int16_t); +static int send_l2cap_con_rej + (ng_l2cap_con_p, u_int8_t, u_int16_t, u_int16_t, u_int16_t); +static int send_l2cap_cfg_rsp + (ng_l2cap_con_p, u_int8_t, u_int16_t, u_int16_t, struct mbuf *); +static int get_next_l2cap_opt + (struct mbuf *, int *, ng_l2cap_cfg_opt_p, ng_l2cap_cfg_opt_val_p); + +/* + * Receive L2CAP packet. First get L2CAP header and verify packet. Than + * get destination channel and process packet. + */ + +int +ng_l2cap_receive(ng_l2cap_con_p con) +{ + ng_l2cap_p l2cap = con->l2cap; + ng_l2cap_hdr_t *hdr = NULL; + int error = 0; + + /* Check packet */ + if (con->rx_pkt->m_pkthdr.len < sizeof(*hdr)) { + NG_L2CAP_ERR( +"%s: %s - invalid L2CAP packet. Packet too small, len=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + con->rx_pkt->m_pkthdr.len); + error = EMSGSIZE; + goto drop; + } + + /* Get L2CAP header */ + NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*hdr)); + if (con->rx_pkt == NULL) + return (ENOBUFS); + + hdr = mtod(con->rx_pkt, ng_l2cap_hdr_t *); + hdr->length = le16toh(hdr->length); + hdr->dcid = le16toh(hdr->dcid); + + /* Check payload size */ + if (hdr->length != con->rx_pkt->m_pkthdr.len - sizeof(*hdr)) { + NG_L2CAP_ERR( +"%s: %s - invalid L2CAP packet. Payload length mismatch, length=%d, len=%zd\n", + __func__, NG_NODE_NAME(l2cap->node), hdr->length, + con->rx_pkt->m_pkthdr.len - sizeof(*hdr)); + error = EMSGSIZE; + goto drop; + } + + /* Process packet */ + switch (hdr->dcid) { + case NG_L2CAP_SIGNAL_CID: /* L2CAP command */ + m_adj(con->rx_pkt, sizeof(*hdr)); + error = ng_l2cap_process_signal_cmd(con); + break; + + case NG_L2CAP_CLT_CID: /* Connectionless packet */ + error = ng_l2cap_l2ca_clt_receive(con); + break; + + default: /* Data packet */ + error = ng_l2cap_l2ca_receive(con); + break; + } + + return (error); +drop: + NG_FREE_M(con->rx_pkt); + + return (error); +} /* ng_l2cap_receive */ + +/* + * Process L2CAP signaling command. We already know that destination channel ID + * is 0x1 that means we have received signaling command from peer's L2CAP layer. + * So get command header, decode and process it. + * + * XXX do we need to check signaling MTU here? + */ + +static int +ng_l2cap_process_signal_cmd(ng_l2cap_con_p con) +{ + ng_l2cap_p l2cap = con->l2cap; + ng_l2cap_cmd_hdr_t *hdr = NULL; + struct mbuf *m = NULL; + + while (con->rx_pkt != NULL) { + /* Verify packet length */ + if (con->rx_pkt->m_pkthdr.len < sizeof(*hdr)) { + NG_L2CAP_ERR( +"%s: %s - invalid L2CAP signaling command. Packet too small, len=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + con->rx_pkt->m_pkthdr.len); + NG_FREE_M(con->rx_pkt); + + return (EMSGSIZE); + } + + /* Get signaling command */ + NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*hdr)); + if (con->rx_pkt == NULL) + return (ENOBUFS); + + hdr = mtod(con->rx_pkt, ng_l2cap_cmd_hdr_t *); + hdr->length = le16toh(hdr->length); + m_adj(con->rx_pkt, sizeof(*hdr)); + + /* Verify command length */ + if (con->rx_pkt->m_pkthdr.len < hdr->length) { + NG_L2CAP_ERR( +"%s: %s - invalid L2CAP signaling command, code=%#x, ident=%d. " \ +"Invalid command length=%d, m_pkthdr.len=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + hdr->code, hdr->ident, hdr->length, + con->rx_pkt->m_pkthdr.len); + NG_FREE_M(con->rx_pkt); + + return (EMSGSIZE); + } + + /* Get the command, save the rest (if any) */ + if (con->rx_pkt->m_pkthdr.len > hdr->length) + m = m_split(con->rx_pkt, hdr->length, M_DONTWAIT); + else + m = NULL; + + /* Process command */ + switch (hdr->code) { + case NG_L2CAP_CMD_REJ: + ng_l2cap_process_cmd_rej(con, hdr->ident); + break; + + case NG_L2CAP_CON_REQ: + ng_l2cap_process_con_req(con, hdr->ident); + break; + + case NG_L2CAP_CON_RSP: + ng_l2cap_process_con_rsp(con, hdr->ident); + break; + + case NG_L2CAP_CFG_REQ: + ng_l2cap_process_cfg_req(con, hdr->ident); + break; + + case NG_L2CAP_CFG_RSP: + ng_l2cap_process_cfg_rsp(con, hdr->ident); + break; + + case NG_L2CAP_DISCON_REQ: + ng_l2cap_process_discon_req(con, hdr->ident); + break; + + case NG_L2CAP_DISCON_RSP: + ng_l2cap_process_discon_rsp(con, hdr->ident); + break; + + case NG_L2CAP_ECHO_REQ: + ng_l2cap_process_echo_req(con, hdr->ident); + break; + + case NG_L2CAP_ECHO_RSP: + ng_l2cap_process_echo_rsp(con, hdr->ident); + break; + + case NG_L2CAP_INFO_REQ: + ng_l2cap_process_info_req(con, hdr->ident); + break; + + case NG_L2CAP_INFO_RSP: + ng_l2cap_process_info_rsp(con, hdr->ident); + break; + + default: + NG_L2CAP_ERR( +"%s: %s - unknown L2CAP signaling command, code=%#x, ident=%d, length=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + hdr->code, hdr->ident, hdr->length); + + /* + * Send L2CAP_CommandRej. Do not really care + * about the result + */ + + send_l2cap_reject(con, hdr->ident, + NG_L2CAP_REJ_NOT_UNDERSTOOD, 0, 0, 0); + NG_FREE_M(con->rx_pkt); + break; + } + + con->rx_pkt = m; + } + + return (0); +} /* ng_l2cap_process_signal_cmd */ + +/* + * Process L2CAP_CommandRej command + */ + +static int +ng_l2cap_process_cmd_rej(ng_l2cap_con_p con, u_int8_t ident) +{ + ng_l2cap_p l2cap = con->l2cap; + ng_l2cap_cmd_rej_cp *cp = NULL; + ng_l2cap_cmd_p cmd = NULL; + + /* Get command parameters */ + NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*cp)); + if (con->rx_pkt == NULL) + return (ENOBUFS); + + cp = mtod(con->rx_pkt, ng_l2cap_cmd_rej_cp *); + cp->reason = le16toh(cp->reason); + + /* Check if we have pending command descriptor */ + cmd = ng_l2cap_cmd_by_ident(con, ident); + if (cmd != NULL) { + /* If command timeout already happened then ignore reject */ + if (ng_l2cap_command_untimeout(cmd) != 0) { + NG_FREE_M(con->rx_pkt); + return (ETIMEDOUT); + } + + ng_l2cap_unlink_cmd(cmd); + + switch (cmd->code) { + case NG_L2CAP_CON_REQ: + ng_l2cap_l2ca_con_rsp(cmd->ch,cmd->token,cp->reason,0); + ng_l2cap_free_chan(cmd->ch); + break; + + case NG_L2CAP_CFG_REQ: + ng_l2cap_l2ca_cfg_rsp(cmd->ch, cmd->token, cp->reason); + break; + + case NG_L2CAP_DISCON_REQ: + ng_l2cap_l2ca_discon_rsp(cmd->ch,cmd->token,cp->reason); + ng_l2cap_free_chan(cmd->ch); /* XXX free channel */ + break; + + case NG_L2CAP_ECHO_REQ: + ng_l2cap_l2ca_ping_rsp(cmd->con, cmd->token, + cp->reason, NULL); + break; + + case NG_L2CAP_INFO_REQ: + ng_l2cap_l2ca_get_info_rsp(cmd->con, cmd->token, + cp->reason, NULL); + break; + + default: + NG_L2CAP_ALERT( +"%s: %s - unexpected L2CAP_CommandRej. Unexpected L2CAP command opcode=%d\n", + __func__, NG_NODE_NAME(l2cap->node), cmd->code); + break; + } + + ng_l2cap_free_cmd(cmd); + } else + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_CommandRej command. " \ +"Requested ident does not exist, ident=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ident); + + NG_FREE_M(con->rx_pkt); + + return (0); +} /* ng_l2cap_process_cmd_rej */ + +/* + * Process L2CAP_ConnectReq command + */ + +static int +ng_l2cap_process_con_req(ng_l2cap_con_p con, u_int8_t ident) +{ + ng_l2cap_p l2cap = con->l2cap; + struct mbuf *m = con->rx_pkt; + ng_l2cap_con_req_cp *cp = NULL; + ng_l2cap_chan_p ch = NULL; + int error = 0; + u_int16_t dcid, psm; + + /* Get command parameters */ + NG_L2CAP_M_PULLUP(m, sizeof(*cp)); + if (m == NULL) + return (ENOBUFS); + + cp = mtod(m, ng_l2cap_con_req_cp *); + psm = le16toh(cp->psm); + dcid = le16toh(cp->scid); + + NG_FREE_M(m); + con->rx_pkt = NULL; + + /* + * Create new channel and send L2CA_ConnectInd notification + * to the upper layer protocol. + */ + + ch = ng_l2cap_new_chan(l2cap, con, psm); + if (ch == NULL) + return (send_l2cap_con_rej(con, ident, 0, dcid, + NG_L2CAP_NO_RESOURCES)); + + /* Update channel IDs */ + ch->dcid = dcid; + + /* Sent L2CA_ConnectInd notification to the upper layer */ + ch->ident = ident; + ch->state = NG_L2CAP_W4_L2CA_CON_RSP; + + error = ng_l2cap_l2ca_con_ind(ch); + if (error != 0) { + send_l2cap_con_rej(con, ident, ch->scid, dcid, + (error == ENOMEM)? NG_L2CAP_NO_RESOURCES : + NG_L2CAP_PSM_NOT_SUPPORTED); + ng_l2cap_free_chan(ch); + } + + return (error); +} /* ng_l2cap_process_con_req */ + +/* + * Process L2CAP_ConnectRsp command + */ + +static int +ng_l2cap_process_con_rsp(ng_l2cap_con_p con, u_int8_t ident) +{ + ng_l2cap_p l2cap = con->l2cap; + struct mbuf *m = con->rx_pkt; + ng_l2cap_con_rsp_cp *cp = NULL; + ng_l2cap_cmd_p cmd = NULL; + u_int16_t scid, dcid, result, status; + int error = 0; + + /* Get command parameters */ + NG_L2CAP_M_PULLUP(m, sizeof(*cp)); + if (m == NULL) + return (ENOBUFS); + + cp = mtod(m, ng_l2cap_con_rsp_cp *); + dcid = le16toh(cp->dcid); + scid = le16toh(cp->scid); + result = le16toh(cp->result); + status = le16toh(cp->status); + + NG_FREE_M(m); + con->rx_pkt = NULL; + + /* Check if we have pending command descriptor */ + cmd = ng_l2cap_cmd_by_ident(con, ident); + if (cmd == NULL) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_ConnectRsp command. ident=%d, con_handle=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ident, + con->con_handle); + + return (ENOENT); + } + + /* Verify channel state, if invalid - do nothing */ + if (cmd->ch->state != NG_L2CAP_W4_L2CAP_CON_RSP) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_ConnectRsp. " \ +"Invalid channel state, cid=%d, state=%d\n", + __func__, NG_NODE_NAME(l2cap->node), scid, + cmd->ch->state); + goto reject; + } + + /* Verify CIDs and send reject if does not match */ + if (cmd->ch->scid != scid) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_ConnectRsp. Channel IDs do not match, scid=%d(%d)\n", + __func__, NG_NODE_NAME(l2cap->node), cmd->ch->scid, + scid); + goto reject; + } + + /* + * Looks good. We got confirmation from our peer. Now process + * it. First disable RTX timer. Then check the result and send + * notification to the upper layer. If command timeout already + * happened then ignore response. + */ + + if ((error = ng_l2cap_command_untimeout(cmd)) != 0) + return (error); + + if (result == NG_L2CAP_PENDING) { + /* + * Our peer wants more time to complete connection. We shall + * start ERTX timer and wait. Keep command in the list. + */ + + cmd->ch->dcid = dcid; + ng_l2cap_command_timeout(cmd, bluetooth_l2cap_ertx_timeout()); + + error = ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token, + result, status); + if (error != 0) + ng_l2cap_free_chan(cmd->ch); + } else { + ng_l2cap_unlink_cmd(cmd); + + if (result == NG_L2CAP_SUCCESS) { + /* + * Channel is open. Complete command and move to CONFIG + * state. Since we have sent positive confirmation we + * expect to receive L2CA_Config request from the upper + * layer protocol. + */ + + cmd->ch->dcid = dcid; + cmd->ch->state = NG_L2CAP_CONFIG; + } else + /* There was an error, so close the channel */ + NG_L2CAP_INFO( +"%s: %s - failed to open L2CAP channel, result=%d, status=%d\n", + __func__, NG_NODE_NAME(l2cap->node), result, + status); + + error = ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token, + result, status); + + /* XXX do we have to remove the channel on error? */ + if (error != 0 || result != NG_L2CAP_SUCCESS) + ng_l2cap_free_chan(cmd->ch); + + ng_l2cap_free_cmd(cmd); + } + + return (error); + +reject: + /* Send reject. Do not really care about the result */ + send_l2cap_reject(con, ident, NG_L2CAP_REJ_INVALID_CID, 0, scid, dcid); + + return (0); +} /* ng_l2cap_process_con_rsp */ + +/* + * Process L2CAP_ConfigReq command + */ + +static int +ng_l2cap_process_cfg_req(ng_l2cap_con_p con, u_int8_t ident) +{ + ng_l2cap_p l2cap = con->l2cap; + struct mbuf *m = con->rx_pkt; + ng_l2cap_cfg_req_cp *cp = NULL; + ng_l2cap_chan_p ch = NULL; + u_int16_t dcid, respond, result; + ng_l2cap_cfg_opt_t hdr; + ng_l2cap_cfg_opt_val_t val; + int off, error = 0; + + /* Get command parameters */ + con->rx_pkt = NULL; + NG_L2CAP_M_PULLUP(m, sizeof(*cp)); + if (m == NULL) + return (ENOBUFS); + + cp = mtod(m, ng_l2cap_cfg_req_cp *); + dcid = le16toh(cp->dcid); + respond = NG_L2CAP_OPT_CFLAG(le16toh(cp->flags)); + m_adj(m, sizeof(*cp)); + + /* Check if we have this channel and it is in valid state */ + ch = ng_l2cap_chan_by_scid(l2cap, dcid); + if (ch == NULL) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_ConfigReq command. " \ +"Channel does not exist, cid=%d\n", + __func__, NG_NODE_NAME(l2cap->node), dcid); + goto reject; + } + + /* Verify channel state */ + if (ch->state != NG_L2CAP_CONFIG && ch->state != NG_L2CAP_OPEN) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_ConfigReq. " \ +"Invalid channel state, cid=%d, state=%d\n", + __func__, NG_NODE_NAME(l2cap->node), dcid, ch->state); + goto reject; + } + + if (ch->state == NG_L2CAP_OPEN) { /* Re-configuration */ + ch->cfg_state = 0; + ch->state = NG_L2CAP_CONFIG; + } + + for (result = 0, off = 0; ; ) { + error = get_next_l2cap_opt(m, &off, &hdr, &val); + if (error == 0) { /* We done with this packet */ + NG_FREE_M(m); + break; + } else if (error > 0) { /* Got option */ + switch (hdr.type) { + case NG_L2CAP_OPT_MTU: + ch->omtu = val.mtu; + break; + + case NG_L2CAP_OPT_FLUSH_TIMO: + ch->flush_timo = val.flush_timo; + break; + + case NG_L2CAP_OPT_QOS: + bcopy(&val.flow, &ch->iflow, sizeof(ch->iflow)); + break; + + default: /* Ignore unknown hint option */ + break; + } + } else { /* Oops, something is wrong */ + respond = 1; + + if (error == -3) { + + /* + * Adjust mbuf so we can get to the start + * of the first option we did not like. + */ + + m_adj(m, off - sizeof(hdr)); + m->m_pkthdr.len = sizeof(hdr) + hdr.length; + + result = NG_L2CAP_UNKNOWN_OPTION; + } else { + /* XXX FIXME Send other reject codes? */ + NG_FREE_M(m); + result = NG_L2CAP_REJECT; + } + + break; + } + } + + /* + * Now check and see if we have to respond. If everything was OK then + * respond contain "C flag" and (if set) we will respond with empty + * packet and will wait for more options. + * + * Other case is that we did not like peer's options and will respond + * with L2CAP_Config response command with Reject error code. + * + * When "respond == 0" than we have received all options and we will + * sent L2CA_ConfigInd event to the upper layer protocol. + */ + + if (respond) { + error = send_l2cap_cfg_rsp(con, ident, ch->dcid, result, m); + if (error != 0) { + ng_l2cap_l2ca_discon_ind(ch); + ng_l2cap_free_chan(ch); + } + } else { + /* Send L2CA_ConfigInd event to the upper layer protocol */ + ch->ident = ident; + error = ng_l2cap_l2ca_cfg_ind(ch); + if (error != 0) + ng_l2cap_free_chan(ch); + } + + return (error); + +reject: + /* Send reject. Do not really care about the result */ + NG_FREE_M(m); + + send_l2cap_reject(con, ident, NG_L2CAP_REJ_INVALID_CID, 0, 0, dcid); + + return (0); +} /* ng_l2cap_process_cfg_req */ + +/* + * Process L2CAP_ConfigRsp command + */ + +static int +ng_l2cap_process_cfg_rsp(ng_l2cap_con_p con, u_int8_t ident) +{ + ng_l2cap_p l2cap = con->l2cap; + struct mbuf *m = con->rx_pkt; + ng_l2cap_cfg_rsp_cp *cp = NULL; + ng_l2cap_cmd_p cmd = NULL; + u_int16_t scid, cflag, result; + ng_l2cap_cfg_opt_t hdr; + ng_l2cap_cfg_opt_val_t val; + int off, error = 0; + + /* Get command parameters */ + con->rx_pkt = NULL; + NG_L2CAP_M_PULLUP(m, sizeof(*cp)); + if (m == NULL) + return (ENOBUFS); + + cp = mtod(m, ng_l2cap_cfg_rsp_cp *); + scid = le16toh(cp->scid); + cflag = NG_L2CAP_OPT_CFLAG(le16toh(cp->flags)); + result = le16toh(cp->result); + m_adj(m, sizeof(*cp)); + + /* Check if we have this command */ + cmd = ng_l2cap_cmd_by_ident(con, ident); + if (cmd == NULL) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_ConfigRsp command. ident=%d, con_handle=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ident, + con->con_handle); + NG_FREE_M(m); + + return (ENOENT); + } + + /* Verify CIDs and send reject if does not match */ + if (cmd->ch->scid != scid) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_ConfigRsp. " \ +"Channel ID does not match, scid=%d(%d)\n", + __func__, NG_NODE_NAME(l2cap->node), cmd->ch->scid, + scid); + goto reject; + } + + /* Verify channel state and reject if invalid */ + if (cmd->ch->state != NG_L2CAP_CONFIG) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_ConfigRsp. " \ +"Invalid channel state, scid=%d, state=%d\n", + __func__, NG_NODE_NAME(l2cap->node), cmd->ch->scid, + cmd->ch->state); + goto reject; + } + + /* + * Looks like it is our response, so process it. First parse options, + * then verify C flag. If it is set then we shall expect more + * configuration options from the peer and we will wait. Otherwise we + * have received all options and we will send L2CA_ConfigRsp event to + * the upper layer protocol. If command timeout already happened then + * ignore response. + */ + + if ((error = ng_l2cap_command_untimeout(cmd)) != 0) { + NG_FREE_M(m); + return (error); + } + + for (off = 0; ; ) { + error = get_next_l2cap_opt(m, &off, &hdr, &val); + if (error == 0) /* We done with this packet */ + break; + else if (error > 0) { /* Got option */ + switch (hdr.type) { + case NG_L2CAP_OPT_MTU: + cmd->ch->imtu = val.mtu; + break; + + case NG_L2CAP_OPT_FLUSH_TIMO: + cmd->ch->flush_timo = val.flush_timo; + break; + + case NG_L2CAP_OPT_QOS: + bcopy(&val.flow, &cmd->ch->oflow, + sizeof(cmd->ch->oflow)); + break; + + default: /* Ignore unknown hint option */ + break; + } + } else { + /* + * XXX FIXME What to do here? + * + * This is really BAD :( options packet was broken, or + * peer sent us option that we did not understand. Let + * upper layer know and do not wait for more options. + */ + + NG_L2CAP_ALERT( +"%s: %s - failed to parse configuration options, error=%d\n", + __func__, NG_NODE_NAME(l2cap->node), error); + + result = NG_L2CAP_UNKNOWN; + cflag = 0; + + break; + } + } + + NG_FREE_M(m); + + if (cflag) /* Restart timer and wait for more options */ + ng_l2cap_command_timeout(cmd, bluetooth_l2cap_rtx_timeout()); + else { + ng_l2cap_unlink_cmd(cmd); + + /* Send L2CA_Config response to the upper layer protocol */ + error = ng_l2cap_l2ca_cfg_rsp(cmd->ch, cmd->token, result); + if (error != 0) { + /* + * XXX FIXME what to do here? we were not able to send + * response to the upper layer protocol, so for now + * just close the channel. Send L2CAP_Disconnect to + * remote peer? + */ + + NG_L2CAP_ERR( +"%s: %s - failed to send L2CA_Config response, error=%d\n", + __func__, NG_NODE_NAME(l2cap->node), error); + + ng_l2cap_free_chan(cmd->ch); + } + + ng_l2cap_free_cmd(cmd); + } + + return (error); + +reject: + /* Send reject. Do not really care about the result */ + NG_FREE_M(m); + + send_l2cap_reject(con, ident, NG_L2CAP_REJ_INVALID_CID, 0, scid, 0); + + return (0); +} /* ng_l2cap_process_cfg_rsp */ + +/* + * Process L2CAP_DisconnectReq command + */ + +static int +ng_l2cap_process_discon_req(ng_l2cap_con_p con, u_int8_t ident) +{ + ng_l2cap_p l2cap = con->l2cap; + ng_l2cap_discon_req_cp *cp = NULL; + ng_l2cap_chan_p ch = NULL; + ng_l2cap_cmd_p cmd = NULL; + u_int16_t scid, dcid; + + /* Get command parameters */ + NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*cp)); + if (con->rx_pkt == NULL) + return (ENOBUFS); + + cp = mtod(con->rx_pkt, ng_l2cap_discon_req_cp *); + dcid = le16toh(cp->dcid); + scid = le16toh(cp->scid); + + NG_FREE_M(con->rx_pkt); + + /* Check if we have this channel and it is in valid state */ + ch = ng_l2cap_chan_by_scid(l2cap, dcid); + if (ch == NULL) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_DisconnectReq message. " \ +"Channel does not exist, cid=%d\n", + __func__, NG_NODE_NAME(l2cap->node), dcid); + goto reject; + } + + /* XXX Verify channel state and reject if invalid -- is that true? */ + if (ch->state != NG_L2CAP_OPEN && ch->state != NG_L2CAP_CONFIG && + ch->state != NG_L2CAP_W4_L2CAP_DISCON_RSP) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_DisconnectReq. " \ +"Invalid channel state, cid=%d, state=%d\n", + __func__, NG_NODE_NAME(l2cap->node), dcid, ch->state); + goto reject; + } + + /* Match destination channel ID */ + if (ch->dcid != scid || ch->scid != dcid) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_DisconnectReq. " \ +"Channel IDs does not match, channel: scid=%d, dcid=%d, " \ +"request: scid=%d, dcid=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->scid, ch->dcid, + scid, dcid); + goto reject; + } + + /* + * Looks good, so notify upper layer protocol that channel is about + * to be disconnected and send L2CA_DisconnectInd message. Then respond + * with L2CAP_DisconnectRsp. + */ + + if (ch->state != NG_L2CAP_W4_L2CAP_DISCON_RSP) { + ng_l2cap_l2ca_discon_ind(ch); /* do not care about result */ + ng_l2cap_free_chan(ch); + } + + /* Send L2CAP_DisconnectRsp */ + cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_DISCON_RSP, 0); + if (cmd == NULL) + return (ENOMEM); + + _ng_l2cap_discon_rsp(cmd->aux, ident, dcid, scid); + if (cmd->aux == NULL) { + ng_l2cap_free_cmd(cmd); + + return (ENOBUFS); + } + + /* Link command to the queue */ + ng_l2cap_link_cmd(con, cmd); + ng_l2cap_lp_deliver(con); + + return (0); + +reject: + /* Send reject. Do not really care about the result */ + send_l2cap_reject(con, ident, NG_L2CAP_REJ_INVALID_CID, 0, scid, dcid); + + return (0); +} /* ng_l2cap_process_discon_req */ + +/* + * Process L2CAP_DisconnectRsp command + */ + +static int +ng_l2cap_process_discon_rsp(ng_l2cap_con_p con, u_int8_t ident) +{ + ng_l2cap_p l2cap = con->l2cap; + ng_l2cap_discon_rsp_cp *cp = NULL; + ng_l2cap_cmd_p cmd = NULL; + u_int16_t scid, dcid; + int error = 0; + + /* Get command parameters */ + NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*cp)); + if (con->rx_pkt == NULL) + return (ENOBUFS); + + cp = mtod(con->rx_pkt, ng_l2cap_discon_rsp_cp *); + dcid = le16toh(cp->dcid); + scid = le16toh(cp->scid); + + NG_FREE_M(con->rx_pkt); + + /* Check if we have pending command descriptor */ + cmd = ng_l2cap_cmd_by_ident(con, ident); + if (cmd == NULL) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_DisconnectRsp command. ident=%d, con_handle=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ident, + con->con_handle); + goto out; + } + + /* Verify channel state, do nothing if invalid */ + if (cmd->ch->state != NG_L2CAP_W4_L2CAP_DISCON_RSP) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_DisconnectRsp. " \ +"Invalid channel state, cid=%d, state=%d\n", + __func__, NG_NODE_NAME(l2cap->node), scid, + cmd->ch->state); + goto out; + } + + /* Verify CIDs and send reject if does not match */ + if (cmd->ch->scid != scid || cmd->ch->dcid != dcid) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_DisconnectRsp. " \ +"Channel IDs do not match, scid=%d(%d), dcid=%d(%d)\n", + __func__, NG_NODE_NAME(l2cap->node), cmd->ch->scid, + scid, cmd->ch->dcid, dcid); + goto out; + } + + /* + * Looks like we have successfuly disconnected channel, so notify + * upper layer. If command timeout already happened then ignore + * response. + */ + + if ((error = ng_l2cap_command_untimeout(cmd)) != 0) + goto out; + + error = ng_l2cap_l2ca_discon_rsp(cmd->ch, cmd->token, NG_L2CAP_SUCCESS); + ng_l2cap_free_chan(cmd->ch); /* this will free commands too */ +out: + return (error); +} /* ng_l2cap_process_discon_rsp */ + +/* + * Process L2CAP_EchoReq command + */ + +static int +ng_l2cap_process_echo_req(ng_l2cap_con_p con, u_int8_t ident) +{ + ng_l2cap_p l2cap = con->l2cap; + ng_l2cap_cmd_hdr_t *hdr = NULL; + ng_l2cap_cmd_p cmd = NULL; + + con->rx_pkt = ng_l2cap_prepend(con->rx_pkt, sizeof(*hdr)); + if (con->rx_pkt == NULL) { + NG_L2CAP_ALERT( +"%s: %s - ng_l2cap_prepend() failed, size=%zd\n", + __func__, NG_NODE_NAME(l2cap->node), sizeof(*hdr)); + + return (ENOBUFS); + } + + hdr = mtod(con->rx_pkt, ng_l2cap_cmd_hdr_t *); + hdr->code = NG_L2CAP_ECHO_RSP; + hdr->ident = ident; + hdr->length = htole16(con->rx_pkt->m_pkthdr.len - sizeof(*hdr)); + + cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_ECHO_RSP, 0); + if (cmd == NULL) { + NG_FREE_M(con->rx_pkt); + + return (ENOBUFS); + } + + /* Attach data and link command to the queue */ + cmd->aux = con->rx_pkt; + con->rx_pkt = NULL; + ng_l2cap_link_cmd(con, cmd); + ng_l2cap_lp_deliver(con); + + return (0); +} /* ng_l2cap_process_echo_req */ + +/* + * Process L2CAP_EchoRsp command + */ + +static int +ng_l2cap_process_echo_rsp(ng_l2cap_con_p con, u_int8_t ident) +{ + ng_l2cap_p l2cap = con->l2cap; + ng_l2cap_cmd_p cmd = NULL; + int error = 0; + + /* Check if we have this command */ + cmd = ng_l2cap_cmd_by_ident(con, ident); + if (cmd != NULL) { + /* If command timeout already happened then ignore response */ + if ((error = ng_l2cap_command_untimeout(cmd)) != 0) { + NG_FREE_M(con->rx_pkt); + return (error); + } + + ng_l2cap_unlink_cmd(cmd); + + error = ng_l2cap_l2ca_ping_rsp(cmd->con, cmd->token, + NG_L2CAP_SUCCESS, con->rx_pkt); + + ng_l2cap_free_cmd(cmd); + con->rx_pkt = NULL; + } else { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_EchoRsp command. " \ +"Requested ident does not exist, ident=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ident); + NG_FREE_M(con->rx_pkt); + } + + return (error); +} /* ng_l2cap_process_echo_rsp */ + +/* + * Process L2CAP_InfoReq command + */ + +static int +ng_l2cap_process_info_req(ng_l2cap_con_p con, u_int8_t ident) +{ + ng_l2cap_p l2cap = con->l2cap; + ng_l2cap_cmd_p cmd = NULL; + u_int16_t type; + + /* Get command parameters */ + NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(ng_l2cap_info_req_cp)); + if (con->rx_pkt == NULL) + return (ENOBUFS); + + type = le16toh(mtod(con->rx_pkt, ng_l2cap_info_req_cp *)->type); + NG_FREE_M(con->rx_pkt); + + cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_INFO_RSP, 0); + if (cmd == NULL) + return (ENOMEM); + + switch (type) { + case NG_L2CAP_CONNLESS_MTU: + _ng_l2cap_info_rsp(cmd->aux, ident, NG_L2CAP_CONNLESS_MTU, + NG_L2CAP_SUCCESS, NG_L2CAP_MTU_DEFAULT); + break; + + default: + _ng_l2cap_info_rsp(cmd->aux, ident, type, + NG_L2CAP_NOT_SUPPORTED, 0); + break; + } + + if (cmd->aux == NULL) { + ng_l2cap_free_cmd(cmd); + + return (ENOBUFS); + } + + /* Link command to the queue */ + ng_l2cap_link_cmd(con, cmd); + ng_l2cap_lp_deliver(con); + + return (0); +} /* ng_l2cap_process_info_req */ + +/* + * Process L2CAP_InfoRsp command + */ + +static int +ng_l2cap_process_info_rsp(ng_l2cap_con_p con, u_int8_t ident) +{ + ng_l2cap_p l2cap = con->l2cap; + ng_l2cap_info_rsp_cp *cp = NULL; + ng_l2cap_cmd_p cmd = NULL; + int error = 0; + + /* Get command parameters */ + NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*cp)); + if (con->rx_pkt == NULL) + return (ENOBUFS); + + cp = mtod(con->rx_pkt, ng_l2cap_info_rsp_cp *); + cp->type = le16toh(cp->type); + cp->result = le16toh(cp->result); + m_adj(con->rx_pkt, sizeof(*cp)); + + /* Check if we have pending command descriptor */ + cmd = ng_l2cap_cmd_by_ident(con, ident); + if (cmd == NULL) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP_InfoRsp command. " \ +"Requested ident does not exist, ident=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ident); + NG_FREE_M(con->rx_pkt); + + return (ENOENT); + } + + /* If command timeout already happened then ignore response */ + if ((error = ng_l2cap_command_untimeout(cmd)) != 0) { + NG_FREE_M(con->rx_pkt); + return (error); + } + + ng_l2cap_unlink_cmd(cmd); + + if (cp->result == NG_L2CAP_SUCCESS) { + switch (cp->type) { + case NG_L2CAP_CONNLESS_MTU: + if (con->rx_pkt->m_pkthdr.len == sizeof(u_int16_t)) + *mtod(con->rx_pkt, u_int16_t *) = + le16toh(*mtod(con->rx_pkt,u_int16_t *)); + else { + cp->result = NG_L2CAP_UNKNOWN; /* XXX */ + + NG_L2CAP_ERR( +"%s: %s - invalid L2CAP_InfoRsp command. " \ +"Bad connectionless MTU parameter, len=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + con->rx_pkt->m_pkthdr.len); + } + break; + + default: + NG_L2CAP_WARN( +"%s: %s - invalid L2CAP_InfoRsp command. Unknown info type=%d\n", + __func__, NG_NODE_NAME(l2cap->node), cp->type); + break; + } + } + + error = ng_l2cap_l2ca_get_info_rsp(cmd->con, cmd->token, + cp->result, con->rx_pkt); + + ng_l2cap_free_cmd(cmd); + con->rx_pkt = NULL; + + return (error); +} /* ng_l2cap_process_info_rsp */ + +/* + * Send L2CAP reject + */ + +static int +send_l2cap_reject(ng_l2cap_con_p con, u_int8_t ident, u_int16_t reason, + u_int16_t mtu, u_int16_t scid, u_int16_t dcid) +{ + ng_l2cap_cmd_p cmd = NULL; + + cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_CMD_REJ, 0); + if (cmd == NULL) + return (ENOMEM); + + _ng_l2cap_cmd_rej(cmd->aux, cmd->ident, reason, mtu, scid, dcid); + if (cmd->aux == NULL) { + ng_l2cap_free_cmd(cmd); + + return (ENOBUFS); + } + + /* Link command to the queue */ + ng_l2cap_link_cmd(con, cmd); + ng_l2cap_lp_deliver(con); + + return (0); +} /* send_l2cap_reject */ + +/* + * Send L2CAP connection reject + */ + +static int +send_l2cap_con_rej(ng_l2cap_con_p con, u_int8_t ident, u_int16_t scid, + u_int16_t dcid, u_int16_t result) +{ + ng_l2cap_cmd_p cmd = NULL; + + cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_CON_RSP, 0); + if (cmd == NULL) + return (ENOMEM); + + _ng_l2cap_con_rsp(cmd->aux, cmd->ident, scid, dcid, result, 0); + if (cmd->aux == NULL) { + ng_l2cap_free_cmd(cmd); + + return (ENOBUFS); + } + + /* Link command to the queue */ + ng_l2cap_link_cmd(con, cmd); + ng_l2cap_lp_deliver(con); + + return (0); +} /* send_l2cap_con_rej */ + +/* + * Send L2CAP config response + */ + +static int +send_l2cap_cfg_rsp(ng_l2cap_con_p con, u_int8_t ident, u_int16_t scid, + u_int16_t result, struct mbuf *opt) +{ + ng_l2cap_cmd_p cmd = NULL; + + cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_CFG_RSP, 0); + if (cmd == NULL) { + NG_FREE_M(opt); + + return (ENOMEM); + } + + _ng_l2cap_cfg_rsp(cmd->aux, cmd->ident, scid, 0, result, opt); + if (cmd->aux == NULL) { + ng_l2cap_free_cmd(cmd); + + return (ENOBUFS); + } + + /* Link command to the queue */ + ng_l2cap_link_cmd(con, cmd); + ng_l2cap_lp_deliver(con); + + return (0); +} /* send_l2cap_cfg_rsp */ + +/* + * Get next L2CAP configuration option + * + * Return codes: + * 0 no option + * 1 we have got option + * -1 header too short + * -2 bad option value or length + * -3 unknown option + */ + +static int +get_next_l2cap_opt(struct mbuf *m, int *off, ng_l2cap_cfg_opt_p hdr, + ng_l2cap_cfg_opt_val_p val) +{ + int hint, len = m->m_pkthdr.len - (*off); + + if (len == 0) + return (0); + if (len < 0 || len < sizeof(*hdr)) + return (-1); + + m_copydata(m, *off, sizeof(*hdr), (caddr_t) hdr); + *off += sizeof(*hdr); + len -= sizeof(*hdr); + + hint = NG_L2CAP_OPT_HINT(hdr->type); + hdr->type &= NG_L2CAP_OPT_HINT_MASK; + + switch (hdr->type) { + case NG_L2CAP_OPT_MTU: + if (hdr->length != NG_L2CAP_OPT_MTU_SIZE || len < hdr->length) + return (-2); + + m_copydata(m, *off, NG_L2CAP_OPT_MTU_SIZE, (caddr_t) val); + val->mtu = le16toh(val->mtu); + *off += NG_L2CAP_OPT_MTU_SIZE; + break; + + case NG_L2CAP_OPT_FLUSH_TIMO: + if (hdr->length != NG_L2CAP_OPT_FLUSH_TIMO_SIZE || + len < hdr->length) + return (-2); + + m_copydata(m, *off, NG_L2CAP_OPT_FLUSH_TIMO_SIZE, (caddr_t)val); + val->flush_timo = le16toh(val->flush_timo); + *off += NG_L2CAP_OPT_FLUSH_TIMO_SIZE; + break; + + case NG_L2CAP_OPT_QOS: + if (hdr->length != NG_L2CAP_OPT_QOS_SIZE || len < hdr->length) + return (-2); + + m_copydata(m, *off, NG_L2CAP_OPT_QOS_SIZE, (caddr_t) val); + val->flow.token_rate = le32toh(val->flow.token_rate); + val->flow.token_bucket_size = + le32toh(val->flow.token_bucket_size); + val->flow.peak_bandwidth = le32toh(val->flow.peak_bandwidth); + val->flow.latency = le32toh(val->flow.latency); + val->flow.delay_variation = le32toh(val->flow.delay_variation); + *off += NG_L2CAP_OPT_QOS_SIZE; + break; + + default: + if (hint) + *off += hdr->length; + else + return (-3); + break; + } + + return (1); +} /* get_next_l2cap_opt */ + diff --git a/sys/netgraph7/bluetooth/l2cap/ng_l2cap_evnt.h b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_evnt.h new file mode 100644 index 0000000000..d4cd0c93e9 --- /dev/null +++ b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_evnt.h @@ -0,0 +1,40 @@ +/* + * ng_l2cap_evnt.h + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_l2cap_evnt.h,v 1.1 2002/11/24 19:47:06 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/l2cap/ng_l2cap_evnt.h,v 1.3 2005/01/07 01:45:43 imp Exp $ + */ + +#ifndef _NETGRAPH_L2CAP_EVNT_H_ +#define _NETGRAPH_L2CAP_EVNT_H_ + +int ng_l2cap_receive (ng_l2cap_con_p); + +#endif /* ndef _NETGRAPH_L2CAP_EVNT_H_ */ + diff --git a/sys/netgraph7/bluetooth/l2cap/ng_l2cap_llpi.c b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_llpi.c new file mode 100644 index 0000000000..d97e4e933d --- /dev/null +++ b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_llpi.c @@ -0,0 +1,906 @@ +/* + * ng_l2cap_llpi.c + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_l2cap_llpi.c,v 1.5 2003/09/08 19:11:45 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/l2cap/ng_l2cap_llpi.c,v 1.9 2005/07/29 14:44:17 emax Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/****************************************************************************** + ****************************************************************************** + ** Lower Layer Protocol (HCI) Interface module + ****************************************************************************** + ******************************************************************************/ + +/* + * Send LP_ConnectReq event to the lower layer protocol. Create new connection + * descriptor and initialize it. Create LP_ConnectReq event and send it to the + * lower layer, then adjust connection state and start timer. The function WILL + * FAIL if connection to the remote unit already exists. + */ + +int +ng_l2cap_lp_con_req(ng_l2cap_p l2cap, bdaddr_p bdaddr) +{ + struct ng_mesg *msg = NULL; + ng_hci_lp_con_req_ep *ep = NULL; + ng_l2cap_con_p con = NULL; + int error = 0; + + /* Verify that we DO NOT have connection to the remote unit */ + con = ng_l2cap_con_by_addr(l2cap, bdaddr); + if (con != NULL) { + NG_L2CAP_ALERT( +"%s: %s - unexpected LP_ConnectReq event. " \ +"Connection already exists, state=%d, con_handle=%d\n", + __func__, NG_NODE_NAME(l2cap->node), con->state, + con->con_handle); + + return (EEXIST); + } + + /* Check if lower layer protocol is still connected */ + if (l2cap->hci == NULL || NG_HOOK_NOT_VALID(l2cap->hci)) { + NG_L2CAP_ERR( +"%s: %s - hook \"%s\" is not connected or valid\n", + __func__, NG_NODE_NAME(l2cap->node), NG_L2CAP_HOOK_HCI); + + return (ENOTCONN); + } + + /* Create and intialize new connection descriptor */ + con = ng_l2cap_new_con(l2cap, bdaddr); + if (con == NULL) + return (ENOMEM); + + /* Create and send LP_ConnectReq event */ + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_CON_REQ, + sizeof(*ep), M_NOWAIT); + if (msg == NULL) { + ng_l2cap_free_con(con); + + return (ENOMEM); + } + + ep = (ng_hci_lp_con_req_ep *) (msg->data); + bcopy(bdaddr, &ep->bdaddr, sizeof(ep->bdaddr)); + ep->link_type = NG_HCI_LINK_ACL; + + con->flags |= NG_L2CAP_CON_OUTGOING; + con->state = NG_L2CAP_W4_LP_CON_CFM; + ng_l2cap_lp_timeout(con); + + NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->hci, 0); + if (error != 0) { + if ((error = ng_l2cap_lp_untimeout(con)) != 0) + return (error); + + ng_l2cap_free_con(con); + } + + return (error); +} /* ng_l2cap_lp_con_req */ + +/* + * Process LP_ConnectCfm event from the lower layer protocol. It could be + * positive or negative. Verify remote unit address then stop the timer and + * process event. + */ + +int +ng_l2cap_lp_con_cfm(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + ng_hci_lp_con_cfm_ep *ep = NULL; + ng_l2cap_con_p con = NULL; + int error = 0; + + /* Check message */ + if (msg->header.arglen != sizeof(*ep)) { + NG_L2CAP_ALERT( +"%s: %s - invalid LP_ConnectCfm[Neg] message size\n", + __func__, NG_NODE_NAME(l2cap->node)); + error = EMSGSIZE; + goto out; + } + + ep = (ng_hci_lp_con_cfm_ep *) (msg->data); + + /* Check if we have requested/accepted this connection */ + con = ng_l2cap_con_by_addr(l2cap, &ep->bdaddr); + if (con == NULL) { + NG_L2CAP_ERR( +"%s: %s - unexpected LP_ConnectCfm event. Connection does not exist\n", + __func__, NG_NODE_NAME(l2cap->node)); + error = ENOENT; + goto out; + } + + /* Check connection state */ + if (con->state != NG_L2CAP_W4_LP_CON_CFM) { + NG_L2CAP_ALERT( +"%s: %s - unexpected LP_ConnectCfm event. " \ +"Invalid connection state, state=%d, con_handle=%d\n", + __func__, NG_NODE_NAME(l2cap->node), con->state, + con->con_handle); + error = EINVAL; + goto out; + } + + /* + * Looks like it is our confirmation. It is safe now to cancel + * connection timer and notify upper layer. If timeout already + * happened then ignore connection confirmation and let timeout + * handle that. + */ + + if ((error = ng_l2cap_lp_untimeout(con)) != 0) + goto out; + + if (ep->status == 0) { + con->state = NG_L2CAP_CON_OPEN; + con->con_handle = ep->con_handle; + ng_l2cap_lp_deliver(con); + } else /* Negative confirmation - remove connection descriptor */ + ng_l2cap_con_fail(con, ep->status); +out: + return (error); +} /* ng_l2cap_lp_con_cfm */ + +/* + * Process LP_ConnectInd event from the lower layer protocol. This is a good + * place to put some extra check on remote unit address and/or class. We could + * even forward this information to control hook (or check against internal + * black list) and thus implement some kind of firewall. But for now be simple + * and create new connection descriptor, start timer and send LP_ConnectRsp + * event (i.e. accept connection). + */ + +int +ng_l2cap_lp_con_ind(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + ng_hci_lp_con_ind_ep *ep = NULL; + ng_hci_lp_con_rsp_ep *rp = NULL; + struct ng_mesg *rsp = NULL; + ng_l2cap_con_p con = NULL; + int error = 0; + + /* Check message */ + if (msg->header.arglen != sizeof(*ep)) { + NG_L2CAP_ALERT( +"%s: %s - invalid LP_ConnectInd message size\n", + __func__, NG_NODE_NAME(l2cap->node)); + error = EMSGSIZE; + goto out; + } + + ep = (ng_hci_lp_con_ind_ep *) (msg->data); + + /* Make sure we have only one connection to the remote unit */ + con = ng_l2cap_con_by_addr(l2cap, &ep->bdaddr); + if (con != NULL) { + NG_L2CAP_ALERT( +"%s: %s - unexpected LP_ConnectInd event. " \ +"Connection already exists, state=%d, con_handle=%d\n", + __func__, NG_NODE_NAME(l2cap->node), con->state, + con->con_handle); + error = EEXIST; + goto out; + } + + /* Check if lower layer protocol is still connected */ + if (l2cap->hci == NULL || NG_HOOK_NOT_VALID(l2cap->hci)) { + NG_L2CAP_ERR( +"%s: %s - hook \"%s\" is not connected or valid", + __func__, NG_NODE_NAME(l2cap->node), NG_L2CAP_HOOK_HCI); + error = ENOTCONN; + goto out; + } + + /* Create and intialize new connection descriptor */ + con = ng_l2cap_new_con(l2cap, &ep->bdaddr); + if (con == NULL) { + error = ENOMEM; + goto out; + } + + /* Create and send LP_ConnectRsp event */ + NG_MKMESSAGE(rsp, NGM_HCI_COOKIE, NGM_HCI_LP_CON_RSP, + sizeof(*rp), M_NOWAIT); + if (rsp == NULL) { + ng_l2cap_free_con(con); + error = ENOMEM; + goto out; + } + + rp = (ng_hci_lp_con_rsp_ep *)(rsp->data); + rp->status = 0x00; /* accept connection */ + rp->link_type = NG_HCI_LINK_ACL; + bcopy(&ep->bdaddr, &rp->bdaddr, sizeof(rp->bdaddr)); + + con->state = NG_L2CAP_W4_LP_CON_CFM; + ng_l2cap_lp_timeout(con); + + NG_SEND_MSG_HOOK(error, l2cap->node, rsp, l2cap->hci, 0); + if (error != 0) { + if ((error = ng_l2cap_lp_untimeout(con)) != 0) + goto out; + + ng_l2cap_free_con(con); + } +out: + return (error); +} /* ng_hci_lp_con_ind */ + +/* + * Process LP_DisconnectInd event from the lower layer protocol. We have been + * disconnected from the remote unit. So notify the upper layer protocol. + */ + +int +ng_l2cap_lp_discon_ind(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + ng_hci_lp_discon_ind_ep *ep = NULL; + ng_l2cap_con_p con = NULL; + int error = 0; + + /* Check message */ + if (msg->header.arglen != sizeof(*ep)) { + NG_L2CAP_ALERT( +"%s: %s - invalid LP_DisconnectInd message size\n", + __func__, NG_NODE_NAME(l2cap->node)); + error = EMSGSIZE; + goto out; + } + + ep = (ng_hci_lp_discon_ind_ep *) (msg->data); + + /* Check if we have this connection */ + con = ng_l2cap_con_by_handle(l2cap, ep->con_handle); + if (con == NULL) { + NG_L2CAP_ERR( +"%s: %s - unexpected LP_DisconnectInd event. " \ +"Connection does not exist, con_handle=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ep->con_handle); + error = ENOENT; + goto out; + } + + /* XXX Verify connection state -- do we need to check this? */ + if (con->state != NG_L2CAP_CON_OPEN) { + NG_L2CAP_ERR( +"%s: %s - unexpected LP_DisconnectInd event. " \ +"Invalid connection state, state=%d, con_handle=%d\n", + __func__, NG_NODE_NAME(l2cap->node), con->state, + con->con_handle); + error = EINVAL; + goto out; + } + + /* + * Notify upper layer and remove connection + * Note: The connection could have auto disconnect timeout set. Try + * to remove it. If auto disconnect timeout happened then ignore + * disconnect indication and let timeout handle that. + */ + + if (con->flags & NG_L2CAP_CON_AUTO_DISCON_TIMO) + if ((error = ng_l2cap_discon_untimeout(con)) != 0) + return (error); + + ng_l2cap_con_fail(con, ep->reason); +out: + return (error); +} /* ng_l2cap_lp_discon_ind */ + +/* + * Send LP_QoSSetupReq event to the lower layer protocol + */ + +int +ng_l2cap_lp_qos_req(ng_l2cap_p l2cap, u_int16_t con_handle, + ng_l2cap_flow_p flow) +{ + struct ng_mesg *msg = NULL; + ng_hci_lp_qos_req_ep *ep = NULL; + ng_l2cap_con_p con = NULL; + int error = 0; + + /* Verify that we have this connection */ + con = ng_l2cap_con_by_handle(l2cap, con_handle); + if (con == NULL) { + NG_L2CAP_ERR( +"%s: %s - unexpected LP_QoSSetupReq event. " \ +"Connection does not exist, con_handle=%d\n", + __func__, NG_NODE_NAME(l2cap->node), con_handle); + + return (ENOENT); + } + + /* Verify connection state */ + if (con->state != NG_L2CAP_CON_OPEN) { + NG_L2CAP_ERR( +"%s: %s - unexpected LP_QoSSetupReq event. " \ +"Invalid connection state, state=%d, con_handle=%d\n", + __func__, NG_NODE_NAME(l2cap->node), con->state, + con->con_handle); + + return (EINVAL); + } + + /* Check if lower layer protocol is still connected */ + if (l2cap->hci == NULL || NG_HOOK_NOT_VALID(l2cap->hci)) { + NG_L2CAP_ERR( +"%s: %s - hook \"%s\" is not connected or valid", + __func__, NG_NODE_NAME(l2cap->node), NG_L2CAP_HOOK_HCI); + + return (ENOTCONN); + } + + /* Create and send LP_QoSSetupReq event */ + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_QOS_REQ, + sizeof(*ep), M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + ep = (ng_hci_lp_qos_req_ep *) (msg->data); + ep->con_handle = con_handle; + ep->flags = flow->flags; + ep->service_type = flow->service_type; + ep->token_rate = flow->token_rate; + ep->peak_bandwidth = flow->peak_bandwidth; + ep->latency = flow->latency; + ep->delay_variation = flow->delay_variation; + + NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->hci, 0); + + return (error); +} /* ng_l2cap_lp_con_req */ + +/* + * Process LP_QoSSetupCfm from the lower layer protocol + */ + +int +ng_l2cap_lp_qos_cfm(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + ng_hci_lp_qos_cfm_ep *ep = NULL; + int error = 0; + + /* Check message */ + if (msg->header.arglen != sizeof(*ep)) { + NG_L2CAP_ALERT( +"%s: %s - invalid LP_QoSSetupCfm[Neg] message size\n", + __func__, NG_NODE_NAME(l2cap->node)); + error = EMSGSIZE; + goto out; + } + + ep = (ng_hci_lp_qos_cfm_ep *) (msg->data); + /* XXX FIXME do something */ +out: + return (error); +} /* ng_l2cap_lp_qos_cfm */ + +/* + * Process LP_QoSViolationInd event from the lower layer protocol. Lower + * layer protocol has detected QoS Violation, so we MUST notify the + * upper layer. + */ + +int +ng_l2cap_lp_qos_ind(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + ng_hci_lp_qos_ind_ep *ep = NULL; + ng_l2cap_con_p con = NULL; + int error = 0; + + /* Check message */ + if (msg->header.arglen != sizeof(*ep)) { + NG_L2CAP_ALERT( +"%s: %s - invalid LP_QoSViolation message size\n", + __func__, NG_NODE_NAME(l2cap->node)); + error = EMSGSIZE; + goto out; + } + + ep = (ng_hci_lp_qos_ind_ep *) (msg->data); + + /* Check if we have this connection */ + con = ng_l2cap_con_by_handle(l2cap, ep->con_handle); + if (con == NULL) { + NG_L2CAP_ERR( +"%s: %s - unexpected LP_QoSViolationInd event. " \ +"Connection does not exist, con_handle=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ep->con_handle); + error = ENOENT; + goto out; + } + + /* Verify connection state */ + if (con->state != NG_L2CAP_CON_OPEN) { + NG_L2CAP_ERR( +"%s: %s - unexpected LP_QoSViolationInd event. " \ +"Invalid connection state, state=%d, con_handle=%d\n", + __func__, NG_NODE_NAME(l2cap->node), con->state, + con->con_handle); + error = EINVAL; + goto out; + } + + /* XXX FIXME Notify upper layer and terminate channels if required */ +out: + return (error); +} /* ng_l2cap_qos_ind */ + +/* + * Prepare L2CAP packet. Prepend packet with L2CAP packet header and then + * segment it according to HCI MTU. + */ + +int +ng_l2cap_lp_send(ng_l2cap_con_p con, u_int16_t dcid, struct mbuf *m0) +{ + ng_l2cap_p l2cap = con->l2cap; + ng_l2cap_hdr_t *l2cap_hdr = NULL; + ng_hci_acldata_pkt_t *acl_hdr = NULL; + struct mbuf *m_last = NULL, *m = NULL; + int len, flag = NG_HCI_PACKET_START; + + KASSERT((con->tx_pkt == NULL), +("%s: %s - another packet pending?!\n", __func__, NG_NODE_NAME(l2cap->node))); + KASSERT((l2cap->pkt_size > 0), +("%s: %s - invalid l2cap->pkt_size?!\n", __func__, NG_NODE_NAME(l2cap->node))); + + /* Prepend mbuf with L2CAP header */ + m0 = ng_l2cap_prepend(m0, sizeof(*l2cap_hdr)); + if (m0 == NULL) { + NG_L2CAP_ALERT( +"%s: %s - ng_l2cap_prepend(%zd) failed\n", + __func__, NG_NODE_NAME(l2cap->node), + sizeof(*l2cap_hdr)); + + goto fail; + } + + l2cap_hdr = mtod(m0, ng_l2cap_hdr_t *); + l2cap_hdr->length = htole16(m0->m_pkthdr.len - sizeof(*l2cap_hdr)); + l2cap_hdr->dcid = htole16(dcid); + + /* + * Segment single L2CAP packet according to the HCI layer MTU. Convert + * each segment into ACL data packet and prepend it with ACL data packet + * header. Link all segments together via m_nextpkt link. + * + * XXX BC (Broadcast flag) will always be 0 (zero). + */ + + while (m0 != NULL) { + /* Check length of the packet against HCI MTU */ + len = m0->m_pkthdr.len; + if (len > l2cap->pkt_size) { + m = m_split(m0, l2cap->pkt_size, M_DONTWAIT); + if (m == NULL) { + NG_L2CAP_ALERT( +"%s: %s - m_split(%d) failed\n", __func__, NG_NODE_NAME(l2cap->node), + l2cap->pkt_size); + goto fail; + } + + len = l2cap->pkt_size; + } + + /* Convert packet fragment into ACL data packet */ + m0 = ng_l2cap_prepend(m0, sizeof(*acl_hdr)); + if (m0 == NULL) { + NG_L2CAP_ALERT( +"%s: %s - ng_l2cap_prepend(%zd) failed\n", + __func__, NG_NODE_NAME(l2cap->node), + sizeof(*acl_hdr)); + goto fail; + } + + acl_hdr = mtod(m0, ng_hci_acldata_pkt_t *); + acl_hdr->type = NG_HCI_ACL_DATA_PKT; + acl_hdr->length = htole16(len); + acl_hdr->con_handle = htole16(NG_HCI_MK_CON_HANDLE( + con->con_handle, flag, 0)); + + /* Add fragment to the chain */ + m0->m_nextpkt = NULL; + + if (con->tx_pkt == NULL) + con->tx_pkt = m_last = m0; + else { + m_last->m_nextpkt = m0; + m_last = m0; + } + + NG_L2CAP_INFO( +"%s: %s - attaching ACL packet, con_handle=%d, PB=%#x, length=%d\n", + __func__, NG_NODE_NAME(l2cap->node), con->con_handle, + flag, len); + + m0 = m; + m = NULL; + flag = NG_HCI_PACKET_FRAGMENT; + } + + return (0); +fail: + NG_FREE_M(m0); + NG_FREE_M(m); + + while (con->tx_pkt != NULL) { + m = con->tx_pkt->m_nextpkt; + m_freem(con->tx_pkt); + con->tx_pkt = m; + } + + return (ENOBUFS); +} /* ng_l2cap_lp_send */ + +/* + * Receive ACL data packet from the HCI layer. First strip ACL packet header + * and get connection handle, PB (Packet Boundary) flag and payload length. + * Then find connection descriptor and verify its state. Then process ACL + * packet as follows. + * + * 1) If we got first segment (pb == NG_HCI_PACKET_START) then extract L2CAP + * header and get total length of the L2CAP packet. Then start new L2CAP + * packet. + * + * 2) If we got other (then first :) segment (pb == NG_HCI_PACKET_FRAGMENT) + * then add segment to the packet. + */ + +int +ng_l2cap_lp_receive(ng_l2cap_p l2cap, struct mbuf *m) +{ + ng_hci_acldata_pkt_t *acl_hdr = NULL; + ng_l2cap_hdr_t *l2cap_hdr = NULL; + ng_l2cap_con_p con = NULL; + u_int16_t con_handle, length, pb; + int error = 0; + + /* Check ACL data packet */ + if (m->m_pkthdr.len < sizeof(*acl_hdr)) { + NG_L2CAP_ERR( +"%s: %s - invalid ACL data packet. Packet too small, length=%d\n", + __func__, NG_NODE_NAME(l2cap->node), m->m_pkthdr.len); + error = EMSGSIZE; + goto drop; + } + + /* Strip ACL data packet header */ + NG_L2CAP_M_PULLUP(m, sizeof(*acl_hdr)); + if (m == NULL) + return (ENOBUFS); + + acl_hdr = mtod(m, ng_hci_acldata_pkt_t *); + m_adj(m, sizeof(*acl_hdr)); + + /* Get ACL connection handle, PB flag and payload length */ + acl_hdr->con_handle = le16toh(acl_hdr->con_handle); + con_handle = NG_HCI_CON_HANDLE(acl_hdr->con_handle); + pb = NG_HCI_PB_FLAG(acl_hdr->con_handle); + length = le16toh(acl_hdr->length); + + NG_L2CAP_INFO( +"%s: %s - got ACL data packet, con_handle=%d, PB=%#x, length=%d\n", + __func__, NG_NODE_NAME(l2cap->node), con_handle, pb, length); + + /* Get connection descriptor */ + con = ng_l2cap_con_by_handle(l2cap, con_handle); + if (con == NULL) { + NG_L2CAP_ERR( +"%s: %s - unexpected ACL data packet. " \ +"Connection does not exist, con_handle=%d\n", + __func__, NG_NODE_NAME(l2cap->node), con_handle); + error = ENOENT; + goto drop; + } + + /* Verify connection state */ + if (con->state != NG_L2CAP_CON_OPEN) { + NG_L2CAP_ERR( +"%s: %s - unexpected ACL data packet. Invalid connection state=%d\n", + __func__, NG_NODE_NAME(l2cap->node), con->state); + error = EHOSTDOWN; + goto drop; + } + + /* Process packet */ + if (pb == NG_HCI_PACKET_START) { + if (con->rx_pkt != NULL) { + NG_L2CAP_ERR( +"%s: %s - dropping incomplete L2CAP packet, got %d bytes, want %d bytes\n", + __func__, NG_NODE_NAME(l2cap->node), + con->rx_pkt->m_pkthdr.len, con->rx_pkt_len); + NG_FREE_M(con->rx_pkt); + con->rx_pkt_len = 0; + } + + /* Get L2CAP header */ + if (m->m_pkthdr.len < sizeof(*l2cap_hdr)) { + NG_L2CAP_ERR( +"%s: %s - invalid L2CAP packet start fragment. Packet too small, length=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + m->m_pkthdr.len); + error = EMSGSIZE; + goto drop; + } + + NG_L2CAP_M_PULLUP(m, sizeof(*l2cap_hdr)); + if (m == NULL) + return (ENOBUFS); + + l2cap_hdr = mtod(m, ng_l2cap_hdr_t *); + + NG_L2CAP_INFO( +"%s: %s - staring new L2CAP packet, con_handle=%d, length=%d\n", + __func__, NG_NODE_NAME(l2cap->node), con_handle, + le16toh(l2cap_hdr->length)); + + /* Start new L2CAP packet */ + con->rx_pkt = m; + con->rx_pkt_len = le16toh(l2cap_hdr->length)+sizeof(*l2cap_hdr); + } else if (pb == NG_HCI_PACKET_FRAGMENT) { + if (con->rx_pkt == NULL) { + NG_L2CAP_ERR( +"%s: %s - unexpected ACL data packet fragment, con_handle=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + con->con_handle); + goto drop; + } + + /* Add fragment to the L2CAP packet */ + m_cat(con->rx_pkt, m); + con->rx_pkt->m_pkthdr.len += length; + } else { + NG_L2CAP_ERR( +"%s: %s - invalid ACL data packet. Invalid PB flag=%#x\n", + __func__, NG_NODE_NAME(l2cap->node), pb); + error = EINVAL; + goto drop; + } + + con->rx_pkt_len -= length; + if (con->rx_pkt_len < 0) { + NG_L2CAP_ALERT( +"%s: %s - packet length mismatch. Got %d bytes, offset %d bytes\n", + __func__, NG_NODE_NAME(l2cap->node), + con->rx_pkt->m_pkthdr.len, con->rx_pkt_len); + NG_FREE_M(con->rx_pkt); + con->rx_pkt_len = 0; + } else if (con->rx_pkt_len == 0) { + /* OK, we have got complete L2CAP packet, so process it */ + error = ng_l2cap_receive(con); + con->rx_pkt = NULL; + con->rx_pkt_len = 0; + } + + return (error); + +drop: + NG_FREE_M(m); + + return (error); +} /* ng_l2cap_lp_receive */ + +/* + * Send queued ACL packets to the HCI layer + */ + +void +ng_l2cap_lp_deliver(ng_l2cap_con_p con) +{ + ng_l2cap_p l2cap = con->l2cap; + struct mbuf *m = NULL; + int error; + + /* Check connection */ + if (con->state != NG_L2CAP_CON_OPEN) + return; + + if (con->tx_pkt == NULL) + ng_l2cap_con_wakeup(con); + + if (con->tx_pkt == NULL) + return; + + /* Check if lower layer protocol is still connected */ + if (l2cap->hci == NULL || NG_HOOK_NOT_VALID(l2cap->hci)) { + NG_L2CAP_ERR( +"%s: %s - hook \"%s\" is not connected or valid", + __func__, NG_NODE_NAME(l2cap->node), NG_L2CAP_HOOK_HCI); + + goto drop; /* XXX what to do with "pending"? */ + } + + /* Send ACL data packets */ + while (con->pending < con->l2cap->num_pkts && con->tx_pkt != NULL) { + m = con->tx_pkt; + con->tx_pkt = con->tx_pkt->m_nextpkt; + m->m_nextpkt = NULL; + + NG_L2CAP_INFO( +"%s: %s - sending ACL packet, con_handle=%d, len=%d\n", + __func__, NG_NODE_NAME(l2cap->node), con->con_handle, + m->m_pkthdr.len); + + NG_SEND_DATA_ONLY(error, l2cap->hci, m); + if (error != 0) { + NG_L2CAP_ERR( +"%s: %s - could not send ACL data packet, con_handle=%d, error=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + con->con_handle, error); + + goto drop; /* XXX what to do with "pending"? */ + } + + con->pending ++; + } + + NG_L2CAP_INFO( +"%s: %s - %d ACL packets have been sent, con_handle=%d\n", + __func__, NG_NODE_NAME(l2cap->node), con->pending, + con->con_handle); + + return; + +drop: + while (con->tx_pkt != NULL) { + m = con->tx_pkt->m_nextpkt; + m_freem(con->tx_pkt); + con->tx_pkt = m; + } +} /* ng_l2cap_lp_deliver */ + +/* + * Process connection timeout. Remove connection from the list. If there + * are any channels that wait for the connection then notify them. Free + * connection descriptor. + */ + +void +ng_l2cap_process_lp_timeout(node_p node, hook_p hook, void *arg1, int con_handle) +{ + ng_l2cap_p l2cap = NULL; + ng_l2cap_con_p con = NULL; + + if (NG_NODE_NOT_VALID(node)) { + printf("%s: Netgraph node is not valid\n", __func__); + return; + } + + l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(node); + con = ng_l2cap_con_by_handle(l2cap, con_handle); + + if (con == NULL) { + NG_L2CAP_ALERT( +"%s: %s - could not find connection, con_handle=%d\n", + __func__, NG_NODE_NAME(node), con_handle); + return; + } + + if (!(con->flags & NG_L2CAP_CON_LP_TIMO)) { + NG_L2CAP_ALERT( +"%s: %s - no pending LP timeout, con_handle=%d, state=%d, flags=%#x\n", + __func__, NG_NODE_NAME(node), con_handle, con->state, + con->flags); + return; + } + + /* + * Notify channels that connection has timed out. This will remove + * connection, channels and pending commands. + */ + + con->flags &= ~NG_L2CAP_CON_LP_TIMO; + ng_l2cap_con_fail(con, NG_L2CAP_TIMEOUT); +} /* ng_l2cap_process_lp_timeout */ + +/* + * Process auto disconnect timeout and send LP_DisconReq event to the + * lower layer protocol + */ + +void +ng_l2cap_process_discon_timeout(node_p node, hook_p hook, void *arg1, int con_handle) +{ + ng_l2cap_p l2cap = NULL; + ng_l2cap_con_p con = NULL; + struct ng_mesg *msg = NULL; + ng_hci_lp_discon_req_ep *ep = NULL; + int error; + + if (NG_NODE_NOT_VALID(node)) { + printf("%s: Netgraph node is not valid\n", __func__); + return; + } + + l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(node); + con = ng_l2cap_con_by_handle(l2cap, con_handle); + + if (con == NULL) { + NG_L2CAP_ALERT( +"%s: %s - could not find connection, con_handle=%d\n", + __func__, NG_NODE_NAME(node), con_handle); + return; + } + + if (!(con->flags & NG_L2CAP_CON_AUTO_DISCON_TIMO)) { + NG_L2CAP_ALERT( +"%s: %s - no pending disconnect timeout, con_handle=%d, state=%d, flags=%#x\n", + __func__, NG_NODE_NAME(node), con_handle, con->state, + con->flags); + return; + } + + con->flags &= ~NG_L2CAP_CON_AUTO_DISCON_TIMO; + + /* Check if lower layer protocol is still connected */ + if (l2cap->hci == NULL || NG_HOOK_NOT_VALID(l2cap->hci)) { + NG_L2CAP_ERR( +"%s: %s - hook \"%s\" is not connected or valid\n", + __func__, NG_NODE_NAME(l2cap->node), NG_L2CAP_HOOK_HCI); + return; + } + + /* Create and send LP_DisconReq event */ + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_LP_DISCON_REQ, + sizeof(*ep), M_NOWAIT); + if (msg == NULL) + return; + + ep = (ng_hci_lp_discon_req_ep *) (msg->data); + ep->con_handle = con->con_handle; + ep->reason = 0x13; /* User Ended Connection */ + + NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->hci, 0); +} /* ng_l2cap_process_discon_timeout */ + diff --git a/sys/netgraph7/bluetooth/l2cap/ng_l2cap_llpi.h b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_llpi.h new file mode 100644 index 0000000000..a630bd4193 --- /dev/null +++ b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_llpi.h @@ -0,0 +1,51 @@ +/* + * ng_l2cap_llpi.h + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_l2cap_llpi.h,v 1.2 2003/04/28 21:44:59 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/l2cap/ng_l2cap_llpi.h,v 1.3 2005/01/07 01:45:43 imp Exp $ + */ + +#ifndef _NETGRAPH_L2CAP_LLPI_H_ +#define _NETGRAPH_L2CAP_LLPI_H_ + +int ng_l2cap_lp_con_req (ng_l2cap_p, bdaddr_p); +int ng_l2cap_lp_con_cfm (ng_l2cap_p, struct ng_mesg *); +int ng_l2cap_lp_con_ind (ng_l2cap_p, struct ng_mesg *); +int ng_l2cap_lp_discon_ind (ng_l2cap_p, struct ng_mesg *); +int ng_l2cap_lp_qos_req (ng_l2cap_p, u_int16_t, ng_l2cap_flow_p); +int ng_l2cap_lp_qos_cfm (ng_l2cap_p, struct ng_mesg *); +int ng_l2cap_lp_qos_ind (ng_l2cap_p, struct ng_mesg *); +int ng_l2cap_lp_send (ng_l2cap_con_p, u_int16_t,struct mbuf *); +int ng_l2cap_lp_receive (ng_l2cap_p, struct mbuf *); +void ng_l2cap_lp_deliver (ng_l2cap_con_p); +void ng_l2cap_process_lp_timeout (node_p, hook_p, void *, int); +void ng_l2cap_process_discon_timeout (node_p, hook_p, void *, int); + +#endif /* ndef _NETGRAPH_L2CAP_LLPI_H_ */ + diff --git a/sys/netgraph7/bluetooth/l2cap/ng_l2cap_main.c b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_main.c new file mode 100644 index 0000000000..47905a8c3e --- /dev/null +++ b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_main.c @@ -0,0 +1,759 @@ +/* + * ng_l2cap_main.c + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_l2cap_main.c,v 1.2 2003/04/28 21:44:59 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/l2cap/ng_l2cap_main.c,v 1.5 2005/01/07 01:45:43 imp Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/****************************************************************************** + ****************************************************************************** + ** This node implements Link Layer Control and Adaptation Protocol (L2CAP) + ****************************************************************************** + ******************************************************************************/ + +/* MALLOC define */ +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_L2CAP, "netgraph_l2cap", + "Netgraph Bluetooth L2CAP node"); +#else +#define M_NETGRAPH_L2CAP M_NETGRAPH +#endif /* NG_SEPARATE_MALLOC */ + +/* Netgraph node methods */ +static ng_constructor_t ng_l2cap_constructor; +static ng_shutdown_t ng_l2cap_shutdown; +static ng_newhook_t ng_l2cap_newhook; +static ng_connect_t ng_l2cap_connect; +static ng_disconnect_t ng_l2cap_disconnect; +static ng_rcvmsg_t ng_l2cap_lower_rcvmsg; +static ng_rcvmsg_t ng_l2cap_upper_rcvmsg; +static ng_rcvmsg_t ng_l2cap_default_rcvmsg; +static ng_rcvdata_t ng_l2cap_rcvdata; + +/* Netgraph node type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_L2CAP_NODE_TYPE, + .constructor = ng_l2cap_constructor, + .rcvmsg = ng_l2cap_default_rcvmsg, + .shutdown = ng_l2cap_shutdown, + .newhook = ng_l2cap_newhook, + .connect = ng_l2cap_connect, + .rcvdata = ng_l2cap_rcvdata, + .disconnect = ng_l2cap_disconnect, + .cmdlist = ng_l2cap_cmdlist, +}; +NETGRAPH_INIT(l2cap, &typestruct); +MODULE_VERSION(ng_l2cap, NG_BLUETOOTH_VERSION); +MODULE_DEPEND(ng_l2cap, ng_bluetooth, NG_BLUETOOTH_VERSION, + NG_BLUETOOTH_VERSION, NG_BLUETOOTH_VERSION); + +/***************************************************************************** + ***************************************************************************** + ** Netgraph methods implementation + ***************************************************************************** + *****************************************************************************/ + +static void ng_l2cap_cleanup (ng_l2cap_p); +static void ng_l2cap_destroy_channels (ng_l2cap_p); + +/* + * Create new instance of L2CAP node + */ + +static int +ng_l2cap_constructor(node_p node) +{ + ng_l2cap_p l2cap = NULL; + + /* Create new L2CAP node */ + MALLOC(l2cap, ng_l2cap_p, sizeof(*l2cap), + M_NETGRAPH_L2CAP, M_NOWAIT|M_ZERO); + if (l2cap == NULL) + return (ENOMEM); + + l2cap->node = node; + l2cap->debug = NG_L2CAP_WARN_LEVEL; + l2cap->discon_timo = 5; /* sec */ + + LIST_INIT(&l2cap->con_list); + LIST_INIT(&l2cap->chan_list); + + NG_NODE_SET_PRIVATE(node, l2cap); + NG_NODE_FORCE_WRITER(node); + + return (0); +} /* ng_l2cap_constructor */ + +/* + * Shutdown L2CAP node + */ + +static int +ng_l2cap_shutdown(node_p node) +{ + ng_l2cap_p l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(node); + + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + + /* Clean up L2CAP node. Delete all connection, channels and commands */ + l2cap->node = NULL; + ng_l2cap_cleanup(l2cap); + + bzero(l2cap, sizeof(*l2cap)); + FREE(l2cap, M_NETGRAPH_L2CAP); + + return (0); +} /* ng_l2cap_shutdown */ + +/* + * Give our OK for a hook to be added. HCI layer is connected to the HCI + * (NG_L2CAP_HOOK_HCI) hook. As per specification L2CAP layer MUST provide + * Procol/Service Multiplexing, so the L2CAP node provides separate hooks + * for SDP (NG_L2CAP_HOOK_SDP), RFCOMM (NG_L2CAP_HOOK_RFCOMM) and TCP + * (NG_L2CAP_HOOK_TCP) protcols. Unknown PSM will be forwarded to + * NG_L2CAP_HOOK_ORPHAN hook. Control node/application is connected to + * control (NG_L2CAP_HOOK_CTL) hook. + */ + +static int +ng_l2cap_newhook(node_p node, hook_p hook, char const *name) +{ + ng_l2cap_p l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(node); + hook_p *h = NULL; + + if (strcmp(name, NG_L2CAP_HOOK_HCI) == 0) + h = &l2cap->hci; + else if (strcmp(name, NG_L2CAP_HOOK_L2C) == 0) + h = &l2cap->l2c; + else if (strcmp(name, NG_L2CAP_HOOK_CTL) == 0) + h = &l2cap->ctl; + else + return (EINVAL); + + if (*h != NULL) + return (EISCONN); + + *h = hook; + + return (0); +} /* ng_l2cap_newhook */ + +/* + * Give our final OK to connect hook. Nothing to do here. + */ + +static int +ng_l2cap_connect(hook_p hook) +{ + ng_l2cap_p l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + int error = 0; + + if (hook == l2cap->hci) + NG_HOOK_SET_RCVMSG(hook, ng_l2cap_lower_rcvmsg); + else + if (hook == l2cap->l2c || hook == l2cap->ctl) { + NG_HOOK_SET_RCVMSG(hook, ng_l2cap_upper_rcvmsg); + + /* Send delayed notification to the upper layer */ + error = ng_send_fn(l2cap->node, hook, ng_l2cap_send_hook_info, + NULL, 0); + } else + error = EINVAL; + + return (error); +} /* ng_l2cap_connect */ + +/* + * Disconnect the hook. For downstream hook we must notify upper layers. + * + * XXX For upstream hooks this is really ugly :( Hook was disconnected and it + * XXX is now too late to do anything. For now we just clean up our own mess + * XXX and remove all channels that use disconnected upstream hook. If we don't + * XXX do that then L2CAP node can get out of sync with upper layers. + * XXX No notification will be sent to remote peer. + */ + +static int +ng_l2cap_disconnect(hook_p hook) +{ + ng_l2cap_p l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + hook_p *h = NULL; + + if (hook == l2cap->hci) { + ng_l2cap_cleanup(l2cap); + h = &l2cap->hci; + } else + if (hook == l2cap->l2c) { + ng_l2cap_destroy_channels(l2cap); + h = &l2cap->l2c; + } else + if (hook == l2cap->ctl) + h = &l2cap->ctl; + else + return (EINVAL); + + *h = NULL; + + /* Shutdown when all hooks are disconnected */ + if (NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0 && + NG_NODE_IS_VALID(NG_HOOK_NODE(hook))) + ng_rmnode_self(NG_HOOK_NODE(hook)); + + return (0); +} /* ng_l2cap_disconnect */ + +/* + * Process control message from lower layer + */ + +static int +ng_l2cap_lower_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + ng_l2cap_p l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(node); + struct ng_mesg *msg = NGI_MSG(item); /* item still has message */ + int error = 0; + + switch (msg->header.typecookie) { + case NGM_HCI_COOKIE: + switch (msg->header.cmd) { + /* HCI node is ready */ + case NGM_HCI_NODE_UP: { + ng_hci_node_up_ep *ep = NULL; + + if (msg->header.arglen != sizeof(*ep)) + error = EMSGSIZE; + else { + ep = (ng_hci_node_up_ep *)(msg->data); + + NG_L2CAP_INFO( +"%s: %s - HCI node is up, bdaddr: %x:%x:%x:%x:%x:%x, " \ +"pkt_size=%d bytes, num_pkts=%d\n", __func__, NG_NODE_NAME(l2cap->node), + ep->bdaddr.b[5], ep->bdaddr.b[4], + ep->bdaddr.b[3], ep->bdaddr.b[2], + ep->bdaddr.b[1], ep->bdaddr.b[0], + ep->pkt_size, ep->num_pkts); + + bcopy(&ep->bdaddr, &l2cap->bdaddr, + sizeof(l2cap->bdaddr)); + l2cap->pkt_size = ep->pkt_size; + l2cap->num_pkts = ep->num_pkts; + + /* Notify upper layers */ + ng_l2cap_send_hook_info(l2cap->node, + l2cap->l2c, NULL, 0); + ng_l2cap_send_hook_info(l2cap->node, + l2cap->ctl, NULL, 0); + } + } break; + + case NGM_HCI_SYNC_CON_QUEUE: { + ng_hci_sync_con_queue_ep *ep = NULL; + ng_l2cap_con_p con = NULL; + + if (msg->header.arglen != sizeof(*ep)) + error = EMSGSIZE; + else { + ep = (ng_hci_sync_con_queue_ep *)(msg->data); + con = ng_l2cap_con_by_handle(l2cap, + ep->con_handle); + if (con == NULL) + break; + + NG_L2CAP_INFO( +"%s: %s - sync HCI connection queue, con_handle=%d, pending=%d, completed=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + ep->con_handle, con->pending, + ep->completed); + + con->pending -= ep->completed; + if (con->pending < 0) { + NG_L2CAP_WARN( +"%s: %s - pending packet counter is out of sync! " \ +"con_handle=%d, pending=%d, completed=%d\n", __func__, + NG_NODE_NAME(l2cap->node), + con->con_handle, con->pending, + ep->completed); + + con->pending = 0; + } + + ng_l2cap_lp_deliver(con); + } + } break; + + /* LP_ConnectCfm[Neg] */ + case NGM_HCI_LP_CON_CFM: + error = ng_l2cap_lp_con_cfm(l2cap, msg); + break; + + /* LP_ConnectInd */ + case NGM_HCI_LP_CON_IND: + error = ng_l2cap_lp_con_ind(l2cap, msg); + break; + + /* LP_DisconnectInd */ + case NGM_HCI_LP_DISCON_IND: + error = ng_l2cap_lp_discon_ind(l2cap, msg); + break; + + /* LP_QoSSetupCfm[Neg] */ + case NGM_HCI_LP_QOS_CFM: + error = ng_l2cap_lp_qos_cfm(l2cap, msg); + break; + + /* LP_OoSViolationInd */ + case NGM_HCI_LP_QOS_IND: + error = ng_l2cap_lp_qos_ind(l2cap, msg); + break; + + default: + error = EINVAL; + break; + } + break; + + default: + return (ng_l2cap_default_rcvmsg(node, item, lasthook)); + /* NOT REACHED */ + } + + NG_FREE_ITEM(item); + + return (error); +} /* ng_l2cap_lower_rcvmsg */ + +/* + * Process control message from upper layer + */ + +static int +ng_l2cap_upper_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + ng_l2cap_p l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(node); + struct ng_mesg *msg = NGI_MSG(item); /* item still has message */ + int error = 0; + + switch (msg->header.typecookie) { + case NGM_L2CAP_COOKIE: + switch (msg->header.cmd) { + /* L2CA_Connect */ + case NGM_L2CAP_L2CA_CON: + error = ng_l2cap_l2ca_con_req(l2cap, msg); + break; + + /* L2CA_ConnectRsp */ + case NGM_L2CAP_L2CA_CON_RSP: + error = ng_l2cap_l2ca_con_rsp_req(l2cap, msg); + break; + + /* L2CA_Config */ + case NGM_L2CAP_L2CA_CFG: + error = ng_l2cap_l2ca_cfg_req(l2cap, msg); + break; + + /* L2CA_ConfigRsp */ + case NGM_L2CAP_L2CA_CFG_RSP: + error = ng_l2cap_l2ca_cfg_rsp_req(l2cap, msg); + break; + + /* L2CA_Disconnect */ + case NGM_L2CAP_L2CA_DISCON: + error = ng_l2cap_l2ca_discon_req(l2cap, msg); + break; + + /* L2CA_GroupCreate */ + case NGM_L2CAP_L2CA_GRP_CREATE: + error = ng_l2cap_l2ca_grp_create(l2cap, msg); + break; + + /* L2CA_GroupClose */ + case NGM_L2CAP_L2CA_GRP_CLOSE: + error = ng_l2cap_l2ca_grp_close(l2cap, msg); + break; + + /* L2CA_GroupAddMember */ + case NGM_L2CAP_L2CA_GRP_ADD_MEMBER: + error = ng_l2cap_l2ca_grp_add_member_req(l2cap, msg); + break; + + /* L2CA_GroupDeleteMember */ + case NGM_L2CAP_L2CA_GRP_REM_MEMBER: + error = ng_l2cap_l2ca_grp_rem_member(l2cap, msg); + break; + + /* L2CA_GroupMembership */ + case NGM_L2CAP_L2CA_GRP_MEMBERSHIP: + error = ng_l2cap_l2ca_grp_get_members(l2cap, msg); + break; + + /* L2CA_Ping */ + case NGM_L2CAP_L2CA_PING: + error = ng_l2cap_l2ca_ping_req(l2cap, msg); + break; + + /* L2CA_GetInfo */ + case NGM_L2CAP_L2CA_GET_INFO: + error = ng_l2cap_l2ca_get_info_req(l2cap, msg); + break; + + /* L2CA_EnableCLT */ + case NGM_L2CAP_L2CA_ENABLE_CLT: + error = ng_l2cap_l2ca_enable_clt(l2cap, msg); + break; + + default: + return (ng_l2cap_default_rcvmsg(node, item, lasthook)); + /* NOT REACHED */ + } + break; + + default: + return (ng_l2cap_default_rcvmsg(node, item, lasthook)); + /* NOT REACHED */ + } + + NG_FREE_ITEM(item); + + return (error); +} /* ng_l2cap_upper_rcvmsg */ + +/* + * Default control message processing routine + */ + +static int +ng_l2cap_default_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + ng_l2cap_p l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(node); + struct ng_mesg *msg = NULL, *rsp = NULL; + int error = 0; + + /* Detach and process message */ + NGI_GET_MSG(item, msg); + + switch (msg->header.typecookie) { + case NGM_GENERIC_COOKIE: + switch (msg->header.cmd) { + case NGM_TEXT_STATUS: + NG_MKRESPONSE(rsp, msg, NG_TEXTRESPONSE, M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + snprintf(rsp->data, NG_TEXTRESPONSE, + "bdaddr %x:%x:%x:%x:%x:%x, " \ + "pkt_size %d\n" \ + "Hooks %s %s %s\n" \ + "Flags %#x\n", + l2cap->bdaddr.b[5], l2cap->bdaddr.b[4], + l2cap->bdaddr.b[3], l2cap->bdaddr.b[2], + l2cap->bdaddr.b[1], l2cap->bdaddr.b[0], + l2cap->pkt_size, + (l2cap->hci != NULL)? + NG_L2CAP_HOOK_HCI : "", + (l2cap->l2c != NULL)? + NG_L2CAP_HOOK_L2C : "", + (l2cap->ctl != NULL)? + NG_L2CAP_HOOK_CTL : "", + l2cap->flags); + break; + + default: + error = EINVAL; + break; + } + break; + + /* Messages from the upper layer or directed to the local node */ + case NGM_L2CAP_COOKIE: + switch (msg->header.cmd) { + /* Get node flags */ + case NGM_L2CAP_NODE_GET_FLAGS: + NG_MKRESPONSE(rsp, msg, sizeof(ng_l2cap_node_flags_ep), + M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + *((ng_l2cap_node_flags_ep *)(rsp->data)) = + l2cap->flags; + break; + + /* Get node debug */ + case NGM_L2CAP_NODE_GET_DEBUG: + NG_MKRESPONSE(rsp, msg, sizeof(ng_l2cap_node_debug_ep), + M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + *((ng_l2cap_node_debug_ep *)(rsp->data)) = + l2cap->debug; + break; + + /* Set node debug */ + case NGM_L2CAP_NODE_SET_DEBUG: + if (msg->header.arglen != + sizeof(ng_l2cap_node_debug_ep)) + error = EMSGSIZE; + else + l2cap->debug = + *((ng_l2cap_node_debug_ep *)(msg->data)); + break; + + /* Get connection list */ + case NGM_L2CAP_NODE_GET_CON_LIST: { + ng_l2cap_con_p con = NULL; + ng_l2cap_node_con_list_ep *e1 = NULL; + ng_l2cap_node_con_ep *e2 = NULL; + int n = 0; + + /* Count number of connections */ + LIST_FOREACH(con, &l2cap->con_list, next) + n++; + if (n > NG_L2CAP_MAX_CON_NUM) + n = NG_L2CAP_MAX_CON_NUM; + + /* Prepare response */ + NG_MKRESPONSE(rsp, msg, + sizeof(*e1) + n * sizeof(*e2), M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + e1 = (ng_l2cap_node_con_list_ep *)(rsp->data); + e2 = (ng_l2cap_node_con_ep *)(e1 + 1); + + e1->num_connections = n; + + LIST_FOREACH(con, &l2cap->con_list, next) { + e2->state = con->state; + + e2->flags = con->flags; + if (con->tx_pkt != NULL) + e2->flags |= NG_L2CAP_CON_TX; + if (con->rx_pkt != NULL) + e2->flags |= NG_L2CAP_CON_RX; + + e2->pending = con->pending; + + e2->con_handle = con->con_handle; + bcopy(&con->remote, &e2->remote, + sizeof(e2->remote)); + + e2 ++; + if (--n <= 0) + break; + } + } break; + + /* Get channel list */ + case NGM_L2CAP_NODE_GET_CHAN_LIST: { + ng_l2cap_chan_p ch = NULL; + ng_l2cap_node_chan_list_ep *e1 = NULL; + ng_l2cap_node_chan_ep *e2 = NULL; + int n = 0; + + /* Count number of channels */ + LIST_FOREACH(ch, &l2cap->chan_list, next) + n ++; + if (n > NG_L2CAP_MAX_CHAN_NUM) + n = NG_L2CAP_MAX_CHAN_NUM; + + /* Prepare response */ + NG_MKRESPONSE(rsp, msg, + sizeof(ng_l2cap_node_chan_list_ep) + + n * sizeof(ng_l2cap_node_chan_ep), M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + + e1 = (ng_l2cap_node_chan_list_ep *)(rsp->data); + e2 = (ng_l2cap_node_chan_ep *)(e1 + 1); + + e1->num_channels = n; + + LIST_FOREACH(ch, &l2cap->chan_list, next) { + e2->state = ch->state; + + e2->scid = ch->scid; + e2->dcid = ch->dcid; + + e2->imtu = ch->imtu; + e2->omtu = ch->omtu; + + e2->psm = ch->psm; + bcopy(&ch->con->remote, &e2->remote, + sizeof(e2->remote)); + + e2 ++; + if (--n <= 0) + break; + } + } break; + + case NGM_L2CAP_NODE_GET_AUTO_DISCON_TIMO: + NG_MKRESPONSE(rsp, msg, + sizeof(ng_l2cap_node_auto_discon_ep), M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + *((ng_l2cap_node_auto_discon_ep *)(rsp->data)) = + l2cap->discon_timo; + break; + + case NGM_L2CAP_NODE_SET_AUTO_DISCON_TIMO: + if (msg->header.arglen != + sizeof(ng_l2cap_node_auto_discon_ep)) + error = EMSGSIZE; + else + l2cap->discon_timo = + *((ng_l2cap_node_auto_discon_ep *) + (msg->data)); + break; + + default: + error = EINVAL; + break; + } + break; + + default: + error = EINVAL; + break; + } + + NG_RESPOND_MSG(error, node, item, rsp); + NG_FREE_MSG(msg); + + return (error); +} /* ng_l2cap_rcvmsg */ + +/* + * Process data packet from one of our hooks. + * + * From the HCI hook we expect to receive ACL data packets. ACL data packets + * gets re-assembled into one L2CAP packet (according to length) and then gets + * processed. + * + * NOTE: We expect to receive L2CAP packet header in the first fragment. + * Otherwise we WILL NOT be able to get length of the L2CAP packet. + * + * Signaling L2CAP packets (destination channel ID == 0x1) are processed within + * the node. Connectionless data packets (destination channel ID == 0x2) will + * be forwarded to appropriate upstream hook unless it is not connected or + * connectionless traffic for the specified PSM was disabled. + * + * From the upstream hooks we expect to receive data packets. These data + * packets will be converted into L2CAP data packets. The length of each + * L2CAP packet must not exceed channel's omtu (our peer's imtu). Then + * these L2CAP packets will be converted to ACL data packets (according to + * HCI layer MTU) and sent to lower layer. + * + * No data is expected from the control hook. + */ + +static int +ng_l2cap_rcvdata(hook_p hook, item_p item) +{ + ng_l2cap_p l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct mbuf *m = NULL; + int error = 0; + + /* Detach mbuf, discard item and process data */ + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + if (hook == l2cap->hci) + error = ng_l2cap_lp_receive(l2cap, m); + else if (hook == l2cap->l2c) + error = ng_l2cap_l2ca_write_req(l2cap, m); + else { + NG_FREE_M(m); + error = EINVAL; + } + + return (error); +} /* ng_l2cap_rcvdata */ + +/* + * Clean all connections, channels and commands for the L2CAP node + */ + +static void +ng_l2cap_cleanup(ng_l2cap_p l2cap) +{ + ng_l2cap_con_p con = NULL; + + /* Clean up connection and channels */ + while (!LIST_EMPTY(&l2cap->con_list)) { + con = LIST_FIRST(&l2cap->con_list); + + if (con->flags & NG_L2CAP_CON_LP_TIMO) + ng_l2cap_lp_untimeout(con); + else if (con->flags & NG_L2CAP_CON_AUTO_DISCON_TIMO) + ng_l2cap_discon_untimeout(con); + + /* Connection terminated by local host */ + ng_l2cap_con_fail(con, 0x16); + } +} /* ng_l2cap_cleanup */ + +/* + * Destroy all channels that use specified upstream hook + */ + +static void +ng_l2cap_destroy_channels(ng_l2cap_p l2cap) +{ + while (!LIST_EMPTY(&l2cap->chan_list)) + ng_l2cap_free_chan(LIST_FIRST(&l2cap->chan_list)); +} /* ng_l2cap_destroy_channels_by_hook */ + diff --git a/sys/netgraph7/bluetooth/l2cap/ng_l2cap_misc.c b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_misc.c new file mode 100644 index 0000000000..8f69b4a603 --- /dev/null +++ b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_misc.c @@ -0,0 +1,643 @@ +/* + * ng_l2cap_misc.c + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_l2cap_misc.c,v 1.5 2003/09/08 19:11:45 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/l2cap/ng_l2cap_misc.c,v 1.12 2005/08/31 18:13:23 emax Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static u_int16_t ng_l2cap_get_cid (ng_l2cap_p); + +/****************************************************************************** + ****************************************************************************** + ** Utility routines + ****************************************************************************** + ******************************************************************************/ + +/* + * Send hook information to the upper layer + */ + +void +ng_l2cap_send_hook_info(node_p node, hook_p hook, void *arg1, int arg2) +{ + ng_l2cap_p l2cap = NULL; + struct ng_mesg *msg = NULL; + int error = 0; + + if (node == NULL || NG_NODE_NOT_VALID(node) || + hook == NULL || NG_HOOK_NOT_VALID(hook)) + return; + + l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(node); + if (l2cap->hci == NULL || NG_HOOK_NOT_VALID(l2cap->hci) || + bcmp(&l2cap->bdaddr, NG_HCI_BDADDR_ANY, sizeof(l2cap->bdaddr)) == 0) + return; + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_NODE_HOOK_INFO, + sizeof(bdaddr_t), M_NOWAIT); + if (msg != NULL) { + bcopy(&l2cap->bdaddr, msg->data, sizeof(bdaddr_t)); + NG_SEND_MSG_HOOK(error, node, msg, hook, 0); + } else + error = ENOMEM; + + if (error != 0) + NG_L2CAP_INFO( +"%s: %s - failed to send HOOK_INFO message to hook \"%s\", error=%d\n", + __func__, NG_NODE_NAME(l2cap->node), NG_HOOK_NAME(hook), + error); +} /* ng_l2cap_send_hook_info */ + +/* + * Create new connection descriptor for the "remote" unit. + * Will link connection descriptor to the l2cap node. + */ + +ng_l2cap_con_p +ng_l2cap_new_con(ng_l2cap_p l2cap, bdaddr_p bdaddr) +{ + static int fake_con_handle = 0x0f00; + ng_l2cap_con_p con = NULL; + + /* Create new connection descriptor */ + MALLOC(con, ng_l2cap_con_p, sizeof(*con), M_NETGRAPH_L2CAP, + M_NOWAIT|M_ZERO); + if (con == NULL) + return (NULL); + + con->l2cap = l2cap; + con->state = NG_L2CAP_CON_CLOSED; + + /* + * XXX + * + * Assign fake connection handle to the connection descriptor. + * Bluetooth specification marks 0x0f00 - 0x0fff connection + * handles as reserved. We need this fake connection handles + * for timeouts. Connection handle will be passed as argument + * to timeout so when timeout happens we can find the right + * connection descriptor. We can not pass pointers, because + * timeouts are external (to Netgraph) events and there might + * be a race when node/hook goes down and timeout event already + * went into node's queue + */ + + con->con_handle = fake_con_handle ++; + if (fake_con_handle > 0x0fff) + fake_con_handle = 0x0f00; + + bcopy(bdaddr, &con->remote, sizeof(con->remote)); + ng_callout_init(&con->con_timo); + + con->ident = NG_L2CAP_FIRST_IDENT - 1; + TAILQ_INIT(&con->cmd_list); + + /* Link connection */ + LIST_INSERT_HEAD(&l2cap->con_list, con, next); + + return (con); +} /* ng_l2cap_new_con */ + +/* + * Add reference to the connection descriptor + */ + +void +ng_l2cap_con_ref(ng_l2cap_con_p con) +{ + con->refcnt ++; + + if (con->flags & NG_L2CAP_CON_AUTO_DISCON_TIMO) { + if ((con->state != NG_L2CAP_CON_OPEN) || + (con->flags & NG_L2CAP_CON_OUTGOING) == 0) + panic( +"%s: %s - bad auto disconnect timeout, state=%d, flags=%#x\n", + __func__, NG_NODE_NAME(con->l2cap->node), + con->state, con->flags); + + ng_l2cap_discon_untimeout(con); + } +} /* ng_l2cap_con_ref */ + +/* + * Remove reference from the connection descriptor + */ + +void +ng_l2cap_con_unref(ng_l2cap_con_p con) +{ + con->refcnt --; + + if (con->refcnt < 0) + panic( +"%s: %s - con->refcnt < 0\n", __func__, NG_NODE_NAME(con->l2cap->node)); + + /* + * Set auto disconnect timer only if the following conditions are met: + * 1) we have no reference on the connection + * 2) connection is in OPEN state + * 3) it is an outgoing connection + * 4) disconnect timeout > 0 + * 5) connection is not dying + */ + + if ((con->refcnt == 0) && + (con->state == NG_L2CAP_CON_OPEN) && + (con->flags & NG_L2CAP_CON_OUTGOING) && + (con->l2cap->discon_timo > 0) && + ((con->flags & NG_L2CAP_CON_DYING) == 0)) + ng_l2cap_discon_timeout(con); +} /* ng_l2cap_con_unref */ + +/* + * Set auto disconnect timeout + * XXX FIXME: check return code from ng_callout + */ + +int +ng_l2cap_discon_timeout(ng_l2cap_con_p con) +{ + if (con->flags & (NG_L2CAP_CON_LP_TIMO|NG_L2CAP_CON_AUTO_DISCON_TIMO)) + panic( +"%s: %s - invalid timeout, state=%d, flags=%#x\n", + __func__, NG_NODE_NAME(con->l2cap->node), + con->state, con->flags); + + con->flags |= NG_L2CAP_CON_AUTO_DISCON_TIMO; + ng_callout(&con->con_timo, con->l2cap->node, NULL, + con->l2cap->discon_timo * hz, + ng_l2cap_process_discon_timeout, NULL, + con->con_handle); + + return (0); +} /* ng_l2cap_discon_timeout */ + +/* + * Unset auto disconnect timeout + */ + +int +ng_l2cap_discon_untimeout(ng_l2cap_con_p con) +{ + if (!(con->flags & NG_L2CAP_CON_AUTO_DISCON_TIMO)) + panic( +"%s: %s - no disconnect timeout, state=%d, flags=%#x\n", + __func__, NG_NODE_NAME(con->l2cap->node), + con->state, con->flags); + + if (ng_uncallout(&con->con_timo, con->l2cap->node) == 0) + return (ETIMEDOUT); + + con->flags &= ~NG_L2CAP_CON_AUTO_DISCON_TIMO; + + return (0); +} /* ng_l2cap_discon_untimeout */ + +/* + * Free connection descriptor. Will unlink connection and free everything. + */ + +void +ng_l2cap_free_con(ng_l2cap_con_p con) +{ + ng_l2cap_chan_p f = NULL, n = NULL; + + con->state = NG_L2CAP_CON_CLOSED; + + while (con->tx_pkt != NULL) { + struct mbuf *m = con->tx_pkt->m_nextpkt; + + m_freem(con->tx_pkt); + con->tx_pkt = m; + } + + NG_FREE_M(con->rx_pkt); + + for (f = LIST_FIRST(&con->l2cap->chan_list); f != NULL; ) { + n = LIST_NEXT(f, next); + + if (f->con == con) + ng_l2cap_free_chan(f); + + f = n; + } + + while (!TAILQ_EMPTY(&con->cmd_list)) { + ng_l2cap_cmd_p cmd = TAILQ_FIRST(&con->cmd_list); + + ng_l2cap_unlink_cmd(cmd); + if (cmd->flags & NG_L2CAP_CMD_PENDING) + ng_l2cap_command_untimeout(cmd); + ng_l2cap_free_cmd(cmd); + } + + if (con->flags & (NG_L2CAP_CON_AUTO_DISCON_TIMO|NG_L2CAP_CON_LP_TIMO)) + panic( +"%s: %s - timeout pending! state=%d, flags=%#x\n", + __func__, NG_NODE_NAME(con->l2cap->node), + con->state, con->flags); + + LIST_REMOVE(con, next); + + bzero(con, sizeof(*con)); + FREE(con, M_NETGRAPH_L2CAP); +} /* ng_l2cap_free_con */ + +/* + * Get connection by "remote" address + */ + +ng_l2cap_con_p +ng_l2cap_con_by_addr(ng_l2cap_p l2cap, bdaddr_p bdaddr) +{ + ng_l2cap_con_p con = NULL; + + LIST_FOREACH(con, &l2cap->con_list, next) + if (bcmp(bdaddr, &con->remote, sizeof(con->remote)) == 0) + break; + + return (con); +} /* ng_l2cap_con_by_addr */ + +/* + * Get connection by "handle" + */ + +ng_l2cap_con_p +ng_l2cap_con_by_handle(ng_l2cap_p l2cap, u_int16_t con_handle) +{ + ng_l2cap_con_p con = NULL; + + LIST_FOREACH(con, &l2cap->con_list, next) + if (con->con_handle == con_handle) + break; + + return (con); +} /* ng_l2cap_con_by_handle */ + +/* + * Allocate new L2CAP channel descriptor on "con" conection with "psm". + * Will link the channel to the l2cap node + */ + +ng_l2cap_chan_p +ng_l2cap_new_chan(ng_l2cap_p l2cap, ng_l2cap_con_p con, u_int16_t psm) +{ + ng_l2cap_chan_p ch = NULL; + + MALLOC(ch, ng_l2cap_chan_p, sizeof(*ch), M_NETGRAPH_L2CAP, + M_NOWAIT|M_ZERO); + if (ch == NULL) + return (NULL); + + ch->scid = ng_l2cap_get_cid(l2cap); + + if (ch->scid != NG_L2CAP_NULL_CID) { + /* Initialize channel */ + ch->psm = psm; + ch->con = con; + ch->state = NG_L2CAP_CLOSED; + + /* Set MTU and flow control settings to defaults */ + ch->imtu = NG_L2CAP_MTU_DEFAULT; + bcopy(ng_l2cap_default_flow(), &ch->iflow, sizeof(ch->iflow)); + + ch->omtu = NG_L2CAP_MTU_DEFAULT; + bcopy(ng_l2cap_default_flow(), &ch->oflow, sizeof(ch->oflow)); + + ch->flush_timo = NG_L2CAP_FLUSH_TIMO_DEFAULT; + ch->link_timo = NG_L2CAP_LINK_TIMO_DEFAULT; + + LIST_INSERT_HEAD(&l2cap->chan_list, ch, next); + + ng_l2cap_con_ref(con); + } else { + bzero(ch, sizeof(*ch)); + FREE(ch, M_NETGRAPH_L2CAP); + ch = NULL; + } + + return (ch); +} /* ng_l2cap_new_chan */ + +/* + * Get channel by source (local) channel ID + */ + +ng_l2cap_chan_p +ng_l2cap_chan_by_scid(ng_l2cap_p l2cap, u_int16_t scid) +{ + ng_l2cap_chan_p ch = NULL; + + LIST_FOREACH(ch, &l2cap->chan_list, next) + if (ch->scid == scid) + break; + + return (ch); +} /* ng_l2cap_chan_by_scid */ + +/* + * Free channel descriptor. + */ + +void +ng_l2cap_free_chan(ng_l2cap_chan_p ch) +{ + ng_l2cap_cmd_p f = NULL, n = NULL; + + f = TAILQ_FIRST(&ch->con->cmd_list); + while (f != NULL) { + n = TAILQ_NEXT(f, next); + + if (f->ch == ch) { + ng_l2cap_unlink_cmd(f); + if (f->flags & NG_L2CAP_CMD_PENDING) + ng_l2cap_command_untimeout(f); + ng_l2cap_free_cmd(f); + } + + f = n; + } + + LIST_REMOVE(ch, next); + + ng_l2cap_con_unref(ch->con); + + bzero(ch, sizeof(*ch)); + FREE(ch, M_NETGRAPH_L2CAP); +} /* ng_l2cap_free_chan */ + +/* + * Create new L2CAP command descriptor. WILL NOT add command to the queue. + */ + +ng_l2cap_cmd_p +ng_l2cap_new_cmd(ng_l2cap_con_p con, ng_l2cap_chan_p ch, u_int8_t ident, + u_int8_t code, u_int32_t token) +{ + ng_l2cap_cmd_p cmd = NULL; + + KASSERT((ch == NULL || ch->con == con), +("%s: %s - invalid channel pointer!\n", + __func__, NG_NODE_NAME(con->l2cap->node))); + + MALLOC(cmd, ng_l2cap_cmd_p, sizeof(*cmd), M_NETGRAPH_L2CAP, + M_NOWAIT|M_ZERO); + if (cmd == NULL) + return (NULL); + + cmd->con = con; + cmd->ch = ch; + cmd->ident = ident; + cmd->code = code; + cmd->token = token; + ng_callout_init(&cmd->timo); + + return (cmd); +} /* ng_l2cap_new_cmd */ + +/* + * Get pending (i.e. initiated by local side) L2CAP command descriptor by ident + */ + +ng_l2cap_cmd_p +ng_l2cap_cmd_by_ident(ng_l2cap_con_p con, u_int8_t ident) +{ + ng_l2cap_cmd_p cmd = NULL; + + TAILQ_FOREACH(cmd, &con->cmd_list, next) { + if ((cmd->flags & NG_L2CAP_CMD_PENDING) && cmd->ident == ident) { + KASSERT((cmd->con == con), +("%s: %s - invalid connection pointer!\n", + __func__, NG_NODE_NAME(con->l2cap->node))); + + break; + } + } + + return (cmd); +} /* ng_l2cap_cmd_by_ident */ + +/* + * Set LP timeout + * XXX FIXME: check return code from ng_callout + */ + +int +ng_l2cap_lp_timeout(ng_l2cap_con_p con) +{ + if (con->flags & (NG_L2CAP_CON_LP_TIMO|NG_L2CAP_CON_AUTO_DISCON_TIMO)) + panic( +"%s: %s - invalid timeout, state=%d, flags=%#x\n", + __func__, NG_NODE_NAME(con->l2cap->node), + con->state, con->flags); + + con->flags |= NG_L2CAP_CON_LP_TIMO; + ng_callout(&con->con_timo, con->l2cap->node, NULL, + bluetooth_hci_connect_timeout(), + ng_l2cap_process_lp_timeout, NULL, + con->con_handle); + + return (0); +} /* ng_l2cap_lp_timeout */ + +/* + * Unset LP timeout + */ + +int +ng_l2cap_lp_untimeout(ng_l2cap_con_p con) +{ + if (!(con->flags & NG_L2CAP_CON_LP_TIMO)) + panic( +"%s: %s - no LP connection timeout, state=%d, flags=%#x\n", + __func__, NG_NODE_NAME(con->l2cap->node), + con->state, con->flags); + + if (ng_uncallout(&con->con_timo, con->l2cap->node) == 0) + return (ETIMEDOUT); + + con->flags &= ~NG_L2CAP_CON_LP_TIMO; + + return (0); +} /* ng_l2cap_lp_untimeout */ + +/* + * Set L2CAP command timeout + * XXX FIXME: check return code from ng_callout + */ + +int +ng_l2cap_command_timeout(ng_l2cap_cmd_p cmd, int timo) +{ + int arg; + + if (cmd->flags & NG_L2CAP_CMD_PENDING) + panic( +"%s: %s - duplicated command timeout, code=%#x, flags=%#x\n", + __func__, NG_NODE_NAME(cmd->con->l2cap->node), + cmd->code, cmd->flags); + + arg = ((cmd->ident << 16) | cmd->con->con_handle); + cmd->flags |= NG_L2CAP_CMD_PENDING; + ng_callout(&cmd->timo, cmd->con->l2cap->node, NULL, timo, + ng_l2cap_process_command_timeout, NULL, arg); + + return (0); +} /* ng_l2cap_command_timeout */ + +/* + * Unset L2CAP command timeout + */ + +int +ng_l2cap_command_untimeout(ng_l2cap_cmd_p cmd) +{ + if (!(cmd->flags & NG_L2CAP_CMD_PENDING)) + panic( +"%s: %s - no command timeout, code=%#x, flags=%#x\n", + __func__, NG_NODE_NAME(cmd->con->l2cap->node), + cmd->code, cmd->flags); + + if (ng_uncallout(&cmd->timo, cmd->con->l2cap->node) == 0) + return (ETIMEDOUT); + + cmd->flags &= ~NG_L2CAP_CMD_PENDING; + + return (0); +} /* ng_l2cap_command_untimeout */ + +/* + * Prepend "m"buf with "size" bytes + */ + +struct mbuf * +ng_l2cap_prepend(struct mbuf *m, int size) +{ + M_PREPEND(m, size, M_DONTWAIT); + if (m == NULL || (m->m_len < size && (m = m_pullup(m, size)) == NULL)) + return (NULL); + + return (m); +} /* ng_l2cap_prepend */ + +/* + * Default flow settings + */ + +ng_l2cap_flow_p +ng_l2cap_default_flow(void) +{ + static ng_l2cap_flow_t default_flow = { + /* flags */ 0x0, + /* service_type */ NG_HCI_SERVICE_TYPE_BEST_EFFORT, + /* token_rate */ 0xffffffff, /* maximum */ + /* token_bucket_size */ 0xffffffff, /* maximum */ + /* peak_bandwidth */ 0x00000000, /* maximum */ + /* latency */ 0xffffffff, /* don't care */ + /* delay_variation */ 0xffffffff /* don't care */ + }; + + return (&default_flow); +} /* ng_l2cap_default_flow */ + +/* + * Get next available channel ID + * XXX FIXME this is *UGLY* but will do for now + */ + +static u_int16_t +ng_l2cap_get_cid(ng_l2cap_p l2cap) +{ + u_int16_t cid = l2cap->cid + 1; + + if (cid < NG_L2CAP_FIRST_CID) + cid = NG_L2CAP_FIRST_CID; + + while (cid != l2cap->cid) { + if (ng_l2cap_chan_by_scid(l2cap, cid) == NULL) { + l2cap->cid = cid; + + return (cid); + } + + cid ++; + if (cid < NG_L2CAP_FIRST_CID) + cid = NG_L2CAP_FIRST_CID; + } + + return (NG_L2CAP_NULL_CID); +} /* ng_l2cap_get_cid */ + +/* + * Get next available command ident + * XXX FIXME this is *UGLY* but will do for now + */ + +u_int8_t +ng_l2cap_get_ident(ng_l2cap_con_p con) +{ + u_int8_t ident = con->ident + 1; + + if (ident < NG_L2CAP_FIRST_IDENT) + ident = NG_L2CAP_FIRST_IDENT; + + while (ident != con->ident) { + if (ng_l2cap_cmd_by_ident(con, ident) == NULL) { + con->ident = ident; + + return (ident); + } + + ident ++; + if (ident < NG_L2CAP_FIRST_IDENT) + ident = NG_L2CAP_FIRST_IDENT; + } + + return (NG_L2CAP_NULL_IDENT); +} /* ng_l2cap_get_ident */ + diff --git a/sys/netgraph7/bluetooth/l2cap/ng_l2cap_misc.h b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_misc.h new file mode 100644 index 0000000000..e2da0e6935 --- /dev/null +++ b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_misc.h @@ -0,0 +1,106 @@ +/* + * ng_l2cap_misc.h + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_l2cap_misc.h,v 1.3 2003/09/08 19:11:45 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/l2cap/ng_l2cap_misc.h,v 1.6 2005/08/21 19:15:14 pjd Exp $ + */ + +#ifndef _NETGRAPH_L2CAP_MISC_H_ +#define _NETGRAPH_L2CAP_MISC_H_ + +void ng_l2cap_send_hook_info (node_p, hook_p, void *, int); + +/* + * ACL Connections + */ + +ng_l2cap_con_p ng_l2cap_new_con (ng_l2cap_p, bdaddr_p); +void ng_l2cap_con_ref (ng_l2cap_con_p); +void ng_l2cap_con_unref (ng_l2cap_con_p); +ng_l2cap_con_p ng_l2cap_con_by_addr (ng_l2cap_p, bdaddr_p); +ng_l2cap_con_p ng_l2cap_con_by_handle (ng_l2cap_p, u_int16_t); +void ng_l2cap_free_con (ng_l2cap_con_p); + +/* + * L2CAP channels + */ + +ng_l2cap_chan_p ng_l2cap_new_chan (ng_l2cap_p, ng_l2cap_con_p, u_int16_t); +ng_l2cap_chan_p ng_l2cap_chan_by_scid (ng_l2cap_p, u_int16_t); +void ng_l2cap_free_chan (ng_l2cap_chan_p); + +/* + * L2CAP command descriptors + */ + +#define ng_l2cap_link_cmd(con, cmd) \ +do { \ + TAILQ_INSERT_TAIL(&(con)->cmd_list, (cmd), next); \ + ng_l2cap_con_ref((con)); \ +} while (0) + +#define ng_l2cap_unlink_cmd(cmd) \ +do { \ + TAILQ_REMOVE(&((cmd)->con->cmd_list), (cmd), next); \ + ng_l2cap_con_unref((cmd)->con); \ +} while (0) + +#define ng_l2cap_free_cmd(cmd) \ +do { \ + KASSERT(!callout_pending(&(cmd)->timo), ("Pending callout!")); \ + NG_FREE_M((cmd)->aux); \ + bzero((cmd), sizeof(*(cmd))); \ + FREE((cmd), M_NETGRAPH_L2CAP); \ +} while (0) + +ng_l2cap_cmd_p ng_l2cap_new_cmd (ng_l2cap_con_p, ng_l2cap_chan_p, + u_int8_t, u_int8_t, u_int32_t); +ng_l2cap_cmd_p ng_l2cap_cmd_by_ident (ng_l2cap_con_p, u_int8_t); +u_int8_t ng_l2cap_get_ident (ng_l2cap_con_p); + +/* + * Timeout + */ + +int ng_l2cap_discon_timeout (ng_l2cap_con_p); +int ng_l2cap_discon_untimeout (ng_l2cap_con_p); +int ng_l2cap_lp_timeout (ng_l2cap_con_p); +int ng_l2cap_lp_untimeout (ng_l2cap_con_p); +int ng_l2cap_command_timeout (ng_l2cap_cmd_p, int); +int ng_l2cap_command_untimeout (ng_l2cap_cmd_p); + +/* + * Other stuff + */ + +struct mbuf * ng_l2cap_prepend (struct mbuf *, int); +ng_l2cap_flow_p ng_l2cap_default_flow (void); + +#endif /* ndef _NETGRAPH_L2CAP_MISC_H_ */ + diff --git a/sys/netgraph7/bluetooth/l2cap/ng_l2cap_prse.h b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_prse.h new file mode 100644 index 0000000000..bc2dba3666 --- /dev/null +++ b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_prse.h @@ -0,0 +1,87 @@ +/* + * ng_l2cap_prse.h + */ + +/*- + * Copyright (c) 2001 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_l2cap_prse.h,v 1.2 2003/04/28 21:44:59 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/l2cap/ng_l2cap_prse.h,v 1.4 2005/01/07 01:45:43 imp Exp $ + */ + +/*************************************************************************** + *************************************************************************** + ** ng_parse definitions for the L2CAP node + *************************************************************************** + ***************************************************************************/ + +#ifndef _NETGRAPH_L2CAP_PRSE_H_ +#define _NETGRAPH_L2CAP_PRSE_H_ + +/* + * L2CAP node command list + */ + +static const struct ng_cmdlist ng_l2cap_cmdlist[] = { + { + NGM_L2CAP_COOKIE, + NGM_L2CAP_NODE_GET_FLAGS, + "get_flags", + NULL, + &ng_parse_uint16_type + }, + { + NGM_L2CAP_COOKIE, + NGM_L2CAP_NODE_GET_DEBUG, + "get_debug", + NULL, + &ng_parse_uint16_type + }, + { + NGM_L2CAP_COOKIE, + NGM_L2CAP_NODE_SET_DEBUG, + "set_debug", + &ng_parse_uint16_type, + NULL + }, + { + NGM_L2CAP_COOKIE, + NGM_L2CAP_NODE_GET_AUTO_DISCON_TIMO, + "get_disc_timo", + NULL, + &ng_parse_uint16_type + }, + { + NGM_L2CAP_COOKIE, + NGM_L2CAP_NODE_SET_AUTO_DISCON_TIMO, + "set_disc_timo", + &ng_parse_uint16_type, + NULL + }, + { 0, } +}; + +#endif /* ndef _NETGRAPH_L2CAP_PRSE_H_ */ + diff --git a/sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.c b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.c new file mode 100644 index 0000000000..ce2d93c6ef --- /dev/null +++ b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.c @@ -0,0 +1,1642 @@ +/* + * ng_l2cap_ulpi.c + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_l2cap_ulpi.c,v 1.1 2002/11/24 19:47:06 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/l2cap/ng_l2cap_ulpi.c,v 1.5 2005/01/07 01:45:43 imp Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/****************************************************************************** + ****************************************************************************** + ** Upper Layer Protocol Interface module + ****************************************************************************** + ******************************************************************************/ + +/* + * Process L2CA_Connect request from the upper layer protocol. + */ + +int +ng_l2cap_l2ca_con_req(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + ng_l2cap_l2ca_con_ip *ip = NULL; + ng_l2cap_con_p con = NULL; + ng_l2cap_chan_p ch = NULL; + ng_l2cap_cmd_p cmd = NULL; + int error = 0; + + /* Check message */ + if (msg->header.arglen != sizeof(*ip)) { + NG_L2CAP_ALERT( +"%s: %s - invalid L2CA_Connect request message size, size=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + msg->header.arglen); + error = EMSGSIZE; + goto out; + } + + ip = (ng_l2cap_l2ca_con_ip *)(msg->data); + + /* Check if we have connection to the remote unit */ + con = ng_l2cap_con_by_addr(l2cap, &ip->bdaddr); + if (con == NULL) { + /* Submit LP_ConnectReq to the lower layer */ + error = ng_l2cap_lp_con_req(l2cap, &ip->bdaddr); + if (error != 0) { + NG_L2CAP_ERR( +"%s: %s - unable to send LP_ConnectReq message, error=%d\n", + __func__, NG_NODE_NAME(l2cap->node), error); + goto out; + } + + /* This should not fail */ + con = ng_l2cap_con_by_addr(l2cap, &ip->bdaddr); + KASSERT((con != NULL), +("%s: %s - could not find connection!\n", __func__, NG_NODE_NAME(l2cap->node))); + } + + /* + * Create new empty channel descriptor. In case of any failure do + * not touch connection descriptor. + */ + + ch = ng_l2cap_new_chan(l2cap, con, ip->psm); + if (ch == NULL) { + error = ENOMEM; + goto out; + } + + /* Now create L2CAP_ConnectReq command */ + cmd = ng_l2cap_new_cmd(ch->con, ch, ng_l2cap_get_ident(con), + NG_L2CAP_CON_REQ, msg->header.token); + if (cmd == NULL) { + ng_l2cap_free_chan(ch); + error = ENOMEM; + goto out; + } + + if (cmd->ident == NG_L2CAP_NULL_IDENT) { + ng_l2cap_free_cmd(cmd); + ng_l2cap_free_chan(ch); + error = EIO; + goto out; + } + + /* Create L2CAP command packet */ + _ng_l2cap_con_req(cmd->aux, cmd->ident, ch->psm, ch->scid); + if (cmd->aux == NULL) { + ng_l2cap_free_cmd(cmd); + ng_l2cap_free_chan(ch); + error = ENOBUFS; + goto out; + } + + ch->state = NG_L2CAP_W4_L2CAP_CON_RSP; + + /* Link command to the queue */ + ng_l2cap_link_cmd(ch->con, cmd); + ng_l2cap_lp_deliver(ch->con); +out: + return (error); +} /* ng_l2cap_l2ca_con_req */ + +/* + * Send L2CA_Connect response to the upper layer protocol. + */ + +int +ng_l2cap_l2ca_con_rsp(ng_l2cap_chan_p ch, u_int32_t token, u_int16_t result, + u_int16_t status) +{ + ng_l2cap_p l2cap = ch->con->l2cap; + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_con_op *op = NULL; + int error = 0; + + /* Check if upstream hook is connected and valid */ + if (l2cap->l2c == NULL || NG_HOOK_NOT_VALID(l2cap->l2c)) { + NG_L2CAP_ERR( +"%s: %s - unable to send L2CA_Connect response message. " \ +"Hook is not connected or valid, psm=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->psm); + + return (ENOTCONN); + } + + /* Create and send L2CA_Connect response message */ + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CON, + sizeof(*op), M_NOWAIT); + if (msg == NULL) + error = ENOMEM; + else { + msg->header.token = token; + msg->header.flags |= NGF_RESP; + + op = (ng_l2cap_l2ca_con_op *)(msg->data); + + /* + * XXX Spec. says we should only populate LCID when result == 0 + * What about PENDING? What the heck, for now always populate + * LCID :) + */ + + op->lcid = ch->scid; + op->result = result; + op->status = status; + + NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->l2c, 0); + } + + return (error); +} /* ng_l2cap_l2ca_con_rsp */ + +/* + * Process L2CA_ConnectRsp request from the upper layer protocol. + */ + +int +ng_l2cap_l2ca_con_rsp_req(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + ng_l2cap_l2ca_con_rsp_ip *ip = NULL; + ng_l2cap_con_p con = NULL; + ng_l2cap_chan_p ch = NULL; + ng_l2cap_cmd_p cmd = NULL; + u_int16_t dcid; + int error = 0; + + /* Check message */ + if (msg->header.arglen != sizeof(*ip)) { + NG_L2CAP_ALERT( +"%s: %s - invalid L2CA_ConnectRsp request message size, size=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + msg->header.arglen); + error = EMSGSIZE; + goto out; + } + + ip = (ng_l2cap_l2ca_con_rsp_ip *)(msg->data); + + /* Check if we have this channel */ + ch = ng_l2cap_chan_by_scid(l2cap, ip->lcid); + if (ch == NULL) { + NG_L2CAP_ALERT( +"%s: %s - unexpected L2CA_ConnectRsp request message. " \ +"Channel does not exist, lcid=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ip->lcid); + error = ENOENT; + goto out; + } + + /* Check channel state */ + if (ch->state != NG_L2CAP_W4_L2CA_CON_RSP) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CA_ConnectRsp request message. " \ +"Invalid channel state, state=%d, lcid=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->state, + ip->lcid); + error = EINVAL; + goto out; + } + + dcid = ch->dcid; + con = ch->con; + + /* + * Now we are pretty much sure it is our response. So create and send + * L2CAP_ConnectRsp message to our peer. + */ + + if (ch->ident != ip->ident) + NG_L2CAP_WARN( +"%s: %s - channel ident and response ident do not match, scid=%d, ident=%d. " \ +"Will use response ident=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->scid, + ch->ident, ip->ident); + + /* Check result */ + switch (ip->result) { + case NG_L2CAP_SUCCESS: + ch->state = NG_L2CAP_CONFIG; + ch->cfg_state = 0; + break; + + case NG_L2CAP_PENDING: + break; + + default: + ng_l2cap_free_chan(ch); + ch = NULL; + break; + } + + /* Create L2CAP command */ + cmd = ng_l2cap_new_cmd(con, ch, ip->ident, NG_L2CAP_CON_RSP, + msg->header.token); + if (cmd == NULL) { + if (ch != NULL) + ng_l2cap_free_chan(ch); + + error = ENOMEM; + goto out; + } + + _ng_l2cap_con_rsp(cmd->aux, cmd->ident, ip->lcid, dcid, + ip->result, ip->status); + if (cmd->aux == NULL) { + if (ch != NULL) + ng_l2cap_free_chan(ch); + + ng_l2cap_free_cmd(cmd); + error = ENOBUFS; + goto out; + } + + /* Link command to the queue */ + ng_l2cap_link_cmd(con, cmd); + ng_l2cap_lp_deliver(con); +out: + return (error); +} /* ng_l2cap_l2ca_con_rsp_req */ + +/* + * Send L2CAP_ConnectRsp response to the upper layer + */ + +int +ng_l2cap_l2ca_con_rsp_rsp(ng_l2cap_chan_p ch, u_int32_t token, u_int16_t result) +{ + ng_l2cap_p l2cap = ch->con->l2cap; + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_con_rsp_op *op = NULL; + int error = 0; + + /* Check if upstream hook is connected and valid */ + if (l2cap->l2c == NULL || NG_HOOK_NOT_VALID(l2cap->l2c)) { + NG_L2CAP_ERR( +"%s: %s - unable to send L2CA_ConnectRsp response message. " \ +"Hook is not connected or valid, psm=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->psm); + + return (ENOTCONN); + } + + /* Create and send L2CA_ConnectRsp response message */ + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CON_RSP, + sizeof(*op), M_NOWAIT); + if (msg == NULL) + error = ENOMEM; + else { + msg->header.token = token; + msg->header.flags |= NGF_RESP; + + op = (ng_l2cap_l2ca_con_rsp_op *)(msg->data); + op->result = result; + + NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->l2c, 0); + } + + return (error); +} /* ng_l2cap_l2ca_con_rsp_rsp */ + +/* + * Send L2CA_ConnectInd message to the upper layer protocol. + */ + +int +ng_l2cap_l2ca_con_ind(ng_l2cap_chan_p ch) +{ + ng_l2cap_p l2cap = ch->con->l2cap; + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_con_ind_ip *ip = NULL; + int error = 0; + + /* Check if upstream hook is connected and valid */ + if (l2cap->l2c == NULL || NG_HOOK_NOT_VALID(l2cap->l2c)) { + NG_L2CAP_ERR( +"%s: %s - unable to send L2CA_ConnectInd message. " \ +"Hook is not connected or valid, psm=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->psm); + + return (ENOTCONN); + } + + /* Create and send L2CA_ConnectInd message */ + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CON_IND, + sizeof(*ip), M_NOWAIT); + if (msg == NULL) + error = ENOMEM; + else { + ip = (ng_l2cap_l2ca_con_ind_ip *)(msg->data); + + bcopy(&ch->con->remote, &ip->bdaddr, sizeof(ip->bdaddr)); + ip->lcid = ch->scid; + ip->psm = ch->psm; + ip->ident = ch->ident; + + NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->l2c, 0); + } + + return (error); +} /* ng_l2cap_l2ca_con_ind */ + +/* + * Process L2CA_Config request from the upper layer protocol + */ + +int +ng_l2cap_l2ca_cfg_req(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + ng_l2cap_l2ca_cfg_ip *ip = NULL; + ng_l2cap_chan_p ch = NULL; + ng_l2cap_cmd_p cmd = NULL; + struct mbuf *opt = NULL; + u_int16_t *mtu = NULL, *flush_timo = NULL; + ng_l2cap_flow_p flow = NULL; + int error = 0; + + /* Check message */ + if (msg->header.arglen != sizeof(*ip)) { + NG_L2CAP_ALERT( +"%s: %s - Invalid L2CA_Config request message size, size=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + msg->header.arglen); + error = EMSGSIZE; + goto out; + } + + ip = (ng_l2cap_l2ca_cfg_ip *)(msg->data); + + /* Check if we have this channel */ + ch = ng_l2cap_chan_by_scid(l2cap, ip->lcid); + if (ch == NULL) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CA_Config request message. " \ +"Channel does not exist, lcid=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ip->lcid); + error = ENOENT; + goto out; + } + + /* Check channel state */ + if (ch->state != NG_L2CAP_OPEN && ch->state != NG_L2CAP_CONFIG) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CA_Config request message. " \ +"Invalid channel state, state=%d, lcid=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->state, + ch->scid); + error = EINVAL; + goto out; + } + + /* Set requested channel configuration options */ + ch->imtu = ip->imtu; + bcopy(&ip->oflow, &ch->oflow, sizeof(ch->oflow)); + ch->flush_timo = ip->flush_timo; + ch->link_timo = ip->link_timo; + + /* Compare channel settings with defaults */ + if (ch->imtu != NG_L2CAP_MTU_DEFAULT) + mtu = &ch->imtu; + if (ch->flush_timo != NG_L2CAP_FLUSH_TIMO_DEFAULT) + flush_timo = &ch->flush_timo; + if (bcmp(ng_l2cap_default_flow(), &ch->oflow, sizeof(ch->oflow)) != 0) + flow = &ch->oflow; + + /* Create configuration options */ + _ng_l2cap_build_cfg_options(opt, mtu, flush_timo, flow); + if (opt == NULL) { + error = ENOBUFS; + goto out; + } + + /* Create L2CAP command descriptor */ + cmd = ng_l2cap_new_cmd(ch->con, ch, ng_l2cap_get_ident(ch->con), + NG_L2CAP_CFG_REQ, msg->header.token); + if (cmd == NULL) { + NG_FREE_M(opt); + error = ENOMEM; + goto out; + } + + if (cmd->ident == NG_L2CAP_NULL_IDENT) { + ng_l2cap_free_cmd(cmd); + NG_FREE_M(opt); + error = EIO; + goto out; + } + + /* Create L2CAP command packet */ + _ng_l2cap_cfg_req(cmd->aux, cmd->ident, ch->dcid, 0, opt); + if (cmd->aux == NULL) { + ng_l2cap_free_cmd(cmd); + error = ENOBUFS; + goto out; + } + + /* Adjust channel state for re-configuration */ + if (ch->state == NG_L2CAP_OPEN) { + ch->state = NG_L2CAP_CONFIG; + ch->cfg_state = 0; + } + + /* Link command to the queue */ + ng_l2cap_link_cmd(ch->con, cmd); + ng_l2cap_lp_deliver(ch->con); +out: + return (error); +} /* ng_l2cap_l2ca_cfg_req */ + +/* + * Send L2CA_Config response to the upper layer protocol + */ + +int +ng_l2cap_l2ca_cfg_rsp(ng_l2cap_chan_p ch, u_int32_t token, u_int16_t result) +{ + ng_l2cap_p l2cap = ch->con->l2cap; + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_cfg_op *op = NULL; + int error = 0; + + /* Check if upstream hook is connected and valid */ + if (l2cap->l2c == NULL || NG_HOOK_NOT_VALID(l2cap->l2c)) { + NG_L2CAP_ERR( +"%s: %s - unable to send L2CA_Config response message. " \ +"Hook is not connected or valid, psm=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->psm); + + return (ENOTCONN); + } + + /* Create and send L2CA_Config response message */ + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CFG, + sizeof(*op), M_NOWAIT); + if (msg == NULL) + error = ENOMEM; + else { + msg->header.token = token; + msg->header.flags |= NGF_RESP; + + op = (ng_l2cap_l2ca_cfg_op *)(msg->data); + op->result = result; + op->imtu = ch->imtu; + bcopy(&ch->oflow, &op->oflow, sizeof(op->oflow)); + op->flush_timo = ch->flush_timo; + + NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->l2c, 0); + + if (error == 0 && result == NG_L2CAP_SUCCESS) { + ch->cfg_state |= NG_L2CAP_CFG_IN; + + if (ch->cfg_state == NG_L2CAP_CFG_BOTH) + ch->state = NG_L2CAP_OPEN; + } + } + + return (error); +} /* ng_l2cap_l2ca_cfg_rsp */ + +/* + * Process L2CA_ConfigRsp request from the upper layer protocol + * + * XXX XXX XXX + * + * NOTE: The Bluetooth specification says that Configuration_Response + * (L2CA_ConfigRsp) should be used to issue response to configuration request + * indication. The minor problem here is L2CAP command ident. We should use + * ident from original L2CAP request to make sure our peer can match request + * and response. For some reason Bluetooth specification does not include + * ident field into L2CA_ConfigInd and L2CA_ConfigRsp messages. This seems + * strange to me, because L2CA_ConnectInd and L2CA_ConnectRsp do have ident + * field. So we should store last known L2CAP request command ident in channel. + * Also it seems that upper layer can not reject configuration request, as + * Configuration_Response message does not have status/reason field. + */ + +int +ng_l2cap_l2ca_cfg_rsp_req(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + ng_l2cap_l2ca_cfg_rsp_ip *ip = NULL; + ng_l2cap_chan_p ch = NULL; + ng_l2cap_cmd_p cmd = NULL; + struct mbuf *opt = NULL; + u_int16_t *mtu = NULL; + ng_l2cap_flow_p flow = NULL; + int error = 0; + + /* Check message */ + if (msg->header.arglen != sizeof(*ip)) { + NG_L2CAP_ALERT( +"%s: %s - invalid L2CA_ConfigRsp request message size, size=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + msg->header.arglen); + error = EMSGSIZE; + goto out; + } + + ip = (ng_l2cap_l2ca_cfg_rsp_ip *)(msg->data); + + /* Check if we have this channel */ + ch = ng_l2cap_chan_by_scid(l2cap, ip->lcid); + if (ch == NULL) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CA_ConfigRsp request message. " \ +"Channel does not exist, lcid=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ip->lcid); + error = ENOENT; + goto out; + } + + /* Check channel state */ + if (ch->state != NG_L2CAP_CONFIG) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CA_ConfigRsp request message. " \ +"Invalid channel state, state=%d, lcid=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->state, + ch->scid); + error = EINVAL; + goto out; + } + + /* Set channel settings */ + if (ip->omtu != ch->omtu) { + ch->omtu = ip->omtu; + mtu = &ch->omtu; + } + + if (bcmp(&ip->iflow, &ch->iflow, sizeof(ch->iflow)) != 0) { + bcopy(&ip->iflow, &ch->iflow, sizeof(ch->iflow)); + flow = &ch->iflow; + } + + if (mtu != NULL || flow != NULL) { + _ng_l2cap_build_cfg_options(opt, mtu, NULL, flow); + if (opt == NULL) { + error = ENOBUFS; + goto out; + } + } + + /* Create L2CAP command */ + cmd = ng_l2cap_new_cmd(ch->con, ch, ch->ident, NG_L2CAP_CFG_RSP, + msg->header.token); + if (cmd == NULL) { + NG_FREE_M(opt); + error = ENOMEM; + goto out; + } + + _ng_l2cap_cfg_rsp(cmd->aux,cmd->ident,ch->dcid,0,NG_L2CAP_SUCCESS,opt); + if (cmd->aux == NULL) { + ng_l2cap_free_cmd(cmd); + error = ENOBUFS; + goto out; + } + + /* XXX FIXME - not here ??? */ + ch->cfg_state |= NG_L2CAP_CFG_OUT; + if (ch->cfg_state == NG_L2CAP_CFG_BOTH) + ch->state = NG_L2CAP_OPEN; + + /* Link command to the queue */ + ng_l2cap_link_cmd(ch->con, cmd); + ng_l2cap_lp_deliver(ch->con); +out: + return (error); +} /* ng_l2cap_l2ca_cfg_rsp_req */ + +/* + * Send L2CA_ConfigRsp response to the upper layer protocol + */ + +int +ng_l2cap_l2ca_cfg_rsp_rsp(ng_l2cap_chan_p ch, u_int32_t token, u_int16_t result) +{ + ng_l2cap_p l2cap = ch->con->l2cap; + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_cfg_rsp_op *op = NULL; + int error = 0; + + /* Check if upstream hook is connected and valid */ + if (l2cap->l2c == NULL || NG_HOOK_NOT_VALID(l2cap->l2c)) { + NG_L2CAP_ERR( +"%s: %s - unable to send L2CA_ConfigRsp response message. " \ +"Hook is not connected or valid, psm=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->psm); + + return (ENOTCONN); + } + + /* Create and send L2CA_ConfigRsp response message */ + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CFG_RSP, + sizeof(*op), M_NOWAIT); + if (msg == NULL) + error = ENOMEM; + else { + msg->header.token = token; + msg->header.flags |= NGF_RESP; + + op = (ng_l2cap_l2ca_cfg_rsp_op *)(msg->data); + op->result = result; + + NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->l2c, 0); + } + + return (error); +} /* ng_l2cap_l2ca_cfg_rsp_rsp */ + +/* + * Send L2CA_ConfigInd message to the upper layer protocol + * + * XXX XXX XXX + * + * NOTE: The Bluetooth specification says that Configuration_Response + * (L2CA_ConfigRsp) should be used to issue response to configuration request + * indication. The minor problem here is L2CAP command ident. We should use + * ident from original L2CAP request to make sure our peer can match request + * and response. For some reason Bluetooth specification does not include + * ident field into L2CA_ConfigInd and L2CA_ConfigRsp messages. This seems + * strange to me, because L2CA_ConnectInd and L2CA_ConnectRsp do have ident + * field. So we should store last known L2CAP request command ident in channel. + * Also it seems that upper layer can not reject configuration request, as + * Configuration_Response message does not have status/reason field. + */ + +int +ng_l2cap_l2ca_cfg_ind(ng_l2cap_chan_p ch) +{ + ng_l2cap_p l2cap = ch->con->l2cap; + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_cfg_ind_ip *ip = NULL; + int error = 0; + + /* Check if upstream hook is connected and valid */ + if (l2cap->l2c == NULL || NG_HOOK_NOT_VALID(l2cap->l2c)) { + NG_L2CAP_ERR( +"%s: %s - Unable to send L2CA_ConfigInd message. " \ +"Hook is not connected or valid, psm=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->psm); + + return (ENOTCONN); + } + + /* Create and send L2CA_ConnectInd message */ + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CFG_IND, + sizeof(*ip), M_NOWAIT); + if (msg == NULL) + error = ENOMEM; + else { + ip = (ng_l2cap_l2ca_cfg_ind_ip *)(msg->data); + ip->lcid = ch->scid; + ip->omtu = ch->omtu; + bcopy(&ch->iflow, &ip->iflow, sizeof(ip->iflow)); + ip->flush_timo = ch->flush_timo; + + NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->l2c, 0); + } + + return (error); +} /* ng_l2cap_l2ca_cfg_ind */ + +/* + * Process L2CA_Write event + */ + +int +ng_l2cap_l2ca_write_req(ng_l2cap_p l2cap, struct mbuf *m) +{ + ng_l2cap_l2ca_hdr_t *l2ca_hdr = NULL; + ng_l2cap_chan_p ch = NULL; + ng_l2cap_cmd_p cmd = NULL; + int error = 0; + u_int32_t token = 0; + + /* Make sure we can access L2CA data packet header */ + if (m->m_pkthdr.len < sizeof(*l2ca_hdr)) { + NG_L2CAP_ERR( +"%s: %s - L2CA Data packet too small, len=%d\n", + __func__,NG_NODE_NAME(l2cap->node),m->m_pkthdr.len); + error = EMSGSIZE; + goto drop; + } + + /* Get L2CA data packet header */ + NG_L2CAP_M_PULLUP(m, sizeof(*l2ca_hdr)); + if (m == NULL) + return (ENOBUFS); + + l2ca_hdr = mtod(m, ng_l2cap_l2ca_hdr_t *); + token = l2ca_hdr->token; + m_adj(m, sizeof(*l2ca_hdr)); + + /* Verify payload size */ + if (l2ca_hdr->length != m->m_pkthdr.len) { + NG_L2CAP_ERR( +"%s: %s - invalid L2CA Data packet. " \ +"Payload length does not match, length=%d, len=%d\n", + __func__, NG_NODE_NAME(l2cap->node), l2ca_hdr->length, + m->m_pkthdr.len); + error = EMSGSIZE; + goto drop; + } + + /* Check channel ID */ + if (l2ca_hdr->lcid < NG_L2CAP_FIRST_CID) { + NG_L2CAP_ERR( +"%s: %s - invalid L2CA Data packet. Inavlid channel ID, cid=%d\n", + __func__, NG_NODE_NAME(l2cap->node), l2ca_hdr->lcid); + error = EINVAL; + goto drop; + } + + /* Verify that we have the channel and make sure it is open */ + ch = ng_l2cap_chan_by_scid(l2cap, l2ca_hdr->lcid); + if (ch == NULL) { + NG_L2CAP_ERR( +"%s: %s - invalid L2CA Data packet. Channel does not exist, cid=%d\n", + __func__, NG_NODE_NAME(l2cap->node), l2ca_hdr->lcid); + error = ENOENT; + goto drop; + } + + if (ch->state != NG_L2CAP_OPEN) { + NG_L2CAP_ERR( +"%s: %s - invalid L2CA Data packet. Invalid channel state, scid=%d, state=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->scid, + ch->state); + error = EHOSTDOWN; + goto drop; /* XXX not always - re-configure */ + } + + /* Create L2CAP command descriptor */ + cmd = ng_l2cap_new_cmd(ch->con, ch, 0, NGM_L2CAP_L2CA_WRITE, token); + if (cmd == NULL) { + error = ENOMEM; + goto drop; + } + + /* Attach data packet and link command to the queue */ + cmd->aux = m; + ng_l2cap_link_cmd(ch->con, cmd); + ng_l2cap_lp_deliver(ch->con); + + return (error); +drop: + NG_FREE_M(m); + + return (error); +} /* ng_l2cap_l2ca_write_req */ + +/* + * Send L2CA_Write response + */ + +int +ng_l2cap_l2ca_write_rsp(ng_l2cap_chan_p ch, u_int32_t token, u_int16_t result, + u_int16_t length) +{ + ng_l2cap_p l2cap = ch->con->l2cap; + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_write_op *op = NULL; + int error = 0; + + /* Check if upstream hook is connected and valid */ + if (l2cap->l2c == NULL || NG_HOOK_NOT_VALID(l2cap->l2c)) { + NG_L2CAP_ERR( +"%s: %s - unable to send L2CA_WriteRsp message. " \ +"Hook is not connected or valid, psm=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->psm); + + return (ENOTCONN); + } + + /* Create and send L2CA_WriteRsp message */ + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_WRITE, + sizeof(*op), M_NOWAIT); + if (msg == NULL) + error = ENOMEM; + else { + msg->header.token = token; + msg->header.flags |= NGF_RESP; + + op = (ng_l2cap_l2ca_write_op *)(msg->data); + op->result = result; + op->length = length; + op->lcid = ch->scid; + + NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->l2c, 0); + } + + return (error); +} /* ng_l2cap_l2ca_write_rsp */ + +/* + * Receive packet from the lower layer protocol and send it to the upper + * layer protocol (L2CAP_Read) + */ + +int +ng_l2cap_l2ca_receive(ng_l2cap_con_p con) +{ + ng_l2cap_p l2cap = con->l2cap; + ng_l2cap_hdr_t *hdr = NULL; + ng_l2cap_chan_p ch = NULL; + int error = 0; + + NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*hdr)); + if (con->rx_pkt == NULL) + return (ENOBUFS); + + hdr = mtod(con->rx_pkt, ng_l2cap_hdr_t *); + + /* Check channel */ + ch = ng_l2cap_chan_by_scid(l2cap, hdr->dcid); + if (ch == NULL) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CAP data packet. Channel does not exist, cid=%d\n", + __func__, NG_NODE_NAME(l2cap->node), hdr->dcid); + error = ENOENT; + goto drop; + } + + /* Check channel state */ + if (ch->state != NG_L2CAP_OPEN) { + NG_L2CAP_WARN( +"%s: %s - unexpected L2CAP data packet. " \ +"Invalid channel state, cid=%d, state=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->scid, + ch->state); + error = EHOSTDOWN; /* XXX not always - re-configuration */ + goto drop; + } + + /* Check payload size and channel's MTU */ + if (hdr->length > ch->imtu) { + NG_L2CAP_ERR( +"%s: %s - invalid L2CAP data packet. " \ +"Packet too big, length=%d, imtu=%d, cid=%d\n", + __func__, NG_NODE_NAME(l2cap->node), hdr->length, + ch->imtu, ch->scid); + error = EMSGSIZE; + goto drop; + } + + /* + * If we got here then everything looks good and we can sent packet + * to the upper layer protocol. + */ + + /* Check if upstream hook is connected and valid */ + if (l2cap->l2c == NULL || NG_HOOK_NOT_VALID(l2cap->l2c)) { + NG_L2CAP_ERR( +"%s: %s - unable to send L2CAP data packet. " \ +"Hook is not connected or valid, psm=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->psm); + error = ENOTCONN; + goto drop; + } + + NG_SEND_DATA_ONLY(error, l2cap->l2c, con->rx_pkt); + con->rx_pkt = NULL; +drop: + NG_FREE_M(con->rx_pkt); /* checks for != NULL */ + + return (error); +} /* ng_l2cap_receive */ + +/* + * Receive connectioless (multicast) packet from the lower layer protocol and + * send it to the upper layer protocol + */ + +int +ng_l2cap_l2ca_clt_receive(ng_l2cap_con_p con) +{ + struct _clt_pkt { + ng_l2cap_hdr_t h; + ng_l2cap_clt_hdr_t c_h; + } __attribute__ ((packed)) *hdr = NULL; + ng_l2cap_p l2cap = con->l2cap; + int length, error = 0; + + NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*hdr)); + if (con->rx_pkt == NULL) + return (ENOBUFS); + + hdr = mtod(con->rx_pkt, struct _clt_pkt *); + + /* Check packet */ + length = con->rx_pkt->m_pkthdr.len - sizeof(*hdr); + if (length < 0) { + NG_L2CAP_ERR( +"%s: %s - invalid L2CAP CLT data packet. Packet too small, length=%d\n", + __func__, NG_NODE_NAME(l2cap->node), length); + error = EMSGSIZE; + goto drop; + } + + /* Check payload size against CLT MTU */ + if (length > NG_L2CAP_MTU_DEFAULT) { + NG_L2CAP_ERR( +"%s: %s - invalid L2CAP CLT data packet. Packet too big, length=%d, mtu=%d\n", + __func__, NG_NODE_NAME(l2cap->node), length, + NG_L2CAP_MTU_DEFAULT); + error = EMSGSIZE; + goto drop; + } + + hdr->c_h.psm = le16toh(hdr->c_h.psm); + + /* + * If we got here then everything looks good and we can sent packet + * to the upper layer protocol. + */ + + /* Select upstream hook based on PSM */ + switch (hdr->c_h.psm) { + case NG_L2CAP_PSM_SDP: + if (l2cap->flags & NG_L2CAP_CLT_SDP_DISABLED) + goto drop; + break; + + case NG_L2CAP_PSM_RFCOMM: + if (l2cap->flags & NG_L2CAP_CLT_RFCOMM_DISABLED) + goto drop; + break; + + case NG_L2CAP_PSM_TCP: + if (l2cap->flags & NG_L2CAP_CLT_TCP_DISABLED) + goto drop; + break; + } + + /* Check if upstream hook is connected and valid */ + if (l2cap->l2c == NULL || NG_HOOK_NOT_VALID(l2cap->l2c)) { + NG_L2CAP_ERR( +"%s: %s - unable to send L2CAP CLT data packet. " \ +"Hook is not connected or valid, psm=%d\n", + __func__, NG_NODE_NAME(l2cap->node), hdr->c_h.psm); + error = ENOTCONN; + goto drop; + } + + NG_SEND_DATA_ONLY(error, l2cap->l2c, con->rx_pkt); + con->rx_pkt = NULL; +drop: + NG_FREE_M(con->rx_pkt); /* checks for != NULL */ + + return (error); +} /* ng_l2cap_l2ca_clt_receive */ + +/* + * Send L2CA_QoSViolationInd to the upper layer protocol + */ + +int +ng_l2cap_l2ca_qos_ind(ng_l2cap_chan_p ch) +{ + ng_l2cap_p l2cap = ch->con->l2cap; + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_qos_ind_ip *ip = NULL; + int error = 0; + + /* Check if upstream hook is connected and valid */ + if (l2cap->l2c == NULL || NG_HOOK_NOT_VALID(l2cap->l2c)) { + NG_L2CAP_ERR( +"%s: %s - unable to send L2CA_QoSViolationInd message. " \ +"Hook is not connected or valid, psm=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->psm); + + return (ENOTCONN); + } + + /* Create and send L2CA_QoSViolationInd message */ + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_QOS_IND, + sizeof(*ip), M_NOWAIT); + if (msg == NULL) + error = ENOMEM; + else { + ip = (ng_l2cap_l2ca_qos_ind_ip *)(msg->data); + bcopy(&ch->con->remote, &ip->bdaddr, sizeof(ip->bdaddr)); + NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->l2c, 0); + } + + return (error); +} /* ng_l2cap_l2ca_qos_ind */ + +/* + * Process L2CA_Disconnect request from the upper layer protocol. + */ + +int +ng_l2cap_l2ca_discon_req(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + ng_l2cap_l2ca_discon_ip *ip = NULL; + ng_l2cap_chan_p ch = NULL; + ng_l2cap_cmd_p cmd = NULL; + int error = 0; + + /* Check message */ + if (msg->header.arglen != sizeof(*ip)) { + NG_L2CAP_ALERT( +"%s: %s - invalid L2CA_Disconnect request message size, size=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + msg->header.arglen); + error = EMSGSIZE; + goto out; + } + + ip = (ng_l2cap_l2ca_discon_ip *)(msg->data); + + /* Check if we have this channel */ + ch = ng_l2cap_chan_by_scid(l2cap, ip->lcid); + if (ch == NULL) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CA_Disconnect request message. " \ +"Channel does not exist, lcid=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ip->lcid); + error = ENOENT; + goto out; + } + + /* Check channel state */ + if (ch->state != NG_L2CAP_CONFIG && ch->state != NG_L2CAP_OPEN && + ch->state != NG_L2CAP_W4_L2CAP_DISCON_RSP) { + NG_L2CAP_ERR( +"%s: %s - unexpected L2CA_Disconnect request message. " \ +"Invalid channel state, state=%d, lcid=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->state, + ch->scid); + error = EINVAL; + goto out; + } + + /* Create and send L2CAP_DisconReq message */ + cmd = ng_l2cap_new_cmd(ch->con, ch, ng_l2cap_get_ident(ch->con), + NG_L2CAP_DISCON_REQ, msg->header.token); + if (cmd == NULL) { + ng_l2cap_free_chan(ch); + error = ENOMEM; + goto out; + } + + if (cmd->ident == NG_L2CAP_NULL_IDENT) { + ng_l2cap_free_chan(ch); + ng_l2cap_free_cmd(cmd); + error = EIO; + goto out; + } + + _ng_l2cap_discon_req(cmd->aux, cmd->ident, ch->dcid, ch->scid); + if (cmd->aux == NULL) { + ng_l2cap_free_chan(ch); + ng_l2cap_free_cmd(cmd); + error = ENOBUFS; + goto out; + } + + ch->state = NG_L2CAP_W4_L2CAP_DISCON_RSP; + + /* Link command to the queue */ + ng_l2cap_link_cmd(ch->con, cmd); + ng_l2cap_lp_deliver(ch->con); +out: + return (error); +} /* ng_l2cap_l2ca_discon_req */ + +/* + * Send L2CA_Disconnect response to the upper layer protocol + */ + +int +ng_l2cap_l2ca_discon_rsp(ng_l2cap_chan_p ch, u_int32_t token, u_int16_t result) +{ + ng_l2cap_p l2cap = ch->con->l2cap; + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_discon_op *op = NULL; + int error = 0; + + /* Check if upstream hook is connected and valid */ + if (l2cap->l2c == NULL || NG_HOOK_NOT_VALID(l2cap->l2c)) { + NG_L2CAP_ERR( +"%s: %s - unable to send L2CA_Disconnect response message. " \ +"Hook is not connected or valid, psm=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->psm); + + return (ENOTCONN); + } + + /* Create and send L2CA_Disconnect response message */ + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_DISCON, + sizeof(*op), M_NOWAIT); + if (msg == NULL) + error = ENOMEM; + else { + msg->header.token = token; + msg->header.flags |= NGF_RESP; + + op = (ng_l2cap_l2ca_discon_op *)(msg->data); + op->result = result; + + NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->l2c, 0); + } + + return (error); +} /* ng_l2cap_l2ca_discon_rsp */ + +/* + * Send L2CA_DisconnectInd message to the upper layer protocol. + */ + +int +ng_l2cap_l2ca_discon_ind(ng_l2cap_chan_p ch) +{ + ng_l2cap_p l2cap = ch->con->l2cap; + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_discon_ind_ip *ip = NULL; + int error = 0; + + /* Check if upstream hook is connected and valid */ + if (l2cap->l2c == NULL || NG_HOOK_NOT_VALID(l2cap->l2c)) { + NG_L2CAP_ERR( +"%s: %s - unable to send L2CA_DisconnectInd message. " \ +"Hook is not connected or valid, psm=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ch->psm); + + return (ENOTCONN); + } + + /* Create and send L2CA_DisconnectInd message */ + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_DISCON_IND, + sizeof(*ip), M_NOWAIT); + if (msg == NULL) + error = ENOMEM; + else { + ip = (ng_l2cap_l2ca_discon_ind_ip *)(msg->data); + ip->lcid = ch->scid; + NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->l2c, 0); + } + + return (error); +} /* ng_l2cap_l2ca_discon_ind */ + +/* + * Process L2CA_GroupCreate request from the upper layer protocol. + * XXX FIXME + */ + +int +ng_l2cap_l2ca_grp_create(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + return (ENOTSUP); +} /* ng_l2cap_l2ca_grp_create */ + +/* + * Process L2CA_GroupClose request from the upper layer protocol + * XXX FIXME + */ + +int +ng_l2cap_l2ca_grp_close(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + return (ENOTSUP); +} /* ng_l2cap_l2ca_grp_close */ + +/* + * Process L2CA_GroupAddMember request from the upper layer protocol. + * XXX FIXME + */ + +int +ng_l2cap_l2ca_grp_add_member_req(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + return (ENOTSUP); +} /* ng_l2cap_l2ca_grp_add_member_req */ + +/* + * Send L2CA_GroupAddMember response to the upper layer protocol. + * XXX FIXME + */ + +int +ng_l2cap_l2ca_grp_add_member_rsp(ng_l2cap_chan_p ch, u_int32_t token, + u_int16_t result) +{ + return (0); +} /* ng_l2cap_l2ca_grp_add_member_rsp */ + +/* + * Process L2CA_GroupDeleteMember request from the upper layer protocol + * XXX FIXME + */ + +int +ng_l2cap_l2ca_grp_rem_member(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + return (ENOTSUP); +} /* ng_l2cap_l2ca_grp_rem_member */ + +/* + * Process L2CA_GroupGetMembers request from the upper layer protocol + * XXX FIXME + */ + +int +ng_l2cap_l2ca_grp_get_members(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + return (ENOTSUP); +} /* ng_l2cap_l2ca_grp_get_members */ + +/* + * Process L2CA_Ping request from the upper layer protocol + */ + +int +ng_l2cap_l2ca_ping_req(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + ng_l2cap_l2ca_ping_ip *ip = NULL; + ng_l2cap_con_p con = NULL; + ng_l2cap_cmd_p cmd = NULL; + int error = 0; + + /* Verify message */ + if (msg->header.arglen < sizeof(*ip)) { + NG_L2CAP_ALERT( +"%s: %s - invalid L2CA_Ping request message size, size=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + msg->header.arglen); + error = EMSGSIZE; + goto out; + } + + ip = (ng_l2cap_l2ca_ping_ip *)(msg->data); + if (ip->echo_size > NG_L2CAP_MAX_ECHO_SIZE) { + NG_L2CAP_WARN( +"%s: %s - invalid L2CA_Ping request. Echo size is too big, echo_size=%d\n", + __func__, NG_NODE_NAME(l2cap->node), ip->echo_size); + error = EMSGSIZE; + goto out; + } + + /* Check if we have connection to the unit */ + con = ng_l2cap_con_by_addr(l2cap, &ip->bdaddr); + if (con == NULL) { + /* Submit LP_ConnectReq to the lower layer */ + error = ng_l2cap_lp_con_req(l2cap, &ip->bdaddr); + if (error != 0) { + NG_L2CAP_ERR( +"%s: %s - unable to send LP_ConnectReq message, error=%d\n", + __func__, NG_NODE_NAME(l2cap->node), error); + goto out; + } + + /* This should not fail */ + con = ng_l2cap_con_by_addr(l2cap, &ip->bdaddr); + KASSERT((con != NULL), +("%s: %s - could not find connection!\n", __func__, NG_NODE_NAME(l2cap->node))); + } + + /* Create L2CAP command descriptor */ + cmd = ng_l2cap_new_cmd(con, NULL, ng_l2cap_get_ident(con), + NG_L2CAP_ECHO_REQ, msg->header.token); + if (cmd == NULL) { + error = ENOMEM; + goto out; + } + + if (cmd->ident == NG_L2CAP_NULL_IDENT) { + ng_l2cap_free_cmd(cmd); + error = EIO; + goto out; + } + + /* Create L2CAP command packet */ + _ng_l2cap_echo_req(cmd->aux, cmd->ident, + msg->data + sizeof(*ip), ip->echo_size); + if (cmd->aux == NULL) { + ng_l2cap_free_cmd(cmd); + error = ENOBUFS; + goto out; + } + + /* Link command to the queue */ + ng_l2cap_link_cmd(con, cmd); + ng_l2cap_lp_deliver(con); +out: + return (error); +} /* ng_l2cap_l2ca_ping_req */ + +/* + * Send L2CA_Ping response to the upper layer protocol + */ + +int +ng_l2cap_l2ca_ping_rsp(ng_l2cap_con_p con, u_int32_t token, u_int16_t result, + struct mbuf *data) +{ + ng_l2cap_p l2cap = con->l2cap; + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_ping_op *op = NULL; + int error = 0, size = 0; + + /* Check if control hook is connected and valid */ + if (l2cap->ctl == NULL || NG_HOOK_NOT_VALID(l2cap->ctl)) { + NG_L2CAP_WARN( +"%s: %s - unable to send L2CA_Ping response message. " \ +"Hook is not connected or valid\n", + __func__, NG_NODE_NAME(l2cap->node)); + error = ENOTCONN; + goto out; + } + + size = (data == NULL)? 0 : data->m_pkthdr.len; + + /* Create and send L2CA_Ping response message */ + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_PING, + sizeof(*op) + size, M_NOWAIT); + if (msg == NULL) + error = ENOMEM; + else { + msg->header.token = token; + msg->header.flags |= NGF_RESP; + + op = (ng_l2cap_l2ca_ping_op *)(msg->data); + op->result = result; + bcopy(&con->remote, &op->bdaddr, sizeof(op->bdaddr)); + if (data != NULL && size > 0) { + op->echo_size = size; + m_copydata(data, 0, size, (caddr_t) op + sizeof(*op)); + } + + NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->ctl, 0); + } +out: + NG_FREE_M(data); + + return (error); +} /* ng_l2cap_l2ca_ping_rsp */ + +/* + * Process L2CA_GetInfo request from the upper layer protocol + */ + +int +ng_l2cap_l2ca_get_info_req(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + ng_l2cap_l2ca_get_info_ip *ip = NULL; + ng_l2cap_con_p con = NULL; + ng_l2cap_cmd_p cmd = NULL; + int error = 0; + + /* Verify message */ + if (msg->header.arglen != sizeof(*ip)) { + NG_L2CAP_ALERT( +"%s: %s - invalid L2CA_GetInfo request message size, size=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + msg->header.arglen); + error = EMSGSIZE; + goto out; + } + + ip = (ng_l2cap_l2ca_get_info_ip *)(msg->data); + + /* Check if we have connection to the unit */ + con = ng_l2cap_con_by_addr(l2cap, &ip->bdaddr); + if (con == NULL) { + /* Submit LP_ConnectReq to the lower layer */ + error = ng_l2cap_lp_con_req(l2cap, &ip->bdaddr); + if (error != 0) { + NG_L2CAP_ERR( +"%s: %s - unable to send LP_ConnectReq message, error=%d\n", + __func__, NG_NODE_NAME(l2cap->node), error); + goto out; + } + + /* This should not fail */ + con = ng_l2cap_con_by_addr(l2cap, &ip->bdaddr); + KASSERT((con != NULL), +("%s: %s - could not find connection!\n", __func__, NG_NODE_NAME(l2cap->node))); + } + + /* Create L2CAP command descriptor */ + cmd = ng_l2cap_new_cmd(con, NULL, ng_l2cap_get_ident(con), + NG_L2CAP_INFO_REQ, msg->header.token); + if (cmd == NULL) { + error = ENOMEM; + goto out; + } + + if (cmd->ident == NG_L2CAP_NULL_IDENT) { + ng_l2cap_free_cmd(cmd); + error = EIO; + goto out; + } + + /* Create L2CAP command packet */ + _ng_l2cap_info_req(cmd->aux, cmd->ident, ip->info_type); + if (cmd->aux == NULL) { + ng_l2cap_free_cmd(cmd); + error = ENOBUFS; + goto out; + } + + /* Link command to the queue */ + ng_l2cap_link_cmd(con, cmd); + ng_l2cap_lp_deliver(con); +out: + return (error); +} /* ng_l2cap_l2ca_get_info_req */ + +/* + * Send L2CA_GetInfo response to the upper layer protocol + */ + +int +ng_l2cap_l2ca_get_info_rsp(ng_l2cap_con_p con, u_int32_t token, + u_int16_t result, struct mbuf *data) +{ + ng_l2cap_p l2cap = con->l2cap; + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_get_info_op *op = NULL; + int error = 0, size; + + /* Check if control hook is connected and valid */ + if (l2cap->ctl == NULL || NG_HOOK_NOT_VALID(l2cap->ctl)) { + NG_L2CAP_WARN( +"%s: %s - unable to send L2CA_GetInfo response message. " \ +"Hook is not connected or valid\n", + __func__, NG_NODE_NAME(l2cap->node)); + error = ENOTCONN; + goto out; + } + + size = (data == NULL)? 0 : data->m_pkthdr.len; + + /* Create and send L2CA_GetInfo response message */ + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_GET_INFO, + sizeof(*op) + size, M_NOWAIT); + if (msg == NULL) + error = ENOMEM; + else { + msg->header.token = token; + msg->header.flags |= NGF_RESP; + + op = (ng_l2cap_l2ca_get_info_op *)(msg->data); + op->result = result; + if (data != NULL && size > 0) { + op->info_size = size; + m_copydata(data, 0, size, (caddr_t) op + sizeof(*op)); + } + + NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->ctl, 0); + } +out: + NG_FREE_M(data); + + return (error); +} /* ng_l2cap_l2ca_get_info_rsp */ + +/* + * Process L2CA_EnableCLT message from the upper layer protocol + * XXX convert to NGN_L2CAP_NODE_SET_FLAGS? + */ + +int +ng_l2cap_l2ca_enable_clt(ng_l2cap_p l2cap, struct ng_mesg *msg) +{ + ng_l2cap_l2ca_enable_clt_ip *ip = NULL; + int error = 0; +#if 0 + * ng_l2cap_l2ca_enable_clt_op *op = NULL; + * u_int16_t result; + * u_int32_t token; +#endif + + /* Check message */ + if (msg->header.arglen != sizeof(*ip)) { + NG_L2CAP_ALERT( +"%s: %s - invalid L2CA_EnableCLT message size, size=%d\n", + __func__, NG_NODE_NAME(l2cap->node), + msg->header.arglen); + + return (EMSGSIZE); + } + + /* Process request */ + ip = (ng_l2cap_l2ca_enable_clt_ip *) (msg->data); +#if 0 + * result = NG_L2CAP_SUCCESS; +#endif + + switch (ip->psm) + { + case 0: + /* Special case: disable/enable all PSM */ + if (ip->enable) + l2cap->flags &= ~(NG_L2CAP_CLT_SDP_DISABLED | + NG_L2CAP_CLT_RFCOMM_DISABLED | + NG_L2CAP_CLT_TCP_DISABLED); + else + l2cap->flags |= (NG_L2CAP_CLT_SDP_DISABLED | + NG_L2CAP_CLT_RFCOMM_DISABLED | + NG_L2CAP_CLT_TCP_DISABLED); + break; + + case NG_L2CAP_PSM_SDP: + if (ip->enable) + l2cap->flags &= ~NG_L2CAP_CLT_SDP_DISABLED; + else + l2cap->flags |= NG_L2CAP_CLT_SDP_DISABLED; + break; + + case NG_L2CAP_PSM_RFCOMM: + if (ip->enable) + l2cap->flags &= ~NG_L2CAP_CLT_RFCOMM_DISABLED; + else + l2cap->flags |= NG_L2CAP_CLT_RFCOMM_DISABLED; + break; + + case NG_L2CAP_PSM_TCP: + if (ip->enable) + l2cap->flags &= ~NG_L2CAP_CLT_TCP_DISABLED; + else + l2cap->flags |= NG_L2CAP_CLT_TCP_DISABLED; + break; + + default: + NG_L2CAP_ERR( +"%s: %s - unsupported PSM=%d\n", __func__, NG_NODE_NAME(l2cap->node), ip->psm); +#if 0 + * result = NG_L2CAP_PSM_NOT_SUPPORTED; +#endif + error = ENOTSUP; + break; + } + +#if 0 + * /* Create and send response message */ + * token = msg->header.token; + * NG_FREE_MSG(msg); + * NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_ENABLE_CLT, + * sizeof(*op), M_NOWAIT); + * if (msg == NULL) + * error = ENOMEM; + * else { + * msg->header.token = token; + * msg->header.flags |= NGF_RESP; + * + * op = (ng_l2cap_l2ca_enable_clt_op *)(msg->data); + * op->result = result; + * } + * + * /* Send response to control hook */ + * if (l2cap->ctl != NULL && NG_HOOK_IS_VALID(l2cap->ctl)) + * NG_SEND_MSG_HOOK(error, l2cap->node, msg, l2cap->ctl, 0); +#endif + + return (error); +} /* ng_l2cap_l2ca_enable_clt */ + diff --git a/sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.h b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.h new file mode 100644 index 0000000000..4e9380c650 --- /dev/null +++ b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.h @@ -0,0 +1,79 @@ +/* + * ng_l2cap_ulpi.h + */ + +/*- + * Copyright (c) Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_l2cap_ulpi.h,v 1.1 2002/11/24 19:47:06 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/l2cap/ng_l2cap_ulpi.h,v 1.3 2005/01/07 01:45:43 imp Exp $ + */ + +#ifndef _NETGRAPH_L2CAP_ULPI_H_ +#define _NETGRAPH_L2CAP_ULPI_H_ + +int ng_l2cap_l2ca_con_req (ng_l2cap_p, struct ng_mesg *); +int ng_l2cap_l2ca_con_rsp (ng_l2cap_chan_p, u_int32_t, u_int16_t, u_int16_t); +int ng_l2cap_l2ca_con_rsp_req (ng_l2cap_p, struct ng_mesg *); +int ng_l2cap_l2ca_con_rsp_rsp (ng_l2cap_chan_p, u_int32_t, u_int16_t); +int ng_l2cap_l2ca_con_ind (ng_l2cap_chan_p); + +int ng_l2cap_l2ca_cfg_req (ng_l2cap_p, struct ng_mesg *); +int ng_l2cap_l2ca_cfg_rsp (ng_l2cap_chan_p, u_int32_t, u_int16_t); +int ng_l2cap_l2ca_cfg_rsp_req (ng_l2cap_p, struct ng_mesg *); +int ng_l2cap_l2ca_cfg_rsp_rsp (ng_l2cap_chan_p, u_int32_t, u_int16_t); +int ng_l2cap_l2ca_cfg_ind (ng_l2cap_chan_p); + +int ng_l2cap_l2ca_write_req (ng_l2cap_p, struct mbuf *); +int ng_l2cap_l2ca_write_rsp (ng_l2cap_chan_p, u_int32_t, u_int16_t, u_int16_t); + +int ng_l2cap_l2ca_receive (ng_l2cap_con_p); +int ng_l2cap_l2ca_clt_receive (ng_l2cap_con_p); + +int ng_l2cap_l2ca_qos_ind (ng_l2cap_chan_p); + +int ng_l2cap_l2ca_discon_req (ng_l2cap_p, struct ng_mesg *); +int ng_l2cap_l2ca_discon_rsp (ng_l2cap_chan_p, u_int32_t, u_int16_t); +int ng_l2cap_l2ca_discon_ind (ng_l2cap_chan_p); + +int ng_l2cap_l2ca_grp_create (ng_l2cap_p, struct ng_mesg *); +int ng_l2cap_l2ca_grp_close (ng_l2cap_p, struct ng_mesg *); +int ng_l2cap_l2ca_grp_add_member_req (ng_l2cap_p, struct ng_mesg *); +int ng_l2cap_l2ca_grp_add_member_rsp (ng_l2cap_chan_p, u_int32_t, u_int16_t); +int ng_l2cap_l2ca_grp_rem_member (ng_l2cap_p, struct ng_mesg *); +int ng_l2cap_l2ca_grp_get_members (ng_l2cap_p, struct ng_mesg *); + +int ng_l2cap_l2ca_ping_req (ng_l2cap_p, struct ng_mesg *); +int ng_l2cap_l2ca_ping_rsp (ng_l2cap_con_p, u_int32_t, u_int16_t, + struct mbuf *); + +int ng_l2cap_l2ca_get_info_req (ng_l2cap_p, struct ng_mesg *); +int ng_l2cap_l2ca_get_info_rsp (ng_l2cap_con_p, u_int32_t, u_int16_t, + struct mbuf *); + +int ng_l2cap_l2ca_enable_clt (ng_l2cap_p, struct ng_mesg *); + +#endif /* ndef _NETGRAPH_L2CAP_ULPI_H_ */ + diff --git a/sys/netgraph7/bluetooth/l2cap/ng_l2cap_var.h b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_var.h new file mode 100644 index 0000000000..2a93b9adc5 --- /dev/null +++ b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_var.h @@ -0,0 +1,188 @@ +/* + * ng_l2cap_var.h + */ + +/*- + * Copyright (c) 2001 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_l2cap_var.h,v 1.2 2003/04/28 21:44:59 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/l2cap/ng_l2cap_var.h,v 1.6 2005/01/07 01:45:43 imp Exp $ + */ + +#ifndef _NETGRAPH_L2CAP_VAR_H_ +#define _NETGRAPH_L2CAP_VAR_H_ + +/* MALLOC decalation */ +#ifdef NG_SEPARATE_MALLOC +MALLOC_DECLARE(M_NETGRAPH_L2CAP); +#else +#define M_NETGRAPH_L2CAP M_NETGRAPH +#endif /* NG_SEPARATE_MALLOC */ + +/* Debug */ +#define NG_L2CAP_ALERT if (l2cap->debug >= NG_L2CAP_ALERT_LEVEL) printf +#define NG_L2CAP_ERR if (l2cap->debug >= NG_L2CAP_ERR_LEVEL) printf +#define NG_L2CAP_WARN if (l2cap->debug >= NG_L2CAP_WARN_LEVEL) printf +#define NG_L2CAP_INFO if (l2cap->debug >= NG_L2CAP_INFO_LEVEL) printf + +/* Wrapper around m_pullup */ +#define NG_L2CAP_M_PULLUP(m, s) \ + do { \ + if ((m)->m_len < (s)) \ + (m) = m_pullup((m), (s)); \ + if ((m) == NULL) \ + NG_L2CAP_ALERT("%s: %s - m_pullup(%zd) failed\n", \ + __func__, NG_NODE_NAME(l2cap->node), (s)); \ + } while (0) + +/* + * L2CAP signaling command ident's are assigned relative to the connection, + * because there is only one signaling channel (cid == 0x01) for every + * connection. So up to 254 (0xff - 0x01) L2CAP commands can be pending at the + * same time for the same connection. + */ + +#define NG_L2CAP_NULL_IDENT 0x00 /* DO NOT USE THIS IDENT */ +#define NG_L2CAP_FIRST_IDENT 0x01 /* dynamically alloc. (start) */ +#define NG_L2CAP_LAST_IDENT 0xff /* dynamically alloc. (end) */ + +/* + * L2CAP (Node private) + */ + +struct ng_l2cap_con; +struct ng_l2cap_chan; + +typedef struct ng_l2cap { + node_p node; /* node ptr */ + + ng_l2cap_node_debug_ep debug; /* debug level */ + ng_l2cap_node_flags_ep flags; /* L2CAP node flags */ + ng_l2cap_node_auto_discon_ep discon_timo; /* auto discon. timeout */ + + u_int16_t pkt_size; /* max. ACL packet size */ + u_int16_t num_pkts; /* out queue size */ + bdaddr_t bdaddr; /* unit BDADDR */ + + hook_p hci; /* HCI downstream hook */ + hook_p l2c; /* L2CAP upstream hook */ + hook_p ctl; /* control hook */ + + LIST_HEAD(, ng_l2cap_con) con_list; /* ACL connections */ + + u_int16_t cid; /* last allocated CID */ + LIST_HEAD(, ng_l2cap_chan) chan_list; /* L2CAP channels */ +} ng_l2cap_t; +typedef ng_l2cap_t * ng_l2cap_p; + +/* + * L2CAP connection descriptor + */ + +struct ng_l2cap_cmd; + +typedef struct ng_l2cap_con { + ng_l2cap_p l2cap; /* pointer to L2CAP */ + + u_int16_t state; /* ACL connection state */ + u_int16_t flags; /* ACL connection flags */ + + int32_t refcnt; /* reference count */ + + bdaddr_t remote; /* remote unit address */ + u_int16_t con_handle; /* ACL connection handle */ + struct callout con_timo; /* connection timeout */ + + u_int8_t ident; /* last allocated ident */ + TAILQ_HEAD(, ng_l2cap_cmd) cmd_list; /* pending L2CAP cmds */ + + struct mbuf *tx_pkt; /* xmitted L2CAP packet */ + int pending; /* num. of pending pkts */ + + struct mbuf *rx_pkt; /* received L2CAP packet */ + int rx_pkt_len; /* packet len. so far */ + + LIST_ENTRY(ng_l2cap_con) next; /* link */ +} ng_l2cap_con_t; +typedef ng_l2cap_con_t * ng_l2cap_con_p; + +/* + * L2CAP channel descriptor + */ + +typedef struct ng_l2cap_chan { + ng_l2cap_con_p con; /* pointer to connection */ + + u_int16_t state; /* channel state */ + + u_int8_t cfg_state; /* configuration state */ +#define NG_L2CAP_CFG_IN (1 << 0) /* incoming cfg path done */ +#define NG_L2CAP_CFG_OUT (1 << 1) /* outgoing cfg path done */ +#define NG_L2CAP_CFG_BOTH (NG_L2CAP_CFG_IN|NG_L2CAP_CFG_OUT) + + u_int8_t ident; /* last L2CAP req. ident */ + + u_int16_t psm; /* channel PSM */ + u_int16_t scid; /* source channel ID */ + u_int16_t dcid; /* destination channel ID */ + + u_int16_t imtu; /* incoming channel MTU */ + ng_l2cap_flow_t iflow; /* incoming flow control */ + + u_int16_t omtu; /* outgoing channel MTU */ + ng_l2cap_flow_t oflow; /* outgoing flow control */ + + u_int16_t flush_timo; /* flush timeout */ + u_int16_t link_timo; /* link timeout */ + + LIST_ENTRY(ng_l2cap_chan) next; /* link */ +} ng_l2cap_chan_t; +typedef ng_l2cap_chan_t * ng_l2cap_chan_p; + +/* + * L2CAP command descriptor + */ + +typedef struct ng_l2cap_cmd { + ng_l2cap_con_p con; /* L2CAP connection */ + ng_l2cap_chan_p ch; /* L2CAP channel */ + + u_int16_t flags; /* command flags */ +#define NG_L2CAP_CMD_PENDING (1 << 0) /* command is pending */ + + u_int8_t code; /* L2CAP command opcode */ + u_int8_t ident; /* L2CAP command ident */ + u_int32_t token; /* L2CA message token */ + + struct callout timo; /* RTX/ERTX timeout */ + + struct mbuf *aux; /* optional data */ + + TAILQ_ENTRY(ng_l2cap_cmd) next; /* link */ +} ng_l2cap_cmd_t; +typedef ng_l2cap_cmd_t * ng_l2cap_cmd_p; + +#endif /* ndef _NETGRAPH_L2CAP_VAR_H_ */ + diff --git a/sys/netgraph7/bluetooth/socket/TODO b/sys/netgraph7/bluetooth/socket/TODO new file mode 100644 index 0000000000..b40b7dcee8 --- /dev/null +++ b/sys/netgraph7/bluetooth/socket/TODO @@ -0,0 +1,15 @@ +$Id: TODO,v 1.1 2002/11/24 19:47:07 max Exp $ +$FreeBSD: src/sys/netgraph/bluetooth/socket/TODO,v 1.2 2003/05/10 21:44:41 julian Exp $ + +FIXME/TODO list + +1) Deal properly with "shutdown"s and hook "disconnect"s + + How to let L2CAP node that user called "shutdown" on node or + have "disconnect"ed downstream hook. Should L2CAP node deal + with it? + +2) Locking + + It is OK to use mutexes, but is there a better way? + diff --git a/sys/netgraph7/bluetooth/socket/ng_btsocket.c b/sys/netgraph7/bluetooth/socket/ng_btsocket.c new file mode 100644 index 0000000000..1bbccd69c2 --- /dev/null +++ b/sys/netgraph7/bluetooth/socket/ng_btsocket.c @@ -0,0 +1,254 @@ +/* + * ng_btsocket.c + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_btsocket.c,v 1.4 2003/09/14 23:29:06 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/socket/ng_btsocket.c,v 1.13 2006/07/21 17:11:13 rwatson Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int ng_btsocket_modevent (module_t, int, void *); +extern struct domain ng_btsocket_domain; + +/* + * Bluetooth raw HCI sockets + */ + +static struct pr_usrreqs ng_btsocket_hci_raw_usrreqs = { + .pru_abort = ng_btsocket_hci_raw_abort, + .pru_attach = ng_btsocket_hci_raw_attach, + .pru_bind = ng_btsocket_hci_raw_bind, + .pru_connect = ng_btsocket_hci_raw_connect, + .pru_control = ng_btsocket_hci_raw_control, + .pru_detach = ng_btsocket_hci_raw_detach, + .pru_disconnect = ng_btsocket_hci_raw_disconnect, + .pru_peeraddr = ng_btsocket_hci_raw_peeraddr, + .pru_send = ng_btsocket_hci_raw_send, + .pru_shutdown = NULL, + .pru_sockaddr = ng_btsocket_hci_raw_sockaddr, + .pru_close = ng_btsocket_hci_raw_close, +}; + +/* + * Bluetooth raw L2CAP sockets + */ + +static struct pr_usrreqs ng_btsocket_l2cap_raw_usrreqs = { + .pru_abort = ng_btsocket_l2cap_raw_abort, + .pru_attach = ng_btsocket_l2cap_raw_attach, + .pru_bind = ng_btsocket_l2cap_raw_bind, + .pru_connect = ng_btsocket_l2cap_raw_connect, + .pru_control = ng_btsocket_l2cap_raw_control, + .pru_detach = ng_btsocket_l2cap_raw_detach, + .pru_disconnect = ng_btsocket_l2cap_raw_disconnect, + .pru_peeraddr = ng_btsocket_l2cap_raw_peeraddr, + .pru_send = ng_btsocket_l2cap_raw_send, + .pru_shutdown = NULL, + .pru_sockaddr = ng_btsocket_l2cap_raw_sockaddr, + .pru_close = ng_btsocket_l2cap_raw_close, +}; + +/* + * Bluetooth SEQPACKET L2CAP sockets + */ + +static struct pr_usrreqs ng_btsocket_l2cap_usrreqs = { + .pru_abort = ng_btsocket_l2cap_abort, + .pru_accept = ng_btsocket_l2cap_accept, + .pru_attach = ng_btsocket_l2cap_attach, + .pru_bind = ng_btsocket_l2cap_bind, + .pru_connect = ng_btsocket_l2cap_connect, + .pru_control = ng_btsocket_l2cap_control, + .pru_detach = ng_btsocket_l2cap_detach, + .pru_disconnect = ng_btsocket_l2cap_disconnect, + .pru_listen = ng_btsocket_l2cap_listen, + .pru_peeraddr = ng_btsocket_l2cap_peeraddr, + .pru_send = ng_btsocket_l2cap_send, + .pru_shutdown = NULL, + .pru_sockaddr = ng_btsocket_l2cap_sockaddr, + .pru_close = ng_btsocket_l2cap_close, +}; + +/* + * Bluetooth STREAM RFCOMM sockets + */ + +static struct pr_usrreqs ng_btsocket_rfcomm_usrreqs = { + .pru_abort = ng_btsocket_rfcomm_abort, + .pru_accept = ng_btsocket_rfcomm_accept, + .pru_attach = ng_btsocket_rfcomm_attach, + .pru_bind = ng_btsocket_rfcomm_bind, + .pru_connect = ng_btsocket_rfcomm_connect, + .pru_control = ng_btsocket_rfcomm_control, + .pru_detach = ng_btsocket_rfcomm_detach, + .pru_disconnect = ng_btsocket_rfcomm_disconnect, + .pru_listen = ng_btsocket_rfcomm_listen, + .pru_peeraddr = ng_btsocket_rfcomm_peeraddr, + .pru_send = ng_btsocket_rfcomm_send, + .pru_shutdown = NULL, + .pru_sockaddr = ng_btsocket_rfcomm_sockaddr, + .pru_close = ng_btsocket_rfcomm_close, +}; + +/* + * Definitions of protocols supported in the BLUETOOTH domain + */ + +static struct protosw ng_btsocket_protosw[] = { +{ + .pr_type = SOCK_RAW, + .pr_domain = &ng_btsocket_domain, + .pr_protocol = BLUETOOTH_PROTO_HCI, + .pr_flags = PR_ATOMIC|PR_ADDR, + .pr_ctloutput = ng_btsocket_hci_raw_ctloutput, + .pr_init = ng_btsocket_hci_raw_init, + .pr_usrreqs = &ng_btsocket_hci_raw_usrreqs, +}, +{ + .pr_type = SOCK_RAW, + .pr_domain = &ng_btsocket_domain, + .pr_protocol = BLUETOOTH_PROTO_L2CAP, + .pr_flags = PR_ATOMIC|PR_ADDR, + .pr_init = ng_btsocket_l2cap_raw_init, + .pr_usrreqs = &ng_btsocket_l2cap_raw_usrreqs, +}, +{ + .pr_type = SOCK_SEQPACKET, + .pr_domain = &ng_btsocket_domain, + .pr_protocol = BLUETOOTH_PROTO_L2CAP, + .pr_flags = PR_ATOMIC|PR_CONNREQUIRED, + .pr_ctloutput = ng_btsocket_l2cap_ctloutput, + .pr_init = ng_btsocket_l2cap_init, + .pr_usrreqs = &ng_btsocket_l2cap_usrreqs, +}, +{ + .pr_type = SOCK_STREAM, + .pr_domain = &ng_btsocket_domain, + .pr_protocol = BLUETOOTH_PROTO_RFCOMM, + .pr_flags = PR_CONNREQUIRED, + .pr_ctloutput = ng_btsocket_rfcomm_ctloutput, + .pr_init = ng_btsocket_rfcomm_init, + .pr_usrreqs = &ng_btsocket_rfcomm_usrreqs, +} +}; +#define ng_btsocket_protosw_size \ + (sizeof(ng_btsocket_protosw)/sizeof(ng_btsocket_protosw[0])) +#define ng_btsocket_protosw_end \ + &ng_btsocket_protosw[ng_btsocket_protosw_size] + +/* + * BLUETOOTH domain + */ + +struct domain ng_btsocket_domain = { + .dom_family = AF_BLUETOOTH, + .dom_name = "bluetooth", + .dom_protosw = ng_btsocket_protosw, + .dom_protoswNPROTOSW = ng_btsocket_protosw_end +}; + +/* + * Socket sysctl tree + */ + +SYSCTL_NODE(_net_bluetooth_hci, OID_AUTO, sockets, CTLFLAG_RW, + 0, "Bluetooth HCI sockets family"); +SYSCTL_NODE(_net_bluetooth_l2cap, OID_AUTO, sockets, CTLFLAG_RW, + 0, "Bluetooth L2CAP sockets family"); +SYSCTL_NODE(_net_bluetooth_rfcomm, OID_AUTO, sockets, CTLFLAG_RW, + 0, "Bluetooth RFCOMM sockets family"); + +/* + * Module + */ + +static moduledata_t ng_btsocket_mod = { + "ng_btsocket", + ng_btsocket_modevent, + NULL +}; + +DECLARE_MODULE(ng_btsocket, ng_btsocket_mod, SI_SUB_PROTO_DOMAIN, + SI_ORDER_ANY); +MODULE_VERSION(ng_btsocket, NG_BLUETOOTH_VERSION); +MODULE_DEPEND(ng_btsocket, ng_bluetooth, NG_BLUETOOTH_VERSION, + NG_BLUETOOTH_VERSION, NG_BLUETOOTH_VERSION); +MODULE_DEPEND(ng_btsocket, netgraph, NG_ABI_VERSION, + NG_ABI_VERSION, NG_ABI_VERSION); + +/* + * Handle loading and unloading for this node type. + * This is to handle auxiliary linkages (e.g protocol domain addition). + */ + +static int +ng_btsocket_modevent(module_t mod, int event, void *data) +{ + int error = 0; + + switch (event) { + case MOD_LOAD: + net_add_domain(&ng_btsocket_domain); + break; + + case MOD_UNLOAD: + /* XXX can't unload protocol domain yet */ + error = EBUSY; + break; + + default: + error = EOPNOTSUPP; + break; + } + + return (error); +} /* ng_btsocket_modevent */ + diff --git a/sys/netgraph7/bluetooth/socket/ng_btsocket_hci_raw.c b/sys/netgraph7/bluetooth/socket/ng_btsocket_hci_raw.c new file mode 100644 index 0000000000..9f4a038917 --- /dev/null +++ b/sys/netgraph7/bluetooth/socket/ng_btsocket_hci_raw.c @@ -0,0 +1,1639 @@ +/* + * ng_btsocket_hci_raw.c + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_btsocket_hci_raw.c,v 1.14 2003/09/14 23:29:06 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/socket/ng_btsocket_hci_raw.c,v 1.23 2006/11/06 13:42:04 rwatson Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* MALLOC define */ +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_BTSOCKET_HCI_RAW, "netgraph_btsocks_hci_raw", + "Netgraph Bluetooth raw HCI sockets"); +#else +#define M_NETGRAPH_BTSOCKET_HCI_RAW M_NETGRAPH +#endif /* NG_SEPARATE_MALLOC */ + +/* Netgraph node methods */ +static ng_constructor_t ng_btsocket_hci_raw_node_constructor; +static ng_rcvmsg_t ng_btsocket_hci_raw_node_rcvmsg; +static ng_shutdown_t ng_btsocket_hci_raw_node_shutdown; +static ng_newhook_t ng_btsocket_hci_raw_node_newhook; +static ng_connect_t ng_btsocket_hci_raw_node_connect; +static ng_rcvdata_t ng_btsocket_hci_raw_node_rcvdata; +static ng_disconnect_t ng_btsocket_hci_raw_node_disconnect; + +static void ng_btsocket_hci_raw_input (void *, int); +static void ng_btsocket_hci_raw_output(node_p, hook_p, void *, int); +static void ng_btsocket_hci_raw_savctl(ng_btsocket_hci_raw_pcb_p, + struct mbuf **, + struct mbuf *); +static int ng_btsocket_hci_raw_filter(ng_btsocket_hci_raw_pcb_p, + struct mbuf *, int); + +#define ng_btsocket_hci_raw_wakeup_input_task() \ + taskqueue_enqueue(taskqueue_swi, &ng_btsocket_hci_raw_task) + +/* Security filter */ +struct ng_btsocket_hci_raw_sec_filter { + bitstr_t bit_decl(events, 0xff); + bitstr_t bit_decl(commands[0x3f], 0x3ff); +}; + +/* Netgraph type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_BTSOCKET_HCI_RAW_NODE_TYPE, + .constructor = ng_btsocket_hci_raw_node_constructor, + .rcvmsg = ng_btsocket_hci_raw_node_rcvmsg, + .shutdown = ng_btsocket_hci_raw_node_shutdown, + .newhook = ng_btsocket_hci_raw_node_newhook, + .connect = ng_btsocket_hci_raw_node_connect, + .rcvdata = ng_btsocket_hci_raw_node_rcvdata, + .disconnect = ng_btsocket_hci_raw_node_disconnect, +}; + +/* Globals */ +extern int ifqmaxlen; +static u_int32_t ng_btsocket_hci_raw_debug_level; +static u_int32_t ng_btsocket_hci_raw_ioctl_timeout; +static node_p ng_btsocket_hci_raw_node; +static struct ng_bt_itemq ng_btsocket_hci_raw_queue; +static struct mtx ng_btsocket_hci_raw_queue_mtx; +static struct task ng_btsocket_hci_raw_task; +static LIST_HEAD(, ng_btsocket_hci_raw_pcb) ng_btsocket_hci_raw_sockets; +static struct mtx ng_btsocket_hci_raw_sockets_mtx; +static u_int32_t ng_btsocket_hci_raw_token; +static struct mtx ng_btsocket_hci_raw_token_mtx; +static struct ng_btsocket_hci_raw_sec_filter *ng_btsocket_hci_raw_sec_filter; + +/* Sysctl tree */ +SYSCTL_DECL(_net_bluetooth_hci_sockets); +SYSCTL_NODE(_net_bluetooth_hci_sockets, OID_AUTO, raw, CTLFLAG_RW, + 0, "Bluetooth raw HCI sockets family"); +SYSCTL_INT(_net_bluetooth_hci_sockets_raw, OID_AUTO, debug_level, CTLFLAG_RW, + &ng_btsocket_hci_raw_debug_level, NG_BTSOCKET_WARN_LEVEL, + "Bluetooth raw HCI sockets debug level"); +SYSCTL_INT(_net_bluetooth_hci_sockets_raw, OID_AUTO, ioctl_timeout, CTLFLAG_RW, + &ng_btsocket_hci_raw_ioctl_timeout, 5, + "Bluetooth raw HCI sockets ioctl timeout"); +SYSCTL_INT(_net_bluetooth_hci_sockets_raw, OID_AUTO, queue_len, CTLFLAG_RD, + &ng_btsocket_hci_raw_queue.len, 0, + "Bluetooth raw HCI sockets input queue length"); +SYSCTL_INT(_net_bluetooth_hci_sockets_raw, OID_AUTO, queue_maxlen, CTLFLAG_RD, + &ng_btsocket_hci_raw_queue.maxlen, 0, + "Bluetooth raw HCI sockets input queue max. length"); +SYSCTL_INT(_net_bluetooth_hci_sockets_raw, OID_AUTO, queue_drops, CTLFLAG_RD, + &ng_btsocket_hci_raw_queue.drops, 0, + "Bluetooth raw HCI sockets input queue drops"); + +/* Debug */ +#define NG_BTSOCKET_HCI_RAW_INFO \ + if (ng_btsocket_hci_raw_debug_level >= NG_BTSOCKET_INFO_LEVEL) \ + printf + +#define NG_BTSOCKET_HCI_RAW_WARN \ + if (ng_btsocket_hci_raw_debug_level >= NG_BTSOCKET_WARN_LEVEL) \ + printf + +#define NG_BTSOCKET_HCI_RAW_ERR \ + if (ng_btsocket_hci_raw_debug_level >= NG_BTSOCKET_ERR_LEVEL) \ + printf + +#define NG_BTSOCKET_HCI_RAW_ALERT \ + if (ng_btsocket_hci_raw_debug_level >= NG_BTSOCKET_ALERT_LEVEL) \ + printf + +/**************************************************************************** + **************************************************************************** + ** Netgraph specific + **************************************************************************** + ****************************************************************************/ + +/* + * Netgraph node constructor. Do not allow to create node of this type. + */ + +static int +ng_btsocket_hci_raw_node_constructor(node_p node) +{ + return (EINVAL); +} /* ng_btsocket_hci_raw_node_constructor */ + +/* + * Netgraph node destructor. Just let old node go and create new fresh one. + */ + +static int +ng_btsocket_hci_raw_node_shutdown(node_p node) +{ + int error = 0; + + NG_NODE_UNREF(node); + + error = ng_make_node_common(&typestruct, &ng_btsocket_hci_raw_node); + if (error != 0) { + NG_BTSOCKET_HCI_RAW_ALERT( +"%s: Could not create Netgraph node, error=%d\n", __func__, error); + + ng_btsocket_hci_raw_node = NULL; + + return (ENOMEM); + } + + error = ng_name_node(ng_btsocket_hci_raw_node, + NG_BTSOCKET_HCI_RAW_NODE_TYPE); + if (error != 0) { + NG_BTSOCKET_HCI_RAW_ALERT( +"%s: Could not name Netgraph node, error=%d\n", __func__, error); + + NG_NODE_UNREF(ng_btsocket_hci_raw_node); + ng_btsocket_hci_raw_node = NULL; + + return (EINVAL); + } + + return (0); +} /* ng_btsocket_hci_raw_node_shutdown */ + +/* + * Create new hook. Just say "yes" + */ + +static int +ng_btsocket_hci_raw_node_newhook(node_p node, hook_p hook, char const *name) +{ + return (0); +} /* ng_btsocket_hci_raw_node_newhook */ + +/* + * Connect hook. Just say "yes" + */ + +static int +ng_btsocket_hci_raw_node_connect(hook_p hook) +{ + return (0); +} /* ng_btsocket_hci_raw_node_connect */ + +/* + * Disconnect hook + */ + +static int +ng_btsocket_hci_raw_node_disconnect(hook_p hook) +{ + return (0); +} /* ng_btsocket_hci_raw_node_disconnect */ + +/* + * Receive control message. + * Make sure it is a message from HCI node and it is a response. + * Enqueue item and schedule input task. + */ + +static int +ng_btsocket_hci_raw_node_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct ng_mesg *msg = NGI_MSG(item); /* item still has message */ + int error = 0; + + /* + * Check for empty sockets list creates LOR when both sender and + * receiver device are connected to the same host, so remove it + * for now + */ + + if (msg != NULL && + (msg->header.typecookie == NGM_HCI_COOKIE || + msg->header.typecookie == NGM_GENERIC_COOKIE) && + msg->header.flags & NGF_RESP) { + if (msg->header.token == 0) { + NG_FREE_ITEM(item); + return (0); + } + + mtx_lock(&ng_btsocket_hci_raw_queue_mtx); + if (NG_BT_ITEMQ_FULL(&ng_btsocket_hci_raw_queue)) { + NG_BTSOCKET_HCI_RAW_ERR( +"%s: Input queue is full\n", __func__); + + NG_BT_ITEMQ_DROP(&ng_btsocket_hci_raw_queue); + NG_FREE_ITEM(item); + error = ENOBUFS; + } else { + NG_BT_ITEMQ_ENQUEUE(&ng_btsocket_hci_raw_queue, item); + error = ng_btsocket_hci_raw_wakeup_input_task(); + } + mtx_unlock(&ng_btsocket_hci_raw_queue_mtx); + } else { + NG_FREE_ITEM(item); + error = EINVAL; + } + + return (error); +} /* ng_btsocket_hci_raw_node_rcvmsg */ + +/* + * Receive packet from the one of our hook. + * Prepend every packet with sockaddr_hci and record sender's node name. + * Enqueue item and schedule input task. + */ + +static int +ng_btsocket_hci_raw_node_rcvdata(hook_p hook, item_p item) +{ + struct mbuf *nam = NULL; + int error; + + /* + * Check for empty sockets list creates LOR when both sender and + * receiver device are connected to the same host, so remove it + * for now + */ + + MGET(nam, M_DONTWAIT, MT_SONAME); + if (nam != NULL) { + struct sockaddr_hci *sa = mtod(nam, struct sockaddr_hci *); + + nam->m_len = sizeof(struct sockaddr_hci); + + sa->hci_len = sizeof(*sa); + sa->hci_family = AF_BLUETOOTH; + strlcpy(sa->hci_node, NG_PEER_NODE_NAME(hook), + sizeof(sa->hci_node)); + + NGI_GET_M(item, nam->m_next); + NGI_M(item) = nam; + + mtx_lock(&ng_btsocket_hci_raw_queue_mtx); + if (NG_BT_ITEMQ_FULL(&ng_btsocket_hci_raw_queue)) { + NG_BTSOCKET_HCI_RAW_ERR( +"%s: Input queue is full\n", __func__); + + NG_BT_ITEMQ_DROP(&ng_btsocket_hci_raw_queue); + NG_FREE_ITEM(item); + error = ENOBUFS; + } else { + NG_BT_ITEMQ_ENQUEUE(&ng_btsocket_hci_raw_queue, item); + error = ng_btsocket_hci_raw_wakeup_input_task(); + } + mtx_unlock(&ng_btsocket_hci_raw_queue_mtx); + } else { + NG_BTSOCKET_HCI_RAW_ERR( +"%s: Failed to allocate address mbuf\n", __func__); + + NG_FREE_ITEM(item); + error = ENOBUFS; + } + + return (error); +} /* ng_btsocket_hci_raw_node_rcvdata */ + +/**************************************************************************** + **************************************************************************** + ** Sockets specific + **************************************************************************** + ****************************************************************************/ + +/* + * Get next token. We need token to avoid theoretical race where process + * submits ioctl() message then interrupts ioctl() and re-submits another + * ioctl() on the same socket *before* first ioctl() complete. + */ + +static void +ng_btsocket_hci_raw_get_token(u_int32_t *token) +{ + mtx_lock(&ng_btsocket_hci_raw_token_mtx); + + if (++ ng_btsocket_hci_raw_token == 0) + ng_btsocket_hci_raw_token = 1; + + *token = ng_btsocket_hci_raw_token; + + mtx_unlock(&ng_btsocket_hci_raw_token_mtx); +} /* ng_btsocket_hci_raw_get_token */ + +/* + * Send Netgraph message to the node - do not expect reply + */ + +static int +ng_btsocket_hci_raw_send_ngmsg(char *path, int cmd, void *arg, int arglen) +{ + struct ng_mesg *msg = NULL; + int error = 0; + + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, cmd, arglen, M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + if (arg != NULL && arglen > 0) + bcopy(arg, msg->data, arglen); + + NG_SEND_MSG_PATH(error, ng_btsocket_hci_raw_node, msg, path, 0); + + return (error); +} /* ng_btsocket_hci_raw_send_ngmsg */ + +/* + * Send Netgraph message to the node (no data) and wait for reply + */ + +static int +ng_btsocket_hci_raw_send_sync_ngmsg(ng_btsocket_hci_raw_pcb_p pcb, char *path, + int cmd, void *rsp, int rsplen) +{ + struct ng_mesg *msg = NULL; + int error = 0; + + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, cmd, 0, M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + ng_btsocket_hci_raw_get_token(&msg->header.token); + pcb->token = msg->header.token; + pcb->msg = NULL; + + NG_SEND_MSG_PATH(error, ng_btsocket_hci_raw_node, msg, path, 0); + if (error != 0) { + pcb->token = 0; + return (error); + } + + error = msleep(&pcb->msg, &pcb->pcb_mtx, PZERO|PCATCH, "hcictl", + ng_btsocket_hci_raw_ioctl_timeout * hz); + pcb->token = 0; + + if (error != 0) + return (error); + + if (pcb->msg != NULL && pcb->msg->header.cmd == cmd) + bcopy(pcb->msg->data, rsp, rsplen); + else + error = EINVAL; + + NG_FREE_MSG(pcb->msg); /* checks for != NULL */ + + return (0); +} /* ng_btsocket_hci_raw_send_sync_ngmsg */ + +/* + * Create control information for the packet + */ + +static void +ng_btsocket_hci_raw_savctl(ng_btsocket_hci_raw_pcb_p pcb, struct mbuf **ctl, + struct mbuf *m) +{ + int dir; + struct timeval tv; + + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + if (pcb->flags & NG_BTSOCKET_HCI_RAW_DIRECTION) { + dir = (m->m_flags & M_PROTO1)? 1 : 0; + *ctl = sbcreatecontrol((caddr_t) &dir, sizeof(dir), + SCM_HCI_RAW_DIRECTION, SOL_HCI_RAW); + if (*ctl != NULL) + ctl = &((*ctl)->m_next); + } + + if (pcb->so->so_options & SO_TIMESTAMP) { + microtime(&tv); + *ctl = sbcreatecontrol((caddr_t) &tv, sizeof(tv), + SCM_TIMESTAMP, SOL_SOCKET); + if (*ctl != NULL) + ctl = &((*ctl)->m_next); + } +} /* ng_btsocket_hci_raw_savctl */ + +/* + * Raw HCI sockets data input routine + */ + +static void +ng_btsocket_hci_raw_data_input(struct mbuf *nam) +{ + ng_btsocket_hci_raw_pcb_p pcb = NULL; + struct mbuf *m0 = NULL, *m = NULL; + struct sockaddr_hci *sa = NULL; + + m0 = nam->m_next; + nam->m_next = NULL; + + KASSERT((nam->m_type == MT_SONAME), + ("%s: m_type=%d\n", __func__, nam->m_type)); + KASSERT((m0->m_flags & M_PKTHDR), + ("%s: m_flags=%#x\n", __func__, m0->m_flags)); + + sa = mtod(nam, struct sockaddr_hci *); + + mtx_lock(&ng_btsocket_hci_raw_sockets_mtx); + + LIST_FOREACH(pcb, &ng_btsocket_hci_raw_sockets, next) { + + mtx_lock(&pcb->pcb_mtx); + + /* + * If socket was bound then check address and + * make sure it matches. + */ + + if (pcb->addr.hci_node[0] != 0 && + strcmp(sa->hci_node, pcb->addr.hci_node) != 0) + goto next; + + /* + * Check packet against filters + * XXX do we have to call m_pullup() here? + */ + + if (ng_btsocket_hci_raw_filter(pcb, m0, 1) != 0) + goto next; + + /* + * Make a copy of the packet, append to the socket's + * receive queue and wakeup socket. sbappendaddr() + * will check if socket has enough buffer space. + */ + + m = m_dup(m0, M_DONTWAIT); + if (m != NULL) { + struct mbuf *ctl = NULL; + + ng_btsocket_hci_raw_savctl(pcb, &ctl, m); + + if (sbappendaddr(&pcb->so->so_rcv, + (struct sockaddr *) sa, m, ctl)) + sorwakeup(pcb->so); + else { + NG_BTSOCKET_HCI_RAW_INFO( +"%s: sbappendaddr() failed\n", __func__); + + NG_FREE_M(m); + NG_FREE_M(ctl); + } + } +next: + mtx_unlock(&pcb->pcb_mtx); + } + + mtx_unlock(&ng_btsocket_hci_raw_sockets_mtx); + + NG_FREE_M(nam); + NG_FREE_M(m0); +} /* ng_btsocket_hci_raw_data_input */ + +/* + * Raw HCI sockets message input routine + */ + +static void +ng_btsocket_hci_raw_msg_input(struct ng_mesg *msg) +{ + ng_btsocket_hci_raw_pcb_p pcb = NULL; + + mtx_lock(&ng_btsocket_hci_raw_sockets_mtx); + + LIST_FOREACH(pcb, &ng_btsocket_hci_raw_sockets, next) { + mtx_lock(&pcb->pcb_mtx); + + if (msg->header.token == pcb->token) { + pcb->msg = msg; + wakeup(&pcb->msg); + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_hci_raw_sockets_mtx); + + return; + } + + mtx_unlock(&pcb->pcb_mtx); + } + + mtx_unlock(&ng_btsocket_hci_raw_sockets_mtx); + + NG_FREE_MSG(msg); /* checks for != NULL */ +} /* ng_btsocket_hci_raw_msg_input */ + +/* + * Raw HCI sockets input routines + */ + +static void +ng_btsocket_hci_raw_input(void *context, int pending) +{ + item_p item = NULL; + + for (;;) { + mtx_lock(&ng_btsocket_hci_raw_queue_mtx); + NG_BT_ITEMQ_DEQUEUE(&ng_btsocket_hci_raw_queue, item); + mtx_unlock(&ng_btsocket_hci_raw_queue_mtx); + + if (item == NULL) + break; + + switch(item->el_flags & NGQF_TYPE) { + case NGQF_DATA: { + struct mbuf *m = NULL; + + NGI_GET_M(item, m); + ng_btsocket_hci_raw_data_input(m); + } break; + + case NGQF_MESG: { + struct ng_mesg *msg = NULL; + + NGI_GET_MSG(item, msg); + ng_btsocket_hci_raw_msg_input(msg); + } break; + + default: + KASSERT(0, +("%s: invalid item type=%ld\n", __func__, (item->el_flags & NGQF_TYPE))); + break; + } + + NG_FREE_ITEM(item); + } +} /* ng_btsocket_hci_raw_input */ + +/* + * Raw HCI sockets output routine + */ + +static void +ng_btsocket_hci_raw_output(node_p node, hook_p hook, void *arg1, int arg2) +{ + struct mbuf *nam = (struct mbuf *) arg1, *m = NULL; + struct sockaddr_hci *sa = NULL; + int error; + + m = nam->m_next; + nam->m_next = NULL; + + KASSERT((nam->m_type == MT_SONAME), + ("%s: m_type=%d\n", __func__, nam->m_type)); + KASSERT((m->m_flags & M_PKTHDR), + ("%s: m_flags=%#x\n", __func__, m->m_flags)); + + sa = mtod(nam, struct sockaddr_hci *); + + /* + * Find downstream hook + * XXX For now access node hook list directly. Should be safe because + * we used ng_send_fn() and we should have exclusive lock on the node. + */ + + LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) { + if (hook == NULL || NG_HOOK_NOT_VALID(hook) || + NG_NODE_NOT_VALID(NG_PEER_NODE(hook))) + continue; + + if (strcmp(sa->hci_node, NG_PEER_NODE_NAME(hook)) == 0) { + NG_SEND_DATA_ONLY(error, hook, m); /* sets m to NULL */ + break; + } + } + + NG_FREE_M(nam); /* check for != NULL */ + NG_FREE_M(m); +} /* ng_btsocket_hci_raw_output */ + +/* + * Check frame against security and socket filters. + * d (direction bit) == 1 means incoming frame. + */ + +static int +ng_btsocket_hci_raw_filter(ng_btsocket_hci_raw_pcb_p pcb, struct mbuf *m, int d) +{ + int type, event, opcode; + + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + switch ((type = *mtod(m, u_int8_t *))) { + case NG_HCI_CMD_PKT: + if (!(pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED)) { + opcode = le16toh(mtod(m, ng_hci_cmd_pkt_t *)->opcode); + + if (!bit_test( +ng_btsocket_hci_raw_sec_filter->commands[NG_HCI_OGF(opcode) - 1], +NG_HCI_OCF(opcode) - 1)) + return (EPERM); + } + + if (d && !bit_test(pcb->filter.packet_mask, NG_HCI_CMD_PKT - 1)) + return (EPERM); + break; + + case NG_HCI_ACL_DATA_PKT: + case NG_HCI_SCO_DATA_PKT: + if (!(pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED) || + !bit_test(pcb->filter.packet_mask, type - 1) || + !d) + return (EPERM); + break; + + case NG_HCI_EVENT_PKT: + if (!d) + return (EINVAL); + + event = mtod(m, ng_hci_event_pkt_t *)->event - 1; + + if (!(pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED)) + if (!bit_test(ng_btsocket_hci_raw_sec_filter->events, event)) + return (EPERM); + + if (!bit_test(pcb->filter.event_mask, event)) + return (EPERM); + break; + + default: + return (EINVAL); + } + + return (0); +} /* ng_btsocket_hci_raw_filter */ + +/* + * Initialize everything + */ + +void +ng_btsocket_hci_raw_init(void) +{ + bitstr_t *f = NULL; + int error = 0; + + ng_btsocket_hci_raw_node = NULL; + ng_btsocket_hci_raw_debug_level = NG_BTSOCKET_WARN_LEVEL; + ng_btsocket_hci_raw_ioctl_timeout = 5; + + /* Register Netgraph node type */ + error = ng_newtype(&typestruct); + if (error != 0) { + NG_BTSOCKET_HCI_RAW_ALERT( +"%s: Could not register Netgraph node type, error=%d\n", __func__, error); + + return; + } + + /* Create Netgrapg node */ + error = ng_make_node_common(&typestruct, &ng_btsocket_hci_raw_node); + if (error != 0) { + NG_BTSOCKET_HCI_RAW_ALERT( +"%s: Could not create Netgraph node, error=%d\n", __func__, error); + + ng_btsocket_hci_raw_node = NULL; + + return; + } + + error = ng_name_node(ng_btsocket_hci_raw_node, + NG_BTSOCKET_HCI_RAW_NODE_TYPE); + if (error != 0) { + NG_BTSOCKET_HCI_RAW_ALERT( +"%s: Could not name Netgraph node, error=%d\n", __func__, error); + + NG_NODE_UNREF(ng_btsocket_hci_raw_node); + ng_btsocket_hci_raw_node = NULL; + + return; + } + + /* Create input queue */ + NG_BT_ITEMQ_INIT(&ng_btsocket_hci_raw_queue, ifqmaxlen); + mtx_init(&ng_btsocket_hci_raw_queue_mtx, + "btsocks_hci_raw_queue_mtx", NULL, MTX_DEF); + TASK_INIT(&ng_btsocket_hci_raw_task, 0, + ng_btsocket_hci_raw_input, NULL); + + /* Create list of sockets */ + LIST_INIT(&ng_btsocket_hci_raw_sockets); + mtx_init(&ng_btsocket_hci_raw_sockets_mtx, + "btsocks_hci_raw_sockets_mtx", NULL, MTX_DEF); + + /* Tokens */ + ng_btsocket_hci_raw_token = 0; + mtx_init(&ng_btsocket_hci_raw_token_mtx, + "btsocks_hci_raw_token_mtx", NULL, MTX_DEF); + + /* + * Security filter + * XXX never FREE()ed + */ + + ng_btsocket_hci_raw_sec_filter = NULL; + + MALLOC(ng_btsocket_hci_raw_sec_filter, + struct ng_btsocket_hci_raw_sec_filter *, + sizeof(struct ng_btsocket_hci_raw_sec_filter), + M_NETGRAPH_BTSOCKET_HCI_RAW, M_NOWAIT|M_ZERO); + if (ng_btsocket_hci_raw_sec_filter == NULL) { + printf("%s: Could not allocate security filter!\n", __func__); + return; + } + + /* + * XXX How paranoid can we get? + * + * Initialize security filter. If bit is set in the mask then + * unprivileged socket is allowed to send (receive) this command + * (event). + */ + + /* Enable all events */ + memset(&ng_btsocket_hci_raw_sec_filter->events, 0xff, + sizeof(ng_btsocket_hci_raw_sec_filter->events)/ + sizeof(ng_btsocket_hci_raw_sec_filter->events[0])); + + /* Disable some critical events */ + f = ng_btsocket_hci_raw_sec_filter->events; + bit_clear(f, NG_HCI_EVENT_RETURN_LINK_KEYS - 1); + bit_clear(f, NG_HCI_EVENT_LINK_KEY_NOTIFICATION - 1); + bit_clear(f, NG_HCI_EVENT_VENDOR - 1); + + /* Commands - Link control */ + f = ng_btsocket_hci_raw_sec_filter->commands[NG_HCI_OGF_LINK_CONTROL-1]; + bit_set(f, NG_HCI_OCF_INQUIRY - 1); + bit_set(f, NG_HCI_OCF_INQUIRY_CANCEL - 1); + bit_set(f, NG_HCI_OCF_PERIODIC_INQUIRY - 1); + bit_set(f, NG_HCI_OCF_EXIT_PERIODIC_INQUIRY - 1); + bit_set(f, NG_HCI_OCF_REMOTE_NAME_REQ - 1); + bit_set(f, NG_HCI_OCF_READ_REMOTE_FEATURES - 1); + bit_set(f, NG_HCI_OCF_READ_REMOTE_VER_INFO - 1); + bit_set(f, NG_HCI_OCF_READ_CLOCK_OFFSET - 1); + + /* Commands - Link policy */ + f = ng_btsocket_hci_raw_sec_filter->commands[NG_HCI_OGF_LINK_POLICY-1]; + bit_set(f, NG_HCI_OCF_ROLE_DISCOVERY - 1); + bit_set(f, NG_HCI_OCF_READ_LINK_POLICY_SETTINGS - 1); + + /* Commands - Host controller and baseband */ + f = ng_btsocket_hci_raw_sec_filter->commands[NG_HCI_OGF_HC_BASEBAND-1]; + bit_set(f, NG_HCI_OCF_READ_PIN_TYPE - 1); + bit_set(f, NG_HCI_OCF_READ_LOCAL_NAME - 1); + bit_set(f, NG_HCI_OCF_READ_CON_ACCEPT_TIMO - 1); + bit_set(f, NG_HCI_OCF_READ_PAGE_TIMO - 1); + bit_set(f, NG_HCI_OCF_READ_SCAN_ENABLE - 1); + bit_set(f, NG_HCI_OCF_READ_PAGE_SCAN_ACTIVITY - 1); + bit_set(f, NG_HCI_OCF_READ_INQUIRY_SCAN_ACTIVITY - 1); + bit_set(f, NG_HCI_OCF_READ_AUTH_ENABLE - 1); + bit_set(f, NG_HCI_OCF_READ_ENCRYPTION_MODE - 1); + bit_set(f, NG_HCI_OCF_READ_UNIT_CLASS - 1); + bit_set(f, NG_HCI_OCF_READ_VOICE_SETTINGS - 1); + bit_set(f, NG_HCI_OCF_READ_AUTO_FLUSH_TIMO - 1); + bit_set(f, NG_HCI_OCF_READ_NUM_BROADCAST_RETRANS - 1); + bit_set(f, NG_HCI_OCF_READ_HOLD_MODE_ACTIVITY - 1); + bit_set(f, NG_HCI_OCF_READ_XMIT_LEVEL - 1); + bit_set(f, NG_HCI_OCF_READ_SCO_FLOW_CONTROL - 1); + bit_set(f, NG_HCI_OCF_READ_LINK_SUPERVISION_TIMO - 1); + bit_set(f, NG_HCI_OCF_READ_SUPPORTED_IAC_NUM - 1); + bit_set(f, NG_HCI_OCF_READ_IAC_LAP - 1); + bit_set(f, NG_HCI_OCF_READ_PAGE_SCAN_PERIOD - 1); + bit_set(f, NG_HCI_OCF_READ_PAGE_SCAN - 1); + + /* Commands - Informational */ + f = ng_btsocket_hci_raw_sec_filter->commands[NG_HCI_OGF_INFO - 1]; + bit_set(f, NG_HCI_OCF_READ_LOCAL_VER - 1); + bit_set(f, NG_HCI_OCF_READ_LOCAL_FEATURES - 1); + bit_set(f, NG_HCI_OCF_READ_BUFFER_SIZE - 1); + bit_set(f, NG_HCI_OCF_READ_COUNTRY_CODE - 1); + bit_set(f, NG_HCI_OCF_READ_BDADDR - 1); + + /* Commands - Status */ + f = ng_btsocket_hci_raw_sec_filter->commands[NG_HCI_OGF_STATUS - 1]; + bit_set(f, NG_HCI_OCF_READ_FAILED_CONTACT_CNTR - 1); + bit_set(f, NG_HCI_OCF_GET_LINK_QUALITY - 1); + bit_set(f, NG_HCI_OCF_READ_RSSI - 1); + + /* Commands - Testing */ + f = ng_btsocket_hci_raw_sec_filter->commands[NG_HCI_OGF_TESTING - 1]; + bit_set(f, NG_HCI_OCF_READ_LOOPBACK_MODE - 1); +} /* ng_btsocket_hci_raw_init */ + +/* + * Abort connection on socket + */ + +void +ng_btsocket_hci_raw_abort(struct socket *so) +{ +} /* ng_btsocket_hci_raw_abort */ + +void +ng_btsocket_hci_raw_close(struct socket *so) +{ +} /* ng_btsocket_hci_raw_close */ + +/* + * Create new raw HCI socket + */ + +int +ng_btsocket_hci_raw_attach(struct socket *so, int proto, struct thread *td) +{ + ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); + int error = 0; + + if (pcb != NULL) + return (EISCONN); + + if (ng_btsocket_hci_raw_node == NULL) + return (EPROTONOSUPPORT); + if (proto != BLUETOOTH_PROTO_HCI) + return (EPROTONOSUPPORT); + if (so->so_type != SOCK_RAW) + return (ESOCKTNOSUPPORT); + + error = soreserve(so, NG_BTSOCKET_HCI_RAW_SENDSPACE, + NG_BTSOCKET_HCI_RAW_RECVSPACE); + if (error != 0) + return (error); + + MALLOC(pcb, ng_btsocket_hci_raw_pcb_p, sizeof(*pcb), + M_NETGRAPH_BTSOCKET_HCI_RAW, M_NOWAIT|M_ZERO); + if (pcb == NULL) + return (ENOMEM); + + so->so_pcb = (caddr_t) pcb; + pcb->so = so; + + if (priv_check(td, PRIV_NETBLUETOOTH_RAW) == 0) + pcb->flags |= NG_BTSOCKET_HCI_RAW_PRIVILEGED; + + /* + * Set default socket filter. By default socket only accepts HCI + * Command_Complete and Command_Status event packets. + */ + + bit_set(pcb->filter.event_mask, NG_HCI_EVENT_COMMAND_COMPL - 1); + bit_set(pcb->filter.event_mask, NG_HCI_EVENT_COMMAND_STATUS - 1); + + mtx_init(&pcb->pcb_mtx, "btsocks_hci_raw_pcb_mtx", NULL, MTX_DEF); + + mtx_lock(&ng_btsocket_hci_raw_sockets_mtx); + LIST_INSERT_HEAD(&ng_btsocket_hci_raw_sockets, pcb, next); + mtx_unlock(&ng_btsocket_hci_raw_sockets_mtx); + + return (0); +} /* ng_btsocket_hci_raw_attach */ + +/* + * Bind raw HCI socket + */ + +int +ng_btsocket_hci_raw_bind(struct socket *so, struct sockaddr *nam, + struct thread *td) +{ + ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); + struct sockaddr_hci *sa = (struct sockaddr_hci *) nam; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_hci_raw_node == NULL) + return (EINVAL); + + if (sa == NULL) + return (EINVAL); + if (sa->hci_family != AF_BLUETOOTH) + return (EAFNOSUPPORT); + if (sa->hci_len != sizeof(*sa)) + return (EINVAL); + if (sa->hci_node[0] == 0) + return (EINVAL); + + mtx_lock(&pcb->pcb_mtx); + bcopy(sa, &pcb->addr, sizeof(pcb->addr)); + mtx_unlock(&pcb->pcb_mtx); + + return (0); +} /* ng_btsocket_hci_raw_bind */ + +/* + * Connect raw HCI socket + */ + +int +ng_btsocket_hci_raw_connect(struct socket *so, struct sockaddr *nam, + struct thread *td) +{ + ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); + struct sockaddr_hci *sa = (struct sockaddr_hci *) nam; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_hci_raw_node == NULL) + return (EINVAL); + + if (sa == NULL) + return (EINVAL); + if (sa->hci_family != AF_BLUETOOTH) + return (EAFNOSUPPORT); + if (sa->hci_len != sizeof(*sa)) + return (EINVAL); + if (sa->hci_node[0] == 0) + return (EDESTADDRREQ); + + mtx_lock(&pcb->pcb_mtx); + + if (bcmp(sa, &pcb->addr, sizeof(pcb->addr)) != 0) { + mtx_unlock(&pcb->pcb_mtx); + return (EADDRNOTAVAIL); + } + + soisconnected(so); + + mtx_unlock(&pcb->pcb_mtx); + + return (0); +} /* ng_btsocket_hci_raw_connect */ + +/* + * Process ioctl on socket + */ + +int +ng_btsocket_hci_raw_control(struct socket *so, u_long cmd, caddr_t data, + struct ifnet *ifp, struct thread *td) +{ + ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); + char path[NG_NODESIZ + 1]; + struct ng_mesg *msg = NULL; + int error = 0; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_hci_raw_node == NULL) + return (EINVAL); + + mtx_lock(&pcb->pcb_mtx); + + /* Check if we have device name */ + if (pcb->addr.hci_node[0] == 0) { + mtx_unlock(&pcb->pcb_mtx); + return (EHOSTUNREACH); + } + + /* Check if we have pending ioctl() */ + if (pcb->token != 0) { + mtx_unlock(&pcb->pcb_mtx); + return (EBUSY); + } + + snprintf(path, sizeof(path), "%s:", pcb->addr.hci_node); + + switch (cmd) { + case SIOC_HCI_RAW_NODE_GET_STATE: { + struct ng_btsocket_hci_raw_node_state *p = + (struct ng_btsocket_hci_raw_node_state *) data; + + error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, + NGM_HCI_NODE_GET_STATE, + &p->state, sizeof(p->state)); + } break; + + case SIOC_HCI_RAW_NODE_INIT: + if (pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED) + error = ng_btsocket_hci_raw_send_ngmsg(path, + NGM_HCI_NODE_INIT, NULL, 0); + else + error = EPERM; + break; + + case SIOC_HCI_RAW_NODE_GET_DEBUG: { + struct ng_btsocket_hci_raw_node_debug *p = + (struct ng_btsocket_hci_raw_node_debug *) data; + + error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, + NGM_HCI_NODE_GET_DEBUG, + &p->debug, sizeof(p->debug)); + } break; + + case SIOC_HCI_RAW_NODE_SET_DEBUG: { + struct ng_btsocket_hci_raw_node_debug *p = + (struct ng_btsocket_hci_raw_node_debug *) data; + + if (pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED) + error = ng_btsocket_hci_raw_send_ngmsg(path, + NGM_HCI_NODE_SET_DEBUG, &p->debug, + sizeof(p->debug)); + else + error = EPERM; + } break; + + case SIOC_HCI_RAW_NODE_GET_BUFFER: { + struct ng_btsocket_hci_raw_node_buffer *p = + (struct ng_btsocket_hci_raw_node_buffer *) data; + + error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, + NGM_HCI_NODE_GET_BUFFER, + &p->buffer, sizeof(p->buffer)); + } break; + + case SIOC_HCI_RAW_NODE_GET_BDADDR: { + struct ng_btsocket_hci_raw_node_bdaddr *p = + (struct ng_btsocket_hci_raw_node_bdaddr *) data; + + error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, + NGM_HCI_NODE_GET_BDADDR, + &p->bdaddr, sizeof(p->bdaddr)); + } break; + + case SIOC_HCI_RAW_NODE_GET_FEATURES: { + struct ng_btsocket_hci_raw_node_features *p = + (struct ng_btsocket_hci_raw_node_features *) data; + + error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, + NGM_HCI_NODE_GET_FEATURES, + &p->features, sizeof(p->features)); + } break; + + case SIOC_HCI_RAW_NODE_GET_STAT: { + struct ng_btsocket_hci_raw_node_stat *p = + (struct ng_btsocket_hci_raw_node_stat *) data; + + error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, + NGM_HCI_NODE_GET_STAT, + &p->stat, sizeof(p->stat)); + } break; + + case SIOC_HCI_RAW_NODE_RESET_STAT: + if (pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED) + error = ng_btsocket_hci_raw_send_ngmsg(path, + NGM_HCI_NODE_RESET_STAT, NULL, 0); + else + error = EPERM; + break; + + case SIOC_HCI_RAW_NODE_FLUSH_NEIGHBOR_CACHE: + if (pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED) + error = ng_btsocket_hci_raw_send_ngmsg(path, + NGM_HCI_NODE_FLUSH_NEIGHBOR_CACHE, + NULL, 0); + else + error = EPERM; + break; + + case SIOC_HCI_RAW_NODE_GET_NEIGHBOR_CACHE: { + struct ng_btsocket_hci_raw_node_neighbor_cache *p = + (struct ng_btsocket_hci_raw_node_neighbor_cache *) data; + ng_hci_node_get_neighbor_cache_ep *p1 = NULL; + ng_hci_node_neighbor_cache_entry_ep *p2 = NULL; + + if (p->num_entries <= 0 || + p->num_entries > NG_HCI_MAX_NEIGHBOR_NUM || + p->entries == NULL) { + error = EINVAL; + break; + } + + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, + NGM_HCI_NODE_GET_NEIGHBOR_CACHE, 0, M_NOWAIT); + if (msg == NULL) { + error = ENOMEM; + break; + } + ng_btsocket_hci_raw_get_token(&msg->header.token); + pcb->token = msg->header.token; + pcb->msg = NULL; + + NG_SEND_MSG_PATH(error, ng_btsocket_hci_raw_node, msg, path, 0); + if (error != 0) { + pcb->token = 0; + break; + } + + error = msleep(&pcb->msg, &pcb->pcb_mtx, + PZERO|PCATCH, "hcictl", + ng_btsocket_hci_raw_ioctl_timeout * hz); + pcb->token = 0; + + if (error != 0) + break; + + if (pcb->msg != NULL && + pcb->msg->header.cmd == NGM_HCI_NODE_GET_NEIGHBOR_CACHE) { + /* Return data back to user space */ + p1 = (ng_hci_node_get_neighbor_cache_ep *) + (pcb->msg->data); + p2 = (ng_hci_node_neighbor_cache_entry_ep *) + (p1 + 1); + + p->num_entries = min(p->num_entries, p1->num_entries); + if (p->num_entries > 0) + error = copyout((caddr_t) p2, + (caddr_t) p->entries, + p->num_entries * sizeof(*p2)); + } else + error = EINVAL; + + NG_FREE_MSG(pcb->msg); /* checks for != NULL */ + }break; + + case SIOC_HCI_RAW_NODE_GET_CON_LIST: { + struct ng_btsocket_hci_raw_con_list *p = + (struct ng_btsocket_hci_raw_con_list *) data; + ng_hci_node_con_list_ep *p1 = NULL; + ng_hci_node_con_ep *p2 = NULL; + + if (p->num_connections == 0 || + p->num_connections > NG_HCI_MAX_CON_NUM || + p->connections == NULL) { + error = EINVAL; + break; + } + + NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_NODE_GET_CON_LIST, + 0, M_NOWAIT); + if (msg == NULL) { + error = ENOMEM; + break; + } + ng_btsocket_hci_raw_get_token(&msg->header.token); + pcb->token = msg->header.token; + pcb->msg = NULL; + + NG_SEND_MSG_PATH(error, ng_btsocket_hci_raw_node, msg, path, 0); + if (error != 0) { + pcb->token = 0; + break; + } + + error = msleep(&pcb->msg, &pcb->pcb_mtx, + PZERO|PCATCH, "hcictl", + ng_btsocket_hci_raw_ioctl_timeout * hz); + pcb->token = 0; + + if (error != 0) + break; + + if (pcb->msg != NULL && + pcb->msg->header.cmd == NGM_HCI_NODE_GET_CON_LIST) { + /* Return data back to user space */ + p1 = (ng_hci_node_con_list_ep *)(pcb->msg->data); + p2 = (ng_hci_node_con_ep *)(p1 + 1); + + p->num_connections = min(p->num_connections, + p1->num_connections); + if (p->num_connections > 0) + error = copyout((caddr_t) p2, + (caddr_t) p->connections, + p->num_connections * sizeof(*p2)); + } else + error = EINVAL; + + NG_FREE_MSG(pcb->msg); /* checks for != NULL */ + } break; + + case SIOC_HCI_RAW_NODE_GET_LINK_POLICY_MASK: { + struct ng_btsocket_hci_raw_node_link_policy_mask *p = + (struct ng_btsocket_hci_raw_node_link_policy_mask *) + data; + + error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, + NGM_HCI_NODE_GET_LINK_POLICY_SETTINGS_MASK, + &p->policy_mask, sizeof(p->policy_mask)); + } break; + + case SIOC_HCI_RAW_NODE_SET_LINK_POLICY_MASK: { + struct ng_btsocket_hci_raw_node_link_policy_mask *p = + (struct ng_btsocket_hci_raw_node_link_policy_mask *) + data; + + if (pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED) + error = ng_btsocket_hci_raw_send_ngmsg(path, + NGM_HCI_NODE_SET_LINK_POLICY_SETTINGS_MASK, + &p->policy_mask, + sizeof(p->policy_mask)); + else + error = EPERM; + } break; + + case SIOC_HCI_RAW_NODE_GET_PACKET_MASK: { + struct ng_btsocket_hci_raw_node_packet_mask *p = + (struct ng_btsocket_hci_raw_node_packet_mask *) data; + + error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, + NGM_HCI_NODE_GET_PACKET_MASK, + &p->packet_mask, sizeof(p->packet_mask)); + } break; + + case SIOC_HCI_RAW_NODE_SET_PACKET_MASK: { + struct ng_btsocket_hci_raw_node_packet_mask *p = + (struct ng_btsocket_hci_raw_node_packet_mask *) data; + + if (pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED) + error = ng_btsocket_hci_raw_send_ngmsg(path, + NGM_HCI_NODE_SET_PACKET_MASK, + &p->packet_mask, + sizeof(p->packet_mask)); + else + error = EPERM; + } break; + + case SIOC_HCI_RAW_NODE_GET_ROLE_SWITCH: { + struct ng_btsocket_hci_raw_node_role_switch *p = + (struct ng_btsocket_hci_raw_node_role_switch *) data; + + error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, + NGM_HCI_NODE_GET_ROLE_SWITCH, + &p->role_switch, sizeof(p->role_switch)); + } break; + + case SIOC_HCI_RAW_NODE_SET_ROLE_SWITCH: { + struct ng_btsocket_hci_raw_node_role_switch *p = + (struct ng_btsocket_hci_raw_node_role_switch *) data; + + if (pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED) + error = ng_btsocket_hci_raw_send_ngmsg(path, + NGM_HCI_NODE_SET_ROLE_SWITCH, + &p->role_switch, + sizeof(p->role_switch)); + else + error = EPERM; + } break; + + case SIOC_HCI_RAW_NODE_LIST_NAMES: { + struct ng_btsocket_hci_raw_node_list_names *nl = + (struct ng_btsocket_hci_raw_node_list_names *) data; + struct nodeinfo *ni = nl->names; + + if (nl->num_names == 0) { + error = EINVAL; + break; + } + + NG_MKMESSAGE(msg, NGM_GENERIC_COOKIE, NGM_LISTNAMES, + 0, M_NOWAIT); + if (msg == NULL) { + error = ENOMEM; + break; + } + ng_btsocket_hci_raw_get_token(&msg->header.token); + pcb->token = msg->header.token; + pcb->msg = NULL; + + NG_SEND_MSG_PATH(error, ng_btsocket_hci_raw_node, msg, ".:", 0); + if (error != 0) { + pcb->token = 0; + break; + } + + error = msleep(&pcb->msg, &pcb->pcb_mtx, + PZERO|PCATCH, "hcictl", + ng_btsocket_hci_raw_ioctl_timeout * hz); + pcb->token = 0; + + if (error != 0) + break; + + if (pcb->msg != NULL && pcb->msg->header.cmd == NGM_LISTNAMES) { + /* Return data back to user space */ + struct namelist *nl1 = (struct namelist *) pcb->msg->data; + struct nodeinfo *ni1 = &nl1->nodeinfo[0]; + + while (nl->num_names > 0 && nl1->numnames > 0) { + if (strcmp(ni1->type, NG_HCI_NODE_TYPE) == 0) { + error = copyout((caddr_t) ni1, + (caddr_t) ni, + sizeof(*ni)); + if (error != 0) + break; + + nl->num_names --; + ni ++; + } + + nl1->numnames --; + ni1 ++; + } + + nl->num_names = ni - nl->names; + } else + error = EINVAL; + + NG_FREE_MSG(pcb->msg); /* checks for != NULL */ + } break; + + default: + error = EINVAL; + break; + } + + mtx_unlock(&pcb->pcb_mtx); + + return (error); +} /* ng_btsocket_hci_raw_control */ + +/* + * Process getsockopt/setsockopt system calls + */ + +int +ng_btsocket_hci_raw_ctloutput(struct socket *so, struct sockopt *sopt) +{ + ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); + struct ng_btsocket_hci_raw_filter filter; + int error = 0, dir; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_hci_raw_node == NULL) + return (EINVAL); + + if (sopt->sopt_level != SOL_HCI_RAW) + return (0); + + mtx_lock(&pcb->pcb_mtx); + + switch (sopt->sopt_dir) { + case SOPT_GET: + switch (sopt->sopt_name) { + case SO_HCI_RAW_FILTER: + error = sooptcopyout(sopt, &pcb->filter, + sizeof(pcb->filter)); + break; + + case SO_HCI_RAW_DIRECTION: + dir = (pcb->flags & NG_BTSOCKET_HCI_RAW_DIRECTION)?1:0; + error = sooptcopyout(sopt, &dir, sizeof(dir)); + break; + + default: + error = EINVAL; + break; + } + break; + + case SOPT_SET: + switch (sopt->sopt_name) { + case SO_HCI_RAW_FILTER: + error = sooptcopyin(sopt, &filter, sizeof(filter), + sizeof(filter)); + if (error == 0) + bcopy(&filter, &pcb->filter, + sizeof(pcb->filter)); + break; + + case SO_HCI_RAW_DIRECTION: + error = sooptcopyin(sopt, &dir, sizeof(dir), + sizeof(dir)); + if (error != 0) + break; + + if (dir) + pcb->flags |= NG_BTSOCKET_HCI_RAW_DIRECTION; + else + pcb->flags &= ~NG_BTSOCKET_HCI_RAW_DIRECTION; + break; + + default: + error = EINVAL; + break; + } + break; + + default: + error = EINVAL; + break; + } + + mtx_unlock(&pcb->pcb_mtx); + + return (error); +} /* ng_btsocket_hci_raw_ctloutput */ + +/* + * Detach raw HCI socket + */ + +void +ng_btsocket_hci_raw_detach(struct socket *so) +{ + ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); + + KASSERT(pcb != NULL, ("ng_btsocket_hci_raw_detach: pcb == NULL")); + + if (ng_btsocket_hci_raw_node == NULL) + return; + + mtx_lock(&ng_btsocket_hci_raw_sockets_mtx); + mtx_lock(&pcb->pcb_mtx); + + LIST_REMOVE(pcb, next); + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_hci_raw_sockets_mtx); + + mtx_destroy(&pcb->pcb_mtx); + + bzero(pcb, sizeof(*pcb)); + FREE(pcb, M_NETGRAPH_BTSOCKET_HCI_RAW); + + so->so_pcb = NULL; +} /* ng_btsocket_hci_raw_detach */ + +/* + * Disconnect raw HCI socket + */ + +int +ng_btsocket_hci_raw_disconnect(struct socket *so) +{ + ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_hci_raw_node == NULL) + return (EINVAL); + + mtx_lock(&pcb->pcb_mtx); + soisdisconnected(so); + mtx_unlock(&pcb->pcb_mtx); + + return (0); +} /* ng_btsocket_hci_raw_disconnect */ + +/* + * Get socket peer's address + */ + +int +ng_btsocket_hci_raw_peeraddr(struct socket *so, struct sockaddr **nam) +{ + return (ng_btsocket_hci_raw_sockaddr(so, nam)); +} /* ng_btsocket_hci_raw_peeraddr */ + +/* + * Send data + */ + +int +ng_btsocket_hci_raw_send(struct socket *so, int flags, struct mbuf *m, + struct sockaddr *sa, struct mbuf *control, struct thread *td) +{ + ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); + struct mbuf *nam = NULL; + int error = 0; + + if (ng_btsocket_hci_raw_node == NULL) { + error = ENETDOWN; + goto drop; + } + if (pcb == NULL) { + error = EINVAL; + goto drop; + } + if (control != NULL) { + error = EINVAL; + goto drop; + } + + if (m->m_pkthdr.len < sizeof(ng_hci_cmd_pkt_t) || + m->m_pkthdr.len > sizeof(ng_hci_cmd_pkt_t) + NG_HCI_CMD_PKT_SIZE) { + error = EMSGSIZE; + goto drop; + } + + if (m->m_len < sizeof(ng_hci_cmd_pkt_t)) { + if ((m = m_pullup(m, sizeof(ng_hci_cmd_pkt_t))) == NULL) { + error = ENOBUFS; + goto drop; + } + } + if (*mtod(m, u_int8_t *) != NG_HCI_CMD_PKT) { + error = ENOTSUP; + goto drop; + } + + mtx_lock(&pcb->pcb_mtx); + + error = ng_btsocket_hci_raw_filter(pcb, m, 0); + if (error != 0) { + mtx_unlock(&pcb->pcb_mtx); + goto drop; + } + + if (sa == NULL) { + if (pcb->addr.hci_node[0] == 0) { + mtx_unlock(&pcb->pcb_mtx); + error = EDESTADDRREQ; + goto drop; + } + + sa = (struct sockaddr *) &pcb->addr; + } + + MGET(nam, M_DONTWAIT, MT_SONAME); + if (nam == NULL) { + mtx_unlock(&pcb->pcb_mtx); + error = ENOBUFS; + goto drop; + } + + nam->m_len = sizeof(struct sockaddr_hci); + bcopy(sa,mtod(nam, struct sockaddr_hci *),sizeof(struct sockaddr_hci)); + + nam->m_next = m; + m = NULL; + + mtx_unlock(&pcb->pcb_mtx); + + return (ng_send_fn(ng_btsocket_hci_raw_node, NULL, + ng_btsocket_hci_raw_output, nam, 0)); +drop: + NG_FREE_M(control); /* NG_FREE_M checks for != NULL */ + NG_FREE_M(nam); + NG_FREE_M(m); + + return (error); +} /* ng_btsocket_hci_raw_send */ + +/* + * Get socket address + */ + +int +ng_btsocket_hci_raw_sockaddr(struct socket *so, struct sockaddr **nam) +{ + ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); + struct sockaddr_hci sa; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_hci_raw_node == NULL) + return (EINVAL); + + bzero(&sa, sizeof(sa)); + sa.hci_len = sizeof(sa); + sa.hci_family = AF_BLUETOOTH; + + mtx_lock(&pcb->pcb_mtx); + strlcpy(sa.hci_node, pcb->addr.hci_node, sizeof(sa.hci_node)); + mtx_unlock(&pcb->pcb_mtx); + + *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); + + return ((*nam == NULL)? ENOMEM : 0); +} /* ng_btsocket_hci_raw_sockaddr */ + diff --git a/sys/netgraph7/bluetooth/socket/ng_btsocket_l2cap.c b/sys/netgraph7/bluetooth/socket/ng_btsocket_l2cap.c new file mode 100644 index 0000000000..82544be25c --- /dev/null +++ b/sys/netgraph7/bluetooth/socket/ng_btsocket_l2cap.c @@ -0,0 +1,2814 @@ +/* + * ng_btsocket_l2cap.c + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_btsocket_l2cap.c,v 1.16 2003/09/14 23:29:06 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/socket/ng_btsocket_l2cap.c,v 1.25 2007/10/31 16:17:20 emax Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* MALLOC define */ +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_BTSOCKET_L2CAP, "netgraph_btsocks_l2cap", + "Netgraph Bluetooth L2CAP sockets"); +#else +#define M_NETGRAPH_BTSOCKET_L2CAP M_NETGRAPH +#endif /* NG_SEPARATE_MALLOC */ + +/* Netgraph node methods */ +static ng_constructor_t ng_btsocket_l2cap_node_constructor; +static ng_rcvmsg_t ng_btsocket_l2cap_node_rcvmsg; +static ng_shutdown_t ng_btsocket_l2cap_node_shutdown; +static ng_newhook_t ng_btsocket_l2cap_node_newhook; +static ng_connect_t ng_btsocket_l2cap_node_connect; +static ng_rcvdata_t ng_btsocket_l2cap_node_rcvdata; +static ng_disconnect_t ng_btsocket_l2cap_node_disconnect; + +static void ng_btsocket_l2cap_input (void *, int); +static void ng_btsocket_l2cap_rtclean (void *, int); + +/* Netgraph type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_BTSOCKET_L2CAP_NODE_TYPE, + .constructor = ng_btsocket_l2cap_node_constructor, + .rcvmsg = ng_btsocket_l2cap_node_rcvmsg, + .shutdown = ng_btsocket_l2cap_node_shutdown, + .newhook = ng_btsocket_l2cap_node_newhook, + .connect = ng_btsocket_l2cap_node_connect, + .rcvdata = ng_btsocket_l2cap_node_rcvdata, + .disconnect = ng_btsocket_l2cap_node_disconnect, +}; + +/* Globals */ +extern int ifqmaxlen; +static u_int32_t ng_btsocket_l2cap_debug_level; +static node_p ng_btsocket_l2cap_node; +static struct ng_bt_itemq ng_btsocket_l2cap_queue; +static struct mtx ng_btsocket_l2cap_queue_mtx; +static struct task ng_btsocket_l2cap_queue_task; +static LIST_HEAD(, ng_btsocket_l2cap_pcb) ng_btsocket_l2cap_sockets; +static struct mtx ng_btsocket_l2cap_sockets_mtx; +static LIST_HEAD(, ng_btsocket_l2cap_rtentry) ng_btsocket_l2cap_rt; +static struct mtx ng_btsocket_l2cap_rt_mtx; +static struct task ng_btsocket_l2cap_rt_task; + +/* Sysctl tree */ +SYSCTL_DECL(_net_bluetooth_l2cap_sockets); +SYSCTL_NODE(_net_bluetooth_l2cap_sockets, OID_AUTO, seq, CTLFLAG_RW, + 0, "Bluetooth SEQPACKET L2CAP sockets family"); +SYSCTL_INT(_net_bluetooth_l2cap_sockets_seq, OID_AUTO, debug_level, + CTLFLAG_RW, + &ng_btsocket_l2cap_debug_level, NG_BTSOCKET_WARN_LEVEL, + "Bluetooth SEQPACKET L2CAP sockets debug level"); +SYSCTL_INT(_net_bluetooth_l2cap_sockets_seq, OID_AUTO, queue_len, + CTLFLAG_RD, + &ng_btsocket_l2cap_queue.len, 0, + "Bluetooth SEQPACKET L2CAP sockets input queue length"); +SYSCTL_INT(_net_bluetooth_l2cap_sockets_seq, OID_AUTO, queue_maxlen, + CTLFLAG_RD, + &ng_btsocket_l2cap_queue.maxlen, 0, + "Bluetooth SEQPACKET L2CAP sockets input queue max. length"); +SYSCTL_INT(_net_bluetooth_l2cap_sockets_seq, OID_AUTO, queue_drops, + CTLFLAG_RD, + &ng_btsocket_l2cap_queue.drops, 0, + "Bluetooth SEQPACKET L2CAP sockets input queue drops"); + +/* Debug */ +#define NG_BTSOCKET_L2CAP_INFO \ + if (ng_btsocket_l2cap_debug_level >= NG_BTSOCKET_INFO_LEVEL) \ + printf + +#define NG_BTSOCKET_L2CAP_WARN \ + if (ng_btsocket_l2cap_debug_level >= NG_BTSOCKET_WARN_LEVEL) \ + printf + +#define NG_BTSOCKET_L2CAP_ERR \ + if (ng_btsocket_l2cap_debug_level >= NG_BTSOCKET_ERR_LEVEL) \ + printf + +#define NG_BTSOCKET_L2CAP_ALERT \ + if (ng_btsocket_l2cap_debug_level >= NG_BTSOCKET_ALERT_LEVEL) \ + printf + +/* + * Netgraph message processing routines + */ + +static int ng_btsocket_l2cap_process_l2ca_con_req_rsp + (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); +static int ng_btsocket_l2cap_process_l2ca_con_rsp_rsp + (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); +static int ng_btsocket_l2cap_process_l2ca_con_ind + (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); + +static int ng_btsocket_l2cap_process_l2ca_cfg_req_rsp + (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); +static int ng_btsocket_l2cap_process_l2ca_cfg_rsp_rsp + (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); +static int ng_btsocket_l2cap_process_l2ca_cfg_ind + (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); + +static int ng_btsocket_l2cap_process_l2ca_discon_rsp + (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); +static int ng_btsocket_l2cap_process_l2ca_discon_ind + (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); + +static int ng_btsocket_l2cap_process_l2ca_write_rsp + (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p); + +/* + * Send L2CA_xxx messages to the lower layer + */ + +static int ng_btsocket_l2cap_send_l2ca_con_req + (ng_btsocket_l2cap_pcb_p); +static int ng_btsocket_l2cap_send_l2ca_con_rsp_req + (u_int32_t, ng_btsocket_l2cap_rtentry_p, bdaddr_p, int, int, int); +static int ng_btsocket_l2cap_send_l2ca_cfg_req + (ng_btsocket_l2cap_pcb_p); +static int ng_btsocket_l2cap_send_l2ca_cfg_rsp + (ng_btsocket_l2cap_pcb_p); +static int ng_btsocket_l2cap_send_l2ca_discon_req + (u_int32_t, ng_btsocket_l2cap_pcb_p); + +static int ng_btsocket_l2cap_send2 + (ng_btsocket_l2cap_pcb_p); + +/* + * Timeout processing routines + */ + +static void ng_btsocket_l2cap_timeout (ng_btsocket_l2cap_pcb_p); +static void ng_btsocket_l2cap_untimeout (ng_btsocket_l2cap_pcb_p); +static void ng_btsocket_l2cap_process_timeout (void *); + +/* + * Other stuff + */ + +static ng_btsocket_l2cap_pcb_p ng_btsocket_l2cap_pcb_by_addr(bdaddr_p, int); +static ng_btsocket_l2cap_pcb_p ng_btsocket_l2cap_pcb_by_token(u_int32_t); +static ng_btsocket_l2cap_pcb_p ng_btsocket_l2cap_pcb_by_cid (bdaddr_p, int); +static int ng_btsocket_l2cap_result2errno(int); + +#define ng_btsocket_l2cap_wakeup_input_task() \ + taskqueue_enqueue(taskqueue_swi_giant, &ng_btsocket_l2cap_queue_task) + +#define ng_btsocket_l2cap_wakeup_route_task() \ + taskqueue_enqueue(taskqueue_swi_giant, &ng_btsocket_l2cap_rt_task) + +/***************************************************************************** + ***************************************************************************** + ** Netgraph node interface + ***************************************************************************** + *****************************************************************************/ + +/* + * Netgraph node constructor. Do not allow to create node of this type. + */ + +static int +ng_btsocket_l2cap_node_constructor(node_p node) +{ + return (EINVAL); +} /* ng_btsocket_l2cap_node_constructor */ + +/* + * Do local shutdown processing. Let old node go and create new fresh one. + */ + +static int +ng_btsocket_l2cap_node_shutdown(node_p node) +{ + int error = 0; + + NG_NODE_UNREF(node); + + /* Create new node */ + error = ng_make_node_common(&typestruct, &ng_btsocket_l2cap_node); + if (error != 0) { + NG_BTSOCKET_L2CAP_ALERT( +"%s: Could not create Netgraph node, error=%d\n", __func__, error); + + ng_btsocket_l2cap_node = NULL; + + return (error); + } + + error = ng_name_node(ng_btsocket_l2cap_node, + NG_BTSOCKET_L2CAP_NODE_TYPE); + if (error != 0) { + NG_BTSOCKET_L2CAP_ALERT( +"%s: Could not name Netgraph node, error=%d\n", __func__, error); + + NG_NODE_UNREF(ng_btsocket_l2cap_node); + ng_btsocket_l2cap_node = NULL; + + return (error); + } + + return (0); +} /* ng_btsocket_l2cap_node_shutdown */ + +/* + * We allow any hook to be connected to the node. + */ + +static int +ng_btsocket_l2cap_node_newhook(node_p node, hook_p hook, char const *name) +{ + return (0); +} /* ng_btsocket_l2cap_node_newhook */ + +/* + * Just say "YEP, that's OK by me!" + */ + +static int +ng_btsocket_l2cap_node_connect(hook_p hook) +{ + NG_HOOK_SET_PRIVATE(hook, NULL); + NG_HOOK_REF(hook); /* Keep extra reference to the hook */ + +#if 0 + NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); + NG_HOOK_FORCE_QUEUE(hook); +#endif + + return (0); +} /* ng_btsocket_l2cap_node_connect */ + +/* + * Hook disconnection. Schedule route cleanup task + */ + +static int +ng_btsocket_l2cap_node_disconnect(hook_p hook) +{ + /* + * If hook has private information than we must have this hook in + * the routing table and must schedule cleaning for the routing table. + * Otherwise hook was connected but we never got "hook_info" message, + * so we have never added this hook to the routing table and it save + * to just delete it. + */ + + if (NG_HOOK_PRIVATE(hook) != NULL) + return (ng_btsocket_l2cap_wakeup_route_task()); + + NG_HOOK_UNREF(hook); /* Remove extra reference */ + + return (0); +} /* ng_btsocket_l2cap_node_disconnect */ + +/* + * Process incoming messages + */ + +static int +ng_btsocket_l2cap_node_rcvmsg(node_p node, item_p item, hook_p hook) +{ + struct ng_mesg *msg = NGI_MSG(item); /* item still has message */ + int error = 0; + + if (msg != NULL && msg->header.typecookie == NGM_L2CAP_COOKIE) { + mtx_lock(&ng_btsocket_l2cap_queue_mtx); + if (NG_BT_ITEMQ_FULL(&ng_btsocket_l2cap_queue)) { + NG_BTSOCKET_L2CAP_ERR( +"%s: Input queue is full (msg)\n", __func__); + + NG_BT_ITEMQ_DROP(&ng_btsocket_l2cap_queue); + NG_FREE_ITEM(item); + error = ENOBUFS; + } else { + if (hook != NULL) { + NG_HOOK_REF(hook); + NGI_SET_HOOK(item, hook); + } + + NG_BT_ITEMQ_ENQUEUE(&ng_btsocket_l2cap_queue, item); + error = ng_btsocket_l2cap_wakeup_input_task(); + } + mtx_unlock(&ng_btsocket_l2cap_queue_mtx); + } else { + NG_FREE_ITEM(item); + error = EINVAL; + } + + return (error); +} /* ng_btsocket_l2cap_node_rcvmsg */ + +/* + * Receive data on a hook + */ + +static int +ng_btsocket_l2cap_node_rcvdata(hook_p hook, item_p item) +{ + int error = 0; + + mtx_lock(&ng_btsocket_l2cap_queue_mtx); + if (NG_BT_ITEMQ_FULL(&ng_btsocket_l2cap_queue)) { + NG_BTSOCKET_L2CAP_ERR( +"%s: Input queue is full (data)\n", __func__); + + NG_BT_ITEMQ_DROP(&ng_btsocket_l2cap_queue); + NG_FREE_ITEM(item); + error = ENOBUFS; + } else { + NG_HOOK_REF(hook); + NGI_SET_HOOK(item, hook); + + NG_BT_ITEMQ_ENQUEUE(&ng_btsocket_l2cap_queue, item); + error = ng_btsocket_l2cap_wakeup_input_task(); + } + mtx_unlock(&ng_btsocket_l2cap_queue_mtx); + + return (error); +} /* ng_btsocket_l2cap_node_rcvdata */ + +/* + * Process L2CA_Connect respose. Socket layer must have initiated connection, + * so we have to have a socket associated with message token. + */ + +static int +ng_btsocket_l2cap_process_l2ca_con_req_rsp(struct ng_mesg *msg, + ng_btsocket_l2cap_rtentry_p rt) +{ + ng_l2cap_l2ca_con_op *op = NULL; + ng_btsocket_l2cap_pcb_t *pcb = NULL; + int error = 0; + + if (msg->header.arglen != sizeof(*op)) + return (EMSGSIZE); + + op = (ng_l2cap_l2ca_con_op *)(msg->data); + + mtx_lock(&ng_btsocket_l2cap_sockets_mtx); + + /* Look for the socket with the token */ + pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); + if (pcb == NULL) { + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + return (ENOENT); + } + + mtx_lock(&pcb->pcb_mtx); + + NG_BTSOCKET_L2CAP_INFO( +"%s: Got L2CA_Connect response, token=%d, src bdaddr=%x:%x:%x:%x:%x:%x, " \ +"dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, status=%d, " \ +"state=%d\n", __func__, msg->header.token, + pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], + pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], + pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], + pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], + pcb->psm, op->lcid, op->result, op->status, + pcb->state); + + if (pcb->state != NG_BTSOCKET_L2CAP_CONNECTING) { + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (ENOENT); + } + + ng_btsocket_l2cap_untimeout(pcb); + + if (op->result == NG_L2CAP_PENDING) { + ng_btsocket_l2cap_timeout(pcb); + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (0); + } + + if (op->result == NG_L2CAP_SUCCESS) { + /* + * Channel is now open, so update local channel ID and + * start configuration process. Source and destination + * addresses as well as route must be already set. + */ + + pcb->cid = op->lcid; + + error = ng_btsocket_l2cap_send_l2ca_cfg_req(pcb); + if (error != 0) { + /* Send disconnect request with "zero" token */ + ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); + + /* ... and close the socket */ + pcb->state = NG_BTSOCKET_L2CAP_CLOSED; + soisdisconnected(pcb->so); + } else { + pcb->cfg_state = NG_BTSOCKET_L2CAP_CFG_IN_SENT; + pcb->state = NG_BTSOCKET_L2CAP_CONFIGURING; + + ng_btsocket_l2cap_timeout(pcb); + } + } else { + /* + * We have failed to open connection, so convert result + * code to "errno" code and disconnect the socket. Channel + * already has been closed. + */ + + pcb->so->so_error = ng_btsocket_l2cap_result2errno(op->result); + pcb->state = NG_BTSOCKET_L2CAP_CLOSED; + soisdisconnected(pcb->so); + } + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (error); +} /* ng_btsocket_l2cap_process_l2ca_con_req_rsp */ + +/* + * Process L2CA_ConnectRsp response + */ + +static int +ng_btsocket_l2cap_process_l2ca_con_rsp_rsp(struct ng_mesg *msg, + ng_btsocket_l2cap_rtentry_p rt) +{ + ng_l2cap_l2ca_con_rsp_op *op = NULL; + ng_btsocket_l2cap_pcb_t *pcb = NULL; + + if (msg->header.arglen != sizeof(*op)) + return (EMSGSIZE); + + op = (ng_l2cap_l2ca_con_rsp_op *)(msg->data); + + mtx_lock(&ng_btsocket_l2cap_sockets_mtx); + + /* Look for the socket with the token */ + pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); + if (pcb == NULL) { + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + return (ENOENT); + } + + mtx_lock(&pcb->pcb_mtx); + + NG_BTSOCKET_L2CAP_INFO( +"%s: Got L2CA_ConnectRsp response, token=%d, src bdaddr=%x:%x:%x:%x:%x:%x, " \ +"dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, state=%d\n", + __func__, msg->header.token, + pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], + pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], + pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], + pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], + pcb->psm, pcb->cid, op->result, pcb->state); + + if (pcb->state != NG_BTSOCKET_L2CAP_CONNECTING) { + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (ENOENT); + } + + ng_btsocket_l2cap_untimeout(pcb); + + /* Check the result and disconnect the socket on failure */ + if (op->result != NG_L2CAP_SUCCESS) { + /* Close the socket - channel already closed */ + pcb->so->so_error = ng_btsocket_l2cap_result2errno(op->result); + pcb->state = NG_BTSOCKET_L2CAP_CLOSED; + soisdisconnected(pcb->so); + } else { + /* Move to CONFIGURING state and wait for CONFIG_IND */ + pcb->cfg_state = 0; + pcb->state = NG_BTSOCKET_L2CAP_CONFIGURING; + ng_btsocket_l2cap_timeout(pcb); + } + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (0); +} /* ng_btsocket_process_l2ca_con_rsp_rsp */ + +/* + * Process L2CA_Connect indicator. Find socket that listens on address + * and PSM. Find exact or closest match. Create new socket and initiate + * connection. + */ + +static int +ng_btsocket_l2cap_process_l2ca_con_ind(struct ng_mesg *msg, + ng_btsocket_l2cap_rtentry_p rt) +{ + ng_l2cap_l2ca_con_ind_ip *ip = NULL; + ng_btsocket_l2cap_pcb_t *pcb = NULL, *pcb1 = NULL; + int error = 0; + u_int32_t token = 0; + u_int16_t result = 0; + + if (msg->header.arglen != sizeof(*ip)) + return (EMSGSIZE); + + ip = (ng_l2cap_l2ca_con_ind_ip *)(msg->data); + + NG_BTSOCKET_L2CAP_INFO( +"%s: Got L2CA_Connect indicator, src bdaddr=%x:%x:%x:%x:%x:%x, " \ +"dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, ident=%d\n", + __func__, + rt->src.b[5], rt->src.b[4], rt->src.b[3], + rt->src.b[2], rt->src.b[1], rt->src.b[0], + ip->bdaddr.b[5], ip->bdaddr.b[4], ip->bdaddr.b[3], + ip->bdaddr.b[2], ip->bdaddr.b[1], ip->bdaddr.b[0], + ip->psm, ip->lcid, ip->ident); + + mtx_lock(&ng_btsocket_l2cap_sockets_mtx); + + pcb = ng_btsocket_l2cap_pcb_by_addr(&rt->src, ip->psm); + if (pcb != NULL) { + struct socket *so1 = NULL; + + mtx_lock(&pcb->pcb_mtx); + + /* + * First check the pending connections queue and if we have + * space then create new socket and set proper source address. + */ + + if (pcb->so->so_qlen <= pcb->so->so_qlimit) + so1 = sonewconn(pcb->so, 0); + + if (so1 == NULL) { + result = NG_L2CAP_NO_RESOURCES; + goto respond; + } + + /* + * If we got here than we have created new socket. So complete + * connection. If we we listening on specific address then copy + * source address from listening socket, otherwise copy source + * address from hook's routing information. + */ + + pcb1 = so2l2cap_pcb(so1); + KASSERT((pcb1 != NULL), +("%s: pcb1 == NULL\n", __func__)); + + mtx_lock(&pcb1->pcb_mtx); + + if (bcmp(&pcb->src, NG_HCI_BDADDR_ANY, sizeof(pcb->src)) != 0) + bcopy(&pcb->src, &pcb1->src, sizeof(pcb1->src)); + else + bcopy(&rt->src, &pcb1->src, sizeof(pcb1->src)); + + pcb1->flags &= ~NG_BTSOCKET_L2CAP_CLIENT; + + bcopy(&ip->bdaddr, &pcb1->dst, sizeof(pcb1->dst)); + pcb1->psm = ip->psm; + pcb1->cid = ip->lcid; + pcb1->rt = rt; + + /* Copy socket settings */ + pcb1->imtu = pcb->imtu; + bcopy(&pcb->oflow, &pcb1->oflow, sizeof(pcb1->oflow)); + pcb1->flush_timo = pcb->flush_timo; + + token = pcb1->token; + } else + /* Nobody listens on requested BDADDR/PSM */ + result = NG_L2CAP_PSM_NOT_SUPPORTED; + +respond: + error = ng_btsocket_l2cap_send_l2ca_con_rsp_req(token, rt, + &ip->bdaddr, ip->ident, ip->lcid, result); + if (pcb1 != NULL) { + if (error != 0) { + pcb1->so->so_error = error; + pcb1->state = NG_BTSOCKET_L2CAP_CLOSED; + soisdisconnected(pcb1->so); + } else { + pcb1->state = NG_BTSOCKET_L2CAP_CONNECTING; + soisconnecting(pcb1->so); + + ng_btsocket_l2cap_timeout(pcb1); + } + + mtx_unlock(&pcb1->pcb_mtx); + } + + if (pcb != NULL) + mtx_unlock(&pcb->pcb_mtx); + + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (error); +} /* ng_btsocket_l2cap_process_l2ca_con_ind */ + +/* + * Process L2CA_Config response + */ + +static int +ng_btsocket_l2cap_process_l2ca_cfg_req_rsp(struct ng_mesg *msg, + ng_btsocket_l2cap_rtentry_p rt) +{ + ng_l2cap_l2ca_cfg_op *op = NULL; + ng_btsocket_l2cap_pcb_p pcb = NULL; + + if (msg->header.arglen != sizeof(*op)) + return (EMSGSIZE); + + op = (ng_l2cap_l2ca_cfg_op *)(msg->data); + + mtx_lock(&ng_btsocket_l2cap_sockets_mtx); + + /* + * Socket must have issued a Configure request, so we must have a + * socket that wants to be configured. Use Netgraph message token + * to find it + */ + + pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); + if (pcb == NULL) { + /* + * XXX FIXME what to do here? We could not find a + * socket with requested token. We even can not send + * Disconnect, because we do not know channel ID + */ + + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + return (ENOENT); + } + + mtx_lock(&pcb->pcb_mtx); + + NG_BTSOCKET_L2CAP_INFO( +"%s: Got L2CA_Config response, token=%d, src bdaddr=%x:%x:%x:%x:%x:%x, " \ +"dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, state=%d, " \ +"cfg_state=%x\n", + __func__, msg->header.token, + pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], + pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], + pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], + pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], + pcb->psm, pcb->cid, op->result, pcb->state, pcb->cfg_state); + + if (pcb->state != NG_BTSOCKET_L2CAP_CONFIGURING) { + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (ENOENT); + } + + if (op->result == NG_L2CAP_SUCCESS) { + /* + * XXX FIXME Actually set flush and link timeout. + * Set QoS here if required. Resolve conficts (flush_timo). + * Save incoming MTU (peer's outgoing MTU) and outgoing flow + * spec. + */ + + pcb->imtu = op->imtu; + bcopy(&op->oflow, &pcb->oflow, sizeof(pcb->oflow)); + pcb->flush_timo = op->flush_timo; + + /* + * We have configured incoming side, so record it and check + * if configuration is complete. If complete then mark socket + * as connected, otherwise wait for the peer. + */ + + pcb->cfg_state &= ~NG_BTSOCKET_L2CAP_CFG_IN_SENT; + pcb->cfg_state |= NG_BTSOCKET_L2CAP_CFG_IN; + + if (pcb->cfg_state == NG_BTSOCKET_L2CAP_CFG_BOTH) { + /* Configuration complete - mark socket as open */ + ng_btsocket_l2cap_untimeout(pcb); + pcb->state = NG_BTSOCKET_L2CAP_OPEN; + soisconnected(pcb->so); + } + } else { + /* + * Something went wrong. Could be unacceptable parameters, + * reject or unknown option. That's too bad, but we will + * not negotiate. Send Disconnect and close the channel. + */ + + ng_btsocket_l2cap_untimeout(pcb); + + switch (op->result) { + case NG_L2CAP_UNACCEPTABLE_PARAMS: + case NG_L2CAP_UNKNOWN_OPTION: + pcb->so->so_error = EINVAL; + break; + + default: + pcb->so->so_error = ECONNRESET; + break; + } + + /* Send disconnect with "zero" token */ + ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); + + /* ... and close the socket */ + pcb->state = NG_BTSOCKET_L2CAP_CLOSED; + soisdisconnected(pcb->so); + } + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (0); +} /* ng_btsocket_l2cap_process_l2ca_cfg_req_rsp */ + +/* + * Process L2CA_ConfigRsp response + */ + +static int +ng_btsocket_l2cap_process_l2ca_cfg_rsp_rsp(struct ng_mesg *msg, + ng_btsocket_l2cap_rtentry_p rt) +{ + ng_l2cap_l2ca_cfg_rsp_op *op = NULL; + ng_btsocket_l2cap_pcb_t *pcb = NULL; + int error = 0; + + if (msg->header.arglen != sizeof(*op)) + return (EMSGSIZE); + + op = (ng_l2cap_l2ca_cfg_rsp_op *)(msg->data); + + mtx_lock(&ng_btsocket_l2cap_sockets_mtx); + + /* Look for the socket with the token */ + pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); + if (pcb == NULL) { + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + return (ENOENT); + } + + mtx_lock(&pcb->pcb_mtx); + + NG_BTSOCKET_L2CAP_INFO( +"%s: Got L2CA_ConfigRsp response, token=%d, src bdaddr=%x:%x:%x:%x:%x:%x, " \ +"dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, state=%d, " \ +"cfg_state=%x\n", + __func__, msg->header.token, + pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], + pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], + pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], + pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], + pcb->psm, pcb->cid, op->result, pcb->state, pcb->cfg_state); + + if (pcb->state != NG_BTSOCKET_L2CAP_CONFIGURING) { + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (ENOENT); + } + + /* Check the result and disconnect socket of failure */ + if (op->result != NG_L2CAP_SUCCESS) + goto disconnect; + + /* + * Now we done with remote side configuration. Configure local + * side if we have not done it yet. + */ + + pcb->cfg_state &= ~NG_BTSOCKET_L2CAP_CFG_OUT_SENT; + pcb->cfg_state |= NG_BTSOCKET_L2CAP_CFG_OUT; + + if (pcb->cfg_state == NG_BTSOCKET_L2CAP_CFG_BOTH) { + /* Configuration complete - mask socket as open */ + ng_btsocket_l2cap_untimeout(pcb); + pcb->state = NG_BTSOCKET_L2CAP_OPEN; + soisconnected(pcb->so); + } else { + if (!(pcb->cfg_state & NG_BTSOCKET_L2CAP_CFG_IN_SENT)) { + /* Send L2CA_Config request - incoming path */ + error = ng_btsocket_l2cap_send_l2ca_cfg_req(pcb); + if (error != 0) + goto disconnect; + + pcb->cfg_state |= NG_BTSOCKET_L2CAP_CFG_IN_SENT; + } + } + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (error); + +disconnect: + ng_btsocket_l2cap_untimeout(pcb); + + /* Send disconnect with "zero" token */ + ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); + + /* ... and close the socket */ + pcb->state = NG_BTSOCKET_L2CAP_CLOSED; + soisdisconnected(pcb->so); + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (error); +} /* ng_btsocket_l2cap_process_l2ca_cfg_rsp_rsp */ + +/* + * Process L2CA_Config indicator + */ + +static int +ng_btsocket_l2cap_process_l2ca_cfg_ind(struct ng_mesg *msg, + ng_btsocket_l2cap_rtentry_p rt) +{ + ng_l2cap_l2ca_cfg_ind_ip *ip = NULL; + ng_btsocket_l2cap_pcb_t *pcb = NULL; + int error = 0; + + if (msg->header.arglen != sizeof(*ip)) + return (EMSGSIZE); + + ip = (ng_l2cap_l2ca_cfg_ind_ip *)(msg->data); + + mtx_lock(&ng_btsocket_l2cap_sockets_mtx); + + /* Check for the open socket that has given channel ID */ + pcb = ng_btsocket_l2cap_pcb_by_cid(&rt->src, ip->lcid); + if (pcb == NULL) { + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + return (ENOENT); + } + + mtx_lock(&pcb->pcb_mtx); + + NG_BTSOCKET_L2CAP_INFO( +"%s: Got L2CA_Config indicator, src bdaddr=%x:%x:%x:%x:%x:%x, " \ +"dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, state=%d, cfg_state=%x\n", + __func__, + pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], + pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], + pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], + pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], + pcb->psm, pcb->cid, pcb->state, pcb->cfg_state); + + /* XXX FIXME re-configuration on open socket */ + if (pcb->state != NG_BTSOCKET_L2CAP_CONFIGURING) { + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (ENOENT); + } + + /* + * XXX FIXME Actually set flush and link timeout. Set QoS here if + * required. Resolve conficts (flush_timo). Note outgoing MTU (peer's + * incoming MTU) and incoming flow spec. + */ + + pcb->omtu = ip->omtu; + bcopy(&ip->iflow, &pcb->iflow, sizeof(pcb->iflow)); + pcb->flush_timo = ip->flush_timo; + + /* + * Send L2CA_Config response to our peer and check for the errors, + * if any send disconnect to close the channel. + */ + + if (!(pcb->cfg_state & NG_BTSOCKET_L2CAP_CFG_OUT_SENT)) { + error = ng_btsocket_l2cap_send_l2ca_cfg_rsp(pcb); + if (error != 0) { + ng_btsocket_l2cap_untimeout(pcb); + + pcb->so->so_error = error; + + /* Send disconnect with "zero" token */ + ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); + + /* ... and close the socket */ + pcb->state = NG_BTSOCKET_L2CAP_CLOSED; + soisdisconnected(pcb->so); + } else + pcb->cfg_state |= NG_BTSOCKET_L2CAP_CFG_OUT_SENT; + } + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (error); +} /* ng_btsocket_l2cap_process_l2cap_cfg_ind */ + +/* + * Process L2CA_Disconnect response + */ + +static int +ng_btsocket_l2cap_process_l2ca_discon_rsp(struct ng_mesg *msg, + ng_btsocket_l2cap_rtentry_p rt) +{ + ng_l2cap_l2ca_discon_op *op = NULL; + ng_btsocket_l2cap_pcb_t *pcb = NULL; + + /* Check message */ + if (msg->header.arglen != sizeof(*op)) + return (EMSGSIZE); + + op = (ng_l2cap_l2ca_discon_op *)(msg->data); + + mtx_lock(&ng_btsocket_l2cap_sockets_mtx); + + /* + * Socket layer must have issued L2CA_Disconnect request, so there + * must be a socket that wants to be disconnected. Use Netgraph + * message token to find it. + */ + + pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); + if (pcb == NULL) { + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + return (0); + } + + mtx_lock(&pcb->pcb_mtx); + + /* XXX Close socket no matter what op->result says */ + if (pcb->state != NG_BTSOCKET_L2CAP_CLOSED) { + NG_BTSOCKET_L2CAP_INFO( +"%s: Got L2CA_Disconnect response, token=%d, src bdaddr=%x:%x:%x:%x:%x:%x, " \ +"dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, state=%d\n", + __func__, msg->header.token, + pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], + pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], + pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], + pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], + pcb->psm, pcb->cid, op->result, pcb->state); + + ng_btsocket_l2cap_untimeout(pcb); + + pcb->state = NG_BTSOCKET_L2CAP_CLOSED; + soisdisconnected(pcb->so); + } + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (0); +} /* ng_btsocket_l2cap_process_l2ca_discon_rsp */ + +/* + * Process L2CA_Disconnect indicator + */ + +static int +ng_btsocket_l2cap_process_l2ca_discon_ind(struct ng_mesg *msg, + ng_btsocket_l2cap_rtentry_p rt) +{ + ng_l2cap_l2ca_discon_ind_ip *ip = NULL; + ng_btsocket_l2cap_pcb_t *pcb = NULL; + + /* Check message */ + if (msg->header.arglen != sizeof(*ip)) + return (EMSGSIZE); + + ip = (ng_l2cap_l2ca_discon_ind_ip *)(msg->data); + + mtx_lock(&ng_btsocket_l2cap_sockets_mtx); + + /* Look for the socket with given channel ID */ + pcb = ng_btsocket_l2cap_pcb_by_cid(&rt->src, ip->lcid); + if (pcb == NULL) { + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + return (0); + } + + /* + * Channel has already been destroyed, so disconnect the socket + * and be done with it. If there was any pending request we can + * not do anything here anyway. + */ + + mtx_lock(&pcb->pcb_mtx); + + NG_BTSOCKET_L2CAP_INFO( +"%s: Got L2CA_Disconnect indicator, src bdaddr=%x:%x:%x:%x:%x:%x, " \ +"dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, state=%d\n", + __func__, + pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], + pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], + pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], + pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], + pcb->psm, pcb->cid, pcb->state); + + if (pcb->flags & NG_BTSOCKET_L2CAP_TIMO) + ng_btsocket_l2cap_untimeout(pcb); + + pcb->state = NG_BTSOCKET_L2CAP_CLOSED; + soisdisconnected(pcb->so); + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (0); +} /* ng_btsocket_l2cap_process_l2ca_discon_ind */ + +/* + * Process L2CA_Write response + */ + +static int +ng_btsocket_l2cap_process_l2ca_write_rsp(struct ng_mesg *msg, + ng_btsocket_l2cap_rtentry_p rt) +{ + ng_l2cap_l2ca_write_op *op = NULL; + ng_btsocket_l2cap_pcb_t *pcb = NULL; + + /* Check message */ + if (msg->header.arglen != sizeof(*op)) + return (EMSGSIZE); + + op = (ng_l2cap_l2ca_write_op *)(msg->data); + + mtx_lock(&ng_btsocket_l2cap_sockets_mtx); + + /* Look for the socket with given token */ + pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); + if (pcb == NULL) { + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + return (ENOENT); + } + + mtx_lock(&pcb->pcb_mtx); + + NG_BTSOCKET_L2CAP_INFO( +"%s: Got L2CA_Write response, src bdaddr=%x:%x:%x:%x:%x:%x, " \ +"dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, length=%d, " \ +"state=%d\n", __func__, + pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], + pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], + pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], + pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], + pcb->psm, pcb->cid, op->result, op->length, + pcb->state); + + if (pcb->state != NG_BTSOCKET_L2CAP_OPEN) { + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (ENOENT); + } + + ng_btsocket_l2cap_untimeout(pcb); + + /* + * Check if we have more data to send + */ + + sbdroprecord(&pcb->so->so_snd); + if (pcb->so->so_snd.sb_cc > 0) { + if (ng_btsocket_l2cap_send2(pcb) == 0) + ng_btsocket_l2cap_timeout(pcb); + else + sbdroprecord(&pcb->so->so_snd); /* XXX */ + } + + /* + * Now set the result, drop packet from the socket send queue and + * ask for more (wakeup sender) + */ + + pcb->so->so_error = ng_btsocket_l2cap_result2errno(op->result); + sowwakeup(pcb->so); + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (0); +} /* ng_btsocket_l2cap_process_l2ca_write_rsp */ + +/* + * Send L2CA_Connect request + */ + +static int +ng_btsocket_l2cap_send_l2ca_con_req(ng_btsocket_l2cap_pcb_p pcb) +{ + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_con_ip *ip = NULL; + int error = 0; + + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + if (pcb->rt == NULL || + pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) + return (ENETDOWN); + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CON, + sizeof(*ip), M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + msg->header.token = pcb->token; + + ip = (ng_l2cap_l2ca_con_ip *)(msg->data); + bcopy(&pcb->dst, &ip->bdaddr, sizeof(ip->bdaddr)); + ip->psm = pcb->psm; + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_node, msg,pcb->rt->hook, 0); + + return (error); +} /* ng_btsocket_l2cap_send_l2ca_con_req */ + +/* + * Send L2CA_Connect response + */ + +static int +ng_btsocket_l2cap_send_l2ca_con_rsp_req(u_int32_t token, + ng_btsocket_l2cap_rtentry_p rt, bdaddr_p dst, int ident, + int lcid, int result) +{ + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_con_rsp_ip *ip = NULL; + int error = 0; + + if (rt == NULL || rt->hook == NULL || NG_HOOK_NOT_VALID(rt->hook)) + return (ENETDOWN); + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CON_RSP, + sizeof(*ip), M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + msg->header.token = token; + + ip = (ng_l2cap_l2ca_con_rsp_ip *)(msg->data); + bcopy(dst, &ip->bdaddr, sizeof(ip->bdaddr)); + ip->ident = ident; + ip->lcid = lcid; + ip->result = result; + ip->status = 0; + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_node, msg, rt->hook, 0); + + return (error); +} /* ng_btsocket_l2cap_send_l2ca_con_rsp_req */ + +/* + * Send L2CA_Config request + */ + +static int +ng_btsocket_l2cap_send_l2ca_cfg_req(ng_btsocket_l2cap_pcb_p pcb) +{ + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_cfg_ip *ip = NULL; + int error = 0; + + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + if (pcb->rt == NULL || + pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) + return (ENETDOWN); + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CFG, + sizeof(*ip), M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + msg->header.token = pcb->token; + + ip = (ng_l2cap_l2ca_cfg_ip *)(msg->data); + ip->lcid = pcb->cid; + ip->imtu = pcb->imtu; + bcopy(&pcb->oflow, &ip->oflow, sizeof(ip->oflow)); + ip->flush_timo = pcb->flush_timo; + ip->link_timo = pcb->link_timo; + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_node, msg,pcb->rt->hook, 0); + + return (error); +} /* ng_btsocket_l2cap_send_l2ca_cfg_req */ + +/* + * Send L2CA_Config response + */ + +static int +ng_btsocket_l2cap_send_l2ca_cfg_rsp(ng_btsocket_l2cap_pcb_p pcb) +{ + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_cfg_rsp_ip *ip = NULL; + int error = 0; + + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + if (pcb->rt == NULL || + pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) + return (ENETDOWN); + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CFG_RSP, + sizeof(*ip), M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + msg->header.token = pcb->token; + + ip = (ng_l2cap_l2ca_cfg_rsp_ip *)(msg->data); + ip->lcid = pcb->cid; + ip->omtu = pcb->omtu; + bcopy(&pcb->iflow, &ip->iflow, sizeof(ip->iflow)); + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_node, msg, pcb->rt->hook, 0); + + return (error); +} /* ng_btsocket_l2cap_send_l2ca_cfg_rsp */ + +/* + * Send L2CA_Disconnect request + */ + +static int +ng_btsocket_l2cap_send_l2ca_discon_req(u_int32_t token, + ng_btsocket_l2cap_pcb_p pcb) +{ + struct ng_mesg *msg = NULL; + ng_l2cap_l2ca_discon_ip *ip = NULL; + int error = 0; + + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + if (pcb->rt == NULL || + pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) + return (ENETDOWN); + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_DISCON, + sizeof(*ip), M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + msg->header.token = token; + + ip = (ng_l2cap_l2ca_discon_ip *)(msg->data); + ip->lcid = pcb->cid; + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_node, msg,pcb->rt->hook, 0); + + return (error); +} /* ng_btsocket_l2cap_send_l2ca_discon_req */ + +/***************************************************************************** + ***************************************************************************** + ** Socket interface + ***************************************************************************** + *****************************************************************************/ + +/* + * L2CAP sockets data input routine + */ + +static void +ng_btsocket_l2cap_data_input(struct mbuf *m, hook_p hook) +{ + ng_l2cap_hdr_t *hdr = NULL; + ng_l2cap_clt_hdr_t *clt_hdr = NULL; + ng_btsocket_l2cap_pcb_t *pcb = NULL; + ng_btsocket_l2cap_rtentry_t *rt = NULL; + + if (hook == NULL) { + NG_BTSOCKET_L2CAP_ALERT( +"%s: Invalid source hook for L2CAP data packet\n", __func__); + goto drop; + } + + rt = (ng_btsocket_l2cap_rtentry_t *) NG_HOOK_PRIVATE(hook); + if (rt == NULL) { + NG_BTSOCKET_L2CAP_ALERT( +"%s: Could not find out source bdaddr for L2CAP data packet\n", __func__); + goto drop; + } + + /* Make sure we can access header */ + if (m->m_pkthdr.len < sizeof(*hdr)) { + NG_BTSOCKET_L2CAP_ERR( +"%s: L2CAP data packet too small, len=%d\n", __func__, m->m_pkthdr.len); + goto drop; + } + + if (m->m_len < sizeof(*hdr)) { + m = m_pullup(m, sizeof(*hdr)); + if (m == NULL) + goto drop; + } + + /* Strip L2CAP packet header and verify packet length */ + hdr = mtod(m, ng_l2cap_hdr_t *); + m_adj(m, sizeof(*hdr)); + + if (hdr->length != m->m_pkthdr.len) { + NG_BTSOCKET_L2CAP_ERR( +"%s: Bad L2CAP data packet length, len=%d, length=%d\n", + __func__, m->m_pkthdr.len, hdr->length); + goto drop; + } + + /* + * Now process packet. Two cases: + * + * 1) Normal packet (cid != 2) then find connected socket and append + * mbuf to the socket queue. Wakeup socket. + * + * 2) Broadcast packet (cid == 2) then find all sockets that connected + * to the given PSM and have SO_BROADCAST bit set and append mbuf + * to the socket queue. Wakeup socket. + */ + + NG_BTSOCKET_L2CAP_INFO( +"%s: Received L2CAP data packet: src bdaddr=%x:%x:%x:%x:%x:%x, " \ +"dcid=%d, length=%d\n", + __func__, + rt->src.b[5], rt->src.b[4], rt->src.b[3], + rt->src.b[2], rt->src.b[1], rt->src.b[0], + hdr->dcid, hdr->length); + + if (hdr->dcid >= NG_L2CAP_FIRST_CID) { + + mtx_lock(&ng_btsocket_l2cap_sockets_mtx); + + /* Normal packet: find connected socket */ + pcb = ng_btsocket_l2cap_pcb_by_cid(&rt->src, hdr->dcid); + if (pcb == NULL) { + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + goto drop; + } + + mtx_lock(&pcb->pcb_mtx); + + if (pcb->state != NG_BTSOCKET_L2CAP_OPEN) { + NG_BTSOCKET_L2CAP_ERR( +"%s: No connected socket found, src bdaddr=%x:%x:%x:%x:%x:%x, dcid=%d, " \ +"state=%d\n", __func__, + rt->src.b[5], rt->src.b[4], rt->src.b[3], + rt->src.b[2], rt->src.b[1], rt->src.b[0], + hdr->dcid, pcb->state); + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + goto drop; + } + + /* Check packet size against socket's incoming MTU */ + if (hdr->length > pcb->imtu) { + NG_BTSOCKET_L2CAP_ERR( +"%s: L2CAP data packet too big, src bdaddr=%x:%x:%x:%x:%x:%x, " \ +"dcid=%d, length=%d, imtu=%d\n", + __func__, + rt->src.b[5], rt->src.b[4], rt->src.b[3], + rt->src.b[2], rt->src.b[1], rt->src.b[0], + hdr->dcid, hdr->length, pcb->imtu); + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + goto drop; + } + + /* Check if we have enough space in socket receive queue */ + if (m->m_pkthdr.len > sbspace(&pcb->so->so_rcv)) { + + /* + * This is really bad. Receive queue on socket does + * not have enough space for the packet. We do not + * have any other choice but drop the packet. L2CAP + * does not provide any flow control. + */ + + NG_BTSOCKET_L2CAP_ERR( +"%s: Not enough space in socket receive queue. Dropping L2CAP data packet, " \ +"src bdaddr=%x:%x:%x:%x:%x:%x, dcid=%d, len=%d, space=%ld\n", + __func__, + rt->src.b[5], rt->src.b[4], rt->src.b[3], + rt->src.b[2], rt->src.b[1], rt->src.b[0], + hdr->dcid, m->m_pkthdr.len, + sbspace(&pcb->so->so_rcv)); + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + goto drop; + } + + /* Append packet to the socket receive queue and wakeup */ + sbappendrecord(&pcb->so->so_rcv, m); + m = NULL; + + sorwakeup(pcb->so); + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + } else if (hdr->dcid == NG_L2CAP_CLT_CID) { + /* Broadcast packet: give packet to all sockets */ + + /* Check packet size against connectionless MTU */ + if (hdr->length > NG_L2CAP_MTU_DEFAULT) { + NG_BTSOCKET_L2CAP_ERR( +"%s: Connectionless L2CAP data packet too big, " \ +"src bdaddr=%x:%x:%x:%x:%x:%x, length=%d\n", + __func__, + rt->src.b[5], rt->src.b[4], rt->src.b[3], + rt->src.b[2], rt->src.b[1], rt->src.b[0], + hdr->length); + goto drop; + } + + /* Make sure we can access connectionless header */ + if (m->m_pkthdr.len < sizeof(*clt_hdr)) { + NG_BTSOCKET_L2CAP_ERR( +"%s: Can not get L2CAP connectionless packet header, " \ +"src bdaddr=%x:%x:%x:%x:%x:%x, length=%d\n", + __func__, + rt->src.b[5], rt->src.b[4], rt->src.b[3], + rt->src.b[2], rt->src.b[1], rt->src.b[0], + hdr->length); + goto drop; + } + + if (m->m_len < sizeof(*clt_hdr)) { + m = m_pullup(m, sizeof(*clt_hdr)); + if (m == NULL) + goto drop; + } + + /* Strip connectionless header and deliver packet */ + clt_hdr = mtod(m, ng_l2cap_clt_hdr_t *); + m_adj(m, sizeof(*clt_hdr)); + + NG_BTSOCKET_L2CAP_INFO( +"%s: Got L2CAP connectionless data packet, " \ +"src bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, length=%d\n", + __func__, + rt->src.b[5], rt->src.b[4], rt->src.b[3], + rt->src.b[2], rt->src.b[1], rt->src.b[0], + clt_hdr->psm, hdr->length); + + mtx_lock(&ng_btsocket_l2cap_sockets_mtx); + + LIST_FOREACH(pcb, &ng_btsocket_l2cap_sockets, next) { + struct mbuf *copy = NULL; + + mtx_lock(&pcb->pcb_mtx); + + if (bcmp(&rt->src, &pcb->src, sizeof(pcb->src)) != 0 || + pcb->psm != clt_hdr->psm || + pcb->state != NG_BTSOCKET_L2CAP_OPEN || + (pcb->so->so_options & SO_BROADCAST) == 0 || + m->m_pkthdr.len > sbspace(&pcb->so->so_rcv)) + goto next; + + /* + * Create a copy of the packet and append it to the + * socket's queue. If m_dup() failed - no big deal + * it is a broadcast traffic after all + */ + + copy = m_dup(m, M_DONTWAIT); + if (copy != NULL) { + sbappendrecord(&pcb->so->so_rcv, copy); + sorwakeup(pcb->so); + } +next: + mtx_unlock(&pcb->pcb_mtx); + } + + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + } +drop: + NG_FREE_M(m); /* checks for m != NULL */ +} /* ng_btsocket_l2cap_data_input */ + +/* + * L2CAP sockets default message input routine + */ + +static void +ng_btsocket_l2cap_default_msg_input(struct ng_mesg *msg, hook_p hook) +{ + switch (msg->header.cmd) { + case NGM_L2CAP_NODE_HOOK_INFO: { + ng_btsocket_l2cap_rtentry_t *rt = NULL; + + if (hook == NULL || msg->header.arglen != sizeof(bdaddr_t)) + break; + + if (bcmp(msg->data, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) + break; + + mtx_lock(&ng_btsocket_l2cap_rt_mtx); + + rt = (ng_btsocket_l2cap_rtentry_t *) NG_HOOK_PRIVATE(hook); + if (rt == NULL) { + MALLOC(rt, ng_btsocket_l2cap_rtentry_p, sizeof(*rt), + M_NETGRAPH_BTSOCKET_L2CAP, M_NOWAIT|M_ZERO); + if (rt == NULL) { + mtx_unlock(&ng_btsocket_l2cap_rt_mtx); + break; + } + + LIST_INSERT_HEAD(&ng_btsocket_l2cap_rt, rt, next); + + NG_HOOK_SET_PRIVATE(hook, rt); + } + + bcopy(msg->data, &rt->src, sizeof(rt->src)); + rt->hook = hook; + + mtx_unlock(&ng_btsocket_l2cap_rt_mtx); + + NG_BTSOCKET_L2CAP_INFO( +"%s: Updating hook \"%s\", src bdaddr=%x:%x:%x:%x:%x:%x\n", + __func__, NG_HOOK_NAME(hook), + rt->src.b[5], rt->src.b[4], rt->src.b[3], + rt->src.b[2], rt->src.b[1], rt->src.b[0]); + } break; + + default: + NG_BTSOCKET_L2CAP_WARN( +"%s: Unknown message, cmd=%d\n", __func__, msg->header.cmd); + break; + } + + NG_FREE_MSG(msg); /* Checks for msg != NULL */ +} /* ng_btsocket_l2cap_default_msg_input */ + +/* + * L2CAP sockets L2CA message input routine + */ + +static void +ng_btsocket_l2cap_l2ca_msg_input(struct ng_mesg *msg, hook_p hook) +{ + ng_btsocket_l2cap_rtentry_p rt = NULL; + + if (hook == NULL) { + NG_BTSOCKET_L2CAP_ALERT( +"%s: Invalid source hook for L2CA message\n", __func__); + goto drop; + } + + rt = (ng_btsocket_l2cap_rtentry_p) NG_HOOK_PRIVATE(hook); + if (rt == NULL) { + NG_BTSOCKET_L2CAP_ALERT( +"%s: Could not find out source bdaddr for L2CA message\n", __func__); + goto drop; + } + + switch (msg->header.cmd) { + case NGM_L2CAP_L2CA_CON: /* L2CA_Connect response */ + ng_btsocket_l2cap_process_l2ca_con_req_rsp(msg, rt); + break; + + case NGM_L2CAP_L2CA_CON_RSP: /* L2CA_ConnectRsp response */ + ng_btsocket_l2cap_process_l2ca_con_rsp_rsp(msg, rt); + break; + + case NGM_L2CAP_L2CA_CON_IND: /* L2CA_Connect indicator */ + ng_btsocket_l2cap_process_l2ca_con_ind(msg, rt); + break; + + case NGM_L2CAP_L2CA_CFG: /* L2CA_Config response */ + ng_btsocket_l2cap_process_l2ca_cfg_req_rsp(msg, rt); + break; + + case NGM_L2CAP_L2CA_CFG_RSP: /* L2CA_ConfigRsp response */ + ng_btsocket_l2cap_process_l2ca_cfg_rsp_rsp(msg, rt); + break; + + case NGM_L2CAP_L2CA_CFG_IND: /* L2CA_Config indicator */ + ng_btsocket_l2cap_process_l2ca_cfg_ind(msg, rt); + break; + + case NGM_L2CAP_L2CA_DISCON: /* L2CA_Disconnect response */ + ng_btsocket_l2cap_process_l2ca_discon_rsp(msg, rt); + break; + + case NGM_L2CAP_L2CA_DISCON_IND: /* L2CA_Disconnect indicator */ + ng_btsocket_l2cap_process_l2ca_discon_ind(msg, rt); + break; + + case NGM_L2CAP_L2CA_WRITE: /* L2CA_Write response */ + ng_btsocket_l2cap_process_l2ca_write_rsp(msg, rt); + break; + + /* XXX FIXME add other L2CA messages */ + + default: + NG_BTSOCKET_L2CAP_WARN( +"%s: Unknown L2CA message, cmd=%d\n", __func__, msg->header.cmd); + break; + } +drop: + NG_FREE_MSG(msg); +} /* ng_btsocket_l2cap_l2ca_msg_input */ + +/* + * L2CAP sockets input routine + */ + +static void +ng_btsocket_l2cap_input(void *context, int pending) +{ + item_p item = NULL; + hook_p hook = NULL; + + for (;;) { + mtx_lock(&ng_btsocket_l2cap_queue_mtx); + NG_BT_ITEMQ_DEQUEUE(&ng_btsocket_l2cap_queue, item); + mtx_unlock(&ng_btsocket_l2cap_queue_mtx); + + if (item == NULL) + break; + + NGI_GET_HOOK(item, hook); + if (hook != NULL && NG_HOOK_NOT_VALID(hook)) + goto drop; + + switch(item->el_flags & NGQF_TYPE) { + case NGQF_DATA: { + struct mbuf *m = NULL; + + NGI_GET_M(item, m); + ng_btsocket_l2cap_data_input(m, hook); + } break; + + case NGQF_MESG: { + struct ng_mesg *msg = NULL; + + NGI_GET_MSG(item, msg); + + switch (msg->header.cmd) { + case NGM_L2CAP_L2CA_CON: + case NGM_L2CAP_L2CA_CON_RSP: + case NGM_L2CAP_L2CA_CON_IND: + case NGM_L2CAP_L2CA_CFG: + case NGM_L2CAP_L2CA_CFG_RSP: + case NGM_L2CAP_L2CA_CFG_IND: + case NGM_L2CAP_L2CA_DISCON: + case NGM_L2CAP_L2CA_DISCON_IND: + case NGM_L2CAP_L2CA_WRITE: + /* XXX FIXME add other L2CA messages */ + ng_btsocket_l2cap_l2ca_msg_input(msg, hook); + break; + + default: + ng_btsocket_l2cap_default_msg_input(msg, hook); + break; + } + } break; + + default: + KASSERT(0, +("%s: invalid item type=%ld\n", __func__, (item->el_flags & NGQF_TYPE))); + break; + } +drop: + if (hook != NULL) + NG_HOOK_UNREF(hook); + + NG_FREE_ITEM(item); + } +} /* ng_btsocket_l2cap_input */ + +/* + * Route cleanup task. Gets scheduled when hook is disconnected. Here we + * will find all sockets that use "invalid" hook and disconnect them. + */ + +static void +ng_btsocket_l2cap_rtclean(void *context, int pending) +{ + ng_btsocket_l2cap_pcb_p pcb = NULL, pcb_next = NULL; + ng_btsocket_l2cap_rtentry_p rt = NULL; + + mtx_lock(&ng_btsocket_l2cap_rt_mtx); + mtx_lock(&ng_btsocket_l2cap_sockets_mtx); + + /* + * First disconnect all sockets that use "invalid" hook + */ + + for (pcb = LIST_FIRST(&ng_btsocket_l2cap_sockets); pcb != NULL; ) { + mtx_lock(&pcb->pcb_mtx); + pcb_next = LIST_NEXT(pcb, next); + + if (pcb->rt != NULL && + pcb->rt->hook != NULL && NG_HOOK_NOT_VALID(pcb->rt->hook)) { + if (pcb->flags & NG_BTSOCKET_L2CAP_TIMO) + ng_btsocket_l2cap_untimeout(pcb); + + pcb->so->so_error = ENETDOWN; + pcb->state = NG_BTSOCKET_L2CAP_CLOSED; + soisdisconnected(pcb->so); + + pcb->token = 0; + pcb->cid = 0; + pcb->rt = NULL; + } + + mtx_unlock(&pcb->pcb_mtx); + pcb = pcb_next; + } + + /* + * Now cleanup routing table + */ + + for (rt = LIST_FIRST(&ng_btsocket_l2cap_rt); rt != NULL; ) { + ng_btsocket_l2cap_rtentry_p rt_next = LIST_NEXT(rt, next); + + if (rt->hook != NULL && NG_HOOK_NOT_VALID(rt->hook)) { + LIST_REMOVE(rt, next); + + NG_HOOK_SET_PRIVATE(rt->hook, NULL); + NG_HOOK_UNREF(rt->hook); /* Remove extra reference */ + + bzero(rt, sizeof(*rt)); + FREE(rt, M_NETGRAPH_BTSOCKET_L2CAP); + } + + rt = rt_next; + } + + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + mtx_unlock(&ng_btsocket_l2cap_rt_mtx); +} /* ng_btsocket_l2cap_rtclean */ + +/* + * Initialize everything + */ + +void +ng_btsocket_l2cap_init(void) +{ + int error = 0; + + ng_btsocket_l2cap_node = NULL; + ng_btsocket_l2cap_debug_level = NG_BTSOCKET_WARN_LEVEL; + + /* Register Netgraph node type */ + error = ng_newtype(&typestruct); + if (error != 0) { + NG_BTSOCKET_L2CAP_ALERT( +"%s: Could not register Netgraph node type, error=%d\n", __func__, error); + + return; + } + + /* Create Netgrapg node */ + error = ng_make_node_common(&typestruct, &ng_btsocket_l2cap_node); + if (error != 0) { + NG_BTSOCKET_L2CAP_ALERT( +"%s: Could not create Netgraph node, error=%d\n", __func__, error); + + ng_btsocket_l2cap_node = NULL; + + return; + } + + error = ng_name_node(ng_btsocket_l2cap_node, + NG_BTSOCKET_L2CAP_NODE_TYPE); + if (error != 0) { + NG_BTSOCKET_L2CAP_ALERT( +"%s: Could not name Netgraph node, error=%d\n", __func__, error); + + NG_NODE_UNREF(ng_btsocket_l2cap_node); + ng_btsocket_l2cap_node = NULL; + + return; + } + + /* Create input queue */ + NG_BT_ITEMQ_INIT(&ng_btsocket_l2cap_queue, ifqmaxlen); + mtx_init(&ng_btsocket_l2cap_queue_mtx, + "btsocks_l2cap_queue_mtx", NULL, MTX_DEF); + TASK_INIT(&ng_btsocket_l2cap_queue_task, 0, + ng_btsocket_l2cap_input, NULL); + + /* Create list of sockets */ + LIST_INIT(&ng_btsocket_l2cap_sockets); + mtx_init(&ng_btsocket_l2cap_sockets_mtx, + "btsocks_l2cap_sockets_mtx", NULL, MTX_DEF); + + /* Routing table */ + LIST_INIT(&ng_btsocket_l2cap_rt); + mtx_init(&ng_btsocket_l2cap_rt_mtx, + "btsocks_l2cap_rt_mtx", NULL, MTX_DEF); + TASK_INIT(&ng_btsocket_l2cap_rt_task, 0, + ng_btsocket_l2cap_rtclean, NULL); +} /* ng_btsocket_l2cap_init */ + +/* + * Abort connection on socket + */ + +void +ng_btsocket_l2cap_abort(struct socket *so) +{ + so->so_error = ECONNABORTED; + + (void)ng_btsocket_l2cap_disconnect(so); +} /* ng_btsocket_l2cap_abort */ + +void +ng_btsocket_l2cap_close(struct socket *so) +{ + + (void)ng_btsocket_l2cap_disconnect(so); +} /* ng_btsocket_l2cap_close */ + +/* + * Accept connection on socket. Nothing to do here, socket must be connected + * and ready, so just return peer address and be done with it. + */ + +int +ng_btsocket_l2cap_accept(struct socket *so, struct sockaddr **nam) +{ + if (ng_btsocket_l2cap_node == NULL) + return (EINVAL); + + return (ng_btsocket_l2cap_peeraddr(so, nam)); +} /* ng_btsocket_l2cap_accept */ + +/* + * Create and attach new socket + */ + +int +ng_btsocket_l2cap_attach(struct socket *so, int proto, struct thread *td) +{ + static u_int32_t token = 0; + ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); + int error; + + /* Check socket and protocol */ + if (ng_btsocket_l2cap_node == NULL) + return (EPROTONOSUPPORT); + if (so->so_type != SOCK_SEQPACKET) + return (ESOCKTNOSUPPORT); + +#if 0 /* XXX sonewconn() calls "pru_attach" with proto == 0 */ + if (proto != 0) + if (proto != BLUETOOTH_PROTO_L2CAP) + return (EPROTONOSUPPORT); +#endif /* XXX */ + + if (pcb != NULL) + return (EISCONN); + + /* Reserve send and receive space if it is not reserved yet */ + if ((so->so_snd.sb_hiwat == 0) || (so->so_rcv.sb_hiwat == 0)) { + error = soreserve(so, NG_BTSOCKET_L2CAP_SENDSPACE, + NG_BTSOCKET_L2CAP_RECVSPACE); + if (error != 0) + return (error); + } + + /* Allocate the PCB */ + MALLOC(pcb, ng_btsocket_l2cap_pcb_p, sizeof(*pcb), + M_NETGRAPH_BTSOCKET_L2CAP, M_NOWAIT | M_ZERO); + if (pcb == NULL) + return (ENOMEM); + + /* Link the PCB and the socket */ + so->so_pcb = (caddr_t) pcb; + pcb->so = so; + pcb->state = NG_BTSOCKET_L2CAP_CLOSED; + + /* Initialize PCB */ + pcb->imtu = pcb->omtu = NG_L2CAP_MTU_DEFAULT; + + /* Default flow */ + pcb->iflow.flags = 0x0; + pcb->iflow.service_type = NG_HCI_SERVICE_TYPE_BEST_EFFORT; + pcb->iflow.token_rate = 0xffffffff; /* maximum */ + pcb->iflow.token_bucket_size = 0xffffffff; /* maximum */ + pcb->iflow.peak_bandwidth = 0x00000000; /* maximum */ + pcb->iflow.latency = 0xffffffff; /* don't care */ + pcb->iflow.delay_variation = 0xffffffff; /* don't care */ + + bcopy(&pcb->iflow, &pcb->oflow, sizeof(pcb->oflow)); + + pcb->flush_timo = NG_L2CAP_FLUSH_TIMO_DEFAULT; + pcb->link_timo = NG_L2CAP_LINK_TIMO_DEFAULT; + + callout_handle_init(&pcb->timo); + + /* + * XXX Mark PCB mutex as DUPOK to prevent "duplicated lock of + * the same type" message. When accepting new L2CAP connection + * ng_btsocket_l2cap_process_l2ca_con_ind() holds both PCB mutexes + * for "old" (accepting) PCB and "new" (created) PCB. + */ + + mtx_init(&pcb->pcb_mtx, "btsocks_l2cap_pcb_mtx", NULL, + MTX_DEF|MTX_DUPOK); + + /* + * Add the PCB to the list + * + * XXX FIXME VERY IMPORTANT! + * + * This is totally FUBAR. We could get here in two cases: + * + * 1) When user calls socket() + * 2) When we need to accept new incomming connection and call + * sonewconn() + * + * In the first case we must acquire ng_btsocket_l2cap_sockets_mtx. + * In the second case we hold ng_btsocket_l2cap_sockets_mtx already. + * So we now need to distinguish between these cases. From reading + * /sys/kern/uipc_socket.c we can find out that sonewconn() calls + * pru_attach with proto == 0 and td == NULL. For now use this fact + * to figure out if we were called from socket() or from sonewconn(). + */ + + if (td != NULL) + mtx_lock(&ng_btsocket_l2cap_sockets_mtx); + else + mtx_assert(&ng_btsocket_l2cap_sockets_mtx, MA_OWNED); + + /* Set PCB token. Use ng_btsocket_l2cap_sockets_mtx for protection */ + if (++ token == 0) + token ++; + + pcb->token = token; + + LIST_INSERT_HEAD(&ng_btsocket_l2cap_sockets, pcb, next); + + if (td != NULL) + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (0); +} /* ng_btsocket_l2cap_attach */ + +/* + * Bind socket + */ + +int +ng_btsocket_l2cap_bind(struct socket *so, struct sockaddr *nam, + struct thread *td) +{ + ng_btsocket_l2cap_pcb_t *pcb = NULL; + struct sockaddr_l2cap *sa = (struct sockaddr_l2cap *) nam; + int psm, error = 0; + + if (ng_btsocket_l2cap_node == NULL) + return (EINVAL); + + /* Verify address */ + if (sa == NULL) + return (EINVAL); + if (sa->l2cap_family != AF_BLUETOOTH) + return (EAFNOSUPPORT); + if (sa->l2cap_len != sizeof(*sa)) + return (EINVAL); + + psm = le16toh(sa->l2cap_psm); + + /* + * Check if other socket has this address already (look for exact + * match PSM and bdaddr) and assign socket address if it's available. + * + * Note: socket can be bound to ANY PSM (zero) thus allowing several + * channels with the same PSM between the same pair of BD_ADDR'es. + */ + + mtx_lock(&ng_btsocket_l2cap_sockets_mtx); + + LIST_FOREACH(pcb, &ng_btsocket_l2cap_sockets, next) + if (psm != 0 && psm == pcb->psm && + bcmp(&pcb->src, &sa->l2cap_bdaddr, sizeof(bdaddr_t)) == 0) + break; + + if (pcb == NULL) { + /* Set socket address */ + pcb = so2l2cap_pcb(so); + if (pcb != NULL) { + bcopy(&sa->l2cap_bdaddr, &pcb->src, sizeof(pcb->src)); + pcb->psm = psm; + } else + error = EINVAL; + } else + error = EADDRINUSE; + + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + return (error); +} /* ng_btsocket_l2cap_bind */ + +/* + * Connect socket + */ + +int +ng_btsocket_l2cap_connect(struct socket *so, struct sockaddr *nam, + struct thread *td) +{ + ng_btsocket_l2cap_pcb_t *pcb = so2l2cap_pcb(so); + struct sockaddr_l2cap *sa = (struct sockaddr_l2cap *) nam; + ng_btsocket_l2cap_rtentry_t *rt = NULL; + int have_src, error = 0; + + /* Check socket */ + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_node == NULL) + return (EINVAL); + if (pcb->state == NG_BTSOCKET_L2CAP_CONNECTING) + return (EINPROGRESS); + + /* Verify address */ + if (sa == NULL) + return (EINVAL); + if (sa->l2cap_family != AF_BLUETOOTH) + return (EAFNOSUPPORT); + if (sa->l2cap_len != sizeof(*sa)) + return (EINVAL); + if (sa->l2cap_psm == 0 || + bcmp(&sa->l2cap_bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) + return (EDESTADDRREQ); + if (pcb->psm != 0 && pcb->psm != le16toh(sa->l2cap_psm)) + return (EINVAL); + + /* + * Routing. Socket should be bound to some source address. The source + * address can be ANY. Destination address must be set and it must not + * be ANY. If source address is ANY then find first rtentry that has + * src != dst. + */ + + mtx_lock(&ng_btsocket_l2cap_rt_mtx); + mtx_lock(&ng_btsocket_l2cap_sockets_mtx); + mtx_lock(&pcb->pcb_mtx); + + /* Send destination address and PSM */ + bcopy(&sa->l2cap_bdaddr, &pcb->dst, sizeof(pcb->dst)); + pcb->psm = le16toh(sa->l2cap_psm); + + pcb->rt = NULL; + have_src = bcmp(&pcb->src, NG_HCI_BDADDR_ANY, sizeof(pcb->src)); + + LIST_FOREACH(rt, &ng_btsocket_l2cap_rt, next) { + if (rt->hook == NULL || NG_HOOK_NOT_VALID(rt->hook)) + continue; + + /* Match src and dst */ + if (have_src) { + if (bcmp(&pcb->src, &rt->src, sizeof(rt->src)) == 0) + break; + } else { + if (bcmp(&pcb->dst, &rt->src, sizeof(rt->src)) != 0) + break; + } + } + + if (rt != NULL) { + pcb->rt = rt; + + if (!have_src) + bcopy(&rt->src, &pcb->src, sizeof(pcb->src)); + } else + error = EHOSTUNREACH; + + /* + * Send L2CA_Connect request + */ + + if (error == 0) { + error = ng_btsocket_l2cap_send_l2ca_con_req(pcb); + if (error == 0) { + pcb->flags |= NG_BTSOCKET_L2CAP_CLIENT; + pcb->state = NG_BTSOCKET_L2CAP_CONNECTING; + soisconnecting(pcb->so); + + ng_btsocket_l2cap_timeout(pcb); + } + } + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + mtx_unlock(&ng_btsocket_l2cap_rt_mtx); + + return (error); +} /* ng_btsocket_l2cap_connect */ + +/* + * Process ioctl's calls on socket + */ + +int +ng_btsocket_l2cap_control(struct socket *so, u_long cmd, caddr_t data, + struct ifnet *ifp, struct thread *td) +{ + return (EINVAL); +} /* ng_btsocket_l2cap_control */ + +/* + * Process getsockopt/setsockopt system calls + */ + +int +ng_btsocket_l2cap_ctloutput(struct socket *so, struct sockopt *sopt) +{ + ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); + int error = 0; + ng_l2cap_cfg_opt_val_t v; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_node == NULL) + return (EINVAL); + + if (sopt->sopt_level != SOL_L2CAP) + return (0); + + mtx_lock(&pcb->pcb_mtx); + + switch (sopt->sopt_dir) { + case SOPT_GET: + switch (sopt->sopt_name) { + case SO_L2CAP_IMTU: /* get incoming MTU */ + error = sooptcopyout(sopt, &pcb->imtu, + sizeof(pcb->imtu)); + break; + + case SO_L2CAP_OMTU: /* get outgoing (peer incoming) MTU */ + error = sooptcopyout(sopt, &pcb->omtu, + sizeof(pcb->omtu)); + break; + + case SO_L2CAP_IFLOW: /* get incoming flow spec. */ + error = sooptcopyout(sopt, &pcb->iflow, + sizeof(pcb->iflow)); + break; + + case SO_L2CAP_OFLOW: /* get outgoing flow spec. */ + error = sooptcopyout(sopt, &pcb->oflow, + sizeof(pcb->oflow)); + break; + + case SO_L2CAP_FLUSH: /* get flush timeout */ + error = sooptcopyout(sopt, &pcb->flush_timo, + sizeof(pcb->flush_timo)); + break; + + default: + error = ENOPROTOOPT; + break; + } + break; + + case SOPT_SET: + /* + * XXX + * We do not allow to change these parameters while socket is + * connected or we are in the process of creating a connection. + * May be this should indicate re-configuration of the open + * channel? + */ + + if (pcb->state != NG_BTSOCKET_L2CAP_CLOSED) { + error = EACCES; + break; + } + + switch (sopt->sopt_name) { + case SO_L2CAP_IMTU: /* set incoming MTU */ + error = sooptcopyin(sopt, &v, sizeof(v), sizeof(v.mtu)); + if (error == 0) + pcb->imtu = v.mtu; + break; + + case SO_L2CAP_OFLOW: /* set outgoing flow spec. */ + error = sooptcopyin(sopt, &v, sizeof(v),sizeof(v.flow)); + if (error == 0) + bcopy(&v.flow, &pcb->oflow, sizeof(pcb->oflow)); + break; + + case SO_L2CAP_FLUSH: /* set flush timeout */ + error = sooptcopyin(sopt, &v, sizeof(v), + sizeof(v.flush_timo)); + if (error == 0) + pcb->flush_timo = v.flush_timo; + break; + + default: + error = ENOPROTOOPT; + break; + } + break; + + default: + error = EINVAL; + break; + } + + mtx_unlock(&pcb->pcb_mtx); + + return (error); +} /* ng_btsocket_l2cap_ctloutput */ + +/* + * Detach and destroy socket + */ + +void +ng_btsocket_l2cap_detach(struct socket *so) +{ + ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); + + KASSERT(pcb != NULL, ("ng_btsocket_l2cap_detach: pcb == NULL")); + + if (ng_btsocket_l2cap_node == NULL) + return; + + mtx_lock(&ng_btsocket_l2cap_sockets_mtx); + mtx_lock(&pcb->pcb_mtx); + + /* XXX what to do with pending request? */ + if (pcb->flags & NG_BTSOCKET_L2CAP_TIMO) + ng_btsocket_l2cap_untimeout(pcb); + + if (pcb->state != NG_BTSOCKET_L2CAP_CLOSED && + pcb->state != NG_BTSOCKET_L2CAP_DISCONNECTING) + /* Send disconnect request with "zero" token */ + ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); + + pcb->state = NG_BTSOCKET_L2CAP_CLOSED; + + LIST_REMOVE(pcb, next); + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); + + mtx_destroy(&pcb->pcb_mtx); + bzero(pcb, sizeof(*pcb)); + FREE(pcb, M_NETGRAPH_BTSOCKET_L2CAP); + + soisdisconnected(so); + so->so_pcb = NULL; +} /* ng_btsocket_l2cap_detach */ + +/* + * Disconnect socket + */ + +int +ng_btsocket_l2cap_disconnect(struct socket *so) +{ + ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); + int error = 0; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_node == NULL) + return (EINVAL); + + mtx_lock(&pcb->pcb_mtx); + + if (pcb->state == NG_BTSOCKET_L2CAP_DISCONNECTING) { + mtx_unlock(&pcb->pcb_mtx); + return (EINPROGRESS); + } + + if (pcb->state != NG_BTSOCKET_L2CAP_CLOSED) { + /* XXX FIXME what to do with pending request? */ + if (pcb->flags & NG_BTSOCKET_L2CAP_TIMO) + ng_btsocket_l2cap_untimeout(pcb); + + error = ng_btsocket_l2cap_send_l2ca_discon_req(pcb->token, pcb); + if (error == 0) { + pcb->state = NG_BTSOCKET_L2CAP_DISCONNECTING; + soisdisconnecting(so); + + ng_btsocket_l2cap_timeout(pcb); + } + + /* XXX FIXME what to do if error != 0 */ + } + + mtx_unlock(&pcb->pcb_mtx); + + return (error); +} /* ng_btsocket_l2cap_disconnect */ + +/* + * Listen on socket + */ + +int +ng_btsocket_l2cap_listen(struct socket *so, int backlog, struct thread *td) +{ + ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); + int error; + + SOCK_LOCK(so); + error = solisten_proto_check(so); + if (error != 0) + goto out; + if (pcb == NULL) { + error = EINVAL; + goto out; + } + if (ng_btsocket_l2cap_node == NULL) { + error = EINVAL; + goto out; + } + if (pcb->psm == 0) { + error = EADDRNOTAVAIL; + goto out; + } + solisten_proto(so, backlog); +out: + SOCK_UNLOCK(so); + return (error); +} /* ng_btsocket_listen */ + +/* + * Get peer address + */ + +int +ng_btsocket_l2cap_peeraddr(struct socket *so, struct sockaddr **nam) +{ + ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); + struct sockaddr_l2cap sa; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_node == NULL) + return (EINVAL); + + bcopy(&pcb->dst, &sa.l2cap_bdaddr, sizeof(sa.l2cap_bdaddr)); + sa.l2cap_psm = htole16(pcb->psm); + sa.l2cap_len = sizeof(sa); + sa.l2cap_family = AF_BLUETOOTH; + + *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); + + return ((*nam == NULL)? ENOMEM : 0); +} /* ng_btsocket_l2cap_peeraddr */ + +/* + * Send data to socket + */ + +int +ng_btsocket_l2cap_send(struct socket *so, int flags, struct mbuf *m, + struct sockaddr *nam, struct mbuf *control, struct thread *td) +{ + ng_btsocket_l2cap_pcb_t *pcb = so2l2cap_pcb(so); + int error = 0; + + if (ng_btsocket_l2cap_node == NULL) { + error = ENETDOWN; + goto drop; + } + + /* Check socket and input */ + if (pcb == NULL || m == NULL || control != NULL) { + error = EINVAL; + goto drop; + } + + mtx_lock(&pcb->pcb_mtx); + + /* Make sure socket is connected */ + if (pcb->state != NG_BTSOCKET_L2CAP_OPEN) { + mtx_unlock(&pcb->pcb_mtx); + error = ENOTCONN; + goto drop; + } + + /* Check route */ + if (pcb->rt == NULL || + pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) { + mtx_unlock(&pcb->pcb_mtx); + error = ENETDOWN; + goto drop; + } + + /* Check packet size agains outgoing (peer's incoming) MTU) */ + if (m->m_pkthdr.len > pcb->omtu) { + NG_BTSOCKET_L2CAP_ERR( +"%s: Packet too big, len=%d, omtu=%d\n", __func__, m->m_pkthdr.len, pcb->omtu); + + mtx_unlock(&pcb->pcb_mtx); + error = EMSGSIZE; + goto drop; + } + + /* + * First put packet on socket send queue. Then check if we have + * pending timeout. If we do not have timeout then we must send + * packet and schedule timeout. Otherwise do nothing and wait for + * L2CA_WRITE_RSP. + */ + + sbappendrecord(&pcb->so->so_snd, m); + m = NULL; + + if (!(pcb->flags & NG_BTSOCKET_L2CAP_TIMO)) { + error = ng_btsocket_l2cap_send2(pcb); + if (error == 0) + ng_btsocket_l2cap_timeout(pcb); + else + sbdroprecord(&pcb->so->so_snd); /* XXX */ + } + + mtx_unlock(&pcb->pcb_mtx); +drop: + NG_FREE_M(m); /* checks for != NULL */ + NG_FREE_M(control); + + return (error); +} /* ng_btsocket_l2cap_send */ + +/* + * Send first packet in the socket queue to the L2CAP layer + */ + +static int +ng_btsocket_l2cap_send2(ng_btsocket_l2cap_pcb_p pcb) +{ + struct mbuf *m = NULL; + ng_l2cap_l2ca_hdr_t *hdr = NULL; + int error = 0; + + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + if (pcb->so->so_snd.sb_cc == 0) + return (EINVAL); /* XXX */ + + m = m_dup(pcb->so->so_snd.sb_mb, M_DONTWAIT); + if (m == NULL) + return (ENOBUFS); + + /* Create L2CA packet header */ + M_PREPEND(m, sizeof(*hdr), M_DONTWAIT); + if (m != NULL) + if (m->m_len < sizeof(*hdr)) + m = m_pullup(m, sizeof(*hdr)); + + if (m == NULL) { + NG_BTSOCKET_L2CAP_ERR( +"%s: Failed to create L2CA packet header\n", __func__); + + return (ENOBUFS); + } + + hdr = mtod(m, ng_l2cap_l2ca_hdr_t *); + hdr->token = pcb->token; + hdr->length = m->m_pkthdr.len - sizeof(*hdr); + hdr->lcid = pcb->cid; + + NG_BTSOCKET_L2CAP_INFO( +"%s: Sending packet: len=%d, length=%d, lcid=%d, token=%d, state=%d\n", + __func__, m->m_pkthdr.len, hdr->length, hdr->lcid, + hdr->token, pcb->state); + + /* + * If we got here than we have successfuly creates new L2CAP + * data packet and now we can send it to the L2CAP layer + */ + + NG_SEND_DATA_ONLY(error, pcb->rt->hook, m); + + return (error); +} /* ng_btsocket_l2cap_send2 */ + +/* + * Get socket address + */ + +int +ng_btsocket_l2cap_sockaddr(struct socket *so, struct sockaddr **nam) +{ + ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); + struct sockaddr_l2cap sa; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_node == NULL) + return (EINVAL); + + bcopy(&pcb->src, &sa.l2cap_bdaddr, sizeof(sa.l2cap_bdaddr)); + sa.l2cap_psm = htole16(pcb->psm); + sa.l2cap_len = sizeof(sa); + sa.l2cap_family = AF_BLUETOOTH; + + *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); + + return ((*nam == NULL)? ENOMEM : 0); +} /* ng_btsocket_l2cap_sockaddr */ + +/***************************************************************************** + ***************************************************************************** + ** Misc. functions + ***************************************************************************** + *****************************************************************************/ + +/* + * Look for the socket that listens on given PSM and bdaddr. Returns exact or + * close match (if any). Caller must hold ng_btsocket_l2cap_sockets_mtx. + */ + +static ng_btsocket_l2cap_pcb_p +ng_btsocket_l2cap_pcb_by_addr(bdaddr_p bdaddr, int psm) +{ + ng_btsocket_l2cap_pcb_p p = NULL, p1 = NULL; + + mtx_assert(&ng_btsocket_l2cap_sockets_mtx, MA_OWNED); + + LIST_FOREACH(p, &ng_btsocket_l2cap_sockets, next) { + if (p->so == NULL || !(p->so->so_options & SO_ACCEPTCONN) || + p->psm != psm) + continue; + + if (bcmp(&p->src, bdaddr, sizeof(p->src)) == 0) + break; + + if (bcmp(&p->src, NG_HCI_BDADDR_ANY, sizeof(p->src)) == 0) + p1 = p; + } + + return ((p != NULL)? p : p1); +} /* ng_btsocket_l2cap_pcb_by_addr */ + +/* + * Look for the socket that has given token. + * Caller must hold ng_btsocket_l2cap_sockets_mtx. + */ + +static ng_btsocket_l2cap_pcb_p +ng_btsocket_l2cap_pcb_by_token(u_int32_t token) +{ + ng_btsocket_l2cap_pcb_p p = NULL; + + if (token == 0) + return (NULL); + + mtx_assert(&ng_btsocket_l2cap_sockets_mtx, MA_OWNED); + + LIST_FOREACH(p, &ng_btsocket_l2cap_sockets, next) + if (p->token == token) + break; + + return (p); +} /* ng_btsocket_l2cap_pcb_by_token */ + +/* + * Look for the socket that assigned to given source address and channel ID. + * Caller must hold ng_btsocket_l2cap_sockets_mtx + */ + +static ng_btsocket_l2cap_pcb_p +ng_btsocket_l2cap_pcb_by_cid(bdaddr_p src, int cid) +{ + ng_btsocket_l2cap_pcb_p p = NULL; + + mtx_assert(&ng_btsocket_l2cap_sockets_mtx, MA_OWNED); + + LIST_FOREACH(p, &ng_btsocket_l2cap_sockets, next) + if (p->cid == cid && bcmp(src, &p->src, sizeof(p->src)) == 0) + break; + + return (p); +} /* ng_btsocket_l2cap_pcb_by_cid */ + +/* + * Set timeout on socket + */ + +static void +ng_btsocket_l2cap_timeout(ng_btsocket_l2cap_pcb_p pcb) +{ + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + if (!(pcb->flags & NG_BTSOCKET_L2CAP_TIMO)) { + pcb->flags |= NG_BTSOCKET_L2CAP_TIMO; + pcb->timo = timeout(ng_btsocket_l2cap_process_timeout, pcb, + bluetooth_l2cap_ertx_timeout()); + } else + KASSERT(0, +("%s: Duplicated socket timeout?!\n", __func__)); +} /* ng_btsocket_l2cap_timeout */ + +/* + * Unset timeout on socket + */ + +static void +ng_btsocket_l2cap_untimeout(ng_btsocket_l2cap_pcb_p pcb) +{ + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + if (pcb->flags & NG_BTSOCKET_L2CAP_TIMO) { + untimeout(ng_btsocket_l2cap_process_timeout, pcb, pcb->timo); + pcb->flags &= ~NG_BTSOCKET_L2CAP_TIMO; + } else + KASSERT(0, +("%s: No socket timeout?!\n", __func__)); +} /* ng_btsocket_l2cap_untimeout */ + +/* + * Process timeout on socket + */ + +static void +ng_btsocket_l2cap_process_timeout(void *xpcb) +{ + ng_btsocket_l2cap_pcb_p pcb = (ng_btsocket_l2cap_pcb_p) xpcb; + + mtx_lock(&pcb->pcb_mtx); + + pcb->flags &= ~NG_BTSOCKET_L2CAP_TIMO; + pcb->so->so_error = ETIMEDOUT; + + switch (pcb->state) { + case NG_BTSOCKET_L2CAP_CONNECTING: + case NG_BTSOCKET_L2CAP_CONFIGURING: + /* Send disconnect request with "zero" token */ + if (pcb->cid != 0) + ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); + + /* ... and close the socket */ + pcb->state = NG_BTSOCKET_L2CAP_CLOSED; + soisdisconnected(pcb->so); + break; + + case NG_BTSOCKET_L2CAP_OPEN: + /* Send timeout - drop packet and wakeup sender */ + sbdroprecord(&pcb->so->so_snd); + sowwakeup(pcb->so); + break; + + case NG_BTSOCKET_L2CAP_DISCONNECTING: + /* Disconnect timeout - disconnect the socket anyway */ + pcb->state = NG_BTSOCKET_L2CAP_CLOSED; + soisdisconnected(pcb->so); + break; + + default: + NG_BTSOCKET_L2CAP_ERR( +"%s: Invalid socket state=%d\n", __func__, pcb->state); + break; + } + + mtx_unlock(&pcb->pcb_mtx); +} /* ng_btsocket_l2cap_process_timeout */ + +/* + * Translate HCI/L2CAP error code into "errno" code + * XXX Note: Some L2CAP and HCI error codes have the same value, but + * different meaning + */ + +static int +ng_btsocket_l2cap_result2errno(int result) +{ + switch (result) { + case 0x00: /* No error */ + return (0); + + case 0x01: /* Unknown HCI command */ + return (ENODEV); + + case 0x02: /* No connection */ + return (ENOTCONN); + + case 0x03: /* Hardware failure */ + return (EIO); + + case 0x04: /* Page timeout */ + return (EHOSTDOWN); + + case 0x05: /* Authentication failure */ + case 0x06: /* Key missing */ + case 0x18: /* Pairing not allowed */ + case 0x21: /* Role change not allowed */ + case 0x24: /* LMP PSU not allowed */ + case 0x25: /* Encryption mode not acceptable */ + case 0x26: /* Unit key used */ + return (EACCES); + + case 0x07: /* Memory full */ + return (ENOMEM); + + case 0x08: /* Connection timeout */ + case 0x10: /* Host timeout */ + case 0x22: /* LMP response timeout */ + case 0xee: /* HCI timeout */ + case 0xeeee: /* L2CAP timeout */ + return (ETIMEDOUT); + + case 0x09: /* Max number of connections */ + case 0x0a: /* Max number of SCO connections to a unit */ + return (EMLINK); + + case 0x0b: /* ACL connection already exists */ + return (EEXIST); + + case 0x0c: /* Command disallowed */ + return (EBUSY); + + case 0x0d: /* Host rejected due to limited resources */ + case 0x0e: /* Host rejected due to securiity reasons */ + case 0x0f: /* Host rejected due to remote unit is a personal unit */ + case 0x1b: /* SCO offset rejected */ + case 0x1c: /* SCO interval rejected */ + case 0x1d: /* SCO air mode rejected */ + return (ECONNREFUSED); + + case 0x11: /* Unsupported feature or parameter value */ + case 0x19: /* Unknown LMP PDU */ + case 0x1a: /* Unsupported remote feature */ + case 0x20: /* Unsupported LMP parameter value */ + case 0x27: /* QoS is not supported */ + case 0x29: /* Paring with unit key not supported */ + return (EOPNOTSUPP); + + case 0x12: /* Invalid HCI command parameter */ + case 0x1e: /* Invalid LMP parameters */ + return (EINVAL); + + case 0x13: /* Other end terminated connection: User ended connection */ + case 0x14: /* Other end terminated connection: Low resources */ + case 0x15: /* Other end terminated connection: About to power off */ + return (ECONNRESET); + + case 0x16: /* Connection terminated by local host */ + return (ECONNABORTED); + +#if 0 /* XXX not yet */ + case 0x17: /* Repeated attempts */ + case 0x1f: /* Unspecified error */ + case 0x23: /* LMP error transaction collision */ + case 0x28: /* Instant passed */ +#endif + } + + return (ENOSYS); +} /* ng_btsocket_l2cap_result2errno */ + diff --git a/sys/netgraph7/bluetooth/socket/ng_btsocket_l2cap_raw.c b/sys/netgraph7/bluetooth/socket/ng_btsocket_l2cap_raw.c new file mode 100644 index 0000000000..06d6f91ee2 --- /dev/null +++ b/sys/netgraph7/bluetooth/socket/ng_btsocket_l2cap_raw.c @@ -0,0 +1,1306 @@ +/* + * ng_btsocket_l2cap_raw.c + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_btsocket_l2cap_raw.c,v 1.12 2003/09/14 23:29:06 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/socket/ng_btsocket_l2cap_raw.c,v 1.20 2006/11/06 13:42:04 rwatson Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* MALLOC define */ +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_BTSOCKET_L2CAP_RAW, "netgraph_btsocks_l2cap_raw", + "Netgraph Bluetooth raw L2CAP sockets"); +#else +#define M_NETGRAPH_BTSOCKET_L2CAP_RAW M_NETGRAPH +#endif /* NG_SEPARATE_MALLOC */ + +/* Netgraph node methods */ +static ng_constructor_t ng_btsocket_l2cap_raw_node_constructor; +static ng_rcvmsg_t ng_btsocket_l2cap_raw_node_rcvmsg; +static ng_shutdown_t ng_btsocket_l2cap_raw_node_shutdown; +static ng_newhook_t ng_btsocket_l2cap_raw_node_newhook; +static ng_connect_t ng_btsocket_l2cap_raw_node_connect; +static ng_rcvdata_t ng_btsocket_l2cap_raw_node_rcvdata; +static ng_disconnect_t ng_btsocket_l2cap_raw_node_disconnect; + +static void ng_btsocket_l2cap_raw_input (void *, int); +static void ng_btsocket_l2cap_raw_rtclean (void *, int); +static void ng_btsocket_l2cap_raw_get_token (u_int32_t *); + +static int ng_btsocket_l2cap_raw_send_ngmsg + (hook_p, int, void *, int); +static int ng_btsocket_l2cap_raw_send_sync_ngmsg + (ng_btsocket_l2cap_raw_pcb_p, int, void *, int); + +#define ng_btsocket_l2cap_raw_wakeup_input_task() \ + taskqueue_enqueue(taskqueue_swi, &ng_btsocket_l2cap_raw_queue_task) + +#define ng_btsocket_l2cap_raw_wakeup_route_task() \ + taskqueue_enqueue(taskqueue_swi, &ng_btsocket_l2cap_raw_rt_task) + +/* Netgraph type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_BTSOCKET_L2CAP_RAW_NODE_TYPE, + .constructor = ng_btsocket_l2cap_raw_node_constructor, + .rcvmsg = ng_btsocket_l2cap_raw_node_rcvmsg, + .shutdown = ng_btsocket_l2cap_raw_node_shutdown, + .newhook = ng_btsocket_l2cap_raw_node_newhook, + .connect = ng_btsocket_l2cap_raw_node_connect, + .rcvdata = ng_btsocket_l2cap_raw_node_rcvdata, + .disconnect = ng_btsocket_l2cap_raw_node_disconnect, +}; + +/* Globals */ +extern int ifqmaxlen; +static u_int32_t ng_btsocket_l2cap_raw_debug_level; +static u_int32_t ng_btsocket_l2cap_raw_ioctl_timeout; +static node_p ng_btsocket_l2cap_raw_node; +static struct ng_bt_itemq ng_btsocket_l2cap_raw_queue; +static struct mtx ng_btsocket_l2cap_raw_queue_mtx; +static struct task ng_btsocket_l2cap_raw_queue_task; +static LIST_HEAD(, ng_btsocket_l2cap_raw_pcb) ng_btsocket_l2cap_raw_sockets; +static struct mtx ng_btsocket_l2cap_raw_sockets_mtx; +static u_int32_t ng_btsocket_l2cap_raw_token; +static struct mtx ng_btsocket_l2cap_raw_token_mtx; +static LIST_HEAD(, ng_btsocket_l2cap_rtentry) ng_btsocket_l2cap_raw_rt; +static struct mtx ng_btsocket_l2cap_raw_rt_mtx; +static struct task ng_btsocket_l2cap_raw_rt_task; + +/* Sysctl tree */ +SYSCTL_DECL(_net_bluetooth_l2cap_sockets); +SYSCTL_NODE(_net_bluetooth_l2cap_sockets, OID_AUTO, raw, CTLFLAG_RW, + 0, "Bluetooth raw L2CAP sockets family"); +SYSCTL_INT(_net_bluetooth_l2cap_sockets_raw, OID_AUTO, debug_level, + CTLFLAG_RW, + &ng_btsocket_l2cap_raw_debug_level, NG_BTSOCKET_WARN_LEVEL, + "Bluetooth raw L2CAP sockets debug level"); +SYSCTL_INT(_net_bluetooth_l2cap_sockets_raw, OID_AUTO, ioctl_timeout, + CTLFLAG_RW, + &ng_btsocket_l2cap_raw_ioctl_timeout, 5, + "Bluetooth raw L2CAP sockets ioctl timeout"); +SYSCTL_INT(_net_bluetooth_l2cap_sockets_raw, OID_AUTO, queue_len, + CTLFLAG_RD, + &ng_btsocket_l2cap_raw_queue.len, 0, + "Bluetooth raw L2CAP sockets input queue length"); +SYSCTL_INT(_net_bluetooth_l2cap_sockets_raw, OID_AUTO, queue_maxlen, + CTLFLAG_RD, + &ng_btsocket_l2cap_raw_queue.maxlen, 0, + "Bluetooth raw L2CAP sockets input queue max. length"); +SYSCTL_INT(_net_bluetooth_l2cap_sockets_raw, OID_AUTO, queue_drops, + CTLFLAG_RD, + &ng_btsocket_l2cap_raw_queue.drops, 0, + "Bluetooth raw L2CAP sockets input queue drops"); + +/* Debug */ +#define NG_BTSOCKET_L2CAP_RAW_INFO \ + if (ng_btsocket_l2cap_raw_debug_level >= NG_BTSOCKET_INFO_LEVEL) \ + printf + +#define NG_BTSOCKET_L2CAP_RAW_WARN \ + if (ng_btsocket_l2cap_raw_debug_level >= NG_BTSOCKET_WARN_LEVEL) \ + printf + +#define NG_BTSOCKET_L2CAP_RAW_ERR \ + if (ng_btsocket_l2cap_raw_debug_level >= NG_BTSOCKET_ERR_LEVEL) \ + printf + +#define NG_BTSOCKET_L2CAP_RAW_ALERT \ + if (ng_btsocket_l2cap_raw_debug_level >= NG_BTSOCKET_ALERT_LEVEL) \ + printf + +/***************************************************************************** + ***************************************************************************** + ** Netgraph node interface + ***************************************************************************** + *****************************************************************************/ + +/* + * Netgraph node constructor. Do not allow to create node of this type. + */ + +static int +ng_btsocket_l2cap_raw_node_constructor(node_p node) +{ + return (EINVAL); +} /* ng_btsocket_l2cap_raw_node_constructor */ + +/* + * Do local shutdown processing. Let old node go and create new fresh one. + */ + +static int +ng_btsocket_l2cap_raw_node_shutdown(node_p node) +{ + int error = 0; + + NG_NODE_UNREF(node); + + /* Create new node */ + error = ng_make_node_common(&typestruct, &ng_btsocket_l2cap_raw_node); + if (error != 0) { + NG_BTSOCKET_L2CAP_RAW_ALERT( +"%s: Could not create Netgraph node, error=%d\n", __func__, error); + + ng_btsocket_l2cap_raw_node = NULL; + + return (error); + } + + error = ng_name_node(ng_btsocket_l2cap_raw_node, + NG_BTSOCKET_L2CAP_RAW_NODE_TYPE); + if (error != 0) { + NG_BTSOCKET_L2CAP_RAW_ALERT( +"%s: Could not name Netgraph node, error=%d\n", __func__, error); + + NG_NODE_UNREF(ng_btsocket_l2cap_raw_node); + ng_btsocket_l2cap_raw_node = NULL; + + return (error); + } + + return (0); +} /* ng_btsocket_l2cap_raw_node_shutdown */ + +/* + * We allow any hook to be connected to the node. + */ + +static int +ng_btsocket_l2cap_raw_node_newhook(node_p node, hook_p hook, char const *name) +{ + return (0); +} /* ng_btsocket_l2cap_raw_node_newhook */ + +/* + * Just say "YEP, that's OK by me!" + */ + +static int +ng_btsocket_l2cap_raw_node_connect(hook_p hook) +{ + NG_HOOK_SET_PRIVATE(hook, NULL); + NG_HOOK_REF(hook); /* Keep extra reference to the hook */ + + return (0); +} /* ng_btsocket_l2cap_raw_node_connect */ + +/* + * Hook disconnection. Schedule route cleanup task + */ + +static int +ng_btsocket_l2cap_raw_node_disconnect(hook_p hook) +{ + /* + * If hook has private information than we must have this hook in + * the routing table and must schedule cleaning for the routing table. + * Otherwise hook was connected but we never got "hook_info" message, + * so we have never added this hook to the routing table and it save + * to just delete it. + */ + + if (NG_HOOK_PRIVATE(hook) != NULL) + return (ng_btsocket_l2cap_raw_wakeup_route_task()); + + NG_HOOK_UNREF(hook); /* Remove extra reference */ + + return (0); +} /* ng_btsocket_l2cap_raw_node_disconnect */ + +/* + * Process incoming messages + */ + +static int +ng_btsocket_l2cap_raw_node_rcvmsg(node_p node, item_p item, hook_p hook) +{ + struct ng_mesg *msg = NGI_MSG(item); /* item still has message */ + int error = 0; + + if (msg != NULL && msg->header.typecookie == NGM_L2CAP_COOKIE) { + + /* + * NGM_L2CAP_NODE_HOOK_INFO is special message initiated by + * L2CAP layer. Ignore all other messages if they are not + * replies or token is zero + */ + + if (msg->header.cmd != NGM_L2CAP_NODE_HOOK_INFO) { + if (msg->header.token == 0 || + !(msg->header.flags & NGF_RESP)) { + NG_FREE_ITEM(item); + return (0); + } + } + + mtx_lock(&ng_btsocket_l2cap_raw_queue_mtx); + if (NG_BT_ITEMQ_FULL(&ng_btsocket_l2cap_raw_queue)) { + NG_BTSOCKET_L2CAP_RAW_ERR( +"%s: Input queue is full\n", __func__); + + NG_BT_ITEMQ_DROP(&ng_btsocket_l2cap_raw_queue); + NG_FREE_ITEM(item); + error = ENOBUFS; + } else { + if (hook != NULL) { + NG_HOOK_REF(hook); + NGI_SET_HOOK(item, hook); + } + + NG_BT_ITEMQ_ENQUEUE(&ng_btsocket_l2cap_raw_queue, item); + error = ng_btsocket_l2cap_raw_wakeup_input_task(); + } + mtx_unlock(&ng_btsocket_l2cap_raw_queue_mtx); + } else { + NG_FREE_ITEM(item); + error = EINVAL; + } + + return (error); +} /* ng_btsocket_l2cap_raw_node_rcvmsg */ + +/* + * Receive data on a hook + */ + +static int +ng_btsocket_l2cap_raw_node_rcvdata(hook_p hook, item_p item) +{ + NG_FREE_ITEM(item); + + return (EINVAL); +} /* ng_btsocket_l2cap_raw_node_rcvdata */ + +/***************************************************************************** + ***************************************************************************** + ** Socket interface + ***************************************************************************** + *****************************************************************************/ + +/* + * L2CAP sockets input routine + */ + +static void +ng_btsocket_l2cap_raw_input(void *context, int pending) +{ + item_p item = NULL; + hook_p hook = NULL; + struct ng_mesg *msg = NULL; + + for (;;) { + mtx_lock(&ng_btsocket_l2cap_raw_queue_mtx); + NG_BT_ITEMQ_DEQUEUE(&ng_btsocket_l2cap_raw_queue, item); + mtx_unlock(&ng_btsocket_l2cap_raw_queue_mtx); + + if (item == NULL) + break; + + KASSERT((item->el_flags & NGQF_TYPE) == NGQF_MESG, +("%s: invalid item type=%ld\n", __func__, (item->el_flags & NGQF_TYPE))); + + NGI_GET_MSG(item, msg); + NGI_GET_HOOK(item, hook); + NG_FREE_ITEM(item); + + switch (msg->header.cmd) { + case NGM_L2CAP_NODE_HOOK_INFO: { + ng_btsocket_l2cap_rtentry_t *rt = NULL; + + if (hook == NULL || NG_HOOK_NOT_VALID(hook) || + msg->header.arglen != sizeof(bdaddr_t)) + break; + + if (bcmp(msg->data, NG_HCI_BDADDR_ANY, + sizeof(bdaddr_t)) == 0) + break; + + rt = (ng_btsocket_l2cap_rtentry_t *) + NG_HOOK_PRIVATE(hook); + if (rt == NULL) { + MALLOC(rt, ng_btsocket_l2cap_rtentry_p, + sizeof(*rt), + M_NETGRAPH_BTSOCKET_L2CAP_RAW, + M_NOWAIT|M_ZERO); + if (rt == NULL) + break; + + NG_HOOK_SET_PRIVATE(hook, rt); + + mtx_lock(&ng_btsocket_l2cap_raw_rt_mtx); + + LIST_INSERT_HEAD(&ng_btsocket_l2cap_raw_rt, + rt, next); + } else + mtx_lock(&ng_btsocket_l2cap_raw_rt_mtx); + + bcopy(msg->data, &rt->src, sizeof(rt->src)); + rt->hook = hook; + + NG_BTSOCKET_L2CAP_RAW_INFO( +"%s: Updating hook \"%s\", src bdaddr=%x:%x:%x:%x:%x:%x\n", + __func__, NG_HOOK_NAME(hook), + rt->src.b[5], rt->src.b[4], rt->src.b[3], + rt->src.b[2], rt->src.b[1], rt->src.b[0]); + + mtx_unlock(&ng_btsocket_l2cap_raw_rt_mtx); + } break; + + case NGM_L2CAP_NODE_GET_FLAGS: + case NGM_L2CAP_NODE_GET_DEBUG: + case NGM_L2CAP_NODE_GET_CON_LIST: + case NGM_L2CAP_NODE_GET_CHAN_LIST: + case NGM_L2CAP_NODE_GET_AUTO_DISCON_TIMO: + case NGM_L2CAP_L2CA_PING: + case NGM_L2CAP_L2CA_GET_INFO: { + ng_btsocket_l2cap_raw_pcb_p pcb = NULL; + + mtx_lock(&ng_btsocket_l2cap_raw_sockets_mtx); + + LIST_FOREACH(pcb,&ng_btsocket_l2cap_raw_sockets,next) { + mtx_lock(&pcb->pcb_mtx); + + if (pcb->token == msg->header.token) { + pcb->msg = msg; + msg = NULL; + wakeup(&pcb->msg); + mtx_unlock(&pcb->pcb_mtx); + break; + } + + mtx_unlock(&pcb->pcb_mtx); + } + + mtx_unlock(&ng_btsocket_l2cap_raw_sockets_mtx); + } break; + + default: + NG_BTSOCKET_L2CAP_RAW_WARN( +"%s: Unknown message, cmd=%d\n", __func__, msg->header.cmd); + break; + } + + if (hook != NULL) + NG_HOOK_UNREF(hook); /* remove extra reference */ + + NG_FREE_MSG(msg); /* Checks for msg != NULL */ + } +} /* ng_btsocket_l2cap_raw_input */ + +/* + * Route cleanup task. Gets scheduled when hook is disconnected. Here we + * will find all sockets that use "invalid" hook and disconnect them. + */ + +static void +ng_btsocket_l2cap_raw_rtclean(void *context, int pending) +{ + ng_btsocket_l2cap_raw_pcb_p pcb = NULL; + ng_btsocket_l2cap_rtentry_p rt = NULL; + + /* + * First disconnect all sockets that use "invalid" hook + */ + + mtx_lock(&ng_btsocket_l2cap_raw_sockets_mtx); + + LIST_FOREACH(pcb, &ng_btsocket_l2cap_raw_sockets, next) { + mtx_lock(&pcb->pcb_mtx); + + if (pcb->rt != NULL && + pcb->rt->hook != NULL && NG_HOOK_NOT_VALID(pcb->rt->hook)) { + if (pcb->so != NULL && + pcb->so->so_state & SS_ISCONNECTED) + soisdisconnected(pcb->so); + + pcb->rt = NULL; + } + + mtx_unlock(&pcb->pcb_mtx); + } + + mtx_unlock(&ng_btsocket_l2cap_raw_sockets_mtx); + + /* + * Now cleanup routing table + */ + + mtx_lock(&ng_btsocket_l2cap_raw_rt_mtx); + + for (rt = LIST_FIRST(&ng_btsocket_l2cap_raw_rt); rt != NULL; ) { + ng_btsocket_l2cap_rtentry_p rt_next = LIST_NEXT(rt, next); + + if (rt->hook != NULL && NG_HOOK_NOT_VALID(rt->hook)) { + LIST_REMOVE(rt, next); + + NG_HOOK_SET_PRIVATE(rt->hook, NULL); + NG_HOOK_UNREF(rt->hook); /* Remove extra reference */ + + bzero(rt, sizeof(*rt)); + FREE(rt, M_NETGRAPH_BTSOCKET_L2CAP_RAW); + } + + rt = rt_next; + } + + mtx_unlock(&ng_btsocket_l2cap_raw_rt_mtx); +} /* ng_btsocket_l2cap_raw_rtclean */ + +/* + * Initialize everything + */ + +void +ng_btsocket_l2cap_raw_init(void) +{ + int error = 0; + + ng_btsocket_l2cap_raw_node = NULL; + ng_btsocket_l2cap_raw_debug_level = NG_BTSOCKET_WARN_LEVEL; + ng_btsocket_l2cap_raw_ioctl_timeout = 5; + + /* Register Netgraph node type */ + error = ng_newtype(&typestruct); + if (error != 0) { + NG_BTSOCKET_L2CAP_RAW_ALERT( +"%s: Could not register Netgraph node type, error=%d\n", __func__, error); + + return; + } + + /* Create Netgrapg node */ + error = ng_make_node_common(&typestruct, &ng_btsocket_l2cap_raw_node); + if (error != 0) { + NG_BTSOCKET_L2CAP_RAW_ALERT( +"%s: Could not create Netgraph node, error=%d\n", __func__, error); + + ng_btsocket_l2cap_raw_node = NULL; + + return; + } + + error = ng_name_node(ng_btsocket_l2cap_raw_node, + NG_BTSOCKET_L2CAP_RAW_NODE_TYPE); + if (error != 0) { + NG_BTSOCKET_L2CAP_RAW_ALERT( +"%s: Could not name Netgraph node, error=%d\n", __func__, error); + + NG_NODE_UNREF(ng_btsocket_l2cap_raw_node); + ng_btsocket_l2cap_raw_node = NULL; + + return; + } + + /* Create input queue */ + NG_BT_ITEMQ_INIT(&ng_btsocket_l2cap_raw_queue, ifqmaxlen); + mtx_init(&ng_btsocket_l2cap_raw_queue_mtx, + "btsocks_l2cap_raw_queue_mtx", NULL, MTX_DEF); + TASK_INIT(&ng_btsocket_l2cap_raw_queue_task, 0, + ng_btsocket_l2cap_raw_input, NULL); + + /* Create list of sockets */ + LIST_INIT(&ng_btsocket_l2cap_raw_sockets); + mtx_init(&ng_btsocket_l2cap_raw_sockets_mtx, + "btsocks_l2cap_raw_sockets_mtx", NULL, MTX_DEF); + + /* Tokens */ + ng_btsocket_l2cap_raw_token = 0; + mtx_init(&ng_btsocket_l2cap_raw_token_mtx, + "btsocks_l2cap_raw_token_mtx", NULL, MTX_DEF); + + /* Routing table */ + LIST_INIT(&ng_btsocket_l2cap_raw_rt); + mtx_init(&ng_btsocket_l2cap_raw_rt_mtx, + "btsocks_l2cap_raw_rt_mtx", NULL, MTX_DEF); + TASK_INIT(&ng_btsocket_l2cap_raw_rt_task, 0, + ng_btsocket_l2cap_raw_rtclean, NULL); +} /* ng_btsocket_l2cap_raw_init */ + +/* + * Abort connection on socket + */ + +void +ng_btsocket_l2cap_raw_abort(struct socket *so) +{ + + (void)ng_btsocket_l2cap_raw_disconnect(so); +} /* ng_btsocket_l2cap_raw_abort */ + +void +ng_btsocket_l2cap_raw_close(struct socket *so) +{ + + (void)ng_btsocket_l2cap_raw_disconnect(so); +} /* ng_btsocket_l2cap_raw_close */ + +/* + * Create and attach new socket + */ + +int +ng_btsocket_l2cap_raw_attach(struct socket *so, int proto, struct thread *td) +{ + ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); + int error; + + if (pcb != NULL) + return (EISCONN); + + if (ng_btsocket_l2cap_raw_node == NULL) + return (EPROTONOSUPPORT); + if (so->so_type != SOCK_RAW) + return (ESOCKTNOSUPPORT); + + /* Reserve send and receive space if it is not reserved yet */ + error = soreserve(so, NG_BTSOCKET_L2CAP_RAW_SENDSPACE, + NG_BTSOCKET_L2CAP_RAW_RECVSPACE); + if (error != 0) + return (error); + + /* Allocate the PCB */ + MALLOC(pcb, ng_btsocket_l2cap_raw_pcb_p, sizeof(*pcb), + M_NETGRAPH_BTSOCKET_L2CAP_RAW, M_NOWAIT|M_ZERO); + if (pcb == NULL) + return (ENOMEM); + + /* Link the PCB and the socket */ + so->so_pcb = (caddr_t) pcb; + pcb->so = so; + + if (priv_check(td, PRIV_NETBLUETOOTH_RAW) == 0) + pcb->flags |= NG_BTSOCKET_L2CAP_RAW_PRIVILEGED; + + mtx_init(&pcb->pcb_mtx, "btsocks_l2cap_raw_pcb_mtx", NULL, MTX_DEF); + + /* Add the PCB to the list */ + mtx_lock(&ng_btsocket_l2cap_raw_sockets_mtx); + LIST_INSERT_HEAD(&ng_btsocket_l2cap_raw_sockets, pcb, next); + mtx_unlock(&ng_btsocket_l2cap_raw_sockets_mtx); + + return (0); +} /* ng_btsocket_l2cap_raw_attach */ + +/* + * Bind socket + */ + +int +ng_btsocket_l2cap_raw_bind(struct socket *so, struct sockaddr *nam, + struct thread *td) +{ + ng_btsocket_l2cap_raw_pcb_t *pcb = so2l2cap_raw_pcb(so); + struct sockaddr_l2cap *sa = (struct sockaddr_l2cap *) nam; + ng_btsocket_l2cap_rtentry_t *rt = NULL; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_raw_node == NULL) + return (EINVAL); + + if (sa == NULL) + return (EINVAL); + if (sa->l2cap_family != AF_BLUETOOTH) + return (EAFNOSUPPORT); + if (sa->l2cap_len != sizeof(*sa)) + return (EINVAL); + + if (bcmp(&sa->l2cap_bdaddr, NG_HCI_BDADDR_ANY, + sizeof(sa->l2cap_bdaddr)) != 0) { + mtx_lock(&ng_btsocket_l2cap_raw_rt_mtx); + + LIST_FOREACH(rt, &ng_btsocket_l2cap_raw_rt, next) { + if (rt->hook == NULL || NG_HOOK_NOT_VALID(rt->hook)) + continue; + + if (bcmp(&sa->l2cap_bdaddr, &rt->src, + sizeof(rt->src)) == 0) + break; + } + + mtx_unlock(&ng_btsocket_l2cap_raw_rt_mtx); + + if (rt == NULL) + return (ENETDOWN); + } else + rt = NULL; + + mtx_lock(&pcb->pcb_mtx); + bcopy(&sa->l2cap_bdaddr, &pcb->src, sizeof(pcb->src)); + pcb->rt = rt; + mtx_unlock(&pcb->pcb_mtx); + + return (0); +} /* ng_btsocket_l2cap_raw_bind */ + +/* + * Connect socket + */ + +int +ng_btsocket_l2cap_raw_connect(struct socket *so, struct sockaddr *nam, + struct thread *td) +{ + ng_btsocket_l2cap_raw_pcb_t *pcb = so2l2cap_raw_pcb(so); + struct sockaddr_l2cap *sa = (struct sockaddr_l2cap *) nam; + ng_btsocket_l2cap_rtentry_t *rt = NULL; + int error; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_raw_node == NULL) + return (EINVAL); + + if (sa == NULL) + return (EINVAL); + if (sa->l2cap_family != AF_BLUETOOTH) + return (EAFNOSUPPORT); + if (sa->l2cap_len != sizeof(*sa)) + return (EINVAL); + if (bcmp(&sa->l2cap_bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) + return (EINVAL); + + mtx_lock(&pcb->pcb_mtx); + + bcopy(&sa->l2cap_bdaddr, &pcb->dst, sizeof(pcb->dst)); + + if (bcmp(&pcb->src, &pcb->dst, sizeof(pcb->src)) == 0) { + mtx_unlock(&pcb->pcb_mtx); + + return (EADDRNOTAVAIL); + } + + /* + * If there is route already - use it + */ + + if (pcb->rt != NULL) { + soisconnected(so); + mtx_unlock(&pcb->pcb_mtx); + + return (0); + } + + /* + * Find the first hook that does not match specified destination address + */ + + mtx_lock(&ng_btsocket_l2cap_raw_rt_mtx); + + LIST_FOREACH(rt, &ng_btsocket_l2cap_raw_rt, next) { + if (rt->hook == NULL || NG_HOOK_NOT_VALID(rt->hook)) + continue; + + if (bcmp(&pcb->dst, &rt->src, sizeof(rt->src)) != 0) + break; + } + + if (rt != NULL) { + soisconnected(so); + + pcb->rt = rt; + bcopy(&rt->src, &pcb->src, sizeof(pcb->src)); + + error = 0; + } else + error = ENETDOWN; + + mtx_unlock(&ng_btsocket_l2cap_raw_rt_mtx); + mtx_unlock(&pcb->pcb_mtx); + + return (error); +} /* ng_btsocket_l2cap_raw_connect */ + +/* + * Process ioctl's calls on socket + */ + +int +ng_btsocket_l2cap_raw_control(struct socket *so, u_long cmd, caddr_t data, + struct ifnet *ifp, struct thread *td) +{ + ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); + struct ng_mesg *msg = NULL; + int error = 0; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_raw_node == NULL) + return (EINVAL); + + mtx_lock(&pcb->pcb_mtx); + + /* Check if we route info */ + if (pcb->rt == NULL) { + mtx_unlock(&pcb->pcb_mtx); + return (EHOSTUNREACH); + } + + /* Check if we have pending ioctl() */ + if (pcb->token != 0) { + mtx_unlock(&pcb->pcb_mtx); + return (EBUSY); + } + + switch (cmd) { + case SIOC_L2CAP_NODE_GET_FLAGS: { + struct ng_btsocket_l2cap_raw_node_flags *p = + (struct ng_btsocket_l2cap_raw_node_flags *) data; + + error = ng_btsocket_l2cap_raw_send_sync_ngmsg(pcb, + NGM_L2CAP_NODE_GET_FLAGS, + &p->flags, sizeof(p->flags)); + } break; + + case SIOC_L2CAP_NODE_GET_DEBUG: { + struct ng_btsocket_l2cap_raw_node_debug *p = + (struct ng_btsocket_l2cap_raw_node_debug *) data; + + error = ng_btsocket_l2cap_raw_send_sync_ngmsg(pcb, + NGM_L2CAP_NODE_GET_DEBUG, + &p->debug, sizeof(p->debug)); + } break; + + case SIOC_L2CAP_NODE_SET_DEBUG: { + struct ng_btsocket_l2cap_raw_node_debug *p = + (struct ng_btsocket_l2cap_raw_node_debug *) data; + + if (pcb->flags & NG_BTSOCKET_L2CAP_RAW_PRIVILEGED) + error = ng_btsocket_l2cap_raw_send_ngmsg(pcb->rt->hook, + NGM_L2CAP_NODE_SET_DEBUG, + &p->debug, sizeof(p->debug)); + else + error = EPERM; + } break; + + case SIOC_L2CAP_NODE_GET_CON_LIST: { + struct ng_btsocket_l2cap_raw_con_list *p = + (struct ng_btsocket_l2cap_raw_con_list *) data; + ng_l2cap_node_con_list_ep *p1 = NULL; + ng_l2cap_node_con_ep *p2 = NULL; + + if (p->num_connections == 0 || + p->num_connections > NG_L2CAP_MAX_CON_NUM || + p->connections == NULL) { + error = EINVAL; + break; + } + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_NODE_GET_CON_LIST, + 0, M_NOWAIT); + if (msg == NULL) { + error = ENOMEM; + break; + } + ng_btsocket_l2cap_raw_get_token(&msg->header.token); + pcb->token = msg->header.token; + pcb->msg = NULL; + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, + pcb->rt->hook, 0); + if (error != 0) { + pcb->token = 0; + break; + } + + error = msleep(&pcb->msg, &pcb->pcb_mtx, PZERO|PCATCH, "l2ctl", + ng_btsocket_l2cap_raw_ioctl_timeout * hz); + pcb->token = 0; + + if (error != 0) + break; + + if (pcb->msg != NULL && + pcb->msg->header.cmd == NGM_L2CAP_NODE_GET_CON_LIST) { + /* Return data back to user space */ + p1 = (ng_l2cap_node_con_list_ep *)(pcb->msg->data); + p2 = (ng_l2cap_node_con_ep *)(p1 + 1); + + p->num_connections = min(p->num_connections, + p1->num_connections); + if (p->num_connections > 0) + error = copyout((caddr_t) p2, + (caddr_t) p->connections, + p->num_connections * sizeof(*p2)); + } else + error = EINVAL; + + NG_FREE_MSG(pcb->msg); /* checks for != NULL */ + } break; + + case SIOC_L2CAP_NODE_GET_CHAN_LIST: { + struct ng_btsocket_l2cap_raw_chan_list *p = + (struct ng_btsocket_l2cap_raw_chan_list *) data; + ng_l2cap_node_chan_list_ep *p1 = NULL; + ng_l2cap_node_chan_ep *p2 = NULL; + + if (p->num_channels == 0 || + p->num_channels > NG_L2CAP_MAX_CHAN_NUM || + p->channels == NULL) { + error = EINVAL; + break; + } + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, + NGM_L2CAP_NODE_GET_CHAN_LIST, 0, M_NOWAIT); + if (msg == NULL) { + error = ENOMEM; + break; + } + ng_btsocket_l2cap_raw_get_token(&msg->header.token); + pcb->token = msg->header.token; + pcb->msg = NULL; + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, + pcb->rt->hook, 0); + if (error != 0) { + pcb->token = 0; + break; + } + + error = msleep(&pcb->msg, &pcb->pcb_mtx, PZERO|PCATCH, "l2ctl", + ng_btsocket_l2cap_raw_ioctl_timeout * hz); + pcb->token = 0; + + if (error != 0) + break; + + if (pcb->msg != NULL && + pcb->msg->header.cmd == NGM_L2CAP_NODE_GET_CHAN_LIST) { + /* Return data back to user space */ + p1 = (ng_l2cap_node_chan_list_ep *)(pcb->msg->data); + p2 = (ng_l2cap_node_chan_ep *)(p1 + 1); + + p->num_channels = min(p->num_channels, + p1->num_channels); + if (p->num_channels > 0) + error = copyout((caddr_t) p2, + (caddr_t) p->channels, + p->num_channels * sizeof(*p2)); + } else + error = EINVAL; + + NG_FREE_MSG(pcb->msg); /* checks for != NULL */ + } break; + + case SIOC_L2CAP_L2CA_PING: { + struct ng_btsocket_l2cap_raw_ping *p = + (struct ng_btsocket_l2cap_raw_ping *) data; + ng_l2cap_l2ca_ping_ip *ip = NULL; + ng_l2cap_l2ca_ping_op *op = NULL; + + if (!(pcb->flags & NG_BTSOCKET_L2CAP_RAW_PRIVILEGED)) { + error = EPERM; + break; + } + + if ((p->echo_size != 0 && p->echo_data == NULL) || + p->echo_size > NG_L2CAP_MAX_ECHO_SIZE) { + error = EINVAL; + break; + } + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, + NGM_L2CAP_L2CA_PING, sizeof(*ip) + p->echo_size, + M_NOWAIT); + if (msg == NULL) { + error = ENOMEM; + break; + } + ng_btsocket_l2cap_raw_get_token(&msg->header.token); + pcb->token = msg->header.token; + pcb->msg = NULL; + + ip = (ng_l2cap_l2ca_ping_ip *)(msg->data); + bcopy(&pcb->dst, &ip->bdaddr, sizeof(ip->bdaddr)); + ip->echo_size = p->echo_size; + + if (ip->echo_size > 0) { + error = copyin(p->echo_data, ip + 1, p->echo_size); + if (error != 0) { + NG_FREE_MSG(msg); + pcb->token = 0; + break; + } + } + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, + pcb->rt->hook, 0); + if (error != 0) { + pcb->token = 0; + break; + } + + error = msleep(&pcb->msg, &pcb->pcb_mtx, PZERO|PCATCH, "l2ctl", + bluetooth_l2cap_rtx_timeout()); + pcb->token = 0; + + if (error != 0) + break; + + if (pcb->msg != NULL && + pcb->msg->header.cmd == NGM_L2CAP_L2CA_PING) { + /* Return data back to the user space */ + op = (ng_l2cap_l2ca_ping_op *)(pcb->msg->data); + p->result = op->result; + p->echo_size = min(p->echo_size, op->echo_size); + + if (p->echo_size > 0) + error = copyout(op + 1, p->echo_data, + p->echo_size); + } else + error = EINVAL; + + NG_FREE_MSG(pcb->msg); /* checks for != NULL */ + } break; + + case SIOC_L2CAP_L2CA_GET_INFO: { + struct ng_btsocket_l2cap_raw_get_info *p = + (struct ng_btsocket_l2cap_raw_get_info *) data; + ng_l2cap_l2ca_get_info_ip *ip = NULL; + ng_l2cap_l2ca_get_info_op *op = NULL; + + if (!(pcb->flags & NG_BTSOCKET_L2CAP_RAW_PRIVILEGED)) { + error = EPERM; + break; + } + + if (p->info_size != 0 && p->info_data == NULL) { + error = EINVAL; + break; + } + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, + NGM_L2CAP_L2CA_GET_INFO, sizeof(*ip) + p->info_size, + M_NOWAIT); + if (msg == NULL) { + error = ENOMEM; + break; + } + ng_btsocket_l2cap_raw_get_token(&msg->header.token); + pcb->token = msg->header.token; + pcb->msg = NULL; + + ip = (ng_l2cap_l2ca_get_info_ip *)(msg->data); + bcopy(&pcb->dst, &ip->bdaddr, sizeof(ip->bdaddr)); + ip->info_type = p->info_type; + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, + pcb->rt->hook, 0); + if (error != 0) { + pcb->token = 0; + break; + } + + error = msleep(&pcb->msg, &pcb->pcb_mtx, PZERO|PCATCH, "l2ctl", + bluetooth_l2cap_rtx_timeout()); + pcb->token = 0; + + if (error != 0) + break; + + if (pcb->msg != NULL && + pcb->msg->header.cmd == NGM_L2CAP_L2CA_GET_INFO) { + /* Return data back to the user space */ + op = (ng_l2cap_l2ca_get_info_op *)(pcb->msg->data); + p->result = op->result; + p->info_size = min(p->info_size, op->info_size); + + if (p->info_size > 0) + error = copyout(op + 1, p->info_data, + p->info_size); + } else + error = EINVAL; + + NG_FREE_MSG(pcb->msg); /* checks for != NULL */ + } break; + + case SIOC_L2CAP_NODE_GET_AUTO_DISCON_TIMO: { + struct ng_btsocket_l2cap_raw_auto_discon_timo *p = + (struct ng_btsocket_l2cap_raw_auto_discon_timo *) data; + + error = ng_btsocket_l2cap_raw_send_sync_ngmsg(pcb, + NGM_L2CAP_NODE_GET_AUTO_DISCON_TIMO, + &p->timeout, sizeof(p->timeout)); + } break; + + case SIOC_L2CAP_NODE_SET_AUTO_DISCON_TIMO: { + struct ng_btsocket_l2cap_raw_auto_discon_timo *p = + (struct ng_btsocket_l2cap_raw_auto_discon_timo *) data; + + if (pcb->flags & NG_BTSOCKET_L2CAP_RAW_PRIVILEGED) + error = ng_btsocket_l2cap_raw_send_ngmsg(pcb->rt->hook, + NGM_L2CAP_NODE_SET_AUTO_DISCON_TIMO, + &p->timeout, sizeof(p->timeout)); + else + error = EPERM; + } break; + + default: + error = EINVAL; + break; + } + + mtx_unlock(&pcb->pcb_mtx); + + return (error); +} /* ng_btsocket_l2cap_raw_control */ + +/* + * Detach and destroy socket + */ + +void +ng_btsocket_l2cap_raw_detach(struct socket *so) +{ + ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); + + KASSERT(pcb != NULL, ("nt_btsocket_l2cap_raw_detach: pcb == NULL")); + if (ng_btsocket_l2cap_raw_node == NULL) + return; + + mtx_lock(&ng_btsocket_l2cap_raw_sockets_mtx); + mtx_lock(&pcb->pcb_mtx); + + LIST_REMOVE(pcb, next); + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&ng_btsocket_l2cap_raw_sockets_mtx); + + mtx_destroy(&pcb->pcb_mtx); + + bzero(pcb, sizeof(*pcb)); + FREE(pcb, M_NETGRAPH_BTSOCKET_L2CAP_RAW); + + so->so_pcb = NULL; +} /* ng_btsocket_l2cap_raw_detach */ + +/* + * Disconnect socket + */ + +int +ng_btsocket_l2cap_raw_disconnect(struct socket *so) +{ + ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_raw_node == NULL) + return (EINVAL); + + mtx_lock(&pcb->pcb_mtx); + pcb->rt = NULL; + soisdisconnected(so); + mtx_unlock(&pcb->pcb_mtx); + + return (0); +} /* ng_btsocket_l2cap_raw_disconnect */ + +/* + * Get peer address + */ + +int +ng_btsocket_l2cap_raw_peeraddr(struct socket *so, struct sockaddr **nam) +{ + ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); + struct sockaddr_l2cap sa; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_raw_node == NULL) + return (EINVAL); + + mtx_lock(&pcb->pcb_mtx); + bcopy(&pcb->dst, &sa.l2cap_bdaddr, sizeof(sa.l2cap_bdaddr)); + mtx_unlock(&pcb->pcb_mtx); + + sa.l2cap_psm = 0; + sa.l2cap_len = sizeof(sa); + sa.l2cap_family = AF_BLUETOOTH; + + *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); + + return ((*nam == NULL)? ENOMEM : 0); +} /* ng_btsocket_l2cap_raw_peeraddr */ + +/* + * Send data to socket + */ + +int +ng_btsocket_l2cap_raw_send(struct socket *so, int flags, struct mbuf *m, + struct sockaddr *nam, struct mbuf *control, struct thread *td) +{ + NG_FREE_M(m); /* Checks for m != NULL */ + NG_FREE_M(control); + + return (EOPNOTSUPP); +} /* ng_btsocket_l2cap_raw_send */ + +/* + * Get socket address + */ + +int +ng_btsocket_l2cap_raw_sockaddr(struct socket *so, struct sockaddr **nam) +{ + ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); + struct sockaddr_l2cap sa; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_raw_node == NULL) + return (EINVAL); + + mtx_lock(&pcb->pcb_mtx); + bcopy(&pcb->src, &sa.l2cap_bdaddr, sizeof(sa.l2cap_bdaddr)); + mtx_unlock(&pcb->pcb_mtx); + + sa.l2cap_psm = 0; + sa.l2cap_len = sizeof(sa); + sa.l2cap_family = AF_BLUETOOTH; + + *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); + + return ((*nam == NULL)? ENOMEM : 0); +} /* ng_btsocket_l2cap_raw_sockaddr */ + +/* + * Get next token + */ + +static void +ng_btsocket_l2cap_raw_get_token(u_int32_t *token) +{ + mtx_lock(&ng_btsocket_l2cap_raw_token_mtx); + + if (++ ng_btsocket_l2cap_raw_token == 0) + ng_btsocket_l2cap_raw_token = 1; + + *token = ng_btsocket_l2cap_raw_token; + + mtx_unlock(&ng_btsocket_l2cap_raw_token_mtx); +} /* ng_btsocket_l2cap_raw_get_token */ + +/* + * Send Netgraph message to the node - do not expect reply + */ + +static int +ng_btsocket_l2cap_raw_send_ngmsg(hook_p hook, int cmd, void *arg, int arglen) +{ + struct ng_mesg *msg = NULL; + int error = 0; + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, cmd, arglen, M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + if (arg != NULL && arglen > 0) + bcopy(arg, msg->data, arglen); + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, hook, 0); + + return (error); +} /* ng_btsocket_l2cap_raw_send_ngmsg */ + +/* + * Send Netgraph message to the node (no data) and wait for reply + */ + +static int +ng_btsocket_l2cap_raw_send_sync_ngmsg(ng_btsocket_l2cap_raw_pcb_p pcb, + int cmd, void *rsp, int rsplen) +{ + struct ng_mesg *msg = NULL; + int error = 0; + + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, cmd, 0, M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + ng_btsocket_l2cap_raw_get_token(&msg->header.token); + pcb->token = msg->header.token; + pcb->msg = NULL; + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, + pcb->rt->hook, 0); + if (error != 0) { + pcb->token = 0; + return (error); + } + + error = msleep(&pcb->msg, &pcb->pcb_mtx, PZERO|PCATCH, "l2ctl", + ng_btsocket_l2cap_raw_ioctl_timeout * hz); + pcb->token = 0; + + if (error != 0) + return (error); + + if (pcb->msg != NULL && pcb->msg->header.cmd == cmd) + bcopy(pcb->msg->data, rsp, rsplen); + else + error = EINVAL; + + NG_FREE_MSG(pcb->msg); /* checks for != NULL */ + + return (0); +} /* ng_btsocket_l2cap_raw_send_sync_ngmsg */ + diff --git a/sys/netgraph7/bluetooth/socket/ng_btsocket_rfcomm.c b/sys/netgraph7/bluetooth/socket/ng_btsocket_rfcomm.c new file mode 100644 index 0000000000..3eaeccf524 --- /dev/null +++ b/sys/netgraph7/bluetooth/socket/ng_btsocket_rfcomm.c @@ -0,0 +1,3576 @@ +/* + * ng_btsocket_rfcomm.c + */ + +/*- + * Copyright (c) 2001-2003 Maksim Yevmenkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ng_btsocket_rfcomm.c,v 1.28 2003/09/14 23:29:06 max Exp $ + * $FreeBSD: src/sys/netgraph/bluetooth/socket/ng_btsocket_rfcomm.c,v 1.27 2007/10/29 19:06:47 emax Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* MALLOC define */ +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_BTSOCKET_RFCOMM, "netgraph_btsocks_rfcomm", + "Netgraph Bluetooth RFCOMM sockets"); +#else +#define M_NETGRAPH_BTSOCKET_RFCOMM M_NETGRAPH +#endif /* NG_SEPARATE_MALLOC */ + +/* Debug */ +#define NG_BTSOCKET_RFCOMM_INFO \ + if (ng_btsocket_rfcomm_debug_level >= NG_BTSOCKET_INFO_LEVEL) \ + printf + +#define NG_BTSOCKET_RFCOMM_WARN \ + if (ng_btsocket_rfcomm_debug_level >= NG_BTSOCKET_WARN_LEVEL) \ + printf + +#define NG_BTSOCKET_RFCOMM_ERR \ + if (ng_btsocket_rfcomm_debug_level >= NG_BTSOCKET_ERR_LEVEL) \ + printf + +#define NG_BTSOCKET_RFCOMM_ALERT \ + if (ng_btsocket_rfcomm_debug_level >= NG_BTSOCKET_ALERT_LEVEL) \ + printf + +#define ALOT 0x7fff + +/* Local prototypes */ +static void ng_btsocket_rfcomm_upcall + (struct socket *so, void *arg, int waitflag); +static void ng_btsocket_rfcomm_sessions_task + (void *ctx, int pending); +static void ng_btsocket_rfcomm_session_task + (ng_btsocket_rfcomm_session_p s); +#define ng_btsocket_rfcomm_task_wakeup() \ + taskqueue_enqueue(taskqueue_swi_giant, &ng_btsocket_rfcomm_task) + +static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_connect_ind + (ng_btsocket_rfcomm_session_p s, int channel); +static void ng_btsocket_rfcomm_connect_cfm + (ng_btsocket_rfcomm_session_p s); + +static int ng_btsocket_rfcomm_session_create + (ng_btsocket_rfcomm_session_p *sp, struct socket *l2so, + bdaddr_p src, bdaddr_p dst, struct thread *td); +static int ng_btsocket_rfcomm_session_accept + (ng_btsocket_rfcomm_session_p s0); +static int ng_btsocket_rfcomm_session_connect + (ng_btsocket_rfcomm_session_p s); +static int ng_btsocket_rfcomm_session_receive + (ng_btsocket_rfcomm_session_p s); +static int ng_btsocket_rfcomm_session_send + (ng_btsocket_rfcomm_session_p s); +static void ng_btsocket_rfcomm_session_clean + (ng_btsocket_rfcomm_session_p s); +static void ng_btsocket_rfcomm_session_process_pcb + (ng_btsocket_rfcomm_session_p s); +static ng_btsocket_rfcomm_session_p ng_btsocket_rfcomm_session_by_addr + (bdaddr_p src, bdaddr_p dst); + +static int ng_btsocket_rfcomm_receive_frame + (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); +static int ng_btsocket_rfcomm_receive_sabm + (ng_btsocket_rfcomm_session_p s, int dlci); +static int ng_btsocket_rfcomm_receive_disc + (ng_btsocket_rfcomm_session_p s, int dlci); +static int ng_btsocket_rfcomm_receive_ua + (ng_btsocket_rfcomm_session_p s, int dlci); +static int ng_btsocket_rfcomm_receive_dm + (ng_btsocket_rfcomm_session_p s, int dlci); +static int ng_btsocket_rfcomm_receive_uih + (ng_btsocket_rfcomm_session_p s, int dlci, int pf, struct mbuf *m0); +static int ng_btsocket_rfcomm_receive_mcc + (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); +static int ng_btsocket_rfcomm_receive_test + (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); +static int ng_btsocket_rfcomm_receive_fc + (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); +static int ng_btsocket_rfcomm_receive_msc + (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); +static int ng_btsocket_rfcomm_receive_rpn + (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); +static int ng_btsocket_rfcomm_receive_rls + (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); +static int ng_btsocket_rfcomm_receive_pn + (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); +static void ng_btsocket_rfcomm_set_pn + (ng_btsocket_rfcomm_pcb_p pcb, u_int8_t cr, u_int8_t flow_control, + u_int8_t credits, u_int16_t mtu); + +static int ng_btsocket_rfcomm_send_command + (ng_btsocket_rfcomm_session_p s, u_int8_t type, u_int8_t dlci); +static int ng_btsocket_rfcomm_send_uih + (ng_btsocket_rfcomm_session_p s, u_int8_t address, u_int8_t pf, + u_int8_t credits, struct mbuf *data); +static int ng_btsocket_rfcomm_send_msc + (ng_btsocket_rfcomm_pcb_p pcb); +static int ng_btsocket_rfcomm_send_pn + (ng_btsocket_rfcomm_pcb_p pcb); +static int ng_btsocket_rfcomm_send_credits + (ng_btsocket_rfcomm_pcb_p pcb); + +static int ng_btsocket_rfcomm_pcb_send + (ng_btsocket_rfcomm_pcb_p pcb, int limit); +static void ng_btsocket_rfcomm_pcb_kill + (ng_btsocket_rfcomm_pcb_p pcb, int error); +static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_pcb_by_dlci + (ng_btsocket_rfcomm_session_p s, int dlci); +static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_pcb_listener + (bdaddr_p src, int channel); + +static void ng_btsocket_rfcomm_timeout + (ng_btsocket_rfcomm_pcb_p pcb); +static void ng_btsocket_rfcomm_untimeout + (ng_btsocket_rfcomm_pcb_p pcb); +static void ng_btsocket_rfcomm_process_timeout + (void *xpcb); + +static struct mbuf * ng_btsocket_rfcomm_prepare_packet + (struct sockbuf *sb, int length); + +/* Globals */ +extern int ifqmaxlen; +static u_int32_t ng_btsocket_rfcomm_debug_level; +static u_int32_t ng_btsocket_rfcomm_timo; +struct task ng_btsocket_rfcomm_task; +static LIST_HEAD(, ng_btsocket_rfcomm_session) ng_btsocket_rfcomm_sessions; +static struct mtx ng_btsocket_rfcomm_sessions_mtx; +static LIST_HEAD(, ng_btsocket_rfcomm_pcb) ng_btsocket_rfcomm_sockets; +static struct mtx ng_btsocket_rfcomm_sockets_mtx; + +/* Sysctl tree */ +SYSCTL_DECL(_net_bluetooth_rfcomm_sockets); +SYSCTL_NODE(_net_bluetooth_rfcomm_sockets, OID_AUTO, stream, CTLFLAG_RW, + 0, "Bluetooth STREAM RFCOMM sockets family"); +SYSCTL_INT(_net_bluetooth_rfcomm_sockets_stream, OID_AUTO, debug_level, + CTLFLAG_RW, + &ng_btsocket_rfcomm_debug_level, NG_BTSOCKET_INFO_LEVEL, + "Bluetooth STREAM RFCOMM sockets debug level"); +SYSCTL_INT(_net_bluetooth_rfcomm_sockets_stream, OID_AUTO, timeout, + CTLFLAG_RW, + &ng_btsocket_rfcomm_timo, 60, + "Bluetooth STREAM RFCOMM sockets timeout"); + +/***************************************************************************** + ***************************************************************************** + ** RFCOMM CRC + ***************************************************************************** + *****************************************************************************/ + +static u_int8_t ng_btsocket_rfcomm_crc_table[256] = { + 0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75, + 0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b, + 0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69, + 0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67, + + 0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d, + 0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43, + 0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51, + 0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f, + + 0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05, + 0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b, + 0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19, + 0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17, + + 0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d, + 0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33, + 0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21, + 0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f, + + 0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95, + 0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b, + 0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89, + 0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87, + + 0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad, + 0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3, + 0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1, + 0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf, + + 0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5, + 0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb, + 0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9, + 0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7, + + 0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd, + 0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3, + 0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1, + 0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf +}; + +/* CRC */ +static u_int8_t +ng_btsocket_rfcomm_crc(u_int8_t *data, int length) +{ + u_int8_t crc = 0xff; + + while (length --) + crc = ng_btsocket_rfcomm_crc_table[crc ^ *data++]; + + return (crc); +} /* ng_btsocket_rfcomm_crc */ + +/* FCS on 2 bytes */ +static u_int8_t +ng_btsocket_rfcomm_fcs2(u_int8_t *data) +{ + return (0xff - ng_btsocket_rfcomm_crc(data, 2)); +} /* ng_btsocket_rfcomm_fcs2 */ + +/* FCS on 3 bytes */ +static u_int8_t +ng_btsocket_rfcomm_fcs3(u_int8_t *data) +{ + return (0xff - ng_btsocket_rfcomm_crc(data, 3)); +} /* ng_btsocket_rfcomm_fcs3 */ + +/* + * Check FCS + * + * From Bluetooth spec + * + * "... In 07.10, the frame check sequence (FCS) is calculated on different + * sets of fields for different frame types. These are the fields that the + * FCS are calculated on: + * + * For SABM, DISC, UA, DM frames: on Address, Control and length field. + * For UIH frames: on Address and Control field. + * + * (This is stated here for clarification, and to set the standard for RFCOMM; + * the fields included in FCS calculation have actually changed in version + * 7.0.0 of TS 07.10, but RFCOMM will not change the FCS calculation scheme + * from the one above.) ..." + */ + +static int +ng_btsocket_rfcomm_check_fcs(u_int8_t *data, int type, u_int8_t fcs) +{ + if (type != RFCOMM_FRAME_UIH) + return (ng_btsocket_rfcomm_fcs3(data) != fcs); + + return (ng_btsocket_rfcomm_fcs2(data) != fcs); +} /* ng_btsocket_rfcomm_check_fcs */ + +/***************************************************************************** + ***************************************************************************** + ** Socket interface + ***************************************************************************** + *****************************************************************************/ + +/* + * Initialize everything + */ + +void +ng_btsocket_rfcomm_init(void) +{ + ng_btsocket_rfcomm_debug_level = NG_BTSOCKET_WARN_LEVEL; + ng_btsocket_rfcomm_timo = 60; + + /* RFCOMM task */ + TASK_INIT(&ng_btsocket_rfcomm_task, 0, + ng_btsocket_rfcomm_sessions_task, NULL); + + /* RFCOMM sessions list */ + LIST_INIT(&ng_btsocket_rfcomm_sessions); + mtx_init(&ng_btsocket_rfcomm_sessions_mtx, + "btsocks_rfcomm_sessions_mtx", NULL, MTX_DEF); + + /* RFCOMM sockets list */ + LIST_INIT(&ng_btsocket_rfcomm_sockets); + mtx_init(&ng_btsocket_rfcomm_sockets_mtx, + "btsocks_rfcomm_sockets_mtx", NULL, MTX_DEF); +} /* ng_btsocket_rfcomm_init */ + +/* + * Abort connection on socket + */ + +void +ng_btsocket_rfcomm_abort(struct socket *so) +{ + + so->so_error = ECONNABORTED; + (void)ng_btsocket_rfcomm_disconnect(so); +} /* ng_btsocket_rfcomm_abort */ + +void +ng_btsocket_rfcomm_close(struct socket *so) +{ + + (void)ng_btsocket_rfcomm_disconnect(so); +} /* ng_btsocket_rfcomm_close */ + +/* + * Accept connection on socket. Nothing to do here, socket must be connected + * and ready, so just return peer address and be done with it. + */ + +int +ng_btsocket_rfcomm_accept(struct socket *so, struct sockaddr **nam) +{ + return (ng_btsocket_rfcomm_peeraddr(so, nam)); +} /* ng_btsocket_rfcomm_accept */ + +/* + * Create and attach new socket + */ + +int +ng_btsocket_rfcomm_attach(struct socket *so, int proto, struct thread *td) +{ + ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); + int error; + + /* Check socket and protocol */ + if (so->so_type != SOCK_STREAM) + return (ESOCKTNOSUPPORT); + +#if 0 /* XXX sonewconn() calls "pru_attach" with proto == 0 */ + if (proto != 0) + if (proto != BLUETOOTH_PROTO_RFCOMM) + return (EPROTONOSUPPORT); +#endif /* XXX */ + + if (pcb != NULL) + return (EISCONN); + + /* Reserve send and receive space if it is not reserved yet */ + if ((so->so_snd.sb_hiwat == 0) || (so->so_rcv.sb_hiwat == 0)) { + error = soreserve(so, NG_BTSOCKET_RFCOMM_SENDSPACE, + NG_BTSOCKET_RFCOMM_RECVSPACE); + if (error != 0) + return (error); + } + + /* Allocate the PCB */ + MALLOC(pcb, ng_btsocket_rfcomm_pcb_p, sizeof(*pcb), + M_NETGRAPH_BTSOCKET_RFCOMM, M_NOWAIT | M_ZERO); + if (pcb == NULL) + return (ENOMEM); + + /* Link the PCB and the socket */ + so->so_pcb = (caddr_t) pcb; + pcb->so = so; + + /* Initialize PCB */ + pcb->state = NG_BTSOCKET_RFCOMM_DLC_CLOSED; + pcb->flags = NG_BTSOCKET_RFCOMM_DLC_CFC; + + pcb->lmodem = + pcb->rmodem = (RFCOMM_MODEM_RTC | RFCOMM_MODEM_RTR | RFCOMM_MODEM_DV); + + pcb->mtu = RFCOMM_DEFAULT_MTU; + pcb->tx_cred = 0; + pcb->rx_cred = RFCOMM_DEFAULT_CREDITS; + + mtx_init(&pcb->pcb_mtx, "btsocks_rfcomm_pcb_mtx", NULL, MTX_DEF); + callout_handle_init(&pcb->timo); + + /* Add the PCB to the list */ + mtx_lock(&ng_btsocket_rfcomm_sockets_mtx); + LIST_INSERT_HEAD(&ng_btsocket_rfcomm_sockets, pcb, next); + mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); + + return (0); +} /* ng_btsocket_rfcomm_attach */ + +/* + * Bind socket + */ + +int +ng_btsocket_rfcomm_bind(struct socket *so, struct sockaddr *nam, + struct thread *td) +{ + ng_btsocket_rfcomm_pcb_t *pcb = so2rfcomm_pcb(so), *pcb1; + struct sockaddr_rfcomm *sa = (struct sockaddr_rfcomm *) nam; + + if (pcb == NULL) + return (EINVAL); + + /* Verify address */ + if (sa == NULL) + return (EINVAL); + if (sa->rfcomm_family != AF_BLUETOOTH) + return (EAFNOSUPPORT); + if (sa->rfcomm_len != sizeof(*sa)) + return (EINVAL); + if (sa->rfcomm_channel > 30) + return (EINVAL); + + mtx_lock(&pcb->pcb_mtx); + + if (sa->rfcomm_channel != 0) { + mtx_lock(&ng_btsocket_rfcomm_sockets_mtx); + + LIST_FOREACH(pcb1, &ng_btsocket_rfcomm_sockets, next) { + if (pcb1->channel == sa->rfcomm_channel && + bcmp(&pcb1->src, &sa->rfcomm_bdaddr, + sizeof(pcb1->src)) == 0) { + mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); + mtx_unlock(&pcb->pcb_mtx); + + return (EADDRINUSE); + } + } + + mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); + } + + bcopy(&sa->rfcomm_bdaddr, &pcb->src, sizeof(pcb->src)); + pcb->channel = sa->rfcomm_channel; + + mtx_unlock(&pcb->pcb_mtx); + + return (0); +} /* ng_btsocket_rfcomm_bind */ + +/* + * Connect socket + */ + +int +ng_btsocket_rfcomm_connect(struct socket *so, struct sockaddr *nam, + struct thread *td) +{ + ng_btsocket_rfcomm_pcb_t *pcb = so2rfcomm_pcb(so); + struct sockaddr_rfcomm *sa = (struct sockaddr_rfcomm *) nam; + ng_btsocket_rfcomm_session_t *s = NULL; + struct socket *l2so = NULL; + int dlci, error = 0; + + if (pcb == NULL) + return (EINVAL); + + /* Verify address */ + if (sa == NULL) + return (EINVAL); + if (sa->rfcomm_family != AF_BLUETOOTH) + return (EAFNOSUPPORT); + if (sa->rfcomm_len != sizeof(*sa)) + return (EINVAL); + if (sa->rfcomm_channel > 30) + return (EINVAL); + if (sa->rfcomm_channel == 0 || + bcmp(&sa->rfcomm_bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) + return (EDESTADDRREQ); + + /* + * XXX FIXME - This is FUBAR. socreate() will call soalloc(1), i.e. + * soalloc() is allowed to sleep in MALLOC. This creates "could sleep" + * WITNESS warnings. To work around this problem we will create L2CAP + * socket first and then check if we actually need it. Note that we + * will not check for errors in socreate() because if we failed to + * create L2CAP socket at this point we still might have already open + * session. + */ + + error = socreate(PF_BLUETOOTH, &l2so, SOCK_SEQPACKET, + BLUETOOTH_PROTO_L2CAP, td->td_ucred, td); + + /* + * Look for session between "pcb->src" and "sa->rfcomm_bdaddr" (dst) + */ + + mtx_lock(&ng_btsocket_rfcomm_sessions_mtx); + + s = ng_btsocket_rfcomm_session_by_addr(&pcb->src, &sa->rfcomm_bdaddr); + if (s == NULL) { + /* + * We need to create new RFCOMM session. Check if we have L2CAP + * socket. If l2so == NULL then error has the error code from + * socreate() + */ + + if (l2so == NULL) { + mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); + return (error); + } + + error = ng_btsocket_rfcomm_session_create(&s, l2so, + &pcb->src, &sa->rfcomm_bdaddr, td); + if (error != 0) { + mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); + soclose(l2so); + + return (error); + } + } else if (l2so != NULL) + soclose(l2so); /* we don't need new L2CAP socket */ + + /* + * Check if we already have the same DLCI the the same session + */ + + mtx_lock(&s->session_mtx); + mtx_lock(&pcb->pcb_mtx); + + dlci = RFCOMM_MKDLCI(!INITIATOR(s), sa->rfcomm_channel); + + if (ng_btsocket_rfcomm_pcb_by_dlci(s, dlci) != NULL) { + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&s->session_mtx); + mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); + + return (EBUSY); + } + + /* + * Check session state and if its not acceptable then refuse connection + */ + + switch (s->state) { + case NG_BTSOCKET_RFCOMM_SESSION_CONNECTING: + case NG_BTSOCKET_RFCOMM_SESSION_CONNECTED: + case NG_BTSOCKET_RFCOMM_SESSION_OPEN: + /* + * Update destination address and channel and attach + * DLC to the session + */ + + bcopy(&sa->rfcomm_bdaddr, &pcb->dst, sizeof(pcb->dst)); + pcb->channel = sa->rfcomm_channel; + pcb->dlci = dlci; + + LIST_INSERT_HEAD(&s->dlcs, pcb, session_next); + pcb->session = s; + + ng_btsocket_rfcomm_timeout(pcb); + soisconnecting(pcb->so); + + if (s->state == NG_BTSOCKET_RFCOMM_SESSION_OPEN) { + pcb->mtu = s->mtu; + bcopy(&so2l2cap_pcb(s->l2so)->src, &pcb->src, + sizeof(pcb->src)); + + pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONFIGURING; + + error = ng_btsocket_rfcomm_send_pn(pcb); + if (error == 0) + error = ng_btsocket_rfcomm_task_wakeup(); + } else + pcb->state = NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT; + break; + + default: + error = ECONNRESET; + break; + } + + mtx_unlock(&pcb->pcb_mtx); + mtx_unlock(&s->session_mtx); + mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); + + return (error); +} /* ng_btsocket_rfcomm_connect */ + +/* + * Process ioctl's calls on socket. + * XXX FIXME this should provide interface to the RFCOMM multiplexor channel + */ + +int +ng_btsocket_rfcomm_control(struct socket *so, u_long cmd, caddr_t data, + struct ifnet *ifp, struct thread *td) +{ + return (EINVAL); +} /* ng_btsocket_rfcomm_control */ + +/* + * Process getsockopt/setsockopt system calls + */ + +int +ng_btsocket_rfcomm_ctloutput(struct socket *so, struct sockopt *sopt) +{ + ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); + struct ng_btsocket_rfcomm_fc_info fcinfo; + int error = 0; + + if (pcb == NULL) + return (EINVAL); + if (sopt->sopt_level != SOL_RFCOMM) + return (0); + + mtx_lock(&pcb->pcb_mtx); + + switch (sopt->sopt_dir) { + case SOPT_GET: + switch (sopt->sopt_name) { + case SO_RFCOMM_MTU: + error = sooptcopyout(sopt, &pcb->mtu, sizeof(pcb->mtu)); + break; + + case SO_RFCOMM_FC_INFO: + fcinfo.lmodem = pcb->lmodem; + fcinfo.rmodem = pcb->rmodem; + fcinfo.tx_cred = pcb->tx_cred; + fcinfo.rx_cred = pcb->rx_cred; + fcinfo.cfc = (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC)? + 1 : 0; + fcinfo.reserved = 0; + + error = sooptcopyout(sopt, &fcinfo, sizeof(fcinfo)); + break; + + default: + error = ENOPROTOOPT; + break; + } + break; + + case SOPT_SET: + switch (sopt->sopt_name) { + default: + error = ENOPROTOOPT; + break; + } + break; + + default: + error = EINVAL; + break; + } + + mtx_unlock(&pcb->pcb_mtx); + + return (error); +} /* ng_btsocket_rfcomm_ctloutput */ + +/* + * Detach and destroy socket + */ + +void +ng_btsocket_rfcomm_detach(struct socket *so) +{ + ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); + + KASSERT(pcb != NULL, ("ng_btsocket_rfcomm_detach: pcb == NULL")); + + mtx_lock(&pcb->pcb_mtx); + + switch (pcb->state) { + case NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT: + case NG_BTSOCKET_RFCOMM_DLC_CONFIGURING: + case NG_BTSOCKET_RFCOMM_DLC_CONNECTING: + case NG_BTSOCKET_RFCOMM_DLC_CONNECTED: + /* XXX What to do with pending request? */ + if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO) + ng_btsocket_rfcomm_untimeout(pcb); + + if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT) + pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_DETACHED; + else + pcb->state = NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING; + + ng_btsocket_rfcomm_task_wakeup(); + break; + + case NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING: + ng_btsocket_rfcomm_task_wakeup(); + break; + } + + while (pcb->state != NG_BTSOCKET_RFCOMM_DLC_CLOSED) + msleep(&pcb->state, &pcb->pcb_mtx, PZERO, "rf_det", 0); + + if (pcb->session != NULL) + panic("%s: pcb->session != NULL\n", __func__); + if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO) + panic("%s: timeout on closed DLC, flags=%#x\n", + __func__, pcb->flags); + + mtx_lock(&ng_btsocket_rfcomm_sockets_mtx); + LIST_REMOVE(pcb, next); + mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); + + mtx_unlock(&pcb->pcb_mtx); + + mtx_destroy(&pcb->pcb_mtx); + bzero(pcb, sizeof(*pcb)); + FREE(pcb, M_NETGRAPH_BTSOCKET_RFCOMM); + + soisdisconnected(so); + so->so_pcb = NULL; +} /* ng_btsocket_rfcomm_detach */ + +/* + * Disconnect socket + */ + +int +ng_btsocket_rfcomm_disconnect(struct socket *so) +{ + ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); + + if (pcb == NULL) + return (EINVAL); + + mtx_lock(&pcb->pcb_mtx); + + if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING) { + mtx_unlock(&pcb->pcb_mtx); + return (EINPROGRESS); + } + + /* XXX What to do with pending request? */ + if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO) + ng_btsocket_rfcomm_untimeout(pcb); + + switch (pcb->state) { + case NG_BTSOCKET_RFCOMM_DLC_CONFIGURING: /* XXX can we get here? */ + case NG_BTSOCKET_RFCOMM_DLC_CONNECTING: /* XXX can we get here? */ + case NG_BTSOCKET_RFCOMM_DLC_CONNECTED: + + /* + * Just change DLC state and enqueue RFCOMM task. It will + * queue and send DISC on the DLC. + */ + + pcb->state = NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING; + soisdisconnecting(so); + + ng_btsocket_rfcomm_task_wakeup(); + break; + + case NG_BTSOCKET_RFCOMM_DLC_CLOSED: + case NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT: + break; + + default: + panic("%s: Invalid DLC state=%d, flags=%#x\n", + __func__, pcb->state, pcb->flags); + break; + } + + mtx_unlock(&pcb->pcb_mtx); + + return (0); +} /* ng_btsocket_rfcomm_disconnect */ + +/* + * Listen on socket. First call to listen() will create listening RFCOMM session + */ + +int +ng_btsocket_rfcomm_listen(struct socket *so, int backlog, struct thread *td) +{ + ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so), pcb1; + ng_btsocket_rfcomm_session_p s = NULL; + struct socket *l2so = NULL; + int error, socreate_error, usedchannels; + + if (pcb == NULL) + return (EINVAL); + if (pcb->channel > 30) + return (EADDRNOTAVAIL); + + usedchannels = 0; + + mtx_lock(&pcb->pcb_mtx); + + if (pcb->channel == 0) { + mtx_lock(&ng_btsocket_rfcomm_sockets_mtx); + + LIST_FOREACH(pcb1, &ng_btsocket_rfcomm_sockets, next) + if (pcb1->channel != 0 && + bcmp(&pcb1->src, &pcb->src, sizeof(pcb->src)) == 0) + usedchannels |= (1 << (pcb1->channel - 1)); + + for (pcb->channel = 30; pcb->channel > 0; pcb->channel --) + if (!(usedchannels & (1 << (pcb->channel - 1)))) + break; + + if (pcb->channel == 0) { + mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); + mtx_unlock(&pcb->pcb_mtx); + + return (EADDRNOTAVAIL); + } + + mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); + } + + mtx_unlock(&pcb->pcb_mtx); + + /* + * XXX FIXME - This is FUBAR. socreate() will call soalloc(1), i.e. + * soalloc() is allowed to sleep in MALLOC. This creates "could sleep" + * WITNESS warnings. To work around this problem we will create L2CAP + * socket first and then check if we actually need it. Note that we + * will not check for errors in socreate() because if we failed to + * create L2CAP socket at this point we still might have already open + * session. + */ + + socreate_error = socreate(PF_BLUETOOTH, &l2so, SOCK_SEQPACKET, + BLUETOOTH_PROTO_L2CAP, td->td_ucred, td); + + /* + * Transition the socket and session into the LISTENING state. Check + * for collisions first, as there can only be one. + */ + mtx_lock(&ng_btsocket_rfcomm_sessions_mtx); + SOCK_LOCK(so); + error = solisten_proto_check(so); + SOCK_UNLOCK(so); + if (error != 0) + goto out; + + LIST_FOREACH(s, &ng_btsocket_rfcomm_sessions, next) + if (s->state == NG_BTSOCKET_RFCOMM_SESSION_LISTENING) + break; + + if (s == NULL) { + /* + * We need to create default RFCOMM session. Check if we have + * L2CAP socket. If l2so == NULL then error has the error code + * from socreate() + */ + if (l2so == NULL) { + error = socreate_error; + goto out; + } + + /* + * Create default listen RFCOMM session. The default RFCOMM + * session will listen on ANY address. + * + * XXX FIXME Note that currently there is no way to adjust MTU + * for the default session. + */ + error = ng_btsocket_rfcomm_session_create(&s, l2so, + NG_HCI_BDADDR_ANY, NULL, td); + if (error != 0) + goto out; + l2so = NULL; + } + SOCK_LOCK(so); + solisten_proto(so, backlog); + SOCK_UNLOCK(so); +out: + mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); + /* + * If we still have an l2so reference here, it's unneeded, so release + * it. + */ + if (l2so != NULL) + soclose(l2so); + return (error); +} /* ng_btsocket_listen */ + +/* + * Get peer address + */ + +int +ng_btsocket_rfcomm_peeraddr(struct socket *so, struct sockaddr **nam) +{ + ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); + struct sockaddr_rfcomm sa; + + if (pcb == NULL) + return (EINVAL); + + bcopy(&pcb->dst, &sa.rfcomm_bdaddr, sizeof(sa.rfcomm_bdaddr)); + sa.rfcomm_channel = pcb->channel; + sa.rfcomm_len = sizeof(sa); + sa.rfcomm_family = AF_BLUETOOTH; + + *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); + + return ((*nam == NULL)? ENOMEM : 0); +} /* ng_btsocket_rfcomm_peeraddr */ + +/* + * Send data to socket + */ + +int +ng_btsocket_rfcomm_send(struct socket *so, int flags, struct mbuf *m, + struct sockaddr *nam, struct mbuf *control, struct thread *td) +{ + ng_btsocket_rfcomm_pcb_t *pcb = so2rfcomm_pcb(so); + int error = 0; + + /* Check socket and input */ + if (pcb == NULL || m == NULL || control != NULL) { + error = EINVAL; + goto drop; + } + + mtx_lock(&pcb->pcb_mtx); + + /* Make sure DLC is connected */ + if (pcb->state != NG_BTSOCKET_RFCOMM_DLC_CONNECTED) { + mtx_unlock(&pcb->pcb_mtx); + error = ENOTCONN; + goto drop; + } + + /* Put the packet on the socket's send queue and wakeup RFCOMM task */ + sbappend(&pcb->so->so_snd, m); + m = NULL; + + if (!(pcb->flags & NG_BTSOCKET_RFCOMM_DLC_SENDING)) { + pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_SENDING; + error = ng_btsocket_rfcomm_task_wakeup(); + } + + mtx_unlock(&pcb->pcb_mtx); +drop: + NG_FREE_M(m); /* checks for != NULL */ + NG_FREE_M(control); + + return (error); +} /* ng_btsocket_rfcomm_send */ + +/* + * Get socket address + */ + +int +ng_btsocket_rfcomm_sockaddr(struct socket *so, struct sockaddr **nam) +{ + ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); + struct sockaddr_rfcomm sa; + + if (pcb == NULL) + return (EINVAL); + + bcopy(&pcb->src, &sa.rfcomm_bdaddr, sizeof(sa.rfcomm_bdaddr)); + sa.rfcomm_channel = pcb->channel; + sa.rfcomm_len = sizeof(sa); + sa.rfcomm_family = AF_BLUETOOTH; + + *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); + + return ((*nam == NULL)? ENOMEM : 0); +} /* ng_btsocket_rfcomm_sockaddr */ + +/* + * Upcall function for L2CAP sockets. Enqueue RFCOMM task. + */ + +static void +ng_btsocket_rfcomm_upcall(struct socket *so, void *arg, int waitflag) +{ + int error; + + if (so == NULL) + panic("%s: so == NULL\n", __func__); + + if ((error = ng_btsocket_rfcomm_task_wakeup()) != 0) + NG_BTSOCKET_RFCOMM_ALERT( +"%s: Could not enqueue RFCOMM task, error=%d\n", __func__, error); +} /* ng_btsocket_rfcomm_upcall */ + +/* + * RFCOMM task. Will handle all RFCOMM sessions in one pass. + * XXX FIXME does not scale very well + */ + +static void +ng_btsocket_rfcomm_sessions_task(void *ctx, int pending) +{ + ng_btsocket_rfcomm_session_p s = NULL, s_next = NULL; + + mtx_lock(&ng_btsocket_rfcomm_sessions_mtx); + + for (s = LIST_FIRST(&ng_btsocket_rfcomm_sessions); s != NULL; ) { + mtx_lock(&s->session_mtx); + s_next = LIST_NEXT(s, next); + + ng_btsocket_rfcomm_session_task(s); + + if (s->state == NG_BTSOCKET_RFCOMM_SESSION_CLOSED) { + /* Unlink and clean the session */ + LIST_REMOVE(s, next); + + NG_BT_MBUFQ_DRAIN(&s->outq); + if (!LIST_EMPTY(&s->dlcs)) + panic("%s: DLC list is not empty\n", __func__); + + /* Close L2CAP socket */ + s->l2so->so_upcallarg = NULL; + s->l2so->so_upcall = NULL; + SOCKBUF_LOCK(&s->l2so->so_rcv); + s->l2so->so_rcv.sb_flags &= ~SB_UPCALL; + SOCKBUF_UNLOCK(&s->l2so->so_rcv); + SOCKBUF_LOCK(&s->l2so->so_snd); + s->l2so->so_snd.sb_flags &= ~SB_UPCALL; + SOCKBUF_UNLOCK(&s->l2so->so_snd); + soclose(s->l2so); + + mtx_unlock(&s->session_mtx); + + mtx_destroy(&s->session_mtx); + bzero(s, sizeof(*s)); + FREE(s, M_NETGRAPH_BTSOCKET_RFCOMM); + } else + mtx_unlock(&s->session_mtx); + + s = s_next; + } + + mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); +} /* ng_btsocket_rfcomm_sessions_task */ + +/* + * Process RFCOMM session. Will handle all RFCOMM sockets in one pass. + */ + +static void +ng_btsocket_rfcomm_session_task(ng_btsocket_rfcomm_session_p s) +{ + mtx_assert(&s->session_mtx, MA_OWNED); + + if (s->l2so->so_rcv.sb_state & SBS_CANTRCVMORE) { + NG_BTSOCKET_RFCOMM_INFO( +"%s: L2CAP connection has been terminated, so=%p, so_state=%#x, so_count=%d, " \ +"state=%d, flags=%#x\n", __func__, s->l2so, s->l2so->so_state, + s->l2so->so_count, s->state, s->flags); + + s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; + ng_btsocket_rfcomm_session_clean(s); + } + + /* Now process upcall */ + switch (s->state) { + /* Try to accept new L2CAP connection(s) */ + case NG_BTSOCKET_RFCOMM_SESSION_LISTENING: + while (ng_btsocket_rfcomm_session_accept(s) == 0) + ; + break; + + /* Process the results of the L2CAP connect */ + case NG_BTSOCKET_RFCOMM_SESSION_CONNECTING: + ng_btsocket_rfcomm_session_process_pcb(s); + + if (ng_btsocket_rfcomm_session_connect(s) != 0) { + s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; + ng_btsocket_rfcomm_session_clean(s); + } + break; + + /* Try to receive/send more data */ + case NG_BTSOCKET_RFCOMM_SESSION_CONNECTED: + case NG_BTSOCKET_RFCOMM_SESSION_OPEN: + case NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING: + ng_btsocket_rfcomm_session_process_pcb(s); + + if (ng_btsocket_rfcomm_session_receive(s) != 0) { + s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; + ng_btsocket_rfcomm_session_clean(s); + } else if (ng_btsocket_rfcomm_session_send(s) != 0) { + s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; + ng_btsocket_rfcomm_session_clean(s); + } + break; + + case NG_BTSOCKET_RFCOMM_SESSION_CLOSED: + break; + + default: + panic("%s: Invalid session state=%d, flags=%#x\n", + __func__, s->state, s->flags); + break; + } +} /* ng_btsocket_rfcomm_session_task */ + +/* + * Process RFCOMM connection indicator. Caller must hold s->session_mtx + */ + +static ng_btsocket_rfcomm_pcb_p +ng_btsocket_rfcomm_connect_ind(ng_btsocket_rfcomm_session_p s, int channel) +{ + ng_btsocket_rfcomm_pcb_p pcb = NULL, pcb1 = NULL; + ng_btsocket_l2cap_pcb_p l2pcb = NULL; + struct socket *so1 = NULL; + + mtx_assert(&s->session_mtx, MA_OWNED); + + /* + * Try to find RFCOMM socket that listens on given source address + * and channel. This will return the best possible match. + */ + + l2pcb = so2l2cap_pcb(s->l2so); + pcb = ng_btsocket_rfcomm_pcb_listener(&l2pcb->src, channel); + if (pcb == NULL) + return (NULL); + + /* + * Check the pending connections queue and if we have space then + * create new socket and set proper source and destination address, + * and channel. + */ + + mtx_lock(&pcb->pcb_mtx); + + if (pcb->so->so_qlen <= pcb->so->so_qlimit) + so1 = sonewconn(pcb->so, 0); + + mtx_unlock(&pcb->pcb_mtx); + + if (so1 == NULL) + return (NULL); + + /* + * If we got here than we have created new socket. So complete the + * connection. Set source and destination address from the session. + */ + + pcb1 = so2rfcomm_pcb(so1); + if (pcb1 == NULL) + panic("%s: pcb1 == NULL\n", __func__); + + mtx_lock(&pcb1->pcb_mtx); + + bcopy(&l2pcb->src, &pcb1->src, sizeof(pcb1->src)); + bcopy(&l2pcb->dst, &pcb1->dst, sizeof(pcb1->dst)); + pcb1->channel = channel; + + /* Link new DLC to the session. We already hold s->session_mtx */ + LIST_INSERT_HEAD(&s->dlcs, pcb1, session_next); + pcb1->session = s; + + mtx_unlock(&pcb1->pcb_mtx); + + return (pcb1); +} /* ng_btsocket_rfcomm_connect_ind */ + +/* + * Process RFCOMM connect confirmation. Caller must hold s->session_mtx. + */ + +static void +ng_btsocket_rfcomm_connect_cfm(ng_btsocket_rfcomm_session_p s) +{ + ng_btsocket_rfcomm_pcb_p pcb = NULL, pcb_next = NULL; + int error; + + mtx_assert(&s->session_mtx, MA_OWNED); + + /* + * Wake up all waiting sockets and send PN request for each of them. + * Note that timeout already been set in ng_btsocket_rfcomm_connect() + * + * Note: cannot use LIST_FOREACH because ng_btsocket_rfcomm_pcb_kill + * will unlink DLC from the session + */ + + for (pcb = LIST_FIRST(&s->dlcs); pcb != NULL; ) { + mtx_lock(&pcb->pcb_mtx); + pcb_next = LIST_NEXT(pcb, session_next); + + if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT) { + pcb->mtu = s->mtu; + bcopy(&so2l2cap_pcb(s->l2so)->src, &pcb->src, + sizeof(pcb->src)); + + error = ng_btsocket_rfcomm_send_pn(pcb); + if (error == 0) + pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONFIGURING; + else + ng_btsocket_rfcomm_pcb_kill(pcb, error); + } + + mtx_unlock(&pcb->pcb_mtx); + pcb = pcb_next; + } +} /* ng_btsocket_rfcomm_connect_cfm */ + +/***************************************************************************** + ***************************************************************************** + ** RFCOMM sessions + ***************************************************************************** + *****************************************************************************/ + +/* + * Create new RFCOMM session. That function WILL NOT take ownership over l2so. + * Caller MUST free l2so if function failed. + */ + +static int +ng_btsocket_rfcomm_session_create(ng_btsocket_rfcomm_session_p *sp, + struct socket *l2so, bdaddr_p src, bdaddr_p dst, + struct thread *td) +{ + ng_btsocket_rfcomm_session_p s = NULL; + struct sockaddr_l2cap l2sa; + struct sockopt l2sopt; + int error; + u_int16_t mtu; + + mtx_assert(&ng_btsocket_rfcomm_sessions_mtx, MA_OWNED); + + /* Allocate the RFCOMM session */ + MALLOC(s, ng_btsocket_rfcomm_session_p, sizeof(*s), + M_NETGRAPH_BTSOCKET_RFCOMM, M_NOWAIT | M_ZERO); + if (s == NULL) + return (ENOMEM); + + /* Set defaults */ + s->mtu = RFCOMM_DEFAULT_MTU; + s->flags = 0; + s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; + NG_BT_MBUFQ_INIT(&s->outq, ifqmaxlen); + + /* + * XXX Mark session mutex as DUPOK to prevent "duplicated lock of + * the same type" message. When accepting new L2CAP connection + * ng_btsocket_rfcomm_session_accept() holds both session mutexes + * for "old" (accepting) session and "new" (created) session. + */ + + mtx_init(&s->session_mtx, "btsocks_rfcomm_session_mtx", NULL, + MTX_DEF|MTX_DUPOK); + + LIST_INIT(&s->dlcs); + + /* Prepare L2CAP socket */ + l2so->so_upcallarg = NULL; + l2so->so_upcall = ng_btsocket_rfcomm_upcall; + SOCKBUF_LOCK(&l2so->so_rcv); + l2so->so_rcv.sb_flags |= SB_UPCALL; + SOCKBUF_UNLOCK(&l2so->so_rcv); + SOCKBUF_LOCK(&l2so->so_snd); + l2so->so_snd.sb_flags |= SB_UPCALL; + SOCKBUF_UNLOCK(&l2so->so_snd); + l2so->so_state |= SS_NBIO; + s->l2so = l2so; + + mtx_lock(&s->session_mtx); + + /* + * "src" == NULL and "dst" == NULL means just create session. + * caller must do the rest + */ + + if (src == NULL && dst == NULL) + goto done; + + /* + * Set incoming MTU on L2CAP socket. It is RFCOMM session default MTU + * plus 5 bytes: RFCOMM frame header, one extra byte for length and one + * extra byte for credits. + */ + + mtu = s->mtu + sizeof(struct rfcomm_frame_hdr) + 1 + 1; + + l2sopt.sopt_dir = SOPT_SET; + l2sopt.sopt_level = SOL_L2CAP; + l2sopt.sopt_name = SO_L2CAP_IMTU; + l2sopt.sopt_val = (void *) &mtu; + l2sopt.sopt_valsize = sizeof(mtu); + l2sopt.sopt_td = NULL; + + error = sosetopt(s->l2so, &l2sopt); + if (error != 0) + goto bad; + + /* Bind socket to "src" address */ + l2sa.l2cap_len = sizeof(l2sa); + l2sa.l2cap_family = AF_BLUETOOTH; + l2sa.l2cap_psm = (dst == NULL)? htole16(NG_L2CAP_PSM_RFCOMM) : 0; + bcopy(src, &l2sa.l2cap_bdaddr, sizeof(l2sa.l2cap_bdaddr)); + + error = sobind(s->l2so, (struct sockaddr *) &l2sa, td); + if (error != 0) + goto bad; + + /* If "dst" is not NULL then initiate connect(), otherwise listen() */ + if (dst == NULL) { + s->flags = 0; + s->state = NG_BTSOCKET_RFCOMM_SESSION_LISTENING; + + error = solisten(s->l2so, 10, td); + if (error != 0) + goto bad; + } else { + s->flags = NG_BTSOCKET_RFCOMM_SESSION_INITIATOR; + s->state = NG_BTSOCKET_RFCOMM_SESSION_CONNECTING; + + l2sa.l2cap_len = sizeof(l2sa); + l2sa.l2cap_family = AF_BLUETOOTH; + l2sa.l2cap_psm = htole16(NG_L2CAP_PSM_RFCOMM); + bcopy(dst, &l2sa.l2cap_bdaddr, sizeof(l2sa.l2cap_bdaddr)); + + error = soconnect(s->l2so, (struct sockaddr *) &l2sa, td); + if (error != 0) + goto bad; + } + +done: + LIST_INSERT_HEAD(&ng_btsocket_rfcomm_sessions, s, next); + *sp = s; + + mtx_unlock(&s->session_mtx); + + return (0); + +bad: + mtx_unlock(&s->session_mtx); + + /* Return L2CAP socket back to its original state */ + l2so->so_upcallarg = NULL; + l2so->so_upcall = NULL; + SOCKBUF_LOCK(&l2so->so_rcv); + l2so->so_rcv.sb_flags &= ~SB_UPCALL; + SOCKBUF_UNLOCK(&l2so->so_rcv); + SOCKBUF_LOCK(&l2so->so_snd); + l2so->so_snd.sb_flags &= ~SB_UPCALL; + SOCKBUF_UNLOCK(&l2so->so_snd); + l2so->so_state &= ~SS_NBIO; + + mtx_destroy(&s->session_mtx); + bzero(s, sizeof(*s)); + FREE(s, M_NETGRAPH_BTSOCKET_RFCOMM); + + return (error); +} /* ng_btsocket_rfcomm_session_create */ + +/* + * Process accept() on RFCOMM session + * XXX FIXME locking for "l2so"? + */ + +static int +ng_btsocket_rfcomm_session_accept(ng_btsocket_rfcomm_session_p s0) +{ + struct socket *l2so = NULL; + struct sockaddr_l2cap *l2sa = NULL; + ng_btsocket_l2cap_pcb_t *l2pcb = NULL; + ng_btsocket_rfcomm_session_p s = NULL; + int error = 0; + + mtx_assert(&ng_btsocket_rfcomm_sessions_mtx, MA_OWNED); + mtx_assert(&s0->session_mtx, MA_OWNED); + + /* Check if there is a complete L2CAP connection in the queue */ + if ((error = s0->l2so->so_error) != 0) { + NG_BTSOCKET_RFCOMM_ERR( +"%s: Could not accept connection on L2CAP socket, error=%d\n", __func__, error); + s0->l2so->so_error = 0; + + return (error); + } + + ACCEPT_LOCK(); + if (TAILQ_EMPTY(&s0->l2so->so_comp)) { + ACCEPT_UNLOCK(); + if (s0->l2so->so_rcv.sb_state & SBS_CANTRCVMORE) + return (ECONNABORTED); + return (EWOULDBLOCK); + } + + /* Accept incoming L2CAP connection */ + l2so = TAILQ_FIRST(&s0->l2so->so_comp); + if (l2so == NULL) + panic("%s: l2so == NULL\n", __func__); + + TAILQ_REMOVE(&s0->l2so->so_comp, l2so, so_list); + s0->l2so->so_qlen --; + l2so->so_qstate &= ~SQ_COMP; + l2so->so_head = NULL; + SOCK_LOCK(l2so); + soref(l2so); + l2so->so_state |= SS_NBIO; + SOCK_UNLOCK(l2so); + ACCEPT_UNLOCK(); + + error = soaccept(l2so, (struct sockaddr **) &l2sa); + if (error != 0) { + NG_BTSOCKET_RFCOMM_ERR( +"%s: soaccept() on L2CAP socket failed, error=%d\n", __func__, error); + soclose(l2so); + + return (error); + } + + /* + * Check if there is already active RFCOMM session between two devices. + * If so then close L2CAP connection. We only support one RFCOMM session + * between each pair of devices. Note that here we assume session in any + * state. The session even could be in the middle of disconnecting. + */ + + l2pcb = so2l2cap_pcb(l2so); + s = ng_btsocket_rfcomm_session_by_addr(&l2pcb->src, &l2pcb->dst); + if (s == NULL) { + /* Create a new RFCOMM session */ + error = ng_btsocket_rfcomm_session_create(&s, l2so, NULL, NULL, + curthread /* XXX */); + if (error == 0) { + mtx_lock(&s->session_mtx); + + s->flags = 0; + s->state = NG_BTSOCKET_RFCOMM_SESSION_CONNECTED; + + /* + * Adjust MTU on incomming connection. Reserve 5 bytes: + * RFCOMM frame header, one extra byte for length and + * one extra byte for credits. + */ + + s->mtu = min(l2pcb->imtu, l2pcb->omtu) - + sizeof(struct rfcomm_frame_hdr) - 1 - 1; + + mtx_unlock(&s->session_mtx); + } else { + NG_BTSOCKET_RFCOMM_ALERT( +"%s: Failed to create new RFCOMM session, error=%d\n", __func__, error); + + soclose(l2so); + } + } else { + NG_BTSOCKET_RFCOMM_WARN( +"%s: Rejecting duplicating RFCOMM session between src=%x:%x:%x:%x:%x:%x and " \ +"dst=%x:%x:%x:%x:%x:%x, state=%d, flags=%#x\n", __func__, + l2pcb->src.b[5], l2pcb->src.b[4], l2pcb->src.b[3], + l2pcb->src.b[2], l2pcb->src.b[1], l2pcb->src.b[0], + l2pcb->dst.b[5], l2pcb->dst.b[4], l2pcb->dst.b[3], + l2pcb->dst.b[2], l2pcb->dst.b[1], l2pcb->dst.b[0], + s->state, s->flags); + + error = EBUSY; + soclose(l2so); + } + + return (error); +} /* ng_btsocket_rfcomm_session_accept */ + +/* + * Process connect() on RFCOMM session + * XXX FIXME locking for "l2so"? + */ + +static int +ng_btsocket_rfcomm_session_connect(ng_btsocket_rfcomm_session_p s) +{ + ng_btsocket_l2cap_pcb_p l2pcb = so2l2cap_pcb(s->l2so); + int error; + + mtx_assert(&s->session_mtx, MA_OWNED); + + /* First check if connection has failed */ + if ((error = s->l2so->so_error) != 0) { + s->l2so->so_error = 0; + + NG_BTSOCKET_RFCOMM_ERR( +"%s: Could not connect RFCOMM session, error=%d, state=%d, flags=%#x\n", + __func__, error, s->state, s->flags); + + return (error); + } + + /* Is connection still in progress? */ + if (s->l2so->so_state & SS_ISCONNECTING) + return (0); + + /* + * If we got here then we are connected. Send SABM on DLCI 0 to + * open multiplexor channel. + */ + + if (error == 0) { + s->state = NG_BTSOCKET_RFCOMM_SESSION_CONNECTED; + + /* + * Adjust MTU on outgoing connection. Reserve 5 bytes: RFCOMM + * frame header, one extra byte for length and one extra byte + * for credits. + */ + + s->mtu = min(l2pcb->imtu, l2pcb->omtu) - + sizeof(struct rfcomm_frame_hdr) - 1 - 1; + + error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_SABM,0); + if (error == 0) + error = ng_btsocket_rfcomm_task_wakeup(); + } + + return (error); +}/* ng_btsocket_rfcomm_session_connect */ + +/* + * Receive data on RFCOMM session + * XXX FIXME locking for "l2so"? + */ + +static int +ng_btsocket_rfcomm_session_receive(ng_btsocket_rfcomm_session_p s) +{ + struct mbuf *m = NULL; + struct uio uio; + int more, flags, error; + + mtx_assert(&s->session_mtx, MA_OWNED); + + /* Can we read from the L2CAP socket? */ + if (!soreadable(s->l2so)) + return (0); + + /* First check for error on L2CAP socket */ + if ((error = s->l2so->so_error) != 0) { + s->l2so->so_error = 0; + + NG_BTSOCKET_RFCOMM_ERR( +"%s: Could not receive data from L2CAP socket, error=%d, state=%d, flags=%#x\n", + __func__, error, s->state, s->flags); + + return (error); + } + + /* + * Read all packets from the L2CAP socket. + * XXX FIXME/VERIFY is that correct? For now use m->m_nextpkt as + * indication that there is more packets on the socket's buffer. + * Also what should we use in uio.uio_resid? + * May be s->mtu + sizeof(struct rfcomm_frame_hdr) + 1 + 1? + */ + + for (more = 1; more; ) { + /* Try to get next packet from socket */ + bzero(&uio, sizeof(uio)); +/* uio.uio_td = NULL; */ + uio.uio_resid = 1000000000; + flags = MSG_DONTWAIT; + + m = NULL; + error = soreceive(s->l2so, NULL, &uio, &m, + (struct mbuf **) NULL, &flags); + if (error != 0) { + if (error == EWOULDBLOCK) + return (0); /* XXX can happen? */ + + NG_BTSOCKET_RFCOMM_ERR( +"%s: Could not receive data from L2CAP socket, error=%d\n", __func__, error); + + return (error); + } + + more = (m->m_nextpkt != NULL); + m->m_nextpkt = NULL; + + ng_btsocket_rfcomm_receive_frame(s, m); + } + + return (0); +} /* ng_btsocket_rfcomm_session_receive */ + +/* + * Send data on RFCOMM session + * XXX FIXME locking for "l2so"? + */ + +static int +ng_btsocket_rfcomm_session_send(ng_btsocket_rfcomm_session_p s) +{ + struct mbuf *m = NULL; + int error; + + mtx_assert(&s->session_mtx, MA_OWNED); + + /* Send as much as we can from the session queue */ + while (sowriteable(s->l2so)) { + /* Check if socket still OK */ + if ((error = s->l2so->so_error) != 0) { + s->l2so->so_error = 0; + + NG_BTSOCKET_RFCOMM_ERR( +"%s: Detected error=%d on L2CAP socket, state=%d, flags=%#x\n", + __func__, error, s->state, s->flags); + + return (error); + } + + NG_BT_MBUFQ_DEQUEUE(&s->outq, m); + if (m == NULL) + return (0); /* we are done */ + + /* Call send function on the L2CAP socket */ + error = (*s->l2so->so_proto->pr_usrreqs->pru_send)(s->l2so, + 0, m, NULL, NULL, curthread /* XXX */); + if (error != 0) { + NG_BTSOCKET_RFCOMM_ERR( +"%s: Could not send data to L2CAP socket, error=%d\n", __func__, error); + + return (error); + } + } + + return (0); +} /* ng_btsocket_rfcomm_session_send */ + +/* + * Close and disconnect all DLCs for the given session. Caller must hold + * s->sesson_mtx. Will wakeup session. + */ + +static void +ng_btsocket_rfcomm_session_clean(ng_btsocket_rfcomm_session_p s) +{ + ng_btsocket_rfcomm_pcb_p pcb = NULL, pcb_next = NULL; + int error; + + mtx_assert(&s->session_mtx, MA_OWNED); + + /* + * Note: cannot use LIST_FOREACH because ng_btsocket_rfcomm_pcb_kill + * will unlink DLC from the session + */ + + for (pcb = LIST_FIRST(&s->dlcs); pcb != NULL; ) { + mtx_lock(&pcb->pcb_mtx); + pcb_next = LIST_NEXT(pcb, session_next); + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Disconnecting dlci=%d, state=%d, flags=%#x\n", + __func__, pcb->dlci, pcb->state, pcb->flags); + + if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_CONNECTED) + error = ECONNRESET; + else + error = ECONNREFUSED; + + ng_btsocket_rfcomm_pcb_kill(pcb, error); + + mtx_unlock(&pcb->pcb_mtx); + pcb = pcb_next; + } +} /* ng_btsocket_rfcomm_session_clean */ + +/* + * Process all DLCs on the session. Caller MUST hold s->session_mtx. + */ + +static void +ng_btsocket_rfcomm_session_process_pcb(ng_btsocket_rfcomm_session_p s) +{ + ng_btsocket_rfcomm_pcb_p pcb = NULL, pcb_next = NULL; + int error; + + mtx_assert(&s->session_mtx, MA_OWNED); + + /* + * Note: cannot use LIST_FOREACH because ng_btsocket_rfcomm_pcb_kill + * will unlink DLC from the session + */ + + for (pcb = LIST_FIRST(&s->dlcs); pcb != NULL; ) { + mtx_lock(&pcb->pcb_mtx); + pcb_next = LIST_NEXT(pcb, session_next); + + switch (pcb->state) { + + /* + * If DLC in W4_CONNECT state then we should check for both + * timeout and detach. + */ + + case NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT: + if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_DETACHED) + ng_btsocket_rfcomm_pcb_kill(pcb, 0); + else if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT) + ng_btsocket_rfcomm_pcb_kill(pcb, ETIMEDOUT); + break; + + /* + * If DLC in CONFIGURING or CONNECTING state then we only + * should check for timeout. If detach() was called then + * DLC will be moved into DISCONNECTING state. + */ + + case NG_BTSOCKET_RFCOMM_DLC_CONFIGURING: + case NG_BTSOCKET_RFCOMM_DLC_CONNECTING: + if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT) + ng_btsocket_rfcomm_pcb_kill(pcb, ETIMEDOUT); + break; + + /* + * If DLC in CONNECTED state then we need to send data (if any) + * from the socket's send queue. Note that we will send data + * from either all sockets or none. This may overload session's + * outgoing queue (but we do not check for that). + * + * XXX FIXME need scheduler for RFCOMM sockets + */ + + case NG_BTSOCKET_RFCOMM_DLC_CONNECTED: + error = ng_btsocket_rfcomm_pcb_send(pcb, ALOT); + if (error != 0) + ng_btsocket_rfcomm_pcb_kill(pcb, error); + break; + + /* + * If DLC in DISCONNECTING state then we must send DISC frame. + * Note that if DLC has timeout set then we do not need to + * resend DISC frame. + * + * XXX FIXME need to drain all data from the socket's queue + * if LINGER option was set + */ + + case NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING: + if (!(pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO)) { + error = ng_btsocket_rfcomm_send_command( + pcb->session, RFCOMM_FRAME_DISC, + pcb->dlci); + if (error == 0) + ng_btsocket_rfcomm_timeout(pcb); + else + ng_btsocket_rfcomm_pcb_kill(pcb, error); + } else if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT) + ng_btsocket_rfcomm_pcb_kill(pcb, ETIMEDOUT); + break; + +/* case NG_BTSOCKET_RFCOMM_DLC_CLOSED: */ + default: + panic("%s: Invalid DLC state=%d, flags=%#x\n", + __func__, pcb->state, pcb->flags); + break; + } + + mtx_unlock(&pcb->pcb_mtx); + pcb = pcb_next; + } +} /* ng_btsocket_rfcomm_session_process_pcb */ + +/* + * Find RFCOMM session between "src" and "dst". + * Caller MUST hold ng_btsocket_rfcomm_sessions_mtx. + */ + +static ng_btsocket_rfcomm_session_p +ng_btsocket_rfcomm_session_by_addr(bdaddr_p src, bdaddr_p dst) +{ + ng_btsocket_rfcomm_session_p s = NULL; + ng_btsocket_l2cap_pcb_p l2pcb = NULL; + int any_src; + + mtx_assert(&ng_btsocket_rfcomm_sessions_mtx, MA_OWNED); + + any_src = (bcmp(src, NG_HCI_BDADDR_ANY, sizeof(*src)) == 0); + + LIST_FOREACH(s, &ng_btsocket_rfcomm_sessions, next) { + l2pcb = so2l2cap_pcb(s->l2so); + + if ((any_src || bcmp(&l2pcb->src, src, sizeof(*src)) == 0) && + bcmp(&l2pcb->dst, dst, sizeof(*dst)) == 0) + break; + } + + return (s); +} /* ng_btsocket_rfcomm_session_by_addr */ + +/***************************************************************************** + ***************************************************************************** + ** RFCOMM + ***************************************************************************** + *****************************************************************************/ + +/* + * Process incoming RFCOMM frame. Caller must hold s->session_mtx. + * XXX FIXME check frame length + */ + +static int +ng_btsocket_rfcomm_receive_frame(ng_btsocket_rfcomm_session_p s, + struct mbuf *m0) +{ + struct rfcomm_frame_hdr *hdr = NULL; + struct mbuf *m = NULL; + u_int16_t length; + u_int8_t dlci, type; + int error = 0; + + mtx_assert(&s->session_mtx, MA_OWNED); + + /* Pullup as much as we can into first mbuf (for direct access) */ + length = min(m0->m_pkthdr.len, MHLEN); + if (m0->m_len < length) { + if ((m0 = m_pullup(m0, length)) == NULL) { + NG_BTSOCKET_RFCOMM_ALERT( +"%s: m_pullup(%d) failed\n", __func__, length); + + return (ENOBUFS); + } + } + + hdr = mtod(m0, struct rfcomm_frame_hdr *); + dlci = RFCOMM_DLCI(hdr->address); + type = RFCOMM_TYPE(hdr->control); + + /* Test EA bit in length. If not set then we have 2 bytes of length */ + if (!RFCOMM_EA(hdr->length)) { + bcopy(&hdr->length, &length, sizeof(length)); + length = le16toh(length) >> 1; + m_adj(m0, sizeof(*hdr) + 1); + } else { + length = hdr->length >> 1; + m_adj(m0, sizeof(*hdr)); + } + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Got frame type=%#x, dlci=%d, length=%d, cr=%d, pf=%d, len=%d\n", + __func__, type, dlci, length, RFCOMM_CR(hdr->address), + RFCOMM_PF(hdr->control), m0->m_pkthdr.len); + + /* + * Get FCS (the last byte in the frame) + * XXX this will not work if mbuf chain ends with empty mbuf. + * XXX let's hope it never happens :) + */ + + for (m = m0; m->m_next != NULL; m = m->m_next) + ; + if (m->m_len <= 0) + panic("%s: Empty mbuf at the end of the chain, len=%d\n", + __func__, m->m_len); + + /* + * Check FCS. We only need to calculate FCS on first 2 or 3 bytes + * and already m_pullup'ed mbuf chain, so it should be safe. + */ + + if (ng_btsocket_rfcomm_check_fcs((u_int8_t *) hdr, type, m->m_data[m->m_len - 1])) { + NG_BTSOCKET_RFCOMM_ERR( +"%s: Invalid RFCOMM packet. Bad checksum\n", __func__); + NG_FREE_M(m0); + + return (EINVAL); + } + + m_adj(m0, -1); /* Trim FCS byte */ + + /* + * Process RFCOMM frame. + * + * From TS 07.10 spec + * + * "... In the case where a SABM or DISC command with the P bit set + * to 0 is received then the received frame shall be discarded..." + * + * "... If a unsolicited DM response is received then the frame shall + * be processed irrespective of the P/F setting... " + * + * "... The station may transmit response frames with the F bit set + * to 0 at any opportunity on an asynchronous basis. However, in the + * case where a UA response is received with the F bit set to 0 then + * the received frame shall be discarded..." + * + * From Bluetooth spec + * + * "... When credit based flow control is being used, the meaning of + * the P/F bit in the control field of the RFCOMM header is redefined + * for UIH frames..." + */ + + switch (type) { + case RFCOMM_FRAME_SABM: + if (RFCOMM_PF(hdr->control)) + error = ng_btsocket_rfcomm_receive_sabm(s, dlci); + break; + + case RFCOMM_FRAME_DISC: + if (RFCOMM_PF(hdr->control)) + error = ng_btsocket_rfcomm_receive_disc(s, dlci); + break; + + case RFCOMM_FRAME_UA: + if (RFCOMM_PF(hdr->control)) + error = ng_btsocket_rfcomm_receive_ua(s, dlci); + break; + + case RFCOMM_FRAME_DM: + error = ng_btsocket_rfcomm_receive_dm(s, dlci); + break; + + case RFCOMM_FRAME_UIH: + if (dlci == 0) + error = ng_btsocket_rfcomm_receive_mcc(s, m0); + else + error = ng_btsocket_rfcomm_receive_uih(s, dlci, + RFCOMM_PF(hdr->control), m0); + + return (error); + /* NOT REACHED */ + + default: + NG_BTSOCKET_RFCOMM_ERR( +"%s: Invalid RFCOMM packet. Unknown type=%#x\n", __func__, type); + error = EINVAL; + break; + } + + NG_FREE_M(m0); + + return (error); +} /* ng_btsocket_rfcomm_receive_frame */ + +/* + * Process RFCOMM SABM frame + */ + +static int +ng_btsocket_rfcomm_receive_sabm(ng_btsocket_rfcomm_session_p s, int dlci) +{ + ng_btsocket_rfcomm_pcb_p pcb = NULL; + int error = 0; + + mtx_assert(&s->session_mtx, MA_OWNED); + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Got SABM, session state=%d, flags=%#x, mtu=%d, dlci=%d\n", + __func__, s->state, s->flags, s->mtu, dlci); + + /* DLCI == 0 means open multiplexor channel */ + if (dlci == 0) { + switch (s->state) { + case NG_BTSOCKET_RFCOMM_SESSION_CONNECTED: + case NG_BTSOCKET_RFCOMM_SESSION_OPEN: + error = ng_btsocket_rfcomm_send_command(s, + RFCOMM_FRAME_UA, dlci); + if (error == 0) { + s->state = NG_BTSOCKET_RFCOMM_SESSION_OPEN; + ng_btsocket_rfcomm_connect_cfm(s); + } else { + s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; + ng_btsocket_rfcomm_session_clean(s); + } + break; + + default: + NG_BTSOCKET_RFCOMM_WARN( +"%s: Got SABM for session in invalid state state=%d, flags=%#x\n", + __func__, s->state, s->flags); + error = EINVAL; + break; + } + + return (error); + } + + /* Make sure multiplexor channel is open */ + if (s->state != NG_BTSOCKET_RFCOMM_SESSION_OPEN) { + NG_BTSOCKET_RFCOMM_ERR( +"%s: Got SABM for dlci=%d with mulitplexor channel closed, state=%d, " \ +"flags=%#x\n", __func__, dlci, s->state, s->flags); + + return (EINVAL); + } + + /* + * Check if we have this DLCI. This might happen when remote + * peer uses PN command before actual open (SABM) happens. + */ + + pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, dlci); + if (pcb != NULL) { + mtx_lock(&pcb->pcb_mtx); + + if (pcb->state != NG_BTSOCKET_RFCOMM_DLC_CONNECTING) { + NG_BTSOCKET_RFCOMM_ERR( +"%s: Got SABM for dlci=%d in invalid state=%d, flags=%#x\n", + __func__, dlci, pcb->state, pcb->flags); + mtx_unlock(&pcb->pcb_mtx); + + return (ENOENT); + } + + ng_btsocket_rfcomm_untimeout(pcb); + + error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_UA,dlci); + if (error == 0) + error = ng_btsocket_rfcomm_send_msc(pcb); + + if (error == 0) { + pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONNECTED; + soisconnected(pcb->so); + } else + ng_btsocket_rfcomm_pcb_kill(pcb, error); + + mtx_unlock(&pcb->pcb_mtx); + + return (error); + } + + /* + * We do not have requested DLCI, so it must be an incoming connection + * with default parameters. Try to accept it. + */ + + pcb = ng_btsocket_rfcomm_connect_ind(s, RFCOMM_SRVCHANNEL(dlci)); + if (pcb != NULL) { + mtx_lock(&pcb->pcb_mtx); + + pcb->dlci = dlci; + + error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_UA,dlci); + if (error == 0) + error = ng_btsocket_rfcomm_send_msc(pcb); + + if (error == 0) { + pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONNECTED; + soisconnected(pcb->so); + } else + ng_btsocket_rfcomm_pcb_kill(pcb, error); + + mtx_unlock(&pcb->pcb_mtx); + } else + /* Nobody is listen()ing on the requested DLCI */ + error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_DM,dlci); + + return (error); +} /* ng_btsocket_rfcomm_receive_sabm */ + +/* + * Process RFCOMM DISC frame + */ + +static int +ng_btsocket_rfcomm_receive_disc(ng_btsocket_rfcomm_session_p s, int dlci) +{ + ng_btsocket_rfcomm_pcb_p pcb = NULL; + int error = 0; + + mtx_assert(&s->session_mtx, MA_OWNED); + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Got DISC, session state=%d, flags=%#x, mtu=%d, dlci=%d\n", + __func__, s->state, s->flags, s->mtu, dlci); + + /* DLCI == 0 means close multiplexor channel */ + if (dlci == 0) { + /* XXX FIXME assume that remote side will close the socket */ + error = ng_btsocket_rfcomm_send_command(s, RFCOMM_FRAME_UA, 0); + if (error == 0) { + if (s->state == NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING) + s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; /* XXX */ + else + s->state = NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING; + } else + s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; /* XXX */ + + ng_btsocket_rfcomm_session_clean(s); + } else { + pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, dlci); + if (pcb != NULL) { + int err; + + mtx_lock(&pcb->pcb_mtx); + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Got DISC for dlci=%d, state=%d, flags=%#x\n", + __func__, dlci, pcb->state, pcb->flags); + + error = ng_btsocket_rfcomm_send_command(s, + RFCOMM_FRAME_UA, dlci); + + if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_CONNECTED) + err = 0; + else + err = ECONNREFUSED; + + ng_btsocket_rfcomm_pcb_kill(pcb, err); + + mtx_unlock(&pcb->pcb_mtx); + } else { + NG_BTSOCKET_RFCOMM_WARN( +"%s: Got DISC for non-existing dlci=%d\n", __func__, dlci); + + error = ng_btsocket_rfcomm_send_command(s, + RFCOMM_FRAME_DM, dlci); + } + } + + return (error); +} /* ng_btsocket_rfcomm_receive_disc */ + +/* + * Process RFCOMM UA frame + */ + +static int +ng_btsocket_rfcomm_receive_ua(ng_btsocket_rfcomm_session_p s, int dlci) +{ + ng_btsocket_rfcomm_pcb_p pcb = NULL; + int error = 0; + + mtx_assert(&s->session_mtx, MA_OWNED); + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Got UA, session state=%d, flags=%#x, mtu=%d, dlci=%d\n", + __func__, s->state, s->flags, s->mtu, dlci); + + /* dlci == 0 means multiplexor channel */ + if (dlci == 0) { + switch (s->state) { + case NG_BTSOCKET_RFCOMM_SESSION_CONNECTED: + s->state = NG_BTSOCKET_RFCOMM_SESSION_OPEN; + ng_btsocket_rfcomm_connect_cfm(s); + break; + + case NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING: + s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; + ng_btsocket_rfcomm_session_clean(s); + break; + + default: + NG_BTSOCKET_RFCOMM_WARN( +"%s: Got UA for session in invalid state=%d(%d), flags=%#x, mtu=%d\n", + __func__, s->state, INITIATOR(s), s->flags, + s->mtu); + error = ENOENT; + break; + } + + return (error); + } + + /* Check if we have this DLCI */ + pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, dlci); + if (pcb != NULL) { + mtx_lock(&pcb->pcb_mtx); + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Got UA for dlci=%d, state=%d, flags=%#x\n", + __func__, dlci, pcb->state, pcb->flags); + + switch (pcb->state) { + case NG_BTSOCKET_RFCOMM_DLC_CONNECTING: + ng_btsocket_rfcomm_untimeout(pcb); + + error = ng_btsocket_rfcomm_send_msc(pcb); + if (error == 0) { + pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONNECTED; + soisconnected(pcb->so); + } + break; + + case NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING: + ng_btsocket_rfcomm_pcb_kill(pcb, 0); + break; + + default: + NG_BTSOCKET_RFCOMM_WARN( +"%s: Got UA for dlci=%d in invalid state=%d, flags=%#x\n", + __func__, dlci, pcb->state, pcb->flags); + error = ENOENT; + break; + } + + mtx_unlock(&pcb->pcb_mtx); + } else { + NG_BTSOCKET_RFCOMM_WARN( +"%s: Got UA for non-existing dlci=%d\n", __func__, dlci); + + error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_DM,dlci); + } + + return (error); +} /* ng_btsocket_rfcomm_receive_ua */ + +/* + * Process RFCOMM DM frame + */ + +static int +ng_btsocket_rfcomm_receive_dm(ng_btsocket_rfcomm_session_p s, int dlci) +{ + ng_btsocket_rfcomm_pcb_p pcb = NULL; + int error; + + mtx_assert(&s->session_mtx, MA_OWNED); + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Got DM, session state=%d, flags=%#x, mtu=%d, dlci=%d\n", + __func__, s->state, s->flags, s->mtu, dlci); + + /* DLCI == 0 means multiplexor channel */ + if (dlci == 0) { + /* Disconnect all dlc's on the session */ + s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; + ng_btsocket_rfcomm_session_clean(s); + } else { + pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, dlci); + if (pcb != NULL) { + mtx_lock(&pcb->pcb_mtx); + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Got DM for dlci=%d, state=%d, flags=%#x\n", + __func__, dlci, pcb->state, pcb->flags); + + if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_CONNECTED) + error = ECONNRESET; + else + error = ECONNREFUSED; + + ng_btsocket_rfcomm_pcb_kill(pcb, error); + + mtx_unlock(&pcb->pcb_mtx); + } else + NG_BTSOCKET_RFCOMM_WARN( +"%s: Got DM for non-existing dlci=%d\n", __func__, dlci); + } + + return (0); +} /* ng_btsocket_rfcomm_receive_dm */ + +/* + * Process RFCOMM UIH frame (data) + */ + +static int +ng_btsocket_rfcomm_receive_uih(ng_btsocket_rfcomm_session_p s, int dlci, + int pf, struct mbuf *m0) +{ + ng_btsocket_rfcomm_pcb_p pcb = NULL; + int error = 0; + + mtx_assert(&s->session_mtx, MA_OWNED); + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Got UIH, session state=%d, flags=%#x, mtu=%d, dlci=%d, pf=%d, len=%d\n", + __func__, s->state, s->flags, s->mtu, dlci, pf, + m0->m_pkthdr.len); + + /* XXX should we do it here? Check for session flow control */ + if (s->flags & NG_BTSOCKET_RFCOMM_SESSION_LFC) { + NG_BTSOCKET_RFCOMM_WARN( +"%s: Got UIH with session flow control asserted, state=%d, flags=%#x\n", + __func__, s->state, s->flags); + goto drop; + } + + /* Check if we have this dlci */ + pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, dlci); + if (pcb == NULL) { + NG_BTSOCKET_RFCOMM_WARN( +"%s: Got UIH for non-existing dlci=%d\n", __func__, dlci); + error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_DM,dlci); + goto drop; + } + + mtx_lock(&pcb->pcb_mtx); + + /* Check dlci state */ + if (pcb->state != NG_BTSOCKET_RFCOMM_DLC_CONNECTED) { + NG_BTSOCKET_RFCOMM_WARN( +"%s: Got UIH for dlci=%d in invalid state=%d, flags=%#x\n", + __func__, dlci, pcb->state, pcb->flags); + error = EINVAL; + goto drop1; + } + + /* Check dlci flow control */ + if (((pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) && pcb->rx_cred <= 0) || + (pcb->lmodem & RFCOMM_MODEM_FC)) { + NG_BTSOCKET_RFCOMM_ERR( +"%s: Got UIH for dlci=%d with asserted flow control, state=%d, " \ +"flags=%#x, rx_cred=%d, lmodem=%#x\n", + __func__, dlci, pcb->state, pcb->flags, + pcb->rx_cred, pcb->lmodem); + goto drop1; + } + + /* Did we get any credits? */ + if ((pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) && pf) { + NG_BTSOCKET_RFCOMM_INFO( +"%s: Got %d more credits for dlci=%d, state=%d, flags=%#x, " \ +"rx_cred=%d, tx_cred=%d\n", + __func__, *mtod(m0, u_int8_t *), dlci, pcb->state, + pcb->flags, pcb->rx_cred, pcb->tx_cred); + + pcb->tx_cred += *mtod(m0, u_int8_t *); + m_adj(m0, 1); + + /* Send more from the DLC. XXX check for errors? */ + ng_btsocket_rfcomm_pcb_send(pcb, ALOT); + } + + /* OK the of the rest of the mbuf is the data */ + if (m0->m_pkthdr.len > 0) { + /* If we are using credit flow control decrease rx_cred here */ + if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) { + /* Give remote peer more credits (if needed) */ + if (-- pcb->rx_cred <= RFCOMM_MAX_CREDITS / 2) + ng_btsocket_rfcomm_send_credits(pcb); + else + NG_BTSOCKET_RFCOMM_INFO( +"%s: Remote side still has credits, dlci=%d, state=%d, flags=%#x, " \ +"rx_cred=%d, tx_cred=%d\n", __func__, dlci, pcb->state, pcb->flags, + pcb->rx_cred, pcb->tx_cred); + } + + /* Check packet against mtu on dlci */ + if (m0->m_pkthdr.len > pcb->mtu) { + NG_BTSOCKET_RFCOMM_ERR( +"%s: Got oversized UIH for dlci=%d, state=%d, flags=%#x, mtu=%d, len=%d\n", + __func__, dlci, pcb->state, pcb->flags, + pcb->mtu, m0->m_pkthdr.len); + + error = EMSGSIZE; + } else if (m0->m_pkthdr.len > sbspace(&pcb->so->so_rcv)) { + + /* + * This is really bad. Receive queue on socket does + * not have enough space for the packet. We do not + * have any other choice but drop the packet. + */ + + NG_BTSOCKET_RFCOMM_ERR( +"%s: Not enough space in socket receive queue. Dropping UIH for dlci=%d, " \ +"state=%d, flags=%#x, len=%d, space=%ld\n", + __func__, dlci, pcb->state, pcb->flags, + m0->m_pkthdr.len, sbspace(&pcb->so->so_rcv)); + + error = ENOBUFS; + } else { + /* Append packet to the socket receive queue */ + sbappend(&pcb->so->so_rcv, m0); + m0 = NULL; + + sorwakeup(pcb->so); + } + } +drop1: + mtx_unlock(&pcb->pcb_mtx); +drop: + NG_FREE_M(m0); /* checks for != NULL */ + + return (error); +} /* ng_btsocket_rfcomm_receive_uih */ + +/* + * Process RFCOMM MCC command (Multiplexor) + * + * From TS 07.10 spec + * + * "5.4.3.1 Information Data + * + * ...The frames (UIH) sent by the initiating station have the C/R bit set + * to 1 and those sent by the responding station have the C/R bit set to 0..." + * + * "5.4.6.2 Operating procedures + * + * Messages always exist in pairs; a command message and a corresponding + * response message. If the C/R bit is set to 1 the message is a command, + * if it is set to 0 the message is a response... + * + * ... + * + * NOTE: Notice that when UIH frames are used to convey information on DLCI 0 + * there are at least two different fields that contain a C/R bit, and the + * bits are set of different form. The C/R bit in the Type field shall be set + * as it is stated above, while the C/R bit in the Address field (see subclause + * 5.2.1.2) shall be set as it is described in subclause 5.4.3.1." + */ + +static int +ng_btsocket_rfcomm_receive_mcc(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) +{ + struct rfcomm_mcc_hdr *hdr = NULL; + u_int8_t cr, type, length; + + mtx_assert(&s->session_mtx, MA_OWNED); + + /* + * We can access data directly in the first mbuf, because we have + * m_pullup()'ed mbuf chain in ng_btsocket_rfcomm_receive_frame(). + * All MCC commands should fit into single mbuf (except probably TEST). + */ + + hdr = mtod(m0, struct rfcomm_mcc_hdr *); + cr = RFCOMM_CR(hdr->type); + type = RFCOMM_MCC_TYPE(hdr->type); + length = RFCOMM_MCC_LENGTH(hdr->length); + + /* Check MCC frame length */ + if (sizeof(*hdr) + length != m0->m_pkthdr.len) { + NG_BTSOCKET_RFCOMM_ERR( +"%s: Invalid MCC frame length=%d, len=%d\n", + __func__, length, m0->m_pkthdr.len); + NG_FREE_M(m0); + + return (EMSGSIZE); + } + + switch (type) { + case RFCOMM_MCC_TEST: + return (ng_btsocket_rfcomm_receive_test(s, m0)); + /* NOT REACHED */ + + case RFCOMM_MCC_FCON: + case RFCOMM_MCC_FCOFF: + return (ng_btsocket_rfcomm_receive_fc(s, m0)); + /* NOT REACHED */ + + case RFCOMM_MCC_MSC: + return (ng_btsocket_rfcomm_receive_msc(s, m0)); + /* NOT REACHED */ + + case RFCOMM_MCC_RPN: + return (ng_btsocket_rfcomm_receive_rpn(s, m0)); + /* NOT REACHED */ + + case RFCOMM_MCC_RLS: + return (ng_btsocket_rfcomm_receive_rls(s, m0)); + /* NOT REACHED */ + + case RFCOMM_MCC_PN: + return (ng_btsocket_rfcomm_receive_pn(s, m0)); + /* NOT REACHED */ + + case RFCOMM_MCC_NSC: + NG_BTSOCKET_RFCOMM_ERR( +"%s: Got MCC NSC, type=%#x, cr=%d, length=%d, session state=%d, flags=%#x, " \ +"mtu=%d, len=%d\n", __func__, RFCOMM_MCC_TYPE(*((u_int8_t *)(hdr + 1))), cr, + length, s->state, s->flags, s->mtu, m0->m_pkthdr.len); + NG_FREE_M(m0); + break; + + default: + NG_BTSOCKET_RFCOMM_ERR( +"%s: Got unknown MCC, type=%#x, cr=%d, length=%d, session state=%d, " \ +"flags=%#x, mtu=%d, len=%d\n", + __func__, type, cr, length, s->state, s->flags, + s->mtu, m0->m_pkthdr.len); + + /* Reuse mbuf to send NSC */ + hdr = mtod(m0, struct rfcomm_mcc_hdr *); + m0->m_pkthdr.len = m0->m_len = sizeof(*hdr); + + /* Create MCC NSC header */ + hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_NSC); + hdr->length = RFCOMM_MKLEN8(1); + + /* Put back MCC command type we did not like */ + m0->m_data[m0->m_len] = RFCOMM_MKMCC_TYPE(cr, type); + m0->m_pkthdr.len ++; + m0->m_len ++; + + /* Send UIH frame */ + return (ng_btsocket_rfcomm_send_uih(s, + RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0)); + /* NOT REACHED */ + } + + return (0); +} /* ng_btsocket_rfcomm_receive_mcc */ + +/* + * Receive RFCOMM TEST MCC command + */ + +static int +ng_btsocket_rfcomm_receive_test(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) +{ + struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr *); + int error = 0; + + mtx_assert(&s->session_mtx, MA_OWNED); + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Got MCC TEST, cr=%d, length=%d, session state=%d, flags=%#x, mtu=%d, " \ +"len=%d\n", __func__, RFCOMM_CR(hdr->type), RFCOMM_MCC_LENGTH(hdr->length), + s->state, s->flags, s->mtu, m0->m_pkthdr.len); + + if (RFCOMM_CR(hdr->type)) { + hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_TEST); + error = ng_btsocket_rfcomm_send_uih(s, + RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); + } else + NG_FREE_M(m0); /* XXX ignore response */ + + return (error); +} /* ng_btsocket_rfcomm_receive_test */ + +/* + * Receive RFCOMM FCON/FCOFF MCC command + */ + +static int +ng_btsocket_rfcomm_receive_fc(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) +{ + struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr *); + u_int8_t type = RFCOMM_MCC_TYPE(hdr->type); + int error = 0; + + mtx_assert(&s->session_mtx, MA_OWNED); + + /* + * Turn ON/OFF aggregate flow on the entire session. When remote peer + * asserted flow control no transmission shall occur except on dlci 0 + * (control channel). + */ + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Got MCC FC%s, cr=%d, length=%d, session state=%d, flags=%#x, mtu=%d, " \ +"len=%d\n", __func__, (type == RFCOMM_MCC_FCON)? "ON" : "OFF", + RFCOMM_CR(hdr->type), RFCOMM_MCC_LENGTH(hdr->length), + s->state, s->flags, s->mtu, m0->m_pkthdr.len); + + if (RFCOMM_CR(hdr->type)) { + if (type == RFCOMM_MCC_FCON) + s->flags &= ~NG_BTSOCKET_RFCOMM_SESSION_RFC; + else + s->flags |= NG_BTSOCKET_RFCOMM_SESSION_RFC; + + hdr->type = RFCOMM_MKMCC_TYPE(0, type); + error = ng_btsocket_rfcomm_send_uih(s, + RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); + } else + NG_FREE_M(m0); /* XXX ignore response */ + + return (error); +} /* ng_btsocket_rfcomm_receive_fc */ + +/* + * Receive RFCOMM MSC MCC command + */ + +static int +ng_btsocket_rfcomm_receive_msc(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) +{ + struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr*); + struct rfcomm_mcc_msc *msc = (struct rfcomm_mcc_msc *)(hdr+1); + ng_btsocket_rfcomm_pcb_t *pcb = NULL; + int error = 0; + + mtx_assert(&s->session_mtx, MA_OWNED); + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Got MCC MSC, dlci=%d, cr=%d, length=%d, session state=%d, flags=%#x, " \ +"mtu=%d, len=%d\n", + __func__, RFCOMM_DLCI(msc->address), RFCOMM_CR(hdr->type), + RFCOMM_MCC_LENGTH(hdr->length), s->state, s->flags, + s->mtu, m0->m_pkthdr.len); + + if (RFCOMM_CR(hdr->type)) { + pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, RFCOMM_DLCI(msc->address)); + if (pcb == NULL) { + NG_BTSOCKET_RFCOMM_WARN( +"%s: Got MSC command for non-existing dlci=%d\n", + __func__, RFCOMM_DLCI(msc->address)); + NG_FREE_M(m0); + + return (ENOENT); + } + + mtx_lock(&pcb->pcb_mtx); + + if (pcb->state != NG_BTSOCKET_RFCOMM_DLC_CONNECTING && + pcb->state != NG_BTSOCKET_RFCOMM_DLC_CONNECTED) { + NG_BTSOCKET_RFCOMM_WARN( +"%s: Got MSC on dlci=%d in invalid state=%d\n", + __func__, RFCOMM_DLCI(msc->address), + pcb->state); + + mtx_unlock(&pcb->pcb_mtx); + NG_FREE_M(m0); + + return (EINVAL); + } + + pcb->rmodem = msc->modem; /* Update remote port signals */ + + hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_MSC); + error = ng_btsocket_rfcomm_send_uih(s, + RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); + +#if 0 /* YYY */ + /* Send more data from DLC. XXX check for errors? */ + if (!(pcb->rmodem & RFCOMM_MODEM_FC) && + !(pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC)) + ng_btsocket_rfcomm_pcb_send(pcb, ALOT); +#endif /* YYY */ + + mtx_unlock(&pcb->pcb_mtx); + } else + NG_FREE_M(m0); /* XXX ignore response */ + + return (error); +} /* ng_btsocket_rfcomm_receive_msc */ + +/* + * Receive RFCOMM RPN MCC command + * XXX FIXME do we need htole16/le16toh for RPN param_mask? + */ + +static int +ng_btsocket_rfcomm_receive_rpn(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) +{ + struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr *); + struct rfcomm_mcc_rpn *rpn = (struct rfcomm_mcc_rpn *)(hdr + 1); + int error = 0; + u_int16_t param_mask; + u_int8_t bit_rate, data_bits, stop_bits, parity, + flow_control, xon_char, xoff_char; + + mtx_assert(&s->session_mtx, MA_OWNED); + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Got MCC RPN, dlci=%d, cr=%d, length=%d, session state=%d, flags=%#x, " \ +"mtu=%d, len=%d\n", + __func__, RFCOMM_DLCI(rpn->dlci), RFCOMM_CR(hdr->type), + RFCOMM_MCC_LENGTH(hdr->length), s->state, s->flags, + s->mtu, m0->m_pkthdr.len); + + if (RFCOMM_CR(hdr->type)) { + param_mask = RFCOMM_RPN_PM_ALL; + + if (RFCOMM_MCC_LENGTH(hdr->length) == 1) { + /* Request - return default setting */ + bit_rate = RFCOMM_RPN_BR_115200; + data_bits = RFCOMM_RPN_DATA_8; + stop_bits = RFCOMM_RPN_STOP_1; + parity = RFCOMM_RPN_PARITY_NONE; + flow_control = RFCOMM_RPN_FLOW_NONE; + xon_char = RFCOMM_RPN_XON_CHAR; + xoff_char = RFCOMM_RPN_XOFF_CHAR; + } else { + /* + * Ignore/accept bit_rate, 8 bits, 1 stop bit, no + * parity, no flow control lines, default XON/XOFF + * chars. + */ + + bit_rate = rpn->bit_rate; + rpn->param_mask = le16toh(rpn->param_mask); /* XXX */ + + data_bits = RFCOMM_RPN_DATA_BITS(rpn->line_settings); + if (rpn->param_mask & RFCOMM_RPN_PM_DATA && + data_bits != RFCOMM_RPN_DATA_8) { + data_bits = RFCOMM_RPN_DATA_8; + param_mask ^= RFCOMM_RPN_PM_DATA; + } + + stop_bits = RFCOMM_RPN_STOP_BITS(rpn->line_settings); + if (rpn->param_mask & RFCOMM_RPN_PM_STOP && + stop_bits != RFCOMM_RPN_STOP_1) { + stop_bits = RFCOMM_RPN_STOP_1; + param_mask ^= RFCOMM_RPN_PM_STOP; + } + + parity = RFCOMM_RPN_PARITY(rpn->line_settings); + if (rpn->param_mask & RFCOMM_RPN_PM_PARITY && + parity != RFCOMM_RPN_PARITY_NONE) { + parity = RFCOMM_RPN_PARITY_NONE; + param_mask ^= RFCOMM_RPN_PM_PARITY; + } + + flow_control = rpn->flow_control; + if (rpn->param_mask & RFCOMM_RPN_PM_FLOW && + flow_control != RFCOMM_RPN_FLOW_NONE) { + flow_control = RFCOMM_RPN_FLOW_NONE; + param_mask ^= RFCOMM_RPN_PM_FLOW; + } + + xon_char = rpn->xon_char; + if (rpn->param_mask & RFCOMM_RPN_PM_XON && + xon_char != RFCOMM_RPN_XON_CHAR) { + xon_char = RFCOMM_RPN_XON_CHAR; + param_mask ^= RFCOMM_RPN_PM_XON; + } + + xoff_char = rpn->xoff_char; + if (rpn->param_mask & RFCOMM_RPN_PM_XOFF && + xoff_char != RFCOMM_RPN_XOFF_CHAR) { + xoff_char = RFCOMM_RPN_XOFF_CHAR; + param_mask ^= RFCOMM_RPN_PM_XOFF; + } + } + + rpn->bit_rate = bit_rate; + rpn->line_settings = RFCOMM_MKRPN_LINE_SETTINGS(data_bits, + stop_bits, parity); + rpn->flow_control = flow_control; + rpn->xon_char = xon_char; + rpn->xoff_char = xoff_char; + rpn->param_mask = htole16(param_mask); /* XXX */ + + m0->m_pkthdr.len = m0->m_len = sizeof(*hdr) + sizeof(*rpn); + + hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_RPN); + error = ng_btsocket_rfcomm_send_uih(s, + RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); + } else + NG_FREE_M(m0); /* XXX ignore response */ + + return (error); +} /* ng_btsocket_rfcomm_receive_rpn */ + +/* + * Receive RFCOMM RLS MCC command + */ + +static int +ng_btsocket_rfcomm_receive_rls(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) +{ + struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr *); + struct rfcomm_mcc_rls *rls = (struct rfcomm_mcc_rls *)(hdr + 1); + int error = 0; + + mtx_assert(&s->session_mtx, MA_OWNED); + + /* + * XXX FIXME Do we have to do anything else here? Remote peer tries to + * tell us something about DLCI. Just report what we have received and + * return back received values as required by TS 07.10 spec. + */ + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Got MCC RLS, dlci=%d, status=%#x, cr=%d, length=%d, session state=%d, " \ +"flags=%#x, mtu=%d, len=%d\n", + __func__, RFCOMM_DLCI(rls->address), rls->status, + RFCOMM_CR(hdr->type), RFCOMM_MCC_LENGTH(hdr->length), + s->state, s->flags, s->mtu, m0->m_pkthdr.len); + + if (RFCOMM_CR(hdr->type)) { + if (rls->status & 0x1) + NG_BTSOCKET_RFCOMM_ERR( +"%s: Got RLS dlci=%d, error=%#x\n", __func__, RFCOMM_DLCI(rls->address), + rls->status >> 1); + + hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_RLS); + error = ng_btsocket_rfcomm_send_uih(s, + RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); + } else + NG_FREE_M(m0); /* XXX ignore responses */ + + return (error); +} /* ng_btsocket_rfcomm_receive_rls */ + +/* + * Receive RFCOMM PN MCC command + */ + +static int +ng_btsocket_rfcomm_receive_pn(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) +{ + struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr*); + struct rfcomm_mcc_pn *pn = (struct rfcomm_mcc_pn *)(hdr+1); + ng_btsocket_rfcomm_pcb_t *pcb = NULL; + int error = 0; + + mtx_assert(&s->session_mtx, MA_OWNED); + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Got MCC PN, dlci=%d, cr=%d, length=%d, flow_control=%#x, priority=%d, " \ +"ack_timer=%d, mtu=%d, max_retrans=%d, credits=%d, session state=%d, " \ +"flags=%#x, session mtu=%d, len=%d\n", + __func__, pn->dlci, RFCOMM_CR(hdr->type), + RFCOMM_MCC_LENGTH(hdr->length), pn->flow_control, pn->priority, + pn->ack_timer, le16toh(pn->mtu), pn->max_retrans, pn->credits, + s->state, s->flags, s->mtu, m0->m_pkthdr.len); + + if (pn->dlci == 0) { + NG_BTSOCKET_RFCOMM_ERR("%s: Zero dlci in MCC PN\n", __func__); + NG_FREE_M(m0); + + return (EINVAL); + } + + /* Check if we have this dlci */ + pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, pn->dlci); + if (pcb != NULL) { + mtx_lock(&pcb->pcb_mtx); + + if (RFCOMM_CR(hdr->type)) { + /* PN Request */ + ng_btsocket_rfcomm_set_pn(pcb, 1, pn->flow_control, + pn->credits, pn->mtu); + + if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) { + pn->flow_control = 0xe0; + pn->credits = RFCOMM_DEFAULT_CREDITS; + } else { + pn->flow_control = 0; + pn->credits = 0; + } + + hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_PN); + error = ng_btsocket_rfcomm_send_uih(s, + RFCOMM_MKADDRESS(INITIATOR(s), 0), + 0, 0, m0); + } else { + /* PN Response - proceed with SABM. Timeout still set */ + if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_CONFIGURING) { + ng_btsocket_rfcomm_set_pn(pcb, 0, + pn->flow_control, pn->credits, pn->mtu); + + pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONNECTING; + error = ng_btsocket_rfcomm_send_command(s, + RFCOMM_FRAME_SABM, pn->dlci); + } else + NG_BTSOCKET_RFCOMM_WARN( +"%s: Got PN response for dlci=%d in invalid state=%d\n", + __func__, pn->dlci, pcb->state); + + NG_FREE_M(m0); + } + + mtx_unlock(&pcb->pcb_mtx); + } else if (RFCOMM_CR(hdr->type)) { + /* PN request to non-existing dlci - incomming connection */ + pcb = ng_btsocket_rfcomm_connect_ind(s, + RFCOMM_SRVCHANNEL(pn->dlci)); + if (pcb != NULL) { + mtx_lock(&pcb->pcb_mtx); + + pcb->dlci = pn->dlci; + + ng_btsocket_rfcomm_set_pn(pcb, 1, pn->flow_control, + pn->credits, pn->mtu); + + if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) { + pn->flow_control = 0xe0; + pn->credits = RFCOMM_DEFAULT_CREDITS; + } else { + pn->flow_control = 0; + pn->credits = 0; + } + + hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_PN); + error = ng_btsocket_rfcomm_send_uih(s, + RFCOMM_MKADDRESS(INITIATOR(s), 0), + 0, 0, m0); + + if (error == 0) { + ng_btsocket_rfcomm_timeout(pcb); + pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONNECTING; + soisconnecting(pcb->so); + } else + ng_btsocket_rfcomm_pcb_kill(pcb, error); + + mtx_unlock(&pcb->pcb_mtx); + } else { + /* Nobody is listen()ing on this channel */ + error = ng_btsocket_rfcomm_send_command(s, + RFCOMM_FRAME_DM, pn->dlci); + NG_FREE_M(m0); + } + } else + NG_FREE_M(m0); /* XXX ignore response to non-existing dlci */ + + return (error); +} /* ng_btsocket_rfcomm_receive_pn */ + +/* + * Set PN parameters for dlci. Caller must hold pcb->pcb_mtx. + * + * From Bluetooth spec. + * + * "... The CL1 - CL4 field is completely redefined. (In TS07.10 this defines + * the convergence layer to use, which is not applicable to RFCOMM. In RFCOMM, + * in Bluetooth versions up to 1.0B, this field was forced to 0). + * + * In the PN request sent prior to a DLC establishment, this field must contain + * the value 15 (0xF), indicating support of credit based flow control in the + * sender. See Table 5.3 below. If the PN response contains any other value + * than 14 (0xE) in this field, it is inferred that the peer RFCOMM entity is + * not supporting the credit based flow control feature. (This is only possible + * if the peer RFCOMM implementation is only conforming to Bluetooth version + * 1.0B.) If a PN request is sent on an already open DLC, then this field must + * contain the value zero; it is not possible to set initial credits more + * than once per DLC activation. A responding implementation must set this + * field in the PN response to 14 (0xE), if (and only if) the value in the PN + * request was 15..." + */ + +static void +ng_btsocket_rfcomm_set_pn(ng_btsocket_rfcomm_pcb_p pcb, u_int8_t cr, + u_int8_t flow_control, u_int8_t credits, u_int16_t mtu) +{ + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + pcb->mtu = le16toh(mtu); + + if (cr) { + if (flow_control == 0xf0) { + pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_CFC; + pcb->tx_cred = credits; + } else { + pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_CFC; + pcb->tx_cred = 0; + } + } else { + if (flow_control == 0xe0) { + pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_CFC; + pcb->tx_cred = credits; + } else { + pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_CFC; + pcb->tx_cred = 0; + } + } + + NG_BTSOCKET_RFCOMM_INFO( +"%s: cr=%d, dlci=%d, state=%d, flags=%#x, mtu=%d, rx_cred=%d, tx_cred=%d\n", + __func__, cr, pcb->dlci, pcb->state, pcb->flags, pcb->mtu, + pcb->rx_cred, pcb->tx_cred); +} /* ng_btsocket_rfcomm_set_pn */ + +/* + * Send RFCOMM SABM/DISC/UA/DM frames. Caller must hold s->session_mtx + */ + +static int +ng_btsocket_rfcomm_send_command(ng_btsocket_rfcomm_session_p s, + u_int8_t type, u_int8_t dlci) +{ + struct rfcomm_cmd_hdr *hdr = NULL; + struct mbuf *m = NULL; + int cr; + + mtx_assert(&s->session_mtx, MA_OWNED); + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Sending command type %#x, session state=%d, flags=%#x, mtu=%d, dlci=%d\n", + __func__, type, s->state, s->flags, s->mtu, dlci); + + switch (type) { + case RFCOMM_FRAME_SABM: + case RFCOMM_FRAME_DISC: + cr = INITIATOR(s); + break; + + case RFCOMM_FRAME_UA: + case RFCOMM_FRAME_DM: + cr = !INITIATOR(s); + break; + + default: + panic("%s: Invalid frame type=%#x\n", __func__, type); + return (EINVAL); + /* NOT REACHED */ + } + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) + return (ENOBUFS); + + m->m_pkthdr.len = m->m_len = sizeof(*hdr); + + hdr = mtod(m, struct rfcomm_cmd_hdr *); + hdr->address = RFCOMM_MKADDRESS(cr, dlci); + hdr->control = RFCOMM_MKCONTROL(type, 1); + hdr->length = RFCOMM_MKLEN8(0); + hdr->fcs = ng_btsocket_rfcomm_fcs3((u_int8_t *) hdr); + + NG_BT_MBUFQ_ENQUEUE(&s->outq, m); + + return (0); +} /* ng_btsocket_rfcomm_send_command */ + +/* + * Send RFCOMM UIH frame. Caller must hold s->session_mtx + */ + +static int +ng_btsocket_rfcomm_send_uih(ng_btsocket_rfcomm_session_p s, u_int8_t address, + u_int8_t pf, u_int8_t credits, struct mbuf *data) +{ + struct rfcomm_frame_hdr *hdr = NULL; + struct mbuf *m = NULL, *mcrc = NULL; + u_int16_t length; + + mtx_assert(&s->session_mtx, MA_OWNED); + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + NG_FREE_M(data); + return (ENOBUFS); + } + m->m_pkthdr.len = m->m_len = sizeof(*hdr); + + MGET(mcrc, M_DONTWAIT, MT_DATA); + if (mcrc == NULL) { + NG_FREE_M(data); + return (ENOBUFS); + } + mcrc->m_len = 1; + + /* Fill UIH frame header */ + hdr = mtod(m, struct rfcomm_frame_hdr *); + hdr->address = address; + hdr->control = RFCOMM_MKCONTROL(RFCOMM_FRAME_UIH, pf); + + /* Calculate FCS */ + mcrc->m_data[0] = ng_btsocket_rfcomm_fcs2((u_int8_t *) hdr); + + /* Put length back */ + length = (data != NULL)? data->m_pkthdr.len : 0; + if (length > 127) { + u_int16_t l = htole16(RFCOMM_MKLEN16(length)); + + bcopy(&l, &hdr->length, sizeof(l)); + m->m_pkthdr.len ++; + m->m_len ++; + } else + hdr->length = RFCOMM_MKLEN8(length); + + if (pf) { + m->m_data[m->m_len] = credits; + m->m_pkthdr.len ++; + m->m_len ++; + } + + /* Add payload */ + if (data != NULL) { + m_cat(m, data); + m->m_pkthdr.len += length; + } + + /* Put FCS back */ + m_cat(m, mcrc); + m->m_pkthdr.len ++; + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Sending UIH state=%d, flags=%#x, address=%d, length=%d, pf=%d, " \ +"credits=%d, len=%d\n", + __func__, s->state, s->flags, address, length, pf, credits, + m->m_pkthdr.len); + + NG_BT_MBUFQ_ENQUEUE(&s->outq, m); + + return (0); +} /* ng_btsocket_rfcomm_send_uih */ + +/* + * Send MSC request. Caller must hold pcb->pcb_mtx and pcb->session->session_mtx + */ + +static int +ng_btsocket_rfcomm_send_msc(ng_btsocket_rfcomm_pcb_p pcb) +{ + struct mbuf *m = NULL; + struct rfcomm_mcc_hdr *hdr = NULL; + struct rfcomm_mcc_msc *msc = NULL; + + mtx_assert(&pcb->session->session_mtx, MA_OWNED); + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) + return (ENOBUFS); + + m->m_pkthdr.len = m->m_len = sizeof(*hdr) + sizeof(*msc); + + hdr = mtod(m, struct rfcomm_mcc_hdr *); + msc = (struct rfcomm_mcc_msc *)(hdr + 1); + + hdr->type = RFCOMM_MKMCC_TYPE(1, RFCOMM_MCC_MSC); + hdr->length = RFCOMM_MKLEN8(sizeof(*msc)); + + msc->address = RFCOMM_MKADDRESS(1, pcb->dlci); + msc->modem = pcb->lmodem; + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Sending MSC dlci=%d, state=%d, flags=%#x, address=%d, modem=%#x\n", + __func__, pcb->dlci, pcb->state, pcb->flags, msc->address, + msc->modem); + + return (ng_btsocket_rfcomm_send_uih(pcb->session, + RFCOMM_MKADDRESS(INITIATOR(pcb->session), 0), 0, 0, m)); +} /* ng_btsocket_rfcomm_send_msc */ + +/* + * Send PN request. Caller must hold pcb->pcb_mtx and pcb->session->session_mtx + */ + +static int +ng_btsocket_rfcomm_send_pn(ng_btsocket_rfcomm_pcb_p pcb) +{ + struct mbuf *m = NULL; + struct rfcomm_mcc_hdr *hdr = NULL; + struct rfcomm_mcc_pn *pn = NULL; + + mtx_assert(&pcb->session->session_mtx, MA_OWNED); + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) + return (ENOBUFS); + + m->m_pkthdr.len = m->m_len = sizeof(*hdr) + sizeof(*pn); + + hdr = mtod(m, struct rfcomm_mcc_hdr *); + pn = (struct rfcomm_mcc_pn *)(hdr + 1); + + hdr->type = RFCOMM_MKMCC_TYPE(1, RFCOMM_MCC_PN); + hdr->length = RFCOMM_MKLEN8(sizeof(*pn)); + + pn->dlci = pcb->dlci; + + /* + * Set default DLCI priority as described in GSM 07.10 + * (ETSI TS 101 369) clause 5.6 page 42 + */ + + pn->priority = (pcb->dlci < 56)? (((pcb->dlci >> 3) << 3) + 7) : 61; + pn->ack_timer = 0; + pn->mtu = htole16(pcb->mtu); + pn->max_retrans = 0; + + if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) { + pn->flow_control = 0xf0; + pn->credits = pcb->rx_cred; + } else { + pn->flow_control = 0; + pn->credits = 0; + } + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Sending PN dlci=%d, state=%d, flags=%#x, mtu=%d, flow_control=%#x, " \ +"credits=%d\n", __func__, pcb->dlci, pcb->state, pcb->flags, pcb->mtu, + pn->flow_control, pn->credits); + + return (ng_btsocket_rfcomm_send_uih(pcb->session, + RFCOMM_MKADDRESS(INITIATOR(pcb->session), 0), 0, 0, m)); +} /* ng_btsocket_rfcomm_send_pn */ + +/* + * Calculate and send credits based on available space in receive buffer + */ + +static int +ng_btsocket_rfcomm_send_credits(ng_btsocket_rfcomm_pcb_p pcb) +{ + int error = 0; + u_int8_t credits; + + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + mtx_assert(&pcb->session->session_mtx, MA_OWNED); + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Sending more credits, dlci=%d, state=%d, flags=%#x, mtu=%d, " \ +"space=%ld, tx_cred=%d, rx_cred=%d\n", + __func__, pcb->dlci, pcb->state, pcb->flags, pcb->mtu, + sbspace(&pcb->so->so_rcv), pcb->tx_cred, pcb->rx_cred); + + credits = sbspace(&pcb->so->so_rcv) / pcb->mtu; + if (credits > 0) { + if (pcb->rx_cred + credits > RFCOMM_MAX_CREDITS) + credits = RFCOMM_MAX_CREDITS - pcb->rx_cred; + + error = ng_btsocket_rfcomm_send_uih( + pcb->session, + RFCOMM_MKADDRESS(INITIATOR(pcb->session), + pcb->dlci), 1, credits, NULL); + if (error == 0) { + pcb->rx_cred += credits; + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Gave remote side %d more credits, dlci=%d, state=%d, flags=%#x, " \ +"rx_cred=%d, tx_cred=%d\n", __func__, credits, pcb->dlci, pcb->state, + pcb->flags, pcb->rx_cred, pcb->tx_cred); + } else + NG_BTSOCKET_RFCOMM_ERR( +"%s: Could not send credits, error=%d, dlci=%d, state=%d, flags=%#x, " \ +"mtu=%d, space=%ld, tx_cred=%d, rx_cred=%d\n", + __func__, error, pcb->dlci, pcb->state, + pcb->flags, pcb->mtu, sbspace(&pcb->so->so_rcv), + pcb->tx_cred, pcb->rx_cred); + } + + return (error); +} /* ng_btsocket_rfcomm_send_credits */ + +/***************************************************************************** + ***************************************************************************** + ** RFCOMM DLCs + ***************************************************************************** + *****************************************************************************/ + +/* + * Send data from socket send buffer + * Caller must hold pcb->pcb_mtx and pcb->session->session_mtx + */ + +static int +ng_btsocket_rfcomm_pcb_send(ng_btsocket_rfcomm_pcb_p pcb, int limit) +{ + struct mbuf *m = NULL; + int sent, length, error; + + mtx_assert(&pcb->session->session_mtx, MA_OWNED); + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) + limit = min(limit, pcb->tx_cred); + else if (!(pcb->rmodem & RFCOMM_MODEM_FC)) + limit = min(limit, RFCOMM_MAX_CREDITS); /* XXX ??? */ + else + limit = 0; + + if (limit == 0) { + NG_BTSOCKET_RFCOMM_INFO( +"%s: Could not send - remote flow control asserted, dlci=%d, flags=%#x, " \ +"rmodem=%#x, tx_cred=%d\n", + __func__, pcb->dlci, pcb->flags, pcb->rmodem, + pcb->tx_cred); + + return (0); + } + + for (error = 0, sent = 0; sent < limit; sent ++) { + length = min(pcb->mtu, pcb->so->so_snd.sb_cc); + if (length == 0) + break; + + /* Get the chunk from the socket's send buffer */ + m = ng_btsocket_rfcomm_prepare_packet(&pcb->so->so_snd, length); + if (m == NULL) { + error = ENOBUFS; + break; + } + + sbdrop(&pcb->so->so_snd, length); + + error = ng_btsocket_rfcomm_send_uih(pcb->session, + RFCOMM_MKADDRESS(INITIATOR(pcb->session), + pcb->dlci), 0, 0, m); + if (error != 0) + break; + } + + if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) + pcb->tx_cred -= sent; + + if (error == 0 && sent > 0) { + pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_SENDING; + sowwakeup(pcb->so); + } + + return (error); +} /* ng_btsocket_rfcomm_pcb_send */ + +/* + * Unlink and disconnect DLC. If ng_btsocket_rfcomm_pcb_kill() returns + * non zero value than socket has no reference and has to be detached. + * Caller must hold pcb->pcb_mtx and pcb->session->session_mtx + */ + +static void +ng_btsocket_rfcomm_pcb_kill(ng_btsocket_rfcomm_pcb_p pcb, int error) +{ + ng_btsocket_rfcomm_session_p s = pcb->session; + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Killing DLC, so=%p, dlci=%d, state=%d, flags=%#x, error=%d\n", + __func__, pcb->so, pcb->dlci, pcb->state, pcb->flags, error); + + if (pcb->session == NULL) + panic("%s: DLC without session, pcb=%p, state=%d, flags=%#x\n", + __func__, pcb, pcb->state, pcb->flags); + + mtx_assert(&pcb->session->session_mtx, MA_OWNED); + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO) + ng_btsocket_rfcomm_untimeout(pcb); + + /* Detach DLC from the session. Does not matter which state DLC in */ + LIST_REMOVE(pcb, session_next); + pcb->session = NULL; + + /* Change DLC state and wakeup all sleepers */ + pcb->state = NG_BTSOCKET_RFCOMM_DLC_CLOSED; + pcb->so->so_error = error; + soisdisconnected(pcb->so); + wakeup(&pcb->state); + + /* Check if we have any DLCs left on the session */ + if (LIST_EMPTY(&s->dlcs) && INITIATOR(s)) { + NG_BTSOCKET_RFCOMM_INFO( +"%s: Disconnecting session, state=%d, flags=%#x, mtu=%d\n", + __func__, s->state, s->flags, s->mtu); + + switch (s->state) { + case NG_BTSOCKET_RFCOMM_SESSION_CLOSED: + case NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING: + /* + * Do not have to do anything here. We can get here + * when L2CAP connection was terminated or we have + * received DISC on multiplexor channel + */ + break; + + case NG_BTSOCKET_RFCOMM_SESSION_OPEN: + /* Send DISC on multiplexor channel */ + error = ng_btsocket_rfcomm_send_command(s, + RFCOMM_FRAME_DISC, 0); + if (error == 0) { + s->state = NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING; + break; + } + /* FALL THROUGH */ + + case NG_BTSOCKET_RFCOMM_SESSION_CONNECTING: + case NG_BTSOCKET_RFCOMM_SESSION_CONNECTED: + s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; + break; + +/* case NG_BTSOCKET_RFCOMM_SESSION_LISTENING: */ + default: + panic("%s: Invalid session state=%d, flags=%#x\n", + __func__, s->state, s->flags); + break; + } + + ng_btsocket_rfcomm_task_wakeup(); + } +} /* ng_btsocket_rfcomm_pcb_kill */ + +/* + * Look for given dlci for given RFCOMM session. Caller must hold s->session_mtx + */ + +static ng_btsocket_rfcomm_pcb_p +ng_btsocket_rfcomm_pcb_by_dlci(ng_btsocket_rfcomm_session_p s, int dlci) +{ + ng_btsocket_rfcomm_pcb_p pcb = NULL; + + mtx_assert(&s->session_mtx, MA_OWNED); + + LIST_FOREACH(pcb, &s->dlcs, session_next) + if (pcb->dlci == dlci) + break; + + return (pcb); +} /* ng_btsocket_rfcomm_pcb_by_dlci */ + +/* + * Look for socket that listens on given src address and given channel + */ + +static ng_btsocket_rfcomm_pcb_p +ng_btsocket_rfcomm_pcb_listener(bdaddr_p src, int channel) +{ + ng_btsocket_rfcomm_pcb_p pcb = NULL, pcb1 = NULL; + + mtx_lock(&ng_btsocket_rfcomm_sockets_mtx); + + LIST_FOREACH(pcb, &ng_btsocket_rfcomm_sockets, next) { + if (pcb->channel != channel || + !(pcb->so->so_options & SO_ACCEPTCONN)) + continue; + + if (bcmp(&pcb->src, src, sizeof(*src)) == 0) + break; + + if (bcmp(&pcb->src, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) + pcb1 = pcb; + } + + mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); + + return ((pcb != NULL)? pcb : pcb1); +} /* ng_btsocket_rfcomm_pcb_listener */ + +/***************************************************************************** + ***************************************************************************** + ** Misc. functions + ***************************************************************************** + *****************************************************************************/ + +/* + * Set timeout. Caller MUST hold pcb_mtx + */ + +static void +ng_btsocket_rfcomm_timeout(ng_btsocket_rfcomm_pcb_p pcb) +{ + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + if (!(pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO)) { + pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_TIMO; + pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT; + pcb->timo = timeout(ng_btsocket_rfcomm_process_timeout, pcb, + ng_btsocket_rfcomm_timo * hz); + } else + panic("%s: Duplicated socket timeout?!\n", __func__); +} /* ng_btsocket_rfcomm_timeout */ + +/* + * Unset pcb timeout. Caller MUST hold pcb_mtx + */ + +static void +ng_btsocket_rfcomm_untimeout(ng_btsocket_rfcomm_pcb_p pcb) +{ + mtx_assert(&pcb->pcb_mtx, MA_OWNED); + + if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO) { + untimeout(ng_btsocket_rfcomm_process_timeout, pcb, pcb->timo); + pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_TIMO; + pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT; + } else + panic("%s: No socket timeout?!\n", __func__); +} /* ng_btsocket_rfcomm_timeout */ + +/* + * Process pcb timeout + */ + +static void +ng_btsocket_rfcomm_process_timeout(void *xpcb) +{ + ng_btsocket_rfcomm_pcb_p pcb = (ng_btsocket_rfcomm_pcb_p) xpcb; + + mtx_lock(&pcb->pcb_mtx); + + NG_BTSOCKET_RFCOMM_INFO( +"%s: Timeout, so=%p, dlci=%d, state=%d, flags=%#x\n", + __func__, pcb->so, pcb->dlci, pcb->state, pcb->flags); + + pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_TIMO; + pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT; + + switch (pcb->state) { + case NG_BTSOCKET_RFCOMM_DLC_CONFIGURING: + case NG_BTSOCKET_RFCOMM_DLC_CONNECTING: + pcb->state = NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING; + break; + + case NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT: + case NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING: + break; + + default: + panic( +"%s: DLC timeout in invalid state, dlci=%d, state=%d, flags=%#x\n", + __func__, pcb->dlci, pcb->state, pcb->flags); + break; + } + + ng_btsocket_rfcomm_task_wakeup(); + + mtx_unlock(&pcb->pcb_mtx); +} /* ng_btsocket_rfcomm_process_timeout */ + +/* + * Get up to length bytes from the socket buffer + */ + +static struct mbuf * +ng_btsocket_rfcomm_prepare_packet(struct sockbuf *sb, int length) +{ + struct mbuf *top = NULL, *m = NULL, *n = NULL, *nextpkt = NULL; + int mlen, noff, len; + + MGETHDR(top, M_DONTWAIT, MT_DATA); + if (top == NULL) + return (NULL); + + top->m_pkthdr.len = length; + top->m_len = 0; + mlen = MHLEN; + + m = top; + n = sb->sb_mb; + nextpkt = n->m_nextpkt; + noff = 0; + + while (length > 0 && n != NULL) { + len = min(mlen - m->m_len, n->m_len - noff); + if (len > length) + len = length; + + bcopy(mtod(n, caddr_t)+noff, mtod(m, caddr_t)+m->m_len, len); + m->m_len += len; + noff += len; + length -= len; + + if (length > 0 && m->m_len == mlen) { + MGET(m->m_next, M_DONTWAIT, MT_DATA); + if (m->m_next == NULL) { + NG_FREE_M(top); + return (NULL); + } + + m = m->m_next; + m->m_len = 0; + mlen = MLEN; + } + + if (noff == n->m_len) { + noff = 0; + n = n->m_next; + + if (n == NULL) + n = nextpkt; + + nextpkt = (n != NULL)? n->m_nextpkt : NULL; + } + } + + if (length < 0) + panic("%s: length=%d\n", __func__, length); + if (length > 0 && n == NULL) + panic("%s: bogus length=%d, n=%p\n", __func__, length, n); + + return (top); +} /* ng_btsocket_rfcomm_prepare_packet */ + diff --git a/sys/netgraph7/netflow/netflow.c b/sys/netgraph7/netflow/netflow.c new file mode 100644 index 0000000000..96ba4951d9 --- /dev/null +++ b/sys/netgraph7/netflow/netflow.c @@ -0,0 +1,718 @@ +/*- + * Copyright (c) 2004-2005 Gleb Smirnoff + * Copyright (c) 2001-2003 Roman V. Palagin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $SourceForge: netflow.c,v 1.41 2004/09/05 11:41:10 glebius Exp $ + */ + +static const char rcs_id[] = + "@(#) $FreeBSD: src/sys/netgraph/netflow/netflow.c,v 1.29 2008/05/09 23:02:57 julian Exp $"; + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#define NBUCKETS (65536) /* must be power of 2 */ + +/* This hash is for TCP or UDP packets. */ +#define FULL_HASH(addr1, addr2, port1, port2) \ + (((addr1 ^ (addr1 >> 16) ^ \ + htons(addr2 ^ (addr2 >> 16))) ^ \ + port1 ^ htons(port2)) & \ + (NBUCKETS - 1)) + +/* This hash is for all other IP packets. */ +#define ADDR_HASH(addr1, addr2) \ + ((addr1 ^ (addr1 >> 16) ^ \ + htons(addr2 ^ (addr2 >> 16))) & \ + (NBUCKETS - 1)) + +/* Macros to shorten logical constructions */ +/* XXX: priv must exist in namespace */ +#define INACTIVE(fle) (time_uptime - fle->f.last > priv->info.nfinfo_inact_t) +#define AGED(fle) (time_uptime - fle->f.first > priv->info.nfinfo_act_t) +#define ISFREE(fle) (fle->f.packets == 0) + +/* + * 4 is a magical number: statistically number of 4-packet flows is + * bigger than 5,6,7...-packet flows by an order of magnitude. Most UDP/ICMP + * scans are 1 packet (~ 90% of flow cache). TCP scans are 2-packet in case + * of reachable host and 4-packet otherwise. + */ +#define SMALL(fle) (fle->f.packets <= 4) + +/* + * Cisco uses milliseconds for uptime. Bad idea, since it overflows + * every 48+ days. But we will do same to keep compatibility. This macro + * does overflowable multiplication to 1000. + */ +#define MILLIUPTIME(t) (((t) << 9) + /* 512 */ \ + ((t) << 8) + /* 256 */ \ + ((t) << 7) + /* 128 */ \ + ((t) << 6) + /* 64 */ \ + ((t) << 5) + /* 32 */ \ + ((t) << 3)) /* 8 */ + +MALLOC_DECLARE(M_NETFLOW_HASH); +MALLOC_DEFINE(M_NETFLOW_HASH, "netflow_hash", "NetFlow hash"); + +static int export_add(item_p, struct flow_entry *); +static int export_send(priv_p, item_p, int flags); + +/* Generate hash for a given flow record. */ +static __inline uint32_t +ip_hash(struct flow_rec *r) +{ + switch (r->r_ip_p) { + case IPPROTO_TCP: + case IPPROTO_UDP: + return FULL_HASH(r->r_src.s_addr, r->r_dst.s_addr, + r->r_sport, r->r_dport); + default: + return ADDR_HASH(r->r_src.s_addr, r->r_dst.s_addr); + } +} + +/* This is callback from uma(9), called on alloc. */ +static int +uma_ctor_flow(void *mem, int size, void *arg, int how) +{ + priv_p priv = (priv_p )arg; + + if (atomic_load_acq_32(&priv->info.nfinfo_used) >= CACHESIZE) + return (ENOMEM); + + atomic_add_32(&priv->info.nfinfo_used, 1); + + return (0); +} + +/* This is callback from uma(9), called on free. */ +static void +uma_dtor_flow(void *mem, int size, void *arg) +{ + priv_p priv = (priv_p )arg; + + atomic_subtract_32(&priv->info.nfinfo_used, 1); +} + +/* + * Detach export datagram from priv, if there is any. + * If there is no, allocate a new one. + */ +static item_p +get_export_dgram(priv_p priv) +{ + item_p item = NULL; + + mtx_lock(&priv->export_mtx); + if (priv->export_item != NULL) { + item = priv->export_item; + priv->export_item = NULL; + } + mtx_unlock(&priv->export_mtx); + + if (item == NULL) { + struct netflow_v5_export_dgram *dgram; + struct mbuf *m; + + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (m == NULL) + return (NULL); + item = ng_package_data(m, NG_NOFLAGS); + if (item == NULL) + return (NULL); + dgram = mtod(m, struct netflow_v5_export_dgram *); + dgram->header.count = 0; + dgram->header.version = htons(NETFLOW_V5); + + } + + return (item); +} + +/* + * Re-attach incomplete datagram back to priv. + * If there is already another one, then send incomplete. */ +static void +return_export_dgram(priv_p priv, item_p item, int flags) +{ + /* + * It may happen on SMP, that some thread has already + * put its item there, in this case we bail out and + * send what we have to collector. + */ + mtx_lock(&priv->export_mtx); + if (priv->export_item == NULL) { + priv->export_item = item; + mtx_unlock(&priv->export_mtx); + } else { + mtx_unlock(&priv->export_mtx); + export_send(priv, item, flags); + } +} + +/* + * The flow is over. Call export_add() and free it. If datagram is + * full, then call export_send(). + */ +static __inline void +expire_flow(priv_p priv, item_p *item, struct flow_entry *fle, int flags) +{ + if (*item == NULL) + *item = get_export_dgram(priv); + if (*item == NULL) { + atomic_add_32(&priv->info.nfinfo_export_failed, 1); + uma_zfree_arg(priv->zone, fle, priv); + return; + } + if (export_add(*item, fle) > 0) { + export_send(priv, *item, flags); + *item = NULL; + } + uma_zfree_arg(priv->zone, fle, priv); +} + +/* Get a snapshot of node statistics */ +void +ng_netflow_copyinfo(priv_p priv, struct ng_netflow_info *i) +{ + /* XXX: atomic */ + memcpy((void *)i, (void *)&priv->info, sizeof(priv->info)); +} + +/* + * Insert a record into defined slot. + * + * First we get for us a free flow entry, then fill in all + * possible fields in it. + * + * TODO: consider dropping hash mutex while filling in datagram, + * as this was done in previous version. Need to test & profile + * to be sure. + */ +static __inline int +hash_insert(priv_p priv, struct flow_hash_entry *hsh, struct flow_rec *r, + int plen, uint8_t tcp_flags) +{ + struct flow_entry *fle; + struct sockaddr_in sin; + struct rtentry *rt; + + mtx_assert(&hsh->mtx, MA_OWNED); + + fle = uma_zalloc_arg(priv->zone, priv, M_NOWAIT); + if (fle == NULL) { + atomic_add_32(&priv->info.nfinfo_alloc_failed, 1); + return (ENOMEM); + } + + /* + * Now fle is totally ours. It is detached from all lists, + * we can safely edit it. + */ + + bcopy(r, &fle->f.r, sizeof(struct flow_rec)); + fle->f.bytes = plen; + fle->f.packets = 1; + fle->f.tcp_flags = tcp_flags; + + fle->f.first = fle->f.last = time_uptime; + + /* + * First we do route table lookup on destination address. So we can + * fill in out_ifx, dst_mask, nexthop, and dst_as in future releases. + */ + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(struct sockaddr_in); + sin.sin_family = AF_INET; + sin.sin_addr = fle->f.r.r_dst; + /* XXX MRT 0 as a default.. need the m here to get fib */ + rt = rtalloc1_fib((struct sockaddr *)&sin, 0, RTF_CLONING, 0); + if (rt != NULL) { + fle->f.fle_o_ifx = rt->rt_ifp->if_index; + + if (rt->rt_flags & RTF_GATEWAY && + rt->rt_gateway->sa_family == AF_INET) + fle->f.next_hop = + ((struct sockaddr_in *)(rt->rt_gateway))->sin_addr; + + if (rt_mask(rt)) + fle->f.dst_mask = bitcount32(((struct sockaddr_in *) + rt_mask(rt))->sin_addr.s_addr); + else if (rt->rt_flags & RTF_HOST) + /* Give up. We can't determine mask :( */ + fle->f.dst_mask = 32; + + RTFREE_LOCKED(rt); + } + + /* Do route lookup on source address, to fill in src_mask. */ + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(struct sockaddr_in); + sin.sin_family = AF_INET; + sin.sin_addr = fle->f.r.r_src; + /* XXX MRT 0 as a default revisit. need the mbuf for fib*/ + rt = rtalloc1_fib((struct sockaddr *)&sin, 0, RTF_CLONING, 0); + if (rt != NULL) { + if (rt_mask(rt)) + fle->f.src_mask = bitcount32(((struct sockaddr_in *) + rt_mask(rt))->sin_addr.s_addr); + else if (rt->rt_flags & RTF_HOST) + /* Give up. We can't determine mask :( */ + fle->f.src_mask = 32; + + RTFREE_LOCKED(rt); + } + + /* Push new flow at the and of hash. */ + TAILQ_INSERT_TAIL(&hsh->head, fle, fle_hash); + + return (0); +} + + +/* + * Non-static functions called from ng_netflow.c + */ + +/* Allocate memory and set up flow cache */ +int +ng_netflow_cache_init(priv_p priv) +{ + struct flow_hash_entry *hsh; + int i; + + /* Initialize cache UMA zone. */ + priv->zone = uma_zcreate("NetFlow cache", sizeof(struct flow_entry), + uma_ctor_flow, uma_dtor_flow, NULL, NULL, UMA_ALIGN_CACHE, 0); + uma_zone_set_max(priv->zone, CACHESIZE); + + /* Allocate hash. */ + MALLOC(priv->hash, struct flow_hash_entry *, + NBUCKETS * sizeof(struct flow_hash_entry), + M_NETFLOW_HASH, M_WAITOK | M_ZERO); + + if (priv->hash == NULL) { + uma_zdestroy(priv->zone); + return (ENOMEM); + } + + /* Initialize hash. */ + for (i = 0, hsh = priv->hash; i < NBUCKETS; i++, hsh++) { + mtx_init(&hsh->mtx, "hash mutex", NULL, MTX_DEF); + TAILQ_INIT(&hsh->head); + } + + mtx_init(&priv->export_mtx, "export dgram lock", NULL, MTX_DEF); + + return (0); +} + +/* Free all flow cache memory. Called from node close method. */ +void +ng_netflow_cache_flush(priv_p priv) +{ + struct flow_entry *fle, *fle1; + struct flow_hash_entry *hsh; + item_p item = NULL; + int i; + + /* + * We are going to free probably billable data. + * Expire everything before freeing it. + * No locking is required since callout is already drained. + */ + for (hsh = priv->hash, i = 0; i < NBUCKETS; hsh++, i++) + TAILQ_FOREACH_SAFE(fle, &hsh->head, fle_hash, fle1) { + TAILQ_REMOVE(&hsh->head, fle, fle_hash); + expire_flow(priv, &item, fle, NG_QUEUE); + } + + if (item != NULL) + export_send(priv, item, NG_QUEUE); + + uma_zdestroy(priv->zone); + + /* Destroy hash mutexes. */ + for (i = 0, hsh = priv->hash; i < NBUCKETS; i++, hsh++) + mtx_destroy(&hsh->mtx); + + /* Free hash memory. */ + if (priv->hash) + FREE(priv->hash, M_NETFLOW_HASH); + + mtx_destroy(&priv->export_mtx); +} + +/* Insert packet from into flow cache. */ +int +ng_netflow_flow_add(priv_p priv, struct ip *ip, iface_p iface, + struct ifnet *ifp) +{ + register struct flow_entry *fle, *fle1; + struct flow_hash_entry *hsh; + struct flow_rec r; + item_p item = NULL; + int hlen, plen; + int error = 0; + uint8_t tcp_flags = 0; + + /* Try to fill flow_rec r */ + bzero(&r, sizeof(r)); + /* check version */ + if (ip->ip_v != IPVERSION) + return (EINVAL); + + /* verify min header length */ + hlen = ip->ip_hl << 2; + + if (hlen < sizeof(struct ip)) + return (EINVAL); + + r.r_src = ip->ip_src; + r.r_dst = ip->ip_dst; + + /* save packet length */ + plen = ntohs(ip->ip_len); + + r.r_ip_p = ip->ip_p; + r.r_tos = ip->ip_tos; + + /* Configured in_ifx overrides mbuf's */ + if (iface->info.ifinfo_index == 0) { + if (ifp != NULL) + r.r_i_ifx = ifp->if_index; + } else + r.r_i_ifx = iface->info.ifinfo_index; + + /* + * XXX NOTE: only first fragment of fragmented TCP, UDP and + * ICMP packet will be recorded with proper s_port and d_port. + * Following fragments will be recorded simply as IP packet with + * ip_proto = ip->ip_p and s_port, d_port set to zero. + * I know, it looks like bug. But I don't want to re-implement + * ip packet assebmling here. Anyway, (in)famous trafd works this way - + * and nobody complains yet :) + */ + if ((ip->ip_off & htons(IP_OFFMASK)) == 0) + switch(r.r_ip_p) { + case IPPROTO_TCP: + { + register struct tcphdr *tcp; + + tcp = (struct tcphdr *)((caddr_t )ip + hlen); + r.r_sport = tcp->th_sport; + r.r_dport = tcp->th_dport; + tcp_flags = tcp->th_flags; + break; + } + case IPPROTO_UDP: + r.r_ports = *(uint32_t *)((caddr_t )ip + hlen); + break; + } + + /* Update node statistics. XXX: race... */ + priv->info.nfinfo_packets ++; + priv->info.nfinfo_bytes += plen; + + /* Find hash slot. */ + hsh = &priv->hash[ip_hash(&r)]; + + mtx_lock(&hsh->mtx); + + /* + * Go through hash and find our entry. If we encounter an + * entry, that should be expired, purge it. We do a reverse + * search since most active entries are first, and most + * searches are done on most active entries. + */ + TAILQ_FOREACH_REVERSE_SAFE(fle, &hsh->head, fhead, fle_hash, fle1) { + if (bcmp(&r, &fle->f.r, sizeof(struct flow_rec)) == 0) + break; + if ((INACTIVE(fle) && SMALL(fle)) || AGED(fle)) { + TAILQ_REMOVE(&hsh->head, fle, fle_hash); + expire_flow(priv, &item, fle, NG_QUEUE); + atomic_add_32(&priv->info.nfinfo_act_exp, 1); + } + } + + if (fle) { /* An existent entry. */ + + fle->f.bytes += plen; + fle->f.packets ++; + fle->f.tcp_flags |= tcp_flags; + fle->f.last = time_uptime; + + /* + * We have the following reasons to expire flow in active way: + * - it hit active timeout + * - a TCP connection closed + * - it is going to overflow counter + */ + if (tcp_flags & TH_FIN || tcp_flags & TH_RST || AGED(fle) || + (fle->f.bytes >= (UINT_MAX - IF_MAXMTU)) ) { + TAILQ_REMOVE(&hsh->head, fle, fle_hash); + expire_flow(priv, &item, fle, NG_QUEUE); + atomic_add_32(&priv->info.nfinfo_act_exp, 1); + } else { + /* + * It is the newest, move it to the tail, + * if it isn't there already. Next search will + * locate it quicker. + */ + if (fle != TAILQ_LAST(&hsh->head, fhead)) { + TAILQ_REMOVE(&hsh->head, fle, fle_hash); + TAILQ_INSERT_TAIL(&hsh->head, fle, fle_hash); + } + } + } else /* A new flow entry. */ + error = hash_insert(priv, hsh, &r, plen, tcp_flags); + + mtx_unlock(&hsh->mtx); + + if (item != NULL) + return_export_dgram(priv, item, NG_QUEUE); + + return (error); +} + +/* + * Return records from cache to userland. + * + * TODO: matching particular IP should be done in kernel, here. + */ +int +ng_netflow_flow_show(priv_p priv, uint32_t last, struct ng_mesg *resp) +{ + struct flow_hash_entry *hsh; + struct flow_entry *fle; + struct ngnf_flows *data; + int i; + + data = (struct ngnf_flows *)resp->data; + data->last = 0; + data->nentries = 0; + + /* Check if this is a first run */ + if (last == 0) { + hsh = priv->hash; + i = 0; + } else { + if (last > NBUCKETS-1) + return (EINVAL); + hsh = priv->hash + last; + i = last; + } + + /* + * We will transfer not more than NREC_AT_ONCE. More data + * will come in next message. + * We send current hash index to userland, and userland should + * return it back to us. Then, we will restart with new entry. + * + * The resulting cache snapshot is inaccurate for the + * following reasons: + * - we skip locked hash entries + * - we bail out, if someone wants our entry + * - we skip rest of entry, when hit NREC_AT_ONCE + */ + for (; i < NBUCKETS; hsh++, i++) { + if (mtx_trylock(&hsh->mtx) == 0) + continue; + + TAILQ_FOREACH(fle, &hsh->head, fle_hash) { + if (hsh->mtx.mtx_lock & MTX_CONTESTED) + break; + + bcopy(&fle->f, &(data->entries[data->nentries]), + sizeof(fle->f)); + data->nentries++; + if (data->nentries == NREC_AT_ONCE) { + mtx_unlock(&hsh->mtx); + if (++i < NBUCKETS) + data->last = i; + return (0); + } + } + mtx_unlock(&hsh->mtx); + } + + return (0); +} + +/* We have full datagram in privdata. Send it to export hook. */ +static int +export_send(priv_p priv, item_p item, int flags) +{ + struct mbuf *m = NGI_M(item); + struct netflow_v5_export_dgram *dgram = mtod(m, + struct netflow_v5_export_dgram *); + struct netflow_v5_header *header = &dgram->header; + struct timespec ts; + int error = 0; + + /* Fill mbuf header. */ + m->m_len = m->m_pkthdr.len = sizeof(struct netflow_v5_record) * + header->count + sizeof(struct netflow_v5_header); + + /* Fill export header. */ + header->sys_uptime = htonl(MILLIUPTIME(time_uptime)); + getnanotime(&ts); + header->unix_secs = htonl(ts.tv_sec); + header->unix_nsecs = htonl(ts.tv_nsec); + header->engine_type = 0; + header->engine_id = 0; + header->pad = 0; + header->flow_seq = htonl(atomic_fetchadd_32(&priv->flow_seq, + header->count)); + header->count = htons(header->count); + + if (priv->export != NULL) + NG_FWD_ITEM_HOOK_FLAGS(error, item, priv->export, flags); + else + NG_FREE_ITEM(item); + + return (error); +} + + +/* Add export record to dgram. */ +static int +export_add(item_p item, struct flow_entry *fle) +{ + struct netflow_v5_export_dgram *dgram = mtod(NGI_M(item), + struct netflow_v5_export_dgram *); + struct netflow_v5_header *header = &dgram->header; + struct netflow_v5_record *rec; + + rec = &dgram->r[header->count]; + header->count ++; + + KASSERT(header->count <= NETFLOW_V5_MAX_RECORDS, + ("ng_netflow: export too big")); + + /* Fill in export record. */ + rec->src_addr = fle->f.r.r_src.s_addr; + rec->dst_addr = fle->f.r.r_dst.s_addr; + rec->next_hop = fle->f.next_hop.s_addr; + rec->i_ifx = htons(fle->f.fle_i_ifx); + rec->o_ifx = htons(fle->f.fle_o_ifx); + rec->packets = htonl(fle->f.packets); + rec->octets = htonl(fle->f.bytes); + rec->first = htonl(MILLIUPTIME(fle->f.first)); + rec->last = htonl(MILLIUPTIME(fle->f.last)); + rec->s_port = fle->f.r.r_sport; + rec->d_port = fle->f.r.r_dport; + rec->flags = fle->f.tcp_flags; + rec->prot = fle->f.r.r_ip_p; + rec->tos = fle->f.r.r_tos; + rec->dst_mask = fle->f.dst_mask; + rec->src_mask = fle->f.src_mask; + + /* Not supported fields. */ + rec->src_as = rec->dst_as = 0; + + if (header->count == NETFLOW_V5_MAX_RECORDS) + return (1); /* end of datagram */ + else + return (0); +} + +/* Periodic flow expiry run. */ +void +ng_netflow_expire(void *arg) +{ + struct flow_entry *fle, *fle1; + struct flow_hash_entry *hsh; + priv_p priv = (priv_p )arg; + item_p item = NULL; + uint32_t used; + int i; + + /* + * Going through all the cache. + */ + for (hsh = priv->hash, i = 0; i < NBUCKETS; hsh++, i++) { + /* + * Skip entries, that are already being worked on. + */ + if (mtx_trylock(&hsh->mtx) == 0) + continue; + + used = atomic_load_acq_32(&priv->info.nfinfo_used); + TAILQ_FOREACH_SAFE(fle, &hsh->head, fle_hash, fle1) { + /* + * Interrupt thread wants this entry! + * Quick! Quick! Bail out! + */ + if (hsh->mtx.mtx_lock & MTX_CONTESTED) + break; + + /* + * Don't expire aggressively while hash collision + * ratio is predicted small. + */ + if (used <= (NBUCKETS*2) && !INACTIVE(fle)) + break; + + if ((INACTIVE(fle) && (SMALL(fle) || + (used > (NBUCKETS*2)))) || AGED(fle)) { + TAILQ_REMOVE(&hsh->head, fle, fle_hash); + expire_flow(priv, &item, fle, NG_NOFLAGS); + used--; + atomic_add_32(&priv->info.nfinfo_inact_exp, 1); + } + } + mtx_unlock(&hsh->mtx); + } + + if (item != NULL) + return_export_dgram(priv, item, NG_NOFLAGS); + + /* Schedule next expire. */ + callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, + (void *)priv); +} diff --git a/sys/netgraph7/netflow/netflow.h b/sys/netgraph7/netflow/netflow.h new file mode 100644 index 0000000000..cb7a125429 --- /dev/null +++ b/sys/netgraph7/netflow/netflow.h @@ -0,0 +1,129 @@ +/*- + * Copyright (c) 2004 Gleb Smirnoff + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $SourceForge: netflow.h,v 1.8 2004/09/16 17:05:11 glebius Exp $ + * $FreeBSD: src/sys/netgraph/netflow/netflow.h,v 1.4 2006/04/25 20:01:50 maxim Exp $ + */ + +/* netflow timeouts in seconds */ + +#define ACTIVE_TIMEOUT (30*60) /* maximum flow lifetime is 30 min */ +#define INACTIVE_TIMEOUT 15 + +/* + * More info can be found in these Cisco documents: + * + * Cisco IOS NetFlow, White Papers. + * http://www.cisco.com/en/US/products/ps6601/prod_white_papers_list.html + * + * Cisco CNS NetFlow Collection Engine User Guide, 5.0.2, NetFlow Export + * Datagram Formats. + * http://www.cisco.com/en/US/products/sw/netmgtsw/ps1964/products_user_guide_chapter09186a00803f3147.html#wp26453 + * + */ + +#define NETFLOW_V1 1 +#define NETFLOW_V5 5 + +struct netflow_v1_header +{ + uint16_t version; /* NetFlow version */ + uint16_t count; /* Number of records in flow */ + uint32_t sys_uptime; /* System uptime */ + uint32_t unix_secs; /* Current seconds since 0000 UTC 1970 */ + uint32_t unix_nsecs; /* Remaining nanoseconds since 0000 UTC 1970 */ +} __attribute__((__packed__)); + +struct netflow_v5_header +{ + uint16_t version; /* NetFlow version */ + uint16_t count; /* Number of records in flow */ + uint32_t sys_uptime; /* System uptime */ + uint32_t unix_secs; /* Current seconds since 0000 UTC 1970 */ + uint32_t unix_nsecs; /* Remaining nanoseconds since 0000 UTC 1970 */ + uint32_t flow_seq; /* Sequence number of the first record */ + uint8_t engine_type; /* Type of flow switching engine (RP,VIP,etc.) */ + uint8_t engine_id; /* Slot number of the flow switching engine */ + uint16_t pad; /* Pad to word boundary */ +} __attribute__((__packed__)); + +struct netflow_v1_record +{ + uint32_t src_addr; /* Source IP address */ + uint32_t dst_addr; /* Destination IP address */ + uint32_t next_hop; /* Next hop IP address */ + uint16_t in_ifx; /* Source interface index */ + uint16_t out_ifx; /* Destination interface index */ + uint32_t packets; /* Number of packets in a flow */ + uint32_t octets; /* Number of octets in a flow */ + uint32_t first; /* System uptime at start of a flow */ + uint32_t last; /* System uptime at end of a flow */ + uint16_t s_port; /* Source port */ + uint16_t d_port; /* Destination port */ + uint16_t pad1; /* Pad to word boundary */ + uint8_t prot; /* IP protocol */ + uint8_t tos; /* IP type of service */ + uint8_t flags; /* Cumulative OR of tcp flags */ + uint8_t pad2; /* Pad to word boundary */ + uint16_t pad3; /* Pad to word boundary */ + uint8_t reserved[5]; /* Reserved for future use */ +} __attribute__((__packed__)); + +struct netflow_v5_record +{ + uint32_t src_addr; /* Source IP address */ + uint32_t dst_addr; /* Destination IP address */ + uint32_t next_hop; /* Next hop IP address */ + uint16_t i_ifx; /* Source interface index */ + uint16_t o_ifx; /* Destination interface index */ + uint32_t packets; /* Number of packets in a flow */ + uint32_t octets; /* Number of octets in a flow */ + uint32_t first; /* System uptime at start of a flow */ + uint32_t last; /* System uptime at end of a flow */ + uint16_t s_port; /* Source port */ + uint16_t d_port; /* Destination port */ + uint8_t pad1; /* Pad to word boundary */ + uint8_t flags; /* Cumulative OR of tcp flags */ + uint8_t prot; /* IP protocol */ + uint8_t tos; /* IP type of service */ + uint16_t src_as; /* Src peer/origin Autonomous System */ + uint16_t dst_as; /* Dst peer/origin Autonomous System */ + uint8_t src_mask; /* Source route's mask bits */ + uint8_t dst_mask; /* Destination route's mask bits */ + uint16_t pad2; /* Pad to word boundary */ +} __attribute__((__packed__)); + +#define NETFLOW_V1_MAX_RECORDS 24 +#define NETFLOW_V5_MAX_RECORDS 30 + +#define NETFLOW_V1_MAX_SIZE (sizeof(netflow_v1_header)+ \ + sizeof(netflow_v1_record)*NETFLOW_V1_MAX_RECORDS) +#define NETFLOW_V5_MAX_SIZE (sizeof(netflow_v5_header)+ \ + sizeof(netflow_v5_record)*NETFLOW_V5_MAX_RECORDS) + +struct netflow_v5_export_dgram { + struct netflow_v5_header header; + struct netflow_v5_record r[NETFLOW_V5_MAX_RECORDS]; +} __attribute__((__packed__)); diff --git a/sys/netgraph7/netflow/ng_netflow.c b/sys/netgraph7/netflow/ng_netflow.c new file mode 100644 index 0000000000..c853008b4c --- /dev/null +++ b/sys/netgraph7/netflow/ng_netflow.c @@ -0,0 +1,664 @@ +/*- + * Copyright (c) 2004-2005 Gleb Smirnoff + * Copyright (c) 2001-2003 Roman V. Palagin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $SourceForge: ng_netflow.c,v 1.30 2004/09/05 11:37:43 glebius Exp $ + */ + +static const char rcs_id[] = + "@(#) $FreeBSD: src/sys/netgraph/netflow/ng_netflow.c,v 1.17 2008/04/16 16:47:14 kris Exp $"; + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* Netgraph methods */ +static ng_constructor_t ng_netflow_constructor; +static ng_rcvmsg_t ng_netflow_rcvmsg; +static ng_close_t ng_netflow_close; +static ng_shutdown_t ng_netflow_rmnode; +static ng_newhook_t ng_netflow_newhook; +static ng_rcvdata_t ng_netflow_rcvdata; +static ng_disconnect_t ng_netflow_disconnect; + +/* Parse type for struct ng_netflow_info */ +static const struct ng_parse_struct_field ng_netflow_info_type_fields[] + = NG_NETFLOW_INFO_TYPE; +static const struct ng_parse_type ng_netflow_info_type = { + &ng_parse_struct_type, + &ng_netflow_info_type_fields +}; + +/* Parse type for struct ng_netflow_ifinfo */ +static const struct ng_parse_struct_field ng_netflow_ifinfo_type_fields[] + = NG_NETFLOW_IFINFO_TYPE; +static const struct ng_parse_type ng_netflow_ifinfo_type = { + &ng_parse_struct_type, + &ng_netflow_ifinfo_type_fields +}; + +/* Parse type for struct ng_netflow_setdlt */ +static const struct ng_parse_struct_field ng_netflow_setdlt_type_fields[] + = NG_NETFLOW_SETDLT_TYPE; +static const struct ng_parse_type ng_netflow_setdlt_type = { + &ng_parse_struct_type, + &ng_netflow_setdlt_type_fields +}; + +/* Parse type for ng_netflow_setifindex */ +static const struct ng_parse_struct_field ng_netflow_setifindex_type_fields[] + = NG_NETFLOW_SETIFINDEX_TYPE; +static const struct ng_parse_type ng_netflow_setifindex_type = { + &ng_parse_struct_type, + &ng_netflow_setifindex_type_fields +}; + +/* Parse type for ng_netflow_settimeouts */ +static const struct ng_parse_struct_field ng_netflow_settimeouts_type_fields[] + = NG_NETFLOW_SETTIMEOUTS_TYPE; +static const struct ng_parse_type ng_netflow_settimeouts_type = { + &ng_parse_struct_type, + &ng_netflow_settimeouts_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_netflow_cmds[] = { + { + NGM_NETFLOW_COOKIE, + NGM_NETFLOW_INFO, + "info", + NULL, + &ng_netflow_info_type + }, + { + NGM_NETFLOW_COOKIE, + NGM_NETFLOW_IFINFO, + "ifinfo", + &ng_parse_uint16_type, + &ng_netflow_ifinfo_type + }, + { + NGM_NETFLOW_COOKIE, + NGM_NETFLOW_SETDLT, + "setdlt", + &ng_netflow_setdlt_type, + NULL + }, + { + NGM_NETFLOW_COOKIE, + NGM_NETFLOW_SETIFINDEX, + "setifindex", + &ng_netflow_setifindex_type, + NULL + }, + { + NGM_NETFLOW_COOKIE, + NGM_NETFLOW_SETTIMEOUTS, + "settimeouts", + &ng_netflow_settimeouts_type, + NULL + }, + { 0 } +}; + + +/* Netgraph node type descriptor */ +static struct ng_type ng_netflow_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_NETFLOW_NODE_TYPE, + .constructor = ng_netflow_constructor, + .rcvmsg = ng_netflow_rcvmsg, + .close = ng_netflow_close, + .shutdown = ng_netflow_rmnode, + .newhook = ng_netflow_newhook, + .rcvdata = ng_netflow_rcvdata, + .disconnect = ng_netflow_disconnect, + .cmdlist = ng_netflow_cmds, +}; +NETGRAPH_INIT(netflow, &ng_netflow_typestruct); + +/* Called at node creation */ +static int +ng_netflow_constructor(node_p node) +{ + priv_p priv; + int error = 0; + + /* Initialize private data */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT); + if (priv == NULL) + return (ENOMEM); + bzero(priv, sizeof(*priv)); + + /* Make node and its data point at each other */ + NG_NODE_SET_PRIVATE(node, priv); + priv->node = node; + + /* Initialize timeouts to default values */ + priv->info.nfinfo_inact_t = INACTIVE_TIMEOUT; + priv->info.nfinfo_act_t = ACTIVE_TIMEOUT; + + /* Initialize callout handle */ + callout_init(&priv->exp_callout, CALLOUT_MPSAFE); + + /* Allocate memory and set up flow cache */ + if ((error = ng_netflow_cache_init(priv))) + return (error); + + return (0); +} + +/* + * ng_netflow supports two hooks: data and export. + * Incoming traffic is expected on data, and expired + * netflow datagrams are sent to export. + */ +static int +ng_netflow_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (strncmp(name, NG_NETFLOW_HOOK_DATA, /* an iface hook? */ + strlen(NG_NETFLOW_HOOK_DATA)) == 0) { + iface_p iface; + int ifnum = -1; + const char *cp; + char *eptr; + + cp = name + strlen(NG_NETFLOW_HOOK_DATA); + if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) + return (EINVAL); + + ifnum = (int)strtoul(cp, &eptr, 10); + if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES) + return (EINVAL); + + /* See if hook is already connected */ + if (priv->ifaces[ifnum].hook != NULL) + return (EISCONN); + + iface = &priv->ifaces[ifnum]; + + /* Link private info and hook together */ + NG_HOOK_SET_PRIVATE(hook, iface); + iface->hook = hook; + + /* + * In most cases traffic accounting is done on an + * Ethernet interface, so default data link type + * will be DLT_EN10MB. + */ + iface->info.ifinfo_dlt = DLT_EN10MB; + + } else if (strncmp(name, NG_NETFLOW_HOOK_OUT, + strlen(NG_NETFLOW_HOOK_OUT)) == 0) { + iface_p iface; + int ifnum = -1; + const char *cp; + char *eptr; + + cp = name + strlen(NG_NETFLOW_HOOK_OUT); + if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) + return (EINVAL); + + ifnum = (int)strtoul(cp, &eptr, 10); + if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES) + return (EINVAL); + + /* See if hook is already connected */ + if (priv->ifaces[ifnum].out != NULL) + return (EISCONN); + + iface = &priv->ifaces[ifnum]; + + /* Link private info and hook together */ + NG_HOOK_SET_PRIVATE(hook, iface); + iface->out = hook; + + } else if (strcmp(name, NG_NETFLOW_HOOK_EXPORT) == 0) { + + if (priv->export != NULL) + return (EISCONN); + + priv->export = hook; + +#if 0 /* TODO: profile & test first */ + /* + * We send export dgrams in interrupt handlers and in + * callout threads. We'd better queue data for later + * netgraph ISR processing. + */ + NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); +#endif + + /* Exporter is ready. Let's schedule expiry. */ + callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, + (void *)priv); + } else + return (EINVAL); + + return (0); +} + +/* Get a netgraph control message. */ +static int +ng_netflow_rcvmsg (node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + + /* Deal with message according to cookie and command */ + switch (msg->header.typecookie) { + case NGM_NETFLOW_COOKIE: + switch (msg->header.cmd) { + case NGM_NETFLOW_INFO: + { + struct ng_netflow_info *i; + + NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_info), + M_NOWAIT); + i = (struct ng_netflow_info *)resp->data; + ng_netflow_copyinfo(priv, i); + + break; + } + case NGM_NETFLOW_IFINFO: + { + struct ng_netflow_ifinfo *i; + const uint16_t *index; + + if (msg->header.arglen != sizeof(uint16_t)) + ERROUT(EINVAL); + + index = (uint16_t *)msg->data; + if (*index >= NG_NETFLOW_MAXIFACES) + ERROUT(EINVAL); + + /* connected iface? */ + if (priv->ifaces[*index].hook == NULL) + ERROUT(EINVAL); + + NG_MKRESPONSE(resp, msg, + sizeof(struct ng_netflow_ifinfo), M_NOWAIT); + i = (struct ng_netflow_ifinfo *)resp->data; + memcpy((void *)i, (void *)&priv->ifaces[*index].info, + sizeof(priv->ifaces[*index].info)); + + break; + } + case NGM_NETFLOW_SETDLT: + { + struct ng_netflow_setdlt *set; + struct ng_netflow_iface *iface; + + if (msg->header.arglen != sizeof(struct ng_netflow_setdlt)) + ERROUT(EINVAL); + + set = (struct ng_netflow_setdlt *)msg->data; + if (set->iface >= NG_NETFLOW_MAXIFACES) + ERROUT(EINVAL); + iface = &priv->ifaces[set->iface]; + + /* connected iface? */ + if (iface->hook == NULL) + ERROUT(EINVAL); + + switch (set->dlt) { + case DLT_EN10MB: + iface->info.ifinfo_dlt = DLT_EN10MB; + break; + case DLT_RAW: + iface->info.ifinfo_dlt = DLT_RAW; + break; + default: + ERROUT(EINVAL); + } + break; + } + case NGM_NETFLOW_SETIFINDEX: + { + struct ng_netflow_setifindex *set; + struct ng_netflow_iface *iface; + + if (msg->header.arglen != sizeof(struct ng_netflow_setifindex)) + ERROUT(EINVAL); + + set = (struct ng_netflow_setifindex *)msg->data; + if (set->iface >= NG_NETFLOW_MAXIFACES) + ERROUT(EINVAL); + iface = &priv->ifaces[set->iface]; + + /* connected iface? */ + if (iface->hook == NULL) + ERROUT(EINVAL); + + iface->info.ifinfo_index = set->index; + + break; + } + case NGM_NETFLOW_SETTIMEOUTS: + { + struct ng_netflow_settimeouts *set; + + if (msg->header.arglen != sizeof(struct ng_netflow_settimeouts)) + ERROUT(EINVAL); + + set = (struct ng_netflow_settimeouts *)msg->data; + + priv->info.nfinfo_inact_t = set->inactive_timeout; + priv->info.nfinfo_act_t = set->active_timeout; + + break; + } + case NGM_NETFLOW_SHOW: + { + uint32_t *last; + + if (msg->header.arglen != sizeof(uint32_t)) + ERROUT(EINVAL); + + last = (uint32_t *)msg->data; + + NG_MKRESPONSE(resp, msg, NGRESP_SIZE, M_NOWAIT); + + if (!resp) + ERROUT(ENOMEM); + + error = ng_netflow_flow_show(priv, *last, resp); + + break; + } + default: + ERROUT(EINVAL); /* unknown command */ + break; + } + break; + default: + ERROUT(EINVAL); /* incorrect cookie */ + break; + } + + /* + * Take care of synchronous response, if any. + * Free memory and return. + */ +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + + return (error); +} + +/* Receive data on hook. */ +static int +ng_netflow_rcvdata (hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + const iface_p iface = NG_HOOK_PRIVATE(hook); + struct mbuf *m = NULL; + struct ip *ip; + int pullup_len = 0; + int error = 0; + + if (hook == priv->export) { + /* + * Data arrived on export hook. + * This must not happen. + */ + log(LOG_ERR, "ng_netflow: incoming data on export hook!\n"); + ERROUT(EINVAL); + }; + + if (hook == iface->out) { + /* + * Data arrived on out hook. Bypass it. + */ + if (iface->hook == NULL) + ERROUT(ENOTCONN); + + NG_FWD_ITEM_HOOK(error, item, iface->hook); + return (error); + } + + NGI_GET_M(item, m); + + /* Increase counters. */ + iface->info.ifinfo_packets++; + + /* + * Depending on interface data link type and packet contents + * we pullup enough data, so that ng_netflow_flow_add() does not + * need to know about mbuf at all. We keep current length of data + * needed to be contiguous in pullup_len. mtod() is done at the + * very end one more time, since m can had changed after pulluping. + * + * In case of unrecognized data we don't return error, but just + * pass data to downstream hook, if it is available. + */ + +#define M_CHECK(length) do { \ + pullup_len += length; \ + if ((m)->m_pkthdr.len < (pullup_len)) { \ + error = EINVAL; \ + goto bypass; \ + } \ + if ((m)->m_len < (pullup_len) && \ + (((m) = m_pullup((m),(pullup_len))) == NULL)) { \ + error = ENOBUFS; \ + goto done; \ + } \ +} while (0) + + switch (iface->info.ifinfo_dlt) { + case DLT_EN10MB: /* Ethernet */ + { + struct ether_header *eh; + uint16_t etype; + + M_CHECK(sizeof(struct ether_header)); + eh = mtod(m, struct ether_header *); + + /* Make sure this is IP frame. */ + etype = ntohs(eh->ether_type); + switch (etype) { + case ETHERTYPE_IP: + M_CHECK(sizeof(struct ip)); + eh = mtod(m, struct ether_header *); + ip = (struct ip *)(eh + 1); + break; + case ETHERTYPE_VLAN: + { + struct ether_vlan_header *evh; + + M_CHECK(sizeof(struct ether_vlan_header) - + sizeof(struct ether_header)); + evh = mtod(m, struct ether_vlan_header *); + if (ntohs(evh->evl_proto) == ETHERTYPE_IP) { + M_CHECK(sizeof(struct ip)); + ip = (struct ip *)(evh + 1); + break; + } + } + default: + goto bypass; /* pass this frame */ + } + break; + } + case DLT_RAW: /* IP packets */ + M_CHECK(sizeof(struct ip)); + ip = mtod(m, struct ip *); + break; + default: + goto bypass; + break; + } + + if ((ip->ip_off & htons(IP_OFFMASK)) == 0) { + /* + * In case of IP header with options, we haven't pulled + * up enough, yet. + */ + pullup_len += (ip->ip_hl << 2) - sizeof(struct ip); + + switch (ip->ip_p) { + case IPPROTO_TCP: + M_CHECK(sizeof(struct tcphdr)); + break; + case IPPROTO_UDP: + M_CHECK(sizeof(struct udphdr)); + break; + } + } + + switch (iface->info.ifinfo_dlt) { + case DLT_EN10MB: + { + struct ether_header *eh; + + eh = mtod(m, struct ether_header *); + switch (ntohs(eh->ether_type)) { + case ETHERTYPE_IP: + ip = (struct ip *)(eh + 1); + break; + case ETHERTYPE_VLAN: + { + struct ether_vlan_header *evh; + + evh = mtod(m, struct ether_vlan_header *); + ip = (struct ip *)(evh + 1); + break; + } + default: + panic("ng_netflow entered deadcode"); + } + break; + } + case DLT_RAW: + ip = mtod(m, struct ip *); + break; + default: + panic("ng_netflow entered deadcode"); + } + +#undef M_CHECK + + error = ng_netflow_flow_add(priv, ip, iface, m->m_pkthdr.rcvif); + +bypass: + if (iface->out != NULL) { + /* XXX: error gets overwritten here */ + NG_FWD_NEW_DATA(error, item, iface->out, m); + return (error); + } +done: + if (item) + NG_FREE_ITEM(item); + if (m) + NG_FREE_M(m); + + return (error); +} + +/* We will be shut down in a moment */ +static int +ng_netflow_close(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + callout_drain(&priv->exp_callout); + ng_netflow_cache_flush(priv); + + return (0); +} + +/* Do local shutdown processing. */ +static int +ng_netflow_rmnode(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(priv->node); + + FREE(priv, M_NETGRAPH); + + return (0); +} + +/* Hook disconnection. */ +static int +ng_netflow_disconnect(hook_p hook) +{ + node_p node = NG_HOOK_NODE(hook); + priv_p priv = NG_NODE_PRIVATE(node); + iface_p iface = NG_HOOK_PRIVATE(hook); + + if (iface != NULL) { + if (iface->hook == hook) + iface->hook = NULL; + if (iface->out == hook) + iface->out = NULL; + } + + /* if export hook disconnected stop running expire(). */ + if (hook == priv->export) { + callout_drain(&priv->exp_callout); + priv->export = NULL; + } + + /* Removal of the last link destroys the node. */ + if (NG_NODE_NUMHOOKS(node) == 0) + ng_rmnode_self(node); + + return (0); +} diff --git a/sys/netgraph7/netflow/ng_netflow.h b/sys/netgraph7/netflow/ng_netflow.h new file mode 100644 index 0000000000..32a080c614 --- /dev/null +++ b/sys/netgraph7/netflow/ng_netflow.h @@ -0,0 +1,275 @@ +/*- + * Copyright (c) 2004-2005 Gleb Smirnoff + * Copyright (c) 2001-2003 Roman V. Palagin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $SourceForge: ng_netflow.h,v 1.26 2004/09/04 15:44:55 glebius Exp $ + * $FreeBSD: src/sys/netgraph/netflow/ng_netflow.h,v 1.10 2007/03/28 13:59:13 glebius Exp $ + */ + +#ifndef _NG_NETFLOW_H_ +#define _NG_NETFLOW_H_ + +#define NG_NETFLOW_NODE_TYPE "netflow" +#define NGM_NETFLOW_COOKIE 1137078102 + +#define NG_NETFLOW_MAXIFACES USHRT_MAX + +/* Hook names */ + +#define NG_NETFLOW_HOOK_DATA "iface" +#define NG_NETFLOW_HOOK_OUT "out" +#define NG_NETFLOW_HOOK_EXPORT "export" + +/* Netgraph commands understood by netflow node */ +enum { + NGM_NETFLOW_INFO = 1|NGM_READONLY|NGM_HASREPLY, /* get node info */ + NGM_NETFLOW_IFINFO = 2|NGM_READONLY|NGM_HASREPLY, /* get iface info */ + NGM_NETFLOW_SHOW = 3|NGM_READONLY|NGM_HASREPLY, /* show ip cache flow */ + NGM_NETFLOW_SETDLT = 4, /* set data-link type */ + NGM_NETFLOW_SETIFINDEX = 5, /* set interface index */ + NGM_NETFLOW_SETTIMEOUTS = 6, /* set active/inactive flow timeouts */ +}; + +/* This structure is returned by the NGM_NETFLOW_INFO message */ +struct ng_netflow_info { + uint64_t nfinfo_bytes; /* accounted bytes */ + uint32_t nfinfo_packets; /* accounted packets */ + uint32_t nfinfo_used; /* used cache records */ + uint32_t nfinfo_alloc_failed; /* failed allocations */ + uint32_t nfinfo_export_failed; /* failed exports */ + uint32_t nfinfo_act_exp; /* active expiries */ + uint32_t nfinfo_inact_exp; /* inactive expiries */ + uint32_t nfinfo_inact_t; /* flow inactive timeout */ + uint32_t nfinfo_act_t; /* flow active timeout */ +}; + +/* This structure is returned by the NGM_NETFLOW_IFINFO message */ +struct ng_netflow_ifinfo { + uint32_t ifinfo_packets; /* number of packets for this iface */ + uint8_t ifinfo_dlt; /* Data Link Type, DLT_XXX */ +#define MAXDLTNAMELEN 20 + u_int16_t ifinfo_index; /* connected iface index */ +}; + + +/* This structure is passed to NGM_NETFLOW_SETDLT message */ +struct ng_netflow_setdlt { + uint16_t iface; /* which iface dlt change */ + uint8_t dlt; /* DLT_XXX from bpf.h */ +}; + +/* This structure is passed to NGM_NETFLOW_SETIFINDEX */ +struct ng_netflow_setifindex { + u_int16_t iface; /* which iface index change */ + u_int16_t index; /* new index */ +}; + +/* This structure is passed to NGM_NETFLOW_SETTIMEOUTS */ +struct ng_netflow_settimeouts { + uint32_t inactive_timeout; /* flow inactive timeout */ + uint32_t active_timeout; /* flow active timeout */ +}; + +/* This is unique data, which identifies flow */ +struct flow_rec { + struct in_addr r_src; + struct in_addr r_dst; + union { + struct { + uint16_t s_port; /* source TCP/UDP port */ + uint16_t d_port; /* destination TCP/UDP port */ + } dir; + uint32_t both; + } ports; + union { + struct { + u_char prot; /* IP protocol */ + u_char tos; /* IP TOS */ + uint16_t i_ifx; /* input interface index */ + } i; + uint32_t all; + } misc; +}; + +#define r_ip_p misc.i.prot +#define r_tos misc.i.tos +#define r_i_ifx misc.i.i_ifx +#define r_misc misc.all +#define r_ports ports.both +#define r_sport ports.dir.s_port +#define r_dport ports.dir.d_port + +/* A flow entry which accumulates statistics */ +struct flow_entry_data { + struct flow_rec r; + struct in_addr next_hop; + uint16_t fle_o_ifx; /* output interface index */ +#define fle_i_ifx r.misc.i.i_ifx + uint8_t dst_mask; /* destination route mask bits */ + uint8_t src_mask; /* source route mask bits */ + u_long packets; + u_long bytes; + long first; /* uptime on first packet */ + long last; /* uptime on last packet */ + u_char tcp_flags; /* cumulative OR */ +}; + +/* + * How many flow records we will transfer at once + * without overflowing socket receive buffer + */ +#define NREC_AT_ONCE 1000 +#define NGRESP_SIZE (sizeof(struct ngnf_flows) + (NREC_AT_ONCE * \ + sizeof(struct flow_entry_data))) +#define SORCVBUF_SIZE (NGRESP_SIZE + 2 * sizeof(struct ng_mesg)) + +/* This struct is returned to userland, when "show cache ip flow" */ +struct ngnf_flows { + uint32_t nentries; + uint32_t last; + struct flow_entry_data entries[0]; +}; + +/* Everything below is for kernel */ + +#ifdef _KERNEL + +struct flow_entry { + struct flow_entry_data f; + TAILQ_ENTRY(flow_entry) fle_hash; /* entries in hash slot */ +}; + +/* Parsing declarations */ + +/* Parse the info structure */ +#define NG_NETFLOW_INFO_TYPE { \ + { "Bytes", &ng_parse_uint64_type }, \ + { "Packets", &ng_parse_uint32_type }, \ + { "Records used", &ng_parse_uint32_type },\ + { "Failed allocations", &ng_parse_uint32_type },\ + { "Failed exports", &ng_parse_uint32_type },\ + { "Active expiries", &ng_parse_uint32_type },\ + { "Inactive expiries", &ng_parse_uint32_type },\ + { "Inactive timeout", &ng_parse_uint32_type },\ + { "Active timeout", &ng_parse_uint32_type },\ + { NULL } \ +} + +/* Parse the ifinfo structure */ +#define NG_NETFLOW_IFINFO_TYPE { \ + { "packets", &ng_parse_uint32_type }, \ + { "data link type", &ng_parse_uint8_type }, \ + { "index", &ng_parse_uint16_type }, \ + { NULL } \ +} + +/* Parse the setdlt structure */ +#define NG_NETFLOW_SETDLT_TYPE { \ + { "iface", &ng_parse_uint16_type }, \ + { "dlt", &ng_parse_uint8_type }, \ + { NULL } \ +} + +/* Parse the setifindex structure */ +#define NG_NETFLOW_SETIFINDEX_TYPE { \ + { "iface", &ng_parse_uint16_type }, \ + { "index", &ng_parse_uint16_type }, \ + { NULL } \ +} + +/* Parse the settimeouts structure */ +#define NG_NETFLOW_SETTIMEOUTS_TYPE { \ + { "inactive", &ng_parse_uint32_type }, \ + { "active", &ng_parse_uint32_type }, \ + { NULL } \ +} + +/* Private hook data */ +struct ng_netflow_iface { + hook_p hook; /* NULL when disconnected */ + hook_p out; /* NULL when no bypass hook */ + struct ng_netflow_ifinfo info; +}; + +typedef struct ng_netflow_iface *iface_p; +typedef struct ng_netflow_ifinfo *ifinfo_p; + +/* Structure describing our flow engine */ +struct netflow { + node_p node; /* link to the node itself */ + hook_p export; /* export data goes there */ + + struct ng_netflow_info info; + struct callout exp_callout; /* expiry periodic job */ + + /* + * Flow entries are allocated in uma(9) zone zone. They are + * indexed by hash hash. Each hash element consist of tailqueue + * head and mutex to protect this element. + */ +#define CACHESIZE (65536*4) +#define CACHELOWAT (CACHESIZE * 3/4) +#define CACHEHIGHWAT (CACHESIZE * 9/10) + uma_zone_t zone; + struct flow_hash_entry *hash; + + /* + * NetFlow data export + * + * export_item is a data item, it has an mbuf with cluster + * attached to it. A thread detaches export_item from priv + * and works with it. If the export is full it is sent, and + * a new one is allocated. Before exiting thread re-attaches + * its current item back to priv. If there is item already, + * current incomplete datagram is sent. + * export_mtx is used for attaching/detaching. + */ + item_p export_item; + struct mtx export_mtx; + uint32_t flow_seq; /* current flow sequence */ + + struct ng_netflow_iface ifaces[NG_NETFLOW_MAXIFACES]; +}; + +typedef struct netflow *priv_p; + +/* Header of a small list in hash cell */ +struct flow_hash_entry { + struct mtx mtx; + TAILQ_HEAD(fhead, flow_entry) head; +}; + +#define ERROUT(x) { error = (x); goto done; } + +/* Prototypes for netflow.c */ +int ng_netflow_cache_init(priv_p); +void ng_netflow_cache_flush(priv_p); +void ng_netflow_copyinfo(priv_p, struct ng_netflow_info *); +timeout_t ng_netflow_expire; +int ng_netflow_flow_add(priv_p, struct ip *, iface_p, struct ifnet *); +int ng_netflow_flow_show(priv_p, uint32_t last, struct ng_mesg *); + +#endif /* _KERNEL */ +#endif /* _NG_NETFLOW_H_ */ diff --git a/sys/netgraph7/netgraph.h b/sys/netgraph7/netgraph.h new file mode 100644 index 0000000000..3f1284afa8 --- /dev/null +++ b/sys/netgraph7/netgraph.h @@ -0,0 +1,1186 @@ +/* + * netgraph.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/netgraph.h,v 1.74 2008/06/24 18:49:49 gnn Exp $ + * $Whistle: netgraph.h,v 1.29 1999/11/01 07:56:13 julian Exp $ + */ + +#ifndef _NETGRAPH_NETGRAPH_H_ +#define _NETGRAPH_NETGRAPH_H_ + +#ifndef _KERNEL +#error "This file should not be included in user level programs" +#endif + +#include +#include +#include +#include +#include + +#ifdef HAVE_KERNEL_OPTION_HEADERS +#include "opt_netgraph.h" +#endif + +/* debugging options */ +#define NG_SEPARATE_MALLOC /* make modules use their own malloc types */ + +/* + * This defines the in-kernel binary interface version. + * It is possible to change this but leave the external message + * API the same. Each type also has it's own cookies for versioning as well. + * Change it for NETGRAPH_DEBUG version so we cannot mix debug and non debug + * modules. + */ +#define _NG_ABI_VERSION 12 +#ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ +#define NG_ABI_VERSION (_NG_ABI_VERSION + 0x10000) +#else /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ +#define NG_ABI_VERSION _NG_ABI_VERSION +#endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ + + +/* + * Forward references for the basic structures so we can + * define the typedefs and use them in the structures themselves. + */ +struct ng_hook ; +struct ng_node ; +struct ng_item ; +typedef struct ng_item *item_p; +typedef struct ng_node *node_p; +typedef struct ng_hook *hook_p; + +/* node method definitions */ +typedef int ng_constructor_t(node_p node); +typedef int ng_close_t(node_p node); +typedef int ng_shutdown_t(node_p node); +typedef int ng_newhook_t(node_p node, hook_p hook, const char *name); +typedef hook_p ng_findhook_t(node_p node, const char *name); +typedef int ng_connect_t(hook_p hook); +typedef int ng_rcvmsg_t(node_p node, item_p item, hook_p lasthook); +typedef int ng_rcvdata_t(hook_p hook, item_p item); +typedef int ng_disconnect_t(hook_p hook); +typedef int ng_rcvitem (node_p node, hook_p hook, item_p item); + +/*********************************************************************** + ***************** Hook Structure and Methods ************************** + *********************************************************************** + * + * Structure of a hook + */ +struct ng_hook { + char hk_name[NG_HOOKSIZ]; /* what this node knows this link as */ + void *hk_private; /* node dependant ID for this hook */ + int hk_flags; /* info about this hook/link */ + int hk_type; /* tbd: hook data link type */ + struct ng_hook *hk_peer; /* the other end of this link */ + struct ng_node *hk_node; /* The node this hook is attached to */ + LIST_ENTRY(ng_hook) hk_hooks; /* linked list of all hooks on node */ + ng_rcvmsg_t *hk_rcvmsg; /* control messages come here */ + ng_rcvdata_t *hk_rcvdata; /* data comes here */ + int hk_refs; /* dont actually free this till 0 */ +#ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ +#define HK_MAGIC 0x78573011 + int hk_magic; + char *lastfile; + int lastline; + SLIST_ENTRY(ng_hook) hk_all; /* all existing items */ +#endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ +}; +/* Flags for a hook */ +#define HK_INVALID 0x0001 /* don't trust it! */ +#define HK_QUEUE 0x0002 /* queue for later delivery */ +#define HK_FORCE_WRITER 0x0004 /* Incoming data queued as a writer */ +#define HK_DEAD 0x0008 /* This is the dead hook.. don't free */ +#define HK_HI_STACK 0x0010 /* Hook has hi stack usage */ + +/* + * Public Methods for hook + * If you can't do it with these you probably shouldn;t be doing it. + */ +void ng_unref_hook(hook_p hook); /* don't move this */ +#define _NG_HOOK_REF(hook) atomic_add_int(&(hook)->hk_refs, 1) +#define _NG_HOOK_NAME(hook) ((hook)->hk_name) +#define _NG_HOOK_UNREF(hook) ng_unref_hook(hook) +#define _NG_HOOK_SET_PRIVATE(hook, val) do {(hook)->hk_private = val;} while (0) +#define _NG_HOOK_SET_RCVMSG(hook, val) do {(hook)->hk_rcvmsg = val;} while (0) +#define _NG_HOOK_SET_RCVDATA(hook, val) do {(hook)->hk_rcvdata = val;} while (0) +#define _NG_HOOK_PRIVATE(hook) ((hook)->hk_private) +#define _NG_HOOK_NOT_VALID(hook) ((hook)->hk_flags & HK_INVALID) +#define _NG_HOOK_IS_VALID(hook) (!((hook)->hk_flags & HK_INVALID)) +#define _NG_HOOK_NODE(hook) ((hook)->hk_node) /* only rvalue! */ +#define _NG_HOOK_PEER(hook) ((hook)->hk_peer) /* only rvalue! */ +#define _NG_HOOK_FORCE_WRITER(hook) \ + do { hook->hk_flags |= HK_FORCE_WRITER; } while (0) +#define _NG_HOOK_FORCE_QUEUE(hook) do { hook->hk_flags |= HK_QUEUE; } while (0) +#define _NG_HOOK_HI_STACK(hook) do { hook->hk_flags |= HK_HI_STACK; } while (0) + +/* Some shortcuts */ +#define NG_PEER_NODE(hook) NG_HOOK_NODE(NG_HOOK_PEER(hook)) +#define NG_PEER_HOOK_NAME(hook) NG_HOOK_NAME(NG_HOOK_PEER(hook)) +#define NG_PEER_NODE_NAME(hook) NG_NODE_NAME(NG_PEER_NODE(hook)) + +#ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ +#define _NN_ __FILE__,__LINE__ +void dumphook (hook_p hook, char *file, int line); +static __inline void _chkhook(hook_p hook, char *file, int line); +static __inline void _ng_hook_ref(hook_p hook, char * file, int line); +static __inline char * _ng_hook_name(hook_p hook, char * file, int line); +static __inline void _ng_hook_unref(hook_p hook, char * file, int line); +static __inline void _ng_hook_set_private(hook_p hook, + void * val, char * file, int line); +static __inline void _ng_hook_set_rcvmsg(hook_p hook, + ng_rcvmsg_t *val, char * file, int line); +static __inline void _ng_hook_set_rcvdata(hook_p hook, + ng_rcvdata_t *val, char * file, int line); +static __inline void * _ng_hook_private(hook_p hook, char * file, int line); +static __inline int _ng_hook_not_valid(hook_p hook, char * file, int line); +static __inline int _ng_hook_is_valid(hook_p hook, char * file, int line); +static __inline node_p _ng_hook_node(hook_p hook, char * file, int line); +static __inline hook_p _ng_hook_peer(hook_p hook, char * file, int line); +static __inline void _ng_hook_force_writer(hook_p hook, char * file, + int line); +static __inline void _ng_hook_force_queue(hook_p hook, char * file, int line); + +static __inline void +_chkhook(hook_p hook, char *file, int line) +{ + if (hook->hk_magic != HK_MAGIC) { + printf("Accessing freed hook "); + dumphook(hook, file, line); + } + hook->lastline = line; + hook->lastfile = file; +} + +static __inline void +_ng_hook_ref(hook_p hook, char * file, int line) +{ + _chkhook(hook, file, line); + _NG_HOOK_REF(hook); +} + +static __inline char * +_ng_hook_name(hook_p hook, char * file, int line) +{ + _chkhook(hook, file, line); + return (_NG_HOOK_NAME(hook)); +} + +static __inline void +_ng_hook_unref(hook_p hook, char * file, int line) +{ + _chkhook(hook, file, line); + _NG_HOOK_UNREF(hook); +} + +static __inline void +_ng_hook_set_private(hook_p hook, void *val, char * file, int line) +{ + _chkhook(hook, file, line); + _NG_HOOK_SET_PRIVATE(hook, val); +} + +static __inline void +_ng_hook_set_rcvmsg(hook_p hook, ng_rcvmsg_t *val, char * file, int line) +{ + _chkhook(hook, file, line); + _NG_HOOK_SET_RCVMSG(hook, val); +} + +static __inline void +_ng_hook_set_rcvdata(hook_p hook, ng_rcvdata_t *val, char * file, int line) +{ + _chkhook(hook, file, line); + _NG_HOOK_SET_RCVDATA(hook, val); +} + +static __inline void * +_ng_hook_private(hook_p hook, char * file, int line) +{ + _chkhook(hook, file, line); + return (_NG_HOOK_PRIVATE(hook)); +} + +static __inline int +_ng_hook_not_valid(hook_p hook, char * file, int line) +{ + _chkhook(hook, file, line); + return (_NG_HOOK_NOT_VALID(hook)); +} + +static __inline int +_ng_hook_is_valid(hook_p hook, char * file, int line) +{ + _chkhook(hook, file, line); + return (_NG_HOOK_IS_VALID(hook)); +} + +static __inline node_p +_ng_hook_node(hook_p hook, char * file, int line) +{ + _chkhook(hook, file, line); + return (_NG_HOOK_NODE(hook)); +} + +static __inline hook_p +_ng_hook_peer(hook_p hook, char * file, int line) +{ + _chkhook(hook, file, line); + return (_NG_HOOK_PEER(hook)); +} + +static __inline void +_ng_hook_force_writer(hook_p hook, char * file, int line) +{ + _chkhook(hook, file, line); + _NG_HOOK_FORCE_WRITER(hook); +} + +static __inline void +_ng_hook_force_queue(hook_p hook, char * file, int line) +{ + _chkhook(hook, file, line); + _NG_HOOK_FORCE_QUEUE(hook); +} + +static __inline void +_ng_hook_hi_stack(hook_p hook, char * file, int line) +{ + _chkhook(hook, file, line); + _NG_HOOK_HI_STACK(hook); +} + + +#define NG_HOOK_REF(hook) _ng_hook_ref(hook, _NN_) +#define NG_HOOK_NAME(hook) _ng_hook_name(hook, _NN_) +#define NG_HOOK_UNREF(hook) _ng_hook_unref(hook, _NN_) +#define NG_HOOK_SET_PRIVATE(hook, val) _ng_hook_set_private(hook, val, _NN_) +#define NG_HOOK_SET_RCVMSG(hook, val) _ng_hook_set_rcvmsg(hook, val, _NN_) +#define NG_HOOK_SET_RCVDATA(hook, val) _ng_hook_set_rcvdata(hook, val, _NN_) +#define NG_HOOK_PRIVATE(hook) _ng_hook_private(hook, _NN_) +#define NG_HOOK_NOT_VALID(hook) _ng_hook_not_valid(hook, _NN_) +#define NG_HOOK_IS_VALID(hook) _ng_hook_is_valid(hook, _NN_) +#define NG_HOOK_NODE(hook) _ng_hook_node(hook, _NN_) +#define NG_HOOK_PEER(hook) _ng_hook_peer(hook, _NN_) +#define NG_HOOK_FORCE_WRITER(hook) _ng_hook_force_writer(hook, _NN_) +#define NG_HOOK_FORCE_QUEUE(hook) _ng_hook_force_queue(hook, _NN_) +#define NG_HOOK_HI_STACK(hook) _ng_hook_hi_stack(hook, _NN_) + +#else /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ + +#define NG_HOOK_REF(hook) _NG_HOOK_REF(hook) +#define NG_HOOK_NAME(hook) _NG_HOOK_NAME(hook) +#define NG_HOOK_UNREF(hook) _NG_HOOK_UNREF(hook) +#define NG_HOOK_SET_PRIVATE(hook, val) _NG_HOOK_SET_PRIVATE(hook, val) +#define NG_HOOK_SET_RCVMSG(hook, val) _NG_HOOK_SET_RCVMSG(hook, val) +#define NG_HOOK_SET_RCVDATA(hook, val) _NG_HOOK_SET_RCVDATA(hook, val) +#define NG_HOOK_PRIVATE(hook) _NG_HOOK_PRIVATE(hook) +#define NG_HOOK_NOT_VALID(hook) _NG_HOOK_NOT_VALID(hook) +#define NG_HOOK_IS_VALID(hook) _NG_HOOK_IS_VALID(hook) +#define NG_HOOK_NODE(hook) _NG_HOOK_NODE(hook) +#define NG_HOOK_PEER(hook) _NG_HOOK_PEER(hook) +#define NG_HOOK_FORCE_WRITER(hook) _NG_HOOK_FORCE_WRITER(hook) +#define NG_HOOK_FORCE_QUEUE(hook) _NG_HOOK_FORCE_QUEUE(hook) +#define NG_HOOK_HI_STACK(hook) _NG_HOOK_HI_STACK(hook) + +#endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ + +/*********************************************************************** + ***************** Node Structure and Methods ************************** + *********************************************************************** + * Structure of a node + * including the eembedded queue structure. + * + * The structure for queueing Netgraph request items + * embedded in the node structure + */ +struct ng_queue { + u_int q_flags; /* Current r/w/q lock flags */ + u_int q_flags2; /* Other queue flags */ + struct mtx q_mtx; + STAILQ_ENTRY(ng_node) q_work; /* nodes with work to do */ + STAILQ_HEAD(, ng_item) queue; /* actually items queue */ +}; + +struct ng_node { + char nd_name[NG_NODESIZ]; /* optional globally unique name */ + struct ng_type *nd_type; /* the installed 'type' */ + int nd_flags; /* see below for bit definitions */ + int nd_numhooks; /* number of hooks */ + void *nd_private; /* node type dependant node ID */ + ng_ID_t nd_ID; /* Unique per node */ + LIST_HEAD(hooks, ng_hook) nd_hooks; /* linked list of node hooks */ + LIST_ENTRY(ng_node) nd_nodes; /* linked list of all nodes */ + LIST_ENTRY(ng_node) nd_idnodes; /* ID hash collision list */ + struct ng_queue nd_input_queue; /* input queue for locking */ + int nd_refs; /* # of references to this node */ +#ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ +#define ND_MAGIC 0x59264837 + int nd_magic; + char *lastfile; + int lastline; + SLIST_ENTRY(ng_node) nd_all; /* all existing nodes */ +#endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ +}; + +/* Flags for a node */ +#define NGF_INVALID 0x00000001 /* free when refs go to 0 */ +#define NG_INVALID NGF_INVALID /* compat for old code */ +#define NGF_FORCE_WRITER 0x00000004 /* Never multithread this node */ +#define NG_FORCE_WRITER NGF_FORCE_WRITER /* compat for old code */ +#define NGF_CLOSING 0x00000008 /* ng_rmnode() at work */ +#define NG_CLOSING NGF_CLOSING /* compat for old code */ +#define NGF_REALLY_DIE 0x00000010 /* "persistent" node is unloading */ +#define NG_REALLY_DIE NGF_REALLY_DIE /* compat for old code */ +#define NGF_HI_STACK 0x00000020 /* node has hi stack usage */ +#define NGF_TYPE1 0x10000000 /* reserved for type specific storage */ +#define NGF_TYPE2 0x20000000 /* reserved for type specific storage */ +#define NGF_TYPE3 0x40000000 /* reserved for type specific storage */ +#define NGF_TYPE4 0x80000000 /* reserved for type specific storage */ + +/* + * Public methods for nodes. + * If you can't do it with these you probably shouldn't be doing it. + */ +int ng_unref_node(node_p node); /* don't move this */ +#define _NG_NODE_NAME(node) ((node)->nd_name + 0) +#define _NG_NODE_HAS_NAME(node) ((node)->nd_name[0] + 0) +#define _NG_NODE_ID(node) ((node)->nd_ID + 0) +#define _NG_NODE_REF(node) atomic_add_int(&(node)->nd_refs, 1) +#define _NG_NODE_UNREF(node) ng_unref_node(node) +#define _NG_NODE_SET_PRIVATE(node, val) do {(node)->nd_private = val;} while (0) +#define _NG_NODE_PRIVATE(node) ((node)->nd_private) +#define _NG_NODE_IS_VALID(node) (!((node)->nd_flags & NGF_INVALID)) +#define _NG_NODE_NOT_VALID(node) ((node)->nd_flags & NGF_INVALID) +#define _NG_NODE_NUMHOOKS(node) ((node)->nd_numhooks + 0) /* rvalue */ +#define _NG_NODE_FORCE_WRITER(node) \ + do{ node->nd_flags |= NGF_FORCE_WRITER; }while (0) +#define _NG_NODE_HI_STACK(node) \ + do{ node->nd_flags |= NGF_HI_STACK; }while (0) +#define _NG_NODE_REALLY_DIE(node) \ + do{ node->nd_flags |= (NGF_REALLY_DIE|NGF_INVALID); }while (0) +#define _NG_NODE_REVIVE(node) \ + do { node->nd_flags &= ~NGF_INVALID; } while (0) +/* + * The hook iterator. + * This macro will call a function of type ng_fn_eachhook for each + * hook attached to the node. If the function returns 0, then the + * iterator will stop and return a pointer to the hook that returned 0. + */ +typedef int ng_fn_eachhook(hook_p hook, void* arg); +#define _NG_NODE_FOREACH_HOOK(node, fn, arg, rethook) \ + do { \ + hook_p _hook; \ + (rethook) = NULL; \ + LIST_FOREACH(_hook, &((node)->nd_hooks), hk_hooks) { \ + if ((fn)(_hook, arg) == 0) { \ + (rethook) = _hook; \ + break; \ + } \ + } \ + } while (0) + +#ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ +void dumpnode(node_p node, char *file, int line); +static __inline void _chknode(node_p node, char *file, int line); +static __inline char * _ng_node_name(node_p node, char *file, int line); +static __inline int _ng_node_has_name(node_p node, char *file, int line); +static __inline ng_ID_t _ng_node_id(node_p node, char *file, int line); +static __inline void _ng_node_ref(node_p node, char *file, int line); +static __inline int _ng_node_unref(node_p node, char *file, int line); +static __inline void _ng_node_set_private(node_p node, void * val, + char *file, int line); +static __inline void * _ng_node_private(node_p node, char *file, int line); +static __inline int _ng_node_is_valid(node_p node, char *file, int line); +static __inline int _ng_node_not_valid(node_p node, char *file, int line); +static __inline int _ng_node_numhooks(node_p node, char *file, int line); +static __inline void _ng_node_force_writer(node_p node, char *file, int line); +static __inline hook_p _ng_node_foreach_hook(node_p node, + ng_fn_eachhook *fn, void *arg, char *file, int line); +static __inline void _ng_node_revive(node_p node, char *file, int line); + +static __inline void +_chknode(node_p node, char *file, int line) +{ + if (node->nd_magic != ND_MAGIC) { + printf("Accessing freed node "); + dumpnode(node, file, line); + } + node->lastline = line; + node->lastfile = file; +} + +static __inline char * +_ng_node_name(node_p node, char *file, int line) +{ + _chknode(node, file, line); + return(_NG_NODE_NAME(node)); +} + +static __inline int +_ng_node_has_name(node_p node, char *file, int line) +{ + _chknode(node, file, line); + return(_NG_NODE_HAS_NAME(node)); +} + +static __inline ng_ID_t +_ng_node_id(node_p node, char *file, int line) +{ + _chknode(node, file, line); + return(_NG_NODE_ID(node)); +} + +static __inline void +_ng_node_ref(node_p node, char *file, int line) +{ + _chknode(node, file, line); + _NG_NODE_REF(node); +} + +static __inline int +_ng_node_unref(node_p node, char *file, int line) +{ + _chknode(node, file, line); + return (_NG_NODE_UNREF(node)); +} + +static __inline void +_ng_node_set_private(node_p node, void * val, char *file, int line) +{ + _chknode(node, file, line); + _NG_NODE_SET_PRIVATE(node, val); +} + +static __inline void * +_ng_node_private(node_p node, char *file, int line) +{ + _chknode(node, file, line); + return (_NG_NODE_PRIVATE(node)); +} + +static __inline int +_ng_node_is_valid(node_p node, char *file, int line) +{ + _chknode(node, file, line); + return(_NG_NODE_IS_VALID(node)); +} + +static __inline int +_ng_node_not_valid(node_p node, char *file, int line) +{ + _chknode(node, file, line); + return(_NG_NODE_NOT_VALID(node)); +} + +static __inline int +_ng_node_numhooks(node_p node, char *file, int line) +{ + _chknode(node, file, line); + return(_NG_NODE_NUMHOOKS(node)); +} + +static __inline void +_ng_node_force_writer(node_p node, char *file, int line) +{ + _chknode(node, file, line); + _NG_NODE_FORCE_WRITER(node); +} + +static __inline void +_ng_node_hi_stack(node_p node, char *file, int line) +{ + _chknode(node, file, line); + _NG_NODE_HI_STACK(node); +} + +static __inline void +_ng_node_really_die(node_p node, char *file, int line) +{ + _chknode(node, file, line); + _NG_NODE_REALLY_DIE(node); +} + +static __inline void +_ng_node_revive(node_p node, char *file, int line) +{ + _chknode(node, file, line); + _NG_NODE_REVIVE(node); +} + +static __inline hook_p +_ng_node_foreach_hook(node_p node, ng_fn_eachhook *fn, void *arg, + char *file, int line) +{ + hook_p hook; + _chknode(node, file, line); + _NG_NODE_FOREACH_HOOK(node, fn, arg, hook); + return (hook); +} + +#define NG_NODE_NAME(node) _ng_node_name(node, _NN_) +#define NG_NODE_HAS_NAME(node) _ng_node_has_name(node, _NN_) +#define NG_NODE_ID(node) _ng_node_id(node, _NN_) +#define NG_NODE_REF(node) _ng_node_ref(node, _NN_) +#define NG_NODE_UNREF(node) _ng_node_unref(node, _NN_) +#define NG_NODE_SET_PRIVATE(node, val) _ng_node_set_private(node, val, _NN_) +#define NG_NODE_PRIVATE(node) _ng_node_private(node, _NN_) +#define NG_NODE_IS_VALID(node) _ng_node_is_valid(node, _NN_) +#define NG_NODE_NOT_VALID(node) _ng_node_not_valid(node, _NN_) +#define NG_NODE_FORCE_WRITER(node) _ng_node_force_writer(node, _NN_) +#define NG_NODE_HI_STACK(node) _ng_node_hi_stack(node, _NN_) +#define NG_NODE_REALLY_DIE(node) _ng_node_really_die(node, _NN_) +#define NG_NODE_NUMHOOKS(node) _ng_node_numhooks(node, _NN_) +#define NG_NODE_REVIVE(node) _ng_node_revive(node, _NN_) +#define NG_NODE_FOREACH_HOOK(node, fn, arg, rethook) \ + do { \ + rethook = _ng_node_foreach_hook(node, fn, (void *)arg, _NN_); \ + } while (0) + +#else /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ + +#define NG_NODE_NAME(node) _NG_NODE_NAME(node) +#define NG_NODE_HAS_NAME(node) _NG_NODE_HAS_NAME(node) +#define NG_NODE_ID(node) _NG_NODE_ID(node) +#define NG_NODE_REF(node) _NG_NODE_REF(node) +#define NG_NODE_UNREF(node) _NG_NODE_UNREF(node) +#define NG_NODE_SET_PRIVATE(node, val) _NG_NODE_SET_PRIVATE(node, val) +#define NG_NODE_PRIVATE(node) _NG_NODE_PRIVATE(node) +#define NG_NODE_IS_VALID(node) _NG_NODE_IS_VALID(node) +#define NG_NODE_NOT_VALID(node) _NG_NODE_NOT_VALID(node) +#define NG_NODE_FORCE_WRITER(node) _NG_NODE_FORCE_WRITER(node) +#define NG_NODE_HI_STACK(node) _NG_NODE_HI_STACK(node) +#define NG_NODE_REALLY_DIE(node) _NG_NODE_REALLY_DIE(node) +#define NG_NODE_NUMHOOKS(node) _NG_NODE_NUMHOOKS(node) +#define NG_NODE_REVIVE(node) _NG_NODE_REVIVE(node) +#define NG_NODE_FOREACH_HOOK(node, fn, arg, rethook) \ + _NG_NODE_FOREACH_HOOK(node, fn, arg, rethook) +#endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ + +/*********************************************************************** + ************* Node Queue and Item Structures and Methods ************** + *********************************************************************** + * + */ +typedef void ng_item_fn(node_p node, hook_p hook, void *arg1, int arg2); +typedef int ng_item_fn2(node_p node, struct ng_item *item, hook_p hook); +typedef void ng_apply_t(void *context, int error); +struct ng_apply_info { + ng_apply_t *apply; + void *context; + int refs; + int error; +}; +struct ng_item { + u_long el_flags; + STAILQ_ENTRY(ng_item) el_next; + node_p el_dest; /* The node it will be applied against (or NULL) */ + hook_p el_hook; /* Entering hook. Optional in Control messages */ + union { + struct mbuf *da_m; + struct { + struct ng_mesg *msg_msg; + ng_ID_t msg_retaddr; + } msg; + struct { + union { + ng_item_fn *fn_fn; + ng_item_fn2 *fn_fn2; + } fn_fn; + void *fn_arg1; + int fn_arg2; + } fn; + } body; + /* + * Optional callback called when item is being applied, + * and its context. + */ + struct ng_apply_info *apply; + u_int depth; +#ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ + char *lastfile; + int lastline; + TAILQ_ENTRY(ng_item) all; /* all existing items */ +#endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ +}; + +#define NGQF_TYPE 0x03 /* MASK of content definition */ +#define NGQF_MESG 0x00 /* the queue element is a message */ +#define NGQF_DATA 0x01 /* the queue element is data */ +#define NGQF_FN 0x02 /* the queue element is a function */ +#define NGQF_FN2 0x03 /* the queue element is a new function */ + +#define NGQF_RW 0x04 /* MASK for wanted queue mode */ +#define NGQF_READER 0x04 /* wants to be a reader */ +#define NGQF_WRITER 0x00 /* wants to be a writer */ + +#define NGQF_QMODE 0x08 /* MASK for how it was queued */ +#define NGQF_QREADER 0x08 /* was queued as a reader */ +#define NGQF_QWRITER 0x00 /* was queued as a writer */ + +/* + * Get the mbuf (etc) out of an item. + * Sets the value in the item to NULL in case we need to call NG_FREE_ITEM() + * with it, (to avoid freeing the things twice). + * If you don't want to zero out the item then realise that the + * item still owns it. + * Retaddr is different. There are no references on that. It's just a number. + * The debug versions must be either all used everywhere or not at all. + */ + +#define _NGI_M(i) ((i)->body.da_m) +#define _NGI_MSG(i) ((i)->body.msg.msg_msg) +#define _NGI_RETADDR(i) ((i)->body.msg.msg_retaddr) +#define _NGI_FN(i) ((i)->body.fn.fn_fn.fn_fn) +#define _NGI_FN2(i) ((i)->body.fn.fn_fn.fn_fn2) +#define _NGI_ARG1(i) ((i)->body.fn.fn_arg1) +#define _NGI_ARG2(i) ((i)->body.fn.fn_arg2) +#define _NGI_NODE(i) ((i)->el_dest) +#define _NGI_HOOK(i) ((i)->el_hook) +#define _NGI_SET_HOOK(i,h) do { _NGI_HOOK(i) = h; h = NULL;} while (0) +#define _NGI_CLR_HOOK(i) do { \ + hook_p _hook = _NGI_HOOK(i); \ + if (_hook) { \ + _NG_HOOK_UNREF(_hook); \ + _NGI_HOOK(i) = NULL; \ + } \ + } while (0) +#define _NGI_SET_NODE(i,n) do { _NGI_NODE(i) = n; n = NULL;} while (0) +#define _NGI_CLR_NODE(i) do { \ + node_p _node = _NGI_NODE(i); \ + if (_node) { \ + _NG_NODE_UNREF(_node); \ + _NGI_NODE(i) = NULL; \ + } \ + } while (0) + +#ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ +void dumpitem(item_p item, char *file, int line); +static __inline void _ngi_check(item_p item, char *file, int line) ; +static __inline struct mbuf ** _ngi_m(item_p item, char *file, int line) ; +static __inline ng_ID_t * _ngi_retaddr(item_p item, char *file, int line); +static __inline struct ng_mesg ** _ngi_msg(item_p item, char *file, int line) ; +static __inline ng_item_fn ** _ngi_fn(item_p item, char *file, int line) ; +static __inline ng_item_fn2 ** _ngi_fn2(item_p item, char *file, int line) ; +static __inline void ** _ngi_arg1(item_p item, char *file, int line) ; +static __inline int * _ngi_arg2(item_p item, char *file, int line) ; +static __inline node_p _ngi_node(item_p item, char *file, int line); +static __inline hook_p _ngi_hook(item_p item, char *file, int line); + +static __inline void +_ngi_check(item_p item, char *file, int line) +{ + (item)->lastline = line; + (item)->lastfile = file; +} + +static __inline struct mbuf ** +_ngi_m(item_p item, char *file, int line) +{ + _ngi_check(item, file, line); + return (&_NGI_M(item)); +} + +static __inline struct ng_mesg ** +_ngi_msg(item_p item, char *file, int line) +{ + _ngi_check(item, file, line); + return (&_NGI_MSG(item)); +} + +static __inline ng_ID_t * +_ngi_retaddr(item_p item, char *file, int line) +{ + _ngi_check(item, file, line); + return (&_NGI_RETADDR(item)); +} + +static __inline ng_item_fn ** +_ngi_fn(item_p item, char *file, int line) +{ + _ngi_check(item, file, line); + return (&_NGI_FN(item)); +} + +static __inline ng_item_fn2 ** +_ngi_fn2(item_p item, char *file, int line) +{ + _ngi_check(item, file, line); + return (&_NGI_FN2(item)); +} + +static __inline void ** +_ngi_arg1(item_p item, char *file, int line) +{ + _ngi_check(item, file, line); + return (&_NGI_ARG1(item)); +} + +static __inline int * +_ngi_arg2(item_p item, char *file, int line) +{ + _ngi_check(item, file, line); + return (&_NGI_ARG2(item)); +} + +static __inline node_p +_ngi_node(item_p item, char *file, int line) +{ + _ngi_check(item, file, line); + return (_NGI_NODE(item)); +} + +static __inline hook_p +_ngi_hook(item_p item, char *file, int line) +{ + _ngi_check(item, file, line); + return (_NGI_HOOK(item)); +} + +#define NGI_M(i) (*_ngi_m(i, _NN_)) +#define NGI_MSG(i) (*_ngi_msg(i, _NN_)) +#define NGI_RETADDR(i) (*_ngi_retaddr(i, _NN_)) +#define NGI_FN(i) (*_ngi_fn(i, _NN_)) +#define NGI_FN2(i) (*_ngi_fn2(i, _NN_)) +#define NGI_ARG1(i) (*_ngi_arg1(i, _NN_)) +#define NGI_ARG2(i) (*_ngi_arg2(i, _NN_)) +#define NGI_HOOK(i) _ngi_hook(i, _NN_) +#define NGI_NODE(i) _ngi_node(i, _NN_) +#define NGI_SET_HOOK(i,h) \ + do { _ngi_check(i, _NN_); _NGI_SET_HOOK(i, h); } while (0) +#define NGI_CLR_HOOK(i) \ + do { _ngi_check(i, _NN_); _NGI_CLR_HOOK(i); } while (0) +#define NGI_SET_NODE(i,n) \ + do { _ngi_check(i, _NN_); _NGI_SET_NODE(i, n); } while (0) +#define NGI_CLR_NODE(i) \ + do { _ngi_check(i, _NN_); _NGI_CLR_NODE(i); } while (0) + +#define NG_FREE_ITEM(item) \ + do { \ + _ngi_check(item, _NN_); \ + ng_free_item((item)); \ + } while (0) + +#define SAVE_LINE(item) \ + do { \ + (item)->lastline = __LINE__; \ + (item)->lastfile = __FILE__; \ + } while (0) + +#else /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ + +#define NGI_M(i) _NGI_M(i) +#define NGI_MSG(i) _NGI_MSG(i) +#define NGI_RETADDR(i) _NGI_RETADDR(i) +#define NGI_FN(i) _NGI_FN(i) +#define NGI_FN2(i) _NGI_FN2(i) +#define NGI_ARG1(i) _NGI_ARG1(i) +#define NGI_ARG2(i) _NGI_ARG2(i) +#define NGI_NODE(i) _NGI_NODE(i) +#define NGI_HOOK(i) _NGI_HOOK(i) +#define NGI_SET_HOOK(i,h) _NGI_SET_HOOK(i,h) +#define NGI_CLR_HOOK(i) _NGI_CLR_HOOK(i) +#define NGI_SET_NODE(i,n) _NGI_SET_NODE(i,n) +#define NGI_CLR_NODE(i) _NGI_CLR_NODE(i) + +#define NG_FREE_ITEM(item) ng_free_item((item)) +#define SAVE_LINE(item) do {} while (0) + +#endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ + +#define NGI_GET_M(i,m) \ + do { \ + (m) = NGI_M(i); \ + _NGI_M(i) = NULL; \ + } while (0) + +#define NGI_GET_MSG(i,m) \ + do { \ + (m) = NGI_MSG(i); \ + _NGI_MSG(i) = NULL; \ + } while (0) + +#define NGI_GET_NODE(i,n) /* YOU NOW HAVE THE REFERENCE */ \ + do { \ + (n) = NGI_NODE(i); \ + _NGI_NODE(i) = NULL; \ + } while (0) + +#define NGI_GET_HOOK(i,h) \ + do { \ + (h) = NGI_HOOK(i); \ + _NGI_HOOK(i) = NULL; \ + } while (0) + +#define NGI_SET_WRITER(i) ((i)->el_flags &= ~NGQF_QMODE) +#define NGI_SET_READER(i) ((i)->el_flags |= NGQF_QREADER) + +#define NGI_QUEUED_READER(i) ((i)->el_flags & NGQF_QREADER) +#define NGI_QUEUED_WRITER(i) (((i)->el_flags & NGQF_QMODE) == NGQF_QWRITER) + +/********************************************************************** +* Data macros. Send, manipulate and free. +**********************************************************************/ +/* + * Assuming the data is already ok, just set the new address and send + */ +#define NG_FWD_ITEM_HOOK_FLAGS(error, item, hook, flags) \ + do { \ + (error) = \ + ng_address_hook(NULL, (item), (hook), NG_NOFLAGS); \ + if (error == 0) { \ + SAVE_LINE(item); \ + (error) = ng_snd_item((item), (flags)); \ + } \ + (item) = NULL; \ + } while (0) +#define NG_FWD_ITEM_HOOK(error, item, hook) \ + NG_FWD_ITEM_HOOK_FLAGS(error, item, hook, NG_NOFLAGS) + +/* + * Forward a data packet. Mbuf pointer is updated to new value. We + * presume you dealt with the old one when you update it to the new one + * (or it maybe the old one). We got a packet and possibly had to modify + * the mbuf. You should probably use NGI_GET_M() if you are going to use + * this too. + */ +#define NG_FWD_NEW_DATA_FLAGS(error, item, hook, m, flags) \ + do { \ + NGI_M(item) = (m); \ + (m) = NULL; \ + NG_FWD_ITEM_HOOK_FLAGS(error, item, hook, flags); \ + } while (0) +#define NG_FWD_NEW_DATA(error, item, hook, m) \ + NG_FWD_NEW_DATA_FLAGS(error, item, hook, m, NG_NOFLAGS) + +/* Send a previously unpackaged mbuf. XXX: This should be called + * NG_SEND_DATA in future, but this name is kept for compatibility + * reasons. + */ +#define NG_SEND_DATA_FLAGS(error, hook, m, flags) \ + do { \ + item_p _item; \ + if ((_item = ng_package_data((m), flags))) { \ + NG_FWD_ITEM_HOOK_FLAGS(error, _item, hook, flags);\ + } else { \ + (error) = ENOMEM; \ + } \ + (m) = NULL; \ + } while (0) + +#define NG_SEND_DATA_ONLY(error, hook, m) \ + NG_SEND_DATA_FLAGS(error, hook, m, NG_NOFLAGS) +/* NG_SEND_DATA() compat for meta-data times */ +#define NG_SEND_DATA(error, hook, m, x) \ + NG_SEND_DATA_FLAGS(error, hook, m, NG_NOFLAGS) + +#define NG_FREE_MSG(msg) \ + do { \ + if ((msg)) { \ + FREE((msg), M_NETGRAPH_MSG); \ + (msg) = NULL; \ + } \ + } while (0) + +#define NG_FREE_M(m) \ + do { \ + if ((m)) { \ + m_freem((m)); \ + (m) = NULL; \ + } \ + } while (0) + +/***************************************** +* Message macros +*****************************************/ + +#define NG_SEND_MSG_HOOK(error, here, msg, hook, retaddr) \ + do { \ + item_p _item; \ + if ((_item = ng_package_msg(msg, NG_NOFLAGS)) == NULL) {\ + (msg) = NULL; \ + (error) = ENOMEM; \ + break; \ + } \ + if (((error) = ng_address_hook((here), (_item), \ + (hook), (retaddr))) == 0) { \ + SAVE_LINE(_item); \ + (error) = ng_snd_item((_item), 0); \ + } \ + (msg) = NULL; \ + } while (0) + +#define NG_SEND_MSG_PATH(error, here, msg, path, retaddr) \ + do { \ + item_p _item; \ + if ((_item = ng_package_msg(msg, NG_NOFLAGS)) == NULL) {\ + (msg) = NULL; \ + (error) = ENOMEM; \ + break; \ + } \ + if (((error) = ng_address_path((here), (_item), \ + (path), (retaddr))) == 0) { \ + SAVE_LINE(_item); \ + (error) = ng_snd_item((_item), 0); \ + } \ + (msg) = NULL; \ + } while (0) + +#define NG_SEND_MSG_ID(error, here, msg, ID, retaddr) \ + do { \ + item_p _item; \ + if ((_item = ng_package_msg(msg, NG_NOFLAGS)) == NULL) {\ + (msg) = NULL; \ + (error) = ENOMEM; \ + break; \ + } \ + if (((error) = ng_address_ID((here), (_item), \ + (ID), (retaddr))) == 0) { \ + SAVE_LINE(_item); \ + (error) = ng_snd_item((_item), 0); \ + } \ + (msg) = NULL; \ + } while (0) + +/* + * Redirect the message to the next hop using the given hook. + * ng_retarget_msg() frees the item if there is an error + * and returns an error code. It returns 0 on success. + */ +#define NG_FWD_MSG_HOOK(error, here, item, hook, retaddr) \ + do { \ + if (((error) = ng_address_hook((here), (item), \ + (hook), (retaddr))) == 0) { \ + SAVE_LINE(item); \ + (error) = ng_snd_item((item), 0); \ + } \ + (item) = NULL; \ + } while (0) + +/* + * Send a queue item back to it's originator with a response message. + * Assume original message was removed and freed separatly. + */ +#define NG_RESPOND_MSG(error, here, item, resp) \ + do { \ + if (resp) { \ + ng_ID_t _dest = NGI_RETADDR(item); \ + NGI_RETADDR(item) = 0; \ + NGI_MSG(item) = resp; \ + if ((error = ng_address_ID((here), (item), \ + _dest, 0)) == 0) { \ + SAVE_LINE(item); \ + (error) = ng_snd_item((item), NG_QUEUE);\ + } \ + } else \ + NG_FREE_ITEM(item); \ + (item) = NULL; \ + } while (0) + + +/*********************************************************************** + ******** Structures Definitions and Macros for defining a node ******* + *********************************************************************** + * + * Here we define the structures needed to actually define a new node + * type. + */ + +/* + * Command list -- each node type specifies the command that it knows + * how to convert between ASCII and binary using an array of these. + * The last element in the array must be a terminator with cookie=0. + */ + +struct ng_cmdlist { + u_int32_t cookie; /* command typecookie */ + int cmd; /* command number */ + const char *name; /* command name */ + const struct ng_parse_type *mesgType; /* args if !NGF_RESP */ + const struct ng_parse_type *respType; /* args if NGF_RESP */ +}; + +/* + * Structure of a node type + * If data is sent to the "rcvdata()" entrypoint then the system + * may decide to defer it until later by queing it with the normal netgraph + * input queuing system. This is decidde by the HK_QUEUE flag being set in + * the flags word of the peer (receiving) hook. The dequeuing mechanism will + * ensure it is not requeued again. + * Note the input queueing system is to allow modules + * to 'release the stack' or to pass data across spl layers. + * The data will be redelivered as soon as the NETISR code runs + * which may be almost immediatly. A node may also do it's own queueing + * for other reasons (e.g. device output queuing). + */ +struct ng_type { + + u_int32_t version; /* must equal NG_API_VERSION */ + const char *name; /* Unique type name */ + modeventhand_t mod_event; /* Module event handler (optional) */ + ng_constructor_t *constructor; /* Node constructor */ + ng_rcvmsg_t *rcvmsg; /* control messages come here */ + ng_close_t *close; /* warn about forthcoming shutdown */ + ng_shutdown_t *shutdown; /* reset, and free resources */ + ng_newhook_t *newhook; /* first notification of new hook */ + ng_findhook_t *findhook; /* only if you have lots of hooks */ + ng_connect_t *connect; /* final notification of new hook */ + ng_rcvdata_t *rcvdata; /* data comes here */ + ng_disconnect_t *disconnect; /* notify on disconnect */ + + const struct ng_cmdlist *cmdlist; /* commands we can convert */ + + /* R/W data private to the base netgraph code DON'T TOUCH! */ + LIST_ENTRY(ng_type) types; /* linked list of all types */ + int refs; /* number of instances */ +}; + +/* + * Use the NETGRAPH_INIT() macro to link a node type into the + * netgraph system. This works for types compiled into the kernel + * as well as KLD modules. The first argument should be the type + * name (eg, echo) and the second a pointer to the type struct. + * + * If a different link time is desired, e.g., a device driver that + * needs to install its netgraph type before probing, use the + * NETGRAPH_INIT_ORDERED() macro instead. Device drivers probably + * want to use SI_SUB_DRIVERS/SI_ORDER_FIRST. + */ + +#define NETGRAPH_INIT_ORDERED(typename, typestructp, sub, order) \ +static moduledata_t ng_##typename##_mod = { \ + "ng_" #typename, \ + ng_mod_event, \ + (typestructp) \ +}; \ +DECLARE_MODULE(ng_##typename, ng_##typename##_mod, sub, order); \ +MODULE_DEPEND(ng_##typename, netgraph, NG_ABI_VERSION, \ + NG_ABI_VERSION, \ + NG_ABI_VERSION) + +#define NETGRAPH_INIT(tn, tp) \ + NETGRAPH_INIT_ORDERED(tn, tp, SI_SUB_PSEUDO, SI_ORDER_ANY) + +/* Special malloc() type for netgraph structs and ctrl messages */ +/* Only these two types should be visible to nodes */ +MALLOC_DECLARE(M_NETGRAPH); +MALLOC_DECLARE(M_NETGRAPH_MSG); + +/* declare the base of the netgraph sysclt hierarchy */ +/* but only if this file cares about sysctls */ +#ifdef SYSCTL_DECL +SYSCTL_DECL(_net_graph); +#endif + +/* + * Methods that the nodes can use. + * Many of these methods should usually NOT be used directly but via + * Macros above. + */ +int ng_address_ID(node_p here, item_p item, ng_ID_t ID, ng_ID_t retaddr); +int ng_address_hook(node_p here, item_p item, hook_p hook, ng_ID_t retaddr); +int ng_address_path(node_p here, item_p item, char *address, ng_ID_t raddr); +int ng_bypass(hook_p hook1, hook_p hook2); +hook_p ng_findhook(node_p node, const char *name); +struct ng_type *ng_findtype(const char *type); +int ng_make_node_common(struct ng_type *typep, node_p *nodep); +int ng_name_node(node_p node, const char *name); +int ng_newtype(struct ng_type *tp); +ng_ID_t ng_node2ID(node_p node); +item_p ng_package_data(struct mbuf *m, int flags); +item_p ng_package_msg(struct ng_mesg *msg, int flags); +item_p ng_package_msg_self(node_p here, hook_p hook, struct ng_mesg *msg); +void ng_replace_retaddr(node_p here, item_p item, ng_ID_t retaddr); +int ng_rmhook_self(hook_p hook); /* if a node wants to kill a hook */ +int ng_rmnode_self(node_p here); /* if a node wants to suicide */ +int ng_rmtype(struct ng_type *tp); +int ng_snd_item(item_p item, int queue); +int ng_send_fn(node_p node, hook_p hook, ng_item_fn *fn, void *arg1, + int arg2); +int ng_send_fn1(node_p node, hook_p hook, ng_item_fn *fn, void *arg1, + int arg2, int flags); +int ng_send_fn2(node_p node, hook_p hook, item_p pitem, ng_item_fn2 *fn, + void *arg1, int arg2, int flags); +int ng_uncallout(struct callout *c, node_p node); +int ng_callout(struct callout *c, node_p node, hook_p hook, int ticks, + ng_item_fn *fn, void * arg1, int arg2); +#define ng_callout_init(c) callout_init(c, CALLOUT_MPSAFE) + +/* Flags for netgraph functions. */ +#define NG_NOFLAGS 0x00000000 /* no special options */ +#define NG_QUEUE 0x00000001 /* enqueue item, don't dispatch */ +#define NG_WAITOK 0x00000002 /* use M_WAITOK, etc. */ +/* XXXGL: NG_PROGRESS unused since ng_base.c rev. 1.136. Should be deleted? */ +#define NG_PROGRESS 0x00000004 /* return EINPROGRESS if queued */ +#define NG_REUSE_ITEM 0x00000008 /* supplied item should be reused */ + +/* + * prototypes the user should DEFINITELY not use directly + */ +void ng_free_item(item_p item); /* Use NG_FREE_ITEM instead */ +int ng_mod_event(module_t mod, int what, void *arg); + +/* + * Tag definitions and constants + */ + +#define NG_TAG_PRIO 1 + +struct ng_tag_prio { + struct m_tag tag; + char priority; + char discardability; +}; + +#define NG_PRIO_CUTOFF 32 +#define NG_PRIO_LINKSTATE 64 + +/* Macros and declarations to keep compatibility with metadata, which + * is obsoleted now. To be deleted. + */ +typedef void *meta_p; +#define _NGI_META(i) NULL +#define NGI_META(i) NULL +#define NG_FREE_META(meta) +#define NGI_GET_META(i,m) +#define ng_copy_meta(meta) NULL + +#endif /* _NETGRAPH_NETGRAPH_H_ */ diff --git a/sys/netgraph7/ng_UI.c b/sys/netgraph7/ng_UI.c new file mode 100644 index 0000000000..ee0c169f6f --- /dev/null +++ b/sys/netgraph7/ng_UI.c @@ -0,0 +1,248 @@ +/* + * ng_UI.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_UI.c,v 1.20 2005/01/07 01:45:39 imp Exp $ + * $Whistle: ng_UI.c,v 1.14 1999/11/01 09:24:51 julian Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include + +/* + * DEFINITIONS + */ + +/* Everything, starting with sdlc on has defined UI as 0x03 */ +#define HDLC_UI 0x03 + +/* Node private data */ +struct ng_UI_private { + hook_p downlink; + hook_p uplink; +}; +typedef struct ng_UI_private *priv_p; + +/* Netgraph node methods */ +static ng_constructor_t ng_UI_constructor; +static ng_rcvmsg_t ng_UI_rcvmsg; +static ng_shutdown_t ng_UI_shutdown; +static ng_newhook_t ng_UI_newhook; +static ng_rcvdata_t ng_UI_rcvdata; +static ng_disconnect_t ng_UI_disconnect; + +/* Node type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_UI_NODE_TYPE, + .constructor = ng_UI_constructor, + .rcvmsg = ng_UI_rcvmsg, + .shutdown = ng_UI_shutdown, + .newhook = ng_UI_newhook, + .rcvdata = ng_UI_rcvdata, + .disconnect = ng_UI_disconnect, +}; +NETGRAPH_INIT(UI, &typestruct); + +/************************************************************************ + NETGRAPH NODE STUFF + ************************************************************************/ + +/* + * Create a newborn node. We start with an implicit reference. + */ + +static int +ng_UI_constructor(node_p node) +{ + priv_p priv; + + /* Allocate private structure */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (priv == NULL) { + return (ENOMEM); + } + NG_NODE_SET_PRIVATE(node, priv); + return (0); +} + +/* + * Give our ok for a hook to be added + */ +static int +ng_UI_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (!strcmp(name, NG_UI_HOOK_DOWNSTREAM)) { + if (priv->downlink) + return (EISCONN); + priv->downlink = hook; + } else if (!strcmp(name, NG_UI_HOOK_UPSTREAM)) { + if (priv->uplink) + return (EISCONN); + priv->uplink = hook; + } else + return (EINVAL); + return (0); +} + +/* + * Receive a control message + */ +static int +ng_UI_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + int error; + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *msg; + + msg = NGI_MSG(item); /* only peeking */ + if ((msg->header.typecookie == NGM_FLOW_COOKIE) && lasthook) { + if (lasthook == priv->downlink) { + if (priv->uplink) { + NG_FWD_ITEM_HOOK(error, item, priv->uplink); + return (error); + } + } else { + if (priv->downlink) { + NG_FWD_ITEM_HOOK(error, item, priv->downlink); + return (error); + } + } + } + + NG_FREE_ITEM(item); + return (EINVAL); +} + +#define MAX_ENCAPS_HDR 1 +#define ERROUT(x) do { error = (x); goto done; } while (0) + +/* + * Receive a data frame + */ +static int +ng_UI_rcvdata(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + struct mbuf *m; + int error = 0; + + NGI_GET_M(item, m); + if (hook == priv->downlink) { + u_char *start, *ptr; + + if (m->m_len < MAX_ENCAPS_HDR + && !(m = m_pullup(m, MAX_ENCAPS_HDR))) + ERROUT(ENOBUFS); + ptr = start = mtod(m, u_char *); + + /* Must be UI frame */ + if (*ptr++ != HDLC_UI) + ERROUT(0); + + m_adj(m, ptr - start); + NG_FWD_NEW_DATA(error, item, priv->uplink, m); /* m -> NULL */ + } else if (hook == priv->uplink) { + M_PREPEND(m, 1, M_DONTWAIT); /* Prepend IP NLPID */ + if (!m) + ERROUT(ENOBUFS); + mtod(m, u_char *)[0] = HDLC_UI; + NG_FWD_NEW_DATA(error, item, priv->downlink, m); /* m -> NULL */ + } else + panic(__func__); + +done: + NG_FREE_M(m); /* does nothing if m == NULL */ + if (item) + NG_FREE_ITEM(item); + return (error); +} + +/* + * Shutdown node + */ +static int +ng_UI_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + /* Take down netgraph node */ + FREE(priv, M_NETGRAPH); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + return (0); +} + +/* + * Hook disconnection + */ +static int +ng_UI_disconnect(hook_p hook) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + if (hook == priv->downlink) + priv->downlink = NULL; + else if (hook == priv->uplink) + priv->uplink = NULL; + else + panic(__func__); + /* + * If we are not already shutting down, + * and we have no more hooks, then DO shut down. + */ + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) { + ng_rmnode_self(NG_HOOK_NODE(hook)); + } + return (0); +} + diff --git a/sys/netgraph7/ng_UI.h b/sys/netgraph7/ng_UI.h new file mode 100644 index 0000000000..f925050167 --- /dev/null +++ b/sys/netgraph7/ng_UI.h @@ -0,0 +1,56 @@ +/* + * ng_UI.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_UI.h,v 1.5 2005/01/07 01:45:39 imp Exp $ + * $Whistle: ng_UI.h,v 1.6 1999/01/20 00:54:15 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_UI_H_ +#define _NETGRAPH_NG_UI_H_ + +/* Node type name and cookie */ +#define NG_UI_NODE_TYPE "UI" +#define NGM_UI_COOKIE 884639499 + +/* Hook names */ +#define NG_UI_HOOK_DOWNSTREAM "downstream" +#define NG_UI_HOOK_UPSTREAM "upstream" + +#endif /* _NETGRAPH_NG_UI_H_ */ + diff --git a/sys/netgraph7/ng_async.c b/sys/netgraph7/ng_async.c new file mode 100644 index 0000000000..2a27fecd4a --- /dev/null +++ b/sys/netgraph7/ng_async.c @@ -0,0 +1,640 @@ +/* + * ng_async.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_async.c,v 1.22 2005/01/07 01:45:39 imp Exp $ + * $Whistle: ng_async.c,v 1.17 1999/11/01 09:24:51 julian Exp $ + */ + +/* + * This node type implements a PPP style sync <-> async converter. + * See RFC 1661 for details of how asynchronous encoding works. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_ASYNC, "netgraph_async", "netgraph async node "); +#else +#define M_NETGRAPH_ASYNC M_NETGRAPH +#endif + + +/* Async decode state */ +#define MODE_HUNT 0 +#define MODE_NORMAL 1 +#define MODE_ESC 2 + +/* Private data structure */ +struct ng_async_private { + node_p node; /* Our node */ + hook_p async; /* Asynchronous side */ + hook_p sync; /* Synchronous side */ + u_char amode; /* Async hunt/esape mode */ + u_int16_t fcs; /* Decoded async FCS (so far) */ + u_char *abuf; /* Buffer to encode sync into */ + u_char *sbuf; /* Buffer to decode async into */ + u_int slen; /* Length of data in sbuf */ + long lasttime; /* Time of last async packet sent */ + struct ng_async_cfg cfg; /* Configuration */ + struct ng_async_stat stats; /* Statistics */ +}; +typedef struct ng_async_private *sc_p; + +/* Useful macros */ +#define ASYNC_BUF_SIZE(smru) (2 * (smru) + 10) +#define SYNC_BUF_SIZE(amru) ((amru) + 10) +#define ERROUT(x) do { error = (x); goto done; } while (0) + +/* Netgraph methods */ +static ng_constructor_t nga_constructor; +static ng_rcvdata_t nga_rcvdata; +static ng_rcvmsg_t nga_rcvmsg; +static ng_shutdown_t nga_shutdown; +static ng_newhook_t nga_newhook; +static ng_disconnect_t nga_disconnect; + +/* Helper stuff */ +static int nga_rcv_sync(const sc_p sc, item_p item); +static int nga_rcv_async(const sc_p sc, item_p item); + +/* Parse type for struct ng_async_cfg */ +static const struct ng_parse_struct_field nga_config_type_fields[] + = NG_ASYNC_CONFIG_TYPE_INFO; +static const struct ng_parse_type nga_config_type = { + &ng_parse_struct_type, + &nga_config_type_fields +}; + +/* Parse type for struct ng_async_stat */ +static const struct ng_parse_struct_field nga_stats_type_fields[] + = NG_ASYNC_STATS_TYPE_INFO; +static const struct ng_parse_type nga_stats_type = { + &ng_parse_struct_type, + &nga_stats_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist nga_cmdlist[] = { + { + NGM_ASYNC_COOKIE, + NGM_ASYNC_CMD_SET_CONFIG, + "setconfig", + &nga_config_type, + NULL + }, + { + NGM_ASYNC_COOKIE, + NGM_ASYNC_CMD_GET_CONFIG, + "getconfig", + NULL, + &nga_config_type + }, + { + NGM_ASYNC_COOKIE, + NGM_ASYNC_CMD_GET_STATS, + "getstats", + NULL, + &nga_stats_type + }, + { + NGM_ASYNC_COOKIE, + NGM_ASYNC_CMD_CLR_STATS, + "clrstats", + &nga_stats_type, + NULL + }, + { 0 } +}; + +/* Define the netgraph node type */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_ASYNC_NODE_TYPE, + .constructor = nga_constructor, + .rcvmsg = nga_rcvmsg, + .shutdown = nga_shutdown, + .newhook = nga_newhook, + .rcvdata = nga_rcvdata, + .disconnect = nga_disconnect, + .cmdlist = nga_cmdlist +}; +NETGRAPH_INIT(async, &typestruct); + +/* CRC table */ +static const u_int16_t fcstab[]; + +/****************************************************************** + NETGRAPH NODE METHODS +******************************************************************/ + +/* + * Initialize a new node + */ +static int +nga_constructor(node_p node) +{ + sc_p sc; + + MALLOC(sc, sc_p, sizeof(*sc), M_NETGRAPH_ASYNC, M_NOWAIT | M_ZERO); + if (sc == NULL) + return (ENOMEM); + sc->amode = MODE_HUNT; + sc->cfg.accm = ~0; + sc->cfg.amru = NG_ASYNC_DEFAULT_MRU; + sc->cfg.smru = NG_ASYNC_DEFAULT_MRU; + MALLOC(sc->abuf, u_char *, + ASYNC_BUF_SIZE(sc->cfg.smru), M_NETGRAPH_ASYNC, M_NOWAIT); + if (sc->abuf == NULL) + goto fail; + MALLOC(sc->sbuf, u_char *, + SYNC_BUF_SIZE(sc->cfg.amru), M_NETGRAPH_ASYNC, M_NOWAIT); + if (sc->sbuf == NULL) { + FREE(sc->abuf, M_NETGRAPH_ASYNC); +fail: + FREE(sc, M_NETGRAPH_ASYNC); + return (ENOMEM); + } + NG_NODE_SET_PRIVATE(node, sc); + sc->node = node; + return (0); +} + +/* + * Reserve a hook for a pending connection + */ +static int +nga_newhook(node_p node, hook_p hook, const char *name) +{ + const sc_p sc = NG_NODE_PRIVATE(node); + hook_p *hookp; + + if (!strcmp(name, NG_ASYNC_HOOK_ASYNC)) { + /* + * We use a static buffer here so only one packet + * at a time can be allowed to travel in this direction. + * Force Writer semantics. + */ + NG_HOOK_FORCE_WRITER(hook); + hookp = &sc->async; + } else if (!strcmp(name, NG_ASYNC_HOOK_SYNC)) { + /* + * We use a static state here so only one packet + * at a time can be allowed to travel in this direction. + * Force Writer semantics. + * Since we set this for both directions + * we might as well set it for the whole node + * bit I haven;t done that (yet). + */ + NG_HOOK_FORCE_WRITER(hook); + hookp = &sc->sync; + } else { + return (EINVAL); + } + if (*hookp) /* actually can't happen I think [JRE] */ + return (EISCONN); + *hookp = hook; + return (0); +} + +/* + * Receive incoming data + */ +static int +nga_rcvdata(hook_p hook, item_p item) +{ + const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + if (hook == sc->sync) + return (nga_rcv_sync(sc, item)); + if (hook == sc->async) + return (nga_rcv_async(sc, item)); + panic(__func__); +} + +/* + * Receive incoming control message + */ +static int +nga_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const sc_p sc = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_ASYNC_COOKIE: + switch (msg->header.cmd) { + case NGM_ASYNC_CMD_GET_STATS: + NG_MKRESPONSE(resp, msg, sizeof(sc->stats), M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + *((struct ng_async_stat *) resp->data) = sc->stats; + break; + case NGM_ASYNC_CMD_CLR_STATS: + bzero(&sc->stats, sizeof(sc->stats)); + break; + case NGM_ASYNC_CMD_SET_CONFIG: + { + struct ng_async_cfg *const cfg = + (struct ng_async_cfg *) msg->data; + u_char *buf; + + if (msg->header.arglen != sizeof(*cfg)) + ERROUT(EINVAL); + if (cfg->amru < NG_ASYNC_MIN_MRU + || cfg->amru > NG_ASYNC_MAX_MRU + || cfg->smru < NG_ASYNC_MIN_MRU + || cfg->smru > NG_ASYNC_MAX_MRU) + ERROUT(EINVAL); + cfg->enabled = !!cfg->enabled; /* normalize */ + if (cfg->smru > sc->cfg.smru) { /* reallocate buffer */ + MALLOC(buf, u_char *, ASYNC_BUF_SIZE(cfg->smru), + M_NETGRAPH_ASYNC, M_NOWAIT); + if (!buf) + ERROUT(ENOMEM); + FREE(sc->abuf, M_NETGRAPH_ASYNC); + sc->abuf = buf; + } + if (cfg->amru > sc->cfg.amru) { /* reallocate buffer */ + MALLOC(buf, u_char *, SYNC_BUF_SIZE(cfg->amru), + M_NETGRAPH_ASYNC, M_NOWAIT); + if (!buf) + ERROUT(ENOMEM); + FREE(sc->sbuf, M_NETGRAPH_ASYNC); + sc->sbuf = buf; + sc->amode = MODE_HUNT; + sc->slen = 0; + } + if (!cfg->enabled) { + sc->amode = MODE_HUNT; + sc->slen = 0; + } + sc->cfg = *cfg; + break; + } + case NGM_ASYNC_CMD_GET_CONFIG: + NG_MKRESPONSE(resp, msg, sizeof(sc->cfg), M_NOWAIT); + if (!resp) + ERROUT(ENOMEM); + *((struct ng_async_cfg *) resp->data) = sc->cfg; + break; + default: + ERROUT(EINVAL); + } + break; + default: + ERROUT(EINVAL); + } +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Shutdown this node + */ +static int +nga_shutdown(node_p node) +{ + const sc_p sc = NG_NODE_PRIVATE(node); + + FREE(sc->abuf, M_NETGRAPH_ASYNC); + FREE(sc->sbuf, M_NETGRAPH_ASYNC); + bzero(sc, sizeof(*sc)); + FREE(sc, M_NETGRAPH_ASYNC); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + return (0); +} + +/* + * Lose a hook. When both hooks go away, we disappear. + */ +static int +nga_disconnect(hook_p hook) +{ + const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + hook_p *hookp; + + if (hook == sc->async) + hookp = &sc->async; + else if (hook == sc->sync) + hookp = &sc->sync; + else + panic(__func__); + if (!*hookp) + panic("%s 2", __func__); + *hookp = NULL; + bzero(&sc->stats, sizeof(sc->stats)); + sc->lasttime = 0; + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) + ng_rmnode_self(NG_HOOK_NODE(hook)); + return (0); +} + +/****************************************************************** + INTERNAL HELPER STUFF +******************************************************************/ + +/* + * Encode a byte into the async buffer + */ +static __inline void +nga_async_add(const sc_p sc, u_int16_t *fcs, u_int32_t accm, int *len, u_char x) +{ + *fcs = PPP_FCS(*fcs, x); + if ((x < 32 && ((1 << x) & accm)) + || (x == PPP_ESCAPE) + || (x == PPP_FLAG)) { + sc->abuf[(*len)++] = PPP_ESCAPE; + x ^= PPP_TRANS; + } + sc->abuf[(*len)++] = x; +} + +/* + * Receive incoming synchronous data. + */ +static int +nga_rcv_sync(const sc_p sc, item_p item) +{ + struct ifnet *rcvif; + int alen, error = 0; + struct timeval time; + u_int16_t fcs, fcs0; + u_int32_t accm; + struct mbuf *m; + + +#define ADD_BYTE(x) nga_async_add(sc, &fcs, accm, &alen, (x)) + + /* Check for bypass mode */ + if (!sc->cfg.enabled) { + NG_FWD_ITEM_HOOK(error, item, sc->async ); + return (error); + } + NGI_GET_M(item, m); + + rcvif = m->m_pkthdr.rcvif; + + /* Get ACCM; special case LCP frames, which use full ACCM */ + accm = sc->cfg.accm; + if (m->m_pkthdr.len >= 4) { + static const u_char lcphdr[4] = { + PPP_ALLSTATIONS, + PPP_UI, + (u_char)(PPP_LCP >> 8), + (u_char)(PPP_LCP & 0xff) + }; + u_char buf[4]; + + m_copydata(m, 0, 4, (caddr_t)buf); + if (bcmp(buf, &lcphdr, 4) == 0) + accm = ~0; + } + + /* Check for overflow */ + if (m->m_pkthdr.len > sc->cfg.smru) { + sc->stats.syncOverflows++; + NG_FREE_M(m); + NG_FREE_ITEM(item); + return (EMSGSIZE); + } + + /* Update stats */ + sc->stats.syncFrames++; + sc->stats.syncOctets += m->m_pkthdr.len; + + /* Initialize async encoded version of input mbuf */ + alen = 0; + fcs = PPP_INITFCS; + + /* Add beginning sync flag if it's been long enough to need one */ + getmicrotime(&time); + if (time.tv_sec >= sc->lasttime + 1) { + sc->abuf[alen++] = PPP_FLAG; + sc->lasttime = time.tv_sec; + } + + /* Add packet payload */ + while (m != NULL) { + while (m->m_len > 0) { + ADD_BYTE(*mtod(m, u_char *)); + m->m_data++; + m->m_len--; + } + m = m_free(m); + } + + /* Add checksum and final sync flag */ + fcs0 = fcs; + ADD_BYTE(~fcs0 & 0xff); + ADD_BYTE(~fcs0 >> 8); + sc->abuf[alen++] = PPP_FLAG; + + /* Put frame in an mbuf and ship it off */ + if (!(m = m_devget(sc->abuf, alen, 0, rcvif, NULL))) { + NG_FREE_ITEM(item); + error = ENOBUFS; + } else { + NG_FWD_NEW_DATA(error, item, sc->async, m); + } + return (error); +} + +/* + * Receive incoming asynchronous data + * XXX Technically, we should strip out incoming characters + * that are in our ACCM. Not sure if this is good or not. + */ +static int +nga_rcv_async(const sc_p sc, item_p item) +{ + struct ifnet *rcvif; + int error; + struct mbuf *m; + + if (!sc->cfg.enabled) { + NG_FWD_ITEM_HOOK(error, item, sc->sync); + return (error); + } + NGI_GET_M(item, m); + rcvif = m->m_pkthdr.rcvif; + while (m) { + struct mbuf *n; + + for (; m->m_len > 0; m->m_data++, m->m_len--) { + u_char ch = *mtod(m, u_char *); + + sc->stats.asyncOctets++; + if (ch == PPP_FLAG) { /* Flag overrides everything */ + int skip = 0; + + /* Check for runts */ + if (sc->slen < 2) { + if (sc->slen > 0) + sc->stats.asyncRunts++; + goto reset; + } + + /* Verify CRC */ + if (sc->fcs != PPP_GOODFCS) { + sc->stats.asyncBadCheckSums++; + goto reset; + } + sc->slen -= 2; + + /* Strip address and control fields */ + if (sc->slen >= 2 + && sc->sbuf[0] == PPP_ALLSTATIONS + && sc->sbuf[1] == PPP_UI) + skip = 2; + + /* Check for frame too big */ + if (sc->slen - skip > sc->cfg.amru) { + sc->stats.asyncOverflows++; + goto reset; + } + + /* OK, ship it out */ + if ((n = m_devget(sc->sbuf + skip, + sc->slen - skip, 0, rcvif, NULL))) { + if (item) { /* sets NULL -> item */ + NG_FWD_NEW_DATA(error, item, + sc->sync, n); + } else { + NG_SEND_DATA_ONLY(error, + sc->sync ,n); + } + } + sc->stats.asyncFrames++; +reset: + sc->amode = MODE_NORMAL; + sc->fcs = PPP_INITFCS; + sc->slen = 0; + continue; + } + switch (sc->amode) { + case MODE_NORMAL: + if (ch == PPP_ESCAPE) { + sc->amode = MODE_ESC; + continue; + } + break; + case MODE_ESC: + ch ^= PPP_TRANS; + sc->amode = MODE_NORMAL; + break; + case MODE_HUNT: + default: + continue; + } + + /* Add byte to frame */ + if (sc->slen >= SYNC_BUF_SIZE(sc->cfg.amru)) { + sc->stats.asyncOverflows++; + sc->amode = MODE_HUNT; + sc->slen = 0; + } else { + sc->sbuf[sc->slen++] = ch; + sc->fcs = PPP_FCS(sc->fcs, ch); + } + } + m = m_free(m); + } + if (item) + NG_FREE_ITEM(item); + return (0); +} + +/* + * CRC table + * + * Taken from RFC 1171 Appendix B + */ +static const u_int16_t fcstab[256] = { + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, + 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, + 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, + 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, + 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, + 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, + 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, + 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, + 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, + 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, + 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, + 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, + 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, + 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, + 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, + 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, + 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, + 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, + 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, + 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, + 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, + 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, + 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, + 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, + 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, + 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, + 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, + 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, + 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, + 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, + 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, + 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 +}; diff --git a/sys/netgraph7/ng_async.h b/sys/netgraph7/ng_async.h new file mode 100644 index 0000000000..b0ea22da1d --- /dev/null +++ b/sys/netgraph7/ng_async.h @@ -0,0 +1,110 @@ +/* + * ng_async.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_async.h,v 1.11 2005/01/07 01:45:39 imp Exp $ + * $Whistle: ng_async.h,v 1.5 1999/01/25 01:17:14 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_ASYNC_H_ +#define _NETGRAPH_NG_ASYNC_H_ + +/* Type name and cookie */ +#define NG_ASYNC_NODE_TYPE "async" +#define NGM_ASYNC_COOKIE 886473717 + +/* Hook names */ +#define NG_ASYNC_HOOK_SYNC "sync" /* Sync frames */ +#define NG_ASYNC_HOOK_ASYNC "async" /* Async-encoded frames */ + +/* Maximum receive size bounds (for both sync and async sides) */ +#define NG_ASYNC_MIN_MRU 1 +#define NG_ASYNC_MAX_MRU 8192 +#define NG_ASYNC_DEFAULT_MRU 1600 + +/* Frame statistics */ +struct ng_async_stat { + u_int32_t syncOctets; + u_int32_t syncFrames; + u_int32_t syncOverflows; + u_int32_t asyncOctets; + u_int32_t asyncFrames; + u_int32_t asyncRunts; + u_int32_t asyncOverflows; + u_int32_t asyncBadCheckSums; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_ASYNC_STATS_TYPE_INFO { \ + { "syncOctets", &ng_parse_uint32_type }, \ + { "syncFrames", &ng_parse_uint32_type }, \ + { "syncOverflows", &ng_parse_uint32_type }, \ + { "asyncOctets", &ng_parse_uint32_type }, \ + { "asyncFrames", &ng_parse_uint32_type }, \ + { "asyncRunts", &ng_parse_uint32_type }, \ + { "asyncOverflows", &ng_parse_uint32_type }, \ + { "asyncBadCheckSums",&ng_parse_uint32_type }, \ + { NULL } \ +} + +/* Configuration for this node */ +struct ng_async_cfg { + u_char enabled; /* Turn encoding on/off */ + u_int16_t amru; /* Max receive async frame length */ + u_int16_t smru; /* Max receive sync frame length */ + u_int32_t accm; /* ACCM encoding */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_ASYNC_CONFIG_TYPE_INFO { \ + { "enabled", &ng_parse_int8_type }, \ + { "amru", &ng_parse_uint16_type }, \ + { "smru", &ng_parse_uint16_type }, \ + { "accm", &ng_parse_hint32_type }, \ + { NULL } \ +} + +/* Commands */ +enum { + NGM_ASYNC_CMD_GET_STATS = 1, /* returns struct ng_async_stat */ + NGM_ASYNC_CMD_CLR_STATS, + NGM_ASYNC_CMD_SET_CONFIG, /* takes struct ng_async_cfg */ + NGM_ASYNC_CMD_GET_CONFIG, /* returns struct ng_async_cfg */ +}; + +#endif /* _NETGRAPH_NG_ASYNC_H_ */ diff --git a/sys/netgraph7/ng_atmllc.c b/sys/netgraph7/ng_atmllc.c new file mode 100644 index 0000000000..9aa0d0cfd8 --- /dev/null +++ b/sys/netgraph7/ng_atmllc.c @@ -0,0 +1,274 @@ +/*- + * Copyright (c) 2003-2004 Benno Rice + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_atmllc.c,v 1.3 2005/01/07 01:45:39 imp Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include /* for M_HASFCS and ETHER_HDR_LEN */ +#include /* for struct atmllc */ + +#define NG_ATMLLC_HEADER "\252\252\3\0\200\302" +#define NG_ATMLLC_HEADER_LEN (sizeof(struct atmllc)) +#define NG_ATMLLC_TYPE_ETHERNET_FCS 0x0001 +#define NG_ATMLLC_TYPE_FDDI_FCS 0x0004 +#define NG_ATMLLC_TYPE_ETHERNET_NOFCS 0x0007 +#define NG_ATMLLC_TYPE_FDDI_NOFCS 0x000A + +struct ng_atmllc_priv { + hook_p atm; + hook_p ether; + hook_p fddi; +}; + +/* Netgraph methods. */ +static ng_constructor_t ng_atmllc_constructor; +static ng_shutdown_t ng_atmllc_shutdown; +static ng_rcvmsg_t ng_atmllc_rcvmsg; +static ng_newhook_t ng_atmllc_newhook; +static ng_rcvdata_t ng_atmllc_rcvdata; +static ng_disconnect_t ng_atmllc_disconnect; + +static struct ng_type ng_atmllc_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_ATMLLC_NODE_TYPE, + .constructor = ng_atmllc_constructor, + .rcvmsg = ng_atmllc_rcvmsg, + .shutdown = ng_atmllc_shutdown, + .newhook = ng_atmllc_newhook, + .rcvdata = ng_atmllc_rcvdata, + .disconnect = ng_atmllc_disconnect, +}; +NETGRAPH_INIT(atmllc, &ng_atmllc_typestruct); + +static int +ng_atmllc_constructor(node_p node) +{ + struct ng_atmllc_priv *priv; + + MALLOC(priv, struct ng_atmllc_priv *, sizeof(*priv), M_NETGRAPH, + M_NOWAIT | M_ZERO); + if (priv == NULL) { + return (ENOMEM); + } + + NG_NODE_SET_PRIVATE(node, priv); + + return (0); +} + +static int +ng_atmllc_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct ng_mesg *msg; + int error; + + error = 0; + NGI_GET_MSG(item, msg); + msg->header.flags |= NGF_RESP; + NG_RESPOND_MSG(error, node, item, msg); + return (error); +} + +static int +ng_atmllc_shutdown(node_p node) +{ + struct ng_atmllc_priv *priv; + + priv = NG_NODE_PRIVATE(node); + + FREE(priv, M_NETGRAPH); + + NG_NODE_UNREF(node); + + return (0); +} + +static int +ng_atmllc_newhook(node_p node, hook_p hook, const char *name) +{ + struct ng_atmllc_priv *priv; + + priv = NG_NODE_PRIVATE(node); + + if (strcmp(name, NG_ATMLLC_HOOK_ATM) == 0) { + if (priv->atm != NULL) { + return (EISCONN); + } + priv->atm = hook; + } else if (strcmp(name, NG_ATMLLC_HOOK_ETHER) == 0) { + if (priv->ether != NULL) { + return (EISCONN); + } + priv->ether = hook; + } else if (strcmp(name, NG_ATMLLC_HOOK_FDDI) == 0) { + if (priv->fddi != NULL) { + return (EISCONN); + } + priv->fddi = hook; + } else { + return (EINVAL); + } + + return (0); +} + +static int +ng_atmllc_rcvdata(hook_p hook, item_p item) +{ + struct ng_atmllc_priv *priv; + struct mbuf *m; + struct atmllc *hdr; + hook_p outhook; + u_int padding; + int error; + + priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + m = NGI_M(item); + outhook = NULL; + padding = 0; + + if (hook == priv->atm) { + /* Ditch the psuedoheader. */ + hdr = mtod(m, struct atmllc *); + /* m_adj(m, sizeof(struct atm_pseudohdr)); */ + + /* + * Make sure we have the LLC and ethernet headers. + * The ethernet header size is slightly larger than the FDDI + * header, which is convenient. + */ + if (m->m_len < sizeof(struct atmllc) + ETHER_HDR_LEN) { + m = m_pullup(m, sizeof(struct atmllc) + ETHER_HDR_LEN); + if (m == NULL) { + return (ENOMEM); + } + } + + /* Decode the LLC header. */ + hdr = mtod(m, struct atmllc *); + if (ATM_LLC_TYPE(hdr) == NG_ATMLLC_TYPE_ETHERNET_NOFCS) { + m->m_flags &= ~M_HASFCS; + outhook = priv->ether; + padding = 2; + } else if (ATM_LLC_TYPE(hdr) == NG_ATMLLC_TYPE_ETHERNET_FCS) { + m->m_flags |= M_HASFCS; + outhook = priv->ether; + padding = 2; + } else if (ATM_LLC_TYPE(hdr) == NG_ATMLLC_TYPE_FDDI_NOFCS) { + m->m_flags &= ~M_HASFCS; + outhook = priv->fddi; + padding = 3; + } else if (ATM_LLC_TYPE(hdr) == NG_ATMLLC_TYPE_FDDI_FCS) { + m->m_flags |= M_HASFCS; + outhook = priv->fddi; + padding = 3; + } else { + printf("ng_atmllc: unknown type: %x\n", + ATM_LLC_TYPE(hdr)); + } + + /* Remove the LLC header and any padding*/ + m_adj(m, sizeof(struct atmllc) + padding); + } else if (hook == priv->ether) { + /* Add the LLC header */ + M_PREPEND(m, NG_ATMLLC_HEADER_LEN + 2, M_DONTWAIT); + if (m == NULL) { + printf("ng_atmllc: M_PREPEND failed\n"); + NG_FREE_ITEM(item); + return (ENOMEM); + } + hdr = mtod(m, struct atmllc *); + bzero((void *)hdr, sizeof(struct atmllc) + 2); + bcopy(NG_ATMLLC_HEADER, hdr->llchdr, 6); + if ((m->m_flags & M_HASFCS) != 0) { + ATM_LLC_SETTYPE(hdr, NG_ATMLLC_TYPE_ETHERNET_FCS); + } else { + ATM_LLC_SETTYPE(hdr, NG_ATMLLC_TYPE_ETHERNET_NOFCS); + } + outhook = priv->atm; + } else if (hook == priv->fddi) { + /* Add the LLC header */ + M_PREPEND(m, NG_ATMLLC_HEADER_LEN + 3, M_DONTWAIT); + if (m == NULL) { + printf("ng_atmllc: M_PREPEND failed\n"); + NG_FREE_ITEM(item); + return (ENOMEM); + } + hdr = mtod(m, struct atmllc *); + bzero((void *)hdr, sizeof(struct atmllc) + 3); + bcopy(NG_ATMLLC_HEADER, hdr->llchdr, 6); + if ((m->m_flags & M_HASFCS) != 0) { + ATM_LLC_SETTYPE(hdr, NG_ATMLLC_TYPE_FDDI_FCS); + } else { + ATM_LLC_SETTYPE(hdr, NG_ATMLLC_TYPE_FDDI_NOFCS); + } + outhook = priv->atm; + } + + if (outhook == NULL) { + NG_FREE_ITEM(item); + return (0); + } + + NG_FWD_NEW_DATA(error, item, outhook, m); + return (error); +} + +static int +ng_atmllc_disconnect(hook_p hook) +{ + node_p node; + struct ng_atmllc_priv *priv; + + node = NG_HOOK_NODE(hook); + priv = NG_NODE_PRIVATE(node); + + if (hook == priv->atm) { + priv->atm = NULL; + } else if (hook == priv->ether) { + priv->ether = NULL; + } else if (hook == priv->fddi) { + priv->fddi = NULL; + } + + if (NG_NODE_NUMHOOKS(node) == 0 && NG_NODE_IS_VALID(node)) { + ng_rmnode_self(node); + } + + return (0); +} diff --git a/sys/netgraph7/ng_atmllc.h b/sys/netgraph7/ng_atmllc.h new file mode 100644 index 0000000000..f9d009a3e7 --- /dev/null +++ b/sys/netgraph7/ng_atmllc.h @@ -0,0 +1,45 @@ +/*- + * Copyright (c) 2003-2004 Benno Rice + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_atmllc.h,v 1.2 2005/01/07 01:45:39 imp Exp $ + */ + +#ifndef _NETGRAPH_ATMLLC_H_ +#define _NETGRAPH_ATMLLC_H_ + +/* Node type name and magic cookie. */ +#define NG_ATMLLC_NODE_TYPE "atmllc" +#define NGM_ATMLLC_COOKIE 1065246274 + +/* Hook names. */ +#define NG_ATMLLC_HOOK_ATM "atm" +#define NG_ATMLLC_HOOK_ETHER "ether" +#define NG_ATMLLC_HOOK_802_4 "ieee8024" +#define NG_ATMLLC_HOOK_802_5 "ieee8025" +#define NG_ATMLLC_HOOK_802_6 "ieee8026" +#define NG_ATMLLC_HOOK_FDDI "fddi" +#define NG_ATMLLC_HOOK_BPDU "bpdu" + +#endif /* _NETGRAPH_ATMLLC_H_ */ diff --git a/sys/netgraph7/ng_base.c b/sys/netgraph7/ng_base.c new file mode 100644 index 0000000000..2e410f5ccb --- /dev/null +++ b/sys/netgraph7/ng_base.c @@ -0,0 +1,3711 @@ +/* + * ng_base.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Authors: Julian Elischer + * Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_base.c,v 1.159 2008/04/19 05:30:49 mav Exp $ + * $Whistle: ng_base.c,v 1.39 1999/01/28 23:54:53 julian Exp $ + */ + +/* + * This file implements the base netgraph code. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +MODULE_VERSION(netgraph, NG_ABI_VERSION); + +/* Mutex to protect topology events. */ +static struct mtx ng_topo_mtx; + +#ifdef NETGRAPH_DEBUG +static struct mtx ng_nodelist_mtx; /* protects global node/hook lists */ +static struct mtx ngq_mtx; /* protects the queue item list */ + +static SLIST_HEAD(, ng_node) ng_allnodes; +static LIST_HEAD(, ng_node) ng_freenodes; /* in debug, we never free() them */ +static SLIST_HEAD(, ng_hook) ng_allhooks; +static LIST_HEAD(, ng_hook) ng_freehooks; /* in debug, we never free() them */ + +static void ng_dumpitems(void); +static void ng_dumpnodes(void); +static void ng_dumphooks(void); + +#endif /* NETGRAPH_DEBUG */ +/* + * DEAD versions of the structures. + * In order to avoid races, it is sometimes neccesary to point + * at SOMETHING even though theoretically, the current entity is + * INVALID. Use these to avoid these races. + */ +struct ng_type ng_deadtype = { + NG_ABI_VERSION, + "dead", + NULL, /* modevent */ + NULL, /* constructor */ + NULL, /* rcvmsg */ + NULL, /* shutdown */ + NULL, /* newhook */ + NULL, /* findhook */ + NULL, /* connect */ + NULL, /* rcvdata */ + NULL, /* disconnect */ + NULL, /* cmdlist */ +}; + +struct ng_node ng_deadnode = { + "dead", + &ng_deadtype, + NGF_INVALID, + 0, /* numhooks */ + NULL, /* private */ + 0, /* ID */ + LIST_HEAD_INITIALIZER(ng_deadnode.hooks), + {}, /* all_nodes list entry */ + {}, /* id hashtable list entry */ + { 0, + 0, + {}, /* should never use! (should hang) */ + {}, /* workqueue entry */ + STAILQ_HEAD_INITIALIZER(ng_deadnode.nd_input_queue.queue), + }, + 1, /* refs */ +#ifdef NETGRAPH_DEBUG + ND_MAGIC, + __FILE__, + __LINE__, + {NULL} +#endif /* NETGRAPH_DEBUG */ +}; + +struct ng_hook ng_deadhook = { + "dead", + NULL, /* private */ + HK_INVALID | HK_DEAD, + 0, /* undefined data link type */ + &ng_deadhook, /* Peer is self */ + &ng_deadnode, /* attached to deadnode */ + {}, /* hooks list */ + NULL, /* override rcvmsg() */ + NULL, /* override rcvdata() */ + 1, /* refs always >= 1 */ +#ifdef NETGRAPH_DEBUG + HK_MAGIC, + __FILE__, + __LINE__, + {NULL} +#endif /* NETGRAPH_DEBUG */ +}; + +/* + * END DEAD STRUCTURES + */ +/* List nodes with unallocated work */ +static STAILQ_HEAD(, ng_node) ng_worklist = STAILQ_HEAD_INITIALIZER(ng_worklist); +static struct mtx ng_worklist_mtx; /* MUST LOCK NODE FIRST */ + +/* List of installed types */ +static LIST_HEAD(, ng_type) ng_typelist; +static struct mtx ng_typelist_mtx; + +/* Hash related definitions */ +/* XXX Don't need to initialise them because it's a LIST */ +#define NG_ID_HASH_SIZE 128 /* most systems wont need even this many */ +static LIST_HEAD(, ng_node) ng_ID_hash[NG_ID_HASH_SIZE]; +static struct mtx ng_idhash_mtx; +/* Method to find a node.. used twice so do it here */ +#define NG_IDHASH_FN(ID) ((ID) % (NG_ID_HASH_SIZE)) +#define NG_IDHASH_FIND(ID, node) \ + do { \ + mtx_assert(&ng_idhash_mtx, MA_OWNED); \ + LIST_FOREACH(node, &ng_ID_hash[NG_IDHASH_FN(ID)], \ + nd_idnodes) { \ + if (NG_NODE_IS_VALID(node) \ + && (NG_NODE_ID(node) == ID)) { \ + break; \ + } \ + } \ + } while (0) + +#define NG_NAME_HASH_SIZE 128 /* most systems wont need even this many */ +static LIST_HEAD(, ng_node) ng_name_hash[NG_NAME_HASH_SIZE]; +static struct mtx ng_namehash_mtx; +#define NG_NAMEHASH(NAME, HASH) \ + do { \ + u_char h = 0; \ + const u_char *c; \ + for (c = (const u_char*)(NAME); *c; c++)\ + h += *c; \ + (HASH) = h % (NG_NAME_HASH_SIZE); \ + } while (0) + + +/* Internal functions */ +static int ng_add_hook(node_p node, const char *name, hook_p * hookp); +static int ng_generic_msg(node_p here, item_p item, hook_p lasthook); +static ng_ID_t ng_decodeidname(const char *name); +static int ngb_mod_event(module_t mod, int event, void *data); +static void ng_worklist_add(node_p node); +static void ngintr(void); +static int ng_apply_item(node_p node, item_p item, int rw); +static void ng_flush_input_queue(node_p node); +static node_p ng_ID2noderef(ng_ID_t ID); +static int ng_con_nodes(item_p item, node_p node, const char *name, + node_p node2, const char *name2); +static int ng_con_part2(node_p node, item_p item, hook_p hook); +static int ng_con_part3(node_p node, item_p item, hook_p hook); +static int ng_mkpeer(node_p node, const char *name, + const char *name2, char *type); + +/* Imported, these used to be externally visible, some may go back. */ +void ng_destroy_hook(hook_p hook); +node_p ng_name2noderef(node_p node, const char *name); +int ng_path2noderef(node_p here, const char *path, + node_p *dest, hook_p *lasthook); +int ng_make_node(const char *type, node_p *nodepp); +int ng_path_parse(char *addr, char **node, char **path, char **hook); +void ng_rmnode(node_p node, hook_p dummy1, void *dummy2, int dummy3); +void ng_unname(node_p node); + + +/* Our own netgraph malloc type */ +MALLOC_DEFINE(M_NETGRAPH, "netgraph", "netgraph structures and ctrl messages"); +MALLOC_DEFINE(M_NETGRAPH_HOOK, "netgraph_hook", "netgraph hook structures"); +MALLOC_DEFINE(M_NETGRAPH_NODE, "netgraph_node", "netgraph node structures"); +MALLOC_DEFINE(M_NETGRAPH_ITEM, "netgraph_item", "netgraph item structures"); +MALLOC_DEFINE(M_NETGRAPH_MSG, "netgraph_msg", "netgraph name storage"); + +/* Should not be visible outside this file */ + +#define _NG_ALLOC_HOOK(hook) \ + MALLOC(hook, hook_p, sizeof(*hook), M_NETGRAPH_HOOK, M_NOWAIT | M_ZERO) +#define _NG_ALLOC_NODE(node) \ + MALLOC(node, node_p, sizeof(*node), M_NETGRAPH_NODE, M_NOWAIT | M_ZERO) + +#define NG_QUEUE_LOCK_INIT(n) \ + mtx_init(&(n)->q_mtx, "ng_node", NULL, MTX_DEF) +#define NG_QUEUE_LOCK(n) \ + mtx_lock(&(n)->q_mtx) +#define NG_QUEUE_UNLOCK(n) \ + mtx_unlock(&(n)->q_mtx) +#define NG_WORKLIST_LOCK_INIT() \ + mtx_init(&ng_worklist_mtx, "ng_worklist", NULL, MTX_DEF) +#define NG_WORKLIST_LOCK() \ + mtx_lock(&ng_worklist_mtx) +#define NG_WORKLIST_UNLOCK() \ + mtx_unlock(&ng_worklist_mtx) + +#ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ +/* + * In debug mode: + * In an attempt to help track reference count screwups + * we do not free objects back to the malloc system, but keep them + * in a local cache where we can examine them and keep information safely + * after they have been freed. + * We use this scheme for nodes and hooks, and to some extent for items. + */ +static __inline hook_p +ng_alloc_hook(void) +{ + hook_p hook; + SLIST_ENTRY(ng_hook) temp; + mtx_lock(&ng_nodelist_mtx); + hook = LIST_FIRST(&ng_freehooks); + if (hook) { + LIST_REMOVE(hook, hk_hooks); + bcopy(&hook->hk_all, &temp, sizeof(temp)); + bzero(hook, sizeof(struct ng_hook)); + bcopy(&temp, &hook->hk_all, sizeof(temp)); + mtx_unlock(&ng_nodelist_mtx); + hook->hk_magic = HK_MAGIC; + } else { + mtx_unlock(&ng_nodelist_mtx); + _NG_ALLOC_HOOK(hook); + if (hook) { + hook->hk_magic = HK_MAGIC; + mtx_lock(&ng_nodelist_mtx); + SLIST_INSERT_HEAD(&ng_allhooks, hook, hk_all); + mtx_unlock(&ng_nodelist_mtx); + } + } + return (hook); +} + +static __inline node_p +ng_alloc_node(void) +{ + node_p node; + SLIST_ENTRY(ng_node) temp; + mtx_lock(&ng_nodelist_mtx); + node = LIST_FIRST(&ng_freenodes); + if (node) { + LIST_REMOVE(node, nd_nodes); + bcopy(&node->nd_all, &temp, sizeof(temp)); + bzero(node, sizeof(struct ng_node)); + bcopy(&temp, &node->nd_all, sizeof(temp)); + mtx_unlock(&ng_nodelist_mtx); + node->nd_magic = ND_MAGIC; + } else { + mtx_unlock(&ng_nodelist_mtx); + _NG_ALLOC_NODE(node); + if (node) { + node->nd_magic = ND_MAGIC; + mtx_lock(&ng_nodelist_mtx); + SLIST_INSERT_HEAD(&ng_allnodes, node, nd_all); + mtx_unlock(&ng_nodelist_mtx); + } + } + return (node); +} + +#define NG_ALLOC_HOOK(hook) do { (hook) = ng_alloc_hook(); } while (0) +#define NG_ALLOC_NODE(node) do { (node) = ng_alloc_node(); } while (0) + + +#define NG_FREE_HOOK(hook) \ + do { \ + mtx_lock(&ng_nodelist_mtx); \ + LIST_INSERT_HEAD(&ng_freehooks, hook, hk_hooks); \ + hook->hk_magic = 0; \ + mtx_unlock(&ng_nodelist_mtx); \ + } while (0) + +#define NG_FREE_NODE(node) \ + do { \ + mtx_lock(&ng_nodelist_mtx); \ + LIST_INSERT_HEAD(&ng_freenodes, node, nd_nodes); \ + node->nd_magic = 0; \ + mtx_unlock(&ng_nodelist_mtx); \ + } while (0) + +#else /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ + +#define NG_ALLOC_HOOK(hook) _NG_ALLOC_HOOK(hook) +#define NG_ALLOC_NODE(node) _NG_ALLOC_NODE(node) + +#define NG_FREE_HOOK(hook) do { FREE((hook), M_NETGRAPH_HOOK); } while (0) +#define NG_FREE_NODE(node) do { FREE((node), M_NETGRAPH_NODE); } while (0) + +#endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ + +/* Set this to kdb_enter("X") to catch all errors as they occur */ +#ifndef TRAP_ERROR +#define TRAP_ERROR() +#endif + +static ng_ID_t nextID = 1; + +#ifdef INVARIANTS +#define CHECK_DATA_MBUF(m) do { \ + struct mbuf *n; \ + int total; \ + \ + M_ASSERTPKTHDR(m); \ + for (total = 0, n = (m); n != NULL; n = n->m_next) { \ + total += n->m_len; \ + if (n->m_nextpkt != NULL) \ + panic("%s: m_nextpkt", __func__); \ + } \ + \ + if ((m)->m_pkthdr.len != total) { \ + panic("%s: %d != %d", \ + __func__, (m)->m_pkthdr.len, total); \ + } \ + } while (0) +#else +#define CHECK_DATA_MBUF(m) +#endif + +#define ERROUT(x) do { error = (x); goto done; } while (0) + +/************************************************************************ + Parse type definitions for generic messages +************************************************************************/ + +/* Handy structure parse type defining macro */ +#define DEFINE_PARSE_STRUCT_TYPE(lo, up, args) \ +static const struct ng_parse_struct_field \ + ng_ ## lo ## _type_fields[] = NG_GENERIC_ ## up ## _INFO args; \ +static const struct ng_parse_type ng_generic_ ## lo ## _type = { \ + &ng_parse_struct_type, \ + &ng_ ## lo ## _type_fields \ +} + +DEFINE_PARSE_STRUCT_TYPE(mkpeer, MKPEER, ()); +DEFINE_PARSE_STRUCT_TYPE(connect, CONNECT, ()); +DEFINE_PARSE_STRUCT_TYPE(name, NAME, ()); +DEFINE_PARSE_STRUCT_TYPE(rmhook, RMHOOK, ()); +DEFINE_PARSE_STRUCT_TYPE(nodeinfo, NODEINFO, ()); +DEFINE_PARSE_STRUCT_TYPE(typeinfo, TYPEINFO, ()); +DEFINE_PARSE_STRUCT_TYPE(linkinfo, LINKINFO, (&ng_generic_nodeinfo_type)); + +/* Get length of an array when the length is stored as a 32 bit + value immediately preceding the array -- as with struct namelist + and struct typelist. */ +static int +ng_generic_list_getLength(const struct ng_parse_type *type, + const u_char *start, const u_char *buf) +{ + return *((const u_int32_t *)(buf - 4)); +} + +/* Get length of the array of struct linkinfo inside a struct hooklist */ +static int +ng_generic_linkinfo_getLength(const struct ng_parse_type *type, + const u_char *start, const u_char *buf) +{ + const struct hooklist *hl = (const struct hooklist *)start; + + return hl->nodeinfo.hooks; +} + +/* Array type for a variable length array of struct namelist */ +static const struct ng_parse_array_info ng_nodeinfoarray_type_info = { + &ng_generic_nodeinfo_type, + &ng_generic_list_getLength +}; +static const struct ng_parse_type ng_generic_nodeinfoarray_type = { + &ng_parse_array_type, + &ng_nodeinfoarray_type_info +}; + +/* Array type for a variable length array of struct typelist */ +static const struct ng_parse_array_info ng_typeinfoarray_type_info = { + &ng_generic_typeinfo_type, + &ng_generic_list_getLength +}; +static const struct ng_parse_type ng_generic_typeinfoarray_type = { + &ng_parse_array_type, + &ng_typeinfoarray_type_info +}; + +/* Array type for array of struct linkinfo in struct hooklist */ +static const struct ng_parse_array_info ng_generic_linkinfo_array_type_info = { + &ng_generic_linkinfo_type, + &ng_generic_linkinfo_getLength +}; +static const struct ng_parse_type ng_generic_linkinfo_array_type = { + &ng_parse_array_type, + &ng_generic_linkinfo_array_type_info +}; + +DEFINE_PARSE_STRUCT_TYPE(typelist, TYPELIST, (&ng_generic_nodeinfoarray_type)); +DEFINE_PARSE_STRUCT_TYPE(hooklist, HOOKLIST, + (&ng_generic_nodeinfo_type, &ng_generic_linkinfo_array_type)); +DEFINE_PARSE_STRUCT_TYPE(listnodes, LISTNODES, + (&ng_generic_nodeinfoarray_type)); + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_generic_cmds[] = { + { + NGM_GENERIC_COOKIE, + NGM_SHUTDOWN, + "shutdown", + NULL, + NULL + }, + { + NGM_GENERIC_COOKIE, + NGM_MKPEER, + "mkpeer", + &ng_generic_mkpeer_type, + NULL + }, + { + NGM_GENERIC_COOKIE, + NGM_CONNECT, + "connect", + &ng_generic_connect_type, + NULL + }, + { + NGM_GENERIC_COOKIE, + NGM_NAME, + "name", + &ng_generic_name_type, + NULL + }, + { + NGM_GENERIC_COOKIE, + NGM_RMHOOK, + "rmhook", + &ng_generic_rmhook_type, + NULL + }, + { + NGM_GENERIC_COOKIE, + NGM_NODEINFO, + "nodeinfo", + NULL, + &ng_generic_nodeinfo_type + }, + { + NGM_GENERIC_COOKIE, + NGM_LISTHOOKS, + "listhooks", + NULL, + &ng_generic_hooklist_type + }, + { + NGM_GENERIC_COOKIE, + NGM_LISTNAMES, + "listnames", + NULL, + &ng_generic_listnodes_type /* same as NGM_LISTNODES */ + }, + { + NGM_GENERIC_COOKIE, + NGM_LISTNODES, + "listnodes", + NULL, + &ng_generic_listnodes_type + }, + { + NGM_GENERIC_COOKIE, + NGM_LISTTYPES, + "listtypes", + NULL, + &ng_generic_typeinfo_type + }, + { + NGM_GENERIC_COOKIE, + NGM_TEXT_CONFIG, + "textconfig", + NULL, + &ng_parse_string_type + }, + { + NGM_GENERIC_COOKIE, + NGM_TEXT_STATUS, + "textstatus", + NULL, + &ng_parse_string_type + }, + { + NGM_GENERIC_COOKIE, + NGM_ASCII2BINARY, + "ascii2binary", + &ng_parse_ng_mesg_type, + &ng_parse_ng_mesg_type + }, + { + NGM_GENERIC_COOKIE, + NGM_BINARY2ASCII, + "binary2ascii", + &ng_parse_ng_mesg_type, + &ng_parse_ng_mesg_type + }, + { 0 } +}; + +/************************************************************************ + Node routines +************************************************************************/ + +/* + * Instantiate a node of the requested type + */ +int +ng_make_node(const char *typename, node_p *nodepp) +{ + struct ng_type *type; + int error; + + /* Check that the type makes sense */ + if (typename == NULL) { + TRAP_ERROR(); + return (EINVAL); + } + + /* Locate the node type. If we fail we return. Do not try to load + * module. + */ + if ((type = ng_findtype(typename)) == NULL) + return (ENXIO); + + /* + * If we have a constructor, then make the node and + * call the constructor to do type specific initialisation. + */ + if (type->constructor != NULL) { + if ((error = ng_make_node_common(type, nodepp)) == 0) { + if ((error = ((*type->constructor)(*nodepp)) != 0)) { + NG_NODE_UNREF(*nodepp); + } + } + } else { + /* + * Node has no constructor. We cannot ask for one + * to be made. It must be brought into existence by + * some external agency. The external agency should + * call ng_make_node_common() directly to get the + * netgraph part initialised. + */ + TRAP_ERROR(); + error = EINVAL; + } + return (error); +} + +/* + * Generic node creation. Called by node initialisation for externally + * instantiated nodes (e.g. hardware, sockets, etc ). + * The returned node has a reference count of 1. + */ +int +ng_make_node_common(struct ng_type *type, node_p *nodepp) +{ + node_p node; + + /* Require the node type to have been already installed */ + if (ng_findtype(type->name) == NULL) { + TRAP_ERROR(); + return (EINVAL); + } + + /* Make a node and try attach it to the type */ + NG_ALLOC_NODE(node); + if (node == NULL) { + TRAP_ERROR(); + return (ENOMEM); + } + node->nd_type = type; + NG_NODE_REF(node); /* note reference */ + type->refs++; + + NG_QUEUE_LOCK_INIT(&node->nd_input_queue); + STAILQ_INIT(&node->nd_input_queue.queue); + node->nd_input_queue.q_flags = 0; + + /* Initialize hook list for new node */ + LIST_INIT(&node->nd_hooks); + + /* Link us into the name hash. */ + mtx_lock(&ng_namehash_mtx); + LIST_INSERT_HEAD(&ng_name_hash[0], node, nd_nodes); + mtx_unlock(&ng_namehash_mtx); + + /* get an ID and put us in the hash chain */ + mtx_lock(&ng_idhash_mtx); + for (;;) { /* wrap protection, even if silly */ + node_p node2 = NULL; + node->nd_ID = nextID++; /* 137/second for 1 year before wrap */ + + /* Is there a problem with the new number? */ + NG_IDHASH_FIND(node->nd_ID, node2); /* already taken? */ + if ((node->nd_ID != 0) && (node2 == NULL)) { + break; + } + } + LIST_INSERT_HEAD(&ng_ID_hash[NG_IDHASH_FN(node->nd_ID)], + node, nd_idnodes); + mtx_unlock(&ng_idhash_mtx); + + /* Done */ + *nodepp = node; + return (0); +} + +/* + * Forceably start the shutdown process on a node. Either call + * its shutdown method, or do the default shutdown if there is + * no type-specific method. + * + * We can only be called from a shutdown message, so we know we have + * a writer lock, and therefore exclusive access. It also means + * that we should not be on the work queue, but we check anyhow. + * + * Persistent node types must have a type-specific method which + * allocates a new node in which case, this one is irretrievably going away, + * or cleans up anything it needs, and just makes the node valid again, + * in which case we allow the node to survive. + * + * XXX We need to think of how to tell a persistent node that we + * REALLY need to go away because the hardware has gone or we + * are rebooting.... etc. + */ +void +ng_rmnode(node_p node, hook_p dummy1, void *dummy2, int dummy3) +{ + hook_p hook; + + /* Check if it's already shutting down */ + if ((node->nd_flags & NGF_CLOSING) != 0) + return; + + if (node == &ng_deadnode) { + printf ("shutdown called on deadnode\n"); + return; + } + + /* Add an extra reference so it doesn't go away during this */ + NG_NODE_REF(node); + + /* + * Mark it invalid so any newcomers know not to try use it + * Also add our own mark so we can't recurse + * note that NGF_INVALID does not do this as it's also set during + * creation + */ + node->nd_flags |= NGF_INVALID|NGF_CLOSING; + + /* If node has its pre-shutdown method, then call it first*/ + if (node->nd_type && node->nd_type->close) + (*node->nd_type->close)(node); + + /* Notify all remaining connected nodes to disconnect */ + while ((hook = LIST_FIRST(&node->nd_hooks)) != NULL) + ng_destroy_hook(hook); + + /* + * Drain the input queue forceably. + * it has no hooks so what's it going to do, bleed on someone? + * Theoretically we came here from a queue entry that was added + * Just before the queue was closed, so it should be empty anyway. + * Also removes us from worklist if needed. + */ + ng_flush_input_queue(node); + + /* Ask the type if it has anything to do in this case */ + if (node->nd_type && node->nd_type->shutdown) { + (*node->nd_type->shutdown)(node); + if (NG_NODE_IS_VALID(node)) { + /* + * Well, blow me down if the node code hasn't declared + * that it doesn't want to die. + * Presumably it is a persistant node. + * If we REALLY want it to go away, + * e.g. hardware going away, + * Our caller should set NGF_REALLY_DIE in nd_flags. + */ + node->nd_flags &= ~(NGF_INVALID|NGF_CLOSING); + NG_NODE_UNREF(node); /* Assume they still have theirs */ + return; + } + } else { /* do the default thing */ + NG_NODE_UNREF(node); + } + + ng_unname(node); /* basically a NOP these days */ + + /* + * Remove extra reference, possibly the last + * Possible other holders of references may include + * timeout callouts, but theoretically the node's supposed to + * have cancelled them. Possibly hardware dependencies may + * force a driver to 'linger' with a reference. + */ + NG_NODE_UNREF(node); +} + +/* + * Remove a reference to the node, possibly the last. + * deadnode always acts as it it were the last. + */ +int +ng_unref_node(node_p node) +{ + int v; + + if (node == &ng_deadnode) { + return (0); + } + + v = atomic_fetchadd_int(&node->nd_refs, -1); + + if (v == 1) { /* we were the last */ + + mtx_lock(&ng_namehash_mtx); + node->nd_type->refs--; /* XXX maybe should get types lock? */ + LIST_REMOVE(node, nd_nodes); + mtx_unlock(&ng_namehash_mtx); + + mtx_lock(&ng_idhash_mtx); + LIST_REMOVE(node, nd_idnodes); + mtx_unlock(&ng_idhash_mtx); + + mtx_destroy(&node->nd_input_queue.q_mtx); + NG_FREE_NODE(node); + } + return (v - 1); +} + +/************************************************************************ + Node ID handling +************************************************************************/ +static node_p +ng_ID2noderef(ng_ID_t ID) +{ + node_p node; + mtx_lock(&ng_idhash_mtx); + NG_IDHASH_FIND(ID, node); + if(node) + NG_NODE_REF(node); + mtx_unlock(&ng_idhash_mtx); + return(node); +} + +ng_ID_t +ng_node2ID(node_p node) +{ + return (node ? NG_NODE_ID(node) : 0); +} + +/************************************************************************ + Node name handling +************************************************************************/ + +/* + * Assign a node a name. Once assigned, the name cannot be changed. + */ +int +ng_name_node(node_p node, const char *name) +{ + int i, hash; + node_p node2; + + /* Check the name is valid */ + for (i = 0; i < NG_NODESIZ; i++) { + if (name[i] == '\0' || name[i] == '.' || name[i] == ':') + break; + } + if (i == 0 || name[i] != '\0') { + TRAP_ERROR(); + return (EINVAL); + } + if (ng_decodeidname(name) != 0) { /* valid IDs not allowed here */ + TRAP_ERROR(); + return (EINVAL); + } + + /* Check the name isn't already being used */ + if ((node2 = ng_name2noderef(node, name)) != NULL) { + NG_NODE_UNREF(node2); + TRAP_ERROR(); + return (EADDRINUSE); + } + + /* copy it */ + strlcpy(NG_NODE_NAME(node), name, NG_NODESIZ); + + /* Update name hash. */ + NG_NAMEHASH(name, hash); + mtx_lock(&ng_namehash_mtx); + LIST_REMOVE(node, nd_nodes); + LIST_INSERT_HEAD(&ng_name_hash[hash], node, nd_nodes); + mtx_unlock(&ng_namehash_mtx); + + return (0); +} + +/* + * Find a node by absolute name. The name should NOT end with ':' + * The name "." means "this node" and "[xxx]" means "the node + * with ID (ie, at address) xxx". + * + * Returns the node if found, else NULL. + * Eventually should add something faster than a sequential search. + * Note it acquires a reference on the node so you can be sure it's still + * there. + */ +node_p +ng_name2noderef(node_p here, const char *name) +{ + node_p node; + ng_ID_t temp; + int hash; + + /* "." means "this node" */ + if (strcmp(name, ".") == 0) { + NG_NODE_REF(here); + return(here); + } + + /* Check for name-by-ID */ + if ((temp = ng_decodeidname(name)) != 0) { + return (ng_ID2noderef(temp)); + } + + /* Find node by name */ + NG_NAMEHASH(name, hash); + mtx_lock(&ng_namehash_mtx); + LIST_FOREACH(node, &ng_name_hash[hash], nd_nodes) { + if (NG_NODE_IS_VALID(node) && + (strcmp(NG_NODE_NAME(node), name) == 0)) { + break; + } + } + if (node) + NG_NODE_REF(node); + mtx_unlock(&ng_namehash_mtx); + return (node); +} + +/* + * Decode an ID name, eg. "[f03034de]". Returns 0 if the + * string is not valid, otherwise returns the value. + */ +static ng_ID_t +ng_decodeidname(const char *name) +{ + const int len = strlen(name); + char *eptr; + u_long val; + + /* Check for proper length, brackets, no leading junk */ + if ((len < 3) + || (name[0] != '[') + || (name[len - 1] != ']') + || (!isxdigit(name[1]))) { + return ((ng_ID_t)0); + } + + /* Decode number */ + val = strtoul(name + 1, &eptr, 16); + if ((eptr - name != len - 1) + || (val == ULONG_MAX) + || (val == 0)) { + return ((ng_ID_t)0); + } + return (ng_ID_t)val; +} + +/* + * Remove a name from a node. This should only be called + * when shutting down and removing the node. + * IF we allow name changing this may be more resurrected. + */ +void +ng_unname(node_p node) +{ +} + +/************************************************************************ + Hook routines + Names are not optional. Hooks are always connected, except for a + brief moment within these routines. On invalidation or during creation + they are connected to the 'dead' hook. +************************************************************************/ + +/* + * Remove a hook reference + */ +void +ng_unref_hook(hook_p hook) +{ + int v; + + if (hook == &ng_deadhook) { + return; + } + + v = atomic_fetchadd_int(&hook->hk_refs, -1); + + if (v == 1) { /* we were the last */ + if (_NG_HOOK_NODE(hook)) /* it'll probably be ng_deadnode */ + _NG_NODE_UNREF((_NG_HOOK_NODE(hook))); + NG_FREE_HOOK(hook); + } +} + +/* + * Add an unconnected hook to a node. Only used internally. + * Assumes node is locked. (XXX not yet true ) + */ +static int +ng_add_hook(node_p node, const char *name, hook_p *hookp) +{ + hook_p hook; + int error = 0; + + /* Check that the given name is good */ + if (name == NULL) { + TRAP_ERROR(); + return (EINVAL); + } + if (ng_findhook(node, name) != NULL) { + TRAP_ERROR(); + return (EEXIST); + } + + /* Allocate the hook and link it up */ + NG_ALLOC_HOOK(hook); + if (hook == NULL) { + TRAP_ERROR(); + return (ENOMEM); + } + hook->hk_refs = 1; /* add a reference for us to return */ + hook->hk_flags = HK_INVALID; + hook->hk_peer = &ng_deadhook; /* start off this way */ + hook->hk_node = node; + NG_NODE_REF(node); /* each hook counts as a reference */ + + /* Set hook name */ + strlcpy(NG_HOOK_NAME(hook), name, NG_HOOKSIZ); + + /* + * Check if the node type code has something to say about it + * If it fails, the unref of the hook will also unref the node. + */ + if (node->nd_type->newhook != NULL) { + if ((error = (*node->nd_type->newhook)(node, hook, name))) { + NG_HOOK_UNREF(hook); /* this frees the hook */ + return (error); + } + } + /* + * The 'type' agrees so far, so go ahead and link it in. + * We'll ask again later when we actually connect the hooks. + */ + LIST_INSERT_HEAD(&node->nd_hooks, hook, hk_hooks); + node->nd_numhooks++; + NG_HOOK_REF(hook); /* one for the node */ + + if (hookp) + *hookp = hook; + return (0); +} + +/* + * Find a hook + * + * Node types may supply their own optimized routines for finding + * hooks. If none is supplied, we just do a linear search. + * XXX Possibly we should add a reference to the hook? + */ +hook_p +ng_findhook(node_p node, const char *name) +{ + hook_p hook; + + if (node->nd_type->findhook != NULL) + return (*node->nd_type->findhook)(node, name); + LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) { + if (NG_HOOK_IS_VALID(hook) + && (strcmp(NG_HOOK_NAME(hook), name) == 0)) + return (hook); + } + return (NULL); +} + +/* + * Destroy a hook + * + * As hooks are always attached, this really destroys two hooks. + * The one given, and the one attached to it. Disconnect the hooks + * from each other first. We reconnect the peer hook to the 'dead' + * hook so that it can still exist after we depart. We then + * send the peer its own destroy message. This ensures that we only + * interact with the peer's structures when it is locked processing that + * message. We hold a reference to the peer hook so we are guaranteed that + * the peer hook and node are still going to exist until + * we are finished there as the hook holds a ref on the node. + * We run this same code again on the peer hook, but that time it is already + * attached to the 'dead' hook. + * + * This routine is called at all stages of hook creation + * on error detection and must be able to handle any such stage. + */ +void +ng_destroy_hook(hook_p hook) +{ + hook_p peer; + node_p node; + + if (hook == &ng_deadhook) { /* better safe than sorry */ + printf("ng_destroy_hook called on deadhook\n"); + return; + } + + /* + * Protect divorce process with mutex, to avoid races on + * simultaneous disconnect. + */ + mtx_lock(&ng_topo_mtx); + + hook->hk_flags |= HK_INVALID; + + peer = NG_HOOK_PEER(hook); + node = NG_HOOK_NODE(hook); + + if (peer && (peer != &ng_deadhook)) { + /* + * Set the peer to point to ng_deadhook + * from this moment on we are effectively independent it. + * send it an rmhook message of it's own. + */ + peer->hk_peer = &ng_deadhook; /* They no longer know us */ + hook->hk_peer = &ng_deadhook; /* Nor us, them */ + if (NG_HOOK_NODE(peer) == &ng_deadnode) { + /* + * If it's already divorced from a node, + * just free it. + */ + mtx_unlock(&ng_topo_mtx); + } else { + mtx_unlock(&ng_topo_mtx); + ng_rmhook_self(peer); /* Send it a surprise */ + } + NG_HOOK_UNREF(peer); /* account for peer link */ + NG_HOOK_UNREF(hook); /* account for peer link */ + } else + mtx_unlock(&ng_topo_mtx); + + mtx_assert(&ng_topo_mtx, MA_NOTOWNED); + + /* + * Remove the hook from the node's list to avoid possible recursion + * in case the disconnection results in node shutdown. + */ + if (node == &ng_deadnode) { /* happens if called from ng_con_nodes() */ + return; + } + LIST_REMOVE(hook, hk_hooks); + node->nd_numhooks--; + if (node->nd_type->disconnect) { + /* + * The type handler may elect to destroy the node so don't + * trust its existence after this point. (except + * that we still hold a reference on it. (which we + * inherrited from the hook we are destroying) + */ + (*node->nd_type->disconnect) (hook); + } + + /* + * Note that because we will point to ng_deadnode, the original node + * is not decremented automatically so we do that manually. + */ + _NG_HOOK_NODE(hook) = &ng_deadnode; + NG_NODE_UNREF(node); /* We no longer point to it so adjust count */ + NG_HOOK_UNREF(hook); /* Account for linkage (in list) to node */ +} + +/* + * Take two hooks on a node and merge the connection so that the given node + * is effectively bypassed. + */ +int +ng_bypass(hook_p hook1, hook_p hook2) +{ + if (hook1->hk_node != hook2->hk_node) { + TRAP_ERROR(); + return (EINVAL); + } + hook1->hk_peer->hk_peer = hook2->hk_peer; + hook2->hk_peer->hk_peer = hook1->hk_peer; + + hook1->hk_peer = &ng_deadhook; + hook2->hk_peer = &ng_deadhook; + + NG_HOOK_UNREF(hook1); + NG_HOOK_UNREF(hook2); + + /* XXX If we ever cache methods on hooks update them as well */ + ng_destroy_hook(hook1); + ng_destroy_hook(hook2); + return (0); +} + +/* + * Install a new netgraph type + */ +int +ng_newtype(struct ng_type *tp) +{ + const size_t namelen = strlen(tp->name); + + /* Check version and type name fields */ + if ((tp->version != NG_ABI_VERSION) + || (namelen == 0) + || (namelen >= NG_TYPESIZ)) { + TRAP_ERROR(); + if (tp->version != NG_ABI_VERSION) { + printf("Netgraph: Node type rejected. ABI mismatch. Suggest recompile\n"); + } + return (EINVAL); + } + + /* Check for name collision */ + if (ng_findtype(tp->name) != NULL) { + TRAP_ERROR(); + return (EEXIST); + } + + + /* Link in new type */ + mtx_lock(&ng_typelist_mtx); + LIST_INSERT_HEAD(&ng_typelist, tp, types); + tp->refs = 1; /* first ref is linked list */ + mtx_unlock(&ng_typelist_mtx); + return (0); +} + +/* + * unlink a netgraph type + * If no examples exist + */ +int +ng_rmtype(struct ng_type *tp) +{ + /* Check for name collision */ + if (tp->refs != 1) { + TRAP_ERROR(); + return (EBUSY); + } + + /* Unlink type */ + mtx_lock(&ng_typelist_mtx); + LIST_REMOVE(tp, types); + mtx_unlock(&ng_typelist_mtx); + return (0); +} + +/* + * Look for a type of the name given + */ +struct ng_type * +ng_findtype(const char *typename) +{ + struct ng_type *type; + + mtx_lock(&ng_typelist_mtx); + LIST_FOREACH(type, &ng_typelist, types) { + if (strcmp(type->name, typename) == 0) + break; + } + mtx_unlock(&ng_typelist_mtx); + return (type); +} + +/************************************************************************ + Composite routines +************************************************************************/ +/* + * Connect two nodes using the specified hooks, using queued functions. + */ +static int +ng_con_part3(node_p node, item_p item, hook_p hook) +{ + int error = 0; + + /* + * When we run, we know that the node 'node' is locked for us. + * Our caller has a reference on the hook. + * Our caller has a reference on the node. + * (In this case our caller is ng_apply_item() ). + * The peer hook has a reference on the hook. + * We are all set up except for the final call to the node, and + * the clearing of the INVALID flag. + */ + if (NG_HOOK_NODE(hook) == &ng_deadnode) { + /* + * The node must have been freed again since we last visited + * here. ng_destry_hook() has this effect but nothing else does. + * We should just release our references and + * free anything we can think of. + * Since we know it's been destroyed, and it's our caller + * that holds the references, just return. + */ + ERROUT(ENOENT); + } + if (hook->hk_node->nd_type->connect) { + if ((error = (*hook->hk_node->nd_type->connect) (hook))) { + ng_destroy_hook(hook); /* also zaps peer */ + printf("failed in ng_con_part3()\n"); + ERROUT(error); + } + } + /* + * XXX this is wrong for SMP. Possibly we need + * to separate out 'create' and 'invalid' flags. + * should only set flags on hooks we have locked under our node. + */ + hook->hk_flags &= ~HK_INVALID; +done: + NG_FREE_ITEM(item); + return (error); +} + +static int +ng_con_part2(node_p node, item_p item, hook_p hook) +{ + hook_p peer; + int error = 0; + + /* + * When we run, we know that the node 'node' is locked for us. + * Our caller has a reference on the hook. + * Our caller has a reference on the node. + * (In this case our caller is ng_apply_item() ). + * The peer hook has a reference on the hook. + * our node pointer points to the 'dead' node. + * First check the hook name is unique. + * Should not happen because we checked before queueing this. + */ + if (ng_findhook(node, NG_HOOK_NAME(hook)) != NULL) { + TRAP_ERROR(); + ng_destroy_hook(hook); /* should destroy peer too */ + printf("failed in ng_con_part2()\n"); + ERROUT(EEXIST); + } + /* + * Check if the node type code has something to say about it + * If it fails, the unref of the hook will also unref the attached node, + * however since that node is 'ng_deadnode' this will do nothing. + * The peer hook will also be destroyed. + */ + if (node->nd_type->newhook != NULL) { + if ((error = (*node->nd_type->newhook)(node, hook, + hook->hk_name))) { + ng_destroy_hook(hook); /* should destroy peer too */ + printf("failed in ng_con_part2()\n"); + ERROUT(error); + } + } + + /* + * The 'type' agrees so far, so go ahead and link it in. + * We'll ask again later when we actually connect the hooks. + */ + hook->hk_node = node; /* just overwrite ng_deadnode */ + NG_NODE_REF(node); /* each hook counts as a reference */ + LIST_INSERT_HEAD(&node->nd_hooks, hook, hk_hooks); + node->nd_numhooks++; + NG_HOOK_REF(hook); /* one for the node */ + + /* + * We now have a symmetrical situation, where both hooks have been + * linked to their nodes, the newhook methods have been called + * And the references are all correct. The hooks are still marked + * as invalid, as we have not called the 'connect' methods + * yet. + * We can call the local one immediately as we have the + * node locked, but we need to queue the remote one. + */ + if (hook->hk_node->nd_type->connect) { + if ((error = (*hook->hk_node->nd_type->connect) (hook))) { + ng_destroy_hook(hook); /* also zaps peer */ + printf("failed in ng_con_part2(A)\n"); + ERROUT(error); + } + } + + /* + * Acquire topo mutex to avoid race with ng_destroy_hook(). + */ + mtx_lock(&ng_topo_mtx); + peer = hook->hk_peer; + if (peer == &ng_deadhook) { + mtx_unlock(&ng_topo_mtx); + printf("failed in ng_con_part2(B)\n"); + ng_destroy_hook(hook); + ERROUT(ENOENT); + } + mtx_unlock(&ng_topo_mtx); + + if ((error = ng_send_fn2(peer->hk_node, peer, item, &ng_con_part3, + NULL, 0, NG_REUSE_ITEM))) { + printf("failed in ng_con_part2(C)\n"); + ng_destroy_hook(hook); /* also zaps peer */ + return (error); /* item was consumed. */ + } + hook->hk_flags &= ~HK_INVALID; /* need both to be able to work */ + return (0); /* item was consumed. */ +done: + NG_FREE_ITEM(item); + return (error); +} + +/* + * Connect this node with another node. We assume that this node is + * currently locked, as we are only called from an NGM_CONNECT message. + */ +static int +ng_con_nodes(item_p item, node_p node, const char *name, + node_p node2, const char *name2) +{ + int error; + hook_p hook; + hook_p hook2; + + if (ng_findhook(node2, name2) != NULL) { + return(EEXIST); + } + if ((error = ng_add_hook(node, name, &hook))) /* gives us a ref */ + return (error); + /* Allocate the other hook and link it up */ + NG_ALLOC_HOOK(hook2); + if (hook2 == NULL) { + TRAP_ERROR(); + ng_destroy_hook(hook); /* XXX check ref counts so far */ + NG_HOOK_UNREF(hook); /* including our ref */ + return (ENOMEM); + } + hook2->hk_refs = 1; /* start with a reference for us. */ + hook2->hk_flags = HK_INVALID; + hook2->hk_peer = hook; /* Link the two together */ + hook->hk_peer = hook2; + NG_HOOK_REF(hook); /* Add a ref for the peer to each*/ + NG_HOOK_REF(hook2); + hook2->hk_node = &ng_deadnode; + strlcpy(NG_HOOK_NAME(hook2), name2, NG_HOOKSIZ); + + /* + * Queue the function above. + * Procesing continues in that function in the lock context of + * the other node. + */ + if ((error = ng_send_fn2(node2, hook2, item, &ng_con_part2, NULL, 0, + NG_NOFLAGS))) { + printf("failed in ng_con_nodes(): %d\n", error); + ng_destroy_hook(hook); /* also zaps peer */ + } + + NG_HOOK_UNREF(hook); /* Let each hook go if it wants to */ + NG_HOOK_UNREF(hook2); + return (error); +} + +/* + * Make a peer and connect. + * We assume that the local node is locked. + * The new node probably doesn't need a lock until + * it has a hook, because it cannot really have any work until then, + * but we should think about it a bit more. + * + * The problem may come if the other node also fires up + * some hardware or a timer or some other source of activation, + * also it may already get a command msg via it's ID. + * + * We could use the same method as ng_con_nodes() but we'd have + * to add ability to remove the node when failing. (Not hard, just + * make arg1 point to the node to remove). + * Unless of course we just ignore failure to connect and leave + * an unconnected node? + */ +static int +ng_mkpeer(node_p node, const char *name, const char *name2, char *type) +{ + node_p node2; + hook_p hook1, hook2; + int error; + + if ((error = ng_make_node(type, &node2))) { + return (error); + } + + if ((error = ng_add_hook(node, name, &hook1))) { /* gives us a ref */ + ng_rmnode(node2, NULL, NULL, 0); + return (error); + } + + if ((error = ng_add_hook(node2, name2, &hook2))) { + ng_rmnode(node2, NULL, NULL, 0); + ng_destroy_hook(hook1); + NG_HOOK_UNREF(hook1); + return (error); + } + + /* + * Actually link the two hooks together. + */ + hook1->hk_peer = hook2; + hook2->hk_peer = hook1; + + /* Each hook is referenced by the other */ + NG_HOOK_REF(hook1); + NG_HOOK_REF(hook2); + + /* Give each node the opportunity to veto the pending connection */ + if (hook1->hk_node->nd_type->connect) { + error = (*hook1->hk_node->nd_type->connect) (hook1); + } + + if ((error == 0) && hook2->hk_node->nd_type->connect) { + error = (*hook2->hk_node->nd_type->connect) (hook2); + + } + + /* + * drop the references we were holding on the two hooks. + */ + if (error) { + ng_destroy_hook(hook2); /* also zaps hook1 */ + ng_rmnode(node2, NULL, NULL, 0); + } else { + /* As a last act, allow the hooks to be used */ + hook1->hk_flags &= ~HK_INVALID; + hook2->hk_flags &= ~HK_INVALID; + } + NG_HOOK_UNREF(hook1); + NG_HOOK_UNREF(hook2); + return (error); +} + +/************************************************************************ + Utility routines to send self messages +************************************************************************/ + +/* Shut this node down as soon as everyone is clear of it */ +/* Should add arg "immediately" to jump the queue */ +int +ng_rmnode_self(node_p node) +{ + int error; + + if (node == &ng_deadnode) + return (0); + node->nd_flags |= NGF_INVALID; + if (node->nd_flags & NGF_CLOSING) + return (0); + + error = ng_send_fn(node, NULL, &ng_rmnode, NULL, 0); + return (error); +} + +static void +ng_rmhook_part2(node_p node, hook_p hook, void *arg1, int arg2) +{ + ng_destroy_hook(hook); + return ; +} + +int +ng_rmhook_self(hook_p hook) +{ + int error; + node_p node = NG_HOOK_NODE(hook); + + if (node == &ng_deadnode) + return (0); + + error = ng_send_fn(node, hook, &ng_rmhook_part2, NULL, 0); + return (error); +} + +/*********************************************************************** + * Parse and verify a string of the form: + * + * Such a string can refer to a specific node or a specific hook + * on a specific node, depending on how you look at it. In the + * latter case, the PATH component must not end in a dot. + * + * Both and are optional. The is a string + * of hook names separated by dots. This breaks out the original + * string, setting *nodep to "NODE" (or NULL if none) and *pathp + * to "PATH" (or NULL if degenerate). Also, *hookp will point to + * the final hook component of , if any, otherwise NULL. + * + * This returns -1 if the path is malformed. The char ** are optional. + ***********************************************************************/ +int +ng_path_parse(char *addr, char **nodep, char **pathp, char **hookp) +{ + char *node, *path, *hook; + int k; + + /* + * Extract absolute NODE, if any + */ + for (path = addr; *path && *path != ':'; path++); + if (*path) { + node = addr; /* Here's the NODE */ + *path++ = '\0'; /* Here's the PATH */ + + /* Node name must not be empty */ + if (!*node) + return -1; + + /* A name of "." is OK; otherwise '.' not allowed */ + if (strcmp(node, ".") != 0) { + for (k = 0; node[k]; k++) + if (node[k] == '.') + return -1; + } + } else { + node = NULL; /* No absolute NODE */ + path = addr; /* Here's the PATH */ + } + + /* Snoop for illegal characters in PATH */ + for (k = 0; path[k]; k++) + if (path[k] == ':') + return -1; + + /* Check for no repeated dots in PATH */ + for (k = 0; path[k]; k++) + if (path[k] == '.' && path[k + 1] == '.') + return -1; + + /* Remove extra (degenerate) dots from beginning or end of PATH */ + if (path[0] == '.') + path++; + if (*path && path[strlen(path) - 1] == '.') + path[strlen(path) - 1] = 0; + + /* If PATH has a dot, then we're not talking about a hook */ + if (*path) { + for (hook = path, k = 0; path[k]; k++) + if (path[k] == '.') { + hook = NULL; + break; + } + } else + path = hook = NULL; + + /* Done */ + if (nodep) + *nodep = node; + if (pathp) + *pathp = path; + if (hookp) + *hookp = hook; + return (0); +} + +/* + * Given a path, which may be absolute or relative, and a starting node, + * return the destination node. + */ +int +ng_path2noderef(node_p here, const char *address, + node_p *destp, hook_p *lasthook) +{ + char fullpath[NG_PATHSIZ]; + char *nodename, *path, pbuf[2]; + node_p node, oldnode; + char *cp; + hook_p hook = NULL; + + /* Initialize */ + if (destp == NULL) { + TRAP_ERROR(); + return EINVAL; + } + *destp = NULL; + + /* Make a writable copy of address for ng_path_parse() */ + strncpy(fullpath, address, sizeof(fullpath) - 1); + fullpath[sizeof(fullpath) - 1] = '\0'; + + /* Parse out node and sequence of hooks */ + if (ng_path_parse(fullpath, &nodename, &path, NULL) < 0) { + TRAP_ERROR(); + return EINVAL; + } + if (path == NULL) { + pbuf[0] = '.'; /* Needs to be writable */ + pbuf[1] = '\0'; + path = pbuf; + } + + /* + * For an absolute address, jump to the starting node. + * Note that this holds a reference on the node for us. + * Don't forget to drop the reference if we don't need it. + */ + if (nodename) { + node = ng_name2noderef(here, nodename); + if (node == NULL) { + TRAP_ERROR(); + return (ENOENT); + } + } else { + if (here == NULL) { + TRAP_ERROR(); + return (EINVAL); + } + node = here; + NG_NODE_REF(node); + } + + /* + * Now follow the sequence of hooks + * XXX + * We actually cannot guarantee that the sequence + * is not being demolished as we crawl along it + * without extra-ordinary locking etc. + * So this is a bit dodgy to say the least. + * We can probably hold up some things by holding + * the nodelist mutex for the time of this + * crawl if we wanted.. At least that way we wouldn't have to + * worry about the nodes disappearing, but the hooks would still + * be a problem. + */ + for (cp = path; node != NULL && *cp != '\0'; ) { + char *segment; + + /* + * Break out the next path segment. Replace the dot we just + * found with a NUL; "cp" points to the next segment (or the + * NUL at the end). + */ + for (segment = cp; *cp != '\0'; cp++) { + if (*cp == '.') { + *cp++ = '\0'; + break; + } + } + + /* Empty segment */ + if (*segment == '\0') + continue; + + /* We have a segment, so look for a hook by that name */ + hook = ng_findhook(node, segment); + + /* Can't get there from here... */ + if (hook == NULL + || NG_HOOK_PEER(hook) == NULL + || NG_HOOK_NOT_VALID(hook) + || NG_HOOK_NOT_VALID(NG_HOOK_PEER(hook))) { + TRAP_ERROR(); + NG_NODE_UNREF(node); +#if 0 + printf("hooknotvalid %s %s %d %d %d %d ", + path, + segment, + hook == NULL, + NG_HOOK_PEER(hook) == NULL, + NG_HOOK_NOT_VALID(hook), + NG_HOOK_NOT_VALID(NG_HOOK_PEER(hook))); +#endif + return (ENOENT); + } + + /* + * Hop on over to the next node + * XXX + * Big race conditions here as hooks and nodes go away + * *** Idea.. store an ng_ID_t in each hook and use that + * instead of the direct hook in this crawl? + */ + oldnode = node; + if ((node = NG_PEER_NODE(hook))) + NG_NODE_REF(node); /* XXX RACE */ + NG_NODE_UNREF(oldnode); /* XXX another race */ + if (NG_NODE_NOT_VALID(node)) { + NG_NODE_UNREF(node); /* XXX more races */ + node = NULL; + } + } + + /* If node somehow missing, fail here (probably this is not needed) */ + if (node == NULL) { + TRAP_ERROR(); + return (ENXIO); + } + + /* Done */ + *destp = node; + if (lasthook != NULL) + *lasthook = (hook ? NG_HOOK_PEER(hook) : NULL); + return (0); +} + +/***************************************************************\ +* Input queue handling. +* All activities are submitted to the node via the input queue +* which implements a multiple-reader/single-writer gate. +* Items which cannot be handled immediately are queued. +* +* read-write queue locking inline functions * +\***************************************************************/ + +static __inline void ng_queue_rw(node_p node, item_p item, int rw); +static __inline item_p ng_dequeue(node_p node, int *rw); +static __inline item_p ng_acquire_read(node_p node, item_p item); +static __inline item_p ng_acquire_write(node_p node, item_p item); +static __inline void ng_leave_read(node_p node); +static __inline void ng_leave_write(node_p node); + +/* + * Definition of the bits fields in the ng_queue flag word. + * Defined here rather than in netgraph.h because no-one should fiddle + * with them. + * + * The ordering here may be important! don't shuffle these. + */ +/*- + Safety Barrier--------+ (adjustable to suit taste) (not used yet) + | + V ++-------+-------+-------+-------+-------+-------+-------+-------+ + | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | + | |A|c|t|i|v|e| |R|e|a|d|e|r| |C|o|u|n|t| | | | | | | | | |P|A| + | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |O|W| ++-------+-------+-------+-------+-------+-------+-------+-------+ + \___________________________ ____________________________/ | | + V | | + [active reader count] | | + | | + Operation Pending -------------------------------+ | + | + Active Writer ---------------------------------------+ + +Node queue has such semantics: +- All flags modifications are atomic. +- Reader count can be incremented only if there is no writer or pending flags. + As soon as this can't be done with single operation, it is implemented with + spin loop and atomic_cmpset(). +- Writer flag can be set only if there is no any bits set. + It is implemented with atomic_cmpset(). +- Pending flag can be set any time, but to avoid collision on queue processing + all queue fields are protected by the mutex. +- Queue processing thread reads queue holding the mutex, but releases it while + processing. When queue is empty pending flag is removed. +*/ + +#define WRITER_ACTIVE 0x00000001 +#define OP_PENDING 0x00000002 +#define READER_INCREMENT 0x00000004 +#define READER_MASK 0xfffffffc /* Not valid if WRITER_ACTIVE is set */ +#define SAFETY_BARRIER 0x00100000 /* 128K items queued should be enough */ + +/* Defines of more elaborate states on the queue */ +/* Mask of bits a new read cares about */ +#define NGQ_RMASK (WRITER_ACTIVE|OP_PENDING) + +/* Mask of bits a new write cares about */ +#define NGQ_WMASK (NGQ_RMASK|READER_MASK) + +/* Test to decide if there is something on the queue. */ +#define QUEUE_ACTIVE(QP) ((QP)->q_flags & OP_PENDING) + +/* How to decide what the next queued item is. */ +#define HEAD_IS_READER(QP) NGI_QUEUED_READER(STAILQ_FIRST(&(QP)->queue)) +#define HEAD_IS_WRITER(QP) NGI_QUEUED_WRITER(STAILQ_FIRST(&(QP)->queue)) /* notused */ + +/* Read the status to decide if the next item on the queue can now run. */ +#define QUEUED_READER_CAN_PROCEED(QP) \ + (((QP)->q_flags & (NGQ_RMASK & ~OP_PENDING)) == 0) +#define QUEUED_WRITER_CAN_PROCEED(QP) \ + (((QP)->q_flags & (NGQ_WMASK & ~OP_PENDING)) == 0) + +/* Is there a chance of getting ANY work off the queue? */ +#define NEXT_QUEUED_ITEM_CAN_PROCEED(QP) \ + ((HEAD_IS_READER(QP)) ? QUEUED_READER_CAN_PROCEED(QP) : \ + QUEUED_WRITER_CAN_PROCEED(QP)) + +#define NGQRW_R 0 +#define NGQRW_W 1 + +#define NGQ2_WORKQ 0x00000001 + +/* + * Taking into account the current state of the queue and node, possibly take + * the next entry off the queue and return it. Return NULL if there was + * nothing we could return, either because there really was nothing there, or + * because the node was in a state where it cannot yet process the next item + * on the queue. + */ +static __inline item_p +ng_dequeue(node_p node, int *rw) +{ + item_p item; + struct ng_queue *ngq = &node->nd_input_queue; + + /* This MUST be called with the mutex held. */ + mtx_assert(&ngq->q_mtx, MA_OWNED); + + /* If there is nothing queued, then just return. */ + if (!QUEUE_ACTIVE(ngq)) { + CTR4(KTR_NET, "%20s: node [%x] (%p) queue empty; " + "queue flags 0x%lx", __func__, + node->nd_ID, node, ngq->q_flags); + return (NULL); + } + + /* + * From here, we can assume there is a head item. + * We need to find out what it is and if it can be dequeued, given + * the current state of the node. + */ + if (HEAD_IS_READER(ngq)) { + while (1) { + long t = ngq->q_flags; + if (t & WRITER_ACTIVE) { + /* There is writer, reader can't proceed. */ + CTR4(KTR_NET, "%20s: node [%x] (%p) queued reader " + "can't proceed; queue flags 0x%lx", __func__, + node->nd_ID, node, t); + return (NULL); + } + if (atomic_cmpset_acq_int(&ngq->q_flags, t, + t + READER_INCREMENT)) + break; + cpu_spinwait(); + } + /* We have got reader lock for the node. */ + *rw = NGQRW_R; + } else if (atomic_cmpset_acq_int(&ngq->q_flags, OP_PENDING, + OP_PENDING + WRITER_ACTIVE)) { + /* We have got writer lock for the node. */ + *rw = NGQRW_W; + } else { + /* There is somebody other, writer can't proceed. */ + CTR4(KTR_NET, "%20s: node [%x] (%p) queued writer " + "can't proceed; queue flags 0x%lx", __func__, + node->nd_ID, node, ngq->q_flags); + return (NULL); + } + + /* + * Now we dequeue the request (whatever it may be) and correct the + * pending flags and the next and last pointers. + */ + item = STAILQ_FIRST(&ngq->queue); + STAILQ_REMOVE_HEAD(&ngq->queue, el_next); + if (STAILQ_EMPTY(&ngq->queue)) + atomic_clear_int(&ngq->q_flags, OP_PENDING); + CTR6(KTR_NET, "%20s: node [%x] (%p) returning item %p as %s; " + "queue flags 0x%lx", __func__, + node->nd_ID, node, item, *rw ? "WRITER" : "READER" , + ngq->q_flags); + return (item); +} + +/* + * Queue a packet to be picked up later by someone else. + * If the queue could be run now, add node to the queue handler's worklist. + */ +static __inline void +ng_queue_rw(node_p node, item_p item, int rw) +{ + struct ng_queue *ngq = &node->nd_input_queue; + if (rw == NGQRW_W) + NGI_SET_WRITER(item); + else + NGI_SET_READER(item); + + NG_QUEUE_LOCK(ngq); + /* Set OP_PENDING flag and enqueue the item. */ + atomic_set_int(&ngq->q_flags, OP_PENDING); + STAILQ_INSERT_TAIL(&ngq->queue, item, el_next); + + CTR5(KTR_NET, "%20s: node [%x] (%p) queued item %p as %s", __func__, + node->nd_ID, node, item, rw ? "WRITER" : "READER" ); + + /* + * We can take the worklist lock with the node locked + * BUT NOT THE REVERSE! + */ + if (NEXT_QUEUED_ITEM_CAN_PROCEED(ngq)) + ng_worklist_add(node); + NG_QUEUE_UNLOCK(ngq); +} + +/* Acquire reader lock on node. If node is busy, queue the packet. */ +static __inline item_p +ng_acquire_read(node_p node, item_p item) +{ + KASSERT(node != &ng_deadnode, + ("%s: working on deadnode", __func__)); + + /* Reader needs node without writer and pending items. */ + while (1) { + long t = node->nd_input_queue.q_flags; + if (t & NGQ_RMASK) + break; /* Node is not ready for reader. */ + if (atomic_cmpset_acq_int(&node->nd_input_queue.q_flags, + t, t + READER_INCREMENT)) { + /* Successfully grabbed node */ + CTR4(KTR_NET, "%20s: node [%x] (%p) acquired item %p", + __func__, node->nd_ID, node, item); + return (item); + } + cpu_spinwait(); + }; + + /* Queue the request for later. */ + ng_queue_rw(node, item, NGQRW_R); + + return (NULL); +} + +/* Acquire writer lock on node. If node is busy, queue the packet. */ +static __inline item_p +ng_acquire_write(node_p node, item_p item) +{ + KASSERT(node != &ng_deadnode, + ("%s: working on deadnode", __func__)); + + /* Writer needs completely idle node. */ + if (atomic_cmpset_acq_int(&node->nd_input_queue.q_flags, + 0, WRITER_ACTIVE)) { + /* Successfully grabbed node */ + CTR4(KTR_NET, "%20s: node [%x] (%p) acquired item %p", + __func__, node->nd_ID, node, item); + return (item); + } + + /* Queue the request for later. */ + ng_queue_rw(node, item, NGQRW_W); + + return (NULL); +} + +#if 0 +static __inline item_p +ng_upgrade_write(node_p node, item_p item) +{ + struct ng_queue *ngq = &node->nd_input_queue; + KASSERT(node != &ng_deadnode, + ("%s: working on deadnode", __func__)); + + NGI_SET_WRITER(item); + + NG_QUEUE_LOCK(ngq); + + /* + * There will never be no readers as we are there ourselves. + * Set the WRITER_ACTIVE flags ASAP to block out fast track readers. + * The caller we are running from will call ng_leave_read() + * soon, so we must account for that. We must leave again with the + * READER lock. If we find other readers, then + * queue the request for later. However "later" may be rignt now + * if there are no readers. We don't really care if there are queued + * items as we will bypass them anyhow. + */ + atomic_add_int(&ngq->q_flags, WRITER_ACTIVE - READER_INCREMENT); + if ((ngq->q_flags & (NGQ_WMASK & ~OP_PENDING)) == WRITER_ACTIVE) { + NG_QUEUE_UNLOCK(ngq); + + /* It's just us, act on the item. */ + /* will NOT drop writer lock when done */ + ng_apply_item(node, item, 0); + + /* + * Having acted on the item, atomically + * down grade back to READER and finish up + */ + atomic_add_int(&ngq->q_flags, + READER_INCREMENT - WRITER_ACTIVE); + + /* Our caller will call ng_leave_read() */ + return; + } + /* + * It's not just us active, so queue us AT THE HEAD. + * "Why?" I hear you ask. + * Put us at the head of the queue as we've already been + * through it once. If there is nothing else waiting, + * set the correct flags. + */ + if (STAILQ_EMPTY(&ngq->queue)) { + /* We've gone from, 0 to 1 item in the queue */ + atomic_set_int(&ngq->q_flags, OP_PENDING); + + CTR3(KTR_NET, "%20s: node [%x] (%p) set OP_PENDING", __func__, + node->nd_ID, node); + }; + STAILQ_INSERT_HEAD(&ngq->queue, item, el_next); + CTR4(KTR_NET, "%20s: node [%x] (%p) requeued item %p as WRITER", + __func__, node->nd_ID, node, item ); + + /* Reverse what we did above. That downgrades us back to reader */ + atomic_add_int(&ngq->q_flags, READER_INCREMENT - WRITER_ACTIVE); + if (QUEUE_ACTIVE(ngq) && NEXT_QUEUED_ITEM_CAN_PROCEED(ngq)) + ng_worklist_add(node); + NG_QUEUE_UNLOCK(ngq); + + return; +} +#endif + +/* Release reader lock. */ +static __inline void +ng_leave_read(node_p node) +{ + atomic_subtract_rel_int(&node->nd_input_queue.q_flags, READER_INCREMENT); +} + +/* Release writer lock. */ +static __inline void +ng_leave_write(node_p node) +{ + atomic_clear_rel_int(&node->nd_input_queue.q_flags, WRITER_ACTIVE); +} + +/* Purge node queue. Called on node shutdown. */ +static void +ng_flush_input_queue(node_p node) +{ + struct ng_queue *ngq = &node->nd_input_queue; + item_p item; + + NG_QUEUE_LOCK(ngq); + while ((item = STAILQ_FIRST(&ngq->queue)) != NULL) { + STAILQ_REMOVE_HEAD(&ngq->queue, el_next); + if (STAILQ_EMPTY(&ngq->queue)) + atomic_clear_int(&ngq->q_flags, OP_PENDING); + NG_QUEUE_UNLOCK(ngq); + + /* If the item is supplying a callback, call it with an error */ + if (item->apply != NULL) { + if (item->depth == 1) + item->apply->error = ENOENT; + if (refcount_release(&item->apply->refs)) { + (*item->apply->apply)(item->apply->context, + item->apply->error); + } + } + NG_FREE_ITEM(item); + NG_QUEUE_LOCK(ngq); + } + NG_QUEUE_UNLOCK(ngq); +} + +/*********************************************************************** +* Externally visible method for sending or queueing messages or data. +***********************************************************************/ + +/* + * The module code should have filled out the item correctly by this stage: + * Common: + * reference to destination node. + * Reference to destination rcv hook if relevant. + * apply pointer must be or NULL or reference valid struct ng_apply_info. + * Data: + * pointer to mbuf + * Control_Message: + * pointer to msg. + * ID of original sender node. (return address) + * Function: + * Function pointer + * void * argument + * integer argument + * + * The nodes have several routines and macros to help with this task: + */ + +int +ng_snd_item(item_p item, int flags) +{ + hook_p hook; + node_p node; + int queue, rw; + struct ng_queue *ngq; + int error = 0; + + /* We are sending item, so it must be present! */ + KASSERT(item != NULL, ("ng_snd_item: item is NULL")); + +#ifdef NETGRAPH_DEBUG + _ngi_check(item, __FILE__, __LINE__); +#endif + + /* Item was sent once more, postpone apply() call. */ + if (item->apply) + refcount_acquire(&item->apply->refs); + + node = NGI_NODE(item); + /* Node is never optional. */ + KASSERT(node != NULL, ("ng_snd_item: node is NULL")); + + hook = NGI_HOOK(item); + /* Valid hook and mbuf are mandatory for data. */ + if ((item->el_flags & NGQF_TYPE) == NGQF_DATA) { + KASSERT(hook != NULL, ("ng_snd_item: hook for data is NULL")); + if (NGI_M(item) == NULL) + ERROUT(EINVAL); + CHECK_DATA_MBUF(NGI_M(item)); + } + + /* + * If the item or the node specifies single threading, force + * writer semantics. Similarly, the node may say one hook always + * produces writers. These are overrides. + */ + if (((item->el_flags & NGQF_RW) == NGQF_WRITER) || + (node->nd_flags & NGF_FORCE_WRITER) || + (hook && (hook->hk_flags & HK_FORCE_WRITER))) { + rw = NGQRW_W; + } else { + rw = NGQRW_R; + } + + /* + * If sender or receiver requests queued delivery or stack usage + * level is dangerous - enqueue message. + */ + if ((flags & NG_QUEUE) || (hook && (hook->hk_flags & HK_QUEUE))) { + queue = 1; + } else { + queue = 0; +#ifdef GET_STACK_USAGE + /* + * Most of netgraph nodes have small stack consumption and + * for them 25% of free stack space is more than enough. + * Nodes/hooks with higher stack usage should be marked as + * HI_STACK. For them 50% of stack will be guaranteed then. + * XXX: Values 25% and 50% are completely empirical. + */ + size_t st, su, sl; + GET_STACK_USAGE(st, su); + sl = st - su; + if ((sl * 4 < st) || + ((sl * 2 < st) && ((node->nd_flags & NGF_HI_STACK) || + (hook && (hook->hk_flags & HK_HI_STACK))))) { + queue = 1; + } +#endif + } + + if (queue) { + item->depth = 1; + /* Put it on the queue for that node*/ + ng_queue_rw(node, item, rw); + return ((flags & NG_PROGRESS) ? EINPROGRESS : 0); + } + + /* + * We already decided how we will be queueud or treated. + * Try get the appropriate operating permission. + */ + if (rw == NGQRW_R) + item = ng_acquire_read(node, item); + else + item = ng_acquire_write(node, item); + + /* Item was queued while trying to get permission. */ + if (item == NULL) + return ((flags & NG_PROGRESS) ? EINPROGRESS : 0); + + NGI_GET_NODE(item, node); /* zaps stored node */ + + item->depth++; + error = ng_apply_item(node, item, rw); /* drops r/w lock when done */ + + /* If something is waiting on queue and ready, schedule it. */ + ngq = &node->nd_input_queue; + if (QUEUE_ACTIVE(ngq)) { + NG_QUEUE_LOCK(ngq); + if (QUEUE_ACTIVE(ngq) && NEXT_QUEUED_ITEM_CAN_PROCEED(ngq)) + ng_worklist_add(node); + NG_QUEUE_UNLOCK(ngq); + } + + /* + * Node may go away as soon as we remove the reference. + * Whatever we do, DO NOT access the node again! + */ + NG_NODE_UNREF(node); + + return (error); + +done: + /* If was not sent, apply callback here. */ + if (item->apply != NULL) { + if (item->depth == 0 && error != 0) + item->apply->error = error; + if (refcount_release(&item->apply->refs)) { + (*item->apply->apply)(item->apply->context, + item->apply->error); + } + } + + NG_FREE_ITEM(item); + return (error); +} + +/* + * We have an item that was possibly queued somewhere. + * It should contain all the information needed + * to run it on the appropriate node/hook. + * If there is apply pointer and we own the last reference, call apply(). + */ +static int +ng_apply_item(node_p node, item_p item, int rw) +{ + hook_p hook; + ng_rcvdata_t *rcvdata; + ng_rcvmsg_t *rcvmsg; + struct ng_apply_info *apply; + int error = 0, depth; + + /* Node and item are never optional. */ + KASSERT(node != NULL, ("ng_apply_item: node is NULL")); + KASSERT(item != NULL, ("ng_apply_item: item is NULL")); + + NGI_GET_HOOK(item, hook); /* clears stored hook */ +#ifdef NETGRAPH_DEBUG + _ngi_check(item, __FILE__, __LINE__); +#endif + + apply = item->apply; + depth = item->depth; + + switch (item->el_flags & NGQF_TYPE) { + case NGQF_DATA: + /* + * Check things are still ok as when we were queued. + */ + KASSERT(hook != NULL, ("ng_apply_item: hook for data is NULL")); + if (NG_HOOK_NOT_VALID(hook) || + NG_NODE_NOT_VALID(node)) { + error = EIO; + NG_FREE_ITEM(item); + break; + } + /* + * If no receive method, just silently drop it. + * Give preference to the hook over-ride method + */ + if ((!(rcvdata = hook->hk_rcvdata)) + && (!(rcvdata = NG_HOOK_NODE(hook)->nd_type->rcvdata))) { + error = 0; + NG_FREE_ITEM(item); + break; + } + error = (*rcvdata)(hook, item); + break; + case NGQF_MESG: + if (hook && NG_HOOK_NOT_VALID(hook)) { + /* + * The hook has been zapped then we can't use it. + * Immediately drop its reference. + * The message may not need it. + */ + NG_HOOK_UNREF(hook); + hook = NULL; + } + /* + * Similarly, if the node is a zombie there is + * nothing we can do with it, drop everything. + */ + if (NG_NODE_NOT_VALID(node)) { + TRAP_ERROR(); + error = EINVAL; + NG_FREE_ITEM(item); + break; + } + /* + * Call the appropriate message handler for the object. + * It is up to the message handler to free the message. + * If it's a generic message, handle it generically, + * otherwise call the type's message handler (if it exists). + * XXX (race). Remember that a queued message may + * reference a node or hook that has just been + * invalidated. It will exist as the queue code + * is holding a reference, but.. + */ + if ((NGI_MSG(item)->header.typecookie == NGM_GENERIC_COOKIE) && + ((NGI_MSG(item)->header.flags & NGF_RESP) == 0)) { + error = ng_generic_msg(node, item, hook); + break; + } + if (((!hook) || (!(rcvmsg = hook->hk_rcvmsg))) && + (!(rcvmsg = node->nd_type->rcvmsg))) { + TRAP_ERROR(); + error = 0; + NG_FREE_ITEM(item); + break; + } + error = (*rcvmsg)(node, item, hook); + break; + case NGQF_FN: + case NGQF_FN2: + /* + * We have to implicitly trust the hook, + * as some of these are used for system purposes + * where the hook is invalid. In the case of + * the shutdown message we allow it to hit + * even if the node is invalid. + */ + if ((NG_NODE_NOT_VALID(node)) + && (NGI_FN(item) != &ng_rmnode)) { + TRAP_ERROR(); + error = EINVAL; + NG_FREE_ITEM(item); + break; + } + if ((item->el_flags & NGQF_TYPE) == NGQF_FN) { + (*NGI_FN(item))(node, hook, NGI_ARG1(item), + NGI_ARG2(item)); + NG_FREE_ITEM(item); + } else /* it is NGQF_FN2 */ + error = (*NGI_FN2(item))(node, item, hook); + break; + } + /* + * We held references on some of the resources + * that we took from the item. Now that we have + * finished doing everything, drop those references. + */ + if (hook) + NG_HOOK_UNREF(hook); + + if (rw == NGQRW_R) + ng_leave_read(node); + else + ng_leave_write(node); + + /* Apply callback. */ + if (apply != NULL) { + if (depth == 1 && error != 0) + apply->error = error; + if (refcount_release(&apply->refs)) + (*apply->apply)(apply->context, apply->error); + } + + return (error); +} + +/*********************************************************************** + * Implement the 'generic' control messages + ***********************************************************************/ +static int +ng_generic_msg(node_p here, item_p item, hook_p lasthook) +{ + int error = 0; + struct ng_mesg *msg; + struct ng_mesg *resp = NULL; + + NGI_GET_MSG(item, msg); + if (msg->header.typecookie != NGM_GENERIC_COOKIE) { + TRAP_ERROR(); + error = EINVAL; + goto out; + } + switch (msg->header.cmd) { + case NGM_SHUTDOWN: + ng_rmnode(here, NULL, NULL, 0); + break; + case NGM_MKPEER: + { + struct ngm_mkpeer *const mkp = (struct ngm_mkpeer *) msg->data; + + if (msg->header.arglen != sizeof(*mkp)) { + TRAP_ERROR(); + error = EINVAL; + break; + } + mkp->type[sizeof(mkp->type) - 1] = '\0'; + mkp->ourhook[sizeof(mkp->ourhook) - 1] = '\0'; + mkp->peerhook[sizeof(mkp->peerhook) - 1] = '\0'; + error = ng_mkpeer(here, mkp->ourhook, mkp->peerhook, mkp->type); + break; + } + case NGM_CONNECT: + { + struct ngm_connect *const con = + (struct ngm_connect *) msg->data; + node_p node2; + + if (msg->header.arglen != sizeof(*con)) { + TRAP_ERROR(); + error = EINVAL; + break; + } + con->path[sizeof(con->path) - 1] = '\0'; + con->ourhook[sizeof(con->ourhook) - 1] = '\0'; + con->peerhook[sizeof(con->peerhook) - 1] = '\0'; + /* Don't forget we get a reference.. */ + error = ng_path2noderef(here, con->path, &node2, NULL); + if (error) + break; + error = ng_con_nodes(item, here, con->ourhook, + node2, con->peerhook); + NG_NODE_UNREF(node2); + break; + } + case NGM_NAME: + { + struct ngm_name *const nam = (struct ngm_name *) msg->data; + + if (msg->header.arglen != sizeof(*nam)) { + TRAP_ERROR(); + error = EINVAL; + break; + } + nam->name[sizeof(nam->name) - 1] = '\0'; + error = ng_name_node(here, nam->name); + break; + } + case NGM_RMHOOK: + { + struct ngm_rmhook *const rmh = (struct ngm_rmhook *) msg->data; + hook_p hook; + + if (msg->header.arglen != sizeof(*rmh)) { + TRAP_ERROR(); + error = EINVAL; + break; + } + rmh->ourhook[sizeof(rmh->ourhook) - 1] = '\0'; + if ((hook = ng_findhook(here, rmh->ourhook)) != NULL) + ng_destroy_hook(hook); + break; + } + case NGM_NODEINFO: + { + struct nodeinfo *ni; + + NG_MKRESPONSE(resp, msg, sizeof(*ni), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + + /* Fill in node info */ + ni = (struct nodeinfo *) resp->data; + if (NG_NODE_HAS_NAME(here)) + strcpy(ni->name, NG_NODE_NAME(here)); + strcpy(ni->type, here->nd_type->name); + ni->id = ng_node2ID(here); + ni->hooks = here->nd_numhooks; + break; + } + case NGM_LISTHOOKS: + { + const int nhooks = here->nd_numhooks; + struct hooklist *hl; + struct nodeinfo *ni; + hook_p hook; + + /* Get response struct */ + NG_MKRESPONSE(resp, msg, sizeof(*hl) + + (nhooks * sizeof(struct linkinfo)), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + hl = (struct hooklist *) resp->data; + ni = &hl->nodeinfo; + + /* Fill in node info */ + if (NG_NODE_HAS_NAME(here)) + strcpy(ni->name, NG_NODE_NAME(here)); + strcpy(ni->type, here->nd_type->name); + ni->id = ng_node2ID(here); + + /* Cycle through the linked list of hooks */ + ni->hooks = 0; + LIST_FOREACH(hook, &here->nd_hooks, hk_hooks) { + struct linkinfo *const link = &hl->link[ni->hooks]; + + if (ni->hooks >= nhooks) { + log(LOG_ERR, "%s: number of %s changed\n", + __func__, "hooks"); + break; + } + if (NG_HOOK_NOT_VALID(hook)) + continue; + strcpy(link->ourhook, NG_HOOK_NAME(hook)); + strcpy(link->peerhook, NG_PEER_HOOK_NAME(hook)); + if (NG_PEER_NODE_NAME(hook)[0] != '\0') + strcpy(link->nodeinfo.name, + NG_PEER_NODE_NAME(hook)); + strcpy(link->nodeinfo.type, + NG_PEER_NODE(hook)->nd_type->name); + link->nodeinfo.id = ng_node2ID(NG_PEER_NODE(hook)); + link->nodeinfo.hooks = NG_PEER_NODE(hook)->nd_numhooks; + ni->hooks++; + } + break; + } + + case NGM_LISTNAMES: + case NGM_LISTNODES: + { + const int unnamed = (msg->header.cmd == NGM_LISTNODES); + struct namelist *nl; + node_p node; + int num = 0, i; + + mtx_lock(&ng_namehash_mtx); + /* Count number of nodes */ + for (i = 0; i < NG_NAME_HASH_SIZE; i++) { + LIST_FOREACH(node, &ng_name_hash[i], nd_nodes) { + if (NG_NODE_IS_VALID(node) && + (unnamed || NG_NODE_HAS_NAME(node))) { + num++; + } + } + } + mtx_unlock(&ng_namehash_mtx); + + /* Get response struct */ + NG_MKRESPONSE(resp, msg, sizeof(*nl) + + (num * sizeof(struct nodeinfo)), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + nl = (struct namelist *) resp->data; + + /* Cycle through the linked list of nodes */ + nl->numnames = 0; + mtx_lock(&ng_namehash_mtx); + for (i = 0; i < NG_NAME_HASH_SIZE; i++) { + LIST_FOREACH(node, &ng_name_hash[i], nd_nodes) { + struct nodeinfo *const np = + &nl->nodeinfo[nl->numnames]; + + if (NG_NODE_NOT_VALID(node)) + continue; + if (!unnamed && (! NG_NODE_HAS_NAME(node))) + continue; + if (nl->numnames >= num) { + log(LOG_ERR, "%s: number of nodes changed\n", + __func__); + break; + } + if (NG_NODE_HAS_NAME(node)) + strcpy(np->name, NG_NODE_NAME(node)); + strcpy(np->type, node->nd_type->name); + np->id = ng_node2ID(node); + np->hooks = node->nd_numhooks; + nl->numnames++; + } + } + mtx_unlock(&ng_namehash_mtx); + break; + } + + case NGM_LISTTYPES: + { + struct typelist *tl; + struct ng_type *type; + int num = 0; + + mtx_lock(&ng_typelist_mtx); + /* Count number of types */ + LIST_FOREACH(type, &ng_typelist, types) { + num++; + } + mtx_unlock(&ng_typelist_mtx); + + /* Get response struct */ + NG_MKRESPONSE(resp, msg, sizeof(*tl) + + (num * sizeof(struct typeinfo)), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + tl = (struct typelist *) resp->data; + + /* Cycle through the linked list of types */ + tl->numtypes = 0; + mtx_lock(&ng_typelist_mtx); + LIST_FOREACH(type, &ng_typelist, types) { + struct typeinfo *const tp = &tl->typeinfo[tl->numtypes]; + + if (tl->numtypes >= num) { + log(LOG_ERR, "%s: number of %s changed\n", + __func__, "types"); + break; + } + strcpy(tp->type_name, type->name); + tp->numnodes = type->refs - 1; /* don't count list */ + tl->numtypes++; + } + mtx_unlock(&ng_typelist_mtx); + break; + } + + case NGM_BINARY2ASCII: + { + int bufSize = 20 * 1024; /* XXX hard coded constant */ + const struct ng_parse_type *argstype; + const struct ng_cmdlist *c; + struct ng_mesg *binary, *ascii; + + /* Data area must contain a valid netgraph message */ + binary = (struct ng_mesg *)msg->data; + if (msg->header.arglen < sizeof(struct ng_mesg) || + (msg->header.arglen - sizeof(struct ng_mesg) < + binary->header.arglen)) { + TRAP_ERROR(); + error = EINVAL; + break; + } + + /* Get a response message with lots of room */ + NG_MKRESPONSE(resp, msg, sizeof(*ascii) + bufSize, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + ascii = (struct ng_mesg *)resp->data; + + /* Copy binary message header to response message payload */ + bcopy(binary, ascii, sizeof(*binary)); + + /* Find command by matching typecookie and command number */ + for (c = here->nd_type->cmdlist; + c != NULL && c->name != NULL; c++) { + if (binary->header.typecookie == c->cookie + && binary->header.cmd == c->cmd) + break; + } + if (c == NULL || c->name == NULL) { + for (c = ng_generic_cmds; c->name != NULL; c++) { + if (binary->header.typecookie == c->cookie + && binary->header.cmd == c->cmd) + break; + } + if (c->name == NULL) { + NG_FREE_MSG(resp); + error = ENOSYS; + break; + } + } + + /* Convert command name to ASCII */ + snprintf(ascii->header.cmdstr, sizeof(ascii->header.cmdstr), + "%s", c->name); + + /* Convert command arguments to ASCII */ + argstype = (binary->header.flags & NGF_RESP) ? + c->respType : c->mesgType; + if (argstype == NULL) { + *ascii->data = '\0'; + } else { + if ((error = ng_unparse(argstype, + (u_char *)binary->data, + ascii->data, bufSize)) != 0) { + NG_FREE_MSG(resp); + break; + } + } + + /* Return the result as struct ng_mesg plus ASCII string */ + bufSize = strlen(ascii->data) + 1; + ascii->header.arglen = bufSize; + resp->header.arglen = sizeof(*ascii) + bufSize; + break; + } + + case NGM_ASCII2BINARY: + { + int bufSize = 2000; /* XXX hard coded constant */ + const struct ng_cmdlist *c; + const struct ng_parse_type *argstype; + struct ng_mesg *ascii, *binary; + int off = 0; + + /* Data area must contain at least a struct ng_mesg + '\0' */ + ascii = (struct ng_mesg *)msg->data; + if ((msg->header.arglen < sizeof(*ascii) + 1) || + (ascii->header.arglen < 1) || + (msg->header.arglen < sizeof(*ascii) + + ascii->header.arglen)) { + TRAP_ERROR(); + error = EINVAL; + break; + } + ascii->data[ascii->header.arglen - 1] = '\0'; + + /* Get a response message with lots of room */ + NG_MKRESPONSE(resp, msg, sizeof(*binary) + bufSize, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + binary = (struct ng_mesg *)resp->data; + + /* Copy ASCII message header to response message payload */ + bcopy(ascii, binary, sizeof(*ascii)); + + /* Find command by matching ASCII command string */ + for (c = here->nd_type->cmdlist; + c != NULL && c->name != NULL; c++) { + if (strcmp(ascii->header.cmdstr, c->name) == 0) + break; + } + if (c == NULL || c->name == NULL) { + for (c = ng_generic_cmds; c->name != NULL; c++) { + if (strcmp(ascii->header.cmdstr, c->name) == 0) + break; + } + if (c->name == NULL) { + NG_FREE_MSG(resp); + error = ENOSYS; + break; + } + } + + /* Convert command name to binary */ + binary->header.cmd = c->cmd; + binary->header.typecookie = c->cookie; + + /* Convert command arguments to binary */ + argstype = (binary->header.flags & NGF_RESP) ? + c->respType : c->mesgType; + if (argstype == NULL) { + bufSize = 0; + } else { + if ((error = ng_parse(argstype, ascii->data, + &off, (u_char *)binary->data, &bufSize)) != 0) { + NG_FREE_MSG(resp); + break; + } + } + + /* Return the result */ + binary->header.arglen = bufSize; + resp->header.arglen = sizeof(*binary) + bufSize; + break; + } + + case NGM_TEXT_CONFIG: + case NGM_TEXT_STATUS: + /* + * This one is tricky as it passes the command down to the + * actual node, even though it is a generic type command. + * This means we must assume that the item/msg is already freed + * when control passes back to us. + */ + if (here->nd_type->rcvmsg != NULL) { + NGI_MSG(item) = msg; /* put it back as we found it */ + return((*here->nd_type->rcvmsg)(here, item, lasthook)); + } + /* Fall through if rcvmsg not supported */ + default: + TRAP_ERROR(); + error = EINVAL; + } + /* + * Sometimes a generic message may be statically allocated + * to avoid problems with allocating when in tight memeory situations. + * Don't free it if it is so. + * I break them appart here, because erros may cause a free if the item + * in which case we'd be doing it twice. + * they are kept together above, to simplify freeing. + */ +out: + NG_RESPOND_MSG(error, here, item, resp); + if (msg) + NG_FREE_MSG(msg); + return (error); +} + +/************************************************************************ + Queue element get/free routines +************************************************************************/ + +uma_zone_t ng_qzone; +uma_zone_t ng_qdzone; +static int maxalloc = 4096;/* limit the damage of a leak */ +static int maxdata = 512; /* limit the damage of a DoS */ + +TUNABLE_INT("net.graph.maxalloc", &maxalloc); +SYSCTL_INT(_net_graph, OID_AUTO, maxalloc, CTLFLAG_RDTUN, &maxalloc, + 0, "Maximum number of non-data queue items to allocate"); +TUNABLE_INT("net.graph.maxdata", &maxdata); +SYSCTL_INT(_net_graph, OID_AUTO, maxdata, CTLFLAG_RDTUN, &maxdata, + 0, "Maximum number of data queue items to allocate"); + +#ifdef NETGRAPH_DEBUG +static TAILQ_HEAD(, ng_item) ng_itemlist = TAILQ_HEAD_INITIALIZER(ng_itemlist); +static int allocated; /* number of items malloc'd */ +#endif + +/* + * Get a queue entry. + * This is usually called when a packet first enters netgraph. + * By definition, this is usually from an interrupt, or from a user. + * Users are not so important, but try be quick for the times that it's + * an interrupt. + */ +static __inline item_p +ng_alloc_item(int type, int flags) +{ + item_p item; + + KASSERT(((type & ~NGQF_TYPE) == 0), + ("%s: incorrect item type: %d", __func__, type)); + + item = uma_zalloc((type == NGQF_DATA)?ng_qdzone:ng_qzone, + ((flags & NG_WAITOK) ? M_WAITOK : M_NOWAIT) | M_ZERO); + + if (item) { + item->el_flags = type; +#ifdef NETGRAPH_DEBUG + mtx_lock(&ngq_mtx); + TAILQ_INSERT_TAIL(&ng_itemlist, item, all); + allocated++; + mtx_unlock(&ngq_mtx); +#endif + } + + return (item); +} + +/* + * Release a queue entry + */ +void +ng_free_item(item_p item) +{ + /* + * The item may hold resources on it's own. We need to free + * these before we can free the item. What they are depends upon + * what kind of item it is. it is important that nodes zero + * out pointers to resources that they remove from the item + * or we release them again here. + */ + switch (item->el_flags & NGQF_TYPE) { + case NGQF_DATA: + /* If we have an mbuf still attached.. */ + NG_FREE_M(_NGI_M(item)); + break; + case NGQF_MESG: + _NGI_RETADDR(item) = 0; + NG_FREE_MSG(_NGI_MSG(item)); + break; + case NGQF_FN: + case NGQF_FN2: + /* nothing to free really, */ + _NGI_FN(item) = NULL; + _NGI_ARG1(item) = NULL; + _NGI_ARG2(item) = 0; + break; + } + /* If we still have a node or hook referenced... */ + _NGI_CLR_NODE(item); + _NGI_CLR_HOOK(item); + +#ifdef NETGRAPH_DEBUG + mtx_lock(&ngq_mtx); + TAILQ_REMOVE(&ng_itemlist, item, all); + allocated--; + mtx_unlock(&ngq_mtx); +#endif + uma_zfree(((item->el_flags & NGQF_TYPE) == NGQF_DATA)? + ng_qdzone:ng_qzone, item); +} + +/* + * Change type of the queue entry. + * Possibly reallocates it from another UMA zone. + */ +static __inline item_p +ng_realloc_item(item_p pitem, int type, int flags) +{ + item_p item; + int from, to; + + KASSERT((pitem != NULL), ("%s: can't reallocate NULL", __func__)); + KASSERT(((type & ~NGQF_TYPE) == 0), + ("%s: incorrect item type: %d", __func__, type)); + + from = ((pitem->el_flags & NGQF_TYPE) == NGQF_DATA); + to = (type == NGQF_DATA); + if (from != to) { + /* If reallocation is required do it and copy item. */ + if ((item = ng_alloc_item(type, flags)) == NULL) { + ng_free_item(pitem); + return (NULL); + } + *item = *pitem; + ng_free_item(pitem); + } else + item = pitem; + item->el_flags = (item->el_flags & ~NGQF_TYPE) | type; + + return (item); +} + +/************************************************************************ + Module routines +************************************************************************/ + +/* + * Handle the loading/unloading of a netgraph node type module + */ +int +ng_mod_event(module_t mod, int event, void *data) +{ + struct ng_type *const type = data; + int s, error = 0; + + switch (event) { + case MOD_LOAD: + + /* Register new netgraph node type */ + s = splnet(); + if ((error = ng_newtype(type)) != 0) { + splx(s); + break; + } + + /* Call type specific code */ + if (type->mod_event != NULL) + if ((error = (*type->mod_event)(mod, event, data))) { + mtx_lock(&ng_typelist_mtx); + type->refs--; /* undo it */ + LIST_REMOVE(type, types); + mtx_unlock(&ng_typelist_mtx); + } + splx(s); + break; + + case MOD_UNLOAD: + s = splnet(); + if (type->refs > 1) { /* make sure no nodes exist! */ + error = EBUSY; + } else { + if (type->refs == 0) { + /* failed load, nothing to undo */ + splx(s); + break; + } + if (type->mod_event != NULL) { /* check with type */ + error = (*type->mod_event)(mod, event, data); + if (error != 0) { /* type refuses.. */ + splx(s); + break; + } + } + mtx_lock(&ng_typelist_mtx); + LIST_REMOVE(type, types); + mtx_unlock(&ng_typelist_mtx); + } + splx(s); + break; + + default: + if (type->mod_event != NULL) + error = (*type->mod_event)(mod, event, data); + else + error = EOPNOTSUPP; /* XXX ? */ + break; + } + return (error); +} + +/* + * Handle loading and unloading for this code. + * The only thing we need to link into is the NETISR strucure. + */ +static int +ngb_mod_event(module_t mod, int event, void *data) +{ + int error = 0; + + switch (event) { + case MOD_LOAD: + /* Initialize everything. */ + NG_WORKLIST_LOCK_INIT(); + mtx_init(&ng_typelist_mtx, "netgraph types mutex", NULL, + MTX_DEF); + mtx_init(&ng_idhash_mtx, "netgraph idhash mutex", NULL, + MTX_DEF); + mtx_init(&ng_namehash_mtx, "netgraph namehash mutex", NULL, + MTX_DEF); + mtx_init(&ng_topo_mtx, "netgraph topology mutex", NULL, + MTX_DEF); +#ifdef NETGRAPH_DEBUG + mtx_init(&ng_nodelist_mtx, "netgraph nodelist mutex", NULL, + MTX_DEF); + mtx_init(&ngq_mtx, "netgraph item list mutex", NULL, + MTX_DEF); +#endif + ng_qzone = uma_zcreate("NetGraph items", sizeof(struct ng_item), + NULL, NULL, NULL, NULL, UMA_ALIGN_CACHE, 0); + uma_zone_set_max(ng_qzone, maxalloc); + ng_qdzone = uma_zcreate("NetGraph data items", sizeof(struct ng_item), + NULL, NULL, NULL, NULL, UMA_ALIGN_CACHE, 0); + uma_zone_set_max(ng_qdzone, maxdata); + netisr_register(NETISR_NETGRAPH, (netisr_t *)ngintr, NULL, + NETISR_MPSAFE); + break; + case MOD_UNLOAD: + /* You can't unload it because an interface may be using it. */ + error = EBUSY; + break; + default: + error = EOPNOTSUPP; + break; + } + return (error); +} + +static moduledata_t netgraph_mod = { + "netgraph", + ngb_mod_event, + (NULL) +}; +DECLARE_MODULE(netgraph, netgraph_mod, SI_SUB_NETGRAPH, SI_ORDER_MIDDLE); +SYSCTL_NODE(_net, OID_AUTO, graph, CTLFLAG_RW, 0, "netgraph Family"); +SYSCTL_INT(_net_graph, OID_AUTO, abi_version, CTLFLAG_RD, 0, NG_ABI_VERSION,""); +SYSCTL_INT(_net_graph, OID_AUTO, msg_version, CTLFLAG_RD, 0, NG_VERSION, ""); + +#ifdef NETGRAPH_DEBUG +void +dumphook (hook_p hook, char *file, int line) +{ + printf("hook: name %s, %d refs, Last touched:\n", + _NG_HOOK_NAME(hook), hook->hk_refs); + printf(" Last active @ %s, line %d\n", + hook->lastfile, hook->lastline); + if (line) { + printf(" problem discovered at file %s, line %d\n", file, line); + } +} + +void +dumpnode(node_p node, char *file, int line) +{ + printf("node: ID [%x]: type '%s', %d hooks, flags 0x%x, %d refs, %s:\n", + _NG_NODE_ID(node), node->nd_type->name, + node->nd_numhooks, node->nd_flags, + node->nd_refs, node->nd_name); + printf(" Last active @ %s, line %d\n", + node->lastfile, node->lastline); + if (line) { + printf(" problem discovered at file %s, line %d\n", file, line); + } +} + +void +dumpitem(item_p item, char *file, int line) +{ + printf(" ACTIVE item, last used at %s, line %d", + item->lastfile, item->lastline); + switch(item->el_flags & NGQF_TYPE) { + case NGQF_DATA: + printf(" - [data]\n"); + break; + case NGQF_MESG: + printf(" - retaddr[%d]:\n", _NGI_RETADDR(item)); + break; + case NGQF_FN: + printf(" - fn@%p (%p, %p, %p, %d (%x))\n", + _NGI_FN(item), + _NGI_NODE(item), + _NGI_HOOK(item), + item->body.fn.fn_arg1, + item->body.fn.fn_arg2, + item->body.fn.fn_arg2); + break; + case NGQF_FN2: + printf(" - fn2@%p (%p, %p, %p, %d (%x))\n", + _NGI_FN2(item), + _NGI_NODE(item), + _NGI_HOOK(item), + item->body.fn.fn_arg1, + item->body.fn.fn_arg2, + item->body.fn.fn_arg2); + break; + } + if (line) { + printf(" problem discovered at file %s, line %d\n", file, line); + if (_NGI_NODE(item)) { + printf("node %p ([%x])\n", + _NGI_NODE(item), ng_node2ID(_NGI_NODE(item))); + } + } +} + +static void +ng_dumpitems(void) +{ + item_p item; + int i = 1; + TAILQ_FOREACH(item, &ng_itemlist, all) { + printf("[%d] ", i++); + dumpitem(item, NULL, 0); + } +} + +static void +ng_dumpnodes(void) +{ + node_p node; + int i = 1; + mtx_lock(&ng_nodelist_mtx); + SLIST_FOREACH(node, &ng_allnodes, nd_all) { + printf("[%d] ", i++); + dumpnode(node, NULL, 0); + } + mtx_unlock(&ng_nodelist_mtx); +} + +static void +ng_dumphooks(void) +{ + hook_p hook; + int i = 1; + mtx_lock(&ng_nodelist_mtx); + SLIST_FOREACH(hook, &ng_allhooks, hk_all) { + printf("[%d] ", i++); + dumphook(hook, NULL, 0); + } + mtx_unlock(&ng_nodelist_mtx); +} + +static int +sysctl_debug_ng_dump_items(SYSCTL_HANDLER_ARGS) +{ + int error; + int val; + int i; + + val = allocated; + i = 1; + error = sysctl_handle_int(oidp, &val, 0, req); + if (error != 0 || req->newptr == NULL) + return (error); + if (val == 42) { + ng_dumpitems(); + ng_dumpnodes(); + ng_dumphooks(); + } + return (0); +} + +SYSCTL_PROC(_debug, OID_AUTO, ng_dump_items, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_debug_ng_dump_items, "I", "Number of allocated items"); +#endif /* NETGRAPH_DEBUG */ + + +/*********************************************************************** +* Worklist routines +**********************************************************************/ +/* NETISR thread enters here */ +/* + * Pick a node off the list of nodes with work, + * try get an item to process off it. + * If there are no more, remove the node from the list. + */ +static void +ngintr(void) +{ + for (;;) { + node_p node; + + /* Get node from the worklist. */ + NG_WORKLIST_LOCK(); + node = STAILQ_FIRST(&ng_worklist); + if (!node) { + NG_WORKLIST_UNLOCK(); + break; + } + STAILQ_REMOVE_HEAD(&ng_worklist, nd_input_queue.q_work); + NG_WORKLIST_UNLOCK(); + CTR3(KTR_NET, "%20s: node [%x] (%p) taken off worklist", + __func__, node->nd_ID, node); + /* + * We have the node. We also take over the reference + * that the list had on it. + * Now process as much as you can, until it won't + * let you have another item off the queue. + * All this time, keep the reference + * that lets us be sure that the node still exists. + * Let the reference go at the last minute. + */ + for (;;) { + item_p item; + int rw; + + NG_QUEUE_LOCK(&node->nd_input_queue); + item = ng_dequeue(node, &rw); + if (item == NULL) { + node->nd_input_queue.q_flags2 &= ~NGQ2_WORKQ; + NG_QUEUE_UNLOCK(&node->nd_input_queue); + break; /* go look for another node */ + } else { + NG_QUEUE_UNLOCK(&node->nd_input_queue); + NGI_GET_NODE(item, node); /* zaps stored node */ + ng_apply_item(node, item, rw); + NG_NODE_UNREF(node); + } + } + NG_NODE_UNREF(node); + } +} + +/* + * XXX + * It's posible that a debugging NG_NODE_REF may need + * to be outside the mutex zone + */ +static void +ng_worklist_add(node_p node) +{ + + mtx_assert(&node->nd_input_queue.q_mtx, MA_OWNED); + + if ((node->nd_input_queue.q_flags2 & NGQ2_WORKQ) == 0) { + /* + * If we are not already on the work queue, + * then put us on. + */ + node->nd_input_queue.q_flags2 |= NGQ2_WORKQ; + NG_NODE_REF(node); /* XXX fafe in mutex? */ + NG_WORKLIST_LOCK(); + STAILQ_INSERT_TAIL(&ng_worklist, node, nd_input_queue.q_work); + NG_WORKLIST_UNLOCK(); + schednetisr(NETISR_NETGRAPH); + CTR3(KTR_NET, "%20s: node [%x] (%p) put on worklist", __func__, + node->nd_ID, node); + } else { + CTR3(KTR_NET, "%20s: node [%x] (%p) already on worklist", + __func__, node->nd_ID, node); + } +} + + +/*********************************************************************** +* Externally useable functions to set up a queue item ready for sending +***********************************************************************/ + +#ifdef NETGRAPH_DEBUG +#define ITEM_DEBUG_CHECKS \ + do { \ + if (NGI_NODE(item) ) { \ + printf("item already has node"); \ + kdb_enter(KDB_WHY_NETGRAPH, "has node"); \ + NGI_CLR_NODE(item); \ + } \ + if (NGI_HOOK(item) ) { \ + printf("item already has hook"); \ + kdb_enter(KDB_WHY_NETGRAPH, "has hook"); \ + NGI_CLR_HOOK(item); \ + } \ + } while (0) +#else +#define ITEM_DEBUG_CHECKS +#endif + +/* + * Put mbuf into the item. + * Hook and node references will be removed when the item is dequeued. + * (or equivalent) + * (XXX) Unsafe because no reference held by peer on remote node. + * remote node might go away in this timescale. + * We know the hooks can't go away because that would require getting + * a writer item on both nodes and we must have at least a reader + * here to be able to do this. + * Note that the hook loaded is the REMOTE hook. + * + * This is possibly in the critical path for new data. + */ +item_p +ng_package_data(struct mbuf *m, int flags) +{ + item_p item; + + if ((item = ng_alloc_item(NGQF_DATA, flags)) == NULL) { + NG_FREE_M(m); + return (NULL); + } + ITEM_DEBUG_CHECKS; + item->el_flags |= NGQF_READER; + NGI_M(item) = m; + return (item); +} + +/* + * Allocate a queue item and put items into it.. + * Evaluate the address as this will be needed to queue it and + * to work out what some of the fields should be. + * Hook and node references will be removed when the item is dequeued. + * (or equivalent) + */ +item_p +ng_package_msg(struct ng_mesg *msg, int flags) +{ + item_p item; + + if ((item = ng_alloc_item(NGQF_MESG, flags)) == NULL) { + NG_FREE_MSG(msg); + return (NULL); + } + ITEM_DEBUG_CHECKS; + /* Messages items count as writers unless explicitly exempted. */ + if (msg->header.cmd & NGM_READONLY) + item->el_flags |= NGQF_READER; + else + item->el_flags |= NGQF_WRITER; + /* + * Set the current lasthook into the queue item + */ + NGI_MSG(item) = msg; + NGI_RETADDR(item) = 0; + return (item); +} + + + +#define SET_RETADDR(item, here, retaddr) \ + do { /* Data or fn items don't have retaddrs */ \ + if ((item->el_flags & NGQF_TYPE) == NGQF_MESG) { \ + if (retaddr) { \ + NGI_RETADDR(item) = retaddr; \ + } else { \ + /* \ + * The old return address should be ok. \ + * If there isn't one, use the address \ + * here. \ + */ \ + if (NGI_RETADDR(item) == 0) { \ + NGI_RETADDR(item) \ + = ng_node2ID(here); \ + } \ + } \ + } \ + } while (0) + +int +ng_address_hook(node_p here, item_p item, hook_p hook, ng_ID_t retaddr) +{ + hook_p peer; + node_p peernode; + ITEM_DEBUG_CHECKS; + /* + * Quick sanity check.. + * Since a hook holds a reference on it's node, once we know + * that the peer is still connected (even if invalid,) we know + * that the peer node is present, though maybe invalid. + */ + if ((hook == NULL) || + NG_HOOK_NOT_VALID(hook) || + NG_HOOK_NOT_VALID(peer = NG_HOOK_PEER(hook)) || + NG_NODE_NOT_VALID(peernode = NG_PEER_NODE(hook))) { + NG_FREE_ITEM(item); + TRAP_ERROR(); + return (ENETDOWN); + } + + /* + * Transfer our interest to the other (peer) end. + */ + NG_HOOK_REF(peer); + NG_NODE_REF(peernode); + NGI_SET_HOOK(item, peer); + NGI_SET_NODE(item, peernode); + SET_RETADDR(item, here, retaddr); + return (0); +} + +int +ng_address_path(node_p here, item_p item, char *address, ng_ID_t retaddr) +{ + node_p dest = NULL; + hook_p hook = NULL; + int error; + + ITEM_DEBUG_CHECKS; + /* + * Note that ng_path2noderef increments the reference count + * on the node for us if it finds one. So we don't have to. + */ + error = ng_path2noderef(here, address, &dest, &hook); + if (error) { + NG_FREE_ITEM(item); + return (error); + } + NGI_SET_NODE(item, dest); + if ( hook) { + NG_HOOK_REF(hook); /* don't let it go while on the queue */ + NGI_SET_HOOK(item, hook); + } + SET_RETADDR(item, here, retaddr); + return (0); +} + +int +ng_address_ID(node_p here, item_p item, ng_ID_t ID, ng_ID_t retaddr) +{ + node_p dest; + + ITEM_DEBUG_CHECKS; + /* + * Find the target node. + */ + dest = ng_ID2noderef(ID); /* GETS REFERENCE! */ + if (dest == NULL) { + NG_FREE_ITEM(item); + TRAP_ERROR(); + return(EINVAL); + } + /* Fill out the contents */ + NGI_SET_NODE(item, dest); + NGI_CLR_HOOK(item); + SET_RETADDR(item, here, retaddr); + return (0); +} + +/* + * special case to send a message to self (e.g. destroy node) + * Possibly indicate an arrival hook too. + * Useful for removing that hook :-) + */ +item_p +ng_package_msg_self(node_p here, hook_p hook, struct ng_mesg *msg) +{ + item_p item; + + /* + * Find the target node. + * If there is a HOOK argument, then use that in preference + * to the address. + */ + if ((item = ng_alloc_item(NGQF_MESG, NG_NOFLAGS)) == NULL) { + NG_FREE_MSG(msg); + return (NULL); + } + + /* Fill out the contents */ + item->el_flags |= NGQF_WRITER; + NG_NODE_REF(here); + NGI_SET_NODE(item, here); + if (hook) { + NG_HOOK_REF(hook); + NGI_SET_HOOK(item, hook); + } + NGI_MSG(item) = msg; + NGI_RETADDR(item) = ng_node2ID(here); + return (item); +} + +/* + * Send ng_item_fn function call to the specified node. + */ + +int +ng_send_fn(node_p node, hook_p hook, ng_item_fn *fn, void * arg1, int arg2) +{ + + return ng_send_fn1(node, hook, fn, arg1, arg2, NG_NOFLAGS); +} + +int +ng_send_fn1(node_p node, hook_p hook, ng_item_fn *fn, void * arg1, int arg2, + int flags) +{ + item_p item; + + if ((item = ng_alloc_item(NGQF_FN, flags)) == NULL) { + return (ENOMEM); + } + item->el_flags |= NGQF_WRITER; + NG_NODE_REF(node); /* and one for the item */ + NGI_SET_NODE(item, node); + if (hook) { + NG_HOOK_REF(hook); + NGI_SET_HOOK(item, hook); + } + NGI_FN(item) = fn; + NGI_ARG1(item) = arg1; + NGI_ARG2(item) = arg2; + return(ng_snd_item(item, flags)); +} + +/* + * Send ng_item_fn2 function call to the specified node. + * + * If an optional pitem parameter is supplied, its apply + * callback will be copied to the new item. If also NG_REUSE_ITEM + * flag is set, no new item will be allocated, but pitem will + * be used. + */ +int +ng_send_fn2(node_p node, hook_p hook, item_p pitem, ng_item_fn2 *fn, void *arg1, + int arg2, int flags) +{ + item_p item; + + KASSERT((pitem != NULL || (flags & NG_REUSE_ITEM) == 0), + ("%s: NG_REUSE_ITEM but no pitem", __func__)); + + /* + * Allocate a new item if no supplied or + * if we can't use supplied one. + */ + if (pitem == NULL || (flags & NG_REUSE_ITEM) == 0) { + if ((item = ng_alloc_item(NGQF_FN2, flags)) == NULL) + return (ENOMEM); + if (pitem != NULL) + item->apply = pitem->apply; + } else { + if ((item = ng_realloc_item(pitem, NGQF_FN2, flags)) == NULL) + return (ENOMEM); + } + + item->el_flags = (item->el_flags & ~NGQF_RW) | NGQF_WRITER; + NG_NODE_REF(node); /* and one for the item */ + NGI_SET_NODE(item, node); + if (hook) { + NG_HOOK_REF(hook); + NGI_SET_HOOK(item, hook); + } + NGI_FN2(item) = fn; + NGI_ARG1(item) = arg1; + NGI_ARG2(item) = arg2; + return(ng_snd_item(item, flags)); +} + +/* + * Official timeout routines for Netgraph nodes. + */ +static void +ng_callout_trampoline(void *arg) +{ + item_p item = arg; + + ng_snd_item(item, 0); +} + + +int +ng_callout(struct callout *c, node_p node, hook_p hook, int ticks, + ng_item_fn *fn, void * arg1, int arg2) +{ + item_p item, oitem; + + if ((item = ng_alloc_item(NGQF_FN, NG_NOFLAGS)) == NULL) + return (ENOMEM); + + item->el_flags |= NGQF_WRITER; + NG_NODE_REF(node); /* and one for the item */ + NGI_SET_NODE(item, node); + if (hook) { + NG_HOOK_REF(hook); + NGI_SET_HOOK(item, hook); + } + NGI_FN(item) = fn; + NGI_ARG1(item) = arg1; + NGI_ARG2(item) = arg2; + oitem = c->c_arg; + if (callout_reset(c, ticks, &ng_callout_trampoline, item) == 1 && + oitem != NULL) + NG_FREE_ITEM(oitem); + return (0); +} + +/* A special modified version of untimeout() */ +int +ng_uncallout(struct callout *c, node_p node) +{ + item_p item; + int rval; + + KASSERT(c != NULL, ("ng_uncallout: NULL callout")); + KASSERT(node != NULL, ("ng_uncallout: NULL node")); + + rval = callout_stop(c); + item = c->c_arg; + /* Do an extra check */ + if ((rval > 0) && (c->c_func == &ng_callout_trampoline) && + (NGI_NODE(item) == node)) { + /* + * We successfully removed it from the queue before it ran + * So now we need to unreference everything that was + * given extra references. (NG_FREE_ITEM does this). + */ + NG_FREE_ITEM(item); + } + c->c_arg = NULL; + + return (rval); +} + +/* + * Set the address, if none given, give the node here. + */ +void +ng_replace_retaddr(node_p here, item_p item, ng_ID_t retaddr) +{ + if (retaddr) { + NGI_RETADDR(item) = retaddr; + } else { + /* + * The old return address should be ok. + * If there isn't one, use the address here. + */ + NGI_RETADDR(item) = ng_node2ID(here); + } +} + +#define TESTING +#ifdef TESTING +/* just test all the macros */ +void +ng_macro_test(item_p item); +void +ng_macro_test(item_p item) +{ + node_p node = NULL; + hook_p hook = NULL; + struct mbuf *m; + struct ng_mesg *msg; + ng_ID_t retaddr; + int error; + + NGI_GET_M(item, m); + NGI_GET_MSG(item, msg); + retaddr = NGI_RETADDR(item); + NG_SEND_DATA(error, hook, m, NULL); + NG_SEND_DATA_ONLY(error, hook, m); + NG_FWD_NEW_DATA(error, item, hook, m); + NG_FWD_ITEM_HOOK(error, item, hook); + NG_SEND_MSG_HOOK(error, node, msg, hook, retaddr); + NG_SEND_MSG_ID(error, node, msg, retaddr, retaddr); + NG_SEND_MSG_PATH(error, node, msg, ".:", retaddr); + NG_FWD_MSG_HOOK(error, node, item, hook, retaddr); +} +#endif /* TESTING */ + diff --git a/sys/netgraph7/ng_bpf.c b/sys/netgraph7/ng_bpf.c new file mode 100644 index 0000000000..c11e816359 --- /dev/null +++ b/sys/netgraph7/ng_bpf.c @@ -0,0 +1,590 @@ +/* + * ng_bpf.c + */ + +/*- + * Copyright (c) 1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_bpf.c,v 1.24 2008/02/04 19:26:53 mav Exp $ + * $Whistle: ng_bpf.c,v 1.3 1999/12/03 20:30:23 archie Exp $ + */ + +/* + * BPF NETGRAPH NODE TYPE + * + * This node type accepts any number of hook connections. With each hook + * is associated a bpf(4) filter program, and two hook names (each possibly + * the empty string). Incoming packets are compared against the filter; + * matching packets are delivered out the first named hook (or dropped if + * the empty string), and non-matching packets are delivered out the second + * named hook (or dropped if the empty string). + * + * Each hook also keeps statistics about how many packets have matched, etc. + */ + +#include "opt_bpf.h" + +#include +#include +#include +#include +#include +#include + +#include +#ifdef BPF_JITTER +#include +#endif + +#include +#include +#include +#include + +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_BPF, "netgraph_bpf", "netgraph bpf node "); +#else +#define M_NETGRAPH_BPF M_NETGRAPH +#endif + +#define OFFSETOF(s, e) ((char *)&((s *)0)->e - (char *)((s *)0)) + +#define ERROUT(x) do { error = (x); goto done; } while (0) + +/* Per hook private info */ +struct ng_bpf_hookinfo { + hook_p hook; + hook_p match; + hook_p nomatch; + struct ng_bpf_hookprog *prog; +#ifdef BPF_JITTER + bpf_jit_filter *jit_prog; +#endif + struct ng_bpf_hookstat stats; +}; +typedef struct ng_bpf_hookinfo *hinfo_p; + +/* Netgraph methods */ +static ng_constructor_t ng_bpf_constructor; +static ng_rcvmsg_t ng_bpf_rcvmsg; +static ng_shutdown_t ng_bpf_shutdown; +static ng_newhook_t ng_bpf_newhook; +static ng_rcvdata_t ng_bpf_rcvdata; +static ng_disconnect_t ng_bpf_disconnect; + +/* Internal helper functions */ +static int ng_bpf_setprog(hook_p hook, const struct ng_bpf_hookprog *hp); + +/* Parse type for one struct bfp_insn */ +static const struct ng_parse_struct_field ng_bpf_insn_type_fields[] = { + { "code", &ng_parse_hint16_type }, + { "jt", &ng_parse_uint8_type }, + { "jf", &ng_parse_uint8_type }, + { "k", &ng_parse_uint32_type }, + { NULL } +}; +static const struct ng_parse_type ng_bpf_insn_type = { + &ng_parse_struct_type, + &ng_bpf_insn_type_fields +}; + +/* Parse type for the field 'bpf_prog' in struct ng_bpf_hookprog */ +static int +ng_bpf_hookprogary_getLength(const struct ng_parse_type *type, + const u_char *start, const u_char *buf) +{ + const struct ng_bpf_hookprog *hp; + + hp = (const struct ng_bpf_hookprog *) + (buf - OFFSETOF(struct ng_bpf_hookprog, bpf_prog)); + return hp->bpf_prog_len; +} + +static const struct ng_parse_array_info ng_bpf_hookprogary_info = { + &ng_bpf_insn_type, + &ng_bpf_hookprogary_getLength, + NULL +}; +static const struct ng_parse_type ng_bpf_hookprogary_type = { + &ng_parse_array_type, + &ng_bpf_hookprogary_info +}; + +/* Parse type for struct ng_bpf_hookprog */ +static const struct ng_parse_struct_field ng_bpf_hookprog_type_fields[] + = NG_BPF_HOOKPROG_TYPE_INFO(&ng_bpf_hookprogary_type); +static const struct ng_parse_type ng_bpf_hookprog_type = { + &ng_parse_struct_type, + &ng_bpf_hookprog_type_fields +}; + +/* Parse type for struct ng_bpf_hookstat */ +static const struct ng_parse_struct_field ng_bpf_hookstat_type_fields[] + = NG_BPF_HOOKSTAT_TYPE_INFO; +static const struct ng_parse_type ng_bpf_hookstat_type = { + &ng_parse_struct_type, + &ng_bpf_hookstat_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_bpf_cmdlist[] = { + { + NGM_BPF_COOKIE, + NGM_BPF_SET_PROGRAM, + "setprogram", + &ng_bpf_hookprog_type, + NULL + }, + { + NGM_BPF_COOKIE, + NGM_BPF_GET_PROGRAM, + "getprogram", + &ng_parse_hookbuf_type, + &ng_bpf_hookprog_type + }, + { + NGM_BPF_COOKIE, + NGM_BPF_GET_STATS, + "getstats", + &ng_parse_hookbuf_type, + &ng_bpf_hookstat_type + }, + { + NGM_BPF_COOKIE, + NGM_BPF_CLR_STATS, + "clrstats", + &ng_parse_hookbuf_type, + NULL + }, + { + NGM_BPF_COOKIE, + NGM_BPF_GETCLR_STATS, + "getclrstats", + &ng_parse_hookbuf_type, + &ng_bpf_hookstat_type + }, + { 0 } +}; + +/* Netgraph type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_BPF_NODE_TYPE, + .constructor = ng_bpf_constructor, + .rcvmsg = ng_bpf_rcvmsg, + .shutdown = ng_bpf_shutdown, + .newhook = ng_bpf_newhook, + .rcvdata = ng_bpf_rcvdata, + .disconnect = ng_bpf_disconnect, + .cmdlist = ng_bpf_cmdlist, +}; +NETGRAPH_INIT(bpf, &typestruct); + +/* Default BPF program for a hook that matches nothing */ +static const struct ng_bpf_hookprog ng_bpf_default_prog = { + { '\0' }, /* to be filled in at hook creation time */ + { '\0' }, + { '\0' }, + 1, + { BPF_STMT(BPF_RET+BPF_K, 0) } +}; + +/* + * Node constructor + * + * We don't keep any per-node private data + * We go via the hooks. + */ +static int +ng_bpf_constructor(node_p node) +{ + NG_NODE_SET_PRIVATE(node, NULL); + return (0); +} + +/* + * Callback functions to be used by NG_NODE_FOREACH_HOOK() macro. + */ +static int +ng_bpf_addrefs(hook_p hook, void* arg) +{ + hinfo_p hip = NG_HOOK_PRIVATE(hook); + hook_p h = (hook_p)arg; + + if (strcmp(hip->prog->ifMatch, NG_HOOK_NAME(h)) == 0) + hip->match = h; + if (strcmp(hip->prog->ifNotMatch, NG_HOOK_NAME(h)) == 0) + hip->nomatch = h; + return (1); +} + +static int +ng_bpf_remrefs(hook_p hook, void* arg) +{ + hinfo_p hip = NG_HOOK_PRIVATE(hook); + hook_p h = (hook_p)arg; + + if (hip->match == h) + hip->match = NULL; + if (hip->nomatch == h) + hip->nomatch = NULL; + return (1); +} + +/* + * Add a hook + */ +static int +ng_bpf_newhook(node_p node, hook_p hook, const char *name) +{ + hinfo_p hip; + hook_p tmp; + int error; + + /* Create hook private structure */ + MALLOC(hip, hinfo_p, sizeof(*hip), M_NETGRAPH_BPF, M_NOWAIT | M_ZERO); + if (hip == NULL) + return (ENOMEM); + hip->hook = hook; + NG_HOOK_SET_PRIVATE(hook, hip); + + /* Add our reference into other hooks data. */ + NG_NODE_FOREACH_HOOK(node, ng_bpf_addrefs, hook, tmp); + + /* Attach the default BPF program */ + if ((error = ng_bpf_setprog(hook, &ng_bpf_default_prog)) != 0) { + FREE(hip, M_NETGRAPH_BPF); + NG_HOOK_SET_PRIVATE(hook, NULL); + return (error); + } + + /* Set hook name */ + strlcpy(hip->prog->thisHook, name, sizeof(hip->prog->thisHook)); + return (0); +} + +/* + * Receive a control message + */ +static int +ng_bpf_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct ng_mesg *msg; + struct ng_mesg *resp = NULL; + int error = 0; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_BPF_COOKIE: + switch (msg->header.cmd) { + case NGM_BPF_SET_PROGRAM: + { + struct ng_bpf_hookprog *const + hp = (struct ng_bpf_hookprog *)msg->data; + hook_p hook; + + /* Sanity check */ + if (msg->header.arglen < sizeof(*hp) + || msg->header.arglen + != NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) + ERROUT(EINVAL); + + /* Find hook */ + if ((hook = ng_findhook(node, hp->thisHook)) == NULL) + ERROUT(ENOENT); + + /* Set new program */ + if ((error = ng_bpf_setprog(hook, hp)) != 0) + ERROUT(error); + break; + } + + case NGM_BPF_GET_PROGRAM: + { + struct ng_bpf_hookprog *hp; + hook_p hook; + + /* Sanity check */ + if (msg->header.arglen == 0) + ERROUT(EINVAL); + msg->data[msg->header.arglen - 1] = '\0'; + + /* Find hook */ + if ((hook = ng_findhook(node, msg->data)) == NULL) + ERROUT(ENOENT); + + /* Build response */ + hp = ((hinfo_p)NG_HOOK_PRIVATE(hook))->prog; + NG_MKRESPONSE(resp, msg, + NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len), M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + bcopy(hp, resp->data, + NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)); + break; + } + + case NGM_BPF_GET_STATS: + case NGM_BPF_CLR_STATS: + case NGM_BPF_GETCLR_STATS: + { + struct ng_bpf_hookstat *stats; + hook_p hook; + + /* Sanity check */ + if (msg->header.arglen == 0) + ERROUT(EINVAL); + msg->data[msg->header.arglen - 1] = '\0'; + + /* Find hook */ + if ((hook = ng_findhook(node, msg->data)) == NULL) + ERROUT(ENOENT); + stats = &((hinfo_p)NG_HOOK_PRIVATE(hook))->stats; + + /* Build response (if desired) */ + if (msg->header.cmd != NGM_BPF_CLR_STATS) { + NG_MKRESPONSE(resp, + msg, sizeof(*stats), M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + bcopy(stats, resp->data, sizeof(*stats)); + } + + /* Clear stats (if desired) */ + if (msg->header.cmd != NGM_BPF_GET_STATS) + bzero(stats, sizeof(*stats)); + break; + } + + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive data on a hook + * + * Apply the filter, and then drop or forward packet as appropriate. + */ +static int +ng_bpf_rcvdata(hook_p hook, item_p item) +{ + const hinfo_p hip = NG_HOOK_PRIVATE(hook); + int totlen; + int needfree = 0, error = 0, usejit = 0; + u_char *data = NULL; + hinfo_p dhip; + hook_p dest; + u_int len; + struct mbuf *m; + + m = NGI_M(item); /* 'item' still owns it.. we are peeking */ + totlen = m->m_pkthdr.len; + /* Update stats on incoming hook. XXX Can we do 64 bits atomically? */ + /* atomic_add_int64(&hip->stats.recvFrames, 1); */ + /* atomic_add_int64(&hip->stats.recvOctets, totlen); */ + hip->stats.recvFrames++; + hip->stats.recvOctets += totlen; + + /* Don't call bpf_filter() with totlen == 0! */ + if (totlen == 0) { + len = 0; + goto ready; + } + +#ifdef BPF_JITTER + if (bpf_jitter_enable != 0 && hip->jit_prog != NULL) + usejit = 1; +#endif + + /* Need to put packet in contiguous memory for bpf */ + if (m->m_next != NULL && totlen > MHLEN) { + if (usejit) { + MALLOC(data, u_char *, totlen, M_NETGRAPH_BPF, M_NOWAIT); + if (data == NULL) { + NG_FREE_ITEM(item); + return (ENOMEM); + } + needfree = 1; + m_copydata(m, 0, totlen, (caddr_t)data); + } + } else { + if (m->m_next != NULL) { + NGI_M(item) = m = m_pullup(m, totlen); + if (m == NULL) { + NG_FREE_ITEM(item); + return (ENOBUFS); + } + } + data = mtod(m, u_char *); + } + + /* Run packet through filter */ +#ifdef BPF_JITTER + if (usejit) + len = (*(hip->jit_prog->func))(data, totlen, totlen); + else +#endif + if (data) + len = bpf_filter(hip->prog->bpf_prog, data, totlen, totlen); + else + len = bpf_filter(hip->prog->bpf_prog, (u_char *)m, totlen, 0); + if (needfree) + FREE(data, M_NETGRAPH_BPF); +ready: + /* See if we got a match and find destination hook */ + if (len > 0) { + + /* Update stats */ + /* XXX atomically? */ + hip->stats.recvMatchFrames++; + hip->stats.recvMatchOctets += totlen; + + /* Truncate packet length if required by the filter */ + /* Assume this never changes m */ + if (len < totlen) { + m_adj(m, -(totlen - len)); + totlen = len; + } + dest = hip->match; + } else + dest = hip->nomatch; + if (dest == NULL) { + NG_FREE_ITEM(item); + return (0); + } + + /* Deliver frame out destination hook */ + dhip = NG_HOOK_PRIVATE(dest); + dhip->stats.xmitOctets += totlen; + dhip->stats.xmitFrames++; + NG_FWD_ITEM_HOOK(error, item, dest); + return (error); +} + +/* + * Shutdown processing + */ +static int +ng_bpf_shutdown(node_p node) +{ + NG_NODE_UNREF(node); + return (0); +} + +/* + * Hook disconnection + */ +static int +ng_bpf_disconnect(hook_p hook) +{ + const node_p node = NG_HOOK_NODE(hook); + const hinfo_p hip = NG_HOOK_PRIVATE(hook); + hook_p tmp; + + KASSERT(hip != NULL, ("%s: null info", __func__)); + + /* Remove our reference from other hooks data. */ + NG_NODE_FOREACH_HOOK(node, ng_bpf_remrefs, hook, tmp); + + FREE(hip->prog, M_NETGRAPH_BPF); +#ifdef BPF_JITTER + if (hip->jit_prog != NULL) + bpf_destroy_jit_filter(hip->jit_prog); +#endif + FREE(hip, M_NETGRAPH_BPF); + if ((NG_NODE_NUMHOOKS(node) == 0) && + (NG_NODE_IS_VALID(node))) { + ng_rmnode_self(node); + } + return (0); +} + +/************************************************************************ + HELPER STUFF + ************************************************************************/ + +/* + * Set the BPF program associated with a hook + */ +static int +ng_bpf_setprog(hook_p hook, const struct ng_bpf_hookprog *hp0) +{ + const hinfo_p hip = NG_HOOK_PRIVATE(hook); + struct ng_bpf_hookprog *hp; +#ifdef BPF_JITTER + bpf_jit_filter *jit_prog; +#endif + int size; + + /* Check program for validity */ + if (!bpf_validate(hp0->bpf_prog, hp0->bpf_prog_len)) + return (EINVAL); + + /* Make a copy of the program */ + size = NG_BPF_HOOKPROG_SIZE(hp0->bpf_prog_len); + MALLOC(hp, struct ng_bpf_hookprog *, size, M_NETGRAPH_BPF, M_NOWAIT); + if (hp == NULL) + return (ENOMEM); + bcopy(hp0, hp, size); +#ifdef BPF_JITTER + jit_prog = bpf_jitter(hp->bpf_prog, hp->bpf_prog_len); +#endif + + /* Free previous program, if any, and assign new one */ + if (hip->prog != NULL) + FREE(hip->prog, M_NETGRAPH_BPF); + hip->prog = hp; +#ifdef BPF_JITTER + if (hip->jit_prog != NULL) + bpf_destroy_jit_filter(hip->jit_prog); + hip->jit_prog = jit_prog; +#endif + + /* Prepare direct references on target hooks. */ + hip->match = ng_findhook(NG_HOOK_NODE(hook), hip->prog->ifMatch); + hip->nomatch = ng_findhook(NG_HOOK_NODE(hook), hip->prog->ifNotMatch); + return (0); +} diff --git a/sys/netgraph7/ng_bpf.h b/sys/netgraph7/ng_bpf.h new file mode 100644 index 0000000000..540b4af870 --- /dev/null +++ b/sys/netgraph7/ng_bpf.h @@ -0,0 +1,103 @@ +/* + * ng_bpf.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_bpf.h,v 1.11 2005/02/12 18:10:26 ru Exp $ + * $Whistle: ng_bpf.h,v 1.3 1999/12/03 20:30:23 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_BPF_H_ +#define _NETGRAPH_NG_BPF_H_ + +/* Node type name and magic cookie */ +#define NG_BPF_NODE_TYPE "bpf" +#define NGM_BPF_COOKIE 944100792 + +/* Program structure for one hook */ +struct ng_bpf_hookprog { + char thisHook[NG_HOOKSIZ]; /* name of hook */ + char ifMatch[NG_HOOKSIZ]; /* match dest hook */ + char ifNotMatch[NG_HOOKSIZ]; /* !match dest hook */ + int32_t bpf_prog_len; /* #insns in program */ + struct bpf_insn bpf_prog[]; /* bpf program */ +}; + +#define NG_BPF_HOOKPROG_SIZE(numInsn) \ + (sizeof(struct ng_bpf_hookprog) + (numInsn) * sizeof(struct bpf_insn)) + +/* Keep this in sync with the above structure definition */ +#define NG_BPF_HOOKPROG_TYPE_INFO(bptype) { \ + { "thisHook", &ng_parse_hookbuf_type }, \ + { "ifMatch", &ng_parse_hookbuf_type }, \ + { "ifNotMatch", &ng_parse_hookbuf_type }, \ + { "bpf_prog_len", &ng_parse_int32_type }, \ + { "bpf_prog", (bptype) }, \ + { NULL } \ +} + +/* Statistics structure for one hook */ +struct ng_bpf_hookstat { + u_int64_t recvFrames; + u_int64_t recvOctets; + u_int64_t recvMatchFrames; + u_int64_t recvMatchOctets; + u_int64_t xmitFrames; + u_int64_t xmitOctets; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_BPF_HOOKSTAT_TYPE_INFO { \ + { "recvFrames", &ng_parse_uint64_type }, \ + { "recvOctets", &ng_parse_uint64_type }, \ + { "recvMatchFrames", &ng_parse_uint64_type }, \ + { "recvMatchOctets", &ng_parse_uint64_type }, \ + { "xmitFrames", &ng_parse_uint64_type }, \ + { "xmitOctets", &ng_parse_uint64_type }, \ + { NULL } \ +} + +/* Netgraph commands */ +enum { + NGM_BPF_SET_PROGRAM = 1, /* supply a struct ng_bpf_hookprog */ + NGM_BPF_GET_PROGRAM, /* returns a struct ng_bpf_hookprog */ + NGM_BPF_GET_STATS, /* supply name as char[NG_HOOKSIZ] */ + NGM_BPF_CLR_STATS, /* supply name as char[NG_HOOKSIZ] */ + NGM_BPF_GETCLR_STATS, /* supply name as char[NG_HOOKSIZ] */ +}; + +#endif /* _NETGRAPH_NG_BPF_H_ */ diff --git a/sys/netgraph7/ng_bridge.c b/sys/netgraph7/ng_bridge.c new file mode 100644 index 0000000000..befd97eca0 --- /dev/null +++ b/sys/netgraph7/ng_bridge.c @@ -0,0 +1,1042 @@ +/* + * ng_bridge.c + */ + +/*- + * Copyright (c) 2000 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_bridge.c,v 1.31 2005/02/09 15:14:44 ru Exp $ + */ + +/* + * ng_bridge(4) netgraph node type + * + * The node performs standard intelligent Ethernet bridging over + * each of its connected hooks, or links. A simple loop detection + * algorithm is included which disables a link for priv->conf.loopTimeout + * seconds when a host is seen to have jumped from one link to + * another within priv->conf.minStableAge seconds. + * + * We keep a hashtable that maps Ethernet addresses to host info, + * which is contained in struct ng_bridge_host's. These structures + * tell us on which link the host may be found. A host's entry will + * expire after priv->conf.maxStaleness seconds. + * + * This node is optimzed for stable networks, where machines jump + * from one port to the other only rarely. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_BRIDGE, "netgraph_bridge", "netgraph bridge node "); +#else +#define M_NETGRAPH_BRIDGE M_NETGRAPH +#endif + +/* Per-link private data */ +struct ng_bridge_link { + hook_p hook; /* netgraph hook */ + u_int16_t loopCount; /* loop ignore timer */ + struct ng_bridge_link_stats stats; /* link stats */ +}; + +/* Per-node private data */ +struct ng_bridge_private { + struct ng_bridge_bucket *tab; /* hash table bucket array */ + struct ng_bridge_link *links[NG_BRIDGE_MAX_LINKS]; + struct ng_bridge_config conf; /* node configuration */ + node_p node; /* netgraph node */ + u_int numHosts; /* num entries in table */ + u_int numBuckets; /* num buckets in table */ + u_int hashMask; /* numBuckets - 1 */ + int numLinks; /* num connected links */ + struct callout timer; /* one second periodic timer */ +}; +typedef struct ng_bridge_private *priv_p; + +/* Information about a host, stored in a hash table entry */ +struct ng_bridge_hent { + struct ng_bridge_host host; /* actual host info */ + SLIST_ENTRY(ng_bridge_hent) next; /* next entry in bucket */ +}; + +/* Hash table bucket declaration */ +SLIST_HEAD(ng_bridge_bucket, ng_bridge_hent); + +/* Netgraph node methods */ +static ng_constructor_t ng_bridge_constructor; +static ng_rcvmsg_t ng_bridge_rcvmsg; +static ng_shutdown_t ng_bridge_shutdown; +static ng_newhook_t ng_bridge_newhook; +static ng_rcvdata_t ng_bridge_rcvdata; +static ng_disconnect_t ng_bridge_disconnect; + +/* Other internal functions */ +static struct ng_bridge_host *ng_bridge_get(priv_p priv, const u_char *addr); +static int ng_bridge_put(priv_p priv, const u_char *addr, int linkNum); +static void ng_bridge_rehash(priv_p priv); +static void ng_bridge_remove_hosts(priv_p priv, int linkNum); +static void ng_bridge_timeout(node_p node, hook_p hook, void *arg1, int arg2); +static const char *ng_bridge_nodename(node_p node); + +/* Ethernet broadcast */ +static const u_char ng_bridge_bcast_addr[ETHER_ADDR_LEN] = + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +/* Store each hook's link number in the private field */ +#define LINK_NUM(hook) (*(u_int16_t *)(&(hook)->private)) + +/* Compare Ethernet addresses using 32 and 16 bit words instead of bytewise */ +#define ETHER_EQUAL(a,b) (((const u_int32_t *)(a))[0] \ + == ((const u_int32_t *)(b))[0] \ + && ((const u_int16_t *)(a))[2] \ + == ((const u_int16_t *)(b))[2]) + +/* Minimum and maximum number of hash buckets. Must be a power of two. */ +#define MIN_BUCKETS (1 << 5) /* 32 */ +#define MAX_BUCKETS (1 << 14) /* 16384 */ + +/* Configuration default values */ +#define DEFAULT_LOOP_TIMEOUT 60 +#define DEFAULT_MAX_STALENESS (15 * 60) /* same as ARP timeout */ +#define DEFAULT_MIN_STABLE_AGE 1 + +/****************************************************************** + NETGRAPH PARSE TYPES +******************************************************************/ + +/* + * How to determine the length of the table returned by NGM_BRIDGE_GET_TABLE + */ +static int +ng_bridge_getTableLength(const struct ng_parse_type *type, + const u_char *start, const u_char *buf) +{ + const struct ng_bridge_host_ary *const hary + = (const struct ng_bridge_host_ary *)(buf - sizeof(u_int32_t)); + + return hary->numHosts; +} + +/* Parse type for struct ng_bridge_host_ary */ +static const struct ng_parse_struct_field ng_bridge_host_type_fields[] + = NG_BRIDGE_HOST_TYPE_INFO(&ng_parse_enaddr_type); +static const struct ng_parse_type ng_bridge_host_type = { + &ng_parse_struct_type, + &ng_bridge_host_type_fields +}; +static const struct ng_parse_array_info ng_bridge_hary_type_info = { + &ng_bridge_host_type, + ng_bridge_getTableLength +}; +static const struct ng_parse_type ng_bridge_hary_type = { + &ng_parse_array_type, + &ng_bridge_hary_type_info +}; +static const struct ng_parse_struct_field ng_bridge_host_ary_type_fields[] + = NG_BRIDGE_HOST_ARY_TYPE_INFO(&ng_bridge_hary_type); +static const struct ng_parse_type ng_bridge_host_ary_type = { + &ng_parse_struct_type, + &ng_bridge_host_ary_type_fields +}; + +/* Parse type for struct ng_bridge_config */ +static const struct ng_parse_fixedarray_info ng_bridge_ipfwary_type_info = { + &ng_parse_uint8_type, + NG_BRIDGE_MAX_LINKS +}; +static const struct ng_parse_type ng_bridge_ipfwary_type = { + &ng_parse_fixedarray_type, + &ng_bridge_ipfwary_type_info +}; +static const struct ng_parse_struct_field ng_bridge_config_type_fields[] + = NG_BRIDGE_CONFIG_TYPE_INFO(&ng_bridge_ipfwary_type); +static const struct ng_parse_type ng_bridge_config_type = { + &ng_parse_struct_type, + &ng_bridge_config_type_fields +}; + +/* Parse type for struct ng_bridge_link_stat */ +static const struct ng_parse_struct_field ng_bridge_stats_type_fields[] + = NG_BRIDGE_STATS_TYPE_INFO; +static const struct ng_parse_type ng_bridge_stats_type = { + &ng_parse_struct_type, + &ng_bridge_stats_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_bridge_cmdlist[] = { + { + NGM_BRIDGE_COOKIE, + NGM_BRIDGE_SET_CONFIG, + "setconfig", + &ng_bridge_config_type, + NULL + }, + { + NGM_BRIDGE_COOKIE, + NGM_BRIDGE_GET_CONFIG, + "getconfig", + NULL, + &ng_bridge_config_type + }, + { + NGM_BRIDGE_COOKIE, + NGM_BRIDGE_RESET, + "reset", + NULL, + NULL + }, + { + NGM_BRIDGE_COOKIE, + NGM_BRIDGE_GET_STATS, + "getstats", + &ng_parse_uint32_type, + &ng_bridge_stats_type + }, + { + NGM_BRIDGE_COOKIE, + NGM_BRIDGE_CLR_STATS, + "clrstats", + &ng_parse_uint32_type, + NULL + }, + { + NGM_BRIDGE_COOKIE, + NGM_BRIDGE_GETCLR_STATS, + "getclrstats", + &ng_parse_uint32_type, + &ng_bridge_stats_type + }, + { + NGM_BRIDGE_COOKIE, + NGM_BRIDGE_GET_TABLE, + "gettable", + NULL, + &ng_bridge_host_ary_type + }, + { 0 } +}; + +/* Node type descriptor */ +static struct ng_type ng_bridge_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_BRIDGE_NODE_TYPE, + .constructor = ng_bridge_constructor, + .rcvmsg = ng_bridge_rcvmsg, + .shutdown = ng_bridge_shutdown, + .newhook = ng_bridge_newhook, + .rcvdata = ng_bridge_rcvdata, + .disconnect = ng_bridge_disconnect, + .cmdlist = ng_bridge_cmdlist, +}; +NETGRAPH_INIT(bridge, &ng_bridge_typestruct); + +/****************************************************************** + NETGRAPH NODE METHODS +******************************************************************/ + +/* + * Node constructor + */ +static int +ng_bridge_constructor(node_p node) +{ + priv_p priv; + + /* Allocate and initialize private info */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH_BRIDGE, M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + ng_callout_init(&priv->timer); + + /* Allocate and initialize hash table, etc. */ + MALLOC(priv->tab, struct ng_bridge_bucket *, + MIN_BUCKETS * sizeof(*priv->tab), M_NETGRAPH_BRIDGE, M_NOWAIT | M_ZERO); + if (priv->tab == NULL) { + FREE(priv, M_NETGRAPH_BRIDGE); + return (ENOMEM); + } + priv->numBuckets = MIN_BUCKETS; + priv->hashMask = MIN_BUCKETS - 1; + priv->conf.debugLevel = 1; + priv->conf.loopTimeout = DEFAULT_LOOP_TIMEOUT; + priv->conf.maxStaleness = DEFAULT_MAX_STALENESS; + priv->conf.minStableAge = DEFAULT_MIN_STABLE_AGE; + + /* + * This node has all kinds of stuff that could be screwed by SMP. + * Until it gets it's own internal protection, we go through in + * single file. This could hurt a machine bridging beteen two + * GB ethernets so it should be fixed. + * When it's fixed the process SHOULD NOT SLEEP, spinlocks please! + * (and atomic ops ) + */ + NG_NODE_FORCE_WRITER(node); + NG_NODE_SET_PRIVATE(node, priv); + priv->node = node; + + /* Start timer; timer is always running while node is alive */ + ng_callout(&priv->timer, node, NULL, hz, ng_bridge_timeout, NULL, 0); + + /* Done */ + return (0); +} + +/* + * Method for attaching a new hook + */ +static int +ng_bridge_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + /* Check for a link hook */ + if (strncmp(name, NG_BRIDGE_HOOK_LINK_PREFIX, + strlen(NG_BRIDGE_HOOK_LINK_PREFIX)) == 0) { + const char *cp; + char *eptr; + u_long linkNum; + + cp = name + strlen(NG_BRIDGE_HOOK_LINK_PREFIX); + if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) + return (EINVAL); + linkNum = strtoul(cp, &eptr, 10); + if (*eptr != '\0' || linkNum >= NG_BRIDGE_MAX_LINKS) + return (EINVAL); + if (priv->links[linkNum] != NULL) + return (EISCONN); + MALLOC(priv->links[linkNum], struct ng_bridge_link *, + sizeof(*priv->links[linkNum]), M_NETGRAPH_BRIDGE, M_NOWAIT|M_ZERO); + if (priv->links[linkNum] == NULL) + return (ENOMEM); + priv->links[linkNum]->hook = hook; + NG_HOOK_SET_PRIVATE(hook, (void *)linkNum); + priv->numLinks++; + return (0); + } + + /* Unknown hook name */ + return (EINVAL); +} + +/* + * Receive a control message + */ +static int +ng_bridge_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_BRIDGE_COOKIE: + switch (msg->header.cmd) { + case NGM_BRIDGE_GET_CONFIG: + { + struct ng_bridge_config *conf; + + NG_MKRESPONSE(resp, msg, + sizeof(struct ng_bridge_config), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + conf = (struct ng_bridge_config *)resp->data; + *conf = priv->conf; /* no sanity checking needed */ + break; + } + case NGM_BRIDGE_SET_CONFIG: + { + struct ng_bridge_config *conf; + int i; + + if (msg->header.arglen + != sizeof(struct ng_bridge_config)) { + error = EINVAL; + break; + } + conf = (struct ng_bridge_config *)msg->data; + priv->conf = *conf; + for (i = 0; i < NG_BRIDGE_MAX_LINKS; i++) + priv->conf.ipfw[i] = !!priv->conf.ipfw[i]; + break; + } + case NGM_BRIDGE_RESET: + { + int i; + + /* Flush all entries in the hash table */ + ng_bridge_remove_hosts(priv, -1); + + /* Reset all loop detection counters and stats */ + for (i = 0; i < NG_BRIDGE_MAX_LINKS; i++) { + if (priv->links[i] == NULL) + continue; + priv->links[i]->loopCount = 0; + bzero(&priv->links[i]->stats, + sizeof(priv->links[i]->stats)); + } + break; + } + case NGM_BRIDGE_GET_STATS: + case NGM_BRIDGE_CLR_STATS: + case NGM_BRIDGE_GETCLR_STATS: + { + struct ng_bridge_link *link; + int linkNum; + + /* Get link number */ + if (msg->header.arglen != sizeof(u_int32_t)) { + error = EINVAL; + break; + } + linkNum = *((u_int32_t *)msg->data); + if (linkNum < 0 || linkNum >= NG_BRIDGE_MAX_LINKS) { + error = EINVAL; + break; + } + if ((link = priv->links[linkNum]) == NULL) { + error = ENOTCONN; + break; + } + + /* Get/clear stats */ + if (msg->header.cmd != NGM_BRIDGE_CLR_STATS) { + NG_MKRESPONSE(resp, msg, + sizeof(link->stats), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + bcopy(&link->stats, + resp->data, sizeof(link->stats)); + } + if (msg->header.cmd != NGM_BRIDGE_GET_STATS) + bzero(&link->stats, sizeof(link->stats)); + break; + } + case NGM_BRIDGE_GET_TABLE: + { + struct ng_bridge_host_ary *ary; + struct ng_bridge_hent *hent; + int i = 0, bucket; + + NG_MKRESPONSE(resp, msg, sizeof(*ary) + + (priv->numHosts * sizeof(*ary->hosts)), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + ary = (struct ng_bridge_host_ary *)resp->data; + ary->numHosts = priv->numHosts; + for (bucket = 0; bucket < priv->numBuckets; bucket++) { + SLIST_FOREACH(hent, &priv->tab[bucket], next) + ary->hosts[i++] = hent->host; + } + break; + } + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } + + /* Done */ + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive data on a hook + */ +static int +ng_bridge_rcvdata(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_bridge_host *host; + struct ng_bridge_link *link; + struct ether_header *eh; + int error = 0, linkNum, linksSeen; + int manycast; + struct mbuf *m; + struct ng_bridge_link *firstLink; + + NGI_GET_M(item, m); + /* Get link number */ + linkNum = (intptr_t)NG_HOOK_PRIVATE(hook); + KASSERT(linkNum >= 0 && linkNum < NG_BRIDGE_MAX_LINKS, + ("%s: linkNum=%u", __func__, linkNum)); + link = priv->links[linkNum]; + KASSERT(link != NULL, ("%s: link%d null", __func__, linkNum)); + + /* Sanity check packet and pull up header */ + if (m->m_pkthdr.len < ETHER_HDR_LEN) { + link->stats.recvRunts++; + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (EINVAL); + } + if (m->m_len < ETHER_HDR_LEN && !(m = m_pullup(m, ETHER_HDR_LEN))) { + link->stats.memoryFailures++; + NG_FREE_ITEM(item); + return (ENOBUFS); + } + eh = mtod(m, struct ether_header *); + if ((eh->ether_shost[0] & 1) != 0) { + link->stats.recvInvalid++; + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (EINVAL); + } + + /* Is link disabled due to a loopback condition? */ + if (link->loopCount != 0) { + link->stats.loopDrops++; + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (ELOOP); /* XXX is this an appropriate error? */ + } + + /* Update stats */ + link->stats.recvPackets++; + link->stats.recvOctets += m->m_pkthdr.len; + if ((manycast = (eh->ether_dhost[0] & 1)) != 0) { + if (ETHER_EQUAL(eh->ether_dhost, ng_bridge_bcast_addr)) { + link->stats.recvBroadcasts++; + manycast = 2; + } else + link->stats.recvMulticasts++; + } + + /* Look up packet's source Ethernet address in hashtable */ + if ((host = ng_bridge_get(priv, eh->ether_shost)) != NULL) { + + /* Update time since last heard from this host */ + host->staleness = 0; + + /* Did host jump to a different link? */ + if (host->linkNum != linkNum) { + + /* + * If the host's old link was recently established + * on the old link and it's already jumped to a new + * link, declare a loopback condition. + */ + if (host->age < priv->conf.minStableAge) { + + /* Log the problem */ + if (priv->conf.debugLevel >= 2) { + struct ifnet *ifp = m->m_pkthdr.rcvif; + char suffix[32]; + + if (ifp != NULL) + snprintf(suffix, sizeof(suffix), + " (%s)", ifp->if_xname); + else + *suffix = '\0'; + log(LOG_WARNING, "ng_bridge: %s:" + " loopback detected on %s%s\n", + ng_bridge_nodename(node), + NG_HOOK_NAME(hook), suffix); + } + + /* Mark link as linka non grata */ + link->loopCount = priv->conf.loopTimeout; + link->stats.loopDetects++; + + /* Forget all hosts on this link */ + ng_bridge_remove_hosts(priv, linkNum); + + /* Drop packet */ + link->stats.loopDrops++; + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (ELOOP); /* XXX appropriate? */ + } + + /* Move host over to new link */ + host->linkNum = linkNum; + host->age = 0; + } + } else { + if (!ng_bridge_put(priv, eh->ether_shost, linkNum)) { + link->stats.memoryFailures++; + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (ENOMEM); + } + } + + /* Run packet through ipfw processing, if enabled */ +#if 0 + if (priv->conf.ipfw[linkNum] && fw_enable && ip_fw_chk_ptr != NULL) { + /* XXX not implemented yet */ + } +#endif + + /* + * If unicast and destination host known, deliver to host's link, + * unless it is the same link as the packet came in on. + */ + if (!manycast) { + + /* Determine packet destination link */ + if ((host = ng_bridge_get(priv, eh->ether_dhost)) != NULL) { + struct ng_bridge_link *const destLink + = priv->links[host->linkNum]; + + /* If destination same as incoming link, do nothing */ + KASSERT(destLink != NULL, + ("%s: link%d null", __func__, host->linkNum)); + if (destLink == link) { + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (0); + } + + /* Deliver packet out the destination link */ + destLink->stats.xmitPackets++; + destLink->stats.xmitOctets += m->m_pkthdr.len; + NG_FWD_NEW_DATA(error, item, destLink->hook, m); + return (error); + } + + /* Destination host is not known */ + link->stats.recvUnknown++; + } + + /* Distribute unknown, multicast, broadcast pkts to all other links */ + firstLink = NULL; + for (linkNum = linksSeen = 0; linksSeen <= priv->numLinks; linkNum++) { + struct ng_bridge_link *destLink; + struct mbuf *m2 = NULL; + + /* + * If we have checked all the links then now + * send the original on its reserved link + */ + if (linksSeen == priv->numLinks) { + /* If we never saw a good link, leave. */ + if (firstLink == NULL) { + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (0); + } + destLink = firstLink; + } else { + destLink = priv->links[linkNum]; + if (destLink != NULL) + linksSeen++; + /* Skip incoming link and disconnected links */ + if (destLink == NULL || destLink == link) { + continue; + } + if (firstLink == NULL) { + /* + * This is the first usable link we have found. + * Reserve it for the originals. + * If we never find another we save a copy. + */ + firstLink = destLink; + continue; + } + + /* + * It's usable link but not the reserved (first) one. + * Copy mbuf info for sending. + */ + m2 = m_dup(m, M_DONTWAIT); /* XXX m_copypacket() */ + if (m2 == NULL) { + link->stats.memoryFailures++; + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (ENOBUFS); + } + } + + /* Update stats */ + destLink->stats.xmitPackets++; + destLink->stats.xmitOctets += m->m_pkthdr.len; + switch (manycast) { + case 0: /* unicast */ + break; + case 1: /* multicast */ + destLink->stats.xmitMulticasts++; + break; + case 2: /* broadcast */ + destLink->stats.xmitBroadcasts++; + break; + } + + /* Send packet */ + if (destLink == firstLink) { + /* + * If we've sent all the others, send the original + * on the first link we found. + */ + NG_FWD_NEW_DATA(error, item, destLink->hook, m); + break; /* always done last - not really needed. */ + } else { + NG_SEND_DATA_ONLY(error, destLink->hook, m2); + } + } + return (error); +} + +/* + * Shutdown node + */ +static int +ng_bridge_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + /* + * Shut down everything including the timer. Even if the + * callout has already been dequeued and is about to be + * run, ng_bridge_timeout() won't be fired as the node + * is already marked NGF_INVALID, so we're safe to free + * the node now. + */ + KASSERT(priv->numLinks == 0 && priv->numHosts == 0, + ("%s: numLinks=%d numHosts=%d", + __func__, priv->numLinks, priv->numHosts)); + ng_uncallout(&priv->timer, node); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + FREE(priv->tab, M_NETGRAPH_BRIDGE); + FREE(priv, M_NETGRAPH_BRIDGE); + return (0); +} + +/* + * Hook disconnection. + */ +static int +ng_bridge_disconnect(hook_p hook) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + int linkNum; + + /* Get link number */ + linkNum = (intptr_t)NG_HOOK_PRIVATE(hook); + KASSERT(linkNum >= 0 && linkNum < NG_BRIDGE_MAX_LINKS, + ("%s: linkNum=%u", __func__, linkNum)); + + /* Remove all hosts associated with this link */ + ng_bridge_remove_hosts(priv, linkNum); + + /* Free associated link information */ + KASSERT(priv->links[linkNum] != NULL, ("%s: no link", __func__)); + FREE(priv->links[linkNum], M_NETGRAPH_BRIDGE); + priv->links[linkNum] = NULL; + priv->numLinks--; + + /* If no more hooks, go away */ + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) { + ng_rmnode_self(NG_HOOK_NODE(hook)); + } + return (0); +} + +/****************************************************************** + HASH TABLE FUNCTIONS +******************************************************************/ + +/* + * Hash algorithm + */ +#define HASH(addr,mask) ( (((const u_int16_t *)(addr))[0] \ + ^ ((const u_int16_t *)(addr))[1] \ + ^ ((const u_int16_t *)(addr))[2]) & (mask) ) + +/* + * Find a host entry in the table. + */ +static struct ng_bridge_host * +ng_bridge_get(priv_p priv, const u_char *addr) +{ + const int bucket = HASH(addr, priv->hashMask); + struct ng_bridge_hent *hent; + + SLIST_FOREACH(hent, &priv->tab[bucket], next) { + if (ETHER_EQUAL(hent->host.addr, addr)) + return (&hent->host); + } + return (NULL); +} + +/* + * Add a new host entry to the table. This assumes the host doesn't + * already exist in the table. Returns 1 on success, 0 if there + * was a memory allocation failure. + */ +static int +ng_bridge_put(priv_p priv, const u_char *addr, int linkNum) +{ + const int bucket = HASH(addr, priv->hashMask); + struct ng_bridge_hent *hent; + +#ifdef INVARIANTS + /* Assert that entry does not already exist in hashtable */ + SLIST_FOREACH(hent, &priv->tab[bucket], next) { + KASSERT(!ETHER_EQUAL(hent->host.addr, addr), + ("%s: entry %6D exists in table", __func__, addr, ":")); + } +#endif + + /* Allocate and initialize new hashtable entry */ + MALLOC(hent, struct ng_bridge_hent *, + sizeof(*hent), M_NETGRAPH_BRIDGE, M_NOWAIT); + if (hent == NULL) + return (0); + bcopy(addr, hent->host.addr, ETHER_ADDR_LEN); + hent->host.linkNum = linkNum; + hent->host.staleness = 0; + hent->host.age = 0; + + /* Add new element to hash bucket */ + SLIST_INSERT_HEAD(&priv->tab[bucket], hent, next); + priv->numHosts++; + + /* Resize table if necessary */ + ng_bridge_rehash(priv); + return (1); +} + +/* + * Resize the hash table. We try to maintain the number of buckets + * such that the load factor is in the range 0.25 to 1.0. + * + * If we can't get the new memory then we silently fail. This is OK + * because things will still work and we'll try again soon anyway. + */ +static void +ng_bridge_rehash(priv_p priv) +{ + struct ng_bridge_bucket *newTab; + int oldBucket, newBucket; + int newNumBuckets; + u_int newMask; + + /* Is table too full or too empty? */ + if (priv->numHosts > priv->numBuckets + && (priv->numBuckets << 1) <= MAX_BUCKETS) + newNumBuckets = priv->numBuckets << 1; + else if (priv->numHosts < (priv->numBuckets >> 2) + && (priv->numBuckets >> 2) >= MIN_BUCKETS) + newNumBuckets = priv->numBuckets >> 2; + else + return; + newMask = newNumBuckets - 1; + + /* Allocate and initialize new table */ + MALLOC(newTab, struct ng_bridge_bucket *, + newNumBuckets * sizeof(*newTab), M_NETGRAPH_BRIDGE, M_NOWAIT | M_ZERO); + if (newTab == NULL) + return; + + /* Move all entries from old table to new table */ + for (oldBucket = 0; oldBucket < priv->numBuckets; oldBucket++) { + struct ng_bridge_bucket *const oldList = &priv->tab[oldBucket]; + + while (!SLIST_EMPTY(oldList)) { + struct ng_bridge_hent *const hent + = SLIST_FIRST(oldList); + + SLIST_REMOVE_HEAD(oldList, next); + newBucket = HASH(hent->host.addr, newMask); + SLIST_INSERT_HEAD(&newTab[newBucket], hent, next); + } + } + + /* Replace old table with new one */ + if (priv->conf.debugLevel >= 3) { + log(LOG_INFO, "ng_bridge: %s: table size %d -> %d\n", + ng_bridge_nodename(priv->node), + priv->numBuckets, newNumBuckets); + } + FREE(priv->tab, M_NETGRAPH_BRIDGE); + priv->numBuckets = newNumBuckets; + priv->hashMask = newMask; + priv->tab = newTab; + return; +} + +/****************************************************************** + MISC FUNCTIONS +******************************************************************/ + +/* + * Remove all hosts associated with a specific link from the hashtable. + * If linkNum == -1, then remove all hosts in the table. + */ +static void +ng_bridge_remove_hosts(priv_p priv, int linkNum) +{ + int bucket; + + for (bucket = 0; bucket < priv->numBuckets; bucket++) { + struct ng_bridge_hent **hptr = &SLIST_FIRST(&priv->tab[bucket]); + + while (*hptr != NULL) { + struct ng_bridge_hent *const hent = *hptr; + + if (linkNum == -1 || hent->host.linkNum == linkNum) { + *hptr = SLIST_NEXT(hent, next); + FREE(hent, M_NETGRAPH_BRIDGE); + priv->numHosts--; + } else + hptr = &SLIST_NEXT(hent, next); + } + } +} + +/* + * Handle our once-per-second timeout event. We do two things: + * we decrement link->loopCount for those links being muted due to + * a detected loopback condition, and we remove any hosts from + * the hashtable whom we haven't heard from in a long while. + */ +static void +ng_bridge_timeout(node_p node, hook_p hook, void *arg1, int arg2) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + int bucket; + int counter = 0; + int linkNum; + + /* Update host time counters and remove stale entries */ + for (bucket = 0; bucket < priv->numBuckets; bucket++) { + struct ng_bridge_hent **hptr = &SLIST_FIRST(&priv->tab[bucket]); + + while (*hptr != NULL) { + struct ng_bridge_hent *const hent = *hptr; + + /* Make sure host's link really exists */ + KASSERT(priv->links[hent->host.linkNum] != NULL, + ("%s: host %6D on nonexistent link %d\n", + __func__, hent->host.addr, ":", + hent->host.linkNum)); + + /* Remove hosts we haven't heard from in a while */ + if (++hent->host.staleness >= priv->conf.maxStaleness) { + *hptr = SLIST_NEXT(hent, next); + FREE(hent, M_NETGRAPH_BRIDGE); + priv->numHosts--; + } else { + if (hent->host.age < 0xffff) + hent->host.age++; + hptr = &SLIST_NEXT(hent, next); + counter++; + } + } + } + KASSERT(priv->numHosts == counter, + ("%s: hosts: %d != %d", __func__, priv->numHosts, counter)); + + /* Decrease table size if necessary */ + ng_bridge_rehash(priv); + + /* Decrease loop counter on muted looped back links */ + for (counter = linkNum = 0; linkNum < NG_BRIDGE_MAX_LINKS; linkNum++) { + struct ng_bridge_link *const link = priv->links[linkNum]; + + if (link != NULL) { + if (link->loopCount != 0) { + link->loopCount--; + if (link->loopCount == 0 + && priv->conf.debugLevel >= 2) { + log(LOG_INFO, "ng_bridge: %s:" + " restoring looped back link%d\n", + ng_bridge_nodename(node), linkNum); + } + } + counter++; + } + } + KASSERT(priv->numLinks == counter, + ("%s: links: %d != %d", __func__, priv->numLinks, counter)); + + /* Register a new timeout, keeping the existing node reference */ + ng_callout(&priv->timer, node, NULL, hz, ng_bridge_timeout, NULL, 0); +} + +/* + * Return node's "name", even if it doesn't have one. + */ +static const char * +ng_bridge_nodename(node_p node) +{ + static char name[NG_NODESIZ]; + + if (NG_NODE_NAME(node) != NULL) + snprintf(name, sizeof(name), "%s", NG_NODE_NAME(node)); + else + snprintf(name, sizeof(name), "[%x]", ng_node2ID(node)); + return name; +} + diff --git a/sys/netgraph7/ng_bridge.h b/sys/netgraph7/ng_bridge.h new file mode 100644 index 0000000000..66685fd661 --- /dev/null +++ b/sys/netgraph7/ng_bridge.h @@ -0,0 +1,155 @@ +/* + * ng_bridge.h + */ + +/*- + * Copyright (c) 2000 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_bridge.h,v 1.4 2005/10/28 14:41:28 ru Exp $ + */ + +#ifndef _NETGRAPH_NG_BRIDGE_H_ +#define _NETGRAPH_NG_BRIDGE_H_ + +/* Node type name and magic cookie */ +#define NG_BRIDGE_NODE_TYPE "bridge" +#define NGM_BRIDGE_COOKIE 967239368 + +/* Hook names */ +#define NG_BRIDGE_HOOK_LINK_PREFIX "link" /* append decimal integer */ +#define NG_BRIDGE_HOOK_LINK_FMT "link%d" /* for use with printf(3) */ + +/* Maximum number of supported links */ +#define NG_BRIDGE_MAX_LINKS 32 + +/* Node configuration structure */ +struct ng_bridge_config { + u_char ipfw[NG_BRIDGE_MAX_LINKS]; /* enable ipfw */ + u_char debugLevel; /* debug level */ + u_int32_t loopTimeout; /* link loopback mute time */ + u_int32_t maxStaleness; /* max host age before nuking */ + u_int32_t minStableAge; /* min time for a stable host */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_BRIDGE_CONFIG_TYPE_INFO(ainfo) { \ + { "ipfw", (ainfo) }, \ + { "debugLevel", &ng_parse_uint8_type }, \ + { "loopTimeout", &ng_parse_uint32_type }, \ + { "maxStaleness", &ng_parse_uint32_type }, \ + { "minStableAge", &ng_parse_uint32_type }, \ + { NULL } \ +} + +/* Statistics structure (one for each link) */ +struct ng_bridge_link_stats { + u_int64_t recvOctets; /* total octets rec'd on link */ + u_int64_t recvPackets; /* total pkts rec'd on link */ + u_int64_t recvMulticasts; /* multicast pkts rec'd on link */ + u_int64_t recvBroadcasts; /* broadcast pkts rec'd on link */ + u_int64_t recvUnknown; /* pkts rec'd with unknown dest addr */ + u_int64_t recvRunts; /* pkts rec'd less than 14 bytes */ + u_int64_t recvInvalid; /* pkts rec'd with bogus source addr */ + u_int64_t xmitOctets; /* total octets xmit'd on link */ + u_int64_t xmitPackets; /* total pkts xmit'd on link */ + u_int64_t xmitMulticasts; /* multicast pkts xmit'd on link */ + u_int64_t xmitBroadcasts; /* broadcast pkts xmit'd on link */ + u_int64_t loopDrops; /* pkts dropped due to loopback */ + u_int64_t loopDetects; /* number of loop detections */ + u_int64_t memoryFailures; /* times couldn't get mem or mbuf */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_BRIDGE_STATS_TYPE_INFO { \ + { "recvOctets", &ng_parse_uint64_type }, \ + { "recvPackets", &ng_parse_uint64_type }, \ + { "recvMulticast", &ng_parse_uint64_type }, \ + { "recvBroadcast", &ng_parse_uint64_type }, \ + { "recvUnknown", &ng_parse_uint64_type }, \ + { "recvRunts", &ng_parse_uint64_type }, \ + { "recvInvalid", &ng_parse_uint64_type }, \ + { "xmitOctets", &ng_parse_uint64_type }, \ + { "xmitPackets", &ng_parse_uint64_type }, \ + { "xmitMulticasts", &ng_parse_uint64_type }, \ + { "xmitBroadcasts", &ng_parse_uint64_type }, \ + { "loopDrops", &ng_parse_uint64_type }, \ + { "loopDetects", &ng_parse_uint64_type }, \ + { "memoryFailures", &ng_parse_uint64_type }, \ + { NULL } \ +} + +/* Structure describing a single host */ +struct ng_bridge_host { + u_char addr[6]; /* ethernet address */ + u_int16_t linkNum; /* link where addr can be found */ + u_int16_t age; /* seconds ago entry was created */ + u_int16_t staleness; /* seconds ago host last heard from */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_BRIDGE_HOST_TYPE_INFO(entype) { \ + { "addr", (entype) }, \ + { "linkNum", &ng_parse_uint16_type }, \ + { "age", &ng_parse_uint16_type }, \ + { "staleness", &ng_parse_uint16_type }, \ + { NULL } \ +} + +/* Structure returned by NGM_BRIDGE_GET_TABLE */ +struct ng_bridge_host_ary { + u_int32_t numHosts; + struct ng_bridge_host hosts[]; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_BRIDGE_HOST_ARY_TYPE_INFO(harytype) { \ + { "numHosts", &ng_parse_uint32_type }, \ + { "hosts", (harytype) }, \ + { NULL } \ +} + +/* Netgraph control messages */ +enum { + NGM_BRIDGE_SET_CONFIG = 1, /* set node configuration */ + NGM_BRIDGE_GET_CONFIG, /* get node configuration */ + NGM_BRIDGE_RESET, /* reset (forget) all information */ + NGM_BRIDGE_GET_STATS, /* get link stats */ + NGM_BRIDGE_CLR_STATS, /* clear link stats */ + NGM_BRIDGE_GETCLR_STATS, /* atomically get & clear link stats */ + NGM_BRIDGE_GET_TABLE, /* get link table */ +}; + +#endif /* _NETGRAPH_NG_BRIDGE_H_ */ + diff --git a/sys/netgraph7/ng_car.c b/sys/netgraph7/ng_car.c new file mode 100644 index 0000000000..27cf8a1e91 --- /dev/null +++ b/sys/netgraph7/ng_car.c @@ -0,0 +1,766 @@ +/*- + * Copyright (c) 2005 Nuno Antunes + * Copyright (c) 2007 Alexander Motin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_car.c,v 1.7 2008/03/30 07:53:51 mav Exp $ + */ + +/* + * ng_car - An implementation of commited access rate for netgraph + * + * TODO: + * - Sanitize input config values (impose some limits) + * - Implement internal packet painting (possibly using mbuf tags) + * - Implement color-aware mode + * - Implement DSCP marking for IPv4 + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define NG_CAR_QUEUE_SIZE 100 /* Maximum queue size for SHAPE mode */ +#define NG_CAR_QUEUE_MIN_TH 8 /* Minimum RED threshhold for SHAPE mode */ + +/* Hook private info */ +struct hookinfo { + hook_p hook; /* this (source) hook */ + hook_p dest; /* destination hook */ + + int64_t tc; /* commited token bucket counter */ + int64_t te; /* exceeded/peak token bucket counter */ + struct bintime lastRefill; /* last token refill time */ + + struct ng_car_hookconf conf; /* hook configuration */ + struct ng_car_hookstats stats; /* hook stats */ + + struct mbuf *q[NG_CAR_QUEUE_SIZE]; /* circular packet queue */ + u_int q_first; /* first queue element */ + u_int q_last; /* last queue element */ + struct callout q_callout; /* periodic queue processing routine */ + struct mtx q_mtx; /* queue mutex */ +}; + +/* Private information for each node instance */ +struct privdata { + node_p node; /* the node itself */ + struct hookinfo upper; /* hook to upper layers */ + struct hookinfo lower; /* hook to lower layers */ +}; +typedef struct privdata *priv_p; + +static ng_constructor_t ng_car_constructor; +static ng_rcvmsg_t ng_car_rcvmsg; +static ng_shutdown_t ng_car_shutdown; +static ng_newhook_t ng_car_newhook; +static ng_rcvdata_t ng_car_rcvdata; +static ng_disconnect_t ng_car_disconnect; + +static void ng_car_refillhook(struct hookinfo *h); +static void ng_car_schedule(struct hookinfo *h); +void ng_car_q_event(node_p node, hook_p hook, void *arg, int arg2); +static void ng_car_enqueue(struct hookinfo *h, item_p item); + +/* Parse type for struct ng_car_hookstats */ +static const struct ng_parse_struct_field ng_car_hookstats_type_fields[] + = NG_CAR_HOOKSTATS; +static const struct ng_parse_type ng_car_hookstats_type = { + &ng_parse_struct_type, + &ng_car_hookstats_type_fields +}; + +/* Parse type for struct ng_car_bulkstats */ +static const struct ng_parse_struct_field ng_car_bulkstats_type_fields[] + = NG_CAR_BULKSTATS(&ng_car_hookstats_type); +static const struct ng_parse_type ng_car_bulkstats_type = { + &ng_parse_struct_type, + &ng_car_bulkstats_type_fields +}; + +/* Parse type for struct ng_car_hookconf */ +static const struct ng_parse_struct_field ng_car_hookconf_type_fields[] + = NG_CAR_HOOKCONF; +static const struct ng_parse_type ng_car_hookconf_type = { + &ng_parse_struct_type, + &ng_car_hookconf_type_fields +}; + +/* Parse type for struct ng_car_bulkconf */ +static const struct ng_parse_struct_field ng_car_bulkconf_type_fields[] + = NG_CAR_BULKCONF(&ng_car_hookconf_type); +static const struct ng_parse_type ng_car_bulkconf_type = { + &ng_parse_struct_type, + &ng_car_bulkconf_type_fields +}; + +/* Command list */ +static struct ng_cmdlist ng_car_cmdlist[] = { + { + NGM_CAR_COOKIE, + NGM_CAR_GET_STATS, + "getstats", + NULL, + &ng_car_bulkstats_type, + }, + { + NGM_CAR_COOKIE, + NGM_CAR_CLR_STATS, + "clrstats", + NULL, + NULL, + }, + { + NGM_CAR_COOKIE, + NGM_CAR_GETCLR_STATS, + "getclrstats", + NULL, + &ng_car_bulkstats_type, + }, + + { + NGM_CAR_COOKIE, + NGM_CAR_GET_CONF, + "getconf", + NULL, + &ng_car_bulkconf_type, + }, + { + NGM_CAR_COOKIE, + NGM_CAR_SET_CONF, + "setconf", + &ng_car_bulkconf_type, + NULL, + }, + { 0 } +}; + +/* Netgraph node type descriptor */ +static struct ng_type ng_car_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_CAR_NODE_TYPE, + .constructor = ng_car_constructor, + .rcvmsg = ng_car_rcvmsg, + .shutdown = ng_car_shutdown, + .newhook = ng_car_newhook, + .rcvdata = ng_car_rcvdata, + .disconnect = ng_car_disconnect, + .cmdlist = ng_car_cmdlist, +}; +NETGRAPH_INIT(car, &ng_car_typestruct); + +/* + * Node constructor + */ +static int +ng_car_constructor(node_p node) +{ + priv_p priv; + + /* Initialize private descriptor. */ + priv = malloc(sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + + NG_NODE_SET_PRIVATE(node, priv); + priv->node = node; + + /* + * Arbitrary default values + */ + + priv->upper.hook = NULL; + priv->upper.dest = NULL; + priv->upper.tc = priv->upper.conf.cbs = NG_CAR_CBS_MIN; + priv->upper.te = priv->upper.conf.ebs = NG_CAR_EBS_MIN; + priv->upper.conf.cir = NG_CAR_CIR_DFLT; + priv->upper.conf.green_action = NG_CAR_ACTION_FORWARD; + priv->upper.conf.yellow_action = NG_CAR_ACTION_FORWARD; + priv->upper.conf.red_action = NG_CAR_ACTION_DROP; + priv->upper.conf.mode = 0; + getbinuptime(&priv->upper.lastRefill); + priv->upper.q_first = 0; + priv->upper.q_last = 0; + ng_callout_init(&priv->upper.q_callout); + mtx_init(&priv->upper.q_mtx, "ng_car_u", NULL, MTX_DEF); + + priv->lower.hook = NULL; + priv->lower.dest = NULL; + priv->lower.tc = priv->lower.conf.cbs = NG_CAR_CBS_MIN; + priv->lower.te = priv->lower.conf.ebs = NG_CAR_EBS_MIN; + priv->lower.conf.cir = NG_CAR_CIR_DFLT; + priv->lower.conf.green_action = NG_CAR_ACTION_FORWARD; + priv->lower.conf.yellow_action = NG_CAR_ACTION_FORWARD; + priv->lower.conf.red_action = NG_CAR_ACTION_DROP; + priv->lower.conf.mode = 0; + priv->lower.lastRefill = priv->upper.lastRefill; + priv->lower.q_first = 0; + priv->lower.q_last = 0; + ng_callout_init(&priv->lower.q_callout); + mtx_init(&priv->lower.q_mtx, "ng_car_l", NULL, MTX_DEF); + + return (0); +} + +/* + * Add a hook. + */ +static int +ng_car_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (strcmp(name, NG_CAR_HOOK_LOWER) == 0) { + priv->lower.hook = hook; + priv->upper.dest = hook; + bzero(&priv->lower.stats, sizeof(priv->lower.stats)); + NG_HOOK_SET_PRIVATE(hook, &priv->lower); + } else if (strcmp(name, NG_CAR_HOOK_UPPER) == 0) { + priv->upper.hook = hook; + priv->lower.dest = hook; + bzero(&priv->upper.stats, sizeof(priv->upper.stats)); + NG_HOOK_SET_PRIVATE(hook, &priv->upper); + } else + return (EINVAL); + return(0); +} + +/* + * Data has arrived. + */ +static int +ng_car_rcvdata(hook_p hook, item_p item ) +{ + struct hookinfo *const hinfo = NG_HOOK_PRIVATE(hook); + struct mbuf *m; + int error = 0; + u_int len; + + /* If queue is not empty now then enqueue packet. */ + if (hinfo->q_first != hinfo->q_last) { + ng_car_enqueue(hinfo, item); + return (0); + } + + m = NGI_M(item); + +#define NG_CAR_PERFORM_MATCH_ACTION(a) \ + do { \ + switch (a) { \ + case NG_CAR_ACTION_FORWARD: \ + /* Do nothing. */ \ + break; \ + case NG_CAR_ACTION_MARK: \ + /* XXX find a way to mark packets (mbuf tag?) */ \ + ++hinfo->stats.errors; \ + break; \ + case NG_CAR_ACTION_DROP: \ + default: \ + /* Drop packet and return. */ \ + NG_FREE_ITEM(item); \ + ++hinfo->stats.droped_pkts; \ + return (0); \ + } \ + } while (0) + + /* Packet is counted as 128 tokens for better resolution */ + if (hinfo->conf.opt & NG_CAR_COUNT_PACKETS) { + len = 128; + } else { + len = m->m_pkthdr.len; + } + + /* Check commited token bucket. */ + if (hinfo->tc - len >= 0) { + /* This packet is green. */ + ++hinfo->stats.green_pkts; + hinfo->tc -= len; + NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.green_action); + } else { + + /* Refill only if not green without it. */ + ng_car_refillhook(hinfo); + + /* Check commited token bucket again after refill. */ + if (hinfo->tc - len >= 0) { + /* This packet is green */ + ++hinfo->stats.green_pkts; + hinfo->tc -= len; + NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.green_action); + + /* If not green and mode is SHAPE, enqueue packet. */ + } else if (hinfo->conf.mode == NG_CAR_SHAPE) { + ng_car_enqueue(hinfo, item); + return (0); + + /* If not green and mode is RED, calculate probability. */ + } else if (hinfo->conf.mode == NG_CAR_RED) { + /* Is packet is bigger then extended burst? */ + if (len - (hinfo->tc - len) > hinfo->conf.ebs) { + /* This packet is definitely red. */ + ++hinfo->stats.red_pkts; + hinfo->te = 0; + NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.red_action); + + /* Use token bucket to simulate RED-like drop + probability. */ + } else if (hinfo->te + (len - hinfo->tc) < + hinfo->conf.ebs) { + /* This packet is yellow */ + ++hinfo->stats.yellow_pkts; + hinfo->te += len - hinfo->tc; + /* Go to negative tokens. */ + hinfo->tc -= len; + NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.yellow_action); + } else { + /* This packet is probaly red. */ + ++hinfo->stats.red_pkts; + hinfo->te = 0; + NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.red_action); + } + /* If not green and mode is SINGLE/DOUBLE RATE. */ + } else { + /* Check extended token bucket. */ + if (hinfo->te - len >= 0) { + /* This packet is yellow */ + ++hinfo->stats.yellow_pkts; + hinfo->te -= len; + NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.yellow_action); + } else { + /* This packet is red */ + ++hinfo->stats.red_pkts; + NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.red_action); + } + } + } + +#undef NG_CAR_PERFORM_MATCH_ACTION + + NG_FWD_ITEM_HOOK(error, item, hinfo->dest); + if (error != 0) + ++hinfo->stats.errors; + ++hinfo->stats.passed_pkts; + + return (error); +} + +/* + * Receive a control message. + */ +static int +ng_car_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_CAR_COOKIE: + switch (msg->header.cmd) { + case NGM_CAR_GET_STATS: + case NGM_CAR_GETCLR_STATS: + { + struct ng_car_bulkstats *bstats; + + NG_MKRESPONSE(resp, msg, + sizeof(*bstats), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + bstats = (struct ng_car_bulkstats *)resp->data; + + bcopy(&priv->upper.stats, &bstats->downstream, + sizeof(bstats->downstream)); + bcopy(&priv->lower.stats, &bstats->upstream, + sizeof(bstats->upstream)); + } + if (msg->header.cmd == NGM_CAR_GET_STATS) + break; + case NGM_CAR_CLR_STATS: + bzero(&priv->upper.stats, + sizeof(priv->upper.stats)); + bzero(&priv->lower.stats, + sizeof(priv->lower.stats)); + break; + case NGM_CAR_GET_CONF: + { + struct ng_car_bulkconf *bconf; + + NG_MKRESPONSE(resp, msg, + sizeof(*bconf), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + bconf = (struct ng_car_bulkconf *)resp->data; + + bcopy(&priv->upper.conf, &bconf->downstream, + sizeof(bconf->downstream)); + bcopy(&priv->lower.conf, &bconf->upstream, + sizeof(bconf->upstream)); + /* Convert internal 1/(8*128) of pps into pps */ + if (bconf->downstream.opt & NG_CAR_COUNT_PACKETS) { + bconf->downstream.cir /= 1024; + bconf->downstream.pir /= 1024; + bconf->downstream.cbs /= 128; + bconf->downstream.ebs /= 128; + } + if (bconf->upstream.opt & NG_CAR_COUNT_PACKETS) { + bconf->upstream.cir /= 1024; + bconf->upstream.pir /= 1024; + bconf->upstream.cbs /= 128; + bconf->upstream.ebs /= 128; + } + } + break; + case NGM_CAR_SET_CONF: + { + struct ng_car_bulkconf *const bconf = + (struct ng_car_bulkconf *)msg->data; + + /* Check for invalid or illegal config. */ + if (msg->header.arglen != sizeof(*bconf)) { + error = EINVAL; + break; + } + /* Convert pps into internal 1/(8*128) of pps */ + if (bconf->downstream.opt & NG_CAR_COUNT_PACKETS) { + bconf->downstream.cir *= 1024; + bconf->downstream.pir *= 1024; + bconf->downstream.cbs *= 125; + bconf->downstream.ebs *= 125; + } + if (bconf->upstream.opt & NG_CAR_COUNT_PACKETS) { + bconf->upstream.cir *= 1024; + bconf->upstream.pir *= 1024; + bconf->upstream.cbs *= 125; + bconf->upstream.ebs *= 125; + } + if ((bconf->downstream.cir > 1000000000) || + (bconf->downstream.pir > 1000000000) || + (bconf->upstream.cir > 1000000000) || + (bconf->upstream.pir > 1000000000) || + (bconf->downstream.cbs == 0 && + bconf->downstream.ebs == 0) || + (bconf->upstream.cbs == 0 && + bconf->upstream.ebs == 0)) + { + error = EINVAL; + break; + } + if ((bconf->upstream.mode == NG_CAR_SHAPE) && + (bconf->upstream.cir == 0)) { + error = EINVAL; + break; + } + if ((bconf->downstream.mode == NG_CAR_SHAPE) && + (bconf->downstream.cir == 0)) { + error = EINVAL; + break; + } + + /* Copy downstream config. */ + bcopy(&bconf->downstream, &priv->upper.conf, + sizeof(priv->upper.conf)); + priv->upper.tc = priv->upper.conf.cbs; + if (priv->upper.conf.mode == NG_CAR_RED || + priv->upper.conf.mode == NG_CAR_SHAPE) { + priv->upper.te = 0; + } else { + priv->upper.te = priv->upper.conf.ebs; + } + + /* Copy upstream config. */ + bcopy(&bconf->upstream, &priv->lower.conf, + sizeof(priv->lower.conf)); + priv->lower.tc = priv->lower.conf.cbs; + if (priv->lower.conf.mode == NG_CAR_RED || + priv->lower.conf.mode == NG_CAR_SHAPE) { + priv->lower.te = 0; + } else { + priv->lower.te = priv->lower.conf.ebs; + } + } + break; + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Do local shutdown processing. + */ +static int +ng_car_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + ng_uncallout(&priv->upper.q_callout, node); + ng_uncallout(&priv->lower.q_callout, node); + mtx_destroy(&priv->upper.q_mtx); + mtx_destroy(&priv->lower.q_mtx); + NG_NODE_UNREF(priv->node); + free(priv, M_NETGRAPH); + return (0); +} + +/* + * Hook disconnection. + * + * For this type, removal of the last link destroys the node. + */ +static int +ng_car_disconnect(hook_p hook) +{ + struct hookinfo *const hinfo = NG_HOOK_PRIVATE(hook); + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + + if (hinfo) { + /* Purge queue if not empty. */ + while (hinfo->q_first != hinfo->q_last) { + NG_FREE_M(hinfo->q[hinfo->q_first]); + hinfo->q_first++; + if (hinfo->q_first >= NG_CAR_QUEUE_SIZE) + hinfo->q_first = 0; + } + /* Remove hook refs. */ + if (hinfo->hook == priv->upper.hook) + priv->lower.dest = NULL; + else + priv->upper.dest = NULL; + hinfo->hook = NULL; + } + /* Already shutting down? */ + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) + ng_rmnode_self(NG_HOOK_NODE(hook)); + return (0); +} + +/* + * Hook's token buckets refillment. + */ +static void +ng_car_refillhook(struct hookinfo *h) +{ + struct bintime newt, deltat; + unsigned int deltat_us; + + /* Get current time. */ + getbinuptime(&newt); + + /* Get time delta since last refill. */ + deltat = newt; + bintime_sub(&deltat, &h->lastRefill); + + /* Time must go forward. */ + if (deltat.sec < 0) { + h->lastRefill = newt; + return; + } + + /* But not too far forward. */ + if (deltat.sec >= 1000) { + deltat_us = (1000 << 20); + } else { + /* convert bintime to the 1/(2^20) of sec */ + deltat_us = (deltat.sec << 20) + (deltat.frac >> 44); + } + + if (h->conf.mode == NG_CAR_SINGLE_RATE) { + int64_t delta; + /* Refill commited token bucket. */ + h->tc += (h->conf.cir * deltat_us) >> 23; + delta = h->tc - h->conf.cbs; + if (delta > 0) { + h->tc = h->conf.cbs; + + /* Refill exceeded token bucket. */ + h->te += delta; + if (h->te > ((int64_t)h->conf.ebs)) + h->te = h->conf.ebs; + } + + } else if (h->conf.mode == NG_CAR_DOUBLE_RATE) { + /* Refill commited token bucket. */ + h->tc += (h->conf.cir * deltat_us) >> 23; + if (h->tc > ((int64_t)h->conf.cbs)) + h->tc = h->conf.cbs; + + /* Refill peak token bucket. */ + h->te += (h->conf.pir * deltat_us) >> 23; + if (h->te > ((int64_t)h->conf.ebs)) + h->te = h->conf.ebs; + + } else { /* RED or SHAPE mode. */ + /* Refill commited token bucket. */ + h->tc += (h->conf.cir * deltat_us) >> 23; + if (h->tc > ((int64_t)h->conf.cbs)) + h->tc = h->conf.cbs; + } + + /* Remember this moment. */ + h->lastRefill = newt; +} + +/* + * Schedule callout when we will have required tokens. + */ +static void +ng_car_schedule(struct hookinfo *hinfo) +{ + int delay; + + delay = (-(hinfo->tc)) * hz * 8 / hinfo->conf.cir + 1; + + ng_callout(&hinfo->q_callout, NG_HOOK_NODE(hinfo->hook), hinfo->hook, + delay, &ng_car_q_event, NULL, 0); +} + +/* + * Queue processing callout handler. + */ +void +ng_car_q_event(node_p node, hook_p hook, void *arg, int arg2) +{ + struct hookinfo *hinfo = NG_HOOK_PRIVATE(hook); + struct mbuf *m; + int error; + + /* Refill tokens for time we have slept. */ + ng_car_refillhook(hinfo); + + /* If we have some tokens */ + while (hinfo->tc >= 0) { + + /* Send packet. */ + m = hinfo->q[hinfo->q_first]; + NG_SEND_DATA_ONLY(error, hinfo->dest, m); + if (error != 0) + ++hinfo->stats.errors; + ++hinfo->stats.passed_pkts; + + /* Get next one. */ + hinfo->q_first++; + if (hinfo->q_first >= NG_CAR_QUEUE_SIZE) + hinfo->q_first = 0; + + /* Stop if none left. */ + if (hinfo->q_first == hinfo->q_last) + break; + + /* If we have more packet, try it. */ + m = hinfo->q[hinfo->q_first]; + if (hinfo->conf.opt & NG_CAR_COUNT_PACKETS) { + hinfo->tc -= 128; + } else { + hinfo->tc -= m->m_pkthdr.len; + } + } + + /* If something left */ + if (hinfo->q_first != hinfo->q_last) + /* Schedule queue processing. */ + ng_car_schedule(hinfo); +} + +/* + * Enqueue packet. + */ +static void +ng_car_enqueue(struct hookinfo *hinfo, item_p item) +{ + struct mbuf *m; + int len; + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + /* Lock queue mutex. */ + mtx_lock(&hinfo->q_mtx); + + /* Calculate used queue length. */ + len = hinfo->q_last - hinfo->q_first; + if (len < 0) + len += NG_CAR_QUEUE_SIZE; + + /* If queue is overflowed or we have no RED tokens. */ + if ((len >= (NG_CAR_QUEUE_SIZE - 1)) || + (hinfo->te + len >= NG_CAR_QUEUE_SIZE)) { + /* Drop packet. */ + ++hinfo->stats.red_pkts; + ++hinfo->stats.droped_pkts; + NG_FREE_M(m); + + hinfo->te = 0; + } else { + /* This packet is yellow. */ + ++hinfo->stats.yellow_pkts; + + /* Enqueue packet. */ + hinfo->q[hinfo->q_last] = m; + hinfo->q_last++; + if (hinfo->q_last >= NG_CAR_QUEUE_SIZE) + hinfo->q_last = 0; + + /* Use RED tokens. */ + if (len > NG_CAR_QUEUE_MIN_TH) + hinfo->te += len - NG_CAR_QUEUE_MIN_TH; + + /* If this is a first packet in the queue. */ + if (len == 0) { + if (hinfo->conf.opt & NG_CAR_COUNT_PACKETS) { + hinfo->tc -= 128; + } else { + hinfo->tc -= m->m_pkthdr.len; + } + + /* Schedule queue processing. */ + ng_car_schedule(hinfo); + } + } + + /* Unlock queue mutex. */ + mtx_unlock(&hinfo->q_mtx); +} diff --git a/sys/netgraph7/ng_car.h b/sys/netgraph7/ng_car.h new file mode 100644 index 0000000000..596a13839c --- /dev/null +++ b/sys/netgraph7/ng_car.h @@ -0,0 +1,140 @@ +/*- + * Copyright (c) 2005 Nuno Antunes + * Copyright (c) 2007 Alexander Motin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_car.h,v 1.2 2007/12/19 22:50:14 mav Exp $ + */ + +#ifndef _NETGRAPH_NG_CAR_H_ +#define _NETGRAPH_NG_CAR_H_ + +#define NG_CAR_NODE_TYPE "car" +#define NGM_CAR_COOKIE 1173648034 + +/* Hook names */ +#define NG_CAR_HOOK_UPPER "upper" +#define NG_CAR_HOOK_LOWER "lower" + +/* Per hook statistics counters */ +struct ng_car_hookstats { + u_int64_t passed_pkts; /* Counter for passed packets */ + u_int64_t droped_pkts; /* Counter for droped packets */ + u_int64_t green_pkts; /* Counter for green packets */ + u_int64_t yellow_pkts; /* Counter for yellow packets */ + u_int64_t red_pkts; /* Counter for red packets */ + u_int64_t errors; /* Counter for operation errors */ +}; +#define NG_CAR_HOOKSTATS { \ + { "passed", &ng_parse_uint64_type }, \ + { "droped", &ng_parse_uint64_type }, \ + { "green", &ng_parse_uint64_type }, \ + { "yellow", &ng_parse_uint64_type }, \ + { "red", &ng_parse_uint64_type }, \ + { "errors", &ng_parse_uint64_type }, \ + { NULL } \ +} + +/* Bulk statistics */ +struct ng_car_bulkstats { + struct ng_car_hookstats upstream; + struct ng_car_hookstats downstream; +}; +#define NG_CAR_BULKSTATS(hstatstype) { \ + { "upstream", (hstatstype) }, \ + { "downstream", (hstatstype) }, \ + { NULL } \ +} + +/* Per hook configuration */ +struct ng_car_hookconf { + u_int64_t cbs; /* Commited burst size (bytes) */ + u_int64_t ebs; /* Exceeded/Peak burst size (bytes) */ + u_int64_t cir; /* Commited information rate (bits/s) */ + u_int64_t pir; /* Peak information rate (bits/s) */ + u_int8_t green_action; /* Action for green packets */ + u_int8_t yellow_action; /* Action for yellow packets */ + u_int8_t red_action; /* Action for red packets */ + u_int8_t mode; /* single/double rate, ... */ + u_int8_t opt; /* color-aware or color-blind */ +}; +/* Keep this definition in sync with the above structure */ +#define NG_CAR_HOOKCONF { \ + { "cbs", &ng_parse_uint64_type }, \ + { "ebs", &ng_parse_uint64_type }, \ + { "cir", &ng_parse_uint64_type }, \ + { "pir", &ng_parse_uint64_type }, \ + { "greenAction", &ng_parse_uint8_type }, \ + { "yellowAction", &ng_parse_uint8_type }, \ + { "redAction", &ng_parse_uint8_type }, \ + { "mode", &ng_parse_uint8_type }, \ + { "opt", &ng_parse_uint8_type }, \ + { NULL } \ +} + +#define NG_CAR_CBS_MIN 8192 +#define NG_CAR_EBS_MIN 8192 +#define NG_CAR_CIR_DFLT 10240 + +/* possible actions (...Action) */ +enum { + NG_CAR_ACTION_FORWARD = 1, + NG_CAR_ACTION_DROP, + NG_CAR_ACTION_MARK, + NG_CAR_ACTION_SET_TOS +}; + +/* operation modes (mode) */ +enum { + NG_CAR_SINGLE_RATE = 0, + NG_CAR_DOUBLE_RATE, + NG_CAR_RED, + NG_CAR_SHAPE +}; + +/* mode options (opt) */ +#define NG_CAR_COLOR_AWARE 1 +#define NG_CAR_COUNT_PACKETS 2 + +/* Bulk config */ +struct ng_car_bulkconf { + struct ng_car_hookconf upstream; + struct ng_car_hookconf downstream; +}; +#define NG_CAR_BULKCONF(hconftype) { \ + { "upstream", (hconftype) }, \ + { "downstream", (hconftype) }, \ + { NULL } \ +} + +/* Commands */ +enum { + NGM_CAR_GET_STATS = 1, /* Get statistics */ + NGM_CAR_CLR_STATS, /* Clear statistics */ + NGM_CAR_GETCLR_STATS, /* Get and clear statistics */ + NGM_CAR_GET_CONF, /* Get bulk configuration */ + NGM_CAR_SET_CONF, /* Set bulk configuration */ +}; + +#endif /* _NETGRAPH_NG_CAR_H_ */ diff --git a/sys/netgraph7/ng_cisco.c b/sys/netgraph7/ng_cisco.c new file mode 100644 index 0000000000..296230624b --- /dev/null +++ b/sys/netgraph7/ng_cisco.c @@ -0,0 +1,652 @@ +/* + * ng_cisco.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_cisco.c,v 1.29 2007/11/30 23:27:39 julian Exp $ + * $Whistle: ng_cisco.c,v 1.25 1999/11/01 09:24:51 julian Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#define CISCO_MULTICAST 0x8f /* Cisco multicast address */ +#define CISCO_UNICAST 0x0f /* Cisco unicast address */ +#define CISCO_KEEPALIVE 0x8035 /* Cisco keepalive protocol */ +#define CISCO_ADDR_REQ 0 /* Cisco address request */ +#define CISCO_ADDR_REPLY 1 /* Cisco address reply */ +#define CISCO_KEEPALIVE_REQ 2 /* Cisco keepalive request */ + +#define KEEPALIVE_SECS 10 + +struct cisco_header { + u_char address; + u_char control; + u_short protocol; +}; + +#define CISCO_HEADER_LEN sizeof (struct cisco_header) + +struct cisco_packet { + u_long type; + u_long par1; + u_long par2; + u_short rel; + u_short time0; + u_short time1; +}; + +#define CISCO_PACKET_LEN (sizeof(struct cisco_packet)) + +struct protoent { + hook_p hook; /* the hook for this proto */ + u_short af; /* address family, -1 = downstream */ +}; + +struct cisco_priv { + u_long local_seq; + u_long remote_seq; + u_long seqRetries; /* how many times we've been here throwing out + * the same sequence number without ack */ + node_p node; + struct callout handle; + struct protoent downstream; + struct protoent inet; /* IP information */ + struct in_addr localip; + struct in_addr localmask; + struct protoent inet6; /* IPv6 information */ + struct protoent atalk; /* AppleTalk information */ + struct protoent ipx; /* IPX information */ +}; +typedef struct cisco_priv *sc_p; + +/* Netgraph methods */ +static ng_constructor_t cisco_constructor; +static ng_rcvmsg_t cisco_rcvmsg; +static ng_shutdown_t cisco_shutdown; +static ng_newhook_t cisco_newhook; +static ng_rcvdata_t cisco_rcvdata; +static ng_disconnect_t cisco_disconnect; + +/* Other functions */ +static int cisco_input(sc_p sc, item_p item); +static void cisco_keepalive(node_p node, hook_p hook, void *arg1, int arg2); +static int cisco_send(sc_p sc, int type, long par1, long par2); +static void cisco_notify(sc_p sc, uint32_t cmd); + +/* Parse type for struct ng_cisco_ipaddr */ +static const struct ng_parse_struct_field ng_cisco_ipaddr_type_fields[] + = NG_CISCO_IPADDR_TYPE_INFO; +static const struct ng_parse_type ng_cisco_ipaddr_type = { + &ng_parse_struct_type, + &ng_cisco_ipaddr_type_fields +}; + +/* Parse type for struct ng_async_stat */ +static const struct ng_parse_struct_field ng_cisco_stats_type_fields[] + = NG_CISCO_STATS_TYPE_INFO; +static const struct ng_parse_type ng_cisco_stats_type = { + &ng_parse_struct_type, + &ng_cisco_stats_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_cisco_cmdlist[] = { + { + NGM_CISCO_COOKIE, + NGM_CISCO_SET_IPADDR, + "setipaddr", + &ng_cisco_ipaddr_type, + NULL + }, + { + NGM_CISCO_COOKIE, + NGM_CISCO_GET_IPADDR, + "getipaddr", + NULL, + &ng_cisco_ipaddr_type + }, + { + NGM_CISCO_COOKIE, + NGM_CISCO_GET_STATUS, + "getstats", + NULL, + &ng_cisco_stats_type + }, + { 0 } +}; + +/* Node type */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_CISCO_NODE_TYPE, + .constructor = cisco_constructor, + .rcvmsg = cisco_rcvmsg, + .shutdown = cisco_shutdown, + .newhook = cisco_newhook, + .rcvdata = cisco_rcvdata, + .disconnect = cisco_disconnect, + .cmdlist = ng_cisco_cmdlist, +}; +NETGRAPH_INIT(cisco, &typestruct); + +/* + * Node constructor + */ +static int +cisco_constructor(node_p node) +{ + sc_p sc; + + MALLOC(sc, sc_p, sizeof(*sc), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (sc == NULL) + return (ENOMEM); + + ng_callout_init(&sc->handle); + NG_NODE_SET_PRIVATE(node, sc); + sc->node = node; + + /* Initialise the varous protocol hook holders */ + sc->downstream.af = 0xffff; + sc->inet.af = AF_INET; + sc->inet6.af = AF_INET6; + sc->atalk.af = AF_APPLETALK; + sc->ipx.af = AF_IPX; + return (0); +} + +/* + * Check new hook + */ +static int +cisco_newhook(node_p node, hook_p hook, const char *name) +{ + const sc_p sc = NG_NODE_PRIVATE(node); + + if (strcmp(name, NG_CISCO_HOOK_DOWNSTREAM) == 0) { + sc->downstream.hook = hook; + NG_HOOK_SET_PRIVATE(hook, &sc->downstream); + + /* Start keepalives */ + ng_callout(&sc->handle, node, NULL, (hz * KEEPALIVE_SECS), + &cisco_keepalive, (void *)sc, 0); + } else if (strcmp(name, NG_CISCO_HOOK_INET) == 0) { + sc->inet.hook = hook; + NG_HOOK_SET_PRIVATE(hook, &sc->inet); + } else if (strcmp(name, NG_CISCO_HOOK_INET6) == 0) { + sc->inet6.hook = hook; + NG_HOOK_SET_PRIVATE(hook, &sc->inet6); + } else if (strcmp(name, NG_CISCO_HOOK_APPLETALK) == 0) { + sc->atalk.hook = hook; + NG_HOOK_SET_PRIVATE(hook, &sc->atalk); + } else if (strcmp(name, NG_CISCO_HOOK_IPX) == 0) { + sc->ipx.hook = hook; + NG_HOOK_SET_PRIVATE(hook, &sc->ipx); + } else if (strcmp(name, NG_CISCO_HOOK_DEBUG) == 0) { + NG_HOOK_SET_PRIVATE(hook, NULL); /* unimplemented */ + } else + return (EINVAL); + return 0; +} + +/* + * Receive control message. + */ +static int +cisco_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct ng_mesg *msg; + const sc_p sc = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_GENERIC_COOKIE: + switch (msg->header.cmd) { + case NGM_TEXT_STATUS: + { + char *arg; + int pos; + + NG_MKRESPONSE(resp, msg, NG_TEXTRESPONSE, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + arg = (char *) resp->data; + pos = sprintf(arg, + "keepalive period: %d sec; ", KEEPALIVE_SECS); + pos += sprintf(arg + pos, + "unacknowledged keepalives: %ld", sc->seqRetries); + resp->header.arglen = pos + 1; + break; + } + default: + error = EINVAL; + break; + } + break; + case NGM_CISCO_COOKIE: + switch (msg->header.cmd) { + case NGM_CISCO_GET_IPADDR: /* could be a late reply! */ + if ((msg->header.flags & NGF_RESP) == 0) { + struct in_addr *ips; + + NG_MKRESPONSE(resp, msg, + 2 * sizeof(*ips), M_NOWAIT); + if (!resp) { + error = ENOMEM; + break; + } + ips = (struct in_addr *) resp->data; + ips[0] = sc->localip; + ips[1] = sc->localmask; + break; + } + /* FALLTHROUGH */ /* ...if it's a reply */ + case NGM_CISCO_SET_IPADDR: + { + struct in_addr *const ips = (struct in_addr *)msg->data; + + if (msg->header.arglen < 2 * sizeof(*ips)) { + error = EINVAL; + break; + } + sc->localip = ips[0]; + sc->localmask = ips[1]; + break; + } + case NGM_CISCO_GET_STATUS: + { + struct ng_cisco_stats *stat; + + NG_MKRESPONSE(resp, msg, sizeof(*stat), M_NOWAIT); + if (!resp) { + error = ENOMEM; + break; + } + stat = (struct ng_cisco_stats *)resp->data; + stat->seqRetries = sc->seqRetries; + stat->keepAlivePeriod = KEEPALIVE_SECS; + break; + } + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive data + */ +static int +cisco_rcvdata(hook_p hook, item_p item) +{ + const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct protoent *pep; + struct cisco_header *h; + struct mbuf *m; + int error = 0; + + if ((pep = NG_HOOK_PRIVATE(hook)) == NULL) + goto out; + + /* If it came from our downlink, deal with it separately */ + if (pep->af == 0xffff) + return (cisco_input(sc, item)); + + /* OK so it came from a protocol, heading out. Prepend general data + packet header. For now, IP,IPX only */ + m = NGI_M(item); /* still associated with item */ + M_PREPEND(m, CISCO_HEADER_LEN, M_DONTWAIT); + if (!m) { + error = ENOBUFS; + goto out; + } + h = mtod(m, struct cisco_header *); + h->address = CISCO_UNICAST; + h->control = 0; + + switch (pep->af) { + case AF_INET: /* Internet Protocol */ + h->protocol = htons(ETHERTYPE_IP); + break; + case AF_INET6: + h->protocol = htons(ETHERTYPE_IPV6); + break; + case AF_APPLETALK: /* AppleTalk Protocol */ + h->protocol = htons(ETHERTYPE_AT); + break; + case AF_IPX: /* Novell IPX Protocol */ + h->protocol = htons(ETHERTYPE_IPX); + break; + default: + error = EAFNOSUPPORT; + goto out; + } + + /* Send it */ + NG_FWD_NEW_DATA(error, item, sc->downstream.hook, m); + return (error); + +out: + NG_FREE_ITEM(item); + return (error); +} + +/* + * Shutdown node + */ +static int +cisco_shutdown(node_p node) +{ + const sc_p sc = NG_NODE_PRIVATE(node); + + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(sc->node); + FREE(sc, M_NETGRAPH); + return (0); +} + +/* + * Disconnection of a hook + * + * For this type, removal of the last link destroys the node + */ +static int +cisco_disconnect(hook_p hook) +{ + const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct protoent *pep; + + /* Check it's not the debug hook */ + if ((pep = NG_HOOK_PRIVATE(hook))) { + pep->hook = NULL; + if (pep->af == 0xffff) + /* If it is the downstream hook, stop the timers */ + ng_uncallout(&sc->handle, NG_HOOK_NODE(hook)); + } + + /* If no more hooks, remove the node */ + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) + ng_rmnode_self(NG_HOOK_NODE(hook)); + return (0); +} + +/* + * Receive data + */ +static int +cisco_input(sc_p sc, item_p item) +{ + const struct cisco_header *h; + struct cisco_header hdrbuf; + struct protoent *pep; + struct mbuf *m; + int error = 0; + + /* Get data */ + m = NGI_M(item); + + /* Sanity check header length */ + if (m->m_pkthdr.len < sizeof(*h)) { + error = EINVAL; + goto drop; + } + + /* Get cisco header */ + if (m->m_len >= sizeof(*h)) /* the common case */ + h = mtod(m, const struct cisco_header *); + else { + m_copydata(m, 0, sizeof(*h), (caddr_t)&hdrbuf); + h = &hdrbuf; + } + m_adj(m, sizeof(*h)); + + /* Check header address */ + switch (h->address) { + default: /* Invalid Cisco packet. */ + goto drop; + case CISCO_UNICAST: + case CISCO_MULTICAST: + /* Don't check the control field here (RFC 1547). */ + switch (ntohs(h->protocol)) { + default: + goto drop; + case CISCO_KEEPALIVE: + { + const struct cisco_packet *p; + struct cisco_packet pktbuf; + + /* Sanity check packet length */ + if (m->m_pkthdr.len < sizeof(*p)) { + error = EINVAL; + goto drop; + } + + /* Get cisco packet */ + if (m->m_len >= sizeof(*p)) /* the common case */ + p = mtod(m, const struct cisco_packet *); + else { + m_copydata(m, 0, sizeof(*p), (caddr_t)&pktbuf); + p = &pktbuf; + } + + /* Check packet type */ + switch (ntohl(p->type)) { + default: + log(LOG_WARNING, + "cisco: unknown cisco packet type: 0x%lx\n", + (long)ntohl(p->type)); + break; + case CISCO_ADDR_REPLY: + /* Reply on address request, ignore */ + break; + case CISCO_KEEPALIVE_REQ: + sc->remote_seq = ntohl(p->par1); + if (sc->local_seq == ntohl(p->par2)) { + sc->local_seq++; + if (sc->seqRetries > 1) + cisco_notify(sc, NGM_LINK_IS_UP); + sc->seqRetries = 0; + } + break; + case CISCO_ADDR_REQ: + { + struct ng_mesg *msg; + int dummy_error = 0; + + /* Ask inet peer for IP address information */ + if (sc->inet.hook == NULL) + goto nomsg; + NG_MKMESSAGE(msg, NGM_CISCO_COOKIE, + NGM_CISCO_GET_IPADDR, 0, M_NOWAIT); + if (msg == NULL) + goto nomsg; + NG_SEND_MSG_HOOK(dummy_error, + sc->node, msg, sc->inet.hook, 0); + /* + * XXX Now maybe we should set a flag telling + * our receiver to send this message when the response comes in + * instead of now when the data may be bad. + */ + nomsg: + /* Send reply to peer device */ + error = cisco_send(sc, CISCO_ADDR_REPLY, + ntohl(sc->localip.s_addr), + ntohl(sc->localmask.s_addr)); + break; + } + } + goto drop; + } + case ETHERTYPE_IP: + pep = &sc->inet; + break; + case ETHERTYPE_IPV6: + pep = &sc->inet6; + break; + case ETHERTYPE_AT: + pep = &sc->atalk; + break; + case ETHERTYPE_IPX: + pep = &sc->ipx; + break; + } + break; + } + + /* Drop if payload is empty */ + if (m->m_pkthdr.len == 0) { + error = EINVAL; + goto drop; + } + + /* Send it on */ + if (pep->hook == NULL) + goto drop; + NG_FWD_NEW_DATA(error, item, pep->hook, m); + return (error); + +drop: + NG_FREE_ITEM(item); + return (error); +} + + +/* + * Send keepalive packets, every 10 seconds. + */ +static void +cisco_keepalive(node_p node, hook_p hook, void *arg1, int arg2) +{ + const sc_p sc = arg1; + + cisco_send(sc, CISCO_KEEPALIVE_REQ, sc->local_seq, sc->remote_seq); + if (sc->seqRetries++ > 1) + cisco_notify(sc, NGM_LINK_IS_DOWN); + ng_callout(&sc->handle, node, NULL, (hz * KEEPALIVE_SECS), + &cisco_keepalive, (void *)sc, 0); +} + +/* + * Send Cisco keepalive packet. + */ +static int +cisco_send(sc_p sc, int type, long par1, long par2) +{ + struct cisco_header *h; + struct cisco_packet *ch; + struct mbuf *m; + struct timeval time; + u_long t; + int error = 0; + + getmicrouptime(&time); + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (!m) + return (ENOBUFS); + + t = time.tv_sec * 1000 + time.tv_usec / 1000; + m->m_pkthdr.len = m->m_len = CISCO_HEADER_LEN + CISCO_PACKET_LEN; + m->m_pkthdr.rcvif = 0; + + h = mtod(m, struct cisco_header *); + h->address = CISCO_MULTICAST; + h->control = 0; + h->protocol = htons(CISCO_KEEPALIVE); + + ch = (struct cisco_packet *) (h + 1); + ch->type = htonl(type); + ch->par1 = htonl(par1); + ch->par2 = htonl(par2); + ch->rel = -1; + ch->time0 = htons((u_short) (t >> 16)); + ch->time1 = htons((u_short) t); + + NG_SEND_DATA_ONLY(error, sc->downstream.hook, m); + return (error); +} + +/* + * Send linkstate to upstream node. + */ +static void +cisco_notify(sc_p sc, uint32_t cmd) +{ + struct ng_mesg *msg; + int dummy_error = 0; + + if (sc->inet.hook == NULL) /* nothing to notify */ + return; + + NG_MKMESSAGE(msg, NGM_FLOW_COOKIE, cmd, 0, M_NOWAIT); + if (msg != NULL) + NG_SEND_MSG_HOOK(dummy_error, sc->node, msg, sc->inet.hook, 0); +} diff --git a/sys/netgraph7/ng_cisco.h b/sys/netgraph7/ng_cisco.h new file mode 100644 index 0000000000..ac916ad83d --- /dev/null +++ b/sys/netgraph7/ng_cisco.h @@ -0,0 +1,91 @@ +/* + * ng_cisco.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_cisco.h,v 1.8 2005/01/07 01:45:39 imp Exp $ + * $Whistle: ng_cisco.h,v 1.6 1999/01/25 01:21:48 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_CISCO_H_ +#define _NETGRAPH_NG_CISCO_H_ + +/* Node type name and magic cookie */ +#define NG_CISCO_NODE_TYPE "cisco" +#define NGM_CISCO_COOKIE 860707227 + +/* Hook names */ +#define NG_CISCO_HOOK_DOWNSTREAM "downstream" +#define NG_CISCO_HOOK_INET "inet" +#define NG_CISCO_HOOK_INET6 "inet6" +#define NG_CISCO_HOOK_APPLETALK "atalk" +#define NG_CISCO_HOOK_IPX "ipx" +#define NG_CISCO_HOOK_DEBUG "debug" + +/* Netgraph commands */ +enum { + NGM_CISCO_SET_IPADDR = 1, /* requires a struct ng_cisco_ipaddr */ + NGM_CISCO_GET_IPADDR, /* returns a struct ng_cisco_ipaddr */ + NGM_CISCO_GET_STATUS, /* returns a struct ng_cisco_stat */ +}; + +struct ng_cisco_ipaddr { + struct in_addr ipaddr; /* IP address */ + struct in_addr netmask; /* Netmask */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_CISCO_IPADDR_TYPE_INFO { \ + { "ipaddr", &ng_parse_ipaddr_type }, \ + { "netmask", &ng_parse_ipaddr_type }, \ + { NULL } \ +} + +struct ng_cisco_stats { + u_int32_t seqRetries; /* # unack'd retries */ + u_int32_t keepAlivePeriod; /* in seconds */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_CISCO_STATS_TYPE_INFO { \ + { "seqRetries", &ng_parse_uint32_type }, \ + { "keepAlivePeriod", &ng_parse_uint32_type }, \ + { NULL } \ +} + +#endif /* _NETGRAPH_NG_CISCO_H_ */ + diff --git a/sys/netgraph7/ng_deflate.c b/sys/netgraph7/ng_deflate.c new file mode 100644 index 0000000000..d5753df649 --- /dev/null +++ b/sys/netgraph7/ng_deflate.c @@ -0,0 +1,684 @@ +/*- + * Copyright (c) 2006 Alexander Motin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_deflate.c,v 1.3 2007/01/15 05:55:56 glebius Exp $ + */ + +/* + * Deflate PPP compression netgraph node type. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "opt_netgraph.h" + +MALLOC_DEFINE(M_NETGRAPH_DEFLATE, "netgraph_deflate", "netgraph deflate node "); + +/* DEFLATE header length */ +#define DEFLATE_HDRLEN 2 + +#define PROT_COMPD 0x00fd + +#define DEFLATE_BUF_SIZE 4096 + +/* Node private data */ +struct ng_deflate_private { + struct ng_deflate_config cfg; /* configuration */ + u_char inbuf[DEFLATE_BUF_SIZE]; /* input buffer */ + u_char outbuf[DEFLATE_BUF_SIZE]; /* output buffer */ + z_stream cx; /* compression context */ + struct ng_deflate_stats stats; /* statistics */ + ng_ID_t ctrlnode; /* path to controlling node */ + uint16_t seqnum; /* sequence number */ + u_char compress; /* compress/decompress flag */ +}; +typedef struct ng_deflate_private *priv_p; + +/* Netgraph node methods */ +static ng_constructor_t ng_deflate_constructor; +static ng_rcvmsg_t ng_deflate_rcvmsg; +static ng_shutdown_t ng_deflate_shutdown; +static ng_newhook_t ng_deflate_newhook; +static ng_rcvdata_t ng_deflate_rcvdata; +static ng_disconnect_t ng_deflate_disconnect; + +/* Helper functions */ +static void *z_alloc(void *, u_int items, u_int size); +static void z_free(void *, void *ptr); +static int ng_deflate_compress(node_p node, + struct mbuf *m, struct mbuf **resultp); +static int ng_deflate_decompress(node_p node, + struct mbuf *m, struct mbuf **resultp); +static void ng_deflate_reset_req(node_p node); + +/* Parse type for struct ng_deflate_config. */ +static const struct ng_parse_struct_field ng_deflate_config_type_fields[] + = NG_DEFLATE_CONFIG_INFO; +static const struct ng_parse_type ng_deflate_config_type = { + &ng_parse_struct_type, + ng_deflate_config_type_fields +}; + +/* Parse type for struct ng_deflate_stat. */ +static const struct ng_parse_struct_field ng_deflate_stats_type_fields[] + = NG_DEFLATE_STATS_INFO; +static const struct ng_parse_type ng_deflate_stat_type = { + &ng_parse_struct_type, + ng_deflate_stats_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII. */ +static const struct ng_cmdlist ng_deflate_cmds[] = { + { + NGM_DEFLATE_COOKIE, + NGM_DEFLATE_CONFIG, + "config", + &ng_deflate_config_type, + NULL + }, + { + NGM_DEFLATE_COOKIE, + NGM_DEFLATE_RESETREQ, + "resetreq", + NULL, + NULL + }, + { + NGM_DEFLATE_COOKIE, + NGM_DEFLATE_GET_STATS, + "getstats", + NULL, + &ng_deflate_stat_type + }, + { + NGM_DEFLATE_COOKIE, + NGM_DEFLATE_CLR_STATS, + "clrstats", + NULL, + NULL + }, + { + NGM_DEFLATE_COOKIE, + NGM_DEFLATE_GETCLR_STATS, + "getclrstats", + NULL, + &ng_deflate_stat_type + }, + { 0 } +}; + +/* Node type descriptor */ +static struct ng_type ng_deflate_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_DEFLATE_NODE_TYPE, + .constructor = ng_deflate_constructor, + .rcvmsg = ng_deflate_rcvmsg, + .shutdown = ng_deflate_shutdown, + .newhook = ng_deflate_newhook, + .rcvdata = ng_deflate_rcvdata, + .disconnect = ng_deflate_disconnect, + .cmdlist = ng_deflate_cmds, +}; +NETGRAPH_INIT(deflate, &ng_deflate_typestruct); + +/* Depend on separate zlib module. */ +MODULE_DEPEND(ng_deflate, zlib, 1, 1, 1); + +#define ERROUT(x) do { error = (x); goto done; } while (0) + +/************************************************************************ + NETGRAPH NODE STUFF + ************************************************************************/ + +/* + * Node type constructor + */ +static int +ng_deflate_constructor(node_p node) +{ + priv_p priv; + + /* Allocate private structure. */ + priv = malloc(sizeof(*priv), M_NETGRAPH_DEFLATE, M_WAITOK | M_ZERO); + + NG_NODE_SET_PRIVATE(node, priv); + + /* This node is not thread safe. */ + NG_NODE_FORCE_WRITER(node); + + /* Done */ + return (0); +} + +/* + * Give our OK for a hook to be added. + */ +static int +ng_deflate_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (NG_NODE_NUMHOOKS(node) > 0) + return (EINVAL); + + if (strcmp(name, NG_DEFLATE_HOOK_COMP) == 0) + priv->compress = 1; + else if (strcmp(name, NG_DEFLATE_HOOK_DECOMP) == 0) + priv->compress = 0; + else + return (EINVAL); + + return (0); +} + +/* + * Receive a control message + */ +static int +ng_deflate_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + + if (msg->header.typecookie != NGM_DEFLATE_COOKIE) + ERROUT(EINVAL); + + switch (msg->header.cmd) { + case NGM_DEFLATE_CONFIG: + { + struct ng_deflate_config *const cfg + = (struct ng_deflate_config *)msg->data; + + /* Check configuration. */ + if (msg->header.arglen != sizeof(*cfg)) + ERROUT(EINVAL); + if (cfg->enable) { + if (cfg->windowBits < 8 || cfg->windowBits > 15) + ERROUT(EINVAL); + } else + cfg->windowBits = 0; + + /* Clear previous state. */ + if (priv->cfg.enable) { + if (priv->compress) + deflateEnd(&priv->cx); + else + inflateEnd(&priv->cx); + priv->cfg.enable = 0; + } + + /* Configuration is OK, reset to it. */ + priv->cfg = *cfg; + + if (priv->cfg.enable) { + priv->cx.next_in = NULL; + priv->cx.zalloc = z_alloc; + priv->cx.zfree = z_free; + int res; + if (priv->compress) { + if ((res = deflateInit2(&priv->cx, + Z_DEFAULT_COMPRESSION, Z_DEFLATED, + -cfg->windowBits, 8, + Z_DEFAULT_STRATEGY)) != Z_OK) { + log(LOG_NOTICE, + "deflateInit2: error %d, %s\n", + res, priv->cx.msg); + priv->cfg.enable = 0; + ERROUT(ENOMEM); + } + } else { + if ((res = inflateInit2(&priv->cx, + -cfg->windowBits)) != Z_OK) { + log(LOG_NOTICE, + "inflateInit2: error %d, %s\n", + res, priv->cx.msg); + priv->cfg.enable = 0; + ERROUT(ENOMEM); + } + } + } + + /* Initialize other state. */ + priv->seqnum = 0; + + /* Save return address so we can send reset-req's */ + priv->ctrlnode = NGI_RETADDR(item); + break; + } + + case NGM_DEFLATE_RESETREQ: + ng_deflate_reset_req(node); + break; + + case NGM_DEFLATE_GET_STATS: + case NGM_DEFLATE_CLR_STATS: + case NGM_DEFLATE_GETCLR_STATS: + /* Create response if requested. */ + if (msg->header.cmd != NGM_DEFLATE_CLR_STATS) { + NG_MKRESPONSE(resp, msg, + sizeof(struct ng_deflate_stats), M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + bcopy(&priv->stats, resp->data, + sizeof(struct ng_deflate_stats)); + } + + /* Clear stats if requested. */ + if (msg->header.cmd != NGM_DEFLATE_GET_STATS) + bzero(&priv->stats, + sizeof(struct ng_deflate_stats)); + break; + + default: + error = EINVAL; + break; + } +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive incoming data on our hook. + */ +static int +ng_deflate_rcvdata(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + struct mbuf *m, *out; + int error; + + if (!priv->cfg.enable) { + NG_FREE_ITEM(item); + return (ENXIO); + } + + NGI_GET_M(item, m); + /* Compress */ + if (priv->compress) { + if ((error = ng_deflate_compress(node, m, &out)) != 0) { + NG_FREE_ITEM(item); + log(LOG_NOTICE, "%s: error: %d\n", __func__, error); + return (error); + } + + } else { /* Decompress */ + if ((error = ng_deflate_decompress(node, m, &out)) != 0) { + NG_FREE_ITEM(item); + log(LOG_NOTICE, "%s: error: %d\n", __func__, error); + if (priv->ctrlnode != 0) { + struct ng_mesg *msg; + + /* Need to send a reset-request. */ + NG_MKMESSAGE(msg, NGM_DEFLATE_COOKIE, + NGM_DEFLATE_RESETREQ, 0, M_NOWAIT); + if (msg == NULL) + return (error); + NG_SEND_MSG_ID(error, node, msg, + priv->ctrlnode, 0); + } + return (error); + } + } + + NG_FWD_NEW_DATA(error, item, hook, out); + return (error); +} + +/* + * Destroy node. + */ +static int +ng_deflate_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + /* Take down netgraph node. */ + if (priv->cfg.enable) { + if (priv->compress) + deflateEnd(&priv->cx); + else + inflateEnd(&priv->cx); + } + + free(priv, M_NETGRAPH_DEFLATE); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); /* let the node escape */ + return (0); +} + +/* + * Hook disconnection + */ +static int +ng_deflate_disconnect(hook_p hook) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + + if (priv->cfg.enable) { + if (priv->compress) + deflateEnd(&priv->cx); + else + inflateEnd(&priv->cx); + priv->cfg.enable = 0; + } + + /* Go away if no longer connected. */ + if ((NG_NODE_NUMHOOKS(node) == 0) && NG_NODE_IS_VALID(node)) + ng_rmnode_self(node); + return (0); +} + +/************************************************************************ + HELPER STUFF + ************************************************************************/ + +/* + * Space allocation and freeing routines for use by zlib routines. + */ + +static void * +z_alloc(void *notused, u_int items, u_int size) +{ + + return (malloc(items * size, M_NETGRAPH_DEFLATE, M_NOWAIT)); +} + +static void +z_free(void *notused, void *ptr) +{ + + free(ptr, M_NETGRAPH_DEFLATE); +} + +/* + * Compress/encrypt a packet and put the result in a new mbuf at *resultp. + * The original mbuf is not free'd. + */ +static int +ng_deflate_compress(node_p node, struct mbuf *m, struct mbuf **resultp) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + int outlen, inlen; + int rtn; + + /* Initialize. */ + *resultp = NULL; + + inlen = m->m_pkthdr.len; + + priv->stats.FramesPlain++; + priv->stats.InOctets+=inlen; + + if (inlen > DEFLATE_BUF_SIZE) { + priv->stats.Errors++; + NG_FREE_M(m); + return (ENOMEM); + } + + /* Work with contiguous regions of memory. */ + m_copydata(m, 0, inlen, (caddr_t)priv->inbuf); + outlen = DEFLATE_BUF_SIZE; + + /* Compress "inbuf" into "outbuf". */ + /* Prepare to compress. */ + if (priv->inbuf[0] != 0) { + priv->cx.next_in = priv->inbuf; + priv->cx.avail_in = inlen; + } else { + priv->cx.next_in = priv->inbuf + 1; /* compress protocol */ + priv->cx.avail_in = inlen - 1; + } + priv->cx.next_out = priv->outbuf + 2 + DEFLATE_HDRLEN; + priv->cx.avail_out = outlen - 2 - DEFLATE_HDRLEN; + + /* Compress. */ + rtn = deflate(&priv->cx, Z_PACKET_FLUSH); + + /* Check return value. */ + if (rtn != Z_OK) { + priv->stats.Errors++; + log(LOG_NOTICE, "ng_deflate: compression error: %d (%s)\n", + rtn, priv->cx.msg); + NG_FREE_M(m); + return (EINVAL); + } + + /* Calculate resulting size. */ + outlen -= priv->cx.avail_out; + + /* If we can't compress this packet, send it as-is. */ + if (outlen > inlen) { + /* Return original packet uncompressed. */ + *resultp = m; + priv->stats.FramesUncomp++; + priv->stats.OutOctets+=inlen; + } else { + NG_FREE_M(m); + + /* Install header. */ + ((u_int16_t *)priv->outbuf)[0] = htons(PROT_COMPD); + ((u_int16_t *)priv->outbuf)[1] = htons(priv->seqnum); + + /* Return packet in an mbuf. */ + *resultp = m_devget((caddr_t)priv->outbuf, outlen, 0, NULL, + NULL); + if (*resultp == NULL) { + priv->stats.Errors++; + return (ENOMEM); + }; + priv->stats.FramesComp++; + priv->stats.OutOctets+=outlen; + } + + /* Update sequence number. */ + priv->seqnum++; + + return (0); +} + +/* + * Decompress/decrypt packet and put the result in a new mbuf at *resultp. + * The original mbuf is not free'd. + */ +static int +ng_deflate_decompress(node_p node, struct mbuf *m, struct mbuf **resultp) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + int outlen, inlen; + int rtn; + uint16_t proto; + int offset; + uint16_t rseqnum; + + /* Initialize. */ + *resultp = NULL; + + inlen = m->m_pkthdr.len; + + if (inlen > DEFLATE_BUF_SIZE) { + priv->stats.Errors++; + NG_FREE_M(m); + priv->seqnum = 0; + return (ENOMEM); + } + + /* Work with contiguous regions of memory. */ + m_copydata(m, 0, inlen, (caddr_t)priv->inbuf); + + /* Separate proto. */ + if ((priv->inbuf[0] & 0x01) != 0) { + proto = priv->inbuf[0]; + offset = 1; + } else { + proto = ntohs(((uint16_t *)priv->inbuf)[0]); + offset = 2; + } + + priv->stats.InOctets += inlen; + + /* Packet is compressed, so decompress. */ + if (proto == PROT_COMPD) { + priv->stats.FramesComp++; + + /* Check sequence number. */ + rseqnum = ntohs(((uint16_t *)(priv->inbuf + offset))[0]); + offset += 2; + if (rseqnum != priv->seqnum) { + priv->stats.Errors++; + log(LOG_NOTICE, "ng_deflate: wrong sequence: %u " + "instead of %u\n", rseqnum, priv->seqnum); + NG_FREE_M(m); + priv->seqnum = 0; + return (EPIPE); + } + + outlen = DEFLATE_BUF_SIZE; + + /* Decompress "inbuf" into "outbuf". */ + /* Prepare to decompress. */ + priv->cx.next_in = priv->inbuf + offset; + priv->cx.avail_in = inlen - offset; + /* Reserve space for protocol decompression. */ + priv->cx.next_out = priv->outbuf + 1; + priv->cx.avail_out = outlen - 1; + + /* Decompress. */ + rtn = inflate(&priv->cx, Z_PACKET_FLUSH); + + /* Check return value. */ + if (rtn != Z_OK && rtn != Z_STREAM_END) { + priv->stats.Errors++; + NG_FREE_M(m); + priv->seqnum = 0; + log(LOG_NOTICE, "%s: decompression error: %d (%s)\n", + __func__, rtn, priv->cx.msg); + + switch (rtn) { + case Z_MEM_ERROR: + return (ENOMEM); + case Z_DATA_ERROR: + return (EIO); + default: + return (EINVAL); + } + } + + /* Calculate resulting size. */ + outlen -= priv->cx.avail_out; + + NG_FREE_M(m); + + /* Decompress protocol. */ + if ((priv->outbuf[1] & 0x01) != 0) { + priv->outbuf[0] = 0; + /* Return packet in an mbuf. */ + *resultp = m_devget((caddr_t)priv->outbuf, outlen, 0, + NULL, NULL); + } else { + outlen--; + /* Return packet in an mbuf. */ + *resultp = m_devget((caddr_t)(priv->outbuf + 1), + outlen, 0, NULL, NULL); + } + if (*resultp == NULL) { + priv->stats.Errors++; + priv->seqnum = 0; + return (ENOMEM); + }; + priv->stats.FramesPlain++; + priv->stats.OutOctets+=outlen; + + } else { /* Packet is not compressed, just update dictionary. */ + priv->stats.FramesUncomp++; + if (priv->inbuf[0] == 0) { + priv->cx.next_in = priv->inbuf + 1; /* compress protocol */ + priv->cx.avail_in = inlen - 1; + } else { + priv->cx.next_in = priv->inbuf; + priv->cx.avail_in = inlen; + } + + rtn = inflateIncomp(&priv->cx); + + /* Check return value */ + if (rtn != Z_OK) { + priv->stats.Errors++; + log(LOG_NOTICE, "%s: inflateIncomp error: %d (%s)\n", + __func__, rtn, priv->cx.msg); + NG_FREE_M(m); + priv->seqnum = 0; + return (EINVAL); + } + + *resultp = m; + priv->stats.FramesPlain++; + priv->stats.OutOctets += inlen; + } + + /* Update sequence number. */ + priv->seqnum++; + + return (0); +} + +/* + * The peer has sent us a CCP ResetRequest, so reset our transmit state. + */ +static void +ng_deflate_reset_req(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + priv->seqnum = 0; + if (priv->cfg.enable) { + if (priv->compress) + deflateReset(&priv->cx); + else + inflateReset(&priv->cx); + } +} + diff --git a/sys/netgraph7/ng_deflate.h b/sys/netgraph7/ng_deflate.h new file mode 100644 index 0000000000..ec5a12513c --- /dev/null +++ b/sys/netgraph7/ng_deflate.h @@ -0,0 +1,85 @@ +/*- + * Copyright (c) 2006 Alexander Motin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_deflate.h,v 1.1 2006/12/28 15:44:05 glebius Exp $ + */ + +#ifndef _NETGRAPH_NG_DEFLATE_H_ +#define _NETGRAPH_NG_DEFLATE_H_ + +/* Node type name and magic cookie */ +#define NG_DEFLATE_NODE_TYPE "deflate" +#define NGM_DEFLATE_COOKIE 1166642656 + +/* Hook names */ +#define NG_DEFLATE_HOOK_COMP "comp" /* compression hook */ +#define NG_DEFLATE_HOOK_DECOMP "decomp" /* decompression hook */ + +/* Config struct */ +struct ng_deflate_config { + u_char enable; /* node enabled */ + u_char windowBits; /* log2(Window size) */ +}; + +/* Keep this in sync with the above structure definition. */ +#define NG_DEFLATE_CONFIG_INFO { \ + { "enable", &ng_parse_uint8_type }, \ + { "windowBits", &ng_parse_uint8_type }, \ + { NULL } \ +} + +/* Statistics structure for one direction. */ +struct ng_deflate_stats { + uint64_t FramesPlain; + uint64_t FramesComp; + uint64_t FramesUncomp; + uint64_t InOctets; + uint64_t OutOctets; + uint64_t Errors; +}; + +/* Keep this in sync with the above structure definition. */ +#define NG_DEFLATE_STATS_INFO { \ + { "FramesPlain",&ng_parse_uint64_type }, \ + { "FramesComp", &ng_parse_uint64_type }, \ + { "FramesUncomp", &ng_parse_uint64_type }, \ + { "InOctets", &ng_parse_uint64_type }, \ + { "OutOctets", &ng_parse_uint64_type }, \ + { "Errors", &ng_parse_uint64_type }, \ + { NULL } \ +} + +/* Netgraph commands */ +enum { + NGM_DEFLATE_CONFIG = 1, + NGM_DEFLATE_RESETREQ, /* sent either way! */ + NGM_DEFLATE_GET_STATS, + NGM_DEFLATE_CLR_STATS, + NGM_DEFLATE_GETCLR_STATS, +}; + +#endif /* _NETGRAPH_NG_DEFLATE_H_ */ + diff --git a/sys/netgraph7/ng_device.c b/sys/netgraph7/ng_device.c new file mode 100644 index 0000000000..88636fe04c --- /dev/null +++ b/sys/netgraph7/ng_device.c @@ -0,0 +1,492 @@ +/*- + * Copyright (c) 2002 Mark Santcroos + * Copyright (c) 2004-2005 Gleb Smirnoff + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Netgraph "device" node + * + * This node presents a /dev/ngd%d device that interfaces to an other + * netgraph node. + * + * $FreeBSD: src/sys/netgraph/ng_device.c,v 1.22 2006/11/02 17:37:21 andre Exp $ + * + */ + +#if 0 +#define DBG do { printf("ng_device: %s\n", __func__ ); } while (0) +#else +#define DBG do {} while (0) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define ERROUT(x) do { error = (x); goto done; } while (0) + +/* Netgraph methods */ +static int ng_device_mod_event(module_t, int, void *); +static ng_constructor_t ng_device_constructor; +static ng_rcvmsg_t ng_device_rcvmsg; +static ng_shutdown_t ng_device_shutdown; +static ng_newhook_t ng_device_newhook; +static ng_rcvdata_t ng_device_rcvdata; +static ng_disconnect_t ng_device_disconnect; + +/* Netgraph type */ +static struct ng_type ngd_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_DEVICE_NODE_TYPE, + .mod_event = ng_device_mod_event, + .constructor = ng_device_constructor, + .rcvmsg = ng_device_rcvmsg, + .shutdown = ng_device_shutdown, + .newhook = ng_device_newhook, + .rcvdata = ng_device_rcvdata, + .disconnect = ng_device_disconnect, +}; +NETGRAPH_INIT(device, &ngd_typestruct); + +/* per node data */ +struct ngd_private { + struct ifqueue readq; + struct ng_node *node; + struct ng_hook *hook; + struct cdev *ngddev; + struct mtx ngd_mtx; + int unit; + uint16_t flags; +#define NGDF_OPEN 0x0001 +#define NGDF_RWAIT 0x0002 +}; +typedef struct ngd_private *priv_p; + +/* unit number allocator entity */ +static struct unrhdr *ngd_unit; + +/* Maximum number of NGD devices */ +#define MAX_NGD 999 + +static d_close_t ngdclose; +static d_open_t ngdopen; +static d_read_t ngdread; +static d_write_t ngdwrite; +#if 0 +static d_ioctl_t ngdioctl; +#endif +static d_poll_t ngdpoll; + +static struct cdevsw ngd_cdevsw = { + .d_version = D_VERSION, + .d_open = ngdopen, + .d_close = ngdclose, + .d_read = ngdread, + .d_write = ngdwrite, +#if 0 + .d_ioctl = ngdioctl, +#endif + .d_poll = ngdpoll, + .d_name = NG_DEVICE_DEVNAME, +}; + +/****************************************************************************** + * Netgraph methods + ******************************************************************************/ + +/* + * Handle loading and unloading for this node type. + */ +static int +ng_device_mod_event(module_t mod, int event, void *data) +{ + int error = 0; + + switch (event) { + case MOD_LOAD: + ngd_unit = new_unrhdr(0, MAX_NGD, NULL); + break; + case MOD_UNLOAD: + delete_unrhdr(ngd_unit); + break; + default: + error = EOPNOTSUPP; + break; + } + return (error); +} + +/* + * create new node + */ +static int +ng_device_constructor(node_p node) +{ + priv_p priv; + + DBG; + + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + + /* Allocate unit number */ + priv->unit = alloc_unr(ngd_unit); + + /* Initialize mutexes and queue */ + mtx_init(&priv->ngd_mtx, "ng_device", NULL, MTX_DEF); + mtx_init(&priv->readq.ifq_mtx, "ng_device queue", NULL, MTX_DEF); + IFQ_SET_MAXLEN(&priv->readq, ifqmaxlen); + + /* Link everything together */ + NG_NODE_SET_PRIVATE(node, priv); + priv->node = node; + + priv->ngddev = make_dev(&ngd_cdevsw, unit2minor(priv->unit), UID_ROOT, + GID_WHEEL, 0600, NG_DEVICE_DEVNAME "%d", priv->unit); + if(priv->ngddev == NULL) { + printf("%s(): make_dev() failed\n",__func__); + mtx_destroy(&priv->ngd_mtx); + mtx_destroy(&priv->readq.ifq_mtx); + free_unr(ngd_unit, priv->unit); + FREE(priv, M_NETGRAPH); + return(EINVAL); + } + /* XXX: race here? */ + priv->ngddev->si_drv1 = priv; + + return(0); +} + +/* + * Process control message. + */ + +static int +ng_device_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *msg; + struct ng_mesg *resp = NULL; + int error = 0; + + NGI_GET_MSG(item, msg); + + if (msg->header.typecookie == NGM_DEVICE_COOKIE) { + switch (msg->header.cmd) { + case NGM_DEVICE_GET_DEVNAME: + /* XXX: Fix when MAX_NGD us bigger */ + NG_MKRESPONSE(resp, msg, + strlen(NG_DEVICE_DEVNAME) + 4, M_NOWAIT); + + if (resp == NULL) + ERROUT(ENOMEM); + + strlcpy((char *)resp->data, priv->ngddev->si_name, + strlen(priv->ngddev->si_name) + 1); + break; + + default: + error = EINVAL; + break; + } + } else + error = EINVAL; + +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Accept incoming hook. We support only one hook per node. + */ +static int +ng_device_newhook(node_p node, hook_p hook, const char *name) +{ + priv_p priv = NG_NODE_PRIVATE(node); + + DBG; + + /* We have only one hook per node */ + if (priv->hook != NULL) + return (EISCONN); + + priv->hook = hook; + + return(0); +} + +/* + * Receive data from hook, write it to device. + */ +static int +ng_device_rcvdata(hook_p hook, item_p item) +{ + priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct mbuf *m; + + DBG; + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + IF_LOCK(&priv->readq); + if (_IF_QFULL(&priv->readq)) { + _IF_DROP(&priv->readq); + IF_UNLOCK(&priv->readq); + NG_FREE_M(m); + return (ENOBUFS); + } + + _IF_ENQUEUE(&priv->readq, m); + IF_UNLOCK(&priv->readq); + mtx_lock(&priv->ngd_mtx); + if (priv->flags & NGDF_RWAIT) { + priv->flags &= ~NGDF_RWAIT; + wakeup(priv); + } + mtx_unlock(&priv->ngd_mtx); + + return(0); +} + +/* + * Removal of the hook destroys the node. + */ +static int +ng_device_disconnect(hook_p hook) +{ + priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + DBG; + + destroy_dev(priv->ngddev); + mtx_destroy(&priv->ngd_mtx); + + IF_DRAIN(&priv->readq); + mtx_destroy(&(priv)->readq.ifq_mtx); + + free_unr(ngd_unit, priv->unit); + + FREE(priv, M_NETGRAPH); + + ng_rmnode_self(NG_HOOK_NODE(hook)); + + return(0); +} + +/* + * Node shutdown. Everything is already done in disconnect method. + */ +static int +ng_device_shutdown(node_p node) +{ + NG_NODE_UNREF(node); + return (0); +} + +/****************************************************************************** + * Device methods + ******************************************************************************/ + +/* + * the device is opened + */ +static int +ngdopen(struct cdev *dev, int flag, int mode, struct thread *td) +{ + priv_p priv = (priv_p )dev->si_drv1; + + DBG; + + mtx_lock(&priv->ngd_mtx); + priv->flags |= NGDF_OPEN; + mtx_unlock(&priv->ngd_mtx); + + return(0); +} + +/* + * the device is closed + */ +static int +ngdclose(struct cdev *dev, int flag, int mode, struct thread *td) +{ + priv_p priv = (priv_p )dev->si_drv1; + + DBG; + mtx_lock(&priv->ngd_mtx); + priv->flags &= ~NGDF_OPEN; + mtx_unlock(&priv->ngd_mtx); + + return(0); +} + +#if 0 /* + * The ioctl is transformed into netgraph control message. + * We do not process them, yet. + */ +/* + * process ioctl + * + * they are translated into netgraph messages and passed on + * + */ +static int +ngdioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td) +{ + struct ngd_softc *sc = &ngd_softc; + struct ngd_connection * connection = NULL; + struct ngd_connection * tmp; + int error = 0; + struct ng_mesg *msg; + struct ngd_param_s * datap; + + DBG; + + NG_MKMESSAGE(msg, NGM_DEVICE_COOKIE, cmd, sizeof(struct ngd_param_s), + M_NOWAIT); + if (msg == NULL) { + printf("%s(): msg == NULL\n",__func__); + goto nomsg; + } + + /* pass the ioctl data into the ->data area */ + datap = (struct ngd_param_s *)msg->data; + datap->p = addr; + + NG_SEND_MSG_HOOK(error, sc->node, msg, connection->active_hook, 0); + if(error) + printf("%s(): NG_SEND_MSG_HOOK error: %d\n",__func__,error); + +nomsg: + + return(0); +} +#endif /* if 0 */ + +/* + * This function is called when a read(2) is done to our device. + * We process one mbuf from queue. + */ +static int +ngdread(struct cdev *dev, struct uio *uio, int flag) +{ + priv_p priv = (priv_p )dev->si_drv1; + struct mbuf *m; + int len, error = 0; + + DBG; + + /* get an mbuf */ + do { + IF_DEQUEUE(&priv->readq, m); + if (m == NULL) { + if (flag & IO_NDELAY) + return (EWOULDBLOCK); + mtx_lock(&priv->ngd_mtx); + priv->flags |= NGDF_RWAIT; + if ((error = msleep(priv, &priv->ngd_mtx, + PDROP | PCATCH | (PZERO + 1), + "ngdread", 0)) != 0) + return (error); + } + } while (m == NULL); + + while (m && uio->uio_resid > 0 && error == 0) { + len = MIN(uio->uio_resid, m->m_len); + if (len != 0) + error = uiomove(mtod(m, void *), len, uio); + m = m_free(m); + } + + if (m) + m_freem(m); + + return (error); +} + + +/* + * This function is called when our device is written to. + * We read the data from userland into mbuf chain and pass it to the remote hook. + * + */ +static int +ngdwrite(struct cdev *dev, struct uio *uio, int flag) +{ + priv_p priv = (priv_p )dev->si_drv1; + struct mbuf *m; + int error = 0; + + DBG; + + if (uio->uio_resid == 0) + return (0); + + if (uio->uio_resid < 0 || uio->uio_resid > IP_MAXPACKET) + return (EIO); + + if ((m = m_uiotombuf(uio, M_DONTWAIT, 0, 0, M_PKTHDR)) == NULL) + return (ENOBUFS); + + NG_SEND_DATA_ONLY(error, priv->hook, m); + + return (error); +} + +/* + * we are being polled/selected + * check if there is data available for read + */ +static int +ngdpoll(struct cdev *dev, int events, struct thread *td) +{ + priv_p priv = (priv_p )dev->si_drv1; + int revents = 0; + + if (events & (POLLIN | POLLRDNORM) && + !IFQ_IS_EMPTY(&priv->readq)) + revents |= events & (POLLIN | POLLRDNORM); + + return (revents); +} diff --git a/sys/netgraph7/ng_device.h b/sys/netgraph7/ng_device.h new file mode 100644 index 0000000000..f7b2ccdf92 --- /dev/null +++ b/sys/netgraph7/ng_device.h @@ -0,0 +1,49 @@ +/*- + * Copyright (c) 2002 Mark Santcroos + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * $FreeBSD: src/sys/netgraph/ng_device.h,v 1.5 2005/01/07 01:45:39 imp Exp $ + * + */ + +#ifndef _NETGRAPH_NG_DEVICE_H_ +#define _NETGRAPH_NG_DEVICE_H_ + +/* Node type name and magic cookie */ +#define NG_DEVICE_NODE_TYPE "device" +#define NGM_DEVICE_COOKIE 1091129178 +#define NG_DEVICE_DEVNAME "ngd" + +/* Netgraph control messages */ +enum { + NGM_DEVICE_GET_DEVNAME, +}; + +#if 0 +/* passing ioctl params */ +struct ngd_param_s { + void * p; +}; +#endif + +#endif /* _NETGRAPH_NG_DEVICE_H_ */ diff --git a/sys/netgraph7/ng_echo.c b/sys/netgraph7/ng_echo.c new file mode 100644 index 0000000000..ee42955c61 --- /dev/null +++ b/sys/netgraph7/ng_echo.c @@ -0,0 +1,121 @@ +/* + * ng_echo.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elisher + * + * $FreeBSD: src/sys/netgraph/ng_echo.c,v 1.13 2005/04/15 10:14:00 glebius Exp $ + * $Whistle: ng_echo.c,v 1.13 1999/11/01 09:24:51 julian Exp $ + */ + +/* + * Netgraph "echo" node + * + * This node simply bounces data and messages back to whence they came. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Netgraph methods */ +static ng_constructor_t nge_cons; +static ng_rcvmsg_t nge_rcvmsg; +static ng_rcvdata_t nge_rcvdata; +static ng_disconnect_t nge_disconnect; + +/* Netgraph type */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_ECHO_NODE_TYPE, + .constructor = nge_cons, + .rcvmsg = nge_rcvmsg, + .rcvdata = nge_rcvdata, + .disconnect = nge_disconnect, +}; +NETGRAPH_INIT(echo, &typestruct); + +static int +nge_cons(node_p node) +{ + return (0); +} + +/* + * Receive control message. We just bounce it back as a reply. + */ +static int +nge_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct ng_mesg *msg; + int error = 0; + + NGI_GET_MSG(item, msg); + msg->header.flags |= NGF_RESP; + NG_RESPOND_MSG(error, node, item, msg); + return (error); +} + +/* + * Receive data + */ +static int +nge_rcvdata(hook_p hook, item_p item) +{ + int error; + + NG_FWD_ITEM_HOOK(error, item, hook); + return (error); +} + +/* + * Removal of the last link destroys the nodeo + */ +static int +nge_disconnect(hook_p hook) +{ + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) { + ng_rmnode_self(NG_HOOK_NODE(hook)); + } + return (0); +} + diff --git a/sys/netgraph7/ng_echo.h b/sys/netgraph7/ng_echo.h new file mode 100644 index 0000000000..483245be31 --- /dev/null +++ b/sys/netgraph7/ng_echo.h @@ -0,0 +1,51 @@ +/* + * ng_echo.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_echo.h,v 1.4 2005/01/07 01:45:39 imp Exp $ + * $Whistle: ng_echo.h,v 1.3 1999/01/20 00:22:12 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_ECHO_H_ +#define _NETGRAPH_NG_ECHO_H_ + +/* Node type name and magic cookie */ +#define NG_ECHO_NODE_TYPE "echo" +#define NGM_ECHO_COOKIE 884298942 + +#endif /* _NETGRAPH_NG_ECHO_H_ */ diff --git a/sys/netgraph7/ng_eiface.c b/sys/netgraph7/ng_eiface.c new file mode 100644 index 0000000000..17d66f71a9 --- /dev/null +++ b/sys/netgraph7/ng_eiface.c @@ -0,0 +1,591 @@ +/*- + * + * Copyright (c) 1999-2001, Vitaly V Belekhov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_eiface.c,v 1.39 2007/07/26 10:54:33 glebius Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +static const struct ng_cmdlist ng_eiface_cmdlist[] = { + { + NGM_EIFACE_COOKIE, + NGM_EIFACE_GET_IFNAME, + "getifname", + NULL, + &ng_parse_string_type + }, + { + NGM_EIFACE_COOKIE, + NGM_EIFACE_SET, + "set", + &ng_parse_enaddr_type, + NULL + }, + { 0 } +}; + +/* Node private data */ +struct ng_eiface_private { + struct ifnet *ifp; /* per-interface network data */ + int unit; /* Interface unit number */ + node_p node; /* Our netgraph node */ + hook_p ether; /* Hook for ethernet stream */ +}; +typedef struct ng_eiface_private *priv_p; + +/* Interface methods */ +static void ng_eiface_init(void *xsc); +static void ng_eiface_start(struct ifnet *ifp); +static int ng_eiface_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data); +#ifdef DEBUG +static void ng_eiface_print_ioctl(struct ifnet *ifp, int cmd, caddr_t data); +#endif + +/* Netgraph methods */ +static int ng_eiface_mod_event(module_t, int, void *); +static ng_constructor_t ng_eiface_constructor; +static ng_rcvmsg_t ng_eiface_rcvmsg; +static ng_shutdown_t ng_eiface_rmnode; +static ng_newhook_t ng_eiface_newhook; +static ng_rcvdata_t ng_eiface_rcvdata; +static ng_disconnect_t ng_eiface_disconnect; + +/* Node type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_EIFACE_NODE_TYPE, + .mod_event = ng_eiface_mod_event, + .constructor = ng_eiface_constructor, + .rcvmsg = ng_eiface_rcvmsg, + .shutdown = ng_eiface_rmnode, + .newhook = ng_eiface_newhook, + .rcvdata = ng_eiface_rcvdata, + .disconnect = ng_eiface_disconnect, + .cmdlist = ng_eiface_cmdlist +}; +NETGRAPH_INIT(eiface, &typestruct); + +static struct unrhdr *ng_eiface_unit; + +/************************************************************************ + INTERFACE STUFF + ************************************************************************/ + +/* + * Process an ioctl for the virtual interface + */ +static int +ng_eiface_ioctl(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct ifreq *const ifr = (struct ifreq *)data; + int s, error = 0; + +#ifdef DEBUG + ng_eiface_print_ioctl(ifp, command, data); +#endif + s = splimp(); + switch (command) { + + /* These two are mostly handled at a higher layer */ + case SIOCSIFADDR: + error = ether_ioctl(ifp, command, data); + break; + case SIOCGIFADDR: + break; + + /* Set flags */ + case SIOCSIFFLAGS: + /* + * If the interface is marked up and stopped, then start it. + * If it is marked down and running, then stop it. + */ + if (ifp->if_flags & IFF_UP) { + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + ifp->if_drv_flags &= ~(IFF_DRV_OACTIVE); + ifp->if_drv_flags |= IFF_DRV_RUNNING; + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | + IFF_DRV_OACTIVE); + } + break; + + /* Set the interface MTU */ + case SIOCSIFMTU: + if (ifr->ifr_mtu > NG_EIFACE_MTU_MAX || + ifr->ifr_mtu < NG_EIFACE_MTU_MIN) + error = EINVAL; + else + ifp->if_mtu = ifr->ifr_mtu; + break; + + /* Stuff that's not supported */ + case SIOCADDMULTI: + case SIOCDELMULTI: + error = 0; + break; + case SIOCSIFPHYS: + error = EOPNOTSUPP; + break; + + default: + error = EINVAL; + break; + } + splx(s); + return (error); +} + +static void +ng_eiface_init(void *xsc) +{ + priv_p sc = xsc; + struct ifnet *ifp = sc->ifp; + int s; + + s = splimp(); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + splx(s); +} + +/* + * We simply relay the packet to the "ether" hook, if it is connected. + * We have been through the netgraph locking and are guaranteed to + * be the only code running in this node at this time. + */ +static void +ng_eiface_start2(node_p node, hook_p hook, void *arg1, int arg2) +{ + struct ifnet *ifp = arg1; + const priv_p priv = (priv_p)ifp->if_softc; + int error = 0; + struct mbuf *m; + + /* Check interface flags */ + + if (!((ifp->if_flags & IFF_UP) && + (ifp->if_drv_flags & IFF_DRV_RUNNING))) + return; + + for (;;) { + /* + * Grab a packet to transmit. + */ + IF_DEQUEUE(&ifp->if_snd, m); + + /* If there's nothing to send, break. */ + if (m == NULL) + break; + + /* + * Berkeley packet filter. + * Pass packet to bpf if there is a listener. + * XXX is this safe? locking? + */ + BPF_MTAP(ifp, m); + + if (ifp->if_flags & IFF_MONITOR) { + ifp->if_ipackets++; + m_freem(m); + continue; + } + + /* + * Send packet; if hook is not connected, mbuf will get + * freed. + */ + NG_SEND_DATA_ONLY(error, priv->ether, m); + + /* Update stats */ + if (error == 0) + ifp->if_opackets++; + else + ifp->if_oerrors++; + } + + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + return; +} + +/* + * This routine is called to deliver a packet out the interface. + * We simply queue the netgraph version to be called when netgraph locking + * allows it to happen. + * Until we know what the rest of the networking code is doing for + * locking, we don't know how we will interact with it. + * Take comfort from the fact that the ifnet struct is part of our + * private info and can't go away while we are queued. + * [Though we don't know it is still there now....] + * it is possible we don't gain anything from this because + * we would like to get the mbuf and queue it as data + * somehow, but we can't and if we did would we solve anything? + */ +static void +ng_eiface_start(struct ifnet *ifp) +{ + + const priv_p priv = (priv_p)ifp->if_softc; + + /* Don't do anything if output is active */ + if (ifp->if_drv_flags & IFF_DRV_OACTIVE) + return; + + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + + if (ng_send_fn(priv->node, NULL, &ng_eiface_start2, ifp, 0) != 0) + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; +} + +#ifdef DEBUG +/* + * Display an ioctl to the virtual interface + */ + +static void +ng_eiface_print_ioctl(struct ifnet *ifp, int command, caddr_t data) +{ + char *str; + + switch (command & IOC_DIRMASK) { + case IOC_VOID: + str = "IO"; + break; + case IOC_OUT: + str = "IOR"; + break; + case IOC_IN: + str = "IOW"; + break; + case IOC_INOUT: + str = "IORW"; + break; + default: + str = "IO??"; + } + log(LOG_DEBUG, "%s: %s('%c', %d, char[%d])\n", + ifp->if_xname, + str, + IOCGROUP(command), + command & 0xff, + IOCPARM_LEN(command)); +} +#endif /* DEBUG */ + +/************************************************************************ + NETGRAPH NODE STUFF + ************************************************************************/ + +/* + * Constructor for a node + */ +static int +ng_eiface_constructor(node_p node) +{ + struct ifnet *ifp; + priv_p priv; + u_char eaddr[6] = {0,0,0,0,0,0}; + + /* Allocate node and interface private structures */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + + ifp = priv->ifp = if_alloc(IFT_ETHER); + if (ifp == NULL) { + free(priv, M_NETGRAPH); + return (ENOSPC); + } + + /* Link them together */ + ifp->if_softc = priv; + + /* Get an interface unit number */ + priv->unit = alloc_unr(ng_eiface_unit); + + /* Link together node and private info */ + NG_NODE_SET_PRIVATE(node, priv); + priv->node = node; + + /* Initialize interface structure */ + if_initname(ifp, NG_EIFACE_EIFACE_NAME, priv->unit); + ifp->if_init = ng_eiface_init; + ifp->if_output = ether_output; + ifp->if_start = ng_eiface_start; + ifp->if_ioctl = ng_eiface_ioctl; + ifp->if_watchdog = NULL; + ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; + ifp->if_flags = (IFF_SIMPLEX | IFF_BROADCAST | IFF_MULTICAST); + +#if 0 + /* Give this node name */ + bzero(ifname, sizeof(ifname)); + sprintf(ifname, "if%s", ifp->if_xname); + (void)ng_name_node(node, ifname); +#endif + + /* Attach the interface */ + ether_ifattach(ifp, eaddr); + + /* Done */ + return (0); +} + +/* + * Give our ok for a hook to be added + */ +static int +ng_eiface_newhook(node_p node, hook_p hook, const char *name) +{ + priv_p priv = NG_NODE_PRIVATE(node); + struct ifnet *ifp = priv->ifp; + + if (strcmp(name, NG_EIFACE_HOOK_ETHER)) + return (EPFNOSUPPORT); + if (priv->ether != NULL) + return (EISCONN); + priv->ether = hook; + NG_HOOK_SET_PRIVATE(hook, &priv->ether); + + if_link_state_change(ifp, LINK_STATE_UP); + + return (0); +} + +/* + * Receive a control message + */ +static int +ng_eiface_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ifnet *const ifp = priv->ifp; + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_EIFACE_COOKIE: + switch (msg->header.cmd) { + + case NGM_EIFACE_SET: + { + if (msg->header.arglen != ETHER_ADDR_LEN) { + error = EINVAL; + break; + } + error = if_setlladdr(priv->ifp, + (u_char *)msg->data, ETHER_ADDR_LEN); + break; + } + + case NGM_EIFACE_GET_IFNAME: + NG_MKRESPONSE(resp, msg, IFNAMSIZ, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + strlcpy(resp->data, ifp->if_xname, IFNAMSIZ); + break; + + case NGM_EIFACE_GET_IFADDRS: + { + struct ifaddr *ifa; + caddr_t ptr; + int buflen; + +#define SA_SIZE(s) ((s)->sa_lensa_len) + + /* Determine size of response and allocate it */ + buflen = 0; + TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) + buflen += SA_SIZE(ifa->ifa_addr); + NG_MKRESPONSE(resp, msg, buflen, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + + /* Add addresses */ + ptr = resp->data; + TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { + const int len = SA_SIZE(ifa->ifa_addr); + + if (buflen < len) { + log(LOG_ERR, "%s: len changed?\n", + ifp->if_xname); + break; + } + bcopy(ifa->ifa_addr, ptr, len); + ptr += len; + buflen -= len; + } + break; +#undef SA_SIZE + } + + default: + error = EINVAL; + break; + } /* end of inner switch() */ + break; + case NGM_FLOW_COOKIE: + switch (msg->header.cmd) { + case NGM_LINK_IS_UP: + if_link_state_change(ifp, LINK_STATE_UP); + break; + case NGM_LINK_IS_DOWN: + if_link_state_change(ifp, LINK_STATE_DOWN); + break; + default: + break; + } + break; + default: + error = EINVAL; + break; + } + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive data from a hook. Pass the packet to the ether_input routine. + */ +static int +ng_eiface_rcvdata(hook_p hook, item_p item) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct ifnet *const ifp = priv->ifp; + struct mbuf *m; + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + if (!((ifp->if_flags & IFF_UP) && + (ifp->if_drv_flags & IFF_DRV_RUNNING))) { + NG_FREE_M(m); + return (ENETDOWN); + } + + if (m->m_len < ETHER_HDR_LEN) { + m = m_pullup(m, ETHER_HDR_LEN); + if (m == NULL) + return (EINVAL); + } + + /* Note receiving interface */ + m->m_pkthdr.rcvif = ifp; + + /* Update interface stats */ + ifp->if_ipackets++; + + (*ifp->if_input)(ifp, m); + + /* Done */ + return (0); +} + +/* + * Shutdown processing. + */ +static int +ng_eiface_rmnode(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ifnet *const ifp = priv->ifp; + + ether_ifdetach(ifp); + if_free(ifp); + free_unr(ng_eiface_unit, priv->unit); + FREE(priv, M_NETGRAPH); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + return (0); +} + +/* + * Hook disconnection + */ +static int +ng_eiface_disconnect(hook_p hook) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + priv->ether = NULL; + return (0); +} + +/* + * Handle loading and unloading for this node type. + */ +static int +ng_eiface_mod_event(module_t mod, int event, void *data) +{ + int error = 0; + + switch (event) { + case MOD_LOAD: + ng_eiface_unit = new_unrhdr(0, 0xffff, NULL); + break; + case MOD_UNLOAD: + delete_unrhdr(ng_eiface_unit); + break; + default: + error = EOPNOTSUPP; + break; + } + return (error); +} diff --git a/sys/netgraph7/ng_eiface.h b/sys/netgraph7/ng_eiface.h new file mode 100644 index 0000000000..e670d9826c --- /dev/null +++ b/sys/netgraph7/ng_eiface.h @@ -0,0 +1,59 @@ +/* + * ng_eiface.h + */ + +/*- + * Copyright (c) 1999-2001, Vitaly V Belekhov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_eiface.h,v 1.9 2005/02/03 11:52:42 ru Exp $ + */ + +#ifndef _NETGRAPH_NG_EIFACE_H_ +#define _NETGRAPH_NG_EIFACE_H_ + +/* Node type name and magic cookie */ +#define NG_EIFACE_NODE_TYPE "eiface" +#define NGM_EIFACE_COOKIE 948105892 + +/* Interface base name */ +#define NG_EIFACE_EIFACE_NAME "ngeth" + +/* My hook names */ +#define NG_EIFACE_HOOK_ETHER "ether" + +/* MTU bounds */ +#define NG_EIFACE_MTU_MIN 72 +#define NG_EIFACE_MTU_MAX 2312 +#define NG_EIFACE_MTU_DEFAULT 1500 + +/* Netgraph commands */ +enum { + NGM_EIFACE_GET_IFNAME = 1, /* get the interface name */ + NGM_EIFACE_GET_IFADDRS, /* returns list of addresses */ + NGM_EIFACE_SET, /* set ethernet address */ +}; + +#endif /* _NETGRAPH_NG_EIFACE_H_ */ diff --git a/sys/netgraph7/ng_etf.c b/sys/netgraph7/ng_etf.c new file mode 100644 index 0000000000..5a01d67ecb --- /dev/null +++ b/sys/netgraph7/ng_etf.c @@ -0,0 +1,489 @@ +/*- + * ng_etf.c Ethertype filter + */ + +/*- + * Copyright (c) 2001, FreeBSD Incorporated + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_etf.c,v 1.9 2005/03/14 20:49:48 glebius Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +/* If you do complicated mallocs you may want to do this */ +/* and use it for your mallocs */ +#ifdef NG_SEPARATE_MALLOC +static MALLOC_DEFINE(M_NETGRAPH_ETF, "netgraph_etf", "netgraph etf node "); +#else +#define M_NETGRAPH_ETF M_NETGRAPH +#endif + +/* + * This section contains the netgraph method declarations for the + * etf node. These methods define the netgraph 'type'. + */ + +static ng_constructor_t ng_etf_constructor; +static ng_rcvmsg_t ng_etf_rcvmsg; +static ng_shutdown_t ng_etf_shutdown; +static ng_newhook_t ng_etf_newhook; +static ng_rcvdata_t ng_etf_rcvdata; /* note these are both ng_rcvdata_t */ +static ng_disconnect_t ng_etf_disconnect; + +/* Parse type for struct ng_etfstat */ +static const struct ng_parse_struct_field ng_etf_stat_type_fields[] + = NG_ETF_STATS_TYPE_INFO; +static const struct ng_parse_type ng_etf_stat_type = { + &ng_parse_struct_type, + &ng_etf_stat_type_fields +}; +/* Parse type for struct ng_setfilter */ +static const struct ng_parse_struct_field ng_etf_filter_type_fields[] + = NG_ETF_FILTER_TYPE_INFO; +static const struct ng_parse_type ng_etf_filter_type = { + &ng_parse_struct_type, + &ng_etf_filter_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_etf_cmdlist[] = { + { + NGM_ETF_COOKIE, + NGM_ETF_GET_STATUS, + "getstatus", + NULL, + &ng_etf_stat_type, + }, + { + NGM_ETF_COOKIE, + NGM_ETF_SET_FLAG, + "setflag", + &ng_parse_int32_type, + NULL + }, + { + NGM_ETF_COOKIE, + NGM_ETF_SET_FILTER, + "setfilter", + &ng_etf_filter_type, + NULL + }, + { 0 } +}; + +/* Netgraph node type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_ETF_NODE_TYPE, + .constructor = ng_etf_constructor, + .rcvmsg = ng_etf_rcvmsg, + .shutdown = ng_etf_shutdown, + .newhook = ng_etf_newhook, + .rcvdata = ng_etf_rcvdata, + .disconnect = ng_etf_disconnect, + .cmdlist = ng_etf_cmdlist, +}; +NETGRAPH_INIT(etf, &typestruct); + +/* Information we store for each hook on each node */ +struct ETF_hookinfo { + hook_p hook; +}; + +struct filter { + LIST_ENTRY(filter) next; + u_int16_t ethertype; /* network order ethertype */ + hook_p match_hook; /* Hook to use on a match */ +}; + +#define HASHSIZE 16 /* Dont change this without changing HASH() */ +#define HASH(et) ((((et)>>12)+((et)>>8)+((et)>>4)+(et)) & 0x0f) +LIST_HEAD(filterhead, filter); + +/* Information we store for each node */ +struct ETF { + struct ETF_hookinfo downstream_hook; + struct ETF_hookinfo nomatch_hook; + node_p node; /* back pointer to node */ + u_int packets_in; /* packets in from downstream */ + u_int packets_out; /* packets out towards downstream */ + u_int32_t flags; + struct filterhead hashtable[HASHSIZE]; +}; +typedef struct ETF *etf_p; + +static struct filter * +ng_etf_findentry(etf_p etfp, u_int16_t ethertype) +{ + struct filterhead *chain = etfp->hashtable + HASH(ethertype); + struct filter *fil; + + + LIST_FOREACH(fil, chain, next) { + if (fil->ethertype == ethertype) { + return (fil); + } + } + return (NULL); +} + + +/* + * Allocate the private data structure. The generic node has already + * been created. Link them together. We arrive with a reference to the node + * i.e. the reference count is incremented for us already. + */ +static int +ng_etf_constructor(node_p node) +{ + etf_p privdata; + int i; + + /* Initialize private descriptor */ + MALLOC(privdata, etf_p, sizeof(*privdata), M_NETGRAPH_ETF, + M_NOWAIT | M_ZERO); + if (privdata == NULL) + return (ENOMEM); + for (i = 0; i < HASHSIZE; i++) { + LIST_INIT((privdata->hashtable + i)); + } + + /* Link structs together; this counts as our one reference to node */ + NG_NODE_SET_PRIVATE(node, privdata); + privdata->node = node; + return (0); +} + +/* + * Give our ok for a hook to be added... + * All names are ok. Two names are special. + */ +static int +ng_etf_newhook(node_p node, hook_p hook, const char *name) +{ + const etf_p etfp = NG_NODE_PRIVATE(node); + struct ETF_hookinfo *hpriv; + + if (strcmp(name, NG_ETF_HOOK_DOWNSTREAM) == 0) { + etfp->downstream_hook.hook = hook; + NG_HOOK_SET_PRIVATE(hook, &etfp->downstream_hook); + etfp->packets_in = 0; + etfp->packets_out = 0; + } else if (strcmp(name, NG_ETF_HOOK_NOMATCH) == 0) { + etfp->nomatch_hook.hook = hook; + NG_HOOK_SET_PRIVATE(hook, &etfp->nomatch_hook); + } else { + /* + * Any other hook name is valid and can + * later be associated with a filter rule. + */ + MALLOC(hpriv, struct ETF_hookinfo *, sizeof(*hpriv), + M_NETGRAPH_ETF, M_NOWAIT | M_ZERO); + if (hpriv == NULL) { + return (ENOMEM); + } + + NG_HOOK_SET_PRIVATE(hook, hpriv); + hpriv->hook = hook; + } + return(0); +} + +/* + * Get a netgraph control message. + * We actually recieve a queue item that has a pointer to the message. + * If we free the item, the message will be freed too, unless we remove + * it from the item using NGI_GET_MSG(); + * The return address is also stored in the item, as an ng_ID_t, + * accessible as NGI_RETADDR(item); + * Check it is one we understand. If needed, send a response. + * We could save the address for an async action later, but don't here. + * Always free the message. + * The response should be in a malloc'd region that the caller can 'free'. + * The NG_MKRESPONSE macro does all this for us. + * A response is not required. + * Theoretically you could respond defferently to old message types if + * the cookie in the header didn't match what we consider to be current + * (so that old userland programs could continue to work). + */ +static int +ng_etf_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const etf_p etfp = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + /* Deal with message according to cookie and command */ + switch (msg->header.typecookie) { + case NGM_ETF_COOKIE: + switch (msg->header.cmd) { + case NGM_ETF_GET_STATUS: + { + struct ng_etfstat *stats; + + NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); + if (!resp) { + error = ENOMEM; + break; + } + stats = (struct ng_etfstat *) resp->data; + stats->packets_in = etfp->packets_in; + stats->packets_out = etfp->packets_out; + break; + } + case NGM_ETF_SET_FLAG: + if (msg->header.arglen != sizeof(u_int32_t)) { + error = EINVAL; + break; + } + etfp->flags = *((u_int32_t *) msg->data); + break; + case NGM_ETF_SET_FILTER: + { + struct ng_etffilter *f; + struct filter *fil; + hook_p hook; + + /* Check message long enough for this command */ + if (msg->header.arglen != sizeof(*f)) { + error = EINVAL; + break; + } + + /* Make sure hook referenced exists */ + f = (struct ng_etffilter *)msg->data; + hook = ng_findhook(node, f->matchhook); + if (hook == NULL) { + error = ENOENT; + break; + } + + /* and is not the downstream hook */ + if (hook == etfp->downstream_hook.hook) { + error = EINVAL; + break; + } + + /* Check we don't already trap this ethertype */ + if (ng_etf_findentry(etfp, + htons(f->ethertype))) { + error = EEXIST; + break; + } + + /* + * Ok, make the filter and put it in the + * hashtable ready for matching. + */ + MALLOC(fil, struct filter *, sizeof(*fil), + M_NETGRAPH_ETF, M_NOWAIT | M_ZERO); + if (fil == NULL) { + error = ENOMEM; + break; + } + + fil->match_hook = hook; + fil->ethertype = htons(f->ethertype); + LIST_INSERT_HEAD( etfp->hashtable + + HASH(fil->ethertype), + fil, next); + } + break; + default: + error = EINVAL; /* unknown command */ + break; + } + break; + default: + error = EINVAL; /* unknown cookie type */ + break; + } + + /* Take care of synchronous response, if any */ + NG_RESPOND_MSG(error, node, item, resp); + /* Free the message and return */ + NG_FREE_MSG(msg); + return(error); +} + +/* + * Receive data, and do something with it. + * Actually we receive a queue item which holds the data. + * If we free the item it will also free the data unless we have previously + * disassociated it using the NGI_GET_etf() macro. + * Possibly send it out on another link after processing. + * Possibly do something different if it comes from different + * hooks. The caller will never free m , so if we use up this data + * or abort we must free it. + * + * If we want, we may decide to force this data to be queued and reprocessed + * at the netgraph NETISR time. + * We would do that by setting the HK_QUEUE flag on our hook. We would do that + * in the connect() method. + */ +static int +ng_etf_rcvdata(hook_p hook, item_p item ) +{ + const etf_p etfp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct ether_header *eh; + int error = 0; + struct mbuf *m; + u_int16_t ethertype; + struct filter *fil; + + if (NG_HOOK_PRIVATE(hook) == NULL) { /* Shouldn't happen but.. */ + NG_FREE_ITEM(item); + } + + /* + * Everything not from the downstream hook goes to the + * downstream hook. But only if it matches the ethertype + * of the source hook. Un matching must go to/from 'nomatch'. + */ + + /* Make sure we have an entire header */ + NGI_GET_M(item, m); + if (m->m_len < sizeof(*eh) ) { + m = m_pullup(m, sizeof(*eh)); + if (m == NULL) { + NG_FREE_ITEM(item); + return(EINVAL); + } + } + + eh = mtod(m, struct ether_header *); + ethertype = eh->ether_type; + fil = ng_etf_findentry(etfp, ethertype); + + /* + * if from downstream, select between a match hook or + * the nomatch hook + */ + if (hook == etfp->downstream_hook.hook) { + etfp->packets_in++; + if (fil && fil->match_hook) { + NG_FWD_NEW_DATA(error, item, fil->match_hook, m); + } else { + NG_FWD_NEW_DATA(error, item,etfp->nomatch_hook.hook, m); + } + } else { + /* + * It must be heading towards the downstream. + * Check that it's ethertype matches + * the filters for it's input hook. + * If it doesn't have one, check it's from nomatch. + */ + if ((fil && (fil->match_hook != hook)) + || ((fil == NULL) && (hook != etfp->nomatch_hook.hook))) { + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (EPROTOTYPE); + } + NG_FWD_NEW_DATA( error, item, etfp->downstream_hook.hook, m); + if (error == 0) { + etfp->packets_out++; + } + } + return (error); +} + +/* + * Do local shutdown processing.. + * All our links and the name have already been removed. + */ +static int +ng_etf_shutdown(node_p node) +{ + const etf_p privdata = NG_NODE_PRIVATE(node); + + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(privdata->node); + FREE(privdata, M_NETGRAPH_ETF); + return (0); +} + +/* + * Hook disconnection + * + * For this type, removal of the last link destroys the node + */ +static int +ng_etf_disconnect(hook_p hook) +{ + const etf_p etfp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + int i; + struct filter *fil1, *fil2; + + /* purge any rules that refer to this filter */ + for (i = 0; i < HASHSIZE; i++) { + fil1 = LIST_FIRST(&etfp->hashtable[i]); + while (fil1 != NULL) { + fil2 = LIST_NEXT(fil1, next); + if (fil1->match_hook == hook) { + LIST_REMOVE(fil1, next); + FREE(fil1, M_NETGRAPH_ETF); + } + fil1 = fil2; + } + } + + /* If it's not one of the special hooks, then free it */ + if (hook == etfp->downstream_hook.hook) { + etfp->downstream_hook.hook = NULL; + } else if (hook == etfp->nomatch_hook.hook) { + etfp->nomatch_hook.hook = NULL; + } else { + if (NG_HOOK_PRIVATE(hook)) /* Paranoia */ + FREE(NG_HOOK_PRIVATE(hook), M_NETGRAPH_ETF); + } + + NG_HOOK_SET_PRIVATE(hook, NULL); + + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) /* already shutting down? */ + ng_rmnode_self(NG_HOOK_NODE(hook)); + return (0); +} + diff --git a/sys/netgraph7/ng_etf.h b/sys/netgraph7/ng_etf.h new file mode 100644 index 0000000000..3ae8441ff6 --- /dev/null +++ b/sys/netgraph7/ng_etf.h @@ -0,0 +1,90 @@ +/*- + * ng_etf.h + */ + +/*- + * Copyright (c) 2001, FreeBSD Incorporated + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_etf.h,v 1.5 2005/01/07 01:45:39 imp Exp $ + */ + +#ifndef _NETGRAPH_NG_ETF_H_ +#define _NETGRAPH_NG_ETF_H_ + +/* Node type name. This should be unique among all netgraph node types */ +#define NG_ETF_NODE_TYPE "etf" + +/* Node type cookie. Should also be unique. This value MUST change whenever + an incompatible change is made to this header file, to insure consistency. + The de facto method for generating cookies is to take the output of the + date command: date -u +'%s' */ +#define NGM_ETF_COOKIE 983084516 + +/* Hook names */ +#define NG_ETF_HOOK_DOWNSTREAM "downstream" +#define NG_ETF_HOOK_NOMATCH "nomatch" + +/* Netgraph commands understood by this node type */ +enum { + NGM_ETF_SET_FLAG = 1, + NGM_ETF_GET_STATUS, + NGM_ETF_SET_FILTER, + +}; + +/* This structure is returned by the NGM_ETF_GET_STATUS command */ +struct ng_etfstat { + u_int32_t packets_in; /* packets in from downstream */ + u_int32_t packets_out; /* packets out towards downstream */ +}; + +/* + * This needs to be kept in sync with the above structure definition + */ +#define NG_ETF_STATS_TYPE_INFO { \ + { "packets_in", &ng_parse_uint32_type }, \ + { "packets_out", &ng_parse_uint32_type }, \ + { NULL } \ +} + +/* This structure is returned by the NGM_ETF_GET_STATUS command */ +struct ng_etffilter { + char matchhook[NG_HOOKSIZ]; /* hook name */ + u_int16_t ethertype; /* this ethertype to this hook */ +}; + +/* + * This needs to be kept in sync with the above structure definition + */ +#define NG_ETF_FILTER_TYPE_INFO { \ + { "matchhook", &ng_parse_hookbuf_type }, \ + { "ethertype", &ng_parse_uint16_type }, \ + { NULL } \ +} + +#endif /* _NETGRAPH_NG_ETF_H_ */ diff --git a/sys/netgraph7/ng_ether.c b/sys/netgraph7/ng_ether.c new file mode 100644 index 0000000000..fe589c8347 --- /dev/null +++ b/sys/netgraph7/ng_ether.c @@ -0,0 +1,789 @@ + +/* + * ng_ether.c + */ + +/*- + * Copyright (c) 1996-2000 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Authors: Archie Cobbs + * Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_ether.c,v 1.62 2007/03/20 00:36:10 bms Exp $ + */ + +/* + * ng_ether(4) netgraph node type + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define IFP2NG(ifp) (IFP2AC((ifp))->ac_netgraph) + +/* Per-node private data */ +struct private { + struct ifnet *ifp; /* associated interface */ + hook_p upper; /* upper hook connection */ + hook_p lower; /* lower hook connection */ + hook_p orphan; /* orphan hook connection */ + u_char autoSrcAddr; /* always overwrite source address */ + u_char promisc; /* promiscuous mode enabled */ + u_long hwassist; /* hardware checksum capabilities */ + u_int flags; /* flags e.g. really die */ +}; +typedef struct private *priv_p; + +/* Hook pointers used by if_ethersubr.c to callback to netgraph */ +extern void (*ng_ether_input_p)(struct ifnet *ifp, struct mbuf **mp); +extern void (*ng_ether_input_orphan_p)(struct ifnet *ifp, struct mbuf *m); +extern int (*ng_ether_output_p)(struct ifnet *ifp, struct mbuf **mp); +extern void (*ng_ether_attach_p)(struct ifnet *ifp); +extern void (*ng_ether_detach_p)(struct ifnet *ifp); +extern void (*ng_ether_link_state_p)(struct ifnet *ifp, int state); + +/* Functional hooks called from if_ethersubr.c */ +static void ng_ether_input(struct ifnet *ifp, struct mbuf **mp); +static void ng_ether_input_orphan(struct ifnet *ifp, struct mbuf *m); +static int ng_ether_output(struct ifnet *ifp, struct mbuf **mp); +static void ng_ether_attach(struct ifnet *ifp); +static void ng_ether_detach(struct ifnet *ifp); +static void ng_ether_link_state(struct ifnet *ifp, int state); + +/* Other functions */ +static int ng_ether_rcv_lower(node_p node, struct mbuf *m); +static int ng_ether_rcv_upper(node_p node, struct mbuf *m); + +/* Netgraph node methods */ +static ng_constructor_t ng_ether_constructor; +static ng_rcvmsg_t ng_ether_rcvmsg; +static ng_shutdown_t ng_ether_shutdown; +static ng_newhook_t ng_ether_newhook; +static ng_rcvdata_t ng_ether_rcvdata; +static ng_disconnect_t ng_ether_disconnect; +static int ng_ether_mod_event(module_t mod, int event, void *data); + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_ether_cmdlist[] = { + { + NGM_ETHER_COOKIE, + NGM_ETHER_GET_IFNAME, + "getifname", + NULL, + &ng_parse_string_type + }, + { + NGM_ETHER_COOKIE, + NGM_ETHER_GET_IFINDEX, + "getifindex", + NULL, + &ng_parse_int32_type + }, + { + NGM_ETHER_COOKIE, + NGM_ETHER_GET_ENADDR, + "getenaddr", + NULL, + &ng_parse_enaddr_type + }, + { + NGM_ETHER_COOKIE, + NGM_ETHER_SET_ENADDR, + "setenaddr", + &ng_parse_enaddr_type, + NULL + }, + { + NGM_ETHER_COOKIE, + NGM_ETHER_GET_PROMISC, + "getpromisc", + NULL, + &ng_parse_int32_type + }, + { + NGM_ETHER_COOKIE, + NGM_ETHER_SET_PROMISC, + "setpromisc", + &ng_parse_int32_type, + NULL + }, + { + NGM_ETHER_COOKIE, + NGM_ETHER_GET_AUTOSRC, + "getautosrc", + NULL, + &ng_parse_int32_type + }, + { + NGM_ETHER_COOKIE, + NGM_ETHER_SET_AUTOSRC, + "setautosrc", + &ng_parse_int32_type, + NULL + }, + { + NGM_ETHER_COOKIE, + NGM_ETHER_ADD_MULTI, + "addmulti", + &ng_parse_enaddr_type, + NULL + }, + { + NGM_ETHER_COOKIE, + NGM_ETHER_DEL_MULTI, + "delmulti", + &ng_parse_enaddr_type, + NULL + }, + { + NGM_ETHER_COOKIE, + NGM_ETHER_DETACH, + "detach", + NULL, + NULL + }, + { 0 } +}; + +static struct ng_type ng_ether_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_ETHER_NODE_TYPE, + .mod_event = ng_ether_mod_event, + .constructor = ng_ether_constructor, + .rcvmsg = ng_ether_rcvmsg, + .shutdown = ng_ether_shutdown, + .newhook = ng_ether_newhook, + .rcvdata = ng_ether_rcvdata, + .disconnect = ng_ether_disconnect, + .cmdlist = ng_ether_cmdlist, +}; +NETGRAPH_INIT(ether, &ng_ether_typestruct); + +/****************************************************************** + ETHERNET FUNCTION HOOKS +******************************************************************/ + +/* + * Handle a packet that has come in on an interface. We get to + * look at it here before any upper layer protocols do. + * + * NOTE: this function will get called at splimp() + */ +static void +ng_ether_input(struct ifnet *ifp, struct mbuf **mp) +{ + const node_p node = IFP2NG(ifp); + const priv_p priv = NG_NODE_PRIVATE(node); + int error; + + /* If "lower" hook not connected, let packet continue */ + if (priv->lower == NULL) + return; + NG_SEND_DATA_ONLY(error, priv->lower, *mp); /* sets *mp = NULL */ +} + +/* + * Handle a packet that has come in on an interface, and which + * does not match any of our known protocols (an ``orphan''). + * + * NOTE: this function will get called at splimp() + */ +static void +ng_ether_input_orphan(struct ifnet *ifp, struct mbuf *m) +{ + const node_p node = IFP2NG(ifp); + const priv_p priv = NG_NODE_PRIVATE(node); + int error; + + /* If "orphan" hook not connected, discard packet */ + if (priv->orphan == NULL) { + m_freem(m); + return; + } + NG_SEND_DATA_ONLY(error, priv->orphan, m); +} + +/* + * Handle a packet that is going out on an interface. + * The Ethernet header is already attached to the mbuf. + */ +static int +ng_ether_output(struct ifnet *ifp, struct mbuf **mp) +{ + const node_p node = IFP2NG(ifp); + const priv_p priv = NG_NODE_PRIVATE(node); + int error = 0; + + /* If "upper" hook not connected, let packet continue */ + if (priv->upper == NULL) + return (0); + + /* Send it out "upper" hook */ + NG_SEND_DATA_ONLY(error, priv->upper, *mp); + return (error); +} + +/* + * A new Ethernet interface has been attached. + * Create a new node for it, etc. + */ +static void +ng_ether_attach(struct ifnet *ifp) +{ + priv_p priv; + node_p node; + + /* Create node */ + KASSERT(!IFP2NG(ifp), ("%s: node already exists?", __func__)); + if (ng_make_node_common(&ng_ether_typestruct, &node) != 0) { + log(LOG_ERR, "%s: can't %s for %s\n", + __func__, "create node", ifp->if_xname); + return; + } + + /* Allocate private data */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (priv == NULL) { + log(LOG_ERR, "%s: can't %s for %s\n", + __func__, "allocate memory", ifp->if_xname); + NG_NODE_UNREF(node); + return; + } + NG_NODE_SET_PRIVATE(node, priv); + priv->ifp = ifp; + IFP2NG(ifp) = node; + priv->hwassist = ifp->if_hwassist; + + /* Try to give the node the same name as the interface */ + if (ng_name_node(node, ifp->if_xname) != 0) { + log(LOG_WARNING, "%s: can't name node %s\n", + __func__, ifp->if_xname); + } +} + +/* + * An Ethernet interface is being detached. + * REALLY Destroy its node. + */ +static void +ng_ether_detach(struct ifnet *ifp) +{ + const node_p node = IFP2NG(ifp); + const priv_p priv = NG_NODE_PRIVATE(node); + + NG_NODE_REALLY_DIE(node); /* Force real removal of node */ + /* + * We can't assume the ifnet is still around when we run shutdown + * So zap it now. XXX We HOPE that anything running at this time + * handles it (as it should in the non netgraph case). + */ + IFP2NG(ifp) = NULL; + priv->ifp = NULL; /* XXX race if interrupted an output packet */ + ng_rmnode_self(node); /* remove all netgraph parts */ +} + +/* + * Notify graph about link event. + * if_link_state_change() has already checked that the state has changed. + */ +static void +ng_ether_link_state(struct ifnet *ifp, int state) +{ + const node_p node = IFP2NG(ifp); + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *msg; + int cmd, dummy_error = 0; + + if (priv->lower == NULL) + return; + + if (state == LINK_STATE_UP) + cmd = NGM_LINK_IS_UP; + else if (state == LINK_STATE_DOWN) + cmd = NGM_LINK_IS_DOWN; + else + return; + + NG_MKMESSAGE(msg, NGM_FLOW_COOKIE, cmd, 0, M_NOWAIT); + if (msg != NULL) + NG_SEND_MSG_HOOK(dummy_error, node, msg, priv->lower, 0); +} + +/****************************************************************** + NETGRAPH NODE METHODS +******************************************************************/ + +/* + * It is not possible or allowable to create a node of this type. + * Nodes get created when the interface is attached (or, when + * this node type's KLD is loaded). + */ +static int +ng_ether_constructor(node_p node) +{ + return (EINVAL); +} + +/* + * Check for attaching a new hook. + */ +static int +ng_ether_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + hook_p *hookptr; + + /* Divert hook is an alias for lower */ + if (strcmp(name, NG_ETHER_HOOK_DIVERT) == 0) + name = NG_ETHER_HOOK_LOWER; + + /* Which hook? */ + if (strcmp(name, NG_ETHER_HOOK_UPPER) == 0) + hookptr = &priv->upper; + else if (strcmp(name, NG_ETHER_HOOK_LOWER) == 0) + hookptr = &priv->lower; + else if (strcmp(name, NG_ETHER_HOOK_ORPHAN) == 0) + hookptr = &priv->orphan; + else + return (EINVAL); + + /* Check if already connected (shouldn't be, but doesn't hurt) */ + if (*hookptr != NULL) + return (EISCONN); + + /* Disable hardware checksums while 'upper' hook is connected */ + if (hookptr == &priv->upper) + priv->ifp->if_hwassist = 0; + + /* OK */ + *hookptr = hook; + return (0); +} + +/* + * Receive an incoming control message. + */ +static int +ng_ether_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_ETHER_COOKIE: + switch (msg->header.cmd) { + case NGM_ETHER_GET_IFNAME: + NG_MKRESPONSE(resp, msg, IFNAMSIZ, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + strlcpy(resp->data, priv->ifp->if_xname, IFNAMSIZ); + break; + case NGM_ETHER_GET_IFINDEX: + NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + *((u_int32_t *)resp->data) = priv->ifp->if_index; + break; + case NGM_ETHER_GET_ENADDR: + NG_MKRESPONSE(resp, msg, ETHER_ADDR_LEN, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + bcopy(IF_LLADDR(priv->ifp), + resp->data, ETHER_ADDR_LEN); + break; + case NGM_ETHER_SET_ENADDR: + { + if (msg->header.arglen != ETHER_ADDR_LEN) { + error = EINVAL; + break; + } + error = if_setlladdr(priv->ifp, + (u_char *)msg->data, ETHER_ADDR_LEN); + break; + } + case NGM_ETHER_GET_PROMISC: + NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + *((u_int32_t *)resp->data) = priv->promisc; + break; + case NGM_ETHER_SET_PROMISC: + { + u_char want; + + if (msg->header.arglen != sizeof(u_int32_t)) { + error = EINVAL; + break; + } + want = !!*((u_int32_t *)msg->data); + if (want ^ priv->promisc) { + if ((error = ifpromisc(priv->ifp, want)) != 0) + break; + priv->promisc = want; + } + break; + } + case NGM_ETHER_GET_AUTOSRC: + NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + *((u_int32_t *)resp->data) = priv->autoSrcAddr; + break; + case NGM_ETHER_SET_AUTOSRC: + if (msg->header.arglen != sizeof(u_int32_t)) { + error = EINVAL; + break; + } + priv->autoSrcAddr = !!*((u_int32_t *)msg->data); + break; + case NGM_ETHER_ADD_MULTI: + { + struct sockaddr_dl sa_dl; + struct ifmultiaddr *ifma; + + if (msg->header.arglen != ETHER_ADDR_LEN) { + error = EINVAL; + break; + } + bzero(&sa_dl, sizeof(struct sockaddr_dl)); + sa_dl.sdl_len = sizeof(struct sockaddr_dl); + sa_dl.sdl_family = AF_LINK; + sa_dl.sdl_alen = ETHER_ADDR_LEN; + bcopy((void *)msg->data, LLADDR(&sa_dl), + ETHER_ADDR_LEN); + /* + * Netgraph is only permitted to join groups once + * via the if_addmulti() KPI, because it cannot hold + * struct ifmultiaddr * between calls. It may also + * lose a race while we check if the membership + * already exists. + */ + IF_ADDR_LOCK(priv->ifp); + ifma = if_findmulti(priv->ifp, + (struct sockaddr *)&sa_dl); + IF_ADDR_UNLOCK(priv->ifp); + if (ifma != NULL) { + error = EADDRINUSE; + } else { + error = if_addmulti(priv->ifp, + (struct sockaddr *)&sa_dl, &ifma); + } + break; + } + case NGM_ETHER_DEL_MULTI: + { + struct sockaddr_dl sa_dl; + + if (msg->header.arglen != ETHER_ADDR_LEN) { + error = EINVAL; + break; + } + bzero(&sa_dl, sizeof(struct sockaddr_dl)); + sa_dl.sdl_len = sizeof(struct sockaddr_dl); + sa_dl.sdl_family = AF_LINK; + sa_dl.sdl_alen = ETHER_ADDR_LEN; + bcopy((void *)msg->data, LLADDR(&sa_dl), + ETHER_ADDR_LEN); + error = if_delmulti(priv->ifp, + (struct sockaddr *)&sa_dl); + break; + } + case NGM_ETHER_DETACH: + ng_ether_detach(priv->ifp); + break; + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive data on a hook. + */ +static int +ng_ether_rcvdata(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + struct mbuf *m; + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + if (hook == priv->lower || hook == priv->orphan) + return ng_ether_rcv_lower(node, m); + if (hook == priv->upper) + return ng_ether_rcv_upper(node, m); + panic("%s: weird hook", __func__); +#ifdef RESTARTABLE_PANICS /* so we don't get an error msg in LINT */ + return (0); +#endif +} + +/* + * Handle an mbuf received on the "lower" or "orphan" hook. + */ +static int +ng_ether_rcv_lower(node_p node, struct mbuf *m) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ifnet *const ifp = priv->ifp; + + /* Check whether interface is ready for packets */ + if (!((ifp->if_flags & IFF_UP) && + (ifp->if_drv_flags & IFF_DRV_RUNNING))) { + NG_FREE_M(m); + return (ENETDOWN); + } + + /* Make sure header is fully pulled up */ + if (m->m_pkthdr.len < sizeof(struct ether_header)) { + NG_FREE_M(m); + return (EINVAL); + } + if (m->m_len < sizeof(struct ether_header) + && (m = m_pullup(m, sizeof(struct ether_header))) == NULL) + return (ENOBUFS); + + /* Drop in the MAC address if desired */ + if (priv->autoSrcAddr) { + + /* Make the mbuf writable if it's not already */ + if (!M_WRITABLE(m) + && (m = m_pullup(m, sizeof(struct ether_header))) == NULL) + return (ENOBUFS); + + /* Overwrite source MAC address */ + bcopy(IF_LLADDR(ifp), + mtod(m, struct ether_header *)->ether_shost, + ETHER_ADDR_LEN); + } + + /* Send it on its way */ + return ether_output_frame(ifp, m); +} + +/* + * Handle an mbuf received on the "upper" hook. + */ +static int +ng_ether_rcv_upper(node_p node, struct mbuf *m) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ifnet *ifp = priv->ifp; + + /* Check length and pull off header */ + if (m->m_pkthdr.len < sizeof(struct ether_header)) { + NG_FREE_M(m); + return (EINVAL); + } + if (m->m_len < sizeof(struct ether_header) && + (m = m_pullup(m, sizeof(struct ether_header))) == NULL) + return (ENOBUFS); + + m->m_pkthdr.rcvif = ifp; + + /* Pass the packet to the bridge, it may come back to us */ + if (ifp->if_bridge) { + BRIDGE_INPUT(ifp, m); + if (m == NULL) + return (0); + } + + /* Route packet back in */ + ether_demux(ifp, m); + return (0); +} + +/* + * Shutdown node. This resets the node but does not remove it + * unless the REALLY_DIE flag is set. + */ +static int +ng_ether_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (node->nd_flags & NGF_REALLY_DIE) { + /* + * WE came here because the ethernet card is being unloaded, + * so stop being persistant. + * Actually undo all the things we did on creation. + * Assume the ifp has already been freed. + */ + NG_NODE_SET_PRIVATE(node, NULL); + FREE(priv, M_NETGRAPH); + NG_NODE_UNREF(node); /* free node itself */ + return (0); + } + if (priv->promisc) { /* disable promiscuous mode */ + (void)ifpromisc(priv->ifp, 0); + priv->promisc = 0; + } + priv->autoSrcAddr = 1; /* reset auto-src-addr flag */ + NG_NODE_REVIVE(node); /* Signal ng_rmnode we are persisant */ + + return (0); +} + +/* + * Hook disconnection. + */ +static int +ng_ether_disconnect(hook_p hook) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + if (hook == priv->upper) { + priv->upper = NULL; + if (priv->ifp != NULL) /* restore h/w csum */ + priv->ifp->if_hwassist = priv->hwassist; + } else if (hook == priv->lower) + priv->lower = NULL; + else if (hook == priv->orphan) + priv->orphan = NULL; + else + panic("%s: weird hook", __func__); + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) + ng_rmnode_self(NG_HOOK_NODE(hook)); /* reset node */ + return (0); +} + +/****************************************************************** + INITIALIZATION +******************************************************************/ + +/* + * Handle loading and unloading for this node type. + */ +static int +ng_ether_mod_event(module_t mod, int event, void *data) +{ + struct ifnet *ifp; + int error = 0; + int s; + + s = splnet(); + switch (event) { + case MOD_LOAD: + + /* Register function hooks */ + if (ng_ether_attach_p != NULL) { + error = EEXIST; + break; + } + ng_ether_attach_p = ng_ether_attach; + ng_ether_detach_p = ng_ether_detach; + ng_ether_output_p = ng_ether_output; + ng_ether_input_p = ng_ether_input; + ng_ether_input_orphan_p = ng_ether_input_orphan; + ng_ether_link_state_p = ng_ether_link_state; + + /* Create nodes for any already-existing Ethernet interfaces */ + IFNET_RLOCK(); + TAILQ_FOREACH(ifp, &ifnet, if_link) { + if (ifp->if_type == IFT_ETHER + || ifp->if_type == IFT_L2VLAN) + ng_ether_attach(ifp); + } + IFNET_RUNLOCK(); + break; + + case MOD_UNLOAD: + + /* + * Note that the base code won't try to unload us until + * all nodes have been removed, and that can't happen + * until all Ethernet interfaces are removed. In any + * case, we know there are no nodes left if the action + * is MOD_UNLOAD, so there's no need to detach any nodes. + */ + + /* Unregister function hooks */ + ng_ether_attach_p = NULL; + ng_ether_detach_p = NULL; + ng_ether_output_p = NULL; + ng_ether_input_p = NULL; + ng_ether_input_orphan_p = NULL; + ng_ether_link_state_p = NULL; + break; + + default: + error = EOPNOTSUPP; + break; + } + splx(s); + return (error); +} + diff --git a/sys/netgraph7/ng_ether.h b/sys/netgraph7/ng_ether.h new file mode 100644 index 0000000000..d60a949af1 --- /dev/null +++ b/sys/netgraph7/ng_ether.h @@ -0,0 +1,73 @@ + +/* + * ng_ether.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_ether.h,v 1.14 2005/02/14 12:01:09 glebius Exp $ + * $Whistle: ng_ether.h,v 1.1 1999/02/02 03:17:22 julian Exp $ + */ + +#ifndef _NETGRAPH_NG_ETHER_H_ +#define _NETGRAPH_NG_ETHER_H_ + +/* Node type name and magic cookie */ +#define NG_ETHER_NODE_TYPE "ether" +#define NGM_ETHER_COOKIE 917786906 + +/* Hook names */ +#define NG_ETHER_HOOK_LOWER "lower" /* connection to raw device */ +#define NG_ETHER_HOOK_UPPER "upper" /* connection to upper layers */ +#define NG_ETHER_HOOK_DIVERT "divert" /* alias for lower */ +#define NG_ETHER_HOOK_ORPHAN "orphans" /* like lower, unknowns only */ + +/* Netgraph control messages */ +enum { + NGM_ETHER_GET_IFNAME = 1, /* get the interface name */ + NGM_ETHER_GET_IFINDEX, /* get the interface global index # */ + NGM_ETHER_GET_ENADDR, /* get Ethernet address */ + NGM_ETHER_SET_ENADDR, /* set Ethernet address */ + NGM_ETHER_GET_PROMISC, /* get node's promiscuous mode bit */ + NGM_ETHER_SET_PROMISC, /* enable/disable promiscuous mode */ + NGM_ETHER_GET_AUTOSRC, /* get source address override */ + NGM_ETHER_SET_AUTOSRC, /* enable/disable src addr override */ + NGM_ETHER_ADD_MULTI, /* add multicast membership */ + NGM_ETHER_DEL_MULTI, /* delete multicast membership */ + NGM_ETHER_DETACH, /* our way to be shut down */ +}; + +#endif /* _NETGRAPH_NG_ETHER_H_ */ diff --git a/sys/netgraph7/ng_fec.c b/sys/netgraph7/ng_fec.c new file mode 100644 index 0000000000..0b5bdbb7af --- /dev/null +++ b/sys/netgraph7/ng_fec.c @@ -0,0 +1,1366 @@ +/* + * ng_fec.c + */ + +/*- + * Copyright (c) 2001 Berkeley Software Design, Inc. + * Copyright (c) 2000, 2001 + * Bill Paul . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_fec.c,v 1.30 2007/05/18 15:05:49 dwmalone Exp $ + */ +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $Whistle: ng_fec.c,v 1.33 1999/11/01 09:24:51 julian Exp $ + */ + +/* + * This module implements ethernet channel bonding using the Cisco + * Fast EtherChannel mechanism. Two or four ports may be combined + * into a single aggregate interface. + * + * Interfaces are named fec0, fec1, etc. New nodes take the + * first available interface name. + * + * This node also includes Berkeley packet filter support. + * + * Note that this node doesn't need to connect to any other + * netgraph nodes in order to do its work. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include +#ifdef INET +#include +#include +#endif + +#ifdef INET6 +#include +#endif + +#include +#include +#include +#include + +/* + * We need a way to stash a pointer to our netgraph node in the + * ifnet structure so that receive handling works. As far as I can + * tell, although there is an AF_NETGRAPH address family, it's only + * used to identify sockaddr_ng structures: there is no netgraph address + * family domain. This means the AF_NETGRAPH entry in ifp->if_afdata + * should be unused, so we can use to hold our node context. + */ +#define IFP2NG(ifp) ((ifp)->if_afdata[AF_NETGRAPH]) + +/* + * Current fast etherchannel implementations use either 2 or 4 + * ports, so for now we limit the maximum bundle size to 4 interfaces. + */ +#define FEC_BUNDLESIZ 4 + +struct ng_fec_portlist { + struct ifnet *fec_if; + void (*fec_if_input) (struct ifnet *, + struct mbuf *); + int fec_idx; + int fec_ifstat; + struct ether_addr fec_mac; + SLIST_HEAD(__mclhd, ng_fec_mc) fec_mc_head; + TAILQ_ENTRY(ng_fec_portlist) fec_list; +}; + +struct ng_fec_mc { + struct ifmultiaddr *mc_ifma; + SLIST_ENTRY(ng_fec_mc) mc_entries; +}; + +struct ng_fec_bundle { + TAILQ_HEAD(,ng_fec_portlist) ng_fec_ports; + int fec_ifcnt; + int fec_btype; + int (*fec_if_output) (struct ifnet *, + struct mbuf *, + struct sockaddr *, + struct rtentry *); +}; + +#define FEC_BTYPE_MAC 0x01 +#define FEC_BTYPE_INET 0x02 +#define FEC_BTYPE_INET6 0x03 + +/* Node private data */ +struct ng_fec_private { + struct ifnet *ifp; + struct ifmedia ifmedia; + int if_flags; + int if_error; /* XXX */ + int unit; /* Interface unit number */ + node_p node; /* Our netgraph node */ + struct ng_fec_bundle fec_bundle;/* Aggregate bundle */ + struct callout_handle fec_ch; /* callout handle for ticker */ +}; +typedef struct ng_fec_private *priv_p; + +/* Interface methods */ +static void ng_fec_input(struct ifnet *, struct mbuf *); +static void ng_fec_start(struct ifnet *ifp); +static int ng_fec_choose_port(struct ng_fec_bundle *b, + struct mbuf *m, struct ifnet **ifp); +static int ng_fec_setport(struct ifnet *ifp, u_long cmd, caddr_t data); +static void ng_fec_init(void *arg); +static void ng_fec_stop(struct ifnet *ifp); +static int ng_fec_ifmedia_upd(struct ifnet *ifp); +static void ng_fec_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr); +static int ng_fec_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data); +static int ng_fec_output(struct ifnet *ifp, struct mbuf *m0, + struct sockaddr *dst, struct rtentry *rt0); +static void ng_fec_tick(void *arg); +static int ng_fec_addport(struct ng_fec_private *priv, char *iface); +static int ng_fec_delport(struct ng_fec_private *priv, char *iface); +static int ng_fec_ether_cmdmulti(struct ifnet *trifp, struct ng_fec_portlist *p, int set); + +#ifdef DEBUG +static void ng_fec_print_ioctl(struct ifnet *ifp, int cmd, caddr_t data); +#endif + +/* Netgraph methods */ +static int ng_fec_mod_event(module_t, int, void *); +static ng_constructor_t ng_fec_constructor; +static ng_rcvmsg_t ng_fec_rcvmsg; +static ng_shutdown_t ng_fec_shutdown; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_fec_cmds[] = { + { + NGM_FEC_COOKIE, + NGM_FEC_ADD_IFACE, + "add_iface", + &ng_parse_string_type, + NULL, + }, + { + NGM_FEC_COOKIE, + NGM_FEC_DEL_IFACE, + "del_iface", + &ng_parse_string_type, + NULL, + }, + { + NGM_FEC_COOKIE, + NGM_FEC_SET_MODE_MAC, + "set_mode_mac", + NULL, + NULL, + }, + { + NGM_FEC_COOKIE, + NGM_FEC_SET_MODE_INET, + "set_mode_inet", + NULL, + NULL, + }, + { 0 } +}; + +/* Node type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_FEC_NODE_TYPE, + .mod_event = ng_fec_mod_event, + .constructor = ng_fec_constructor, + .rcvmsg = ng_fec_rcvmsg, + .shutdown = ng_fec_shutdown, + .cmdlist = ng_fec_cmds, +}; +NETGRAPH_INIT(fec, &typestruct); + +/* We keep a bitmap indicating which unit numbers are free. + One means the unit number is free, zero means it's taken. */ +static int *ng_fec_units = NULL; +static int ng_fec_units_len = 0; +static int ng_units_in_use = 0; + +#define UNITS_BITSPERWORD (sizeof(*ng_fec_units) * NBBY) + +static struct mtx ng_fec_mtx; + +/* + * Find the first free unit number for a new interface. + * Increase the size of the unit bitmap as necessary. + */ +static __inline int +ng_fec_get_unit(int *unit) +{ + int index, bit; + + mtx_lock(&ng_fec_mtx); + for (index = 0; index < ng_fec_units_len + && ng_fec_units[index] == 0; index++); + if (index == ng_fec_units_len) { /* extend array */ + int i, *newarray, newlen; + + newlen = (2 * ng_fec_units_len) + 4; + MALLOC(newarray, int *, newlen * sizeof(*ng_fec_units), + M_NETGRAPH, M_NOWAIT); + if (newarray == NULL) { + mtx_unlock(&ng_fec_mtx); + return (ENOMEM); + } + bcopy(ng_fec_units, newarray, + ng_fec_units_len * sizeof(*ng_fec_units)); + for (i = ng_fec_units_len; i < newlen; i++) + newarray[i] = ~0; + if (ng_fec_units != NULL) + FREE(ng_fec_units, M_NETGRAPH); + ng_fec_units = newarray; + ng_fec_units_len = newlen; + } + bit = ffs(ng_fec_units[index]) - 1; + KASSERT(bit >= 0 && bit <= UNITS_BITSPERWORD - 1, + ("%s: word=%d bit=%d", __func__, ng_fec_units[index], bit)); + ng_fec_units[index] &= ~(1 << bit); + *unit = (index * UNITS_BITSPERWORD) + bit; + ng_units_in_use++; + mtx_unlock(&ng_fec_mtx); + return (0); +} + +/* + * Free a no longer needed unit number. + */ +static __inline void +ng_fec_free_unit(int unit) +{ + int index, bit; + + index = unit / UNITS_BITSPERWORD; + bit = unit % UNITS_BITSPERWORD; + mtx_lock(&ng_fec_mtx); + KASSERT(index < ng_fec_units_len, + ("%s: unit=%d len=%d", __func__, unit, ng_fec_units_len)); + KASSERT((ng_fec_units[index] & (1 << bit)) == 0, + ("%s: unit=%d is free", __func__, unit)); + ng_fec_units[index] |= (1 << bit); + /* + * XXX We could think about reducing the size of ng_fec_units[] + * XXX here if the last portion is all ones + * XXX At least free it if no more units + * Needed if we are to eventually be able to unload. + */ + ng_units_in_use--; + if (ng_units_in_use == 0) { /* XXX make SMP safe */ + FREE(ng_fec_units, M_NETGRAPH); + ng_fec_units_len = 0; + ng_fec_units = NULL; + } + mtx_unlock(&ng_fec_mtx); +} + +/************************************************************************ + INTERFACE STUFF + ************************************************************************/ + +static int +ng_fec_addport(struct ng_fec_private *priv, char *iface) +{ + struct ng_fec_bundle *b; + struct ifnet *ifp, *bifp; + struct ng_fec_portlist *p, *new; + + if (priv == NULL || iface == NULL) + return(EINVAL); + + b = &priv->fec_bundle; + ifp = priv->ifp; + + /* Only allow reconfiguration if not running. */ + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + printf("fec%d: can't add new iface; bundle is running\n", + priv->unit); + return (EINVAL); + } + + /* Find the interface */ + bifp = ifunit(iface); + if (bifp == NULL) { + printf("fec%d: tried to add iface %s, which " + "doesn't seem to exist\n", priv->unit, iface); + return(ENOENT); + } + + /* See if we have room in the bundle */ + if (b->fec_ifcnt == FEC_BUNDLESIZ) { + printf("fec%d: can't add new iface; bundle is full\n", + priv->unit); + return(ENOSPC); + } + + /* See if the interface is already in the bundle */ + TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { + if (p->fec_if == bifp) { + printf("fec%d: iface %s is already in this " + "bundle\n", priv->unit, iface); + return(EINVAL); + } + } + + /* + * All interfaces must use the same output vector. Once the + * user attaches an interface of one type, make all subsequent + * interfaces have the same output vector. + */ + if (b->fec_if_output != NULL) { + if (b->fec_if_output != bifp->if_output) { + printf("fec%d: iface %s is not the same type " + "as the other interface(s) already in " + "the bundle\n", priv->unit, iface); + return(EINVAL); + } + } + + /* Allocate new list entry. */ + MALLOC(new, struct ng_fec_portlist *, + sizeof(struct ng_fec_portlist), M_NETGRAPH, M_NOWAIT); + if (new == NULL) + return(ENOMEM); + + IF_AFDATA_LOCK(bifp); + IFP2NG(bifp) = priv->node; + IF_AFDATA_UNLOCK(bifp); + + /* + * If this is the first interface added to the bundle, + * use its MAC address for the virtual interface (and, + * by extension, all the other ports in the bundle). + */ + if (b->fec_ifcnt == 0) + if_setlladdr(ifp, IF_LLADDR(bifp), ETHER_ADDR_LEN); + + b->fec_btype = FEC_BTYPE_MAC; + new->fec_idx = b->fec_ifcnt; + b->fec_ifcnt++; + + /* Initialise the list of multicast addresses that we own. */ + SLIST_INIT(&new->fec_mc_head); + + /* Save the real MAC address. */ + bcopy(IF_LLADDR(bifp), + (char *)&new->fec_mac, ETHER_ADDR_LEN); + + /* Set up phony MAC address. */ + if_setlladdr(bifp, IF_LLADDR(ifp), ETHER_ADDR_LEN); + + /* Save original input vector */ + new->fec_if_input = bifp->if_input; + + /* Override it with our own */ + bifp->if_input = ng_fec_input; + + /* Save output vector too. */ + if (b->fec_if_output == NULL) + b->fec_if_output = bifp->if_output; + + /* Add to the queue */ + new->fec_if = bifp; + new->fec_ifstat = -1; + TAILQ_INSERT_TAIL(&b->ng_fec_ports, new, fec_list); + + /* Add multicast addresses to this port. */ + ng_fec_ether_cmdmulti(ifp, new, 1); + + return(0); +} + +static int +ng_fec_delport(struct ng_fec_private *priv, char *iface) +{ + struct ng_fec_bundle *b; + struct ifnet *ifp, *bifp; + struct ng_fec_portlist *p; + + if (priv == NULL || iface == NULL) + return(EINVAL); + + b = &priv->fec_bundle; + ifp = priv->ifp; + + /* Only allow reconfiguration if not running. */ + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + printf("fec%d: can't remove iface; bundle is running\n", + priv->unit); + return (EINVAL); + } + + /* Find the interface */ + bifp = ifunit(iface); + if (bifp == NULL) { + printf("fec%d: tried to remove iface %s, which " + "doesn't seem to exist\n", priv->unit, iface); + return(ENOENT); + } + + TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { + if (p->fec_if == bifp) + break; + } + + if (p == NULL) { + printf("fec%d: tried to remove iface %s which " + "is not in our bundle\n", priv->unit, iface); + return(EINVAL); + } + + /* Stop interface */ + bifp->if_flags &= ~IFF_UP; + (*bifp->if_ioctl)(bifp, SIOCSIFFLAGS, NULL); + + /* Restore MAC address. */ + if_setlladdr(bifp, (u_char *)&p->fec_mac, ETHER_ADDR_LEN); + + /* Restore input vector */ + bifp->if_input = p->fec_if_input; + + /* Remove our node context pointer. */ + IF_AFDATA_LOCK(bifp); + IFP2NG(bifp) = NULL; + IF_AFDATA_UNLOCK(bifp); + + /* Delete port */ + TAILQ_REMOVE(&b->ng_fec_ports, p, fec_list); + FREE(p, M_NETGRAPH); + b->fec_ifcnt--; + + if (b->fec_ifcnt == 0) + b->fec_if_output = NULL; + + return(0); +} + +static int +ng_fec_ether_cmdmulti(struct ifnet *trifp, struct ng_fec_portlist *p, int set) +{ + struct ifnet *ifp = p->fec_if; + struct ng_fec_mc *mc; + struct ifmultiaddr *ifma, *rifma = NULL; + struct sockaddr_dl sdl; + int error; + + bzero((char *)&sdl, sizeof(sdl)); + sdl.sdl_len = sizeof(sdl); + sdl.sdl_family = AF_LINK; + sdl.sdl_type = IFT_ETHER; + sdl.sdl_alen = ETHER_ADDR_LEN; + sdl.sdl_index = ifp->if_index; + + if (set) { + TAILQ_FOREACH(ifma, &trifp->if_multiaddrs, ifma_link) { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + bcopy(LLADDR((struct sockaddr_dl *)ifma->ifma_addr), + LLADDR(&sdl), ETHER_ADDR_LEN); + + error = if_addmulti(ifp, (struct sockaddr *)&sdl, &rifma); + if (error) + return (error); + mc = malloc(sizeof(struct ng_fec_mc), M_DEVBUF, M_NOWAIT); + if (mc == NULL) + return (ENOMEM); + mc->mc_ifma = rifma; + SLIST_INSERT_HEAD(&p->fec_mc_head, mc, mc_entries); + } + } else { + while ((mc = SLIST_FIRST(&p->fec_mc_head)) != NULL) { + SLIST_REMOVE(&p->fec_mc_head, mc, ng_fec_mc, mc_entries); + if_delmulti_ifma(mc->mc_ifma); + free(mc, M_DEVBUF); + } + } + return (0); +} + +static int +ng_fec_ether_setmulti(struct ifnet *ifp) +{ + struct ng_fec_private *priv; + struct ng_fec_bundle *b; + struct ng_fec_portlist *p; + + priv = ifp->if_softc; + b = &priv->fec_bundle; + + TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { + /* First, remove any existing filter entries. */ + ng_fec_ether_cmdmulti(ifp, p, 0); + /* copy all addresses from the fec interface to the port */ + ng_fec_ether_cmdmulti(ifp, p, 1); + } + return (0); +} + +/* + * Pass an ioctl command down to all the underyling interfaces in a + * bundle. Used for setting flags. + */ + +static int +ng_fec_setport(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct ng_fec_private *priv; + struct ng_fec_bundle *b; + struct ifnet *oifp; + struct ng_fec_portlist *p; + + priv = ifp->if_softc; + b = &priv->fec_bundle; + + TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { + oifp = p->fec_if; + if (oifp != NULL) + (*oifp->if_ioctl)(oifp, command, data); + } + + return(0); +} + +static void +ng_fec_init(void *arg) +{ + struct ng_fec_private *priv; + struct ng_fec_bundle *b; + struct ifnet *ifp, *bifp; + struct ng_fec_portlist *p; + + priv = arg; + ifp = priv->ifp; + b = &priv->fec_bundle; + + if (b->fec_ifcnt != 2 && b->fec_ifcnt != FEC_BUNDLESIZ) { + printf("fec%d: invalid bundle " + "size: %d\n", priv->unit, + b->fec_ifcnt); + return; + } + + ng_fec_stop(ifp); + + TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { + bifp = p->fec_if; + bifp->if_flags |= IFF_UP; + (*bifp->if_ioctl)(bifp, SIOCSIFFLAGS, NULL); + /* mark iface as up and let the monitor check it */ + p->fec_ifstat = -1; + } + + ifp->if_drv_flags &= ~(IFF_DRV_OACTIVE); + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + priv->fec_ch = timeout(ng_fec_tick, priv, hz); + + return; +} + +static void +ng_fec_stop(struct ifnet *ifp) +{ + struct ng_fec_private *priv; + struct ng_fec_bundle *b; + struct ifnet *bifp; + struct ng_fec_portlist *p; + + priv = ifp->if_softc; + b = &priv->fec_bundle; + + TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { + bifp = p->fec_if; + bifp->if_flags &= ~IFF_UP; + (*bifp->if_ioctl)(bifp, SIOCSIFFLAGS, NULL); + } + + untimeout(ng_fec_tick, priv, priv->fec_ch); + + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + + return; +} + +static void +ng_fec_tick(void *arg) +{ + struct ng_fec_private *priv; + struct ng_fec_bundle *b; + struct ifmediareq ifmr; + struct ifnet *ifp; + struct ng_fec_portlist *p; + int error = 0; + + priv = arg; + b = &priv->fec_bundle; + + TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { + bzero((char *)&ifmr, sizeof(ifmr)); + ifp = p->fec_if; + error = (*ifp->if_ioctl)(ifp, SIOCGIFMEDIA, (caddr_t)&ifmr); + if (error) { + printf("fec%d: failed to check status " + "of link %s\n", priv->unit, ifp->if_xname); + continue; + } + + if (ifmr.ifm_status & IFM_AVALID) { + if (ifmr.ifm_status & IFM_ACTIVE) { + if (p->fec_ifstat == -1 || + p->fec_ifstat == 0) { + p->fec_ifstat = 1; + printf("fec%d: port %s in bundle " + "is up\n", priv->unit, + ifp->if_xname); + } + } else { + if (p->fec_ifstat == -1 || + p->fec_ifstat == 1) { + p->fec_ifstat = 0; + printf("fec%d: port %s in bundle " + "is down\n", priv->unit, + ifp->if_xname); + } + } + } + } + + ifp = priv->ifp; + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + priv->fec_ch = timeout(ng_fec_tick, priv, hz); + + return; +} + +static int +ng_fec_ifmedia_upd(struct ifnet *ifp) +{ + return(0); +} + +static void ng_fec_ifmedia_sts(struct ifnet *ifp, + struct ifmediareq *ifmr) +{ + struct ng_fec_private *priv; + struct ng_fec_bundle *b; + struct ng_fec_portlist *p; + + priv = ifp->if_softc; + b = &priv->fec_bundle; + + ifmr->ifm_status = IFM_AVALID; + TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { + if (p->fec_ifstat == 1) { + ifmr->ifm_status |= IFM_ACTIVE; + break; + } + } + + return; +} + +/* + * Process an ioctl for the virtual interface + */ +static int +ng_fec_ioctl(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct ifreq *const ifr = (struct ifreq *) data; + int s, error = 0; + struct ng_fec_private *priv; + struct ng_fec_bundle *b; + + priv = ifp->if_softc; + b = &priv->fec_bundle; + +#ifdef DEBUG + ng_fec_print_ioctl(ifp, command, data); +#endif + s = splimp(); + switch (command) { + + /* These two are mostly handled at a higher layer */ + case SIOCSIFADDR: + case SIOCGIFADDR: + error = ether_ioctl(ifp, command, data); + break; + + case SIOCSIFMTU: + if (ifr->ifr_mtu >= NG_FEC_MTU_MIN && + ifr->ifr_mtu <= NG_FEC_MTU_MAX) { + struct ng_fec_portlist *p; + struct ifnet *bifp; + + TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { + bifp = p->fec_if; + error = (*bifp->if_ioctl)(bifp, SIOCSIFMTU, + data); + if (error != 0) + break; + } + if (error == 0) + ifp->if_mtu = ifr->ifr_mtu; + } else + error = EINVAL; + break; + + /* Set flags */ + case SIOCSIFFLAGS: + /* + * If the interface is marked up and stopped, then start it. + * If it is marked down and running, then stop it. + */ + if (ifr->ifr_flags & IFF_UP) { + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + /* Sanity. */ + if (b->fec_ifcnt != 2 && + b->fec_ifcnt != FEC_BUNDLESIZ) { + printf("fec%d: invalid bundle " + "size: %d\n", priv->unit, + b->fec_ifcnt); + error = EINVAL; + break; + } + ng_fec_init(priv); + } + /* + * Bubble down changes in promisc mode to + * underlying interfaces. + */ + if ((ifp->if_flags & IFF_PROMISC) != + (priv->if_flags & IFF_PROMISC)) { + ng_fec_setport(ifp, command, data); + priv->if_flags = ifp->if_flags; + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ng_fec_stop(ifp); + } + break; + + case SIOCADDMULTI: + case SIOCDELMULTI: + ng_fec_ether_setmulti(ifp); + error = 0; + break; + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &priv->ifmedia, command); + break; + /* Stuff that's not supported */ + case SIOCSIFPHYS: + error = EOPNOTSUPP; + break; + + default: + error = EINVAL; + break; + } + (void) splx(s); + return (error); +} + +/* + * This routine spies on mbufs received by underlying network device + * drivers. When we add an interface to our bundle, we override its + * if_input routine with a pointer to ng_fec_input(). This means we + * get to look at all the device's packets before sending them to the + * real ether_input() for processing by the stack. Once we verify the + * packet comes from an interface that's been aggregated into + * our bundle, we fix up the rcvif pointer and increment our + * packet counters so that it looks like the frames are actually + * coming from us. + */ +static void +ng_fec_input(struct ifnet *ifp, struct mbuf *m0) +{ + struct ng_node *node; + struct ng_fec_private *priv; + struct ng_fec_bundle *b; + struct ifnet *bifp; + struct ng_fec_portlist *p; + + /* Sanity check */ + if (ifp == NULL || m0 == NULL) + return; + + node = IFP2NG(ifp); + + /* Sanity check part II */ + if (node == NULL) + return; + + priv = NG_NODE_PRIVATE(node); + b = &priv->fec_bundle; + bifp = priv->ifp; + + TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { + if (p->fec_if == m0->m_pkthdr.rcvif) + break; + } + + /* Wasn't meant for us; leave this frame alone. */ + if (p == NULL) + return; + + /* + * Check for a BPF tap on the underlying interface. This + * is mainly a debugging aid: it allows tcpdump-ing of an + * individual interface in a bundle to work, which it + * otherwise would not. BPF tapping of our own aggregate + * interface will occur once we call ether_input(). + */ + BPF_MTAP(m0->m_pkthdr.rcvif, m0); + + /* Convince the system that this is our frame. */ + m0->m_pkthdr.rcvif = bifp; + + /* + * Count bytes on an individual interface in a bundle. + * The bytes will also be added to the aggregate interface + * once we call ether_input(). + */ + ifp->if_ibytes += m0->m_pkthdr.len; + + bifp->if_ipackets++; + (*bifp->if_input)(bifp, m0); + + return; +} + +/* + * Take a quick peek at the packet and see if it's ok for us to use + * the inet or inet6 hash methods on it, if they're enabled. We do + * this by setting flags in the mbuf header. Once we've made up our + * mind what to do, we pass the frame to output vector for further + * processing. + */ + +static int +ng_fec_output(struct ifnet *ifp, struct mbuf *m, + struct sockaddr *dst, struct rtentry *rt0) +{ + const priv_p priv = (priv_p) ifp->if_softc; + struct ng_fec_bundle *b; + int error; + + /* Check interface flags */ + if (!((ifp->if_flags & IFF_UP) && + (ifp->if_drv_flags & IFF_DRV_RUNNING))) { + m_freem(m); + return (ENETDOWN); + } + + b = &priv->fec_bundle; + + switch (b->fec_btype) { + case FEC_BTYPE_MAC: + m->m_flags |= M_FEC_MAC; + break; +#ifdef INET + case FEC_BTYPE_INET: + /* + * We can't use the INET address port selection + * scheme if this isn't an INET packet. + */ + if (dst->sa_family == AF_INET) + m->m_flags |= M_FEC_INET; +#ifdef INET6 + else if (dst->sa_family == AF_INET6) + m->m_flags |= M_FEC_INET6; +#endif + else { +#ifdef DEBUG + if_printf(ifp, "can't do inet aggregation of non " + "inet packet\n"); +#endif + m->m_flags |= M_FEC_MAC; + } + break; +#endif + default: + if_printf(ifp, "bogus hash type: %d\n", + b->fec_btype); + m_freem(m); + return(EINVAL); + break; + } + + /* + * Pass the frame to the output vector for all the protocol + * handling. This will put the ethernet header on the packet + * for us. + */ + priv->if_error = 0; + error = (*b->fec_if_output)(ifp, m, dst, rt0); + if (priv->if_error && !error) + error = priv->if_error; + + return(error); +} + +/* + * Apply a hash to the source and destination addresses in the packet + * in order to select an interface. Also check link status and handle + * dead links accordingly. + */ + +static int +ng_fec_choose_port(struct ng_fec_bundle *b, + struct mbuf *m, struct ifnet **ifp) +{ + struct ether_header *eh; + struct mbuf *m0; +#ifdef INET + struct ip *ip; +#ifdef INET6 + struct ip6_hdr *ip6; +#endif +#endif + + struct ng_fec_portlist *p; + int port = 0, mask; + + /* + * If there are only two ports, mask off all but the + * last bit for XORing. If there are 4, mask off all + * but the last 2 bits. + */ + mask = b->fec_ifcnt == 2 ? 0x1 : 0x3; + eh = mtod(m, struct ether_header *); +#ifdef INET + ip = (struct ip *)(mtod(m, char *) + + sizeof(struct ether_header)); +#ifdef INET6 + ip6 = (struct ip6_hdr *)(mtod(m, char *) + + sizeof(struct ether_header)); +#endif +#endif + + /* + * The fg_fec_output() routine is supposed to leave a + * flag for us in the mbuf that tells us what hash to + * use, but sometimes a new mbuf is prepended to the + * chain, so we have to search every mbuf in the chain + * to find the flags. + */ + m0 = m; + while (m0) { + if (m0->m_flags & (M_FEC_MAC|M_FEC_INET|M_FEC_INET6)) + break; + m0 = m0->m_next; + } + if (m0 == NULL) + return(EINVAL); + + switch (m0->m_flags & (M_FEC_MAC|M_FEC_INET|M_FEC_INET6)) { + case M_FEC_MAC: + port = (eh->ether_dhost[5] ^ + eh->ether_shost[5]) & mask; + break; +#ifdef INET + case M_FEC_INET: + port = (ntohl(ip->ip_dst.s_addr) ^ + ntohl(ip->ip_src.s_addr)) & mask; + break; +#ifdef INET6 + case M_FEC_INET6: + port = (ip6->ip6_dst.s6_addr[15] ^ + ip6->ip6_dst.s6_addr[15]) & mask; + break; +#endif +#endif + default: + return(EINVAL); + break; + } + + TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) { + if (port == p->fec_idx) + break; + } + + /* + * Now that we've chosen a port, make sure it's + * alive. If it's not alive, cycle through the bundle + * looking for a port that is alive. If we don't find + * any, return an error. + */ + if (p->fec_ifstat != 1) { + struct ng_fec_portlist *n = NULL; + + n = TAILQ_NEXT(p, fec_list); + if (n == NULL) + n = TAILQ_FIRST(&b->ng_fec_ports); + while (n != p) { + if (n->fec_ifstat == 1) + break; + n = TAILQ_NEXT(n, fec_list); + if (n == NULL) + n = TAILQ_FIRST(&b->ng_fec_ports); + } + if (n == p) + return(EAGAIN); + p = n; + } + + *ifp = p->fec_if; + + return(0); +} + +/* + * Now that the packet has been run through ether_output(), yank it + * off our own send queue and stick it on the queue for the appropriate + * underlying physical interface. Note that if the interface's send + * queue is full, we save an error status in our private netgraph + * space which will eventually be handed up to ng_fec_output(), which + * will return it to the rest of the IP stack. We need to do this + * in order to duplicate the effect of ether_output() returning ENOBUFS + * when it detects that an interface's send queue is full. There's no + * other way to signal the error status from here since the if_start() + * routine is spec'ed to return void. + * + * Once the frame is queued, we call ether_output_frame() to initiate + * transmission. + */ +static void +ng_fec_start(struct ifnet *ifp) +{ + struct ng_fec_private *priv; + struct ng_fec_bundle *b; + struct ifnet *oifp = NULL; + struct mbuf *m0; + int error; + + priv = ifp->if_softc; + b = &priv->fec_bundle; + + IF_DEQUEUE(&ifp->if_snd, m0); + if (m0 == NULL) + return; + + BPF_MTAP(ifp, m0); + + /* Queue up packet on the proper port. */ + error = ng_fec_choose_port(b, m0, &oifp); + if (error) { + ifp->if_ierrors++; + m_freem(m0); + priv->if_error = ENOBUFS; + return; + } + ifp->if_opackets++; + + priv->if_error = IF_HANDOFF(&oifp->if_snd, m0, oifp) ? 0 : ENOBUFS; + + return; +} + +#ifdef DEBUG +/* + * Display an ioctl to the virtual interface + */ + +static void +ng_fec_print_ioctl(struct ifnet *ifp, int command, caddr_t data) +{ + char *str; + + switch (command & IOC_DIRMASK) { + case IOC_VOID: + str = "IO"; + break; + case IOC_OUT: + str = "IOR"; + break; + case IOC_IN: + str = "IOW"; + break; + case IOC_INOUT: + str = "IORW"; + break; + default: + str = "IO??"; + } + log(LOG_DEBUG, "%s: %s('%c', %d, char[%d])\n", + ifp->if_xname, + str, + IOCGROUP(command), + command & 0xff, + IOCPARM_LEN(command)); +} +#endif /* DEBUG */ + +/************************************************************************ + NETGRAPH NODE STUFF + ************************************************************************/ + +/* + * Constructor for a node + */ +static int +ng_fec_constructor(node_p node) +{ + char ifname[NG_FEC_FEC_NAME_MAX + 1]; + struct ifnet *ifp; + priv_p priv; + const uint8_t eaddr[ETHER_ADDR_LEN] = {0, 0, 0, 0, 0, 0}; + struct ng_fec_bundle *b; + int error = 0; + + /* Allocate node and interface private structures */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + + ifp = priv->ifp = if_alloc(IFT_ETHER); + if (ifp == NULL) { + FREE(priv, M_NETGRAPH); + return (ENOSPC); + } + b = &priv->fec_bundle; + + /* Link them together */ + ifp->if_softc = priv; + + /* Get an interface unit number */ + if ((error = ng_fec_get_unit(&priv->unit)) != 0) { + if_free(ifp); + FREE(priv, M_NETGRAPH); + return (error); + } + + /* Link together node and private info */ + NG_NODE_SET_PRIVATE(node, priv); + priv->node = node; + + /* Initialize interface structure */ + if_initname(ifp, NG_FEC_FEC_NAME, priv->unit); + ifp->if_start = ng_fec_start; + ifp->if_ioctl = ng_fec_ioctl; + ifp->if_init = ng_fec_init; + ifp->if_watchdog = NULL; + ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; + ifp->if_mtu = NG_FEC_MTU_DEFAULT; + ifp->if_flags = (IFF_SIMPLEX|IFF_BROADCAST|IFF_MULTICAST); + ifp->if_addrlen = 0; /* XXX */ + ifp->if_hdrlen = 0; /* XXX */ + ifp->if_baudrate = 100000000; /* XXX */ + TAILQ_INIT(&ifp->if_addrhead); /* XXX useless - done in if_attach */ + + /* Give this node the same name as the interface (if possible) */ + bzero(ifname, sizeof(ifname)); + strlcpy(ifname, ifp->if_xname, sizeof(ifname)); + if (ng_name_node(node, ifname) != 0) + log(LOG_WARNING, "%s: can't acquire netgraph name\n", ifname); + + /* Attach the interface */ + ether_ifattach(ifp, eaddr); + callout_handle_init(&priv->fec_ch); + + /* Override output method with our own */ + ifp->if_output = ng_fec_output; + + TAILQ_INIT(&b->ng_fec_ports); + b->fec_ifcnt = 0; + + ifmedia_init(&priv->ifmedia, 0, + ng_fec_ifmedia_upd, ng_fec_ifmedia_sts); + ifmedia_add(&priv->ifmedia, IFM_ETHER|IFM_NONE, 0, NULL); + ifmedia_set(&priv->ifmedia, IFM_ETHER|IFM_NONE); + + /* Done */ + return (0); +} + +/* + * Receive a control message + */ +static int +ng_fec_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_fec_bundle *b; + struct ng_mesg *resp = NULL; + struct ng_mesg *msg; + char *ifname; + int error = 0; + + NGI_GET_MSG(item, msg); + b = &priv->fec_bundle; + + switch (msg->header.typecookie) { + case NGM_FEC_COOKIE: + switch (msg->header.cmd) { + case NGM_FEC_ADD_IFACE: + ifname = msg->data; + error = ng_fec_addport(priv, ifname); + break; + case NGM_FEC_DEL_IFACE: + ifname = msg->data; + error = ng_fec_delport(priv, ifname); + break; + case NGM_FEC_SET_MODE_MAC: + b->fec_btype = FEC_BTYPE_MAC; + break; +#ifdef INET + case NGM_FEC_SET_MODE_INET: + b->fec_btype = FEC_BTYPE_INET; + break; +#ifdef INET6 + case NGM_FEC_SET_MODE_INET6: + b->fec_btype = FEC_BTYPE_INET6; + break; +#endif +#endif + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Shutdown and remove the node and its associated interface. + */ +static int +ng_fec_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_fec_bundle *b; + struct ng_fec_portlist *p; + + b = &priv->fec_bundle; + ng_fec_stop(priv->ifp); + + while (!TAILQ_EMPTY(&b->ng_fec_ports)) { + p = TAILQ_FIRST(&b->ng_fec_ports); + ng_fec_ether_cmdmulti(priv->ifp, p, 0); + ng_fec_delport(priv, p->fec_if->if_xname); + } + + ether_ifdetach(priv->ifp); + if_free_type(priv->ifp, IFT_ETHER); + ifmedia_removeall(&priv->ifmedia); + ng_fec_free_unit(priv->unit); + FREE(priv, M_NETGRAPH); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + return (0); +} + +/* + * Handle loading and unloading for this node type. + */ +static int +ng_fec_mod_event(module_t mod, int event, void *data) +{ + int error = 0; + + switch (event) { + case MOD_LOAD: + mtx_init(&ng_fec_mtx, "ng_fec", NULL, MTX_DEF); + break; + case MOD_UNLOAD: + mtx_destroy(&ng_fec_mtx); + break; + default: + error = EOPNOTSUPP; + break; + } + return (error); +} diff --git a/sys/netgraph7/ng_fec.h b/sys/netgraph7/ng_fec.h new file mode 100644 index 0000000000..0144023c94 --- /dev/null +++ b/sys/netgraph7/ng_fec.h @@ -0,0 +1,113 @@ +/* + * ng_fec.h + */ + +/*- + * Copyright (c) 2000 Berkeley Software Design, Inc. + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_fec.h,v 1.3 2005/01/07 01:45:39 imp Exp $ + */ +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $Whistle: ng_fec.h,v 1.5 1999/01/20 00:22:13 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_FEC_H_ +#define _NETGRAPH_NG_FEC_H_ + +#define NETISR_FEC 26 + +/* Node type name and magic cookie */ +#define NG_FEC_NODE_TYPE "fec" +#define NGM_FEC_COOKIE 983566799 + +/* Interface base name */ +#define NG_FEC_FEC_NAME "fec" +#define NG_FEC_FEC_NAME_MAX 15 + +/* MTU bounds */ +#define NG_FEC_MTU_MIN 72 +#define NG_FEC_MTU_MAX 65535 +#define NG_FEC_MTU_DEFAULT 1500 + +/* Special flags for mbufs. */ +#define M_FEC_MAC 0x2000 +#define M_FEC_INET 0x4000 +#define M_FEC_INET6 0x8000 + +/* Netgraph commands */ +enum { + NGM_FEC_ADD_IFACE, + NGM_FEC_DEL_IFACE, + NGM_FEC_SET_MODE_MAC, + NGM_FEC_SET_MODE_INET, + NGM_FEC_SET_MODE_INET6 +}; + +struct ng_fec_ifname { + char ngif_name[NG_FEC_FEC_NAME_MAX + 1]; +}; + +#endif /* _NETGRAPH_NG_FEC_H_ */ diff --git a/sys/netgraph7/ng_frame_relay.c b/sys/netgraph7/ng_frame_relay.c new file mode 100644 index 0000000000..638c6701f1 --- /dev/null +++ b/sys/netgraph7/ng_frame_relay.c @@ -0,0 +1,515 @@ +/* + * ng_frame_relay.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_frame_relay.c,v 1.25 2006/01/14 21:49:31 glebius Exp $ + * $Whistle: ng_frame_relay.c,v 1.20 1999/11/01 09:24:51 julian Exp $ + */ + +/* + * This node implements the frame relay protocol, not including + * the LMI line management. This means basically keeping track + * of which DLCI's are active, doing frame (de)multiplexing, etc. + * + * It has a 'downstream' hook that goes to the line, and a + * hook for each DLCI (eg, 'dlci16'). + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * Line info, and status per channel. + */ +struct ctxinfo { /* one per active hook */ + u_int flags; +#define CHAN_VALID 0x01 /* assigned to a channel */ +#define CHAN_ACTIVE 0x02 /* bottom level active */ + int dlci; /* the dlci assigned to this context */ + hook_p hook; /* if there's a hook assigned.. */ +}; + +#define MAX_CT 16 /* # of dlci's active at a time (POWER OF 2!) */ +struct frmrel_softc { + int unit; /* which card are we? */ + int datahooks; /* number of data hooks attached */ + node_p node; /* netgraph node */ + int addrlen; /* address header length */ + int flags; /* state */ + int mtu; /* guess */ + u_char remote_seq; /* sequence number the remote sent */ + u_char local_seq; /* sequence number the remote rcvd */ + u_short ALT[1024]; /* map DLCIs to CTX */ +#define CTX_VALID 0x8000 /* this bit means it's a valid CTX */ +#define CTX_VALUE (MAX_CT - 1) /* mask for context part */ + struct ctxinfo channel[MAX_CT]; + struct ctxinfo downstream; +}; +typedef struct frmrel_softc *sc_p; + +#define BYTEX_EA 0x01 /* End Address. Always 0 on byte1 */ +#define BYTE1_C_R 0x02 +#define BYTE2_FECN 0x08 /* forwards congestion notification */ +#define BYTE2_BECN 0x04 /* Backward congestion notification */ +#define BYTE2_DE 0x02 /* Discard elligability */ +#define LASTBYTE_D_C 0x02 /* last byte is dl_core or dlci info */ + +/* Used to do headers */ +const static struct segment { + u_char mask; + u_char shift; + u_char width; +} makeup[] = { + { 0xfc, 2, 6 }, + { 0xf0, 4, 4 }, + { 0xfe, 1, 7 }, + { 0xfc, 2, 6 } +}; + +#define SHIFTIN(segment, byte, dlci) \ + { \ + (dlci) <<= (segment)->width; \ + (dlci) |= \ + (((byte) & (segment)->mask) >> (segment)->shift); \ + } + +#define SHIFTOUT(segment, byte, dlci) \ + { \ + (byte) |= (((dlci) << (segment)->shift) & (segment)->mask); \ + (dlci) >>= (segment)->width; \ + } + +/* Netgraph methods */ +static ng_constructor_t ngfrm_constructor; +static ng_shutdown_t ngfrm_shutdown; +static ng_newhook_t ngfrm_newhook; +static ng_rcvdata_t ngfrm_rcvdata; +static ng_disconnect_t ngfrm_disconnect; + +/* Other internal functions */ +static int ngfrm_decode(node_p node, item_p item); +static int ngfrm_addrlen(char *hdr); +static int ngfrm_allocate_CTX(sc_p sc, int dlci); + +/* Netgraph type */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_FRAMERELAY_NODE_TYPE, + .constructor = ngfrm_constructor, + .shutdown = ngfrm_shutdown, + .newhook = ngfrm_newhook, + .rcvdata = ngfrm_rcvdata, + .disconnect = ngfrm_disconnect, +}; +NETGRAPH_INIT(framerelay, &typestruct); + +/* + * Given a DLCI, return the index of the context table entry for it, + * Allocating a new one if needs be, or -1 if none available. + */ +static int +ngfrm_allocate_CTX(sc_p sc, int dlci) +{ + u_int ctxnum = -1; /* what ctx number we are using */ + volatile struct ctxinfo *CTXp = NULL; + + /* Sanity check the dlci value */ + if (dlci > 1023) + return (-1); + + /* Check to see if we already have an entry for this DLCI */ + if (sc->ALT[dlci]) { + if ((ctxnum = sc->ALT[dlci] & CTX_VALUE) < MAX_CT) { + CTXp = sc->channel + ctxnum; + } else { + ctxnum = -1; + sc->ALT[dlci] = 0; /* paranoid but... */ + } + } + + /* + * If the index has no valid entry yet, then we need to allocate a + * CTX number to it + */ + if (CTXp == NULL) { + for (ctxnum = 0; ctxnum < MAX_CT; ctxnum++) { + /* + * If the VALID flag is empty it is unused + */ + if ((sc->channel[ctxnum].flags & CHAN_VALID) == 0) { + bzero(sc->channel + ctxnum, + sizeof(struct ctxinfo)); + CTXp = sc->channel + ctxnum; + sc->ALT[dlci] = ctxnum | CTX_VALID; + sc->channel[ctxnum].dlci = dlci; + sc->channel[ctxnum].flags = CHAN_VALID; + break; + } + } + } + + /* + * If we still don't have a CTX pointer, then we never found a free + * spot so give up now.. + */ + if (!CTXp) { + log(LOG_ERR, "No CTX available for dlci %d\n", dlci); + return (-1); + } + return (ctxnum); +} + +/* + * Node constructor + */ +static int +ngfrm_constructor(node_p node) +{ + sc_p sc; + + MALLOC(sc, sc_p, sizeof(*sc), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (!sc) + return (ENOMEM); + sc->addrlen = 2; /* default */ + + /* Link the node and our private info */ + NG_NODE_SET_PRIVATE(node, sc); + sc->node = node; + return (0); +} + +/* + * Add a new hook + * + * We allow hooks called "debug", "downstream" and dlci[0-1023] + * The hook's private info points to our stash of info about that + * channel. A NULL pointer is debug and a DLCI of -1 means downstream. + */ +static int +ngfrm_newhook(node_p node, hook_p hook, const char *name) +{ + const sc_p sc = NG_NODE_PRIVATE(node); + const char *cp; + char *eptr; + int dlci = 0; + int ctxnum; + + /* Check if it's our friend the control hook */ + if (strcmp(name, NG_FRAMERELAY_HOOK_DEBUG) == 0) { + NG_HOOK_SET_PRIVATE(hook, NULL); /* paranoid */ + return (0); + } + + /* + * All other hooks either start with 'dlci' and have a decimal + * trailing channel number up to 4 digits, or are the downstream + * hook. + */ + if (strncmp(name, NG_FRAMERELAY_HOOK_DLCI, + strlen(NG_FRAMERELAY_HOOK_DLCI)) != 0) { + + /* It must be the downstream connection */ + if (strcmp(name, NG_FRAMERELAY_HOOK_DOWNSTREAM) != 0) + return EINVAL; + + /* Make sure we haven't already got one (paranoid) */ + if (sc->downstream.hook) + return (EADDRINUSE); + + /* OK add it */ + NG_HOOK_SET_PRIVATE(hook, &sc->downstream); + sc->downstream.hook = hook; + sc->downstream.dlci = -1; + sc->downstream.flags |= CHAN_ACTIVE; + sc->datahooks++; + return (0); + } + + /* Must be a dlci hook at this point */ + cp = name + strlen(NG_FRAMERELAY_HOOK_DLCI); + if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) + return (EINVAL); + dlci = (int)strtoul(cp, &eptr, 10); + if (*eptr != '\0' || dlci < 0 || dlci > 1023) + return (EINVAL); + + /* + * We have a dlci, now either find it, or allocate it. It's possible + * that we might have seen packets for it already and made an entry + * for it. + */ + ctxnum = ngfrm_allocate_CTX(sc, dlci); + if (ctxnum == -1) + return (ENOBUFS); + + /* + * Be paranoid: if it's got a hook already, that dlci is in use . + * Generic code can not catch all the synonyms (e.g. dlci016 vs + * dlci16) + */ + if (sc->channel[ctxnum].hook != NULL) + return (EADDRINUSE); + + /* + * Put our hooks into it (pun not intended) + */ + sc->channel[ctxnum].flags |= CHAN_ACTIVE; + NG_HOOK_SET_PRIVATE(hook, sc->channel + ctxnum); + sc->channel[ctxnum].hook = hook; + sc->datahooks++; + return (0); +} + +/* + * Count up the size of the address header if we don't already know + */ +int +ngfrm_addrlen(char *hdr) +{ + if (hdr[0] & BYTEX_EA) + return 0; + if (hdr[1] & BYTEX_EA) + return 2; + if (hdr[2] & BYTEX_EA) + return 3; + if (hdr[3] & BYTEX_EA) + return 4; + return 0; +} + +/* + * Receive data packet + */ +static int +ngfrm_rcvdata(hook_p hook, item_p item) +{ + struct ctxinfo *const ctxp = NG_HOOK_PRIVATE(hook); + struct mbuf *m = NULL; + int error = 0; + int dlci; + sc_p sc; + int alen; + char *data; + + /* Data doesn't come in from just anywhere (e.g debug hook) */ + if (ctxp == NULL) { + error = ENETDOWN; + goto bad; + } + + /* If coming from downstream, decode it to a channel */ + dlci = ctxp->dlci; + if (dlci == -1) + return (ngfrm_decode(NG_HOOK_NODE(hook), item)); + + NGI_GET_M(item, m); + /* Derive the softc we will need */ + sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + /* If there is no live channel, throw it away */ + if ((sc->downstream.hook == NULL) + || ((ctxp->flags & CHAN_ACTIVE) == 0)) { + error = ENETDOWN; + goto bad; + } + + /* Store the DLCI on the front of the packet */ + alen = sc->addrlen; + if (alen == 0) + alen = 2; /* default value for transmit */ + M_PREPEND(m, alen, M_DONTWAIT); + if (m == NULL) { + error = ENOBUFS; + goto bad; + } + data = mtod(m, char *); + + /* + * Shift the lowest bits into the address field untill we are done. + * First byte is MSBits of addr so work backwards. + */ + switch (alen) { + case 2: + data[0] = data[1] = '\0'; + SHIFTOUT(makeup + 1, data[1], dlci); + SHIFTOUT(makeup + 0, data[0], dlci); + data[1] |= BYTEX_EA; + break; + case 3: + data[0] = data[1] = data[2] = '\0'; + SHIFTOUT(makeup + 3, data[2], dlci); /* 3 and 2 is correct */ + SHIFTOUT(makeup + 1, data[1], dlci); + SHIFTOUT(makeup + 0, data[0], dlci); + data[2] |= BYTEX_EA; + break; + case 4: + data[0] = data[1] = data[2] = data[3] = '\0'; + SHIFTOUT(makeup + 3, data[3], dlci); + SHIFTOUT(makeup + 2, data[2], dlci); + SHIFTOUT(makeup + 1, data[1], dlci); + SHIFTOUT(makeup + 0, data[0], dlci); + data[3] |= BYTEX_EA; + break; + default: + panic(__func__); + } + + /* Send it */ + NG_FWD_NEW_DATA(error, item, sc->downstream.hook, m); + return (error); + +bad: + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (error); +} + +/* + * Decode an incoming frame coming from the switch + */ +static int +ngfrm_decode(node_p node, item_p item) +{ + const sc_p sc = NG_NODE_PRIVATE(node); + char *data; + int alen; + u_int dlci = 0; + int error = 0; + int ctxnum; + struct mbuf *m; + + NGI_GET_M(item, m); + if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) { + error = ENOBUFS; + goto out; + } + data = mtod(m, char *); + if ((alen = sc->addrlen) == 0) { + sc->addrlen = alen = ngfrm_addrlen(data); + } + switch (alen) { + case 2: + SHIFTIN(makeup + 0, data[0], dlci); + SHIFTIN(makeup + 1, data[1], dlci); + break; + case 3: + SHIFTIN(makeup + 0, data[0], dlci); + SHIFTIN(makeup + 1, data[1], dlci); + SHIFTIN(makeup + 3, data[2], dlci); /* 3 and 2 is correct */ + break; + case 4: + SHIFTIN(makeup + 0, data[0], dlci); + SHIFTIN(makeup + 1, data[1], dlci); + SHIFTIN(makeup + 2, data[2], dlci); + SHIFTIN(makeup + 3, data[3], dlci); + break; + default: + error = EINVAL; + goto out; + } + + if (dlci > 1023) { + error = EINVAL; + goto out; + } + ctxnum = sc->ALT[dlci]; + if ((ctxnum & CTX_VALID) && sc->channel[ctxnum &= CTX_VALUE].hook) { + /* Send it */ + m_adj(m, alen); + NG_FWD_NEW_DATA(error, item, sc->channel[ctxnum].hook, m); + return (error); + } else { + error = ENETDOWN; + } +out: + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (error); +} + +/* + * Shutdown node + */ +static int +ngfrm_shutdown(node_p node) +{ + const sc_p sc = NG_NODE_PRIVATE(node); + + NG_NODE_SET_PRIVATE(node, NULL); + FREE(sc, M_NETGRAPH); + NG_NODE_UNREF(node); + return (0); +} + +/* + * Hook disconnection + * + * Invalidate the private data associated with this dlci. + * For this type, removal of the last link resets tries to destroy the node. + */ +static int +ngfrm_disconnect(hook_p hook) +{ + const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct ctxinfo *const cp = NG_HOOK_PRIVATE(hook); + int dlci; + + /* If it's a regular dlci hook, then free resources etc.. */ + if (cp != NULL) { + cp->hook = NULL; + dlci = cp->dlci; + if (dlci != -1) + sc->ALT[dlci] = 0; + cp->flags = 0; + sc->datahooks--; + } + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) + ng_rmnode_self(NG_HOOK_NODE(hook)); + return (0); +} diff --git a/sys/netgraph7/ng_frame_relay.h b/sys/netgraph7/ng_frame_relay.h new file mode 100644 index 0000000000..f0b0843091 --- /dev/null +++ b/sys/netgraph7/ng_frame_relay.h @@ -0,0 +1,56 @@ +/* + * ng_frame_relay.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_frame_relay.h,v 1.4 2005/01/07 01:45:39 imp Exp $ + * $Whistle: ng_frame_relay.h,v 1.7 1999/01/20 00:22:13 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_FRAME_RELAY_H_ +#define _NETGRAPH_NG_FRAME_RELAY_H_ + +/* Node type name and magic cookie */ +#define NG_FRAMERELAY_NODE_TYPE "frame_relay" +#define NGM_FRAMERELAY_COOKIE 872148478 + +/* Hook names */ +#define NG_FRAMERELAY_HOOK_DEBUG "debug" +#define NG_FRAMERELAY_HOOK_DOWNSTREAM "downstream" +#define NG_FRAMERELAY_HOOK_DLCI "dlci" /* really just the prefix */ + +#endif /* _NETGRAPH_NG_FRAME_RELAY_H_ */ diff --git a/sys/netgraph7/ng_gif.c b/sys/netgraph7/ng_gif.c new file mode 100644 index 0000000000..3cd6bbc0c2 --- /dev/null +++ b/sys/netgraph7/ng_gif.c @@ -0,0 +1,596 @@ +/* + * ng_gif.c + */ + +/*- + * Copyright 2001 The Aerospace Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions, and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of The Aerospace Corporation may not be used to endorse or + * promote products derived from this software. + * + * THIS SOFTWARE IS PROVIDED BY THE AEROSPACE CORPORATION ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AEROSPACE CORPORATION BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * + * Copyright (c) 1996-2000 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_gif.c,v 1.19 2005/06/10 16:49:21 brooks Exp $ + */ + +/* + * ng_gif(4) netgraph node type + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define IFP2NG(ifp) ((struct ng_node *)((struct gif_softc *)(ifp->if_softc))->gif_netgraph) +#define IFP2NG_SET(ifp, val) (((struct gif_softc *)(ifp->if_softc))->gif_netgraph = (val)) + +/* Per-node private data */ +struct private { + struct ifnet *ifp; /* associated interface */ + hook_p lower; /* lower OR orphan hook connection */ + u_char lowerOrphan; /* whether lower is lower or orphan */ +}; +typedef struct private *priv_p; + +/* Functional hooks called from if_gif.c */ +static void ng_gif_input(struct ifnet *ifp, struct mbuf **mp, int af); +static void ng_gif_input_orphan(struct ifnet *ifp, struct mbuf *m, int af); +static void ng_gif_attach(struct ifnet *ifp); +static void ng_gif_detach(struct ifnet *ifp); + +/* Other functions */ +static void ng_gif_input2(node_p node, struct mbuf **mp, int af); +static int ng_gif_glue_af(struct mbuf **mp, int af); +static int ng_gif_rcv_lower(node_p node, struct mbuf *m); + +/* Netgraph node methods */ +static ng_constructor_t ng_gif_constructor; +static ng_rcvmsg_t ng_gif_rcvmsg; +static ng_shutdown_t ng_gif_shutdown; +static ng_newhook_t ng_gif_newhook; +static ng_connect_t ng_gif_connect; +static ng_rcvdata_t ng_gif_rcvdata; +static ng_disconnect_t ng_gif_disconnect; +static int ng_gif_mod_event(module_t mod, int event, void *data); + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_gif_cmdlist[] = { + { + NGM_GIF_COOKIE, + NGM_GIF_GET_IFNAME, + "getifname", + NULL, + &ng_parse_string_type + }, + { + NGM_GIF_COOKIE, + NGM_GIF_GET_IFINDEX, + "getifindex", + NULL, + &ng_parse_int32_type + }, + { 0 } +}; + +static struct ng_type ng_gif_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_GIF_NODE_TYPE, + .mod_event = ng_gif_mod_event, + .constructor = ng_gif_constructor, + .rcvmsg = ng_gif_rcvmsg, + .shutdown = ng_gif_shutdown, + .newhook = ng_gif_newhook, + .connect = ng_gif_connect, + .rcvdata = ng_gif_rcvdata, + .disconnect = ng_gif_disconnect, + .cmdlist = ng_gif_cmdlist, +}; +MODULE_DEPEND(ng_gif, if_gif, 1,1,1); +NETGRAPH_INIT(gif, &ng_gif_typestruct); + +/****************************************************************** + GIF FUNCTION HOOKS +******************************************************************/ + +/* + * Handle a packet that has come in on an interface. We get to + * look at it here before any upper layer protocols do. + * + * NOTE: this function will get called at splimp() + */ +static void +ng_gif_input(struct ifnet *ifp, struct mbuf **mp, int af) +{ + const node_p node = IFP2NG(ifp); + const priv_p priv = NG_NODE_PRIVATE(node); + + /* If "lower" hook not connected, let packet continue */ + if (priv->lower == NULL || priv->lowerOrphan) + return; + ng_gif_input2(node, mp, af); +} + +/* + * Handle a packet that has come in on an interface, and which + * does not match any of our known protocols (an ``orphan''). + * + * NOTE: this function will get called at splimp() + */ +static void +ng_gif_input_orphan(struct ifnet *ifp, struct mbuf *m, int af) +{ + const node_p node = IFP2NG(ifp); + const priv_p priv = NG_NODE_PRIVATE(node); + + /* If "orphan" hook not connected, let packet continue */ + if (priv->lower == NULL || !priv->lowerOrphan) { + m_freem(m); + return; + } + ng_gif_input2(node, &m, af); + if (m != NULL) + m_freem(m); +} + +/* + * Handle a packet that has come in on a gif interface. + * Attach the address family to the mbuf for later use. + * + * NOTE: this function will get called at splimp() + */ +static void +ng_gif_input2(node_p node, struct mbuf **mp, int af) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + int error; + + /* Glue address family on */ + if ((error = ng_gif_glue_af(mp, af)) != 0) + return; + + /* Send out lower/orphan hook */ + NG_SEND_DATA_ONLY(error, priv->lower, *mp); + *mp = NULL; +} + +/* + * A new gif interface has been attached. + * Create a new node for it, etc. + */ +static void +ng_gif_attach(struct ifnet *ifp) +{ + priv_p priv; + node_p node; + + /* Create node */ + KASSERT(!IFP2NG(ifp), ("%s: node already exists?", __func__)); + if (ng_make_node_common(&ng_gif_typestruct, &node) != 0) { + log(LOG_ERR, "%s: can't %s for %s\n", + __func__, "create node", ifp->if_xname); + return; + } + + /* Allocate private data */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (priv == NULL) { + log(LOG_ERR, "%s: can't %s for %s\n", + __func__, "allocate memory", ifp->if_xname); + NG_NODE_UNREF(node); + return; + } + NG_NODE_SET_PRIVATE(node, priv); + priv->ifp = ifp; + IFP2NG_SET(ifp, node); + + /* Try to give the node the same name as the interface */ + if (ng_name_node(node, ifp->if_xname) != 0) { + log(LOG_WARNING, "%s: can't name node %s\n", + __func__, ifp->if_xname); + } +} + +/* + * An interface is being detached. + * REALLY Destroy its node. + */ +static void +ng_gif_detach(struct ifnet *ifp) +{ + const node_p node = IFP2NG(ifp); + priv_p priv; + + if (node == NULL) /* no node (why not?), ignore */ + return; + priv = NG_NODE_PRIVATE(node); + NG_NODE_REALLY_DIE(node); /* Force real removal of node */ + /* + * We can't assume the ifnet is still around when we run shutdown + * So zap it now. XXX We HOPE that anything running at this time + * handles it (as it should in the non netgraph case). + */ + IFP2NG_SET(ifp, NULL); + priv->ifp = NULL; /* XXX race if interrupted an output packet */ + ng_rmnode_self(node); /* remove all netgraph parts */ +} + +/* + * Optimization for gluing the address family onto + * the front of an incoming packet. + */ +static int +ng_gif_glue_af(struct mbuf **mp, int af) +{ + struct mbuf *m = *mp; + int error = 0; + sa_family_t tmp_af; + + tmp_af = (sa_family_t) af; + + /* + * XXX: should try to bring back some of the optimizations from + * ng_ether.c + */ + + /* + * Doing anything more is likely to get more + * expensive than it's worth.. + * it's probable that everything else is in one + * big lump. The next node will do an m_pullup() + * for exactly the amount of data it needs and + * hopefully everything after that will not + * need one. So let's just use M_PREPEND. + */ + M_PREPEND(m, sizeof (tmp_af), M_DONTWAIT); + if (m == NULL) { + error = ENOBUFS; + goto done; + } + +#if 0 +copy: +#endif + /* Copy header and return (possibly new) mbuf */ + *mtod(m, sa_family_t *) = tmp_af; +#if 0 + bcopy((caddr_t)&tmp_af, mtod(m, sa_family_t *), sizeof(tmp_af)); +#endif +done: + *mp = m; + return error; +} + +/****************************************************************** + NETGRAPH NODE METHODS +******************************************************************/ + +/* + * It is not possible or allowable to create a node of this type. + * Nodes get created when the interface is attached (or, when + * this node type's KLD is loaded). + */ +static int +ng_gif_constructor(node_p node) +{ + return (EINVAL); +} + +/* + * Check for attaching a new hook. + */ +static int +ng_gif_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + u_char orphan = priv->lowerOrphan; + hook_p *hookptr; + + /* Divert hook is an alias for lower */ + if (strcmp(name, NG_GIF_HOOK_DIVERT) == 0) + name = NG_GIF_HOOK_LOWER; + + /* Which hook? */ + if (strcmp(name, NG_GIF_HOOK_LOWER) == 0) { + hookptr = &priv->lower; + orphan = 0; + } else if (strcmp(name, NG_GIF_HOOK_ORPHAN) == 0) { + hookptr = &priv->lower; + orphan = 1; + } else + return (EINVAL); + + /* Check if already connected (shouldn't be, but doesn't hurt) */ + if (*hookptr != NULL) + return (EISCONN); + + /* OK */ + *hookptr = hook; + priv->lowerOrphan = orphan; + return (0); +} + +/* + * Hooks are attached, adjust to force queueing. + * We don't really care which hook it is. + * they should all be queuing for outgoing data. + */ +static int +ng_gif_connect(hook_p hook) +{ + NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); + return (0); +} + +/* + * Receive an incoming control message. + */ +static int +ng_gif_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_GIF_COOKIE: + switch (msg->header.cmd) { + case NGM_GIF_GET_IFNAME: + NG_MKRESPONSE(resp, msg, IFNAMSIZ, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + strlcpy(resp->data, priv->ifp->if_xname, IFNAMSIZ); + break; + case NGM_GIF_GET_IFINDEX: + NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + *((u_int32_t *)resp->data) = priv->ifp->if_index; + break; + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive data on a hook. + */ +static int +ng_gif_rcvdata(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + struct mbuf *m; + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + if (hook == priv->lower) + return ng_gif_rcv_lower(node, m); + panic("%s: weird hook", __func__); +} + +/* + * Handle an mbuf received on the "lower" hook. + */ +static int +ng_gif_rcv_lower(node_p node, struct mbuf *m) +{ + struct sockaddr dst; + const priv_p priv = NG_NODE_PRIVATE(node); + + bzero(&dst, sizeof(dst)); + + /* Make sure header is fully pulled up */ + if (m->m_pkthdr.len < sizeof(sa_family_t)) { + NG_FREE_M(m); + return (EINVAL); + } + if (m->m_len < sizeof(sa_family_t) + && (m = m_pullup(m, sizeof(sa_family_t))) == NULL) { + return (ENOBUFS); + } + + dst.sa_family = *mtod(m, sa_family_t *); + m_adj(m, sizeof(sa_family_t)); + + /* Send it on its way */ + /* + * XXX: gif_output only uses dst for the family and passes the + * fourth argument (rt) to in{,6}_gif_output which ignore it. + * If this changes ng_gif will probably break. + */ + return gif_output(priv->ifp, m, &dst, NULL); +} + +/* + * Shutdown node. This resets the node but does not remove it + * unless the REALLY_DIE flag is set. + */ +static int +ng_gif_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (node->nd_flags & NGF_REALLY_DIE) { + /* + * WE came here because the gif interface is being destroyed, + * so stop being persistant. + * Actually undo all the things we did on creation. + * Assume the ifp has already been freed. + */ + NG_NODE_SET_PRIVATE(node, NULL); + FREE(priv, M_NETGRAPH); + NG_NODE_UNREF(node); /* free node itself */ + return (0); + } + NG_NODE_REVIVE(node); /* Signal ng_rmnode we are persisant */ + return (0); +} + +/* + * Hook disconnection. + */ +static int +ng_gif_disconnect(hook_p hook) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + if (hook == priv->lower) { + priv->lower = NULL; + priv->lowerOrphan = 0; + } else + panic("%s: weird hook", __func__); + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) + ng_rmnode_self(NG_HOOK_NODE(hook)); /* reset node */ + + return (0); +} + +/****************************************************************** + INITIALIZATION +******************************************************************/ + +/* + * Handle loading and unloading for this node type. + */ +static int +ng_gif_mod_event(module_t mod, int event, void *data) +{ + struct ifnet *ifp; + int error = 0; + int s; + + s = splnet(); + switch (event) { + case MOD_LOAD: + + /* Register function hooks */ + if (ng_gif_attach_p != NULL) { + error = EEXIST; + break; + } + ng_gif_attach_p = ng_gif_attach; + ng_gif_detach_p = ng_gif_detach; + ng_gif_input_p = ng_gif_input; + ng_gif_input_orphan_p = ng_gif_input_orphan; + + /* Create nodes for any already-existing gif interfaces */ + IFNET_RLOCK(); + TAILQ_FOREACH(ifp, &ifnet, if_link) { + if (ifp->if_type == IFT_GIF) + ng_gif_attach(ifp); + } + IFNET_RUNLOCK(); + break; + + case MOD_UNLOAD: + + /* + * Note that the base code won't try to unload us until + * all nodes have been removed, and that can't happen + * until all gif interfaces are destroyed. In any + * case, we know there are no nodes left if the action + * is MOD_UNLOAD, so there's no need to detach any nodes. + * + * XXX: what about manual unloads?!? + */ + + /* Unregister function hooks */ + ng_gif_attach_p = NULL; + ng_gif_detach_p = NULL; + ng_gif_input_p = NULL; + ng_gif_input_orphan_p = NULL; + break; + + default: + error = EOPNOTSUPP; + break; + } + splx(s); + return (error); +} + diff --git a/sys/netgraph7/ng_gif.h b/sys/netgraph7/ng_gif.h new file mode 100644 index 0000000000..d284f26540 --- /dev/null +++ b/sys/netgraph7/ng_gif.h @@ -0,0 +1,86 @@ +/* + * ng_gif.h + */ + +/*- + * Copyright 2001 The Aerospace Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions, and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of The Aerospace Corporation may not be used to endorse or + * promote products derived from this software. + * + * THIS SOFTWARE IS PROVIDED BY THE AEROSPACE CORPORATION ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AEROSPACE CORPORATION BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_gif.h,v 1.5 2005/01/07 01:45:39 imp Exp $ + */ + +#ifndef _NETGRAPH_NG_GIF_H_ +#define _NETGRAPH_NG_GIF_H_ + +/* Node type name and magic cookie */ +#define NG_GIF_NODE_TYPE "gif" +#define NGM_GIF_COOKIE 994115727 + +/* Hook names */ +#define NG_GIF_HOOK_LOWER "lower" /* connection to raw device */ +#define NG_GIF_HOOK_DIVERT "divert" /* alias for lower */ +#define NG_GIF_HOOK_ORPHAN "orphans" /* like lower, unknowns only */ + +/* Netgraph control messages */ +enum { + NGM_GIF_GET_IFNAME = 1, /* get the interface name */ + NGM_GIF_GET_IFINDEX /* get the interface global index # */ +}; + +#endif /* _NETGRAPH_NG_GIF_H_ */ diff --git a/sys/netgraph7/ng_gif_demux.c b/sys/netgraph7/ng_gif_demux.c new file mode 100644 index 0000000000..133590ebd3 --- /dev/null +++ b/sys/netgraph7/ng_gif_demux.c @@ -0,0 +1,399 @@ +/* + * ng_gif_demux.c + */ + +/*- + * Copyright 2001 The Aerospace Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions, and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of The Aerospace Corporation may not be used to endorse or + * promote products derived from this software. + * + * THIS SOFTWARE IS PROVIDED BY THE AEROSPACE CORPORATION ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AEROSPACE CORPORATION BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_gif_demux.c,v 1.10 2005/01/07 01:45:39 imp Exp $ + */ + +/* + * ng_gif_demux(4) netgraph node type + * + * Packets received on the "gif" hook have their type header removed + * and are passed to the appropriate hook protocol hook. Packets + * recieved on a protocol hook have a type header added back and are + * passed out the gif hook. The currently supported protocol hooks are: + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_GIF_DEMUX, "netgraph_gif_demux", + "netgraph gif demux node"); +#else +#define M_NETGRAPH_GIF_DEMUX M_NETGRAPH +#endif + +/* This struct describes one address family */ +struct iffam { + sa_family_t family; /* Address family */ + const char *hookname; /* Name for hook */ +}; +typedef const struct iffam *iffam_p; + +/* List of address families supported by our interface */ +const static struct iffam gFamilies[] = { + { AF_INET, NG_GIF_DEMUX_HOOK_INET }, + { AF_INET6, NG_GIF_DEMUX_HOOK_INET6 }, + { AF_APPLETALK, NG_GIF_DEMUX_HOOK_ATALK }, + { AF_IPX, NG_GIF_DEMUX_HOOK_IPX }, + { AF_ATM, NG_GIF_DEMUX_HOOK_ATM }, + { AF_NATM, NG_GIF_DEMUX_HOOK_NATM }, +}; +#define NUM_FAMILIES (sizeof(gFamilies) / sizeof(*gFamilies)) + +/* Per-node private data */ +struct ng_gif_demux_private { + node_p node; /* Our netgraph node */ + hook_p gif; /* The gif hook */ + hook_p hooks[NUM_FAMILIES]; /* The protocol hooks */ +}; +typedef struct ng_gif_demux_private *priv_p; + +/* Netgraph node methods */ +static ng_constructor_t ng_gif_demux_constructor; +static ng_rcvmsg_t ng_gif_demux_rcvmsg; +static ng_shutdown_t ng_gif_demux_shutdown; +static ng_newhook_t ng_gif_demux_newhook; +static ng_rcvdata_t ng_gif_demux_rcvdata; +static ng_disconnect_t ng_gif_demux_disconnect; + +/* Helper stuff */ +static iffam_p get_iffam_from_af(sa_family_t family); +static iffam_p get_iffam_from_hook(priv_p priv, hook_p hook); +static iffam_p get_iffam_from_name(const char *name); +static hook_p *get_hook_from_iffam(priv_p priv, iffam_p iffam); + +/****************************************************************** + NETGRAPH PARSE TYPES +******************************************************************/ + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_gif_demux_cmdlist[] = { + { 0 } +}; + +/* Node type descriptor */ +static struct ng_type ng_gif_demux_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_GIF_DEMUX_NODE_TYPE, + .constructor = ng_gif_demux_constructor, + .rcvmsg = ng_gif_demux_rcvmsg, + .shutdown = ng_gif_demux_shutdown, + .newhook = ng_gif_demux_newhook, + .rcvdata = ng_gif_demux_rcvdata, + .disconnect = ng_gif_demux_disconnect, + .cmdlist = ng_gif_demux_cmdlist, +}; +NETGRAPH_INIT(gif_demux, &ng_gif_demux_typestruct); + +/************************************************************************ + HELPER STUFF + ************************************************************************/ + +/* + * Get the family descriptor from the family ID + */ +static __inline iffam_p +get_iffam_from_af(sa_family_t family) +{ + iffam_p iffam; + int k; + + for (k = 0; k < NUM_FAMILIES; k++) { + iffam = &gFamilies[k]; + if (iffam->family == family) + return (iffam); + } + return (NULL); +} + +/* + * Get the family descriptor from the hook + */ +static __inline iffam_p +get_iffam_from_hook(priv_p priv, hook_p hook) +{ + int k; + + for (k = 0; k < NUM_FAMILIES; k++) + if (priv->hooks[k] == hook) + return (&gFamilies[k]); + return (NULL); +} + +/* + * Get the hook from the iffam descriptor + */ + +static __inline hook_p * +get_hook_from_iffam(priv_p priv, iffam_p iffam) +{ + return (&priv->hooks[iffam - gFamilies]); +} + +/* + * Get the iffam descriptor from the name + */ +static __inline iffam_p +get_iffam_from_name(const char *name) +{ + iffam_p iffam; + int k; + + for (k = 0; k < NUM_FAMILIES; k++) { + iffam = &gFamilies[k]; + if (!strcmp(iffam->hookname, name)) + return (iffam); + } + return (NULL); +} + +/****************************************************************** + NETGRAPH NODE METHODS +******************************************************************/ + +/* + * Node constructor + */ +static int +ng_gif_demux_constructor(node_p node) +{ + priv_p priv; + + /* Allocate and initialize private info */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH_GIF_DEMUX, + M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + priv->node = node; + + NG_NODE_SET_PRIVATE(node, priv); + + /* Done */ + return (0); +} + +/* + * Method for attaching a new hook + */ +static int +ng_gif_demux_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + iffam_p iffam; + hook_p *hookptr; + + if (strcmp(NG_GIF_DEMUX_HOOK_GIF, name) == 0) + hookptr = &priv->gif; + else { + iffam = get_iffam_from_name(name); + if (iffam == NULL) + return (EPFNOSUPPORT); + hookptr = get_hook_from_iffam(NG_NODE_PRIVATE(node), iffam); + } + if (*hookptr != NULL) + return (EISCONN); + *hookptr = hook; + return (0); +} + +/* + * Receive a control message + */ +static int +ng_gif_demux_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_GIF_DEMUX_COOKIE: + switch (msg->header.cmd) { + /* XXX: Add commands here. */ + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } + + /* Done */ + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive data on a hook + */ +static int +ng_gif_demux_rcvdata(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + iffam_p iffam; + hook_p outhook; + int error = 0; + struct mbuf *m; + + /* Pull the mbuf out of the item for processing. */ + NGI_GET_M(item, m); + + if (hook == priv->gif) { + /* + * Pull off the address family header and find the + * output hook. + */ + if (m->m_pkthdr.len < sizeof(sa_family_t)) { + NG_FREE_M(m); + NG_FREE_ITEM(item); + return (EINVAL); + } + if (m->m_len < sizeof(sa_family_t) + && (m = m_pullup(m, sizeof(sa_family_t))) == NULL) { + NG_FREE_ITEM(item); + return (ENOBUFS); + } + iffam = get_iffam_from_af(*mtod(m, sa_family_t *)); + if (iffam == NULL) { + NG_FREE_M(m); + NG_FREE_ITEM(item); + return (EINVAL); + } + outhook = *get_hook_from_iffam(priv, iffam); + m_adj(m, sizeof(sa_family_t)); + } else { + /* + * Add address family header and set the output hook. + */ + iffam = get_iffam_from_hook(priv, hook); + M_PREPEND(m, sizeof (iffam->family), M_DONTWAIT); + if (m == NULL) { + NG_FREE_M(m); + NG_FREE_ITEM(item); + return (ENOBUFS); + } + bcopy(&iffam->family, mtod(m, sa_family_t *), + sizeof(iffam->family)); + outhook = priv->gif; + } + + /* Stuff the mbuf back in. */ + NGI_M(item) = m; + + /* Deliver packet */ + NG_FWD_ITEM_HOOK(error, item, outhook); + return (error); +} + +/* + * Shutdown node + */ +static int +ng_gif_demux_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + FREE(priv, M_NETGRAPH_GIF_DEMUX); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + return (0); +} + +/* + * Hook disconnection. + */ +static int +ng_gif_demux_disconnect(hook_p hook) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + iffam_p iffam; + + if (hook == priv->gif) + priv->gif = NULL; + else { + iffam = get_iffam_from_hook(priv, hook); + if (iffam == NULL) + panic(__func__); + *get_hook_from_iffam(priv, iffam) = NULL; + } + + return (0); +} diff --git a/sys/netgraph7/ng_gif_demux.h b/sys/netgraph7/ng_gif_demux.h new file mode 100644 index 0000000000..f4293cd4e1 --- /dev/null +++ b/sys/netgraph7/ng_gif_demux.h @@ -0,0 +1,51 @@ +/* + * ng_gif_demux.h + */ + +/*- + * Copyright 2001 The Aerospace Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions, and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of The Aerospace Corporation may not be used to endorse or + * promote products derived from this software. + * + * THIS SOFTWARE IS PROVIDED BY THE AEROSPACE CORPORATION ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AEROSPACE CORPORATION BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_gif_demux.h,v 1.5 2005/01/07 01:45:39 imp Exp $ + */ + +#ifndef _NETGRAPH_NG_GIF_DEMUX_H_ +#define _NETGRAPH_NG_GIF_DEMUX_H_ + +/* Node type name and magic cookie */ +#define NG_GIF_DEMUX_NODE_TYPE "gif_demux" +#define NGM_GIF_DEMUX_COOKIE 995567329 + +/* Hook names */ +#define NG_GIF_DEMUX_HOOK_GIF "gif" +#define NG_GIF_DEMUX_HOOK_INET "inet" +#define NG_GIF_DEMUX_HOOK_INET6 "inet6" +#define NG_GIF_DEMUX_HOOK_ATALK "atalk" +#define NG_GIF_DEMUX_HOOK_IPX "ipx" +#define NG_GIF_DEMUX_HOOK_ATM "atm" +#define NG_GIF_DEMUX_HOOK_NATM "natm" + +#endif /* _NETGRAPH_NG_GIF_DEMUX_H_ */ diff --git a/sys/netgraph7/ng_hole.c b/sys/netgraph7/ng_hole.c new file mode 100644 index 0000000000..c98b052e58 --- /dev/null +++ b/sys/netgraph7/ng_hole.c @@ -0,0 +1,227 @@ +/* + * ng_hole.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elisher + * + * $FreeBSD: src/sys/netgraph/ng_hole.c,v 1.15 2005/12/09 07:09:44 ru Exp $ + * $Whistle: ng_hole.c,v 1.10 1999/11/01 09:24:51 julian Exp $ + */ + +/* + * This node is a 'black hole' that simply discards everything it receives + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Per hook private info. */ +struct ng_hole_hookinfo { + struct ng_hole_hookstat stats; +}; +typedef struct ng_hole_hookinfo *hinfo_p; + +/* Parse type for struct ng_hole_hookstat. */ +static const struct ng_parse_struct_field ng_hole_hookstat_type_fields[] = + NG_HOLE_HOOKSTAT_TYPE_INFO; +static const struct ng_parse_type ng_hole_hookstat_type = { + &ng_parse_struct_type, + &ng_hole_hookstat_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII. */ +static const struct ng_cmdlist ng_hole_cmdlist[] = { + { + NGM_HOLE_COOKIE, + NGM_HOLE_GET_STATS, + "getstats", + &ng_parse_hookbuf_type, + &ng_hole_hookstat_type + }, + { + NGM_HOLE_COOKIE, + NGM_HOLE_CLR_STATS, + "clrstats", + &ng_parse_hookbuf_type, + NULL + }, + { + NGM_HOLE_COOKIE, + NGM_HOLE_GETCLR_STATS, + "getclrstats", + &ng_parse_hookbuf_type, + &ng_hole_hookstat_type + }, + { 0 } +}; + +/* Netgraph methods */ +static ng_constructor_t ngh_cons; +static ng_rcvmsg_t ngh_rcvmsg; +static ng_newhook_t ngh_newhook; +static ng_rcvdata_t ngh_rcvdata; +static ng_disconnect_t ngh_disconnect; + +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_HOLE_NODE_TYPE, + .constructor = ngh_cons, + .rcvmsg = ngh_rcvmsg, + .newhook = ngh_newhook, + .rcvdata = ngh_rcvdata, + .disconnect = ngh_disconnect, + .cmdlist = ng_hole_cmdlist, +}; +NETGRAPH_INIT(hole, &typestruct); + +/* + * Be obliging. but no work to do. + */ +static int +ngh_cons(node_p node) +{ + return(0); +} + +/* + * Add a hook. + */ +static int +ngh_newhook(node_p node, hook_p hook, const char *name) +{ + hinfo_p hip; + + /* Create hook private structure. */ + MALLOC(hip, hinfo_p, sizeof(*hip), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (hip == NULL) + return (ENOMEM); + NG_HOOK_SET_PRIVATE(hook, hip); + return (0); +} + +/* + * Receive a control message. + */ +static int +ngh_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct ng_mesg *msg; + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_hole_hookstat *stats; + hook_p hook; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_HOLE_COOKIE: + switch (msg->header.cmd) { + case NGM_HOLE_GET_STATS: + case NGM_HOLE_CLR_STATS: + case NGM_HOLE_GETCLR_STATS: + /* Sanity check. */ + if (msg->header.arglen != NG_HOOKSIZ) { + error = EINVAL; + break; + } + /* Find hook. */ + hook = ng_findhook(node, (char *)msg->data); + if (hook == NULL) { + error = ENOENT; + break; + } + stats = &((hinfo_p)NG_HOOK_PRIVATE(hook))->stats; + /* Build response (if desired). */ + if (msg->header.cmd != NGM_HOLE_CLR_STATS) { + NG_MKRESPONSE(resp, msg, sizeof(*stats), + M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + bcopy(stats, resp->data, sizeof(*stats)); + } + /* Clear stats (if desired). */ + if (msg->header.cmd != NGM_HOLE_GET_STATS) + bzero(stats, sizeof(*stats)); + break; + default: /* Unknown command. */ + error = EINVAL; + break; + } + break; + default: /* Unknown type cookie. */ + error = EINVAL; + break; + } + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive data + */ +static int +ngh_rcvdata(hook_p hook, item_p item) +{ + const hinfo_p hip = NG_HOOK_PRIVATE(hook); + + hip->stats.frames++; + hip->stats.octets += NGI_M(item)->m_pkthdr.len; + NG_FREE_ITEM(item); + return 0; +} + +/* + * Hook disconnection + */ +static int +ngh_disconnect(hook_p hook) +{ + + FREE(NG_HOOK_PRIVATE(hook), M_NETGRAPH); + NG_HOOK_SET_PRIVATE(hook, NULL); + if (NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + ng_rmnode_self(NG_HOOK_NODE(hook)); + return (0); +} diff --git a/sys/netgraph7/ng_hole.h b/sys/netgraph7/ng_hole.h new file mode 100644 index 0000000000..72097572ae --- /dev/null +++ b/sys/netgraph7/ng_hole.h @@ -0,0 +1,71 @@ +/* + * ng_hole.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_hole.h,v 1.6 2005/01/07 01:45:39 imp Exp $ + * $Whistle: ng_hole.h,v 1.3 1999/01/20 00:22:13 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_HOLE_H_ +#define _NETGRAPH_NG_HOLE_H_ + +/* Node type name and magic cookie */ +#define NG_HOLE_NODE_TYPE "hole" +#define NGM_HOLE_COOKIE 915433206 + +/* Statistics structure for one hook. */ +struct ng_hole_hookstat { + uint64_t frames; + uint64_t octets; +}; + +/* Keep this in sync with the above structure definition. */ +#define NG_HOLE_HOOKSTAT_TYPE_INFO { \ + { "frames", &ng_parse_uint64_type }, \ + { "octets", &ng_parse_uint64_type }, \ + { NULL } \ +} + +/* Netgraph commands. */ +enum { + NGM_HOLE_GET_STATS = 1, + NGM_HOLE_CLR_STATS, + NGM_HOLE_GETCLR_STATS, +}; + +#endif /* _NETGRAPH_NG_HOLE_H_ */ diff --git a/sys/netgraph7/ng_hub.c b/sys/netgraph7/ng_hub.c new file mode 100644 index 0000000000..3f6bfaead3 --- /dev/null +++ b/sys/netgraph7/ng_hub.c @@ -0,0 +1,100 @@ +/*- + * Copyright (c) 2004 Ruslan Ermilov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_hub.c,v 1.3 2004/06/26 22:24:16 julian Exp $ + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +static ng_constructor_t ng_hub_constructor; +static ng_rcvdata_t ng_hub_rcvdata; +static ng_disconnect_t ng_hub_disconnect; + +static struct ng_type ng_hub_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_HUB_NODE_TYPE, + .constructor = ng_hub_constructor, + .rcvdata = ng_hub_rcvdata, + .disconnect = ng_hub_disconnect, +}; +NETGRAPH_INIT(hub, &ng_hub_typestruct); + + +static int +ng_hub_constructor(node_p node) +{ + + return (0); +} + +static int +ng_hub_rcvdata(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + int error = 0; + hook_p hook2; + struct mbuf * const m = NGI_M(item), *m2; + int nhooks; + + if ((nhooks = NG_NODE_NUMHOOKS(node)) == 1) { + NG_FREE_ITEM(item); + return (0); + } + LIST_FOREACH(hook2, &node->nd_hooks, hk_hooks) { + if (hook2 == hook) + continue; + if (--nhooks == 1) + NG_FWD_ITEM_HOOK(error, item, hook2); + else { + if ((m2 = m_dup(m, M_DONTWAIT)) == NULL) { + NG_FREE_ITEM(item); + return (ENOBUFS); + } + NG_SEND_DATA_ONLY(error, hook2, m2); + if (error) + continue; /* don't give up */ + } + } + + return (error); +} + +static int +ng_hub_disconnect(hook_p hook) +{ + + if (NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0 && + NG_NODE_IS_VALID(NG_HOOK_NODE(hook))) + ng_rmnode_self(NG_HOOK_NODE(hook)); + return (0); +} diff --git a/sys/netgraph7/ng_hub.h b/sys/netgraph7/ng_hub.h new file mode 100644 index 0000000000..4d5f308a13 --- /dev/null +++ b/sys/netgraph7/ng_hub.h @@ -0,0 +1,36 @@ +/*- + * Copyright (c) 2004 Ruslan Ermilov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_hub.h,v 1.1 2004/04/17 12:42:16 ru Exp $ + */ + +#ifndef _NETGRAPH_NG_HUB_H_ +#define _NETGRAPH_NG_HUB_H_ + +/* Node type name and magic cookie. */ +#define NG_HUB_NODE_TYPE "hub" +#define NGM_HUB_COOKIE 1082189597 + +#endif /* _NETGRAPH_NG_HUB_H_ */ diff --git a/sys/netgraph7/ng_iface.c b/sys/netgraph7/ng_iface.c new file mode 100644 index 0000000000..b3a1bdca27 --- /dev/null +++ b/sys/netgraph7/ng_iface.c @@ -0,0 +1,817 @@ +/* + * ng_iface.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_iface.c,v 1.48 2008/01/31 08:51:48 mav Exp $ + * $Whistle: ng_iface.c,v 1.33 1999/11/01 09:24:51 julian Exp $ + */ + +/* + * This node is also a system networking interface. It has + * a hook for each protocol (IP, AppleTalk, IPX, etc). Packets + * are simply relayed between the interface and the hooks. + * + * Interfaces are named ng0, ng1, etc. New nodes take the + * first available interface name. + * + * This node also includes Berkeley packet filter support. + */ + +#include "opt_atalk.h" +#include "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipx.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_IFACE, "netgraph_iface", "netgraph iface node "); +#else +#define M_NETGRAPH_IFACE M_NETGRAPH +#endif + +/* This struct describes one address family */ +struct iffam { + sa_family_t family; /* Address family */ + const char *hookname; /* Name for hook */ +}; +typedef const struct iffam *iffam_p; + +/* List of address families supported by our interface */ +const static struct iffam gFamilies[] = { + { AF_INET, NG_IFACE_HOOK_INET }, + { AF_INET6, NG_IFACE_HOOK_INET6 }, + { AF_APPLETALK, NG_IFACE_HOOK_ATALK }, + { AF_IPX, NG_IFACE_HOOK_IPX }, + { AF_ATM, NG_IFACE_HOOK_ATM }, + { AF_NATM, NG_IFACE_HOOK_NATM }, +}; +#define NUM_FAMILIES (sizeof(gFamilies) / sizeof(*gFamilies)) + +/* Node private data */ +struct ng_iface_private { + struct ifnet *ifp; /* Our interface */ + int unit; /* Interface unit number */ + node_p node; /* Our netgraph node */ + hook_p hooks[NUM_FAMILIES]; /* Hook for each address family */ +}; +typedef struct ng_iface_private *priv_p; + +/* Interface methods */ +static void ng_iface_start(struct ifnet *ifp); +static int ng_iface_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data); +static int ng_iface_output(struct ifnet *ifp, struct mbuf *m0, + struct sockaddr *dst, struct rtentry *rt0); +static void ng_iface_bpftap(struct ifnet *ifp, + struct mbuf *m, sa_family_t family); +static int ng_iface_send(struct ifnet *ifp, struct mbuf *m, + sa_family_t sa); +#ifdef DEBUG +static void ng_iface_print_ioctl(struct ifnet *ifp, int cmd, caddr_t data); +#endif + +/* Netgraph methods */ +static int ng_iface_mod_event(module_t, int, void *); +static ng_constructor_t ng_iface_constructor; +static ng_rcvmsg_t ng_iface_rcvmsg; +static ng_shutdown_t ng_iface_shutdown; +static ng_newhook_t ng_iface_newhook; +static ng_rcvdata_t ng_iface_rcvdata; +static ng_disconnect_t ng_iface_disconnect; + +/* Helper stuff */ +static iffam_p get_iffam_from_af(sa_family_t family); +static iffam_p get_iffam_from_hook(priv_p priv, hook_p hook); +static iffam_p get_iffam_from_name(const char *name); +static hook_p *get_hook_from_iffam(priv_p priv, iffam_p iffam); + +/* Parse type for struct ng_cisco_ipaddr */ +static const struct ng_parse_struct_field ng_cisco_ipaddr_type_fields[] + = NG_CISCO_IPADDR_TYPE_INFO; +static const struct ng_parse_type ng_cisco_ipaddr_type = { + &ng_parse_struct_type, + &ng_cisco_ipaddr_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_iface_cmds[] = { + { + NGM_IFACE_COOKIE, + NGM_IFACE_GET_IFNAME, + "getifname", + NULL, + &ng_parse_string_type + }, + { + NGM_IFACE_COOKIE, + NGM_IFACE_POINT2POINT, + "point2point", + NULL, + NULL + }, + { + NGM_IFACE_COOKIE, + NGM_IFACE_BROADCAST, + "broadcast", + NULL, + NULL + }, + { + NGM_CISCO_COOKIE, + NGM_CISCO_GET_IPADDR, + "getipaddr", + NULL, + &ng_cisco_ipaddr_type + }, + { + NGM_IFACE_COOKIE, + NGM_IFACE_GET_IFINDEX, + "getifindex", + NULL, + &ng_parse_uint32_type + }, + { 0 } +}; + +/* Node type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_IFACE_NODE_TYPE, + .mod_event = ng_iface_mod_event, + .constructor = ng_iface_constructor, + .rcvmsg = ng_iface_rcvmsg, + .shutdown = ng_iface_shutdown, + .newhook = ng_iface_newhook, + .rcvdata = ng_iface_rcvdata, + .disconnect = ng_iface_disconnect, + .cmdlist = ng_iface_cmds, +}; +NETGRAPH_INIT(iface, &typestruct); + +static struct unrhdr *ng_iface_unit; + +/************************************************************************ + HELPER STUFF + ************************************************************************/ + +/* + * Get the family descriptor from the family ID + */ +static __inline iffam_p +get_iffam_from_af(sa_family_t family) +{ + iffam_p iffam; + int k; + + for (k = 0; k < NUM_FAMILIES; k++) { + iffam = &gFamilies[k]; + if (iffam->family == family) + return (iffam); + } + return (NULL); +} + +/* + * Get the family descriptor from the hook + */ +static __inline iffam_p +get_iffam_from_hook(priv_p priv, hook_p hook) +{ + int k; + + for (k = 0; k < NUM_FAMILIES; k++) + if (priv->hooks[k] == hook) + return (&gFamilies[k]); + return (NULL); +} + +/* + * Get the hook from the iffam descriptor + */ + +static __inline hook_p * +get_hook_from_iffam(priv_p priv, iffam_p iffam) +{ + return (&priv->hooks[iffam - gFamilies]); +} + +/* + * Get the iffam descriptor from the name + */ +static __inline iffam_p +get_iffam_from_name(const char *name) +{ + iffam_p iffam; + int k; + + for (k = 0; k < NUM_FAMILIES; k++) { + iffam = &gFamilies[k]; + if (!strcmp(iffam->hookname, name)) + return (iffam); + } + return (NULL); +} + +/************************************************************************ + INTERFACE STUFF + ************************************************************************/ + +/* + * Process an ioctl for the virtual interface + */ +static int +ng_iface_ioctl(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct ifreq *const ifr = (struct ifreq *) data; + int s, error = 0; + +#ifdef DEBUG + ng_iface_print_ioctl(ifp, command, data); +#endif + s = splimp(); + switch (command) { + + /* These two are mostly handled at a higher layer */ + case SIOCSIFADDR: + ifp->if_flags |= IFF_UP; + ifp->if_drv_flags |= IFF_DRV_RUNNING; + ifp->if_drv_flags &= ~(IFF_DRV_OACTIVE); + break; + case SIOCGIFADDR: + break; + + /* Set flags */ + case SIOCSIFFLAGS: + /* + * If the interface is marked up and stopped, then start it. + * If it is marked down and running, then stop it. + */ + if (ifr->ifr_flags & IFF_UP) { + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + ifp->if_drv_flags &= ~(IFF_DRV_OACTIVE); + ifp->if_drv_flags |= IFF_DRV_RUNNING; + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | + IFF_DRV_OACTIVE); + } + break; + + /* Set the interface MTU */ + case SIOCSIFMTU: + if (ifr->ifr_mtu > NG_IFACE_MTU_MAX + || ifr->ifr_mtu < NG_IFACE_MTU_MIN) + error = EINVAL; + else + ifp->if_mtu = ifr->ifr_mtu; + break; + + /* Stuff that's not supported */ + case SIOCADDMULTI: + case SIOCDELMULTI: + error = 0; + break; + case SIOCSIFPHYS: + error = EOPNOTSUPP; + break; + + default: + error = EINVAL; + break; + } + (void) splx(s); + return (error); +} + +/* + * This routine is called to deliver a packet out the interface. + * We simply look at the address family and relay the packet to + * the corresponding hook, if it exists and is connected. + */ + +static int +ng_iface_output(struct ifnet *ifp, struct mbuf *m, + struct sockaddr *dst, struct rtentry *rt0) +{ + uint32_t af; + int error; + + /* Check interface flags */ + if (!((ifp->if_flags & IFF_UP) && + (ifp->if_drv_flags & IFF_DRV_RUNNING))) { + m_freem(m); + return (ENETDOWN); + } + + /* BPF writes need to be handled specially. */ + if (dst->sa_family == AF_UNSPEC) { + bcopy(dst->sa_data, &af, sizeof(af)); + dst->sa_family = af; + } + + /* Berkeley packet filter */ + ng_iface_bpftap(ifp, m, dst->sa_family); + + if (ALTQ_IS_ENABLED(&ifp->if_snd)) { + M_PREPEND(m, sizeof(sa_family_t), M_DONTWAIT); + if (m == NULL) { + IFQ_LOCK(&ifp->if_snd); + IFQ_INC_DROPS(&ifp->if_snd); + IFQ_UNLOCK(&ifp->if_snd); + ifp->if_oerrors++; + return (ENOBUFS); + } + *(sa_family_t *)m->m_data = dst->sa_family; + IFQ_HANDOFF(ifp, m, error); + } else + error = ng_iface_send(ifp, m, dst->sa_family); + + return (error); +} + +/* + * Start method is used only when ALTQ is enabled. + */ +static void +ng_iface_start(struct ifnet *ifp) +{ + struct mbuf *m; + sa_family_t sa; + + KASSERT(ALTQ_IS_ENABLED(&ifp->if_snd), ("%s without ALTQ", __func__)); + + for(;;) { + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + sa = *mtod(m, sa_family_t *); + m_adj(m, sizeof(sa_family_t)); + ng_iface_send(ifp, m, sa); + } +} + +/* + * Flash a packet by the BPF (requires prepending 4 byte AF header) + * Note the phoney mbuf; this is OK because BPF treats it read-only. + */ +static void +ng_iface_bpftap(struct ifnet *ifp, struct mbuf *m, sa_family_t family) +{ + KASSERT(family != AF_UNSPEC, ("%s: family=AF_UNSPEC", __func__)); + if (bpf_peers_present(ifp->if_bpf)) { + int32_t family4 = (int32_t)family; + bpf_mtap2(ifp->if_bpf, &family4, sizeof(family4), m); + } +} + +/* + * This routine does actual delivery of the packet into the + * netgraph(4). It is called from ng_iface_start() and + * ng_iface_output(). + */ +static int +ng_iface_send(struct ifnet *ifp, struct mbuf *m, sa_family_t sa) +{ + const priv_p priv = (priv_p) ifp->if_softc; + const iffam_p iffam = get_iffam_from_af(sa); + int error; + int len; + + /* Check address family to determine hook (if known) */ + if (iffam == NULL) { + m_freem(m); + log(LOG_WARNING, "%s: can't handle af%d\n", ifp->if_xname, sa); + return (EAFNOSUPPORT); + } + + /* Copy length before the mbuf gets invalidated. */ + len = m->m_pkthdr.len; + + /* Send packet. If hook is not connected, + mbuf will get freed. */ + NG_SEND_DATA_ONLY(error, *get_hook_from_iffam(priv, iffam), m); + + /* Update stats. */ + if (error == 0) { + ifp->if_obytes += len; + ifp->if_opackets++; + } + + return (error); +} + +#ifdef DEBUG +/* + * Display an ioctl to the virtual interface + */ + +static void +ng_iface_print_ioctl(struct ifnet *ifp, int command, caddr_t data) +{ + char *str; + + switch (command & IOC_DIRMASK) { + case IOC_VOID: + str = "IO"; + break; + case IOC_OUT: + str = "IOR"; + break; + case IOC_IN: + str = "IOW"; + break; + case IOC_INOUT: + str = "IORW"; + break; + default: + str = "IO??"; + } + log(LOG_DEBUG, "%s: %s('%c', %d, char[%d])\n", + ifp->if_xname, + str, + IOCGROUP(command), + command & 0xff, + IOCPARM_LEN(command)); +} +#endif /* DEBUG */ + +/************************************************************************ + NETGRAPH NODE STUFF + ************************************************************************/ + +/* + * Constructor for a node + */ +static int +ng_iface_constructor(node_p node) +{ + struct ifnet *ifp; + priv_p priv; + + /* Allocate node and interface private structures */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH_IFACE, M_NOWAIT|M_ZERO); + if (priv == NULL) + return (ENOMEM); + ifp = if_alloc(IFT_PROPVIRTUAL); + if (ifp == NULL) { + FREE(priv, M_NETGRAPH_IFACE); + return (ENOMEM); + } + + /* Link them together */ + ifp->if_softc = priv; + priv->ifp = ifp; + + /* Get an interface unit number */ + priv->unit = alloc_unr(ng_iface_unit); + + /* Link together node and private info */ + NG_NODE_SET_PRIVATE(node, priv); + priv->node = node; + + /* Initialize interface structure */ + if_initname(ifp, NG_IFACE_IFACE_NAME, priv->unit); + ifp->if_output = ng_iface_output; + ifp->if_start = ng_iface_start; + ifp->if_ioctl = ng_iface_ioctl; + ifp->if_watchdog = NULL; + ifp->if_mtu = NG_IFACE_MTU_DEFAULT; + ifp->if_flags = (IFF_SIMPLEX|IFF_POINTOPOINT|IFF_NOARP|IFF_MULTICAST); + ifp->if_type = IFT_PROPVIRTUAL; /* XXX */ + ifp->if_addrlen = 0; /* XXX */ + ifp->if_hdrlen = 0; /* XXX */ + ifp->if_baudrate = 64000; /* XXX */ + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + /* Give this node the same name as the interface (if possible) */ + if (ng_name_node(node, ifp->if_xname) != 0) + log(LOG_WARNING, "%s: can't acquire netgraph name\n", + ifp->if_xname); + + /* Attach the interface */ + if_attach(ifp); + bpfattach(ifp, DLT_NULL, sizeof(u_int32_t)); + + /* Done */ + return (0); +} + +/* + * Give our ok for a hook to be added + */ +static int +ng_iface_newhook(node_p node, hook_p hook, const char *name) +{ + const iffam_p iffam = get_iffam_from_name(name); + hook_p *hookptr; + + if (iffam == NULL) + return (EPFNOSUPPORT); + hookptr = get_hook_from_iffam(NG_NODE_PRIVATE(node), iffam); + if (*hookptr != NULL) + return (EISCONN); + *hookptr = hook; + NG_HOOK_HI_STACK(hook); + return (0); +} + +/* + * Receive a control message + */ +static int +ng_iface_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ifnet *const ifp = priv->ifp; + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_IFACE_COOKIE: + switch (msg->header.cmd) { + case NGM_IFACE_GET_IFNAME: + NG_MKRESPONSE(resp, msg, IFNAMSIZ, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + strlcpy(resp->data, ifp->if_xname, IFNAMSIZ); + break; + + case NGM_IFACE_POINT2POINT: + case NGM_IFACE_BROADCAST: + { + + /* Deny request if interface is UP */ + if ((ifp->if_flags & IFF_UP) != 0) + return (EBUSY); + + /* Change flags */ + switch (msg->header.cmd) { + case NGM_IFACE_POINT2POINT: + ifp->if_flags |= IFF_POINTOPOINT; + ifp->if_flags &= ~IFF_BROADCAST; + break; + case NGM_IFACE_BROADCAST: + ifp->if_flags &= ~IFF_POINTOPOINT; + ifp->if_flags |= IFF_BROADCAST; + break; + } + break; + } + + case NGM_IFACE_GET_IFINDEX: + NG_MKRESPONSE(resp, msg, sizeof(uint32_t), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + *((uint32_t *)resp->data) = priv->ifp->if_index; + break; + + default: + error = EINVAL; + break; + } + break; + case NGM_CISCO_COOKIE: + switch (msg->header.cmd) { + case NGM_CISCO_GET_IPADDR: /* we understand this too */ + { + struct ifaddr *ifa; + + /* Return the first configured IP address */ + TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { + struct ng_cisco_ipaddr *ips; + + if (ifa->ifa_addr->sa_family != AF_INET) + continue; + NG_MKRESPONSE(resp, msg, sizeof(ips), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + ips = (struct ng_cisco_ipaddr *)resp->data; + ips->ipaddr = ((struct sockaddr_in *) + ifa->ifa_addr)->sin_addr; + ips->netmask = ((struct sockaddr_in *) + ifa->ifa_netmask)->sin_addr; + break; + } + + /* No IP addresses on this interface? */ + if (ifa == NULL) + error = EADDRNOTAVAIL; + break; + } + default: + error = EINVAL; + break; + } + break; + case NGM_FLOW_COOKIE: + switch (msg->header.cmd) { + case NGM_LINK_IS_UP: + ifp->if_drv_flags |= IFF_DRV_RUNNING; + break; + case NGM_LINK_IS_DOWN: + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + break; + default: + break; + } + break; + default: + error = EINVAL; + break; + } + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Recive data from a hook. Pass the packet to the correct input routine. + */ +static int +ng_iface_rcvdata(hook_p hook, item_p item) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + const iffam_p iffam = get_iffam_from_hook(priv, hook); + struct ifnet *const ifp = priv->ifp; + struct mbuf *m; + int isr; + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + /* Sanity checks */ + KASSERT(iffam != NULL, ("%s: iffam", __func__)); + M_ASSERTPKTHDR(m); + if ((ifp->if_flags & IFF_UP) == 0) { + NG_FREE_M(m); + return (ENETDOWN); + } + + /* Update interface stats */ + ifp->if_ipackets++; + ifp->if_ibytes += m->m_pkthdr.len; + + /* Note receiving interface */ + m->m_pkthdr.rcvif = ifp; + + /* Berkeley packet filter */ + ng_iface_bpftap(ifp, m, iffam->family); + + /* Send packet */ + switch (iffam->family) { +#ifdef INET + case AF_INET: + isr = NETISR_IP; + break; +#endif +#ifdef INET6 + case AF_INET6: + isr = NETISR_IPV6; + break; +#endif +#ifdef IPX + case AF_IPX: + isr = NETISR_IPX; + break; +#endif +#ifdef NETATALK + case AF_APPLETALK: + isr = NETISR_ATALK2; + break; +#endif + default: + m_freem(m); + return (EAFNOSUPPORT); + } + /* First chunk of an mbuf contains good junk */ + if (harvest.point_to_point) + random_harvest(m, 16, 3, 0, RANDOM_NET); + netisr_dispatch(isr, m); + return (0); +} + +/* + * Shutdown and remove the node and its associated interface. + */ +static int +ng_iface_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + bpfdetach(priv->ifp); + if_detach(priv->ifp); + if_free(priv->ifp); + priv->ifp = NULL; + free_unr(ng_iface_unit, priv->unit); + FREE(priv, M_NETGRAPH_IFACE); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + return (0); +} + +/* + * Hook disconnection. Note that we do *not* shutdown when all + * hooks have been disconnected. + */ +static int +ng_iface_disconnect(hook_p hook) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + const iffam_p iffam = get_iffam_from_hook(priv, hook); + + if (iffam == NULL) + panic(__func__); + *get_hook_from_iffam(priv, iffam) = NULL; + return (0); +} + +/* + * Handle loading and unloading for this node type. + */ +static int +ng_iface_mod_event(module_t mod, int event, void *data) +{ + int error = 0; + + switch (event) { + case MOD_LOAD: + ng_iface_unit = new_unrhdr(0, 0xffff, NULL); + break; + case MOD_UNLOAD: + delete_unrhdr(ng_iface_unit); + break; + default: + error = EOPNOTSUPP; + break; + } + return (error); +} diff --git a/sys/netgraph7/ng_iface.h b/sys/netgraph7/ng_iface.h new file mode 100644 index 0000000000..ca5bc7d0f1 --- /dev/null +++ b/sys/netgraph7/ng_iface.h @@ -0,0 +1,75 @@ +/* + * ng_iface.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_iface.h,v 1.9 2005/02/13 16:36:41 archie Exp $ + * $Whistle: ng_iface.h,v 1.5 1999/01/20 00:22:13 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_IFACE_H_ +#define _NETGRAPH_NG_IFACE_H_ + +/* Node type name and magic cookie */ +#define NG_IFACE_NODE_TYPE "iface" +#define NGM_IFACE_COOKIE 1108312559 + +/* Interface base name */ +#define NG_IFACE_IFACE_NAME "ng" + +/* My hook names */ +#define NG_IFACE_HOOK_INET "inet" +#define NG_IFACE_HOOK_INET6 "inet6" +#define NG_IFACE_HOOK_ATALK "atalk" /* AppleTalk phase 2 */ +#define NG_IFACE_HOOK_IPX "ipx" +#define NG_IFACE_HOOK_ATM "atm" +#define NG_IFACE_HOOK_NATM "natm" + +/* MTU bounds */ +#define NG_IFACE_MTU_MIN 72 +#define NG_IFACE_MTU_MAX 65535 +#define NG_IFACE_MTU_DEFAULT 1500 + +/* Netgraph commands */ +enum { + NGM_IFACE_GET_IFNAME = 1, + NGM_IFACE_POINT2POINT, + NGM_IFACE_BROADCAST, + NGM_IFACE_GET_IFINDEX, +}; + +#endif /* _NETGRAPH_NG_IFACE_H_ */ diff --git a/sys/netgraph7/ng_ip_input.c b/sys/netgraph7/ng_ip_input.c new file mode 100644 index 0000000000..af002dfa79 --- /dev/null +++ b/sys/netgraph7/ng_ip_input.c @@ -0,0 +1,136 @@ +/* + * ng_ip_input.c + */ + +/*- + * Copyright 2001 The Aerospace Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions, and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of The Aerospace Corporation may not be used to endorse or + * promote products derived from this software. + * + * THIS SOFTWARE IS PROVIDED BY THE AEROSPACE CORPORATION "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AEROSPACE CORPORATION BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Brooks Davis + * Derived from: ng_hole.c + * + * $FreeBSD: src/sys/netgraph/ng_ip_input.c,v 1.4 2005/01/07 01:45:39 imp Exp $ + */ + +/* + * This node simply takes IP packets and calls ip_input on them + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Netgraph methods */ +static ng_constructor_t ngipi_cons; +static ng_rcvdata_t ngipi_rcvdata; +static ng_disconnect_t ngipi_disconnect; + +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_IP_INPUT_NODE_TYPE, + .constructor = ngipi_cons, + .rcvdata = ngipi_rcvdata, + .disconnect = ngipi_disconnect, +}; +NETGRAPH_INIT(ip_input, &typestruct); + +/* + * Be obliging. but no work to do. + */ +static int +ngipi_cons(node_p node) +{ + return(0); +} + +/* + * Receive data + */ +static int +ngipi_rcvdata(hook_p hook, item_p item) +{ + struct mbuf *m; + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + netisr_dispatch(NETISR_IP, m); + return 0; +} + +/* + * Hook disconnection + */ +static int +ngipi_disconnect(hook_p hook) +{ + if (NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + ng_rmnode_self(NG_HOOK_NODE(hook)); + return (0); +} diff --git a/sys/netgraph7/ng_ip_input.h b/sys/netgraph7/ng_ip_input.h new file mode 100644 index 0000000000..2c167b9d9b --- /dev/null +++ b/sys/netgraph7/ng_ip_input.h @@ -0,0 +1,77 @@ +/* + * ng_ip_input.h + */ + +/*- + * Copyright 2001 The Aerospace Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions, and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of The Aerospace Corporation may not be used to endorse or + * promote products derived from this software. + * + * THIS SOFTWARE IS PROVIDED BY THE AEROSPACE CORPORATION "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AEROSPACE CORPORATION BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Brooks Davis + * Derived from: ng_hole.h + * + * $FreeBSD: src/sys/netgraph/ng_ip_input.h,v 1.3 2005/01/07 01:45:39 imp Exp $ + */ + +#ifndef _NETGRAPH_NG_IP_INPUT_H_ +#define _NETGRAPH_NG_IP_INPUT_H_ + +/* Node type name and magic cookie */ +#define NG_IP_INPUT_NODE_TYPE "ip_input" +#define NGM_IP_INPUT_COOKIE 994874907 + +#endif /* _NETGRAPH_NG_IP_INPUT_H_ */ diff --git a/sys/netgraph7/ng_ipfw.c b/sys/netgraph7/ng_ipfw.c new file mode 100644 index 0000000000..44242bbdba --- /dev/null +++ b/sys/netgraph7/ng_ipfw.c @@ -0,0 +1,338 @@ +/*- + * Copyright 2005, Gleb Smirnoff + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_ipfw.c,v 1.9 2006/02/14 15:22:24 ru Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static int ng_ipfw_mod_event(module_t mod, int event, void *data); +static ng_constructor_t ng_ipfw_constructor; +static ng_shutdown_t ng_ipfw_shutdown; +static ng_newhook_t ng_ipfw_newhook; +static ng_connect_t ng_ipfw_connect; +static ng_findhook_t ng_ipfw_findhook; +static ng_rcvdata_t ng_ipfw_rcvdata; +static ng_disconnect_t ng_ipfw_disconnect; + +static hook_p ng_ipfw_findhook1(node_p, u_int16_t ); +static int ng_ipfw_input(struct mbuf **, int, struct ip_fw_args *, + int); + +/* We have only one node */ +static node_p fw_node; + +/* Netgraph node type descriptor */ +static struct ng_type ng_ipfw_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_IPFW_NODE_TYPE, + .mod_event = ng_ipfw_mod_event, + .constructor = ng_ipfw_constructor, + .shutdown = ng_ipfw_shutdown, + .newhook = ng_ipfw_newhook, + .connect = ng_ipfw_connect, + .findhook = ng_ipfw_findhook, + .rcvdata = ng_ipfw_rcvdata, + .disconnect = ng_ipfw_disconnect, +}; +NETGRAPH_INIT(ipfw, &ng_ipfw_typestruct); +MODULE_DEPEND(ng_ipfw, ipfw, 2, 2, 2); + +/* Information we store for each hook */ +struct ng_ipfw_hook_priv { + hook_p hook; + u_int16_t rulenum; +}; +typedef struct ng_ipfw_hook_priv *hpriv_p; + +static int +ng_ipfw_mod_event(module_t mod, int event, void *data) +{ + int error = 0; + + switch (event) { + case MOD_LOAD: + + if (ng_ipfw_input_p != NULL) { + error = EEXIST; + break; + } + + /* Setup node without any private data */ + if ((error = ng_make_node_common(&ng_ipfw_typestruct, &fw_node)) + != 0) { + log(LOG_ERR, "%s: can't create ng_ipfw node", __func__); + break; + }; + + /* Try to name node */ + if (ng_name_node(fw_node, "ipfw") != 0) + log(LOG_WARNING, "%s: failed to name node \"ipfw\"", + __func__); + + /* Register hook */ + ng_ipfw_input_p = ng_ipfw_input; + break; + + case MOD_UNLOAD: + /* + * This won't happen if a node exists. + * ng_ipfw_input_p is already cleared. + */ + break; + + default: + error = EOPNOTSUPP; + break; + } + + return (error); +} + +static int +ng_ipfw_constructor(node_p node) +{ + return (EINVAL); /* Only one node */ +} + +static int +ng_ipfw_newhook(node_p node, hook_p hook, const char *name) +{ + hpriv_p hpriv; + u_int16_t rulenum; + const char *cp; + char *endptr; + + /* Protect from leading zero */ + if (name[0] == '0' && name[1] != '\0') + return (EINVAL); + + /* Check that name contains only digits */ + for (cp = name; *cp != '\0'; cp++) + if (!isdigit(*cp)) + return (EINVAL); + + /* Convert it to integer */ + rulenum = (u_int16_t)strtol(name, &endptr, 10); + if (*endptr != '\0') + return (EINVAL); + + /* Allocate memory for this hook's private data */ + MALLOC(hpriv, hpriv_p, sizeof(*hpriv), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (hpriv== NULL) + return (ENOMEM); + + hpriv->hook = hook; + hpriv->rulenum = rulenum; + + NG_HOOK_SET_PRIVATE(hook, hpriv); + + return(0); +} + +/* + * Set hooks into queueing mode, to avoid recursion between + * netgraph layer and ip_{input,output}. + */ +static int +ng_ipfw_connect(hook_p hook) +{ + NG_HOOK_FORCE_QUEUE(hook); + return (0); +} + +/* Look up hook by name */ +hook_p +ng_ipfw_findhook(node_p node, const char *name) +{ + u_int16_t n; /* numeric representation of hook */ + char *endptr; + + n = (u_int16_t)strtol(name, &endptr, 10); + if (*endptr != '\0') + return NULL; + return ng_ipfw_findhook1(node, n); +} + +/* Look up hook by rule number */ +static hook_p +ng_ipfw_findhook1(node_p node, u_int16_t rulenum) +{ + hook_p hook; + hpriv_p hpriv; + + LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) { + hpriv = NG_HOOK_PRIVATE(hook); + if (NG_HOOK_IS_VALID(hook) && (hpriv->rulenum == rulenum)) + return (hook); + } + + return (NULL); +} + + +static int +ng_ipfw_rcvdata(hook_p hook, item_p item) +{ + struct ng_ipfw_tag *ngit; + struct mbuf *m; + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + if ((ngit = (struct ng_ipfw_tag *)m_tag_locate(m, NGM_IPFW_COOKIE, 0, + NULL)) == NULL) { + NG_FREE_M(m); + return (EINVAL); /* XXX: find smth better */ + }; + + switch (ngit->dir) { + case NG_IPFW_OUT: + { + struct ip *ip; + + if (m->m_len < sizeof(struct ip) && + (m = m_pullup(m, sizeof(struct ip))) == NULL) + return (EINVAL); + + ip = mtod(m, struct ip *); + + ip->ip_len = ntohs(ip->ip_len); + ip->ip_off = ntohs(ip->ip_off); + + return ip_output(m, NULL, NULL, IP_FORWARDING, NULL, NULL); + } + case NG_IPFW_IN: + ip_input(m); + return (0); + default: + panic("ng_ipfw_rcvdata: bad dir %u", ngit->dir); + } + + /* not reached */ + return (0); +} + +static int +ng_ipfw_input(struct mbuf **m0, int dir, struct ip_fw_args *fwa, int tee) +{ + struct mbuf *m; + struct ng_ipfw_tag *ngit; + struct ip *ip; + hook_p hook; + int error = 0; + + /* + * Node must be loaded and corresponding hook must be present. + */ + if (fw_node == NULL || + (hook = ng_ipfw_findhook1(fw_node, fwa->cookie)) == NULL) { + if (tee == 0) + m_freem(*m0); + return (ESRCH); /* no hook associated with this rule */ + } + + /* + * We have two modes: in normal mode we add a tag to packet, which is + * important to return packet back to IP stack. In tee mode we make + * a copy of a packet and forward it into netgraph without a tag. + */ + if (tee == 0) { + m = *m0; + *m0 = NULL; /* it belongs now to netgraph */ + + if ((ngit = (struct ng_ipfw_tag *)m_tag_alloc(NGM_IPFW_COOKIE, + 0, TAGSIZ, M_NOWAIT|M_ZERO)) == NULL) { + m_freem(m); + return (ENOMEM); + } + ngit->rule = fwa->rule; + ngit->dir = dir; + ngit->ifp = fwa->oif; + m_tag_prepend(m, &ngit->mt); + + } else + if ((m = m_dup(*m0, M_DONTWAIT)) == NULL) + return (ENOMEM); /* which is ignored */ + + if (m->m_len < sizeof(struct ip) && + (m = m_pullup(m, sizeof(struct ip))) == NULL) + return (EINVAL); + + ip = mtod(m, struct ip *); + ip->ip_len = htons(ip->ip_len); + ip->ip_off = htons(ip->ip_off); + + NG_SEND_DATA_ONLY(error, hook, m); + + return (error); +} + +static int +ng_ipfw_shutdown(node_p node) +{ + + /* + * After our single node has been removed, + * the only thing that can be done is + * 'kldunload ng_ipfw.ko' + */ + ng_ipfw_input_p = NULL; + NG_NODE_UNREF(node); + return (0); +} + +static int +ng_ipfw_disconnect(hook_p hook) +{ + const hpriv_p hpriv = NG_HOOK_PRIVATE(hook); + + FREE(hpriv, M_NETGRAPH); + NG_HOOK_SET_PRIVATE(hook, NULL); + + return (0); +} diff --git a/sys/netgraph7/ng_ipfw.h b/sys/netgraph7/ng_ipfw.h new file mode 100644 index 0000000000..2d174bd4aa --- /dev/null +++ b/sys/netgraph7/ng_ipfw.h @@ -0,0 +1,49 @@ +/*- + * Copyright 2005, Gleb Smirnoff + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_ipfw.h,v 1.2 2006/02/17 09:42:49 glebius Exp $ + */ + +#define NG_IPFW_NODE_TYPE "ipfw" +#define NGM_IPFW_COOKIE 1105988990 + +#ifdef _KERNEL + +typedef int ng_ipfw_input_t(struct mbuf **, int, struct ip_fw_args *, int); +extern ng_ipfw_input_t *ng_ipfw_input_p; +#define NG_IPFW_LOADED (ng_ipfw_input_p != NULL) + +struct ng_ipfw_tag { + struct m_tag mt; /* tag header */ + struct ip_fw *rule; /* matching rule */ + struct ifnet *ifp; /* interface, for ip_output */ + int dir; +#define NG_IPFW_OUT 0 +#define NG_IPFW_IN 1 +}; + +#define TAGSIZ (sizeof(struct ng_ipfw_tag) - sizeof(struct m_tag)) + +#endif /* _KERNEL */ diff --git a/sys/netgraph7/ng_ksocket.c b/sys/netgraph7/ng_ksocket.c new file mode 100644 index 0000000000..137899deed --- /dev/null +++ b/sys/netgraph7/ng_ksocket.c @@ -0,0 +1,1305 @@ +/* + * ng_ksocket.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_ksocket.c,v 1.61 2008/03/07 21:12:56 mav Exp $ + * $Whistle: ng_ksocket.c,v 1.1 1999/11/16 20:04:40 archie Exp $ + */ + +/* + * Kernel socket node type. This node type is basically a kernel-mode + * version of a socket... kindof like the reverse of the socket node type. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_KSOCKET, "netgraph_ksock", "netgraph ksock node "); +#else +#define M_NETGRAPH_KSOCKET M_NETGRAPH +#endif + +#define OFFSETOF(s, e) ((char *)&((s *)0)->e - (char *)((s *)0)) +#define SADATA_OFFSET (OFFSETOF(struct sockaddr, sa_data)) + +/* Node private data */ +struct ng_ksocket_private { + node_p node; + hook_p hook; + struct socket *so; + int fn_sent; /* FN call on incoming event was sent */ + LIST_HEAD(, ng_ksocket_private) embryos; + LIST_ENTRY(ng_ksocket_private) siblings; + u_int32_t flags; + u_int32_t response_token; + ng_ID_t response_addr; +}; +typedef struct ng_ksocket_private *priv_p; + +/* Flags for priv_p */ +#define KSF_CONNECTING 0x00000001 /* Waiting for connection complete */ +#define KSF_ACCEPTING 0x00000002 /* Waiting for accept complete */ +#define KSF_EOFSEEN 0x00000004 /* Have sent 0-length EOF mbuf */ +#define KSF_CLONED 0x00000008 /* Cloned from an accepting socket */ +#define KSF_EMBRYONIC 0x00000010 /* Cloned node with no hooks yet */ + +/* Netgraph node methods */ +static ng_constructor_t ng_ksocket_constructor; +static ng_rcvmsg_t ng_ksocket_rcvmsg; +static ng_shutdown_t ng_ksocket_shutdown; +static ng_newhook_t ng_ksocket_newhook; +static ng_rcvdata_t ng_ksocket_rcvdata; +static ng_connect_t ng_ksocket_connect; +static ng_disconnect_t ng_ksocket_disconnect; + +/* Alias structure */ +struct ng_ksocket_alias { + const char *name; + const int value; + const int family; +}; + +/* Protocol family aliases */ +static const struct ng_ksocket_alias ng_ksocket_families[] = { + { "local", PF_LOCAL }, + { "inet", PF_INET }, + { "inet6", PF_INET6 }, + { "atalk", PF_APPLETALK }, + { "ipx", PF_IPX }, + { "atm", PF_ATM }, + { NULL, -1 }, +}; + +/* Socket type aliases */ +static const struct ng_ksocket_alias ng_ksocket_types[] = { + { "stream", SOCK_STREAM }, + { "dgram", SOCK_DGRAM }, + { "raw", SOCK_RAW }, + { "rdm", SOCK_RDM }, + { "seqpacket", SOCK_SEQPACKET }, + { NULL, -1 }, +}; + +/* Protocol aliases */ +static const struct ng_ksocket_alias ng_ksocket_protos[] = { + { "ip", IPPROTO_IP, PF_INET }, + { "raw", IPPROTO_RAW, PF_INET }, + { "icmp", IPPROTO_ICMP, PF_INET }, + { "igmp", IPPROTO_IGMP, PF_INET }, + { "tcp", IPPROTO_TCP, PF_INET }, + { "udp", IPPROTO_UDP, PF_INET }, + { "gre", IPPROTO_GRE, PF_INET }, + { "esp", IPPROTO_ESP, PF_INET }, + { "ah", IPPROTO_AH, PF_INET }, + { "swipe", IPPROTO_SWIPE, PF_INET }, + { "encap", IPPROTO_ENCAP, PF_INET }, + { "divert", IPPROTO_DIVERT, PF_INET }, + { "pim", IPPROTO_PIM, PF_INET }, + { "ddp", ATPROTO_DDP, PF_APPLETALK }, + { "aarp", ATPROTO_AARP, PF_APPLETALK }, + { NULL, -1 }, +}; + +/* Helper functions */ +static int ng_ksocket_check_accept(priv_p); +static void ng_ksocket_finish_accept(priv_p); +static void ng_ksocket_incoming(struct socket *so, void *arg, int waitflag); +static int ng_ksocket_parse(const struct ng_ksocket_alias *aliases, + const char *s, int family); +static void ng_ksocket_incoming2(node_p node, hook_p hook, + void *arg1, int arg2); + +/************************************************************************ + STRUCT SOCKADDR PARSE TYPE + ************************************************************************/ + +/* Get the length of the data portion of a generic struct sockaddr */ +static int +ng_parse_generic_sockdata_getLength(const struct ng_parse_type *type, + const u_char *start, const u_char *buf) +{ + const struct sockaddr *sa; + + sa = (const struct sockaddr *)(buf - SADATA_OFFSET); + return (sa->sa_len < SADATA_OFFSET) ? 0 : sa->sa_len - SADATA_OFFSET; +} + +/* Type for the variable length data portion of a generic struct sockaddr */ +static const struct ng_parse_type ng_ksocket_generic_sockdata_type = { + &ng_parse_bytearray_type, + &ng_parse_generic_sockdata_getLength +}; + +/* Type for a generic struct sockaddr */ +static const struct ng_parse_struct_field + ng_parse_generic_sockaddr_type_fields[] = { + { "len", &ng_parse_uint8_type }, + { "family", &ng_parse_uint8_type }, + { "data", &ng_ksocket_generic_sockdata_type }, + { NULL } +}; +static const struct ng_parse_type ng_ksocket_generic_sockaddr_type = { + &ng_parse_struct_type, + &ng_parse_generic_sockaddr_type_fields +}; + +/* Convert a struct sockaddr from ASCII to binary. If its a protocol + family that we specially handle, do that, otherwise defer to the + generic parse type ng_ksocket_generic_sockaddr_type. */ +static int +ng_ksocket_sockaddr_parse(const struct ng_parse_type *type, + const char *s, int *off, const u_char *const start, + u_char *const buf, int *buflen) +{ + struct sockaddr *const sa = (struct sockaddr *)buf; + enum ng_parse_token tok; + char fambuf[32]; + int family, len; + char *t; + + /* If next token is a left curly brace, use generic parse type */ + if ((tok = ng_parse_get_token(s, off, &len)) == T_LBRACE) { + return (*ng_ksocket_generic_sockaddr_type.supertype->parse) + (&ng_ksocket_generic_sockaddr_type, + s, off, start, buf, buflen); + } + + /* Get socket address family followed by a slash */ + while (isspace(s[*off])) + (*off)++; + if ((t = index(s + *off, '/')) == NULL) + return (EINVAL); + if ((len = t - (s + *off)) > sizeof(fambuf) - 1) + return (EINVAL); + strncpy(fambuf, s + *off, len); + fambuf[len] = '\0'; + *off += len + 1; + if ((family = ng_ksocket_parse(ng_ksocket_families, fambuf, 0)) == -1) + return (EINVAL); + + /* Set family */ + if (*buflen < SADATA_OFFSET) + return (ERANGE); + sa->sa_family = family; + + /* Set family-specific data and length */ + switch (sa->sa_family) { + case PF_LOCAL: /* Get pathname */ + { + const int pathoff = OFFSETOF(struct sockaddr_un, sun_path); + struct sockaddr_un *const sun = (struct sockaddr_un *)sa; + int toklen, pathlen; + char *path; + + if ((path = ng_get_string_token(s, off, &toklen, NULL)) == NULL) + return (EINVAL); + pathlen = strlen(path); + if (pathlen > SOCK_MAXADDRLEN) { + FREE(path, M_NETGRAPH_KSOCKET); + return (E2BIG); + } + if (*buflen < pathoff + pathlen) { + FREE(path, M_NETGRAPH_KSOCKET); + return (ERANGE); + } + *off += toklen; + bcopy(path, sun->sun_path, pathlen); + sun->sun_len = pathoff + pathlen; + FREE(path, M_NETGRAPH_KSOCKET); + break; + } + + case PF_INET: /* Get an IP address with optional port */ + { + struct sockaddr_in *const sin = (struct sockaddr_in *)sa; + int i; + + /* Parse this: [:port] */ + for (i = 0; i < 4; i++) { + u_long val; + char *eptr; + + val = strtoul(s + *off, &eptr, 10); + if (val > 0xff || eptr == s + *off) + return (EINVAL); + *off += (eptr - (s + *off)); + ((u_char *)&sin->sin_addr)[i] = (u_char)val; + if (i < 3) { + if (s[*off] != '.') + return (EINVAL); + (*off)++; + } else if (s[*off] == ':') { + (*off)++; + val = strtoul(s + *off, &eptr, 10); + if (val > 0xffff || eptr == s + *off) + return (EINVAL); + *off += (eptr - (s + *off)); + sin->sin_port = htons(val); + } else + sin->sin_port = 0; + } + bzero(&sin->sin_zero, sizeof(sin->sin_zero)); + sin->sin_len = sizeof(*sin); + break; + } + +#if 0 + case PF_APPLETALK: /* XXX implement these someday */ + case PF_INET6: + case PF_IPX: +#endif + + default: + return (EINVAL); + } + + /* Done */ + *buflen = sa->sa_len; + return (0); +} + +/* Convert a struct sockaddr from binary to ASCII */ +static int +ng_ksocket_sockaddr_unparse(const struct ng_parse_type *type, + const u_char *data, int *off, char *cbuf, int cbuflen) +{ + const struct sockaddr *sa = (const struct sockaddr *)(data + *off); + int slen = 0; + + /* Output socket address, either in special or generic format */ + switch (sa->sa_family) { + case PF_LOCAL: + { + const int pathoff = OFFSETOF(struct sockaddr_un, sun_path); + const struct sockaddr_un *sun = (const struct sockaddr_un *)sa; + const int pathlen = sun->sun_len - pathoff; + char pathbuf[SOCK_MAXADDRLEN + 1]; + char *pathtoken; + + bcopy(sun->sun_path, pathbuf, pathlen); + if ((pathtoken = ng_encode_string(pathbuf, pathlen)) == NULL) + return (ENOMEM); + slen += snprintf(cbuf, cbuflen, "local/%s", pathtoken); + FREE(pathtoken, M_NETGRAPH_KSOCKET); + if (slen >= cbuflen) + return (ERANGE); + *off += sun->sun_len; + return (0); + } + + case PF_INET: + { + const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; + + slen += snprintf(cbuf, cbuflen, "inet/%d.%d.%d.%d", + ((const u_char *)&sin->sin_addr)[0], + ((const u_char *)&sin->sin_addr)[1], + ((const u_char *)&sin->sin_addr)[2], + ((const u_char *)&sin->sin_addr)[3]); + if (sin->sin_port != 0) { + slen += snprintf(cbuf + strlen(cbuf), + cbuflen - strlen(cbuf), ":%d", + (u_int)ntohs(sin->sin_port)); + } + if (slen >= cbuflen) + return (ERANGE); + *off += sizeof(*sin); + return(0); + } + +#if 0 + case PF_APPLETALK: /* XXX implement these someday */ + case PF_INET6: + case PF_IPX: +#endif + + default: + return (*ng_ksocket_generic_sockaddr_type.supertype->unparse) + (&ng_ksocket_generic_sockaddr_type, + data, off, cbuf, cbuflen); + } +} + +/* Parse type for struct sockaddr */ +static const struct ng_parse_type ng_ksocket_sockaddr_type = { + NULL, + NULL, + NULL, + &ng_ksocket_sockaddr_parse, + &ng_ksocket_sockaddr_unparse, + NULL /* no such thing as a default struct sockaddr */ +}; + +/************************************************************************ + STRUCT NG_KSOCKET_SOCKOPT PARSE TYPE + ************************************************************************/ + +/* Get length of the struct ng_ksocket_sockopt value field, which is the + just the excess of the message argument portion over the length of + the struct ng_ksocket_sockopt. */ +static int +ng_parse_sockoptval_getLength(const struct ng_parse_type *type, + const u_char *start, const u_char *buf) +{ + static const int offset = OFFSETOF(struct ng_ksocket_sockopt, value); + const struct ng_ksocket_sockopt *sopt; + const struct ng_mesg *msg; + + sopt = (const struct ng_ksocket_sockopt *)(buf - offset); + msg = (const struct ng_mesg *)((const u_char *)sopt - sizeof(*msg)); + return msg->header.arglen - sizeof(*sopt); +} + +/* Parse type for the option value part of a struct ng_ksocket_sockopt + XXX Eventually, we should handle the different socket options specially. + XXX This would avoid byte order problems, eg an integer value of 1 is + XXX going to be "[1]" for little endian or "[3=1]" for big endian. */ +static const struct ng_parse_type ng_ksocket_sockoptval_type = { + &ng_parse_bytearray_type, + &ng_parse_sockoptval_getLength +}; + +/* Parse type for struct ng_ksocket_sockopt */ +static const struct ng_parse_struct_field ng_ksocket_sockopt_type_fields[] + = NG_KSOCKET_SOCKOPT_INFO(&ng_ksocket_sockoptval_type); +static const struct ng_parse_type ng_ksocket_sockopt_type = { + &ng_parse_struct_type, + &ng_ksocket_sockopt_type_fields +}; + +/* Parse type for struct ng_ksocket_accept */ +static const struct ng_parse_struct_field ng_ksocket_accept_type_fields[] + = NGM_KSOCKET_ACCEPT_INFO; +static const struct ng_parse_type ng_ksocket_accept_type = { + &ng_parse_struct_type, + &ng_ksocket_accept_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_ksocket_cmds[] = { + { + NGM_KSOCKET_COOKIE, + NGM_KSOCKET_BIND, + "bind", + &ng_ksocket_sockaddr_type, + NULL + }, + { + NGM_KSOCKET_COOKIE, + NGM_KSOCKET_LISTEN, + "listen", + &ng_parse_int32_type, + NULL + }, + { + NGM_KSOCKET_COOKIE, + NGM_KSOCKET_ACCEPT, + "accept", + NULL, + &ng_ksocket_accept_type + }, + { + NGM_KSOCKET_COOKIE, + NGM_KSOCKET_CONNECT, + "connect", + &ng_ksocket_sockaddr_type, + &ng_parse_int32_type + }, + { + NGM_KSOCKET_COOKIE, + NGM_KSOCKET_GETNAME, + "getname", + NULL, + &ng_ksocket_sockaddr_type + }, + { + NGM_KSOCKET_COOKIE, + NGM_KSOCKET_GETPEERNAME, + "getpeername", + NULL, + &ng_ksocket_sockaddr_type + }, + { + NGM_KSOCKET_COOKIE, + NGM_KSOCKET_SETOPT, + "setopt", + &ng_ksocket_sockopt_type, + NULL + }, + { + NGM_KSOCKET_COOKIE, + NGM_KSOCKET_GETOPT, + "getopt", + &ng_ksocket_sockopt_type, + &ng_ksocket_sockopt_type + }, + { 0 } +}; + +/* Node type descriptor */ +static struct ng_type ng_ksocket_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_KSOCKET_NODE_TYPE, + .constructor = ng_ksocket_constructor, + .rcvmsg = ng_ksocket_rcvmsg, + .shutdown = ng_ksocket_shutdown, + .newhook = ng_ksocket_newhook, + .connect = ng_ksocket_connect, + .rcvdata = ng_ksocket_rcvdata, + .disconnect = ng_ksocket_disconnect, + .cmdlist = ng_ksocket_cmds, +}; +NETGRAPH_INIT(ksocket, &ng_ksocket_typestruct); + +#define ERROUT(x) do { error = (x); goto done; } while (0) + +/************************************************************************ + NETGRAPH NODE STUFF + ************************************************************************/ + +/* + * Node type constructor + * The NODE part is assumed to be all set up. + * There is already a reference to the node for us. + */ +static int +ng_ksocket_constructor(node_p node) +{ + priv_p priv; + + /* Allocate private structure */ + MALLOC(priv, priv_p, sizeof(*priv), + M_NETGRAPH_KSOCKET, M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + + LIST_INIT(&priv->embryos); + /* cross link them */ + priv->node = node; + NG_NODE_SET_PRIVATE(node, priv); + + /* Done */ + return (0); +} + +/* + * Give our OK for a hook to be added. The hook name is of the + * form "//" where the three components may + * be decimal numbers or else aliases from the above lists. + * + * Connecting a hook amounts to opening the socket. Disconnecting + * the hook closes the socket and destroys the node as well. + */ +static int +ng_ksocket_newhook(node_p node, hook_p hook, const char *name0) +{ + struct thread *td = curthread; /* XXX broken */ + const priv_p priv = NG_NODE_PRIVATE(node); + char *s1, *s2, name[NG_HOOKSIZ]; + int family, type, protocol, error; + + /* Check if we're already connected */ + if (priv->hook != NULL) + return (EISCONN); + + if (priv->flags & KSF_CLONED) { + if (priv->flags & KSF_EMBRYONIC) { + /* Remove ourselves from our parent's embryo list */ + LIST_REMOVE(priv, siblings); + priv->flags &= ~KSF_EMBRYONIC; + } + } else { + /* Extract family, type, and protocol from hook name */ + snprintf(name, sizeof(name), "%s", name0); + s1 = name; + if ((s2 = index(s1, '/')) == NULL) + return (EINVAL); + *s2++ = '\0'; + family = ng_ksocket_parse(ng_ksocket_families, s1, 0); + if (family == -1) + return (EINVAL); + s1 = s2; + if ((s2 = index(s1, '/')) == NULL) + return (EINVAL); + *s2++ = '\0'; + type = ng_ksocket_parse(ng_ksocket_types, s1, 0); + if (type == -1) + return (EINVAL); + s1 = s2; + protocol = ng_ksocket_parse(ng_ksocket_protos, s1, family); + if (protocol == -1) + return (EINVAL); + + /* Create the socket */ + error = socreate(family, &priv->so, type, protocol, + td->td_ucred, td); + if (error != 0) + return (error); + + /* XXX call soreserve() ? */ + + } + + /* OK */ + priv->hook = hook; + + /* + * In case of misconfigured routing a packet may reenter + * ksocket node recursively. Decouple stack to avoid possible + * panics about sleeping with locks held. + */ + NG_HOOK_FORCE_QUEUE(hook); + + return(0); +} + +static int +ng_ksocket_connect(hook_p hook) +{ + node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + struct socket *const so = priv->so; + + /* Add our hook for incoming data and other events */ + priv->so->so_upcallarg = (caddr_t)node; + priv->so->so_upcall = ng_ksocket_incoming; + SOCKBUF_LOCK(&priv->so->so_rcv); + priv->so->so_rcv.sb_flags |= SB_UPCALL; + SOCKBUF_UNLOCK(&priv->so->so_rcv); + SOCKBUF_LOCK(&priv->so->so_snd); + priv->so->so_snd.sb_flags |= SB_UPCALL; + SOCKBUF_UNLOCK(&priv->so->so_snd); + SOCK_LOCK(priv->so); + priv->so->so_state |= SS_NBIO; + SOCK_UNLOCK(priv->so); + /* + * --Original comment-- + * On a cloned socket we may have already received one or more + * upcalls which we couldn't handle without a hook. Handle + * those now. + * We cannot call the upcall function directly + * from here, because until this function has returned our + * hook isn't connected. + * + * ---meta comment for -current --- + * XXX This is dubius. + * Upcalls between the time that the hook was + * first created and now (on another processesor) will + * be earlier on the queue than the request to finalise the hook. + * By the time the hook is finalised, + * The queued upcalls will have happenned and the code + * will have discarded them because of a lack of a hook. + * (socket not open). + * + * This is a bad byproduct of the complicated way in which hooks + * are now created (3 daisy chained async events). + * + * Since we are a netgraph operation + * We know that we hold a lock on this node. This forces the + * request we make below to be queued rather than implemented + * immediatly which will cause the upcall function to be called a bit + * later. + * However, as we will run any waiting queued operations immediatly + * after doing this one, if we have not finalised the other end + * of the hook, those queued operations will fail. + */ + if (priv->flags & KSF_CLONED) { + ng_send_fn(node, NULL, &ng_ksocket_incoming2, so, M_NOWAIT); + } + + return (0); +} + +/* + * Receive a control message + */ +static int +ng_ksocket_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct thread *td = curthread; /* XXX broken */ + const priv_p priv = NG_NODE_PRIVATE(node); + struct socket *const so = priv->so; + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + ng_ID_t raddr; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_KSOCKET_COOKIE: + switch (msg->header.cmd) { + case NGM_KSOCKET_BIND: + { + struct sockaddr *const sa + = (struct sockaddr *)msg->data; + + /* Sanity check */ + if (msg->header.arglen < SADATA_OFFSET + || msg->header.arglen < sa->sa_len) + ERROUT(EINVAL); + if (so == NULL) + ERROUT(ENXIO); + + /* Bind */ + error = sobind(so, sa, td); + break; + } + case NGM_KSOCKET_LISTEN: + { + /* Sanity check */ + if (msg->header.arglen != sizeof(int32_t)) + ERROUT(EINVAL); + if (so == NULL) + ERROUT(ENXIO); + + /* Listen */ + error = solisten(so, *((int32_t *)msg->data), td); + break; + } + + case NGM_KSOCKET_ACCEPT: + { + /* Sanity check */ + if (msg->header.arglen != 0) + ERROUT(EINVAL); + if (so == NULL) + ERROUT(ENXIO); + + /* Make sure the socket is capable of accepting */ + if (!(so->so_options & SO_ACCEPTCONN)) + ERROUT(EINVAL); + if (priv->flags & KSF_ACCEPTING) + ERROUT(EALREADY); + + error = ng_ksocket_check_accept(priv); + if (error != 0 && error != EWOULDBLOCK) + ERROUT(error); + + /* + * If a connection is already complete, take it. + * Otherwise let the upcall function deal with + * the connection when it comes in. + */ + priv->response_token = msg->header.token; + raddr = priv->response_addr = NGI_RETADDR(item); + if (error == 0) { + ng_ksocket_finish_accept(priv); + } else + priv->flags |= KSF_ACCEPTING; + break; + } + + case NGM_KSOCKET_CONNECT: + { + struct sockaddr *const sa + = (struct sockaddr *)msg->data; + + /* Sanity check */ + if (msg->header.arglen < SADATA_OFFSET + || msg->header.arglen < sa->sa_len) + ERROUT(EINVAL); + if (so == NULL) + ERROUT(ENXIO); + + /* Do connect */ + if ((so->so_state & SS_ISCONNECTING) != 0) + ERROUT(EALREADY); + if ((error = soconnect(so, sa, td)) != 0) { + so->so_state &= ~SS_ISCONNECTING; + ERROUT(error); + } + if ((so->so_state & SS_ISCONNECTING) != 0) { + /* We will notify the sender when we connect */ + priv->response_token = msg->header.token; + raddr = priv->response_addr = NGI_RETADDR(item); + priv->flags |= KSF_CONNECTING; + ERROUT(EINPROGRESS); + } + break; + } + + case NGM_KSOCKET_GETNAME: + case NGM_KSOCKET_GETPEERNAME: + { + int (*func)(struct socket *so, struct sockaddr **nam); + struct sockaddr *sa = NULL; + int len; + + /* Sanity check */ + if (msg->header.arglen != 0) + ERROUT(EINVAL); + if (so == NULL) + ERROUT(ENXIO); + + /* Get function */ + if (msg->header.cmd == NGM_KSOCKET_GETPEERNAME) { + if ((so->so_state + & (SS_ISCONNECTED|SS_ISCONFIRMING)) == 0) + ERROUT(ENOTCONN); + func = so->so_proto->pr_usrreqs->pru_peeraddr; + } else + func = so->so_proto->pr_usrreqs->pru_sockaddr; + + /* Get local or peer address */ + if ((error = (*func)(so, &sa)) != 0) + goto bail; + len = (sa == NULL) ? 0 : sa->sa_len; + + /* Send it back in a response */ + NG_MKRESPONSE(resp, msg, len, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + goto bail; + } + bcopy(sa, resp->data, len); + + bail: + /* Cleanup */ + if (sa != NULL) + FREE(sa, M_SONAME); + break; + } + + case NGM_KSOCKET_GETOPT: + { + struct ng_ksocket_sockopt *ksopt = + (struct ng_ksocket_sockopt *)msg->data; + struct sockopt sopt; + + /* Sanity check */ + if (msg->header.arglen != sizeof(*ksopt)) + ERROUT(EINVAL); + if (so == NULL) + ERROUT(ENXIO); + + /* Get response with room for option value */ + NG_MKRESPONSE(resp, msg, sizeof(*ksopt) + + NG_KSOCKET_MAX_OPTLEN, M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + + /* Get socket option, and put value in the response */ + sopt.sopt_dir = SOPT_GET; + sopt.sopt_level = ksopt->level; + sopt.sopt_name = ksopt->name; + sopt.sopt_td = NULL; + sopt.sopt_valsize = NG_KSOCKET_MAX_OPTLEN; + ksopt = (struct ng_ksocket_sockopt *)resp->data; + sopt.sopt_val = ksopt->value; + if ((error = sogetopt(so, &sopt)) != 0) { + NG_FREE_MSG(resp); + break; + } + + /* Set actual value length */ + resp->header.arglen = sizeof(*ksopt) + + sopt.sopt_valsize; + break; + } + + case NGM_KSOCKET_SETOPT: + { + struct ng_ksocket_sockopt *const ksopt = + (struct ng_ksocket_sockopt *)msg->data; + const int valsize = msg->header.arglen - sizeof(*ksopt); + struct sockopt sopt; + + /* Sanity check */ + if (valsize < 0) + ERROUT(EINVAL); + if (so == NULL) + ERROUT(ENXIO); + + /* Set socket option */ + sopt.sopt_dir = SOPT_SET; + sopt.sopt_level = ksopt->level; + sopt.sopt_name = ksopt->name; + sopt.sopt_val = ksopt->value; + sopt.sopt_valsize = valsize; + sopt.sopt_td = NULL; + error = sosetopt(so, &sopt); + break; + } + + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive incoming data on our hook. Send it out the socket. + */ +static int +ng_ksocket_rcvdata(hook_p hook, item_p item) +{ + struct thread *td = curthread; /* XXX broken */ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + struct socket *const so = priv->so; + struct sockaddr *sa = NULL; + int error; + struct mbuf *m; + struct sa_tag *stag; + + /* Extract data */ + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + /* + * Look if socket address is stored in packet tags. + * If sockaddr is ours, or provided by a third party (zero id), + * then we accept it. + */ + if (((stag = (struct sa_tag *)m_tag_locate(m, NGM_KSOCKET_COOKIE, + NG_KSOCKET_TAG_SOCKADDR, NULL)) != NULL) && + (stag->id == NG_NODE_ID(node) || stag->id == 0)) + sa = &stag->sa; + + /* Reset specific mbuf flags to prevent addressing problems. */ + m->m_flags &= ~(M_BCAST|M_MCAST); + + /* Send packet */ + error = sosend(so, sa, 0, m, 0, 0, td); + + return (error); +} + +/* + * Destroy node + */ +static int +ng_ksocket_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + priv_p embryo; + + /* Close our socket (if any) */ + if (priv->so != NULL) { + SOCKBUF_LOCK(&priv->so->so_rcv); + priv->so->so_rcv.sb_flags &= ~SB_UPCALL; + SOCKBUF_UNLOCK(&priv->so->so_rcv); + SOCKBUF_LOCK(&priv->so->so_snd); + priv->so->so_snd.sb_flags &= ~SB_UPCALL; + SOCKBUF_UNLOCK(&priv->so->so_snd); + priv->so->so_upcall = NULL; + soclose(priv->so); + priv->so = NULL; + } + + /* If we are an embryo, take ourselves out of the parent's list */ + if (priv->flags & KSF_EMBRYONIC) { + LIST_REMOVE(priv, siblings); + priv->flags &= ~KSF_EMBRYONIC; + } + + /* Remove any embryonic children we have */ + while (!LIST_EMPTY(&priv->embryos)) { + embryo = LIST_FIRST(&priv->embryos); + ng_rmnode_self(embryo->node); + } + + /* Take down netgraph node */ + bzero(priv, sizeof(*priv)); + FREE(priv, M_NETGRAPH_KSOCKET); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); /* let the node escape */ + return (0); +} + +/* + * Hook disconnection + */ +static int +ng_ksocket_disconnect(hook_p hook) +{ + KASSERT(NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0, + ("%s: numhooks=%d?", __func__, + NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)))); + if (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))) + ng_rmnode_self(NG_HOOK_NODE(hook)); + return (0); +} + +/************************************************************************ + HELPER STUFF + ************************************************************************/ +/* + * You should not "just call" a netgraph node function from an external + * asynchronous event. This is because in doing so you are ignoring the + * locking on the netgraph nodes. Instead call your function via ng_send_fn(). + * This will call the function you chose, but will first do all the + * locking rigmarole. Your function MAY only be called at some distant future + * time (several millisecs away) so don't give it any arguments + * that may be revoked soon (e.g. on your stack). + * + * To decouple stack, we use queue version of ng_send_fn(). + */ + +static void +ng_ksocket_incoming(struct socket *so, void *arg, int waitflag) +{ + const node_p node = arg; + const priv_p priv = NG_NODE_PRIVATE(node); + int wait = ((waitflag & M_WAITOK) ? NG_WAITOK : 0) | NG_QUEUE; + + /* + * Even if node is not locked, as soon as we are called, we assume + * it exist and it's private area is valid. With some care we can + * access it. Mark node that incoming event for it was sent to + * avoid unneded queue trashing. + */ + if (atomic_cmpset_int(&priv->fn_sent, 0, 1) && + ng_send_fn1(node, NULL, &ng_ksocket_incoming2, so, 0, wait)) { + atomic_store_rel_int(&priv->fn_sent, 0); + } +} + + +/* + * When incoming data is appended to the socket, we get notified here. + * This is also called whenever a significant event occurs for the socket. + * Our original caller may have queued this even some time ago and + * we cannot trust that he even still exists. The node however is being + * held with a reference by the queueing code and guarantied to be valid. + */ +static void +ng_ksocket_incoming2(node_p node, hook_p hook, void *arg1, int arg2) +{ + struct socket *so = arg1; + const priv_p priv = NG_NODE_PRIVATE(node); + struct mbuf *m; + struct ng_mesg *response; + struct uio auio; + int s, flags, error; + + s = splnet(); + + /* so = priv->so; *//* XXX could have derived this like so */ + KASSERT(so == priv->so, ("%s: wrong socket", __func__)); + + /* Allow next incoming event to be queued. */ + atomic_store_rel_int(&priv->fn_sent, 0); + + /* Check whether a pending connect operation has completed */ + if (priv->flags & KSF_CONNECTING) { + if ((error = so->so_error) != 0) { + so->so_error = 0; + so->so_state &= ~SS_ISCONNECTING; + } + if (!(so->so_state & SS_ISCONNECTING)) { + NG_MKMESSAGE(response, NGM_KSOCKET_COOKIE, + NGM_KSOCKET_CONNECT, sizeof(int32_t), M_NOWAIT); + if (response != NULL) { + response->header.flags |= NGF_RESP; + response->header.token = priv->response_token; + *(int32_t *)response->data = error; + /* + * send an async "response" message + * to the node that set us up + * (if it still exists) + */ + NG_SEND_MSG_ID(error, node, + response, priv->response_addr, 0); + } + priv->flags &= ~KSF_CONNECTING; + } + } + + /* Check whether a pending accept operation has completed */ + if (priv->flags & KSF_ACCEPTING) { + error = ng_ksocket_check_accept(priv); + if (error != EWOULDBLOCK) + priv->flags &= ~KSF_ACCEPTING; + if (error == 0) + ng_ksocket_finish_accept(priv); + } + + /* + * If we don't have a hook, we must handle data events later. When + * the hook gets created and is connected, this upcall function + * will be called again. + */ + if (priv->hook == NULL) { + splx(s); + return; + } + + /* Read and forward available mbuf's */ + auio.uio_td = NULL; + auio.uio_resid = 1000000000; + flags = MSG_DONTWAIT; + while (1) { + struct sockaddr *sa = NULL; + struct mbuf *n; + + /* Try to get next packet from socket */ + if ((error = soreceive(so, (so->so_state & SS_ISCONNECTED) ? + NULL : &sa, &auio, &m, (struct mbuf **)0, &flags)) != 0) + break; + + /* See if we got anything */ + if (m == NULL) { + if (sa != NULL) + FREE(sa, M_SONAME); + break; + } + + /* + * Don't trust the various socket layers to get the + * packet header and length correct (e.g. kern/15175). + * + * Also, do not trust that soreceive() will clear m_nextpkt + * for us (e.g. kern/84952, kern/82413). + */ + m->m_pkthdr.csum_flags = 0; + for (n = m, m->m_pkthdr.len = 0; n != NULL; n = n->m_next) { + m->m_pkthdr.len += n->m_len; + n->m_nextpkt = NULL; + } + + /* Put peer's socket address (if any) into a tag */ + if (sa != NULL) { + struct sa_tag *stag; + + stag = (struct sa_tag *)m_tag_alloc(NGM_KSOCKET_COOKIE, + NG_KSOCKET_TAG_SOCKADDR, sizeof(ng_ID_t) + + sa->sa_len, M_NOWAIT); + if (stag == NULL) { + FREE(sa, M_SONAME); + goto sendit; + } + bcopy(sa, &stag->sa, sa->sa_len); + FREE(sa, M_SONAME); + stag->id = NG_NODE_ID(node); + m_tag_prepend(m, &stag->tag); + } + +sendit: /* Forward data with optional peer sockaddr as packet tag */ + NG_SEND_DATA_ONLY(error, priv->hook, m); + } + + /* + * If the peer has closed the connection, forward a 0-length mbuf + * to indicate end-of-file. + */ + if (so->so_rcv.sb_state & SBS_CANTRCVMORE && !(priv->flags & KSF_EOFSEEN)) { + MGETHDR(m, M_NOWAIT, MT_DATA); + if (m != NULL) { + m->m_len = m->m_pkthdr.len = 0; + NG_SEND_DATA_ONLY(error, priv->hook, m); + } + priv->flags |= KSF_EOFSEEN; + } + splx(s); +} + +/* + * Check for a completed incoming connection and return 0 if one is found. + * Otherwise return the appropriate error code. + */ +static int +ng_ksocket_check_accept(priv_p priv) +{ + struct socket *const head = priv->so; + int error; + + if ((error = head->so_error) != 0) { + head->so_error = 0; + return error; + } + /* Unlocked read. */ + if (TAILQ_EMPTY(&head->so_comp)) { + if (head->so_rcv.sb_state & SBS_CANTRCVMORE) + return ECONNABORTED; + return EWOULDBLOCK; + } + return 0; +} + +/* + * Handle the first completed incoming connection, assumed to be already + * on the socket's so_comp queue. + */ +static void +ng_ksocket_finish_accept(priv_p priv) +{ + struct socket *const head = priv->so; + struct socket *so; + struct sockaddr *sa = NULL; + struct ng_mesg *resp; + struct ng_ksocket_accept *resp_data; + node_p node; + priv_p priv2; + int len; + int error; + + ACCEPT_LOCK(); + so = TAILQ_FIRST(&head->so_comp); + if (so == NULL) { /* Should never happen */ + ACCEPT_UNLOCK(); + return; + } + TAILQ_REMOVE(&head->so_comp, so, so_list); + head->so_qlen--; + so->so_qstate &= ~SQ_COMP; + so->so_head = NULL; + SOCK_LOCK(so); + soref(so); + so->so_state |= SS_NBIO; + SOCK_UNLOCK(so); + ACCEPT_UNLOCK(); + + /* XXX KNOTE(&head->so_rcv.sb_sel.si_note, 0); */ + + soaccept(so, &sa); + + len = OFFSETOF(struct ng_ksocket_accept, addr); + if (sa != NULL) + len += sa->sa_len; + + NG_MKMESSAGE(resp, NGM_KSOCKET_COOKIE, NGM_KSOCKET_ACCEPT, len, + M_NOWAIT); + if (resp == NULL) { + soclose(so); + goto out; + } + resp->header.flags |= NGF_RESP; + resp->header.token = priv->response_token; + + /* Clone a ksocket node to wrap the new socket */ + error = ng_make_node_common(&ng_ksocket_typestruct, &node); + if (error) { + FREE(resp, M_NETGRAPH); + soclose(so); + goto out; + } + + if (ng_ksocket_constructor(node) != 0) { + NG_NODE_UNREF(node); + FREE(resp, M_NETGRAPH); + soclose(so); + goto out; + } + + priv2 = NG_NODE_PRIVATE(node); + priv2->so = so; + priv2->flags |= KSF_CLONED | KSF_EMBRYONIC; + + /* + * Insert the cloned node into a list of embryonic children + * on the parent node. When a hook is created on the cloned + * node it will be removed from this list. When the parent + * is destroyed it will destroy any embryonic children it has. + */ + LIST_INSERT_HEAD(&priv->embryos, priv2, siblings); + + so->so_upcallarg = (caddr_t)node; + so->so_upcall = ng_ksocket_incoming; + SOCKBUF_LOCK(&so->so_rcv); + so->so_rcv.sb_flags |= SB_UPCALL; + SOCKBUF_UNLOCK(&so->so_rcv); + SOCKBUF_LOCK(&so->so_snd); + so->so_snd.sb_flags |= SB_UPCALL; + SOCKBUF_UNLOCK(&so->so_snd); + + /* Fill in the response data and send it or return it to the caller */ + resp_data = (struct ng_ksocket_accept *)resp->data; + resp_data->nodeid = NG_NODE_ID(node); + if (sa != NULL) + bcopy(sa, &resp_data->addr, sa->sa_len); + NG_SEND_MSG_ID(error, node, resp, priv->response_addr, 0); + +out: + if (sa != NULL) + FREE(sa, M_SONAME); +} + +/* + * Parse out either an integer value or an alias. + */ +static int +ng_ksocket_parse(const struct ng_ksocket_alias *aliases, + const char *s, int family) +{ + int k, val; + char *eptr; + + /* Try aliases */ + for (k = 0; aliases[k].name != NULL; k++) { + if (strcmp(s, aliases[k].name) == 0 + && aliases[k].family == family) + return aliases[k].value; + } + + /* Try parsing as a number */ + val = (int)strtoul(s, &eptr, 10); + if (val < 0 || *eptr != '\0') + return (-1); + return (val); +} + diff --git a/sys/netgraph7/ng_ksocket.h b/sys/netgraph7/ng_ksocket.h new file mode 100644 index 0000000000..c897f1c052 --- /dev/null +++ b/sys/netgraph7/ng_ksocket.h @@ -0,0 +1,111 @@ +/* + * ng_ksocket.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_ksocket.h,v 1.13 2005/10/28 14:41:28 ru Exp $ + * $Whistle: ng_ksocket.h,v 1.1 1999/11/16 20:04:40 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_KSOCKET_H_ +#define _NETGRAPH_NG_KSOCKET_H_ + +#include + +/* Node type name and magic cookie */ +#define NG_KSOCKET_NODE_TYPE "ksocket" +#define NGM_KSOCKET_COOKIE 942710669 + +/* For NGM_KSOCKET_SETOPT and NGM_KSOCKET_GETOPT control messages */ +struct ng_ksocket_sockopt { + int32_t level; /* second arg of [gs]etsockopt() */ + int32_t name; /* third arg of [gs]etsockopt() */ + u_char value[]; /* fourth arg of [gs]etsockopt() */ +}; + +/* Max length socket option we can return via NGM_KSOCKET_GETOPT + XXX This should not be necessary, we should dynamically size + XXX the response. Until then.. */ +#define NG_KSOCKET_MAX_OPTLEN 1024 + +/* Keep this in sync with the above structure definition */ +#define NG_KSOCKET_SOCKOPT_INFO(svtype) { \ + { "level", &ng_parse_int32_type }, \ + { "name", &ng_parse_int32_type }, \ + { "value", (svtype) }, \ + { NULL } \ +} + +/* For NGM_KSOCKET_ACCEPT control message responses */ +struct ng_ksocket_accept { + u_int32_t nodeid; /* node ID of connected ksocket */ + struct sockaddr addr; /* peer's address (variable length) */ +}; + +/* Keep this in sync with the above structure definition */ +#define NGM_KSOCKET_ACCEPT_INFO { \ + { "nodeid", &ng_parse_hint32_type }, \ + { "addr", &ng_ksocket_generic_sockaddr_type }, \ + { NULL } \ +} + +/* Netgraph commands */ +enum { + NGM_KSOCKET_BIND = 1, + NGM_KSOCKET_LISTEN, + NGM_KSOCKET_ACCEPT, + NGM_KSOCKET_CONNECT, + NGM_KSOCKET_GETNAME, + NGM_KSOCKET_GETPEERNAME, + NGM_KSOCKET_SETOPT, + NGM_KSOCKET_GETOPT, +}; + +#ifdef _KERNEL + +/* Structure for sockaddr tag */ +struct sa_tag { + struct m_tag tag; + ng_ID_t id; + struct sockaddr sa; +}; + +/* Tag information ID's */ +#define NG_KSOCKET_TAG_SOCKADDR 1 /* data is struct sockaddr */ + +#endif /* _KERNEL */ +#endif /* _NETGRAPH_NG_KSOCKET_H_ */ diff --git a/sys/netgraph7/ng_l2tp.c b/sys/netgraph7/ng_l2tp.c new file mode 100644 index 0000000000..6efb5cc4f0 --- /dev/null +++ b/sys/netgraph7/ng_l2tp.c @@ -0,0 +1,1619 @@ +/*- + * Copyright (c) 2001-2002 Packet Design, LLC. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, + * use and redistribution of this software, in source or object code + * forms, with or without modifications are expressly permitted by + * Packet Design; provided, however, that: + * + * (i) Any and all reproductions of the source or object code + * must include the copyright notice above and the following + * disclaimer of warranties; and + * (ii) No rights are granted, in any manner or form, to use + * Packet Design trademarks, including the mark "PACKET DESIGN" + * on advertising, endorsements, or otherwise except as such + * appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING + * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, + * OR NON-INFRINGEMENT. PACKET DESIGN DOES NOT WARRANT, GUARANTEE, + * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS + * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, + * RELIABILITY OR OTHERWISE. IN NO EVENT SHALL PACKET DESIGN BE + * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE + * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL + * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF + * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF PACKET DESIGN IS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_l2tp.c,v 1.25 2008/03/16 21:33:12 mav Exp $ + */ + +/* + * L2TP netgraph node type. + * + * This node type implements the lower layer of the + * L2TP protocol as specified in RFC 2661. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_L2TP, "netgraph_l2tp", "netgraph l2tp node"); +#else +#define M_NETGRAPH_L2TP M_NETGRAPH +#endif + +/* L2TP header format (first 2 bytes only) */ +#define L2TP_HDR_CTRL 0x8000 /* control packet */ +#define L2TP_HDR_LEN 0x4000 /* has length field */ +#define L2TP_HDR_SEQ 0x0800 /* has ns, nr fields */ +#define L2TP_HDR_OFF 0x0200 /* has offset field */ +#define L2TP_HDR_PRIO 0x0100 /* give priority */ +#define L2TP_HDR_VERS_MASK 0x000f /* version field mask */ +#define L2TP_HDR_VERSION 0x0002 /* version field */ + +/* Bits that must be zero or one in first two bytes of header */ +#define L2TP_CTRL_0BITS 0x030d /* ctrl: must be 0 */ +#define L2TP_CTRL_1BITS 0xc802 /* ctrl: must be 1 */ +#define L2TP_DATA_0BITS 0x800d /* data: must be 0 */ +#define L2TP_DATA_1BITS 0x0002 /* data: must be 1 */ + +/* Standard xmit ctrl and data header bits */ +#define L2TP_CTRL_HDR (L2TP_HDR_CTRL | L2TP_HDR_LEN \ + | L2TP_HDR_SEQ | L2TP_HDR_VERSION) +#define L2TP_DATA_HDR (L2TP_HDR_VERSION) /* optional: len, seq */ + +/* Some hard coded values */ +#define L2TP_MAX_XWIN 128 /* my max xmit window */ +#define L2TP_MAX_REXMIT 5 /* default max rexmit */ +#define L2TP_MAX_REXMIT_TO 30 /* default rexmit to */ +#define L2TP_DELAYED_ACK ((hz + 19) / 20) /* delayed ack: 50 ms */ + +/* Default data sequence number configuration for new sessions */ +#define L2TP_CONTROL_DSEQ 1 /* we are the lns */ +#define L2TP_ENABLE_DSEQ 1 /* enable data seq # */ + +/* Compare sequence numbers using circular math */ +#define L2TP_SEQ_DIFF(x, y) ((int)((int16_t)(x) - (int16_t)(y))) + +#define SESSHASHSIZE 0x0020 +#define SESSHASH(x) (((x) ^ ((x) >> 8)) & (SESSHASHSIZE - 1)) + +/* Hook private data (data session hooks only) */ +struct ng_l2tp_hook_private { + struct ng_l2tp_sess_config conf; /* hook/session config */ + struct ng_l2tp_session_stats stats; /* per sessions statistics */ + hook_p hook; /* hook reference */ + u_int16_t ns; /* data ns sequence number */ + u_int16_t nr; /* data nr sequence number */ + LIST_ENTRY(ng_l2tp_hook_private) sessions; +}; +typedef struct ng_l2tp_hook_private *hookpriv_p; + +/* + * Sequence number state + * + * Invariants: + * - If cwnd < ssth, we're doing slow start, otherwise congestion avoidance + * - The number of unacknowledged xmit packets is (ns - rack) <= seq->wmax + * - The first (ns - rack) mbuf's in xwin[] array are copies of these + * unacknowledged packets; the remainder of xwin[] consists first of + * zero or more further untransmitted packets in the transmit queue + * - We try to keep the peer's receive window as full as possible. + * Therefore, (i < cwnd && xwin[i] != NULL) implies (ns - rack) > i. + * - rack_timer is running iff (ns - rack) > 0 (unack'd xmit'd pkts) + * - If xack != nr, there are unacknowledged recv packet(s) (delayed ack) + * - xack_timer is running iff xack != nr (unack'd rec'd pkts) + */ +struct l2tp_seq { + u_int16_t ns; /* next xmit seq we send */ + u_int16_t nr; /* next recv seq we expect */ + u_int16_t inproc; /* packet is in processing */ + u_int16_t rack; /* last 'nr' we rec'd */ + u_int16_t xack; /* last 'nr' we sent */ + u_int16_t wmax; /* peer's max recv window */ + u_int16_t cwnd; /* current congestion window */ + u_int16_t ssth; /* slow start threshold */ + u_int16_t acks; /* # consecutive acks rec'd */ + u_int16_t rexmits; /* # retransmits sent */ + struct callout rack_timer; /* retransmit timer */ + struct callout xack_timer; /* delayed ack timer */ + struct mbuf *xwin[L2TP_MAX_XWIN]; /* transmit window */ + struct mtx mtx; /* seq mutex */ +}; + +/* Node private data */ +struct ng_l2tp_private { + node_p node; /* back pointer to node */ + hook_p ctrl; /* hook to upper layers */ + hook_p lower; /* hook to lower layers */ + struct ng_l2tp_config conf; /* node configuration */ + struct ng_l2tp_stats stats; /* node statistics */ + struct l2tp_seq seq; /* ctrl sequence number state */ + ng_ID_t ftarget; /* failure message target */ + LIST_HEAD(, ng_l2tp_hook_private) sesshash[SESSHASHSIZE]; +}; +typedef struct ng_l2tp_private *priv_p; + +/* Netgraph node methods */ +static ng_constructor_t ng_l2tp_constructor; +static ng_rcvmsg_t ng_l2tp_rcvmsg; +static ng_shutdown_t ng_l2tp_shutdown; +static ng_newhook_t ng_l2tp_newhook; +static ng_rcvdata_t ng_l2tp_rcvdata; +static ng_rcvdata_t ng_l2tp_rcvdata_lower; +static ng_rcvdata_t ng_l2tp_rcvdata_ctrl; +static ng_disconnect_t ng_l2tp_disconnect; + +/* Internal functions */ +static int ng_l2tp_xmit_ctrl(priv_p priv, struct mbuf *m, u_int16_t ns); + +static void ng_l2tp_seq_init(priv_p priv); +static int ng_l2tp_seq_set(priv_p priv, + const struct ng_l2tp_seq_config *conf); +static int ng_l2tp_seq_adjust(priv_p priv, + const struct ng_l2tp_config *conf); +static void ng_l2tp_seq_reset(priv_p priv); +static void ng_l2tp_seq_failure(priv_p priv); +static void ng_l2tp_seq_recv_nr(priv_p priv, u_int16_t nr); +static void ng_l2tp_seq_xack_timeout(node_p node, hook_p hook, + void *arg1, int arg2); +static void ng_l2tp_seq_rack_timeout(node_p node, hook_p hook, + void *arg1, int arg2); + +static hookpriv_p ng_l2tp_find_session(priv_p privp, u_int16_t sid); +static ng_fn_eachhook ng_l2tp_reset_session; + +#ifdef INVARIANTS +static void ng_l2tp_seq_check(struct l2tp_seq *seq); +#endif + +/* Parse type for struct ng_l2tp_seq_config. */ +static const struct ng_parse_struct_field + ng_l2tp_seq_config_fields[] = NG_L2TP_SEQ_CONFIG_TYPE_INFO; +static const struct ng_parse_type ng_l2tp_seq_config_type = { + &ng_parse_struct_type, + &ng_l2tp_seq_config_fields +}; + +/* Parse type for struct ng_l2tp_config */ +static const struct ng_parse_struct_field + ng_l2tp_config_type_fields[] = NG_L2TP_CONFIG_TYPE_INFO; +static const struct ng_parse_type ng_l2tp_config_type = { + &ng_parse_struct_type, + &ng_l2tp_config_type_fields, +}; + +/* Parse type for struct ng_l2tp_sess_config */ +static const struct ng_parse_struct_field + ng_l2tp_sess_config_type_fields[] = NG_L2TP_SESS_CONFIG_TYPE_INFO; +static const struct ng_parse_type ng_l2tp_sess_config_type = { + &ng_parse_struct_type, + &ng_l2tp_sess_config_type_fields, +}; + +/* Parse type for struct ng_l2tp_stats */ +static const struct ng_parse_struct_field + ng_l2tp_stats_type_fields[] = NG_L2TP_STATS_TYPE_INFO; +static const struct ng_parse_type ng_l2tp_stats_type = { + &ng_parse_struct_type, + &ng_l2tp_stats_type_fields +}; + +/* Parse type for struct ng_l2tp_session_stats. */ +static const struct ng_parse_struct_field + ng_l2tp_session_stats_type_fields[] = NG_L2TP_SESSION_STATS_TYPE_INFO; +static const struct ng_parse_type ng_l2tp_session_stats_type = { + &ng_parse_struct_type, + &ng_l2tp_session_stats_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_l2tp_cmdlist[] = { + { + NGM_L2TP_COOKIE, + NGM_L2TP_SET_CONFIG, + "setconfig", + &ng_l2tp_config_type, + NULL + }, + { + NGM_L2TP_COOKIE, + NGM_L2TP_GET_CONFIG, + "getconfig", + NULL, + &ng_l2tp_config_type + }, + { + NGM_L2TP_COOKIE, + NGM_L2TP_SET_SESS_CONFIG, + "setsessconfig", + &ng_l2tp_sess_config_type, + NULL + }, + { + NGM_L2TP_COOKIE, + NGM_L2TP_GET_SESS_CONFIG, + "getsessconfig", + &ng_parse_hint16_type, + &ng_l2tp_sess_config_type + }, + { + NGM_L2TP_COOKIE, + NGM_L2TP_GET_STATS, + "getstats", + NULL, + &ng_l2tp_stats_type + }, + { + NGM_L2TP_COOKIE, + NGM_L2TP_CLR_STATS, + "clrstats", + NULL, + NULL + }, + { + NGM_L2TP_COOKIE, + NGM_L2TP_GETCLR_STATS, + "getclrstats", + NULL, + &ng_l2tp_stats_type + }, + { + NGM_L2TP_COOKIE, + NGM_L2TP_GET_SESSION_STATS, + "getsessstats", + &ng_parse_int16_type, + &ng_l2tp_session_stats_type + }, + { + NGM_L2TP_COOKIE, + NGM_L2TP_CLR_SESSION_STATS, + "clrsessstats", + &ng_parse_int16_type, + NULL + }, + { + NGM_L2TP_COOKIE, + NGM_L2TP_GETCLR_SESSION_STATS, + "getclrsessstats", + &ng_parse_int16_type, + &ng_l2tp_session_stats_type + }, + { + NGM_L2TP_COOKIE, + NGM_L2TP_ACK_FAILURE, + "ackfailure", + NULL, + NULL + }, + { + NGM_L2TP_COOKIE, + NGM_L2TP_SET_SEQ, + "setsequence", + &ng_l2tp_seq_config_type, + NULL + }, + { 0 } +}; + +/* Node type descriptor */ +static struct ng_type ng_l2tp_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_L2TP_NODE_TYPE, + .constructor = ng_l2tp_constructor, + .rcvmsg = ng_l2tp_rcvmsg, + .shutdown = ng_l2tp_shutdown, + .newhook = ng_l2tp_newhook, + .rcvdata = ng_l2tp_rcvdata, + .disconnect = ng_l2tp_disconnect, + .cmdlist = ng_l2tp_cmdlist, +}; +NETGRAPH_INIT(l2tp, &ng_l2tp_typestruct); + +/* Sequence number state sanity checking */ +#ifdef INVARIANTS +#define L2TP_SEQ_CHECK(seq) ng_l2tp_seq_check(seq) +#else +#define L2TP_SEQ_CHECK(x) do { } while (0) +#endif + +/* memmove macro */ +#define memmove(d, s, l) bcopy(s, d, l) + +/* Whether to use m_copypacket() or m_dup() */ +#define L2TP_COPY_MBUF m_copypacket + +#define ERROUT(x) do { error = (x); goto done; } while (0) + +/************************************************************************ + NETGRAPH NODE STUFF +************************************************************************/ + +/* + * Node type constructor + */ +static int +ng_l2tp_constructor(node_p node) +{ + priv_p priv; + int i; + + /* Allocate private structure */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH_L2TP, M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + NG_NODE_SET_PRIVATE(node, priv); + priv->node = node; + + /* Apply a semi-reasonable default configuration */ + priv->conf.peer_win = 1; + priv->conf.rexmit_max = L2TP_MAX_REXMIT; + priv->conf.rexmit_max_to = L2TP_MAX_REXMIT_TO; + + /* Initialize sequence number state */ + ng_l2tp_seq_init(priv); + + for (i = 0; i < SESSHASHSIZE; i++) + LIST_INIT(&priv->sesshash[i]); + + /* Done */ + return (0); +} + +/* + * Give our OK for a hook to be added. + */ +static int +ng_l2tp_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + /* Check hook name */ + if (strcmp(name, NG_L2TP_HOOK_CTRL) == 0) { + if (priv->ctrl != NULL) + return (EISCONN); + priv->ctrl = hook; + NG_HOOK_SET_RCVDATA(hook, ng_l2tp_rcvdata_ctrl); + } else if (strcmp(name, NG_L2TP_HOOK_LOWER) == 0) { + if (priv->lower != NULL) + return (EISCONN); + priv->lower = hook; + NG_HOOK_SET_RCVDATA(hook, ng_l2tp_rcvdata_lower); + } else { + static const char hexdig[16] = "0123456789abcdef"; + u_int16_t session_id; + hookpriv_p hpriv; + uint16_t hash; + const char *hex; + int i; + int j; + + /* Parse hook name to get session ID */ + if (strncmp(name, NG_L2TP_HOOK_SESSION_P, + sizeof(NG_L2TP_HOOK_SESSION_P) - 1) != 0) + return (EINVAL); + hex = name + sizeof(NG_L2TP_HOOK_SESSION_P) - 1; + for (session_id = i = 0; i < 4; i++) { + for (j = 0; j < 16 && hex[i] != hexdig[j]; j++); + if (j == 16) + return (EINVAL); + session_id = (session_id << 4) | j; + } + if (hex[i] != '\0') + return (EINVAL); + + /* Create hook private structure */ + MALLOC(hpriv, hookpriv_p, + sizeof(*hpriv), M_NETGRAPH_L2TP, M_NOWAIT | M_ZERO); + if (hpriv == NULL) + return (ENOMEM); + hpriv->conf.session_id = htons(session_id); + hpriv->conf.control_dseq = L2TP_CONTROL_DSEQ; + hpriv->conf.enable_dseq = L2TP_ENABLE_DSEQ; + hpriv->hook = hook; + NG_HOOK_SET_PRIVATE(hook, hpriv); + hash = SESSHASH(hpriv->conf.session_id); + LIST_INSERT_HEAD(&priv->sesshash[hash], hpriv, sessions); + } + + /* Done */ + return (0); +} + +/* + * Receive a control message. + */ +static int +ng_l2tp_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + struct ng_mesg *msg; + int error = 0; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_L2TP_COOKIE: + switch (msg->header.cmd) { + case NGM_L2TP_SET_CONFIG: + { + struct ng_l2tp_config *const conf = + (struct ng_l2tp_config *)msg->data; + + /* Check for invalid or illegal config */ + if (msg->header.arglen != sizeof(*conf)) { + error = EINVAL; + break; + } + conf->enabled = !!conf->enabled; + conf->match_id = !!conf->match_id; + conf->tunnel_id = htons(conf->tunnel_id); + conf->peer_id = htons(conf->peer_id); + if (priv->conf.enabled + && ((priv->conf.tunnel_id != 0 + && conf->tunnel_id != priv->conf.tunnel_id) + || ((priv->conf.peer_id != 0 + && conf->peer_id != priv->conf.peer_id)))) { + error = EBUSY; + break; + } + + /* Save calling node as failure target */ + priv->ftarget = NGI_RETADDR(item); + + /* Adjust sequence number state */ + if ((error = ng_l2tp_seq_adjust(priv, conf)) != 0) + break; + + /* Update node's config */ + priv->conf = *conf; + break; + } + case NGM_L2TP_GET_CONFIG: + { + struct ng_l2tp_config *conf; + + NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + conf = (struct ng_l2tp_config *)resp->data; + *conf = priv->conf; + + /* Put ID's in host order */ + conf->tunnel_id = ntohs(conf->tunnel_id); + conf->peer_id = ntohs(conf->peer_id); + break; + } + case NGM_L2TP_SET_SESS_CONFIG: + { + struct ng_l2tp_sess_config *const conf = + (struct ng_l2tp_sess_config *)msg->data; + hookpriv_p hpriv; + + /* Check for invalid or illegal config. */ + if (msg->header.arglen != sizeof(*conf)) { + error = EINVAL; + break; + } + + /* Put ID's in network order */ + conf->session_id = htons(conf->session_id); + conf->peer_id = htons(conf->peer_id); + + /* Find matching hook */ + hpriv = ng_l2tp_find_session(priv, conf->session_id); + if (hpriv == NULL) { + error = ENOENT; + break; + } + + /* Update hook's config */ + hpriv->conf = *conf; + break; + } + case NGM_L2TP_GET_SESS_CONFIG: + { + struct ng_l2tp_sess_config *conf; + u_int16_t session_id; + hookpriv_p hpriv; + + /* Get session ID */ + if (msg->header.arglen != sizeof(session_id)) { + error = EINVAL; + break; + } + memcpy(&session_id, msg->data, 2); + session_id = htons(session_id); + + /* Find matching hook */ + hpriv = ng_l2tp_find_session(priv, session_id); + if (hpriv == NULL) { + error = ENOENT; + break; + } + + /* Send response */ + NG_MKRESPONSE(resp, msg, sizeof(hpriv->conf), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + conf = (struct ng_l2tp_sess_config *)resp->data; + *conf = hpriv->conf; + + /* Put ID's in host order */ + conf->session_id = ntohs(conf->session_id); + conf->peer_id = ntohs(conf->peer_id); + break; + } + case NGM_L2TP_GET_STATS: + case NGM_L2TP_CLR_STATS: + case NGM_L2TP_GETCLR_STATS: + { + if (msg->header.cmd != NGM_L2TP_CLR_STATS) { + NG_MKRESPONSE(resp, msg, + sizeof(priv->stats), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + memcpy(resp->data, + &priv->stats, sizeof(priv->stats)); + } + if (msg->header.cmd != NGM_L2TP_GET_STATS) + memset(&priv->stats, 0, sizeof(priv->stats)); + break; + } + case NGM_L2TP_GET_SESSION_STATS: + case NGM_L2TP_CLR_SESSION_STATS: + case NGM_L2TP_GETCLR_SESSION_STATS: + { + uint16_t session_id; + hookpriv_p hpriv; + + /* Get session ID. */ + if (msg->header.arglen != sizeof(session_id)) { + error = EINVAL; + break; + } + bcopy(msg->data, &session_id, sizeof(uint16_t)); + session_id = htons(session_id); + + /* Find matching hook. */ + hpriv = ng_l2tp_find_session(priv, session_id); + if (hpriv == NULL) { + error = ENOENT; + break; + } + + if (msg->header.cmd != NGM_L2TP_CLR_SESSION_STATS) { + NG_MKRESPONSE(resp, msg, + sizeof(hpriv->stats), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + bcopy(&hpriv->stats, resp->data, + sizeof(hpriv->stats)); + } + if (msg->header.cmd != NGM_L2TP_GET_SESSION_STATS) + bzero(&hpriv->stats, sizeof(hpriv->stats)); + break; + } + case NGM_L2TP_SET_SEQ: + { + struct ng_l2tp_seq_config *const conf = + (struct ng_l2tp_seq_config *)msg->data; + + /* Check for invalid or illegal seq config. */ + if (msg->header.arglen != sizeof(*conf)) { + error = EINVAL; + break; + } + conf->ns = htons(conf->ns); + conf->nr = htons(conf->nr); + conf->rack = htons(conf->rack); + conf->xack = htons(conf->xack); + + /* Set sequence numbers. */ + error = ng_l2tp_seq_set(priv, conf); + break; + } + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } + + /* Done */ + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Destroy node + */ +static int +ng_l2tp_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct l2tp_seq *const seq = &priv->seq; + + /* Sanity check */ + L2TP_SEQ_CHECK(seq); + + /* Reset sequence number state */ + ng_l2tp_seq_reset(priv); + + /* Free private data if neither timer is running */ + ng_uncallout(&seq->rack_timer, node); + ng_uncallout(&seq->xack_timer, node); + + mtx_destroy(&seq->mtx); + + FREE(priv, M_NETGRAPH_L2TP); + + /* Unref node */ + NG_NODE_UNREF(node); + return (0); +} + +/* + * Hook disconnection + */ +static int +ng_l2tp_disconnect(hook_p hook) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + + /* Zero out hook pointer */ + if (hook == priv->ctrl) + priv->ctrl = NULL; + else if (hook == priv->lower) + priv->lower = NULL; + else { + const hookpriv_p hpriv = NG_HOOK_PRIVATE(hook); + LIST_REMOVE(hpriv, sessions); + FREE(hpriv, M_NETGRAPH_L2TP); + NG_HOOK_SET_PRIVATE(hook, NULL); + } + + /* Go away if no longer connected to anything */ + if (NG_NODE_NUMHOOKS(node) == 0 && NG_NODE_IS_VALID(node)) + ng_rmnode_self(node); + return (0); +} + +/************************************************************************* + INTERNAL FUNCTIONS +*************************************************************************/ + +/* + * Find the hook with a given session ID (in network order). + */ +static hookpriv_p +ng_l2tp_find_session(priv_p privp, u_int16_t sid) +{ + uint16_t hash = SESSHASH(sid); + hookpriv_p hpriv = NULL; + + LIST_FOREACH(hpriv, &privp->sesshash[hash], sessions) { + if (hpriv->conf.session_id == sid) + break; + } + + return (hpriv); +} + +/* + * Reset a hook's session state. + */ +static int +ng_l2tp_reset_session(hook_p hook, void *arg) +{ + const hookpriv_p hpriv = NG_HOOK_PRIVATE(hook); + + if (hpriv != NULL) { + hpriv->conf.control_dseq = 0; + hpriv->conf.enable_dseq = 0; + bzero(&hpriv->conf, sizeof(struct ng_l2tp_session_stats)); + hpriv->nr = 0; + hpriv->ns = 0; + } + return (-1); +} + +/* + * Handle an incoming frame from below. + */ +static int +ng_l2tp_rcvdata_lower(hook_p h, item_p item) +{ + static const u_int16_t req_bits[2][2] = { + { L2TP_DATA_0BITS, L2TP_DATA_1BITS }, + { L2TP_CTRL_0BITS, L2TP_CTRL_1BITS }, + }; + const node_p node = NG_HOOK_NODE(h); + const priv_p priv = NG_NODE_PRIVATE(node); + hookpriv_p hpriv = NULL; + hook_p hook = NULL; + u_int16_t ids[2]; + struct mbuf *m; + u_int16_t hdr; + u_int16_t ns; + u_int16_t nr; + int is_ctrl; + int error; + int len, plen; + + /* Sanity check */ + L2TP_SEQ_CHECK(&priv->seq); + + /* If not configured, reject */ + if (!priv->conf.enabled) { + NG_FREE_ITEM(item); + ERROUT(ENXIO); + } + + /* Grab mbuf */ + NGI_GET_M(item, m); + + /* Remember full packet length; needed for per session accounting. */ + plen = m->m_pkthdr.len; + + /* Update stats */ + priv->stats.recvPackets++; + priv->stats.recvOctets += plen; + + /* Get initial header */ + if (m->m_pkthdr.len < 6) { + priv->stats.recvRunts++; + NG_FREE_ITEM(item); + NG_FREE_M(m); + ERROUT(EINVAL); + } + if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) { + priv->stats.memoryFailures++; + NG_FREE_ITEM(item); + ERROUT(EINVAL); + } + hdr = ntohs(*mtod(m, u_int16_t *)); + m_adj(m, 2); + + /* Check required header bits and minimum length */ + is_ctrl = (hdr & L2TP_HDR_CTRL) != 0; + if ((hdr & req_bits[is_ctrl][0]) != 0 + || (~hdr & req_bits[is_ctrl][1]) != 0) { + priv->stats.recvInvalid++; + NG_FREE_ITEM(item); + NG_FREE_M(m); + ERROUT(EINVAL); + } + if (m->m_pkthdr.len < 4 /* tunnel, session id */ + + (2 * ((hdr & L2TP_HDR_LEN) != 0)) /* length field */ + + (4 * ((hdr & L2TP_HDR_SEQ) != 0)) /* seq # fields */ + + (2 * ((hdr & L2TP_HDR_OFF) != 0))) { /* offset field */ + priv->stats.recvRunts++; + NG_FREE_ITEM(item); + NG_FREE_M(m); + ERROUT(EINVAL); + } + + /* Get and validate length field if present */ + if ((hdr & L2TP_HDR_LEN) != 0) { + if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) { + priv->stats.memoryFailures++; + NG_FREE_ITEM(item); + ERROUT(EINVAL); + } + len = (u_int16_t)ntohs(*mtod(m, u_int16_t *)) - 4; + m_adj(m, 2); + if (len < 0 || len > m->m_pkthdr.len) { + priv->stats.recvInvalid++; + NG_FREE_ITEM(item); + NG_FREE_M(m); + ERROUT(EINVAL); + } + if (len < m->m_pkthdr.len) /* trim extra bytes */ + m_adj(m, -(m->m_pkthdr.len - len)); + } + + /* Get tunnel ID and session ID */ + if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) { + priv->stats.memoryFailures++; + NG_FREE_ITEM(item); + ERROUT(EINVAL); + } + memcpy(ids, mtod(m, u_int16_t *), 4); + m_adj(m, 4); + + /* Check tunnel ID */ + if (ids[0] != priv->conf.tunnel_id + && (priv->conf.match_id || ids[0] != 0)) { + priv->stats.recvWrongTunnel++; + NG_FREE_ITEM(item); + NG_FREE_M(m); + ERROUT(EADDRNOTAVAIL); + } + + /* Check session ID (for data packets only) */ + if ((hdr & L2TP_HDR_CTRL) == 0) { + hpriv = ng_l2tp_find_session(priv, ids[1]); + if (hpriv == NULL) { + priv->stats.recvUnknownSID++; + NG_FREE_ITEM(item); + NG_FREE_M(m); + ERROUT(ENOTCONN); + } + hook = hpriv->hook; + } + + /* Get Ns, Nr fields if present */ + if ((hdr & L2TP_HDR_SEQ) != 0) { + if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) { + priv->stats.memoryFailures++; + NG_FREE_ITEM(item); + ERROUT(EINVAL); + } + memcpy(&ns, &mtod(m, u_int16_t *)[0], 2); + ns = ntohs(ns); + memcpy(&nr, &mtod(m, u_int16_t *)[1], 2); + nr = ntohs(nr); + m_adj(m, 4); + } + + /* Strip offset padding if present */ + if ((hdr & L2TP_HDR_OFF) != 0) { + u_int16_t offset; + + /* Get length of offset padding */ + if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) { + priv->stats.memoryFailures++; + NG_FREE_ITEM(item); + ERROUT(EINVAL); + } + memcpy(&offset, mtod(m, u_int16_t *), 2); + offset = ntohs(offset); + + /* Trim offset padding */ + if ((2+offset) > m->m_pkthdr.len) { + priv->stats.recvInvalid++; + NG_FREE_ITEM(item); + NG_FREE_M(m); + ERROUT(EINVAL); + } + m_adj(m, 2+offset); + } + + /* Handle control packets */ + if ((hdr & L2TP_HDR_CTRL) != 0) { + struct l2tp_seq *const seq = &priv->seq; + + /* Handle receive ack sequence number Nr */ + ng_l2tp_seq_recv_nr(priv, nr); + + /* Discard ZLB packets */ + if (m->m_pkthdr.len == 0) { + priv->stats.recvZLBs++; + NG_FREE_ITEM(item); + NG_FREE_M(m); + ERROUT(0); + } + + mtx_lock(&seq->mtx); + /* + * If not what we expect or we are busy, drop packet and + * send an immediate ZLB ack. + */ + if (ns != seq->nr || seq->inproc) { + if (L2TP_SEQ_DIFF(ns, seq->nr) <= 0) + priv->stats.recvDuplicates++; + else + priv->stats.recvOutOfOrder++; + mtx_unlock(&seq->mtx); + ng_l2tp_xmit_ctrl(priv, NULL, seq->ns); + NG_FREE_ITEM(item); + NG_FREE_M(m); + ERROUT(0); + } + /* + * Until we deliver this packet we can't receive next one as + * we have no information for sending ack. + */ + seq->inproc = 1; + mtx_unlock(&seq->mtx); + + /* Prepend session ID to packet. */ + M_PREPEND(m, 2, M_DONTWAIT); + if (m == NULL) { + seq->inproc = 0; + priv->stats.memoryFailures++; + NG_FREE_ITEM(item); + ERROUT(ENOBUFS); + } + memcpy(mtod(m, u_int16_t *), &ids[1], 2); + + /* Deliver packet to upper layers */ + NG_FWD_NEW_DATA(error, item, priv->ctrl, m); + + mtx_lock(&seq->mtx); + /* Ready to process next packet. */ + seq->inproc = 0; + + /* If packet was successfully delivered send ack. */ + if (error == 0) { + /* Update recv sequence number */ + seq->nr++; + /* Start receive ack timer, if not already running */ + if (!callout_active(&seq->xack_timer)) { + ng_callout(&seq->xack_timer, priv->node, NULL, + L2TP_DELAYED_ACK, ng_l2tp_seq_xack_timeout, + NULL, 0); + } + } + mtx_unlock(&seq->mtx); + + ERROUT(error); + } + + /* Per session packet, account it. */ + hpriv->stats.recvPackets++; + hpriv->stats.recvOctets += plen; + + /* Follow peer's lead in data sequencing, if configured to do so */ + if (!hpriv->conf.control_dseq) + hpriv->conf.enable_dseq = ((hdr & L2TP_HDR_SEQ) != 0); + + /* Handle data sequence numbers if present and enabled */ + if ((hdr & L2TP_HDR_SEQ) != 0) { + if (hpriv->conf.enable_dseq + && L2TP_SEQ_DIFF(ns, hpriv->nr) < 0) { + NG_FREE_ITEM(item); /* duplicate or out of order */ + NG_FREE_M(m); + priv->stats.recvDataDrops++; + ERROUT(0); + } + hpriv->nr = ns + 1; + } + + /* Drop empty data packets */ + if (m->m_pkthdr.len == 0) { + NG_FREE_ITEM(item); + NG_FREE_M(m); + ERROUT(0); + } + + /* Deliver data */ + NG_FWD_NEW_DATA(error, item, hook, m); +done: + /* Done */ + L2TP_SEQ_CHECK(&priv->seq); + return (error); +} + +/* + * Handle an outgoing control frame. + */ +static int +ng_l2tp_rcvdata_ctrl(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + struct l2tp_seq *const seq = &priv->seq; + struct mbuf *m; + int error; + int i; + u_int16_t ns; + + /* Sanity check */ + L2TP_SEQ_CHECK(&priv->seq); + + /* If not configured, reject */ + if (!priv->conf.enabled) { + NG_FREE_ITEM(item); + ERROUT(ENXIO); + } + + /* Grab mbuf and discard other stuff XXX */ + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + /* Packet should have session ID prepended */ + if (m->m_pkthdr.len < 2) { + priv->stats.xmitInvalid++; + m_freem(m); + ERROUT(EINVAL); + } + + /* Check max length */ + if (m->m_pkthdr.len >= 0x10000 - 14) { + priv->stats.xmitTooBig++; + m_freem(m); + ERROUT(EOVERFLOW); + } + + mtx_lock(&seq->mtx); + + /* Find next empty slot in transmit queue */ + for (i = 0; i < L2TP_MAX_XWIN && seq->xwin[i] != NULL; i++); + if (i == L2TP_MAX_XWIN) { + mtx_unlock(&seq->mtx); + priv->stats.xmitDrops++; + m_freem(m); + ERROUT(ENOBUFS); + } + seq->xwin[i] = m; + + /* If peer's receive window is already full, nothing else to do */ + if (i >= seq->cwnd) { + mtx_unlock(&seq->mtx); + ERROUT(0); + } + + /* Start retransmit timer if not already running */ + if (!callout_active(&seq->rack_timer)) + ng_callout(&seq->rack_timer, node, NULL, + hz, ng_l2tp_seq_rack_timeout, NULL, 0); + + ns = seq->ns++; + + mtx_unlock(&seq->mtx); + + /* Copy packet */ + if ((m = L2TP_COPY_MBUF(m, M_DONTWAIT)) == NULL) { + priv->stats.memoryFailures++; + ERROUT(ENOBUFS); + } + + /* Send packet and increment xmit sequence number */ + error = ng_l2tp_xmit_ctrl(priv, m, ns); +done: + /* Done */ + L2TP_SEQ_CHECK(&priv->seq); + return (error); +} + +/* + * Handle an outgoing data frame. + */ +static int +ng_l2tp_rcvdata(hook_p hook, item_p item) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + const hookpriv_p hpriv = NG_HOOK_PRIVATE(hook); + struct mbuf *m; + u_int16_t hdr; + int error; + int i = 1; + + /* Sanity check */ + L2TP_SEQ_CHECK(&priv->seq); + + /* If not configured, reject */ + if (!priv->conf.enabled) { + NG_FREE_ITEM(item); + ERROUT(ENXIO); + } + + /* Get mbuf */ + NGI_GET_M(item, m); + + /* Check max length */ + if (m->m_pkthdr.len >= 0x10000 - 12) { + priv->stats.xmitDataTooBig++; + NG_FREE_ITEM(item); + NG_FREE_M(m); + ERROUT(EOVERFLOW); + } + + /* Prepend L2TP header */ + M_PREPEND(m, 6 + + (2 * (hpriv->conf.include_length != 0)) + + (4 * (hpriv->conf.enable_dseq != 0)), + M_DONTWAIT); + if (m == NULL) { + priv->stats.memoryFailures++; + NG_FREE_ITEM(item); + ERROUT(ENOBUFS); + } + hdr = L2TP_DATA_HDR; + if (hpriv->conf.include_length) { + hdr |= L2TP_HDR_LEN; + mtod(m, u_int16_t *)[i++] = htons(m->m_pkthdr.len); + } + mtod(m, u_int16_t *)[i++] = priv->conf.peer_id; + mtod(m, u_int16_t *)[i++] = hpriv->conf.peer_id; + if (hpriv->conf.enable_dseq) { + hdr |= L2TP_HDR_SEQ; + mtod(m, u_int16_t *)[i++] = htons(hpriv->ns); + mtod(m, u_int16_t *)[i++] = htons(hpriv->nr); + hpriv->ns++; + } + mtod(m, u_int16_t *)[0] = htons(hdr); + + /* Update per session stats. */ + hpriv->stats.xmitPackets++; + hpriv->stats.xmitOctets += m->m_pkthdr.len; + + /* And the global one. */ + priv->stats.xmitPackets++; + priv->stats.xmitOctets += m->m_pkthdr.len; + + /* Send packet */ + NG_FWD_NEW_DATA(error, item, priv->lower, m); +done: + /* Done */ + L2TP_SEQ_CHECK(&priv->seq); + return (error); +} + +/* + * Send a message to our controlling node that we've failed. + */ +static void +ng_l2tp_seq_failure(priv_p priv) +{ + struct ng_mesg *msg; + int error; + + NG_MKMESSAGE(msg, NGM_L2TP_COOKIE, NGM_L2TP_ACK_FAILURE, 0, M_NOWAIT); + if (msg == NULL) + return; + NG_SEND_MSG_ID(error, priv->node, msg, priv->ftarget, 0); +} + +/************************************************************************ + SEQUENCE NUMBER HANDLING +************************************************************************/ + +/* + * Initialize sequence number state. + */ +static void +ng_l2tp_seq_init(priv_p priv) +{ + struct l2tp_seq *const seq = &priv->seq; + + KASSERT(priv->conf.peer_win >= 1, + ("%s: peer_win is zero", __func__)); + memset(seq, 0, sizeof(*seq)); + seq->cwnd = 1; + seq->wmax = priv->conf.peer_win; + if (seq->wmax > L2TP_MAX_XWIN) + seq->wmax = L2TP_MAX_XWIN; + seq->ssth = seq->wmax; + ng_callout_init(&seq->rack_timer); + ng_callout_init(&seq->xack_timer); + mtx_init(&seq->mtx, "ng_l2tp", NULL, MTX_DEF); + L2TP_SEQ_CHECK(seq); +} + +/* + * Set sequence number state as given from user. + */ +static int +ng_l2tp_seq_set(priv_p priv, const struct ng_l2tp_seq_config *conf) +{ + struct l2tp_seq *const seq = &priv->seq; + + /* If node is enabled, deny update to sequence numbers. */ + if (priv->conf.enabled) + return (EBUSY); + + /* We only can handle the simple cases. */ + if (conf->xack != conf->nr || conf->ns != conf->rack) + return (EINVAL); + + /* Set ns,nr,rack,xack parameters. */ + seq->ns = conf->ns; + seq->nr = conf->nr; + seq->rack = conf->rack; + seq->xack = conf->xack; + + return (0); +} + +/* + * Adjust sequence number state accordingly after reconfiguration. + */ +static int +ng_l2tp_seq_adjust(priv_p priv, const struct ng_l2tp_config *conf) +{ + struct l2tp_seq *const seq = &priv->seq; + u_int16_t new_wmax; + + /* If disabling node, reset state sequence number */ + if (!conf->enabled) { + ng_l2tp_seq_reset(priv); + return (0); + } + + /* Adjust peer's max recv window; it can only increase */ + new_wmax = conf->peer_win; + if (new_wmax > L2TP_MAX_XWIN) + new_wmax = L2TP_MAX_XWIN; + if (new_wmax == 0) + return (EINVAL); + if (new_wmax < seq->wmax) + return (EBUSY); + seq->wmax = new_wmax; + + /* Done */ + return (0); +} + +/* + * Reset sequence number state. + */ +static void +ng_l2tp_seq_reset(priv_p priv) +{ + struct l2tp_seq *const seq = &priv->seq; + hook_p hook; + int i; + + /* Sanity check */ + L2TP_SEQ_CHECK(seq); + + /* Stop timers */ + ng_uncallout(&seq->rack_timer, priv->node); + ng_uncallout(&seq->xack_timer, priv->node); + + /* Free retransmit queue */ + for (i = 0; i < L2TP_MAX_XWIN; i++) { + if (seq->xwin[i] == NULL) + break; + m_freem(seq->xwin[i]); + } + + /* Reset session hooks' sequence number states */ + NG_NODE_FOREACH_HOOK(priv->node, ng_l2tp_reset_session, NULL, hook); + + /* Reset node's sequence number state */ + seq->ns = 0; + seq->nr = 0; + seq->rack = 0; + seq->xack = 0; + seq->wmax = L2TP_MAX_XWIN; + seq->cwnd = 1; + seq->ssth = seq->wmax; + seq->acks = 0; + seq->rexmits = 0; + bzero(seq->xwin, sizeof(seq->xwin)); + + /* Done */ + L2TP_SEQ_CHECK(seq); +} + +/* + * Handle receipt of an acknowledgement value (Nr) from peer. + */ +static void +ng_l2tp_seq_recv_nr(priv_p priv, u_int16_t nr) +{ + struct l2tp_seq *const seq = &priv->seq; + struct mbuf *xwin[L2TP_MAX_XWIN]; /* partial local copy */ + int nack; + int i, j; + uint16_t ns; + + mtx_lock(&seq->mtx); + + /* Verify peer's ACK is in range */ + if ((nack = L2TP_SEQ_DIFF(nr, seq->rack)) <= 0) { + mtx_unlock(&seq->mtx); + return; /* duplicate ack */ + } + if (L2TP_SEQ_DIFF(nr, seq->ns) > 0) { + mtx_unlock(&seq->mtx); + priv->stats.recvBadAcks++; /* ack for packet not sent */ + return; + } + KASSERT(nack <= L2TP_MAX_XWIN, + ("%s: nack=%d > %d", __func__, nack, L2TP_MAX_XWIN)); + + /* Update receive ack stats */ + seq->rack = nr; + seq->rexmits = 0; + + /* Free acknowledged packets and shift up packets in the xmit queue */ + for (i = 0; i < nack; i++) + m_freem(seq->xwin[i]); + memmove(seq->xwin, seq->xwin + nack, + (L2TP_MAX_XWIN - nack) * sizeof(*seq->xwin)); + memset(seq->xwin + (L2TP_MAX_XWIN - nack), 0, + nack * sizeof(*seq->xwin)); + + /* + * Do slow-start/congestion avoidance windowing algorithm described + * in RFC 2661, Appendix A. Here we handle a multiple ACK as if each + * ACK had arrived separately. + */ + if (seq->cwnd < seq->wmax) { + + /* Handle slow start phase */ + if (seq->cwnd < seq->ssth) { + seq->cwnd += nack; + nack = 0; + if (seq->cwnd > seq->ssth) { /* into cg.av. phase */ + nack = seq->cwnd - seq->ssth; + seq->cwnd = seq->ssth; + } + } + + /* Handle congestion avoidance phase */ + if (seq->cwnd >= seq->ssth) { + seq->acks += nack; + while (seq->acks >= seq->cwnd) { + seq->acks -= seq->cwnd; + if (seq->cwnd < seq->wmax) + seq->cwnd++; + } + } + } + + /* Stop xmit timer */ + if (callout_active(&seq->rack_timer)) + ng_uncallout(&seq->rack_timer, priv->node); + + /* If transmit queue is empty, we're done for now */ + if (seq->xwin[0] == NULL) { + mtx_unlock(&seq->mtx); + return; + } + + /* Start restransmit timer again */ + ng_callout(&seq->rack_timer, priv->node, NULL, + hz, ng_l2tp_seq_rack_timeout, NULL, 0); + + /* + * Send more packets, trying to keep peer's receive window full. + * Make copy of everything we need before lock release. + */ + ns = seq->ns; + j = 0; + while ((i = L2TP_SEQ_DIFF(seq->ns, seq->rack)) < seq->cwnd + && seq->xwin[i] != NULL) { + xwin[j++] = seq->xwin[i]; + seq->ns++; + } + + mtx_unlock(&seq->mtx); + + /* + * Send prepared. + * If there is a memory error, pretend packet was sent, as it + * will get retransmitted later anyway. + */ + for (i = 0; i < j; i++) { + struct mbuf *m; + if ((m = L2TP_COPY_MBUF(xwin[i], M_DONTWAIT)) == NULL) + priv->stats.memoryFailures++; + else + ng_l2tp_xmit_ctrl(priv, m, ns); + ns++; + } +} + +/* + * Handle an ack timeout. We have an outstanding ack that we + * were hoping to piggy-back, but haven't, so send a ZLB. + */ +static void +ng_l2tp_seq_xack_timeout(node_p node, hook_p hook, void *arg1, int arg2) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct l2tp_seq *const seq = &priv->seq; + + /* Make sure callout is still active before doing anything */ + if (callout_pending(&seq->xack_timer) || + (!callout_active(&seq->xack_timer))) + return; + + /* Sanity check */ + L2TP_SEQ_CHECK(seq); + + /* Send a ZLB */ + ng_l2tp_xmit_ctrl(priv, NULL, seq->ns); + + /* callout_deactivate() is not needed here + as ng_uncallout() was called by ng_l2tp_xmit_ctrl() */ + + /* Sanity check */ + L2TP_SEQ_CHECK(seq); +} + +/* + * Handle a transmit timeout. The peer has failed to respond + * with an ack for our packet, so retransmit it. + */ +static void +ng_l2tp_seq_rack_timeout(node_p node, hook_p hook, void *arg1, int arg2) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct l2tp_seq *const seq = &priv->seq; + struct mbuf *m; + u_int delay; + + /* Make sure callout is still active before doing anything */ + if (callout_pending(&seq->rack_timer) || + (!callout_active(&seq->rack_timer))) + return; + + /* Sanity check */ + L2TP_SEQ_CHECK(seq); + + priv->stats.xmitRetransmits++; + + /* Have we reached the retransmit limit? If so, notify owner. */ + if (seq->rexmits++ >= priv->conf.rexmit_max) + ng_l2tp_seq_failure(priv); + + /* Restart timer, this time with an increased delay */ + delay = (seq->rexmits > 12) ? (1 << 12) : (1 << seq->rexmits); + if (delay > priv->conf.rexmit_max_to) + delay = priv->conf.rexmit_max_to; + ng_callout(&seq->rack_timer, node, NULL, + hz * delay, ng_l2tp_seq_rack_timeout, NULL, 0); + + /* Do slow-start/congestion algorithm windowing algorithm */ + seq->ns = seq->rack; + seq->ssth = (seq->cwnd + 1) / 2; + seq->cwnd = 1; + seq->acks = 0; + + /* Retransmit oldest unack'd packet */ + if ((m = L2TP_COPY_MBUF(seq->xwin[0], M_DONTWAIT)) == NULL) + priv->stats.memoryFailures++; + else + ng_l2tp_xmit_ctrl(priv, m, seq->ns++); + + /* callout_deactivate() is not needed here + as ng_callout() is getting called each time */ + + /* Sanity check */ + L2TP_SEQ_CHECK(seq); +} + +/* + * Transmit a control stream packet, payload optional. + * The transmit sequence number is not incremented. + */ +static int +ng_l2tp_xmit_ctrl(priv_p priv, struct mbuf *m, u_int16_t ns) +{ + struct l2tp_seq *const seq = &priv->seq; + u_int16_t session_id = 0; + int error; + + mtx_lock(&seq->mtx); + + /* Stop ack timer: we're sending an ack with this packet. + Doing this before to keep state predictable after error. */ + if (callout_active(&seq->xack_timer)) + ng_uncallout(&seq->xack_timer, priv->node); + + seq->xack = seq->nr; + + mtx_unlock(&seq->mtx); + + /* If no mbuf passed, send an empty packet (ZLB) */ + if (m == NULL) { + + /* Create a new mbuf for ZLB packet */ + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + priv->stats.memoryFailures++; + return (ENOBUFS); + } + m->m_len = m->m_pkthdr.len = 12; + m->m_pkthdr.rcvif = NULL; + priv->stats.xmitZLBs++; + } else { + + /* Strip off session ID */ + if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) { + priv->stats.memoryFailures++; + return (ENOBUFS); + } + memcpy(&session_id, mtod(m, u_int16_t *), 2); + m_adj(m, 2); + + /* Make room for L2TP header */ + M_PREPEND(m, 12, M_DONTWAIT); + if (m == NULL) { + priv->stats.memoryFailures++; + return (ENOBUFS); + } + } + + /* Fill in L2TP header */ + mtod(m, u_int16_t *)[0] = htons(L2TP_CTRL_HDR); + mtod(m, u_int16_t *)[1] = htons(m->m_pkthdr.len); + mtod(m, u_int16_t *)[2] = priv->conf.peer_id; + mtod(m, u_int16_t *)[3] = session_id; + mtod(m, u_int16_t *)[4] = htons(ns); + mtod(m, u_int16_t *)[5] = htons(seq->nr); + + /* Update sequence number info and stats */ + priv->stats.xmitPackets++; + priv->stats.xmitOctets += m->m_pkthdr.len; + + /* Send packet */ + NG_SEND_DATA_ONLY(error, priv->lower, m); + return (error); +} + +#ifdef INVARIANTS +/* + * Sanity check sequence number state. + */ +static void +ng_l2tp_seq_check(struct l2tp_seq *seq) +{ + int self_unack, peer_unack; + int i; + +#define CHECK(p) KASSERT((p), ("%s: not: %s", __func__, #p)) + + mtx_lock(&seq->mtx); + + self_unack = L2TP_SEQ_DIFF(seq->nr, seq->xack); + peer_unack = L2TP_SEQ_DIFF(seq->ns, seq->rack); + CHECK(seq->wmax <= L2TP_MAX_XWIN); + CHECK(seq->cwnd >= 1); + CHECK(seq->cwnd <= seq->wmax); + CHECK(seq->ssth >= 1); + CHECK(seq->ssth <= seq->wmax); + if (seq->cwnd < seq->ssth) + CHECK(seq->acks == 0); + else + CHECK(seq->acks <= seq->cwnd); + CHECK(self_unack >= 0); + CHECK(peer_unack >= 0); + CHECK(peer_unack <= seq->wmax); + CHECK((self_unack == 0) ^ callout_active(&seq->xack_timer)); + CHECK((peer_unack == 0) ^ callout_active(&seq->rack_timer)); + for (i = 0; i < peer_unack; i++) + CHECK(seq->xwin[i] != NULL); + for ( ; i < seq->cwnd; i++) /* verify peer's recv window full */ + CHECK(seq->xwin[i] == NULL); + + mtx_unlock(&seq->mtx); + +#undef CHECK +} +#endif /* INVARIANTS */ diff --git a/sys/netgraph7/ng_l2tp.h b/sys/netgraph7/ng_l2tp.h new file mode 100644 index 0000000000..b5f731cd87 --- /dev/null +++ b/sys/netgraph7/ng_l2tp.h @@ -0,0 +1,196 @@ +/*- + * Copyright (c) 2001-2002 Packet Design, LLC. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, + * use and redistribution of this software, in source or object code + * forms, with or without modifications are expressly permitted by + * Packet Design; provided, however, that: + * + * (i) Any and all reproductions of the source or object code + * must include the copyright notice above and the following + * disclaimer of warranties; and + * (ii) No rights are granted, in any manner or form, to use + * Packet Design trademarks, including the mark "PACKET DESIGN" + * on advertising, endorsements, or otherwise except as such + * appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING + * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, + * OR NON-INFRINGEMENT. PACKET DESIGN DOES NOT WARRANT, GUARANTEE, + * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS + * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, + * RELIABILITY OR OTHERWISE. IN NO EVENT SHALL PACKET DESIGN BE + * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE + * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL + * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF + * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF PACKET DESIGN IS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_l2tp.h,v 1.5 2005/01/07 01:45:39 imp Exp $ + */ + +#ifndef _NETGRAPH_NG_L2TP_H_ +#define _NETGRAPH_NG_L2TP_H_ + +/* Node type name and magic cookie */ +#define NG_L2TP_NODE_TYPE "l2tp" +#define NGM_L2TP_COOKIE 1091515793 + +/* Hook names */ +#define NG_L2TP_HOOK_CTRL "ctrl" /* control channel hook */ +#define NG_L2TP_HOOK_LOWER "lower" /* hook to lower layers */ + +/* Session hooks: prefix plus hex session ID, e.g., "session_3e14" */ +#define NG_L2TP_HOOK_SESSION_P "session_" /* session data hook (prefix) */ +#define NG_L2TP_HOOK_SESSION_F "session_%04x" /* session data hook (format) */ + +/* Set intial sequence numbers to not yet enabled node. */ +struct ng_l2tp_seq_config { + u_int16_t ns; /* sequence number to send next */ + u_int16_t nr; /* sequence number to be recved next */ + u_int16_t rack; /* last 'nr' received */ + u_int16_t xack; /* last 'nr' sent */ +}; + +/* Keep this in sync with the above structure definition. */ +#define NG_L2TP_SEQ_CONFIG_TYPE_INFO { \ + { "ns", &ng_parse_uint16_type }, \ + { "nr", &ng_parse_uint16_type }, \ + { NULL } \ +} + +/* Configuration for a node */ +struct ng_l2tp_config { + u_char enabled; /* enables traffic flow */ + u_char match_id; /* tunnel id must match 'tunnel_id' */ + u_int16_t tunnel_id; /* local tunnel id */ + u_int16_t peer_id; /* peer's tunnel id */ + u_int16_t peer_win; /* peer's max recv window size */ + u_int16_t rexmit_max; /* max retransmits before failure */ + u_int16_t rexmit_max_to; /* max delay between retransmits */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_L2TP_CONFIG_TYPE_INFO { \ + { "enabled", &ng_parse_uint8_type }, \ + { "match_id", &ng_parse_uint8_type }, \ + { "tunnel_id", &ng_parse_hint16_type }, \ + { "peer_id", &ng_parse_hint16_type }, \ + { "peer_win", &ng_parse_uint16_type }, \ + { "rexmit_max", &ng_parse_uint16_type }, \ + { "rexmit_max_to", &ng_parse_uint16_type }, \ + { NULL } \ +} + +/* Configuration for a session hook */ +struct ng_l2tp_sess_config { + u_int16_t session_id; /* local session id */ + u_int16_t peer_id; /* peer's session id */ + u_char control_dseq; /* whether we control data sequencing */ + u_char enable_dseq; /* whether to enable data sequencing */ + u_char include_length; /* whether to include length field */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_L2TP_SESS_CONFIG_TYPE_INFO { \ + { "session_id", &ng_parse_hint16_type }, \ + { "peer_id", &ng_parse_hint16_type }, \ + { "control_dseq", &ng_parse_uint8_type }, \ + { "enable_dseq", &ng_parse_uint8_type }, \ + { "include_length", &ng_parse_uint8_type }, \ + { NULL } \ +} + +/* Statistics struct */ +struct ng_l2tp_stats { + u_int32_t xmitPackets; /* number of packets xmit */ + u_int32_t xmitOctets; /* number of octets xmit */ + u_int32_t xmitZLBs; /* ack-only packets transmitted */ + u_int32_t xmitDrops; /* xmits dropped due to full window */ + u_int32_t xmitTooBig; /* ctrl pkts dropped because too big */ + u_int32_t xmitInvalid; /* ctrl packets with no session ID */ + u_int32_t xmitDataTooBig; /* data pkts dropped because too big */ + u_int32_t xmitRetransmits; /* retransmitted packets */ + u_int32_t recvPackets; /* number of packets rec'd */ + u_int32_t recvOctets; /* number of octets rec'd */ + u_int32_t recvRunts; /* too short packets rec'd */ + u_int32_t recvInvalid; /* invalid packets rec'd */ + u_int32_t recvWrongTunnel; /* packets rec'd with wrong tunnel id */ + u_int32_t recvUnknownSID; /* pkts rec'd with unknown session id */ + u_int32_t recvBadAcks; /* ctrl pkts rec'd with invalid 'nr' */ + u_int32_t recvOutOfOrder; /* out of order ctrl pkts rec'd */ + u_int32_t recvDuplicates; /* duplicate ctrl pkts rec'd */ + u_int32_t recvDataDrops; /* dup/out of order data pkts rec'd */ + u_int32_t recvZLBs; /* ack-only packets rec'd */ + u_int32_t memoryFailures; /* times we couldn't allocate memory */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_L2TP_STATS_TYPE_INFO { \ + { "xmitPackets", &ng_parse_uint32_type }, \ + { "xmitOctets", &ng_parse_uint32_type }, \ + { "xmitZLBs", &ng_parse_uint32_type }, \ + { "xmitDrops", &ng_parse_uint32_type }, \ + { "xmitTooBig", &ng_parse_uint32_type }, \ + { "xmitInvalid", &ng_parse_uint32_type }, \ + { "xmitDataTooBig", &ng_parse_uint32_type }, \ + { "xmitRetransmits", &ng_parse_uint32_type }, \ + { "recvPackets", &ng_parse_uint32_type }, \ + { "recvOctets", &ng_parse_uint32_type }, \ + { "recvRunts", &ng_parse_uint32_type }, \ + { "recvInvalid", &ng_parse_uint32_type }, \ + { "recvWrongTunnel", &ng_parse_uint32_type }, \ + { "recvUnknownSID", &ng_parse_uint32_type }, \ + { "recvBadAcks", &ng_parse_uint32_type }, \ + { "recvOutOfOrder", &ng_parse_uint32_type }, \ + { "recvDuplicates", &ng_parse_uint32_type }, \ + { "recvDataDrops", &ng_parse_uint32_type }, \ + { "recvZLBs", &ng_parse_uint32_type }, \ + { "memoryFailures", &ng_parse_uint32_type }, \ + { NULL } \ +} + +/* Session statistics struct. */ +struct ng_l2tp_session_stats { + u_int64_t xmitPackets; /* number of packets xmit */ + u_int64_t xmitOctets; /* number of octets xmit */ + u_int64_t recvPackets; /* number of packets received */ + u_int64_t recvOctets; /* number of octets received */ +}; + +/* Keep this in sync with the above structure definition. */ +#define NG_L2TP_SESSION_STATS_TYPE_INFO { \ + { "xmitPackets", &ng_parse_uint64_type }, \ + { "xmitOctets", &ng_parse_uint64_type }, \ + { "recvPackets", &ng_parse_uint64_type }, \ + { "recvOctets", &ng_parse_uint64_type }, \ + { NULL } \ +} + +/* Netgraph commands */ +enum { + NGM_L2TP_SET_CONFIG = 1, /* supply a struct ng_l2tp_config */ + NGM_L2TP_GET_CONFIG, /* returns a struct ng_l2tp_config */ + NGM_L2TP_SET_SESS_CONFIG, /* supply struct ng_l2tp_sess_config */ + NGM_L2TP_GET_SESS_CONFIG, /* supply a session id (u_int16_t) */ + NGM_L2TP_GET_STATS, /* returns struct ng_l2tp_stats */ + NGM_L2TP_CLR_STATS, /* clears stats */ + NGM_L2TP_GETCLR_STATS, /* returns & clears stats */ + NGM_L2TP_GET_SESSION_STATS, /* returns session stats */ + NGM_L2TP_CLR_SESSION_STATS, /* clears session stats */ + NGM_L2TP_GETCLR_SESSION_STATS, /* returns & clears session stats */ + NGM_L2TP_ACK_FAILURE, /* sent *from* node after ack timeout */ + NGM_L2TP_SET_SEQ /* supply a struct ng_l2tp_seq_config */ +}; + +#endif /* _NETGRAPH_NG_L2TP_H_ */ diff --git a/sys/netgraph7/ng_lmi.c b/sys/netgraph7/ng_lmi.c new file mode 100644 index 0000000000..26ef9c8187 --- /dev/null +++ b/sys/netgraph7/ng_lmi.c @@ -0,0 +1,1082 @@ +/* + * ng_lmi.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_lmi.c,v 1.25 2006/01/14 14:17:27 glebius Exp $ + * $Whistle: ng_lmi.c,v 1.38 1999/11/01 09:24:52 julian Exp $ + */ + +/* + * This node performs the frame relay LMI protocol. It knows how + * to do ITU Annex A, ANSI Annex D, and "Group-of-Four" variants + * of the protocol. + * + * A specific protocol can be forced by connecting the corresponding + * hook to DLCI 0 or 1023 (as appropriate) of a frame relay link. + * + * Alternately, this node can do auto-detection of the LMI protocol + * by connecting hook "auto0" to DLCI 0 and "auto1023" to DLCI 1023. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Human readable names for LMI + */ +#define NAME_ANNEXA NG_LMI_HOOK_ANNEXA +#define NAME_ANNEXD NG_LMI_HOOK_ANNEXD +#define NAME_GROUP4 NG_LMI_HOOK_GROUPOF4 +#define NAME_NONE "None" + +#define MAX_DLCIS 128 +#define MAXDLCI 1023 + +/* + * DLCI states + */ +#define DLCI_NULL 0 +#define DLCI_UP 1 +#define DLCI_DOWN 2 + +/* + * Any received LMI frame should be at least this long + */ +#define LMI_MIN_LENGTH 8 /* XXX verify */ + +/* + * Netgraph node methods and type descriptor + */ +static ng_constructor_t nglmi_constructor; +static ng_rcvmsg_t nglmi_rcvmsg; +static ng_shutdown_t nglmi_shutdown; +static ng_newhook_t nglmi_newhook; +static ng_rcvdata_t nglmi_rcvdata; +static ng_disconnect_t nglmi_disconnect; +static int nglmi_checkdata(hook_p hook, struct mbuf *m); + +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_LMI_NODE_TYPE, + .constructor = nglmi_constructor, + .rcvmsg = nglmi_rcvmsg, + .shutdown = nglmi_shutdown, + .newhook = nglmi_newhook, + .rcvdata = nglmi_rcvdata, + .disconnect = nglmi_disconnect, +}; +NETGRAPH_INIT(lmi, &typestruct); + +/* + * Info and status per node + */ +struct nglmi_softc { + node_p node; /* netgraph node */ + int flags; /* state */ + int poll_count; /* the count of times for autolmi */ + int poll_state; /* state of auto detect machine */ + u_char remote_seq; /* sequence number the remote sent */ + u_char local_seq; /* last sequence number we sent */ + u_char protoID; /* 9 for group of 4, 8 otherwise */ + u_long seq_retries; /* sent this how many time so far */ + struct callout handle; /* see timeout(9) */ + int liv_per_full; + int liv_rate; + int livs; + int need_full; + hook_p lmi_channel; /* whatever we ended up using */ + hook_p lmi_annexA; + hook_p lmi_annexD; + hook_p lmi_group4; + hook_p lmi_channel0; /* auto-detect on DLCI 0 */ + hook_p lmi_channel1023;/* auto-detect on DLCI 1023 */ + char *protoname; /* cache protocol name */ + u_char dlci_state[MAXDLCI + 1]; + int invalidx; /* next dlci's to invalidate */ +}; +typedef struct nglmi_softc *sc_p; + +/* + * Other internal functions + */ +static void LMI_ticker(node_p node, hook_p hook, void *arg1, int arg2); +static void nglmi_startup_fixed(sc_p sc, hook_p hook); +static void nglmi_startup_auto(sc_p sc); +static void nglmi_startup(sc_p sc); +static void nglmi_inquire(sc_p sc, int full); +static void ngauto_state_machine(sc_p sc); + +/* + * Values for 'flags' field + * NB: the SCF_CONNECTED flag is set if and only if the timer is running. + */ +#define SCF_CONNECTED 0x01 /* connected to something */ +#define SCF_AUTO 0x02 /* we are auto-detecting */ +#define SCF_FIXED 0x04 /* we are fixed from the start */ + +#define SCF_LMITYPE 0x18 /* mask for determining Annex mode */ +#define SCF_NOLMI 0x00 /* no LMI type selected yet */ +#define SCF_ANNEX_A 0x08 /* running annex A mode */ +#define SCF_ANNEX_D 0x10 /* running annex D mode */ +#define SCF_GROUP4 0x18 /* running group of 4 */ + +#define SETLMITYPE(sc, annex) \ +do { \ + (sc)->flags &= ~SCF_LMITYPE; \ + (sc)->flags |= (annex); \ +} while (0) + +#define NOPROTO(sc) (((sc)->flags & SCF_LMITYPE) == SCF_NOLMI) +#define ANNEXA(sc) (((sc)->flags & SCF_LMITYPE) == SCF_ANNEX_A) +#define ANNEXD(sc) (((sc)->flags & SCF_LMITYPE) == SCF_ANNEX_D) +#define GROUP4(sc) (((sc)->flags & SCF_LMITYPE) == SCF_GROUP4) + +#define LMIPOLLSIZE 3 +#define LMI_PATIENCE 8 /* declare all DLCI DOWN after N LMI failures */ + +/* + * Node constructor + */ +static int +nglmi_constructor(node_p node) +{ + sc_p sc; + + MALLOC(sc, sc_p, sizeof(*sc), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (sc == NULL) + return (ENOMEM); + + NG_NODE_SET_PRIVATE(node, sc); + sc->node = node; + + ng_callout_init(&sc->handle); + sc->protoname = NAME_NONE; + sc->liv_per_full = NG_LMI_SEQ_PER_FULL; /* make this dynamic */ + sc->liv_rate = NG_LMI_KEEPALIVE_RATE; + return (0); +} + +/* + * The LMI channel has a private pointer which is the same as the + * node private pointer. The debug channel has a NULL private pointer. + */ +static int +nglmi_newhook(node_p node, hook_p hook, const char *name) +{ + sc_p sc = NG_NODE_PRIVATE(node); + + if (strcmp(name, NG_LMI_HOOK_DEBUG) == 0) { + NG_HOOK_SET_PRIVATE(hook, NULL); + return (0); + } + if (sc->flags & SCF_CONNECTED) { + /* already connected, return an error */ + return (EINVAL); + } + if (strcmp(name, NG_LMI_HOOK_ANNEXA) == 0) { + sc->lmi_annexA = hook; + NG_HOOK_SET_PRIVATE(hook, NG_NODE_PRIVATE(node)); + sc->protoID = 8; + SETLMITYPE(sc, SCF_ANNEX_A); + sc->protoname = NAME_ANNEXA; + nglmi_startup_fixed(sc, hook); + } else if (strcmp(name, NG_LMI_HOOK_ANNEXD) == 0) { + sc->lmi_annexD = hook; + NG_HOOK_SET_PRIVATE(hook, NG_NODE_PRIVATE(node)); + sc->protoID = 8; + SETLMITYPE(sc, SCF_ANNEX_D); + sc->protoname = NAME_ANNEXD; + nglmi_startup_fixed(sc, hook); + } else if (strcmp(name, NG_LMI_HOOK_GROUPOF4) == 0) { + sc->lmi_group4 = hook; + NG_HOOK_SET_PRIVATE(hook, NG_NODE_PRIVATE(node)); + sc->protoID = 9; + SETLMITYPE(sc, SCF_GROUP4); + sc->protoname = NAME_GROUP4; + nglmi_startup_fixed(sc, hook); + } else if (strcmp(name, NG_LMI_HOOK_AUTO0) == 0) { + /* Note this, and if B is already installed, we're complete */ + sc->lmi_channel0 = hook; + sc->protoname = NAME_NONE; + NG_HOOK_SET_PRIVATE(hook, NG_NODE_PRIVATE(node)); + if (sc->lmi_channel1023) + nglmi_startup_auto(sc); + } else if (strcmp(name, NG_LMI_HOOK_AUTO1023) == 0) { + /* Note this, and if A is already installed, we're complete */ + sc->lmi_channel1023 = hook; + sc->protoname = NAME_NONE; + NG_HOOK_SET_PRIVATE(hook, NG_NODE_PRIVATE(node)); + if (sc->lmi_channel0) + nglmi_startup_auto(sc); + } else + return (EINVAL); /* unknown hook */ + return (0); +} + +/* + * We have just attached to a live (we hope) node. + * Fire out a LMI inquiry, and then start up the timers. + */ +static void +LMI_ticker(node_p node, hook_p hook, void *arg1, int arg2) +{ + sc_p sc = NG_NODE_PRIVATE(node); + + if (sc->flags & SCF_AUTO) { + ngauto_state_machine(sc); + ng_callout(&sc->handle, node, NULL, NG_LMI_POLL_RATE * hz, + LMI_ticker, NULL, 0); + } else { + if (sc->livs++ >= sc->liv_per_full) { + nglmi_inquire(sc, 1); + /* sc->livs = 0; *//* do this when we get the answer! */ + } else { + nglmi_inquire(sc, 0); + } + ng_callout(&sc->handle, node, NULL, sc->liv_rate * hz, + LMI_ticker, NULL, 0); + } +} + +static void +nglmi_startup_fixed(sc_p sc, hook_p hook) +{ + sc->flags |= (SCF_FIXED | SCF_CONNECTED); + sc->lmi_channel = hook; + nglmi_startup(sc); +} + +static void +nglmi_startup_auto(sc_p sc) +{ + sc->flags |= (SCF_AUTO | SCF_CONNECTED); + sc->poll_state = 0; /* reset state machine */ + sc->poll_count = 0; + nglmi_startup(sc); +} + +static void +nglmi_startup(sc_p sc) +{ + sc->remote_seq = 0; + sc->local_seq = 1; + sc->seq_retries = 0; + sc->livs = sc->liv_per_full - 1; + /* start off the ticker in 1 sec */ + ng_callout(&sc->handle, sc->node, NULL, hz, LMI_ticker, NULL, 0); +} + +static void +nglmi_inquire(sc_p sc, int full) +{ + struct mbuf *m; + struct ng_tag_prio *ptag; + char *cptr, *start; + int error; + + if (sc->lmi_channel == NULL) + return; + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + log(LOG_ERR, "nglmi: unable to start up LMI processing\n"); + return; + } + m->m_pkthdr.rcvif = NULL; + + /* Attach a tag to packet, marking it of link level state priority, so + * that device driver would put it in the beginning of queue */ + + ptag = (struct ng_tag_prio *)m_tag_alloc(NGM_GENERIC_COOKIE, NG_TAG_PRIO, + (sizeof(struct ng_tag_prio) - sizeof(struct m_tag)), M_NOWAIT); + if (ptag != NULL) { /* if it failed, well, it was optional anyhow */ + ptag->priority = NG_PRIO_LINKSTATE; + ptag->discardability = -1; + m_tag_prepend(m, &ptag->tag); + } + + m->m_data += 4; /* leave some room for a header */ + cptr = start = mtod(m, char *); + /* add in the header for an LMI inquiry. */ + *cptr++ = 0x03; /* UI frame */ + if (GROUP4(sc)) + *cptr++ = 0x09; /* proto discriminator */ + else + *cptr++ = 0x08; /* proto discriminator */ + *cptr++ = 0x00; /* call reference */ + *cptr++ = 0x75; /* inquiry */ + + /* If we are Annex-D, add locking shift to codeset 5. */ + if (ANNEXD(sc)) + *cptr++ = 0x95; /* locking shift */ + /* Add a request type */ + if (ANNEXA(sc)) + *cptr++ = 0x51; /* report type */ + else + *cptr++ = 0x01; /* report type */ + *cptr++ = 0x01; /* size = 1 */ + if (full) + *cptr++ = 0x00; /* full */ + else + *cptr++ = 0x01; /* partial */ + + /* Add a link verification IE */ + if (ANNEXA(sc)) + *cptr++ = 0x53; /* verification IE */ + else + *cptr++ = 0x03; /* verification IE */ + *cptr++ = 0x02; /* 2 extra bytes */ + *cptr++ = sc->local_seq; + *cptr++ = sc->remote_seq; + sc->seq_retries++; + + /* Send it */ + m->m_len = m->m_pkthdr.len = cptr - start; + NG_SEND_DATA_ONLY(error, sc->lmi_channel, m); + + /* If we've been sending requests for long enough, and there has + * been no response, then mark as DOWN, any DLCIs that are UP. */ + if (sc->seq_retries == LMI_PATIENCE) { + int count; + + for (count = 0; count < MAXDLCI; count++) + if (sc->dlci_state[count] == DLCI_UP) + sc->dlci_state[count] = DLCI_DOWN; + } +} + +/* + * State machine for LMI auto-detect. The transitions are ordered + * to try the more likely possibilities first. + */ +static void +ngauto_state_machine(sc_p sc) +{ + if ((sc->poll_count <= 0) || (sc->poll_count > LMIPOLLSIZE)) { + /* time to change states in the auto probe machine */ + /* capture wild values of poll_count while we are at it */ + sc->poll_count = LMIPOLLSIZE; + sc->poll_state++; + } + switch (sc->poll_state) { + case 7: + log(LOG_WARNING, "nglmi: no response from exchange\n"); + default: /* capture bad states */ + sc->poll_state = 1; + case 1: + sc->lmi_channel = sc->lmi_channel0; + SETLMITYPE(sc, SCF_ANNEX_D); + break; + case 2: + sc->lmi_channel = sc->lmi_channel1023; + SETLMITYPE(sc, SCF_ANNEX_D); + break; + case 3: + sc->lmi_channel = sc->lmi_channel0; + SETLMITYPE(sc, SCF_ANNEX_A); + break; + case 4: + sc->lmi_channel = sc->lmi_channel1023; + SETLMITYPE(sc, SCF_GROUP4); + break; + case 5: + sc->lmi_channel = sc->lmi_channel1023; + SETLMITYPE(sc, SCF_ANNEX_A); + break; + case 6: + sc->lmi_channel = sc->lmi_channel0; + SETLMITYPE(sc, SCF_GROUP4); + break; + } + + /* send an inquirey encoded appropriatly */ + nglmi_inquire(sc, 0); + sc->poll_count--; +} + +/* + * Receive a netgraph control message. + */ +static int +nglmi_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + sc_p sc = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_GENERIC_COOKIE: + switch (msg->header.cmd) { + case NGM_TEXT_STATUS: + { + char *arg; + int pos, count; + + NG_MKRESPONSE(resp, msg, NG_TEXTRESPONSE, M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + arg = resp->data; + pos = sprintf(arg, "protocol %s ", sc->protoname); + if (sc->flags & SCF_FIXED) + pos += sprintf(arg + pos, "fixed\n"); + else if (sc->flags & SCF_AUTO) + pos += sprintf(arg + pos, "auto-detecting\n"); + else + pos += sprintf(arg + pos, "auto on dlci %d\n", + (sc->lmi_channel == sc->lmi_channel0) ? + 0 : 1023); + pos += sprintf(arg + pos, + "keepalive period: %d seconds\n", sc->liv_rate); + pos += sprintf(arg + pos, + "unacknowledged keepalives: %ld\n", + sc->seq_retries); + for (count = 0; + ((count <= MAXDLCI) + && (pos < (NG_TEXTRESPONSE - 20))); + count++) { + if (sc->dlci_state[count]) { + pos += sprintf(arg + pos, + "dlci %d %s\n", count, + (sc->dlci_state[count] + == DLCI_UP) ? "up" : "down"); + } + } + resp->header.arglen = pos + 1; + break; + } + default: + error = EINVAL; + break; + } + break; + case NGM_LMI_COOKIE: + switch (msg->header.cmd) { + case NGM_LMI_GET_STATUS: + { + struct nglmistat *stat; + int k; + + NG_MKRESPONSE(resp, msg, sizeof(*stat), M_NOWAIT); + if (!resp) { + error = ENOMEM; + break; + } + stat = (struct nglmistat *) resp->data; + strncpy(stat->proto, + sc->protoname, sizeof(stat->proto) - 1); + strncpy(stat->hook, + sc->protoname, sizeof(stat->hook) - 1); + stat->autod = !!(sc->flags & SCF_AUTO); + stat->fixed = !!(sc->flags & SCF_FIXED); + for (k = 0; k <= MAXDLCI; k++) { + switch (sc->dlci_state[k]) { + case DLCI_UP: + stat->up[k / 8] |= (1 << (k % 8)); + /* fall through */ + case DLCI_DOWN: + stat->seen[k / 8] |= (1 << (k % 8)); + break; + } + } + break; + } + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } + + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +#define STEPBY(stepsize) \ + do { \ + packetlen -= (stepsize); \ + data += (stepsize); \ + } while (0) + +/* + * receive data, and use it to update our status. + * Anything coming in on the debug port is discarded. + */ +static int +nglmi_rcvdata(hook_p hook, item_p item) +{ + sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + const u_char *data; + unsigned short dlci; + u_short packetlen; + int resptype_seen = 0; + struct mbuf *m; + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + if (NG_HOOK_PRIVATE(hook) == NULL) { + goto drop; + } + packetlen = m->m_len; + + /* XXX what if it's more than 1 mbuf? */ + if ((packetlen > MHLEN) && !(m->m_flags & M_EXT)) { + log(LOG_WARNING, "nglmi: packetlen (%d) too big\n", packetlen); + goto drop; + } + if (m->m_len < packetlen && (m = m_pullup(m, packetlen)) == NULL) { + log(LOG_WARNING, + "nglmi: m_pullup failed for %d bytes\n", packetlen); + return (0); + } + if (nglmi_checkdata(hook, m) == 0) + return (0); + + /* pass the first 4 bytes (already checked in the nglmi_checkdata()) */ + data = mtod(m, const u_char *); + STEPBY(4); + + /* Now check if there is a 'locking shift'. This is only seen in + * Annex D frames. don't bother checking, we already did that. Don't + * increment immediatly as it might not be there. */ + if (ANNEXD(sc)) + STEPBY(1); + + /* If we get this far we should consider that it is a legitimate + * frame and we know what it is. */ + if (sc->flags & SCF_AUTO) { + /* note the hook that this valid channel came from and drop + * out of auto probe mode. */ + if (ANNEXA(sc)) + sc->protoname = NAME_ANNEXA; + else if (ANNEXD(sc)) + sc->protoname = NAME_ANNEXD; + else if (GROUP4(sc)) + sc->protoname = NAME_GROUP4; + else { + log(LOG_ERR, "nglmi: No known type\n"); + goto drop; + } + sc->lmi_channel = hook; + sc->flags &= ~SCF_AUTO; + log(LOG_INFO, "nglmi: auto-detected %s LMI on DLCI %d\n", + sc->protoname, hook == sc->lmi_channel0 ? 0 : 1023); + } + + /* While there is more data in the status packet, keep processing + * status items. First make sure there is enough data for the + * segment descriptor's length field. */ + while (packetlen >= 2) { + u_int segtype = data[0]; + u_int segsize = data[1]; + + /* Now that we know how long it claims to be, make sure + * there is enough data for the next seg. */ + if (packetlen < segsize + 2) + break; + switch (segtype) { + case 0x01: + case 0x51: + if (resptype_seen) { + log(LOG_WARNING, "nglmi: dup MSGTYPE\n"); + goto nextIE; + } + resptype_seen++; + /* The remote end tells us what kind of response + * this is. Only expect a type 0 or 1. if we are a + * full status, invalidate a few DLCIs just to see + * that they are still ok. */ + if (segsize != 1) + goto nextIE; + switch (data[2]) { + case 1: + /* partial status, do no extra processing */ + break; + case 0: + { + int count = 0; + int idx = sc->invalidx; + + for (count = 0; count < 10; count++) { + if (idx > MAXDLCI) + idx = 0; + if (sc->dlci_state[idx] == DLCI_UP) + sc->dlci_state[idx] = DLCI_DOWN; + idx++; + } + sc->invalidx = idx; + /* we got and we wanted one. relax + * now.. but don't reset to 0 if it + * was unrequested. */ + if (sc->livs > sc->liv_per_full) + sc->livs = 0; + break; + } + } + break; + case 0x03: + case 0x53: + /* The remote tells us what it thinks the sequence + * numbers are. If it's not size 2, it must be a + * duplicate to have gotten this far, skip it. */ + if (segsize != 2) + goto nextIE; + sc->remote_seq = data[2]; + if (sc->local_seq == data[3]) { + sc->local_seq++; + sc->seq_retries = 0; + /* Note that all 3 Frame protocols seem to + * not like 0 as a sequence number. */ + if (sc->local_seq == 0) + sc->local_seq = 1; + } + break; + case 0x07: + case 0x57: + /* The remote tells us about a DLCI that it knows + * about. There may be many of these in a single + * status response */ + switch (segsize) { + case 6:/* only on 'group of 4' */ + dlci = ((u_short) data[2] & 0xff) << 8; + dlci |= (data[3] & 0xff); + if ((dlci < 1024) && (dlci > 0)) { + /* XXX */ + } + break; + case 3: + dlci = ((u_short) data[2] & 0x3f) << 4; + dlci |= ((data[3] & 0x78) >> 3); + if ((dlci < 1024) && (dlci > 0)) { + /* set up the bottom half of the + * support for that dlci if it's not + * already been done */ + /* store this information somewhere */ + } + break; + default: + goto nextIE; + } + if (sc->dlci_state[dlci] != DLCI_UP) { + /* bring new DLCI to life */ + /* may do more here some day */ + if (sc->dlci_state[dlci] != DLCI_DOWN) + log(LOG_INFO, + "nglmi: DLCI %d became active\n", + dlci); + sc->dlci_state[dlci] = DLCI_UP; + } + break; + } +nextIE: + STEPBY(segsize + 2); + } + NG_FREE_M(m); + return (0); + +drop: + NG_FREE_M(m); + return (EINVAL); +} + +/* + * Check that a packet is entirely kosha. + * return 1 of ok, and 0 if not. + * All data is discarded if a 0 is returned. + */ +static int +nglmi_checkdata(hook_p hook, struct mbuf *m) +{ + sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + const u_char *data; + u_short packetlen; + unsigned short dlci; + u_char type; + u_char nextbyte; + int seq_seen = 0; + int resptype_seen = 0; /* 0 , 1 (partial) or 2 (full) */ + int highest_dlci = 0; + + packetlen = m->m_len; + data = mtod(m, const u_char *); + if (*data != 0x03) { + log(LOG_WARNING, "nglmi: unexpected value in LMI(%d)\n", 1); + goto reject; + } + STEPBY(1); + + /* look at the protocol ID */ + nextbyte = *data; + if (sc->flags & SCF_AUTO) { + SETLMITYPE(sc, SCF_NOLMI); /* start with a clean slate */ + switch (nextbyte) { + case 0x8: + sc->protoID = 8; + break; + case 0x9: + SETLMITYPE(sc, SCF_GROUP4); + sc->protoID = 9; + break; + default: + log(LOG_WARNING, "nglmi: bad Protocol ID(%d)\n", + (int) nextbyte); + goto reject; + } + } else { + if (nextbyte != sc->protoID) { + log(LOG_WARNING, "nglmi: unexpected Protocol ID(%d)\n", + (int) nextbyte); + goto reject; + } + } + STEPBY(1); + + /* check call reference (always null in non ISDN frame relay) */ + if (*data != 0x00) { + log(LOG_WARNING, "nglmi: unexpected Call Reference (0x%x)\n", + data[-1]); + goto reject; + } + STEPBY(1); + + /* check message type */ + switch ((type = *data)) { + case 0x75: /* Status enquiry */ + log(LOG_WARNING, "nglmi: unexpected message type(0x%x)\n", + data[-1]); + goto reject; + case 0x7D: /* Status message */ + break; + default: + log(LOG_WARNING, + "nglmi: unexpected msg type(0x%x) \n", (int) type); + goto reject; + } + STEPBY(1); + + /* Now check if there is a 'locking shift'. This is only seen in + * Annex D frames. Don't increment immediately as it might not be + * there. */ + nextbyte = *data; + if (sc->flags & SCF_AUTO) { + if (!(GROUP4(sc))) { + if (nextbyte == 0x95) { + SETLMITYPE(sc, SCF_ANNEX_D); + STEPBY(1); + } else + SETLMITYPE(sc, SCF_ANNEX_A); + } else if (nextbyte == 0x95) { + log(LOG_WARNING, "nglmi: locking shift seen in G4\n"); + goto reject; + } + } else { + if (ANNEXD(sc)) { + if (*data == 0x95) + STEPBY(1); + else { + log(LOG_WARNING, + "nglmi: locking shift missing\n"); + goto reject; + } + } else if (*data == 0x95) { + log(LOG_WARNING, "nglmi: locking shift seen\n"); + goto reject; + } + } + + /* While there is more data in the status packet, keep processing + * status items. First make sure there is enough data for the + * segment descriptor's length field. */ + while (packetlen >= 2) { + u_int segtype = data[0]; + u_int segsize = data[1]; + + /* Now that we know how long it claims to be, make sure + * there is enough data for the next seg. */ + if (packetlen < (segsize + 2)) { + log(LOG_WARNING, "nglmi: IE longer than packet\n"); + break; + } + switch (segtype) { + case 0x01: + case 0x51: + /* According to MCI's HP analyser, we should just + * ignore if there is mor ethan one of these (?). */ + if (resptype_seen) { + log(LOG_WARNING, "nglmi: dup MSGTYPE\n"); + goto nextIE; + } + if (segsize != 1) { + log(LOG_WARNING, "nglmi: MSGTYPE wrong size\n"); + goto reject; + } + /* The remote end tells us what kind of response + * this is. Only expect a type 0 or 1. if it was a + * full (type 0) check we just asked for a type + * full. */ + switch (data[2]) { + case 1:/* partial */ + if (sc->livs > sc->liv_per_full) { + log(LOG_WARNING, + "nglmi: LIV when FULL expected\n"); + goto reject; /* need full */ + } + resptype_seen = 1; + break; + case 0:/* full */ + /* Full response is always acceptable */ + resptype_seen = 2; + break; + default: + log(LOG_WARNING, + "nglmi: Unknown report type %d\n", data[2]); + goto reject; + } + break; + case 0x03: + case 0x53: + /* The remote tells us what it thinks the sequence + * numbers are. I would have thought that there + * needs to be one and only one of these, but MCI + * want us to just ignore extras. (?) */ + if (resptype_seen == 0) { + log(LOG_WARNING, "nglmi: no TYPE before SEQ\n"); + goto reject; + } + if (seq_seen != 0) /* already seen seq numbers */ + goto nextIE; + if (segsize != 2) { + log(LOG_WARNING, "nglmi: bad SEQ sts size\n"); + goto reject; + } + if (sc->local_seq != data[3]) { + log(LOG_WARNING, "nglmi: unexpected SEQ\n"); + goto reject; + } + seq_seen = 1; + break; + case 0x07: + case 0x57: + /* The remote tells us about a DLCI that it knows + * about. There may be many of these in a single + * status response */ + if (seq_seen != 1) { /* already seen seq numbers? */ + log(LOG_WARNING, + "nglmi: No sequence before DLCI\n"); + goto reject; + } + if (resptype_seen != 2) { /* must be full */ + log(LOG_WARNING, + "nglmi: No resp type before DLCI\n"); + goto reject; + } + if (GROUP4(sc)) { + if (segsize != 6) { + log(LOG_WARNING, + "nglmi: wrong IE segsize\n"); + goto reject; + } + dlci = ((u_short) data[2] & 0xff) << 8; + dlci |= (data[3] & 0xff); + } else { + if (segsize != 3) { + log(LOG_WARNING, + "nglmi: DLCI headersize of %d" + " not supported\n", segsize - 1); + goto reject; + } + dlci = ((u_short) data[2] & 0x3f) << 4; + dlci |= ((data[3] & 0x78) >> 3); + } + /* async can only have one of these */ +#if 0 /* async not yet accepted */ + if (async && highest_dlci) { + log(LOG_WARNING, + "nglmi: Async with > 1 DLCI\n"); + goto reject; + } +#endif + /* Annex D says these will always be Ascending, but + * the HP test for G4 says we should accept + * duplicates, so for now allow that. ( <= vs. < ) */ +#if 0 + /* MCI tests want us to accept out of order for AnxD */ + if ((!GROUP4(sc)) && (dlci < highest_dlci)) { + /* duplicate or mis-ordered dlci */ + /* (spec says they will increase in number) */ + log(LOG_WARNING, "nglmi: DLCI out of order\n"); + goto reject; + } +#endif + if (dlci > 1023) { + log(LOG_WARNING, "nglmi: DLCI out of range\n"); + goto reject; + } + highest_dlci = dlci; + break; + default: + log(LOG_WARNING, + "nglmi: unknown LMI segment type %d\n", segtype); + } +nextIE: + STEPBY(segsize + 2); + } + if (packetlen != 0) { /* partial junk at end? */ + log(LOG_WARNING, + "nglmi: %d bytes extra at end of packet\n", packetlen); + goto print; + } + if (resptype_seen == 0) { + log(LOG_WARNING, "nglmi: No response type seen\n"); + goto reject; /* had no response type */ + } + if (seq_seen == 0) { + log(LOG_WARNING, "nglmi: No sequence numbers seen\n"); + goto reject; /* had no sequence numbers */ + } + return (1); + +print: + { + int i, j, k, pos; + char buf[100]; + int loc; + const u_char *bp = mtod(m, const u_char *); + + k = i = 0; + loc = (m->m_len - packetlen); + log(LOG_WARNING, "nglmi: error at location %d\n", loc); + while (k < m->m_len) { + pos = 0; + j = 0; + while ((j++ < 16) && k < m->m_len) { + pos += sprintf(buf + pos, "%c%02x", + ((loc == k) ? '>' : ' '), + bp[k]); + k++; + } + if (i == 0) + log(LOG_WARNING, "nglmi: packet data:%s\n", buf); + else + log(LOG_WARNING, "%04d :%s\n", k, buf); + i++; + } + } + return (1); +reject: + { + int i, j, k, pos; + char buf[100]; + int loc; + const u_char *bp = mtod(m, const u_char *); + + k = i = 0; + loc = (m->m_len - packetlen); + log(LOG_WARNING, "nglmi: error at location %d\n", loc); + while (k < m->m_len) { + pos = 0; + j = 0; + while ((j++ < 16) && k < m->m_len) { + pos += sprintf(buf + pos, "%c%02x", + ((loc == k) ? '>' : ' '), + bp[k]); + k++; + } + if (i == 0) + log(LOG_WARNING, "nglmi: packet data:%s\n", buf); + else + log(LOG_WARNING, "%04d :%s\n", k, buf); + i++; + } + } + NG_FREE_M(m); + return (0); +} + +/* + * Do local shutdown processing.. + * Cut any remaining links and free our local resources. + */ +static int +nglmi_shutdown(node_p node) +{ + const sc_p sc = NG_NODE_PRIVATE(node); + + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(sc->node); + FREE(sc, M_NETGRAPH); + return (0); +} + +/* + * Hook disconnection + * For this type, removal of any link except "debug" destroys the node. + */ +static int +nglmi_disconnect(hook_p hook) +{ + const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + /* OK to remove debug hook(s) */ + if (NG_HOOK_PRIVATE(hook) == NULL) + return (0); + + /* Stop timer if it's currently active */ + if (sc->flags & SCF_CONNECTED) + ng_uncallout(&sc->handle, sc->node); + + /* Self-destruct */ + if (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))) + ng_rmnode_self(NG_HOOK_NODE(hook)); + return (0); +} + diff --git a/sys/netgraph7/ng_lmi.h b/sys/netgraph7/ng_lmi.h new file mode 100644 index 0000000000..3d187e64e3 --- /dev/null +++ b/sys/netgraph7/ng_lmi.h @@ -0,0 +1,81 @@ +/* + * ng_lmi.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_lmi.h,v 1.4 2005/01/07 01:45:39 imp Exp $ + * $Whistle: ng_lmi.h,v 1.9 1999/01/20 00:22:13 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_LMI_H_ +#define _NETGRAPH_NG_LMI_H_ + +/* Node type name and magic cookie */ +#define NG_LMI_NODE_TYPE "lmi" +#define NGM_LMI_COOKIE 867184133 + +/* My hook names */ +#define NG_LMI_HOOK_DEBUG "debug" +#define NG_LMI_HOOK_ANNEXA "annexA" +#define NG_LMI_HOOK_ANNEXD "annexD" +#define NG_LMI_HOOK_GROUPOF4 "group4" +#define NG_LMI_HOOK_AUTO0 "auto0" +#define NG_LMI_HOOK_AUTO1023 "auto1023" + +/* Netgraph commands */ +enum { + NGM_LMI_GET_STATUS = 1, +}; + +#define NGM_LMI_STAT_ARYSIZE (1024/8) + +struct nglmistat { + u_char proto[12]; /* Active proto (same as hook name) */ + u_char hook[12]; /* Active hook */ + u_char fixed; /* Set to fixed LMI mode */ + u_char autod; /* Currently auto-detecting */ + u_char seen[NGM_LMI_STAT_ARYSIZE]; /* DLCIs ever seen */ + u_char up[NGM_LMI_STAT_ARYSIZE]; /* DLCIs currently up */ +}; + +/* Some default values */ +#define NG_LMI_KEEPALIVE_RATE 10 /* seconds per keepalive */ +#define NG_LMI_POLL_RATE 3 /* faster when AUTO polling */ +#define NG_LMI_SEQ_PER_FULL 5 /* keepalives per full status */ +#define NG_LMI_LMI_PRIORITY 64 /* priority for LMI data */ + +#endif /* _NETGRAPH_NG_LMI_H_ */ diff --git a/sys/netgraph7/ng_message.h b/sys/netgraph7/ng_message.h new file mode 100644 index 0000000000..ff5ce01088 --- /dev/null +++ b/sys/netgraph7/ng_message.h @@ -0,0 +1,446 @@ +/* + * ng_message.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_message.h,v 1.29 2006/10/17 11:01:20 glebius Exp $ + * $Whistle: ng_message.h,v 1.12 1999/01/25 01:17:44 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_MESSAGE_H_ +#define _NETGRAPH_NG_MESSAGE_H_ + +/* ASCII string size limits */ +#define NG_TYPESIZ 32 /* max type name len (including null) */ +#define NG_HOOKSIZ 32 /* max hook name len (including null) */ +#define NG_NODESIZ 32 /* max node name len (including null) */ +#define NG_PATHSIZ 512 /* max path len (including null) */ +#define NG_CMDSTRSIZ 32 /* max command string (including null) */ + +#ifndef BURN_BRIDGES +/* don't use these - they will go away */ +#define NG_TYPELEN (NG_TYPESIZ - 1) +#define NG_HOOKLEN (NG_HOOKSIZ - 1) +#define NG_NODELEN (NG_NODESIZ - 1) +#define NG_PATHLEN (NG_PATHSIZ - 1) +#define NG_CMDSTRLEN (NG_CMDSTRSIZ - 1) +#endif + +#define NG_TEXTRESPONSE 1024 /* allow this length for a text response */ + +/* A netgraph message */ +struct ng_mesg { + struct ng_msghdr { + u_char version; /* == NGM_VERSION */ + u_char spare; /* pad to 4 bytes */ + u_int16_t spare2; + u_int32_t arglen; /* length of data */ + u_int32_t cmd; /* command identifier */ + u_int32_t flags; /* message status */ + u_int32_t token; /* match with reply */ + u_int32_t typecookie; /* node's type cookie */ + u_char cmdstr[NG_CMDSTRSIZ]; /* cmd string + \0 */ + } header; + char data[]; /* placeholder for actual data */ +}; + +/* This command is guaranteed to not alter data (or'd into the command). */ +#define NGM_READONLY 0x10000000 +/* This command is guaranteed to have a reply (or'd into the command). */ +#define NGM_HASREPLY 0x20000000 + +/* Keep this in sync with the above structure definition */ +#define NG_GENERIC_NG_MESG_INFO(dtype) { \ + { "version", &ng_parse_uint8_type }, \ + { "spare", &ng_parse_uint8_type }, \ + { "spare2", &ng_parse_uint16_type }, \ + { "arglen", &ng_parse_uint32_type }, \ + { "cmd", &ng_parse_uint32_type }, \ + { "flags", &ng_parse_hint32_type }, \ + { "token", &ng_parse_uint32_type }, \ + { "typecookie", &ng_parse_uint32_type }, \ + { "cmdstr", &ng_parse_cmdbuf_type }, \ + { "data", (dtype) }, \ + { NULL } \ +} + +/* + * Netgraph message header compatibility field + * Interfaces within the kernel are defined by a different + * value (see NG_ABI_VERSION in netgraph.h) + */ +#define NG_VERSION 8 + +/* Flags field flags */ +#define NGF_ORIG 0x00000000 /* the msg is the original request */ +#define NGF_RESP 0x00000001 /* the message is a response */ + +/* Type of a unique node ID. */ +#define ng_ID_t uint32_t + +/* + * Here we describe the "generic" messages that all nodes inherently + * understand. With the exception of NGM_TEXT_STATUS, these are handled + * automatically by the base netgraph code. + */ + +/* Generic message type cookie */ +#define NGM_GENERIC_COOKIE 1137070366 + +/* Generic messages defined for this type cookie. */ +enum { + NGM_SHUTDOWN = 1, /* Shut down node. */ + NGM_MKPEER = 2, /* Create and attach a peer node. */ + NGM_CONNECT = 3, /* Connect two nodes. */ + NGM_NAME = 4, /* Give a node a name. */ + NGM_RMHOOK = 5, /* Break a connection between two nodes. */ + + /* Get nodeinfo for target. */ + NGM_NODEINFO = (6|NGM_READONLY|NGM_HASREPLY), + /* Get list of hooks on node. */ + NGM_LISTHOOKS = (7|NGM_READONLY|NGM_HASREPLY), + /* List globally named nodes. */ + NGM_LISTNAMES = (8|NGM_READONLY|NGM_HASREPLY), + /* List all nodes. */ + NGM_LISTNODES = (9|NGM_READONLY|NGM_HASREPLY), + /* List installed node types. */ + NGM_LISTTYPES = (10|NGM_READONLY|NGM_HASREPLY), + /* (optional) Get text status. */ + NGM_TEXT_STATUS = (11|NGM_READONLY|NGM_HASREPLY), + /* Convert struct ng_mesg to ASCII. */ + NGM_BINARY2ASCII= (12|NGM_READONLY|NGM_HASREPLY), + /* Convert ASCII to struct ng_mesg. */ + NGM_ASCII2BINARY= (13|NGM_READONLY|NGM_HASREPLY), + /* (optional) Get/set text config. */ + NGM_TEXT_CONFIG = 14, +}; + +/* + * Flow control and intra node control messages. + * These are routed between nodes to allow flow control and to allow + * events to be passed around the graph. + * There will be some form of default handling for these but I + * do not yet know what it is.. + */ + +/* Generic message type cookie */ +#define NGM_FLOW_COOKIE 851672669 /* temp for debugging */ + +/* Upstream messages */ +#define NGM_LINK_IS_UP 32 /* e.g. carrier found - no data */ +#define NGM_LINK_IS_DOWN 33 /* carrier lost, includes queue state */ +#define NGM_HIGH_WATER_PASSED 34 /* includes queue state */ +#define NGM_LOW_WATER_PASSED 35 /* includes queue state */ +#define NGM_SYNC_QUEUE_STATE 36 /* sync response from sending packet */ + +/* Downstream messages */ +#define NGM_DROP_LINK 41 /* drop DTR, etc. - stay in the graph */ +#define NGM_RAISE_LINK 42 /* if you previously dropped it */ +#define NGM_FLUSH_QUEUE 43 /* no data */ +#define NGM_GET_BANDWIDTH (44|NGM_READONLY) /* either real or measured */ +#define NGM_SET_XMIT_Q_LIMITS 45 /* includes queue state */ +#define NGM_GET_XMIT_Q_LIMITS (46|NGM_READONLY) /* returns queue state */ +#define NGM_MICROMANAGE 47 /* We want sync. queue state + reply for each packet sent */ +#define NGM_SET_FLOW_MANAGER 48 /* send flow control here */ +/* Structure used for NGM_MKPEER */ +struct ngm_mkpeer { + char type[NG_TYPESIZ]; /* peer type */ + char ourhook[NG_HOOKSIZ]; /* hook name */ + char peerhook[NG_HOOKSIZ]; /* peer hook name */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_GENERIC_MKPEER_INFO() { \ + { "type", &ng_parse_typebuf_type }, \ + { "ourhook", &ng_parse_hookbuf_type }, \ + { "peerhook", &ng_parse_hookbuf_type }, \ + { NULL } \ +} + +/* Structure used for NGM_CONNECT */ +struct ngm_connect { + char path[NG_PATHSIZ]; /* peer path */ + char ourhook[NG_HOOKSIZ]; /* hook name */ + char peerhook[NG_HOOKSIZ]; /* peer hook name */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_GENERIC_CONNECT_INFO() { \ + { "path", &ng_parse_pathbuf_type }, \ + { "ourhook", &ng_parse_hookbuf_type }, \ + { "peerhook", &ng_parse_hookbuf_type }, \ + { NULL } \ +} + +/* Structure used for NGM_NAME */ +struct ngm_name { + char name[NG_NODESIZ]; /* node name */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_GENERIC_NAME_INFO() { \ + { "name", &ng_parse_nodebuf_type }, \ + { NULL } \ +} + +/* Structure used for NGM_RMHOOK */ +struct ngm_rmhook { + char ourhook[NG_HOOKSIZ]; /* hook name */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_GENERIC_RMHOOK_INFO() { \ + { "hook", &ng_parse_hookbuf_type }, \ + { NULL } \ +} + +/* Structure used for NGM_NODEINFO */ +struct nodeinfo { + char name[NG_NODESIZ]; /* node name (if any) */ + char type[NG_TYPESIZ]; /* peer type */ + ng_ID_t id; /* unique identifier */ + u_int32_t hooks; /* number of active hooks */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_GENERIC_NODEINFO_INFO() { \ + { "name", &ng_parse_nodebuf_type }, \ + { "type", &ng_parse_typebuf_type }, \ + { "id", &ng_parse_hint32_type }, \ + { "hooks", &ng_parse_uint32_type }, \ + { NULL } \ +} + +/* Structure used for NGM_LISTHOOKS */ +struct linkinfo { + char ourhook[NG_HOOKSIZ]; /* hook name */ + char peerhook[NG_HOOKSIZ]; /* peer hook */ + struct nodeinfo nodeinfo; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_GENERIC_LINKINFO_INFO(nitype) { \ + { "ourhook", &ng_parse_hookbuf_type }, \ + { "peerhook", &ng_parse_hookbuf_type }, \ + { "nodeinfo", (nitype) }, \ + { NULL } \ +} + +struct hooklist { + struct nodeinfo nodeinfo; /* node information */ + struct linkinfo link[]; /* info about each hook */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_GENERIC_HOOKLIST_INFO(nitype,litype) { \ + { "nodeinfo", (nitype) }, \ + { "linkinfo", (litype) }, \ + { NULL } \ +} + +/* Structure used for NGM_LISTNAMES/NGM_LISTNODES */ +struct namelist { + u_int32_t numnames; + struct nodeinfo nodeinfo[]; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_GENERIC_LISTNODES_INFO(niarraytype) { \ + { "numnames", &ng_parse_uint32_type }, \ + { "nodeinfo", (niarraytype) }, \ + { NULL } \ +} + +/* Structure used for NGM_LISTTYPES */ +struct typeinfo { + char type_name[NG_TYPESIZ]; /* name of type */ + u_int32_t numnodes; /* number alive */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_GENERIC_TYPEINFO_INFO() { \ + { "typename", &ng_parse_typebuf_type }, \ + { "numnodes", &ng_parse_uint32_type }, \ + { NULL } \ +} + +struct typelist { + u_int32_t numtypes; + struct typeinfo typeinfo[]; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_GENERIC_TYPELIST_INFO(tiarraytype) { \ + { "numtypes", &ng_parse_uint32_type }, \ + { "typeinfo", (tiarraytype) }, \ + { NULL } \ +} + +struct ngm_bandwidth { + u_int64_t nominal_in; + u_int64_t seen_in; + u_int64_t nominal_out; + u_int64_t seen_out; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_GENERIC_BANDWIDTH_INFO() { \ + { "nominal_in", &ng_parse_uint64_type }, \ + { "seen_in", &ng_parse_uint64_type }, \ + { "nominal_out", &ng_parse_uint64_type }, \ + { "seen_out", &ng_parse_uint64_type }, \ + { NULL } \ +} + +/* + * Information about a node's 'output' queue. + * This is NOT the netgraph input queueing mechanism, + * but rather any queue the node may implement internally + * This has to consider ALTQ if we are to work with it. + * As far as I can see, ALTQ counts PACKETS, not bytes. + * If ALTQ has several queues and one has passed a watermark + * we should have the priority of that queue be real (and not -1) + * XXX ALTQ stuff is just an idea..... + */ +struct ngm_queue_state { + u_int queue_priority; /* maybe only low-pri is full. -1 = all*/ + u_int max_queuelen_bytes; + u_int max_queuelen_packets; + u_int low_watermark; + u_int high_watermark; + u_int current; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_GENERIC_QUEUE_INFO() { \ + { "max_queuelen_bytes", &ng_parse_uint_type }, \ + { "max_queuelen_packets", &ng_parse_uint_type }, \ + { "high_watermark", &ng_parse_uint_type }, \ + { "low_watermark", &ng_parse_uint_type }, \ + { "current", &ng_parse_uint_type }, \ + { NULL } \ +} + +/* Tell a node who to send async flow control info to. */ +struct flow_manager { + ng_ID_t id; /* unique identifier */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_GENERIC_FLOW_MANAGER_INFO() { \ + { "id", &ng_parse_hint32_type }, \ + { NULL } \ +} + + +/* + * For netgraph nodes that are somehow associated with file descriptors + * (e.g., a device that has a /dev entry and is also a netgraph node), + * we define a generic ioctl for requesting the corresponding nodeinfo + * structure and for assigning a name (if there isn't one already). + * + * For these to you need to also #include . + */ + +#define NGIOCGINFO _IOR('N', 40, struct nodeinfo) /* get node info */ +#define NGIOCSETNAME _IOW('N', 41, struct ngm_name) /* set node name */ + +#ifdef _KERNEL +/* + * Allocate and initialize a netgraph message "msg" with "len" + * extra bytes of argument. Sets "msg" to NULL if fails. + * Does not initialize token. + */ +#define NG_MKMESSAGE(msg, cookie, cmdid, len, how) \ + do { \ + MALLOC((msg), struct ng_mesg *, sizeof(struct ng_mesg) \ + + (len), M_NETGRAPH_MSG, (how) | M_ZERO); \ + if ((msg) == NULL) \ + break; \ + (msg)->header.version = NG_VERSION; \ + (msg)->header.typecookie = (cookie); \ + (msg)->header.cmd = (cmdid); \ + (msg)->header.arglen = (len); \ + strncpy((msg)->header.cmdstr, #cmdid, \ + sizeof((msg)->header.cmdstr) - 1); \ + } while (0) + +/* + * Allocate and initialize a response "rsp" to a message "msg" + * with "len" extra bytes of argument. Sets "rsp" to NULL if fails. + */ +#define NG_MKRESPONSE(rsp, msg, len, how) \ + do { \ + MALLOC((rsp), struct ng_mesg *, sizeof(struct ng_mesg) \ + + (len), M_NETGRAPH_MSG, (how) | M_ZERO); \ + if ((rsp) == NULL) \ + break; \ + (rsp)->header.version = NG_VERSION; \ + (rsp)->header.arglen = (len); \ + (rsp)->header.token = (msg)->header.token; \ + (rsp)->header.typecookie = (msg)->header.typecookie; \ + (rsp)->header.cmd = (msg)->header.cmd; \ + bcopy((msg)->header.cmdstr, (rsp)->header.cmdstr, \ + sizeof((rsp)->header.cmdstr)); \ + (rsp)->header.flags |= NGF_RESP; \ + } while (0) + +/* + * Make a copy of message. Sets "copy" to NULL if fails. + */ +#define NG_COPYMESSAGE(copy, msg, how) \ + do { \ + MALLOC((copy), struct ng_mesg *, sizeof(struct ng_mesg) + \ + (msg)->header.arglen, M_NETGRAPH_MSG, (how) | M_ZERO); \ + if ((copy) == NULL) \ + break; \ + (copy)->header.version = NG_VERSION; \ + (copy)->header.arglen = (msg)->header.arglen; \ + (copy)->header.token = (msg)->header.token; \ + (copy)->header.typecookie = (msg)->header.typecookie; \ + (copy)->header.cmd = (msg)->header.cmd; \ + (copy)->header.flags = (msg)->header.flags; \ + bcopy((msg)->header.cmdstr, (copy)->header.cmdstr, \ + sizeof((copy)->header.cmdstr)); \ + if ((msg)->header.arglen > 0) \ + bcopy((msg)->data, (copy)->data, (msg)->header.arglen); \ + } while (0) + +#endif /* _KERNEL */ + +#endif /* _NETGRAPH_NG_MESSAGE_H_ */ diff --git a/sys/netgraph7/ng_mppc.c b/sys/netgraph7/ng_mppc.c new file mode 100644 index 0000000000..423d6989ea --- /dev/null +++ b/sys/netgraph7/ng_mppc.c @@ -0,0 +1,852 @@ +/* + * ng_mppc.c + */ + +/*- + * Copyright (c) 1996-2000 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $Whistle: ng_mppc.c,v 1.4 1999/11/25 00:10:12 archie Exp $ + * $FreeBSD: src/sys/netgraph/ng_mppc.c,v 1.31 2007/05/18 15:28:01 mav Exp $ + */ + +/* + * Microsoft PPP compression (MPPC) and encryption (MPPE) netgraph node type. + * + * You must define one or both of the NETGRAPH_MPPC_COMPRESSION and/or + * NETGRAPH_MPPC_ENCRYPTION options for this node type to be useful. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "opt_netgraph.h" + +#if !defined(NETGRAPH_MPPC_COMPRESSION) && !defined(NETGRAPH_MPPC_ENCRYPTION) +#ifdef KLD_MODULE +/* XXX NETGRAPH_MPPC_COMPRESSION isn't functional yet */ +#define NETGRAPH_MPPC_ENCRYPTION +#else +/* This case is indicative of an error in sys/conf files */ +#error Need either NETGRAPH_MPPC_COMPRESSION or NETGRAPH_MPPC_ENCRYPTION +#endif +#endif + +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_MPPC, "netgraph_mppc", "netgraph mppc node "); +#else +#define M_NETGRAPH_MPPC M_NETGRAPH +#endif + +#ifdef NETGRAPH_MPPC_COMPRESSION +/* XXX this file doesn't exist yet, but hopefully someday it will... */ +#include +#endif +#ifdef NETGRAPH_MPPC_ENCRYPTION +#include +#endif +#include + +/* Decompression blowup */ +#define MPPC_DECOMP_BUFSIZE 8092 /* allocate buffer this big */ +#define MPPC_DECOMP_SAFETY 100 /* plus this much margin */ + +/* MPPC/MPPE header length */ +#define MPPC_HDRLEN 2 + +/* Key length */ +#define KEYLEN(b) (((b) & MPPE_128) ? 16 : 8) + +/* + * When packets are lost with MPPE, we may have to re-key arbitrarily + * many times to 'catch up' to the new jumped-ahead sequence number. + * Since this can be expensive, we pose a limit on how many re-keyings + * we will do at one time to avoid a possible D.O.S. vulnerability. + * This should instead be a configurable parameter. + */ +#define MPPE_MAX_REKEY 1000 + +/* MPPC packet header bits */ +#define MPPC_FLAG_FLUSHED 0x8000 /* xmitter reset state */ +#define MPPC_FLAG_RESTART 0x4000 /* compress history restart */ +#define MPPC_FLAG_COMPRESSED 0x2000 /* packet is compresed */ +#define MPPC_FLAG_ENCRYPTED 0x1000 /* packet is encrypted */ +#define MPPC_CCOUNT_MASK 0x0fff /* sequence number mask */ + +#define MPPC_CCOUNT_INC(d) ((d) = (((d) + 1) & MPPC_CCOUNT_MASK)) + +#define MPPE_UPDATE_MASK 0xff /* coherency count when we're */ +#define MPPE_UPDATE_FLAG 0xff /* supposed to update key */ + +#define MPPC_COMP_OK 0x05 +#define MPPC_DECOMP_OK 0x05 + +/* Per direction info */ +struct ng_mppc_dir { + struct ng_mppc_config cfg; /* configuration */ + hook_p hook; /* netgraph hook */ + u_int16_t cc:12; /* coherency count */ + u_char flushed; /* clean history (xmit only) */ +#ifdef NETGRAPH_MPPC_COMPRESSION + u_char *history; /* compression history */ +#endif +#ifdef NETGRAPH_MPPC_ENCRYPTION + u_char key[MPPE_KEY_LEN]; /* session key */ + struct rc4_state rc4; /* rc4 state */ +#endif +}; + +/* Node private data */ +struct ng_mppc_private { + struct ng_mppc_dir xmit; /* compress/encrypt config */ + struct ng_mppc_dir recv; /* decompress/decrypt config */ + ng_ID_t ctrlnode; /* path to controlling node */ +}; +typedef struct ng_mppc_private *priv_p; + +/* Netgraph node methods */ +static ng_constructor_t ng_mppc_constructor; +static ng_rcvmsg_t ng_mppc_rcvmsg; +static ng_shutdown_t ng_mppc_shutdown; +static ng_newhook_t ng_mppc_newhook; +static ng_rcvdata_t ng_mppc_rcvdata; +static ng_disconnect_t ng_mppc_disconnect; + +/* Helper functions */ +static int ng_mppc_compress(node_p node, + struct mbuf **datap); +static int ng_mppc_decompress(node_p node, + struct mbuf **datap); +#ifdef NETGRAPH_MPPC_ENCRYPTION +static void ng_mppc_getkey(const u_char *h, u_char *h2, int len); +static void ng_mppc_updatekey(u_int32_t bits, + u_char *key0, u_char *key, struct rc4_state *rc4); +#endif +static void ng_mppc_reset_req(node_p node); + +/* Node type descriptor */ +static struct ng_type ng_mppc_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_MPPC_NODE_TYPE, + .constructor = ng_mppc_constructor, + .rcvmsg = ng_mppc_rcvmsg, + .shutdown = ng_mppc_shutdown, + .newhook = ng_mppc_newhook, + .rcvdata = ng_mppc_rcvdata, + .disconnect = ng_mppc_disconnect, +}; +NETGRAPH_INIT(mppc, &ng_mppc_typestruct); + +#ifdef NETGRAPH_MPPC_ENCRYPTION +/* Depend on separate rc4 module */ +MODULE_DEPEND(ng_mppc, rc4, 1, 1, 1); +#endif + +/* Fixed bit pattern to weaken keysize down to 40 or 56 bits */ +static const u_char ng_mppe_weakenkey[3] = { 0xd1, 0x26, 0x9e }; + +#define ERROUT(x) do { error = (x); goto done; } while (0) + +/************************************************************************ + NETGRAPH NODE STUFF + ************************************************************************/ + +/* + * Node type constructor + */ +static int +ng_mppc_constructor(node_p node) +{ + priv_p priv; + + /* Allocate private structure */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH_MPPC, M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + + NG_NODE_SET_PRIVATE(node, priv); + + /* This node is not thread safe. */ + NG_NODE_FORCE_WRITER(node); + + /* Done */ + return (0); +} + +/* + * Give our OK for a hook to be added + */ +static int +ng_mppc_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + hook_p *hookPtr; + + /* Check hook name */ + if (strcmp(name, NG_MPPC_HOOK_COMP) == 0) + hookPtr = &priv->xmit.hook; + else if (strcmp(name, NG_MPPC_HOOK_DECOMP) == 0) + hookPtr = &priv->recv.hook; + else + return (EINVAL); + + /* See if already connected */ + if (*hookPtr != NULL) + return (EISCONN); + + /* OK */ + *hookPtr = hook; + return (0); +} + +/* + * Receive a control message + */ +static int +ng_mppc_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_MPPC_COOKIE: + switch (msg->header.cmd) { + case NGM_MPPC_CONFIG_COMP: + case NGM_MPPC_CONFIG_DECOMP: + { + struct ng_mppc_config *const cfg + = (struct ng_mppc_config *)msg->data; + const int isComp = + msg->header.cmd == NGM_MPPC_CONFIG_COMP; + struct ng_mppc_dir *const d = isComp ? + &priv->xmit : &priv->recv; + + /* Check configuration */ + if (msg->header.arglen != sizeof(*cfg)) + ERROUT(EINVAL); + if (cfg->enable) { + if ((cfg->bits & ~MPPC_VALID_BITS) != 0) + ERROUT(EINVAL); +#ifndef NETGRAPH_MPPC_COMPRESSION + if ((cfg->bits & MPPC_BIT) != 0) + ERROUT(EPROTONOSUPPORT); +#endif +#ifndef NETGRAPH_MPPC_ENCRYPTION + if ((cfg->bits & MPPE_BITS) != 0) + ERROUT(EPROTONOSUPPORT); +#endif + } else + cfg->bits = 0; + + /* Save return address so we can send reset-req's */ + if (!isComp) + priv->ctrlnode = NGI_RETADDR(item); + + /* Configuration is OK, reset to it */ + d->cfg = *cfg; + +#ifdef NETGRAPH_MPPC_COMPRESSION + /* Initialize state buffers for compression */ + if (d->history != NULL) { + FREE(d->history, M_NETGRAPH_MPPC); + d->history = NULL; + } + if ((cfg->bits & MPPC_BIT) != 0) { + MALLOC(d->history, u_char *, + isComp ? MPPC_SizeOfCompressionHistory() : + MPPC_SizeOfDecompressionHistory(), + M_NETGRAPH_MPPC, M_NOWAIT); + if (d->history == NULL) + ERROUT(ENOMEM); + if (isComp) + MPPC_InitCompressionHistory(d->history); + else { + MPPC_InitDecompressionHistory( + d->history); + } + } +#endif + +#ifdef NETGRAPH_MPPC_ENCRYPTION + /* Generate initial session keys for encryption */ + if ((cfg->bits & MPPE_BITS) != 0) { + const int keylen = KEYLEN(cfg->bits); + + bcopy(cfg->startkey, d->key, keylen); + ng_mppc_getkey(cfg->startkey, d->key, keylen); + if ((cfg->bits & MPPE_40) != 0) + bcopy(&ng_mppe_weakenkey, d->key, 3); + else if ((cfg->bits & MPPE_56) != 0) + bcopy(&ng_mppe_weakenkey, d->key, 1); + rc4_init(&d->rc4, d->key, keylen); + } +#endif + + /* Initialize other state */ + d->cc = 0; + d->flushed = 0; + break; + } + + case NGM_MPPC_RESETREQ: + ng_mppc_reset_req(node); + break; + + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive incoming data on our hook. + */ +static int +ng_mppc_rcvdata(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + int error; + struct mbuf *m; + + NGI_GET_M(item, m); + /* Compress and/or encrypt */ + if (hook == priv->xmit.hook) { + if (!priv->xmit.cfg.enable) { + NG_FREE_M(m); + NG_FREE_ITEM(item); + return (ENXIO); + } + if ((error = ng_mppc_compress(node, &m)) != 0) { + NG_FREE_ITEM(item); + return(error); + } + NG_FWD_NEW_DATA(error, item, priv->xmit.hook, m); + return (error); + } + + /* Decompress and/or decrypt */ + if (hook == priv->recv.hook) { + if (!priv->recv.cfg.enable) { + NG_FREE_M(m); + NG_FREE_ITEM(item); + return (ENXIO); + } + if ((error = ng_mppc_decompress(node, &m)) != 0) { + NG_FREE_ITEM(item); + if (error == EINVAL && priv->ctrlnode != 0) { + struct ng_mesg *msg; + + /* Need to send a reset-request */ + NG_MKMESSAGE(msg, NGM_MPPC_COOKIE, + NGM_MPPC_RESETREQ, 0, M_NOWAIT); + if (msg == NULL) + return (error); + NG_SEND_MSG_ID(error, node, msg, + priv->ctrlnode, 0); + } + return (error); + } + NG_FWD_NEW_DATA(error, item, priv->recv.hook, m); + return (error); + } + + /* Oops */ + panic("%s: unknown hook", __func__); +#ifdef RESTARTABLE_PANICS + return (EINVAL); +#endif +} + +/* + * Destroy node + */ +static int +ng_mppc_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + /* Take down netgraph node */ +#ifdef NETGRAPH_MPPC_COMPRESSION + if (priv->xmit.history != NULL) + FREE(priv->xmit.history, M_NETGRAPH_MPPC); + if (priv->recv.history != NULL) + FREE(priv->recv.history, M_NETGRAPH_MPPC); +#endif + bzero(priv, sizeof(*priv)); + FREE(priv, M_NETGRAPH_MPPC); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); /* let the node escape */ + return (0); +} + +/* + * Hook disconnection + */ +static int +ng_mppc_disconnect(hook_p hook) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + + /* Zero out hook pointer */ + if (hook == priv->xmit.hook) + priv->xmit.hook = NULL; + if (hook == priv->recv.hook) + priv->recv.hook = NULL; + + /* Go away if no longer connected */ + if ((NG_NODE_NUMHOOKS(node) == 0) + && NG_NODE_IS_VALID(node)) + ng_rmnode_self(node); + return (0); +} + +/************************************************************************ + HELPER STUFF + ************************************************************************/ + +/* + * Compress/encrypt a packet and put the result in a new mbuf at *resultp. + * The original mbuf is not free'd. + */ +static int +ng_mppc_compress(node_p node, struct mbuf **datap) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mppc_dir *const d = &priv->xmit; + u_int16_t header; + struct mbuf *m = *datap; + + /* Initialize */ + header = d->cc; + + /* Always set the flushed bit in stateless mode */ + if (d->flushed || ((d->cfg.bits & MPPE_STATELESS) != 0)) { + header |= MPPC_FLAG_FLUSHED; + d->flushed = 0; + } + + /* Compress packet (if compression enabled) */ +#ifdef NETGRAPH_MPPC_COMPRESSION + if ((d->cfg.bits & MPPC_BIT) != 0) { + u_short flags = MPPC_MANDATORY_COMPRESS_FLAGS; + u_char *inbuf, *outbuf; + int outlen, inlen; + u_char *source, *dest; + u_long sourceCnt, destCnt; + int rtn; + + /* Work with contiguous regions of memory. */ + inlen = m->m_pkthdr.len; + inbuf = malloc(inlen, M_NETGRAPH_MPPC, M_NOWAIT); + if (inbuf == NULL) { + m_freem(m); + return (ENOMEM); + } + m_copydata(m, 0, inlen, (caddr_t)inbuf); + + outlen = MPPC_MAX_BLOWUP(inlen); + outbuf = malloc(outlen, M_NETGRAPH_MPPC, M_NOWAIT); + if (outbuf == NULL) { + m_freem(m); + free(inbuf, M_NETGRAPH_MPPC); + return (ENOMEM); + } + + /* Prepare to compress */ + source = inbuf; + sourceCnt = inlen; + dest = outbuf; + destCnt = outlen; + if ((d->cfg.bits & MPPE_STATELESS) == 0) + flags |= MPPC_SAVE_HISTORY; + + /* Compress */ + rtn = MPPC_Compress(&source, &dest, &sourceCnt, + &destCnt, d->history, flags, 0); + + /* Check return value */ + KASSERT(rtn != MPPC_INVALID, ("%s: invalid", __func__)); + if ((rtn & MPPC_EXPANDED) == 0 + && (rtn & MPPC_COMP_OK) == MPPC_COMP_OK) { + outlen -= destCnt; + header |= MPPC_FLAG_COMPRESSED; + if ((rtn & MPPC_RESTART_HISTORY) != 0) + header |= MPPC_FLAG_RESTART; + + /* Replace m by the compresed one. */ + m_freem(m); + m = m_devget((caddr_t)outbuf, outlen, 0, NULL, NULL); + } + d->flushed = (rtn & MPPC_EXPANDED) != 0 + || (flags & MPPC_SAVE_HISTORY) == 0; + + free(inbuf, M_NETGRAPH_MPPC); + free(outbuf, M_NETGRAPH_MPPC); + + /* Check m_devget() result. */ + if (m == NULL) + return (ENOMEM); + } +#endif + + /* Now encrypt packet (if encryption enabled) */ +#ifdef NETGRAPH_MPPC_ENCRYPTION + if ((d->cfg.bits & MPPE_BITS) != 0) { + struct mbuf *m1; + + /* Set header bits */ + header |= MPPC_FLAG_ENCRYPTED; + + /* Update key if it's time */ + if ((d->cfg.bits & MPPE_STATELESS) != 0 + || (d->cc & MPPE_UPDATE_MASK) == MPPE_UPDATE_FLAG) { + ng_mppc_updatekey(d->cfg.bits, + d->cfg.startkey, d->key, &d->rc4); + } else if ((header & MPPC_FLAG_FLUSHED) != 0) { + /* Need to reset key if we say we did + and ng_mppc_updatekey wasn't called to do it also. */ + rc4_init(&d->rc4, d->key, KEYLEN(d->cfg.bits)); + } + + /* We must own the mbuf chain exclusively to modify it. */ + m = m_unshare(m, M_DONTWAIT); + if (m == NULL) + return (ENOMEM); + + /* Encrypt packet */ + m1 = m; + while (m1) { + rc4_crypt(&d->rc4, mtod(m1, u_char *), + mtod(m1, u_char *), m1->m_len); + m1 = m1->m_next; + } + } +#endif + + /* Update coherency count for next time (12 bit arithmetic) */ + MPPC_CCOUNT_INC(d->cc); + + /* Install header */ + M_PREPEND(m, MPPC_HDRLEN, M_DONTWAIT); + if (m != NULL) + *(mtod(m, uint16_t *)) = htons(header); + + *datap = m; + return (*datap == NULL ? ENOBUFS : 0); +} + +/* + * Decompress/decrypt packet and put the result in a new mbuf at *resultp. + * The original mbuf is not free'd. + */ +static int +ng_mppc_decompress(node_p node, struct mbuf **datap) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mppc_dir *const d = &priv->recv; + u_int16_t header, cc; + u_int numLost; + struct mbuf *m = *datap; + + /* Pull off header */ + if (m->m_pkthdr.len < MPPC_HDRLEN) { + m_freem(m); + return (EINVAL); + } + m_copydata(m, 0, MPPC_HDRLEN, (caddr_t)&header); + header = ntohs(header); + cc = (header & MPPC_CCOUNT_MASK); + m_adj(m, MPPC_HDRLEN); + + /* Check for an unexpected jump in the sequence number */ + numLost = ((cc - d->cc) & MPPC_CCOUNT_MASK); + + /* If flushed bit set, we can always handle packet */ + if ((header & MPPC_FLAG_FLUSHED) != 0) { +#ifdef NETGRAPH_MPPC_COMPRESSION + if (d->history != NULL) + MPPC_InitDecompressionHistory(d->history); +#endif +#ifdef NETGRAPH_MPPC_ENCRYPTION + if ((d->cfg.bits & MPPE_BITS) != 0) { + u_int rekey; + + /* How many times are we going to have to re-key? */ + rekey = ((d->cfg.bits & MPPE_STATELESS) != 0) ? + numLost : (numLost / (MPPE_UPDATE_MASK + 1)); + if (rekey > MPPE_MAX_REKEY) { + log(LOG_ERR, "%s: too many (%d) packets" + " dropped, disabling node %p!", + __func__, numLost, node); + priv->recv.cfg.enable = 0; + goto failed; + } + + /* Re-key as necessary to catch up to peer */ + while (d->cc != cc) { + if ((d->cfg.bits & MPPE_STATELESS) != 0 + || (d->cc & MPPE_UPDATE_MASK) + == MPPE_UPDATE_FLAG) { + ng_mppc_updatekey(d->cfg.bits, + d->cfg.startkey, d->key, &d->rc4); + } + MPPC_CCOUNT_INC(d->cc); + } + + /* Reset key (except in stateless mode, see below) */ + if ((d->cfg.bits & MPPE_STATELESS) == 0) + rc4_init(&d->rc4, d->key, KEYLEN(d->cfg.bits)); + } +#endif + d->cc = cc; /* skip over lost seq numbers */ + numLost = 0; /* act like no packets were lost */ + } + + /* Can't decode non-sequential packets without a flushed bit */ + if (numLost != 0) + goto failed; + + /* Decrypt packet */ + if ((header & MPPC_FLAG_ENCRYPTED) != 0) { +#ifdef NETGRAPH_MPPC_ENCRYPTION + struct mbuf *m1; +#endif + + /* Are we not expecting encryption? */ + if ((d->cfg.bits & MPPE_BITS) == 0) { + log(LOG_ERR, "%s: rec'd unexpectedly %s packet", + __func__, "encrypted"); + goto failed; + } + +#ifdef NETGRAPH_MPPC_ENCRYPTION + /* Update key if it's time (always in stateless mode) */ + if ((d->cfg.bits & MPPE_STATELESS) != 0 + || (d->cc & MPPE_UPDATE_MASK) == MPPE_UPDATE_FLAG) { + ng_mppc_updatekey(d->cfg.bits, + d->cfg.startkey, d->key, &d->rc4); + } + + /* We must own the mbuf chain exclusively to modify it. */ + m = m_unshare(m, M_DONTWAIT); + if (m == NULL) + return (ENOMEM); + + /* Decrypt packet */ + m1 = m; + while (m1 != NULL) { + rc4_crypt(&d->rc4, mtod(m1, u_char *), + mtod(m1, u_char *), m1->m_len); + m1 = m1->m_next; + } +#endif + } else { + + /* Are we expecting encryption? */ + if ((d->cfg.bits & MPPE_BITS) != 0) { + log(LOG_ERR, "%s: rec'd unexpectedly %s packet", + __func__, "unencrypted"); + goto failed; + } + } + + /* Update coherency count for next time (12 bit arithmetic) */ + MPPC_CCOUNT_INC(d->cc); + + /* Check for unexpected compressed packet */ + if ((header & MPPC_FLAG_COMPRESSED) != 0 + && (d->cfg.bits & MPPC_BIT) == 0) { + log(LOG_ERR, "%s: rec'd unexpectedly %s packet", + __func__, "compressed"); +failed: + m_freem(m); + return (EINVAL); + } + +#ifdef NETGRAPH_MPPC_COMPRESSION + /* Decompress packet */ + if ((header & MPPC_FLAG_COMPRESSED) != 0) { + int flags = MPPC_MANDATORY_DECOMPRESS_FLAGS; + u_char *decompbuf, *source, *dest; + u_long sourceCnt, destCnt; + int decomplen, rtn; + u_char *buf; + int len; + + /* Copy payload into a contiguous region of memory. */ + len = m->m_pkthdr.len; + buf = malloc(len, M_NETGRAPH_MPPC, M_NOWAIT); + if (buf == NULL) { + m_freem(m); + return (ENOMEM); + } + m_copydata(m, 0, len, (caddr_t)buf); + + /* Allocate a buffer for decompressed data */ + decompbuf = malloc(MPPC_DECOMP_BUFSIZE + MPPC_DECOMP_SAFETY, + M_NETGRAPH_MPPC, M_NOWAIT); + if (decompbuf == NULL) { + m_freem(m); + free(buf, M_NETGRAPH_MPPC); + return (ENOMEM); + } + decomplen = MPPC_DECOMP_BUFSIZE; + + /* Prepare to decompress */ + source = buf; + sourceCnt = len; + dest = decompbuf; + destCnt = decomplen; + if ((header & MPPC_FLAG_RESTART) != 0) + flags |= MPPC_RESTART_HISTORY; + + /* Decompress */ + rtn = MPPC_Decompress(&source, &dest, + &sourceCnt, &destCnt, d->history, flags); + + /* Check return value */ + KASSERT(rtn != MPPC_INVALID, ("%s: invalid", __func__)); + if ((rtn & MPPC_DEST_EXHAUSTED) != 0 + || (rtn & MPPC_DECOMP_OK) != MPPC_DECOMP_OK) { + log(LOG_ERR, "%s: decomp returned 0x%x", + __func__, rtn); + free(buf, M_NETGRAPH_MPPC); + free(decompbuf, M_NETGRAPH_MPPC); + goto failed; + } + + /* Replace compressed data with decompressed data */ + free(buf, M_NETGRAPH_MPPC); + len = decomplen - destCnt; + + m_freem(m); + m = m_devget((caddr_t)decompbuf, len, 0, NULL, NULL); + free(decompbuf, M_NETGRAPH_MPPC); + } +#endif + + /* Return result in an mbuf */ + *datap = m; + return (*datap == NULL ? ENOBUFS : 0); +} + +/* + * The peer has sent us a CCP ResetRequest, so reset our transmit state. + */ +static void +ng_mppc_reset_req(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mppc_dir *const d = &priv->xmit; + +#ifdef NETGRAPH_MPPC_COMPRESSION + if (d->history != NULL) + MPPC_InitCompressionHistory(d->history); +#endif +#ifdef NETGRAPH_MPPC_ENCRYPTION + if ((d->cfg.bits & MPPE_STATELESS) == 0) + rc4_init(&d->rc4, d->key, KEYLEN(d->cfg.bits)); +#endif + d->flushed = 1; +} + +#ifdef NETGRAPH_MPPC_ENCRYPTION +/* + * Generate a new encryption key + */ +static void +ng_mppc_getkey(const u_char *h, u_char *h2, int len) +{ + static const u_char pad1[10] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; + static const u_char pad2[10] = + { 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, }; + u_char hash[20]; + SHA1_CTX c; + int k; + + SHA1Init(&c); + SHA1Update(&c, h, len); + for (k = 0; k < 4; k++) + SHA1Update(&c, pad1, sizeof(pad1)); + SHA1Update(&c, h2, len); + for (k = 0; k < 4; k++) + SHA1Update(&c, pad2, sizeof(pad2)); + SHA1Final(hash, &c); + bcopy(hash, h2, len); +} + +/* + * Update the encryption key + */ +static void +ng_mppc_updatekey(u_int32_t bits, + u_char *key0, u_char *key, struct rc4_state *rc4) +{ + const int keylen = KEYLEN(bits); + + ng_mppc_getkey(key0, key, keylen); + rc4_init(rc4, key, keylen); + rc4_crypt(rc4, key, key, keylen); + if ((bits & MPPE_40) != 0) + bcopy(&ng_mppe_weakenkey, key, 3); + else if ((bits & MPPE_56) != 0) + bcopy(&ng_mppe_weakenkey, key, 1); + rc4_init(rc4, key, keylen); +} +#endif + diff --git a/sys/netgraph7/ng_mppc.h b/sys/netgraph7/ng_mppc.h new file mode 100644 index 0000000000..81c5461849 --- /dev/null +++ b/sys/netgraph7/ng_mppc.h @@ -0,0 +1,85 @@ +/* + * ng_mppc.h + */ + +/*- + * Copyright (c) 1996-2000 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $Whistle: ng_mppc.h,v 1.3 2000/02/12 01:17:22 archie Exp $ + * $FreeBSD: src/sys/netgraph/ng_mppc.h,v 1.5 2005/01/07 01:45:39 imp Exp $ + */ + +#ifndef _NETGRAPH_NG_MPPC_H_ +#define _NETGRAPH_NG_MPPC_H_ + +/* Node type name and magic cookie */ +#define NG_MPPC_NODE_TYPE "mppc" +#define NGM_MPPC_COOKIE 942886745 + +/* Hook names */ +#define NG_MPPC_HOOK_COMP "comp" /* compression hook */ +#define NG_MPPC_HOOK_DECOMP "decomp" /* decompression hook */ + +/* Length of MPPE key */ +#define MPPE_KEY_LEN 16 + +/* Max expansion due to MPPC header and compression algorithm */ +#define MPPC_MAX_BLOWUP(n) ((n) * 9 / 8 + 26) + +/* MPPC/MPPE PPP negotiation bits */ +#define MPPC_BIT 0x00000001 /* mppc compression bits */ +#define MPPE_40 0x00000020 /* use 40 bit key */ +#define MPPE_56 0x00000080 /* use 56 bit key */ +#define MPPE_128 0x00000040 /* use 128 bit key */ +#define MPPE_BITS 0x000000e0 /* mppe encryption bits */ +#define MPPE_STATELESS 0x01000000 /* use stateless mode */ +#define MPPC_VALID_BITS 0x010000e1 /* possibly valid bits */ + +/* Config struct (per-direction) */ +struct ng_mppc_config { + u_char enable; /* enable */ + u_int32_t bits; /* config bits */ + u_char startkey[MPPE_KEY_LEN]; /* start key */ +}; + +/* Netgraph commands */ +enum { + NGM_MPPC_CONFIG_COMP = 1, + NGM_MPPC_CONFIG_DECOMP, + NGM_MPPC_RESETREQ, /* sent either way! */ +}; + +#endif /* _NETGRAPH_NG_MPPC_H_ */ + diff --git a/sys/netgraph7/ng_nat.c b/sys/netgraph7/ng_nat.c new file mode 100644 index 0000000000..a1f85326a9 --- /dev/null +++ b/sys/netgraph7/ng_nat.c @@ -0,0 +1,837 @@ +/*- + * Copyright 2005, Gleb Smirnoff + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_nat.c,v 1.12 2008/06/01 15:13:32 mav Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +static ng_constructor_t ng_nat_constructor; +static ng_rcvmsg_t ng_nat_rcvmsg; +static ng_shutdown_t ng_nat_shutdown; +static ng_newhook_t ng_nat_newhook; +static ng_rcvdata_t ng_nat_rcvdata; +static ng_disconnect_t ng_nat_disconnect; + +static unsigned int ng_nat_translate_flags(unsigned int x); + +/* Parse type for struct ng_nat_mode. */ +static const struct ng_parse_struct_field ng_nat_mode_fields[] + = NG_NAT_MODE_INFO; +static const struct ng_parse_type ng_nat_mode_type = { + &ng_parse_struct_type, + &ng_nat_mode_fields +}; + +/* Parse type for 'description' field in structs. */ +static const struct ng_parse_fixedstring_info ng_nat_description_info + = { NG_NAT_DESC_LENGTH }; +static const struct ng_parse_type ng_nat_description_type = { + &ng_parse_fixedstring_type, + &ng_nat_description_info +}; + +/* Parse type for struct ng_nat_redirect_port. */ +static const struct ng_parse_struct_field ng_nat_redirect_port_fields[] + = NG_NAT_REDIRECT_PORT_TYPE_INFO(&ng_nat_description_type); +static const struct ng_parse_type ng_nat_redirect_port_type = { + &ng_parse_struct_type, + &ng_nat_redirect_port_fields +}; + +/* Parse type for struct ng_nat_redirect_addr. */ +static const struct ng_parse_struct_field ng_nat_redirect_addr_fields[] + = NG_NAT_REDIRECT_ADDR_TYPE_INFO(&ng_nat_description_type); +static const struct ng_parse_type ng_nat_redirect_addr_type = { + &ng_parse_struct_type, + &ng_nat_redirect_addr_fields +}; + +/* Parse type for struct ng_nat_redirect_proto. */ +static const struct ng_parse_struct_field ng_nat_redirect_proto_fields[] + = NG_NAT_REDIRECT_PROTO_TYPE_INFO(&ng_nat_description_type); +static const struct ng_parse_type ng_nat_redirect_proto_type = { + &ng_parse_struct_type, + &ng_nat_redirect_proto_fields +}; + +/* Parse type for struct ng_nat_add_server. */ +static const struct ng_parse_struct_field ng_nat_add_server_fields[] + = NG_NAT_ADD_SERVER_TYPE_INFO; +static const struct ng_parse_type ng_nat_add_server_type = { + &ng_parse_struct_type, + &ng_nat_add_server_fields +}; + +/* Parse type for one struct ng_nat_listrdrs_entry. */ +static const struct ng_parse_struct_field ng_nat_listrdrs_entry_fields[] + = NG_NAT_LISTRDRS_ENTRY_TYPE_INFO(&ng_nat_description_type); +static const struct ng_parse_type ng_nat_listrdrs_entry_type = { + &ng_parse_struct_type, + &ng_nat_listrdrs_entry_fields +}; + +/* Parse type for 'redirects' array in struct ng_nat_list_redirects. */ +static int +ng_nat_listrdrs_ary_getLength(const struct ng_parse_type *type, + const u_char *start, const u_char *buf) +{ + const struct ng_nat_list_redirects *lr; + + lr = (const struct ng_nat_list_redirects *) + (buf - offsetof(struct ng_nat_list_redirects, redirects)); + return lr->total_count; +} + +static const struct ng_parse_array_info ng_nat_listrdrs_ary_info = { + &ng_nat_listrdrs_entry_type, + &ng_nat_listrdrs_ary_getLength, + NULL +}; +static const struct ng_parse_type ng_nat_listrdrs_ary_type = { + &ng_parse_array_type, + &ng_nat_listrdrs_ary_info +}; + +/* Parse type for struct ng_nat_list_redirects. */ +static const struct ng_parse_struct_field ng_nat_list_redirects_fields[] + = NG_NAT_LIST_REDIRECTS_TYPE_INFO(&ng_nat_listrdrs_ary_type); +static const struct ng_parse_type ng_nat_list_redirects_type = { + &ng_parse_struct_type, + &ng_nat_list_redirects_fields +}; + +/* List of commands and how to convert arguments to/from ASCII. */ +static const struct ng_cmdlist ng_nat_cmdlist[] = { + { + NGM_NAT_COOKIE, + NGM_NAT_SET_IPADDR, + "setaliasaddr", + &ng_parse_ipaddr_type, + NULL + }, + { + NGM_NAT_COOKIE, + NGM_NAT_SET_MODE, + "setmode", + &ng_nat_mode_type, + NULL + }, + { + NGM_NAT_COOKIE, + NGM_NAT_SET_TARGET, + "settarget", + &ng_parse_ipaddr_type, + NULL + }, + { + NGM_NAT_COOKIE, + NGM_NAT_REDIRECT_PORT, + "redirectport", + &ng_nat_redirect_port_type, + &ng_parse_uint32_type + }, + { + NGM_NAT_COOKIE, + NGM_NAT_REDIRECT_ADDR, + "redirectaddr", + &ng_nat_redirect_addr_type, + &ng_parse_uint32_type + }, + { + NGM_NAT_COOKIE, + NGM_NAT_REDIRECT_PROTO, + "redirectproto", + &ng_nat_redirect_proto_type, + &ng_parse_uint32_type + }, + { + NGM_NAT_COOKIE, + NGM_NAT_REDIRECT_DYNAMIC, + "redirectdynamic", + &ng_parse_uint32_type, + NULL + }, + { + NGM_NAT_COOKIE, + NGM_NAT_REDIRECT_DELETE, + "redirectdelete", + &ng_parse_uint32_type, + NULL + }, + { + NGM_NAT_COOKIE, + NGM_NAT_ADD_SERVER, + "addserver", + &ng_nat_add_server_type, + NULL + }, + { + NGM_NAT_COOKIE, + NGM_NAT_LIST_REDIRECTS, + "listredirects", + NULL, + &ng_nat_list_redirects_type + }, + { + NGM_NAT_COOKIE, + NGM_NAT_PROXY_RULE, + "proxyrule", + &ng_parse_string_type, + NULL + }, + { 0 } +}; + +/* Netgraph node type descriptor. */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_NAT_NODE_TYPE, + .constructor = ng_nat_constructor, + .rcvmsg = ng_nat_rcvmsg, + .shutdown = ng_nat_shutdown, + .newhook = ng_nat_newhook, + .rcvdata = ng_nat_rcvdata, + .disconnect = ng_nat_disconnect, + .cmdlist = ng_nat_cmdlist, +}; +NETGRAPH_INIT(nat, &typestruct); +MODULE_DEPEND(ng_nat, libalias, 1, 1, 1); + +/* Element for list of redirects. */ +struct ng_nat_rdr_lst { + STAILQ_ENTRY(ng_nat_rdr_lst) entries; + struct alias_link *lnk; + struct ng_nat_listrdrs_entry rdr; +}; +STAILQ_HEAD(rdrhead, ng_nat_rdr_lst); + +/* Information we store for each node. */ +struct ng_nat_priv { + node_p node; /* back pointer to node */ + hook_p in; /* hook for demasquerading */ + hook_p out; /* hook for masquerading */ + struct libalias *lib; /* libalias handler */ + uint32_t flags; /* status flags */ + uint32_t rdrcount; /* number or redirects in list */ + uint32_t nextid; /* for next in turn in list */ + struct rdrhead redirhead; /* redirect list header */ +}; +typedef struct ng_nat_priv *priv_p; + +/* Values of flags */ +#define NGNAT_CONNECTED 0x1 /* We have both hooks connected */ +#define NGNAT_ADDR_DEFINED 0x2 /* NGM_NAT_SET_IPADDR happened */ + +static int +ng_nat_constructor(node_p node) +{ + priv_p priv; + + /* Initialize private descriptor. */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, + M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + + /* Init aliasing engine. */ + priv->lib = LibAliasInit(NULL); + if (priv->lib == NULL) { + FREE(priv, M_NETGRAPH); + return (ENOMEM); + } + + /* Set same ports on. */ + (void )LibAliasSetMode(priv->lib, PKT_ALIAS_SAME_PORTS, + PKT_ALIAS_SAME_PORTS); + + /* Init redirects housekeeping. */ + priv->rdrcount = 0; + priv->nextid = 1; + STAILQ_INIT(&priv->redirhead); + + /* Link structs together. */ + NG_NODE_SET_PRIVATE(node, priv); + priv->node = node; + + /* + * libalias is not thread safe, so our node + * must be single threaded. + */ + NG_NODE_FORCE_WRITER(node); + + return (0); +} + +static int +ng_nat_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (strcmp(name, NG_NAT_HOOK_IN) == 0) { + priv->in = hook; + } else if (strcmp(name, NG_NAT_HOOK_OUT) == 0) { + priv->out = hook; + } else + return (EINVAL); + + if (priv->out != NULL && + priv->in != NULL) + priv->flags |= NGNAT_CONNECTED; + + return(0); +} + +static int +ng_nat_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + struct ng_mesg *msg; + int error = 0; + + NGI_GET_MSG(item, msg); + + switch (msg->header.typecookie) { + case NGM_NAT_COOKIE: + switch (msg->header.cmd) { + case NGM_NAT_SET_IPADDR: + { + struct in_addr *const ia = (struct in_addr *)msg->data; + + if (msg->header.arglen < sizeof(*ia)) { + error = EINVAL; + break; + } + + LibAliasSetAddress(priv->lib, *ia); + + priv->flags |= NGNAT_ADDR_DEFINED; + } + break; + case NGM_NAT_SET_MODE: + { + struct ng_nat_mode *const mode = + (struct ng_nat_mode *)msg->data; + + if (msg->header.arglen < sizeof(*mode)) { + error = EINVAL; + break; + } + + if (LibAliasSetMode(priv->lib, + ng_nat_translate_flags(mode->flags), + ng_nat_translate_flags(mode->mask)) < 0) { + error = ENOMEM; + break; + } + } + break; + case NGM_NAT_SET_TARGET: + { + struct in_addr *const ia = (struct in_addr *)msg->data; + + if (msg->header.arglen < sizeof(*ia)) { + error = EINVAL; + break; + } + + LibAliasSetTarget(priv->lib, *ia); + } + break; + case NGM_NAT_REDIRECT_PORT: + { + struct ng_nat_rdr_lst *entry; + struct ng_nat_redirect_port *const rp = + (struct ng_nat_redirect_port *)msg->data; + + if (msg->header.arglen < sizeof(*rp)) { + error = EINVAL; + break; + } + + if ((entry = malloc(sizeof(struct ng_nat_rdr_lst), + M_NETGRAPH, M_NOWAIT | M_ZERO)) == NULL) { + error = ENOMEM; + break; + } + + /* Try actual redirect. */ + entry->lnk = LibAliasRedirectPort(priv->lib, + rp->local_addr, htons(rp->local_port), + rp->remote_addr, htons(rp->remote_port), + rp->alias_addr, htons(rp->alias_port), + rp->proto); + + if (entry->lnk == NULL) { + error = ENOMEM; + FREE(entry, M_NETGRAPH); + break; + } + + /* Successful, save info in our internal list. */ + entry->rdr.local_addr = rp->local_addr; + entry->rdr.alias_addr = rp->alias_addr; + entry->rdr.remote_addr = rp->remote_addr; + entry->rdr.local_port = rp->local_port; + entry->rdr.alias_port = rp->alias_port; + entry->rdr.remote_port = rp->remote_port; + entry->rdr.proto = rp->proto; + bcopy(rp->description, entry->rdr.description, + NG_NAT_DESC_LENGTH); + + /* Safety precaution. */ + entry->rdr.description[NG_NAT_DESC_LENGTH-1] = '\0'; + + entry->rdr.id = priv->nextid++; + priv->rdrcount++; + + /* Link to list of redirects. */ + STAILQ_INSERT_TAIL(&priv->redirhead, entry, entries); + + /* Response with id of newly added entry. */ + NG_MKRESPONSE(resp, msg, sizeof(entry->rdr.id), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + bcopy(&entry->rdr.id, resp->data, sizeof(entry->rdr.id)); + } + break; + case NGM_NAT_REDIRECT_ADDR: + { + struct ng_nat_rdr_lst *entry; + struct ng_nat_redirect_addr *const ra = + (struct ng_nat_redirect_addr *)msg->data; + + if (msg->header.arglen < sizeof(*ra)) { + error = EINVAL; + break; + } + + if ((entry = malloc(sizeof(struct ng_nat_rdr_lst), + M_NETGRAPH, M_NOWAIT | M_ZERO)) == NULL) { + error = ENOMEM; + break; + } + + /* Try actual redirect. */ + entry->lnk = LibAliasRedirectAddr(priv->lib, + ra->local_addr, ra->alias_addr); + + if (entry->lnk == NULL) { + error = ENOMEM; + FREE(entry, M_NETGRAPH); + break; + } + + /* Successful, save info in our internal list. */ + entry->rdr.local_addr = ra->local_addr; + entry->rdr.alias_addr = ra->alias_addr; + entry->rdr.proto = NG_NAT_REDIRPROTO_ADDR; + bcopy(ra->description, entry->rdr.description, + NG_NAT_DESC_LENGTH); + + /* Safety precaution. */ + entry->rdr.description[NG_NAT_DESC_LENGTH-1] = '\0'; + + entry->rdr.id = priv->nextid++; + priv->rdrcount++; + + /* Link to list of redirects. */ + STAILQ_INSERT_TAIL(&priv->redirhead, entry, entries); + + /* Response with id of newly added entry. */ + NG_MKRESPONSE(resp, msg, sizeof(entry->rdr.id), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + bcopy(&entry->rdr.id, resp->data, sizeof(entry->rdr.id)); + } + break; + case NGM_NAT_REDIRECT_PROTO: + { + struct ng_nat_rdr_lst *entry; + struct ng_nat_redirect_proto *const rp = + (struct ng_nat_redirect_proto *)msg->data; + + if (msg->header.arglen < sizeof(*rp)) { + error = EINVAL; + break; + } + + if ((entry = malloc(sizeof(struct ng_nat_rdr_lst), + M_NETGRAPH, M_NOWAIT | M_ZERO)) == NULL) { + error = ENOMEM; + break; + } + + /* Try actual redirect. */ + entry->lnk = LibAliasRedirectProto(priv->lib, + rp->local_addr, rp->remote_addr, + rp->alias_addr, rp->proto); + + if (entry->lnk == NULL) { + error = ENOMEM; + FREE(entry, M_NETGRAPH); + break; + } + + /* Successful, save info in our internal list. */ + entry->rdr.local_addr = rp->local_addr; + entry->rdr.alias_addr = rp->alias_addr; + entry->rdr.remote_addr = rp->remote_addr; + entry->rdr.proto = rp->proto; + bcopy(rp->description, entry->rdr.description, + NG_NAT_DESC_LENGTH); + + /* Safety precaution. */ + entry->rdr.description[NG_NAT_DESC_LENGTH-1] = '\0'; + + entry->rdr.id = priv->nextid++; + priv->rdrcount++; + + /* Link to list of redirects. */ + STAILQ_INSERT_TAIL(&priv->redirhead, entry, entries); + + /* Response with id of newly added entry. */ + NG_MKRESPONSE(resp, msg, sizeof(entry->rdr.id), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + bcopy(&entry->rdr.id, resp->data, sizeof(entry->rdr.id)); + } + break; + case NGM_NAT_REDIRECT_DYNAMIC: + case NGM_NAT_REDIRECT_DELETE: + { + struct ng_nat_rdr_lst *entry; + uint32_t *const id = (uint32_t *)msg->data; + + if (msg->header.arglen < sizeof(*id)) { + error = EINVAL; + break; + } + + /* Find entry with supplied id. */ + STAILQ_FOREACH(entry, &priv->redirhead, entries) { + if (entry->rdr.id == *id) + break; + } + + /* Not found. */ + if (entry == NULL) { + error = ENOENT; + break; + } + + if (msg->header.cmd == NGM_NAT_REDIRECT_DYNAMIC) { + if (LibAliasRedirectDynamic(priv->lib, + entry->lnk) == -1) { + error = ENOTTY; /* XXX Something better? */ + break; + } + } else { /* NGM_NAT_REDIRECT_DELETE */ + LibAliasRedirectDelete(priv->lib, entry->lnk); + } + + /* Delete entry from our internal list. */ + priv->rdrcount--; + STAILQ_REMOVE(&priv->redirhead, entry, ng_nat_rdr_lst, entries); + FREE(entry, M_NETGRAPH); + } + break; + case NGM_NAT_ADD_SERVER: + { + struct ng_nat_rdr_lst *entry; + struct ng_nat_add_server *const as = + (struct ng_nat_add_server *)msg->data; + + if (msg->header.arglen < sizeof(*as)) { + error = EINVAL; + break; + } + + /* Find entry with supplied id. */ + STAILQ_FOREACH(entry, &priv->redirhead, entries) { + if (entry->rdr.id == as->id) + break; + } + + /* Not found. */ + if (entry == NULL) { + error = ENOENT; + break; + } + + if (LibAliasAddServer(priv->lib, entry->lnk, + as->addr, htons(as->port)) == -1) { + error = ENOMEM; + break; + } + + entry->rdr.lsnat++; + } + break; + case NGM_NAT_LIST_REDIRECTS: + { + struct ng_nat_rdr_lst *entry; + struct ng_nat_list_redirects *ary; + int i = 0; + + NG_MKRESPONSE(resp, msg, sizeof(*ary) + + (priv->rdrcount) * sizeof(*entry), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + + ary = (struct ng_nat_list_redirects *)resp->data; + ary->total_count = priv->rdrcount; + + STAILQ_FOREACH(entry, &priv->redirhead, entries) { + bcopy(&entry->rdr, &ary->redirects[i++], + sizeof(struct ng_nat_listrdrs_entry)); + } + } + break; + case NGM_NAT_PROXY_RULE: + { + char *cmd = (char *)msg->data; + + if (msg->header.arglen < 6) { + error = EINVAL; + break; + } + + if (LibAliasProxyRule(priv->lib, cmd) != 0) + error = ENOMEM; + } + break; + default: + error = EINVAL; /* unknown command */ + break; + } + break; + default: + error = EINVAL; /* unknown cookie type */ + break; + } + + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +static int +ng_nat_rcvdata(hook_p hook, item_p item ) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct mbuf *m; + struct ip *ip; + int rval, error = 0; + char *c; + + /* We have no required hooks. */ + if (!(priv->flags & NGNAT_CONNECTED)) { + NG_FREE_ITEM(item); + return (ENXIO); + } + + /* We have no alias address yet to do anything. */ + if (!(priv->flags & NGNAT_ADDR_DEFINED)) + goto send; + + m = NGI_M(item); + + if ((m = m_megapullup(m, m->m_pkthdr.len)) == NULL) { + NGI_M(item) = NULL; /* avoid double free */ + NG_FREE_ITEM(item); + return (ENOBUFS); + } + + NGI_M(item) = m; + + c = mtod(m, char *); + ip = mtod(m, struct ip *); + + KASSERT(m->m_pkthdr.len == ntohs(ip->ip_len), + ("ng_nat: ip_len != m_pkthdr.len")); + + if (hook == priv->in) { + rval = LibAliasIn(priv->lib, c, m->m_len + M_TRAILINGSPACE(m)); + if (rval != PKT_ALIAS_OK && + rval != PKT_ALIAS_FOUND_HEADER_FRAGMENT) { + NG_FREE_ITEM(item); + return (EINVAL); + } + } else if (hook == priv->out) { + rval = LibAliasOut(priv->lib, c, m->m_len + M_TRAILINGSPACE(m)); + if (rval != PKT_ALIAS_OK) { + NG_FREE_ITEM(item); + return (EINVAL); + } + } else + panic("ng_nat: unknown hook!\n"); + + m->m_pkthdr.len = m->m_len = ntohs(ip->ip_len); + + if ((ip->ip_off & htons(IP_OFFMASK)) == 0 && + ip->ip_p == IPPROTO_TCP) { + struct tcphdr *th = (struct tcphdr *)((caddr_t)ip + + (ip->ip_hl << 2)); + + /* + * Here is our terrible HACK. + * + * Sometimes LibAlias edits contents of TCP packet. + * In this case it needs to recompute full TCP + * checksum. However, the problem is that LibAlias + * doesn't have any idea about checksum offloading + * in kernel. To workaround this, we do not do + * checksumming in LibAlias, but only mark the + * packets in th_x2 field. If we receive a marked + * packet, we calculate correct checksum for it + * aware of offloading. + * + * Why do I do such a terrible hack instead of + * recalculating checksum for each packet? + * Because the previous checksum was not checked! + * Recalculating checksums for EVERY packet will + * hide ALL transmission errors. Yes, marked packets + * still suffer from this problem. But, sigh, natd(8) + * has this problem, too. + */ + + if (th->th_x2) { + th->th_x2 = 0; + ip->ip_len = ntohs(ip->ip_len); + th->th_sum = in_pseudo(ip->ip_src.s_addr, + ip->ip_dst.s_addr, htons(IPPROTO_TCP + + ip->ip_len - (ip->ip_hl << 2))); + + if ((m->m_pkthdr.csum_flags & CSUM_TCP) == 0) { + m->m_pkthdr.csum_data = offsetof(struct tcphdr, + th_sum); + in_delayed_cksum(m); + } + ip->ip_len = htons(ip->ip_len); + } + } + +send: + if (hook == priv->in) + NG_FWD_ITEM_HOOK(error, item, priv->out); + else + NG_FWD_ITEM_HOOK(error, item, priv->in); + + return (error); +} + +static int +ng_nat_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + + /* Free redirects list. */ + while (!STAILQ_EMPTY(&priv->redirhead)) { + struct ng_nat_rdr_lst *entry = STAILQ_FIRST(&priv->redirhead); + STAILQ_REMOVE_HEAD(&priv->redirhead, entries); + FREE(entry, M_NETGRAPH); + }; + + /* Final free. */ + LibAliasUninit(priv->lib); + FREE(priv, M_NETGRAPH); + + return (0); +} + +static int +ng_nat_disconnect(hook_p hook) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + priv->flags &= ~NGNAT_CONNECTED; + + if (hook == priv->out) + priv->out = NULL; + if (hook == priv->in) + priv->in = NULL; + + if (priv->out == NULL && priv->in == NULL) + ng_rmnode_self(NG_HOOK_NODE(hook)); + + return (0); +} + +static unsigned int +ng_nat_translate_flags(unsigned int x) +{ + unsigned int res = 0; + + if (x & NG_NAT_LOG) + res |= PKT_ALIAS_LOG; + if (x & NG_NAT_DENY_INCOMING) + res |= PKT_ALIAS_DENY_INCOMING; + if (x & NG_NAT_SAME_PORTS) + res |= PKT_ALIAS_SAME_PORTS; + if (x & NG_NAT_UNREGISTERED_ONLY) + res |= PKT_ALIAS_UNREGISTERED_ONLY; + if (x & NG_NAT_RESET_ON_ADDR_CHANGE) + res |= PKT_ALIAS_RESET_ON_ADDR_CHANGE; + if (x & NG_NAT_PROXY_ONLY) + res |= PKT_ALIAS_PROXY_ONLY; + if (x & NG_NAT_REVERSE) + res |= PKT_ALIAS_REVERSE; + + return (res); +} diff --git a/sys/netgraph7/ng_nat.h b/sys/netgraph7/ng_nat.h new file mode 100644 index 0000000000..8653cf601c --- /dev/null +++ b/sys/netgraph7/ng_nat.h @@ -0,0 +1,187 @@ +/*- + * Copyright 2005, Gleb Smirnoff + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_nat.h,v 1.4 2008/03/04 11:10:54 mav Exp $ + */ + +#define NG_NAT_NODE_TYPE "nat" +#define NGM_NAT_COOKIE 1107718711 + +#define NG_NAT_HOOK_IN "in" +#define NG_NAT_HOOK_OUT "out" + +/* Arguments for NGM_NAT_SET_MODE message */ +struct ng_nat_mode { + uint32_t flags; + uint32_t mask; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_NAT_MODE_INFO { \ + { "flags", &ng_parse_uint32_type }, \ + { "mask", &ng_parse_uint32_type }, \ + { NULL } \ +} + +#define NG_NAT_LOG 0x01 +#define NG_NAT_DENY_INCOMING 0x02 +#define NG_NAT_SAME_PORTS 0x04 +#define NG_NAT_UNREGISTERED_ONLY 0x10 +#define NG_NAT_RESET_ON_ADDR_CHANGE 0x20 +#define NG_NAT_PROXY_ONLY 0x40 +#define NG_NAT_REVERSE 0x80 + +#define NG_NAT_DESC_LENGTH 64 +#define NG_NAT_REDIRPROTO_ADDR (IPPROTO_MAX + 3) /* LibAlias' LINK_ADDR, also unused in in.h */ + +/* Arguments for NGM_NAT_REDIRECT_PORT message */ +struct ng_nat_redirect_port { + struct in_addr local_addr; + struct in_addr alias_addr; + struct in_addr remote_addr; + uint16_t local_port; + uint16_t alias_port; + uint16_t remote_port; + uint8_t proto; + char description[NG_NAT_DESC_LENGTH]; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_NAT_REDIRECT_PORT_TYPE_INFO(desctype) { \ + { "local_addr", &ng_parse_ipaddr_type }, \ + { "alias_addr", &ng_parse_ipaddr_type }, \ + { "remote_addr", &ng_parse_ipaddr_type }, \ + { "local_port", &ng_parse_uint16_type }, \ + { "alias_port", &ng_parse_uint16_type }, \ + { "remote_port", &ng_parse_uint16_type }, \ + { "proto", &ng_parse_uint8_type }, \ + { "description", (desctype) }, \ + { NULL } \ +} + +/* Arguments for NGM_NAT_REDIRECT_ADDR message */ +struct ng_nat_redirect_addr { + struct in_addr local_addr; + struct in_addr alias_addr; + char description[NG_NAT_DESC_LENGTH]; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_NAT_REDIRECT_ADDR_TYPE_INFO(desctype) { \ + { "local_addr", &ng_parse_ipaddr_type }, \ + { "alias_addr", &ng_parse_ipaddr_type }, \ + { "description", (desctype) }, \ + { NULL } \ +} + +/* Arguments for NGM_NAT_REDIRECT_PROTO message */ +struct ng_nat_redirect_proto { + struct in_addr local_addr; + struct in_addr alias_addr; + struct in_addr remote_addr; + uint8_t proto; + char description[NG_NAT_DESC_LENGTH]; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_NAT_REDIRECT_PROTO_TYPE_INFO(desctype) { \ + { "local_addr", &ng_parse_ipaddr_type }, \ + { "alias_addr", &ng_parse_ipaddr_type }, \ + { "remote_addr", &ng_parse_ipaddr_type }, \ + { "proto", &ng_parse_uint8_type }, \ + { "description", (desctype) }, \ + { NULL } \ +} + +/* Arguments for NGM_NAT_ADD_SERVER message */ +struct ng_nat_add_server { + uint32_t id; + struct in_addr addr; + uint16_t port; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_NAT_ADD_SERVER_TYPE_INFO { \ + { "id", &ng_parse_uint32_type }, \ + { "addr", &ng_parse_ipaddr_type }, \ + { "port", &ng_parse_uint16_type }, \ + { NULL } \ +} + +/* List entry of array returned in NGM_NAT_LIST_REDIRECTS message */ +struct ng_nat_listrdrs_entry { + uint32_t id; /* Anything except zero */ + struct in_addr local_addr; + struct in_addr alias_addr; + struct in_addr remote_addr; + uint16_t local_port; + uint16_t alias_port; + uint16_t remote_port; + uint16_t proto; /* Valid proto or NG_NAT_REDIRPROTO_ADDR */ + uint16_t lsnat; /* LSNAT servers count */ + char description[NG_NAT_DESC_LENGTH]; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_NAT_LISTRDRS_ENTRY_TYPE_INFO(desctype) { \ + { "id", &ng_parse_uint32_type }, \ + { "local_addr", &ng_parse_ipaddr_type }, \ + { "alias_addr", &ng_parse_ipaddr_type }, \ + { "remote_addr", &ng_parse_ipaddr_type }, \ + { "local_port", &ng_parse_uint16_type }, \ + { "alias_port", &ng_parse_uint16_type }, \ + { "remote_port", &ng_parse_uint16_type }, \ + { "proto", &ng_parse_uint16_type }, \ + { "lsnat", &ng_parse_uint16_type }, \ + { "description", (desctype) }, \ + { NULL } \ +} + +/* Structure returned by NGM_NAT_LIST_REDIRECTS */ +struct ng_nat_list_redirects { + uint32_t total_count; + struct ng_nat_listrdrs_entry redirects[]; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_NAT_LIST_REDIRECTS_TYPE_INFO(redirtype) { \ + { "total_count", &ng_parse_uint32_type }, \ + { "redirects", (redirtype) }, \ + { NULL } \ +} + +enum { + NGM_NAT_SET_IPADDR = 1, + NGM_NAT_SET_MODE, + NGM_NAT_SET_TARGET, + NGM_NAT_REDIRECT_PORT, + NGM_NAT_REDIRECT_ADDR, + NGM_NAT_REDIRECT_PROTO, + NGM_NAT_REDIRECT_DYNAMIC, + NGM_NAT_REDIRECT_DELETE, + NGM_NAT_ADD_SERVER, + NGM_NAT_LIST_REDIRECTS, + NGM_NAT_PROXY_RULE, +}; diff --git a/sys/netgraph7/ng_one2many.c b/sys/netgraph7/ng_one2many.c new file mode 100644 index 0000000000..56e6cffa1e --- /dev/null +++ b/sys/netgraph7/ng_one2many.c @@ -0,0 +1,609 @@ +/* + * ng_one2many.c + */ + +/*- + * Copyright (c) 2000 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_one2many.c,v 1.21 2005/03/11 10:29:38 glebius Exp $ + */ + +/* + * ng_one2many(4) netgraph node type + * + * Packets received on the "one" hook are sent out each of the + * "many" hooks accoring to an algorithm. Packets received on any + * "many" hook are always delivered to the "one" hook. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* Per-link private data */ +struct ng_one2many_link { + hook_p hook; /* netgraph hook */ + struct ng_one2many_link_stats stats; /* link stats */ +}; + +/* Per-node private data */ +struct ng_one2many_private { + node_p node; /* link to node */ + struct ng_one2many_config conf; /* node configuration */ + struct ng_one2many_link one; /* "one" hook */ + struct ng_one2many_link many[NG_ONE2MANY_MAX_LINKS]; + u_int16_t nextMany; /* next round-robin */ + u_int16_t numActiveMany; /* # active "many" */ + u_int16_t activeMany[NG_ONE2MANY_MAX_LINKS]; +}; +typedef struct ng_one2many_private *priv_p; + +/* Netgraph node methods */ +static ng_constructor_t ng_one2many_constructor; +static ng_rcvmsg_t ng_one2many_rcvmsg; +static ng_shutdown_t ng_one2many_shutdown; +static ng_newhook_t ng_one2many_newhook; +static ng_rcvdata_t ng_one2many_rcvdata; +static ng_disconnect_t ng_one2many_disconnect; + +/* Other functions */ +static void ng_one2many_update_many(priv_p priv); +static void ng_one2many_notify(priv_p priv, uint32_t cmd); + +/****************************************************************** + NETGRAPH PARSE TYPES +******************************************************************/ + +/* Parse type for struct ng_one2many_config */ +static const struct ng_parse_fixedarray_info + ng_one2many_enableLinks_array_type_info = { + &ng_parse_uint8_type, + NG_ONE2MANY_MAX_LINKS +}; +static const struct ng_parse_type ng_one2many_enableLinks_array_type = { + &ng_parse_fixedarray_type, + &ng_one2many_enableLinks_array_type_info, +}; +static const struct ng_parse_struct_field ng_one2many_config_type_fields[] + = NG_ONE2MANY_CONFIG_TYPE_INFO(&ng_one2many_enableLinks_array_type); +static const struct ng_parse_type ng_one2many_config_type = { + &ng_parse_struct_type, + &ng_one2many_config_type_fields +}; + +/* Parse type for struct ng_one2many_link_stats */ +static const struct ng_parse_struct_field ng_one2many_link_stats_type_fields[] + = NG_ONE2MANY_LINK_STATS_TYPE_INFO; +static const struct ng_parse_type ng_one2many_link_stats_type = { + &ng_parse_struct_type, + &ng_one2many_link_stats_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_one2many_cmdlist[] = { + { + NGM_ONE2MANY_COOKIE, + NGM_ONE2MANY_SET_CONFIG, + "setconfig", + &ng_one2many_config_type, + NULL + }, + { + NGM_ONE2MANY_COOKIE, + NGM_ONE2MANY_GET_CONFIG, + "getconfig", + NULL, + &ng_one2many_config_type + }, + { + NGM_ONE2MANY_COOKIE, + NGM_ONE2MANY_GET_STATS, + "getstats", + &ng_parse_int32_type, + &ng_one2many_link_stats_type + }, + { + NGM_ONE2MANY_COOKIE, + NGM_ONE2MANY_CLR_STATS, + "clrstats", + &ng_parse_int32_type, + NULL, + }, + { + NGM_ONE2MANY_COOKIE, + NGM_ONE2MANY_GETCLR_STATS, + "getclrstats", + &ng_parse_int32_type, + &ng_one2many_link_stats_type + }, + { 0 } +}; + +/* Node type descriptor */ +static struct ng_type ng_one2many_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_ONE2MANY_NODE_TYPE, + .constructor = ng_one2many_constructor, + .rcvmsg = ng_one2many_rcvmsg, + .shutdown = ng_one2many_shutdown, + .newhook = ng_one2many_newhook, + .rcvdata = ng_one2many_rcvdata, + .disconnect = ng_one2many_disconnect, + .cmdlist = ng_one2many_cmdlist, +}; +NETGRAPH_INIT(one2many, &ng_one2many_typestruct); + +/****************************************************************** + NETGRAPH NODE METHODS +******************************************************************/ + +/* + * Node constructor + */ +static int +ng_one2many_constructor(node_p node) +{ + priv_p priv; + + /* Allocate and initialize private info */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + priv->conf.xmitAlg = NG_ONE2MANY_XMIT_ROUNDROBIN; + priv->conf.failAlg = NG_ONE2MANY_FAIL_MANUAL; + + /* cross reference */ + NG_NODE_SET_PRIVATE(node, priv); + priv->node = node; + + /* Done */ + return (0); +} + +/* + * Method for attaching a new hook + */ +static int +ng_one2many_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_one2many_link *link; + int linkNum; + u_long i; + + /* Which hook? */ + if (strncmp(name, NG_ONE2MANY_HOOK_MANY_PREFIX, + strlen(NG_ONE2MANY_HOOK_MANY_PREFIX)) == 0) { + const char *cp; + char *eptr; + + cp = name + strlen(NG_ONE2MANY_HOOK_MANY_PREFIX); + if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) + return (EINVAL); + i = strtoul(cp, &eptr, 10); + if (*eptr != '\0' || i < 0 || i >= NG_ONE2MANY_MAX_LINKS) + return (EINVAL); + linkNum = (int)i; + link = &priv->many[linkNum]; + } else if (strcmp(name, NG_ONE2MANY_HOOK_ONE) == 0) { + linkNum = NG_ONE2MANY_ONE_LINKNUM; + link = &priv->one; + } else + return (EINVAL); + + /* Is hook already connected? (should never happen) */ + if (link->hook != NULL) + return (EISCONN); + + /* Setup private info for this link */ + NG_HOOK_SET_PRIVATE(hook, (void *)(intptr_t)linkNum); + link->hook = hook; + bzero(&link->stats, sizeof(link->stats)); + if (linkNum != NG_ONE2MANY_ONE_LINKNUM) { + priv->conf.enabledLinks[linkNum] = 1; /* auto-enable link */ + ng_one2many_update_many(priv); + } + + /* Done */ + return (0); +} + +/* + * Receive a control message + */ +static int +ng_one2many_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_ONE2MANY_COOKIE: + switch (msg->header.cmd) { + case NGM_ONE2MANY_SET_CONFIG: + { + struct ng_one2many_config *conf; + int i; + + /* Check that new configuration is valid */ + if (msg->header.arglen != sizeof(*conf)) { + error = EINVAL; + break; + } + conf = (struct ng_one2many_config *)msg->data; + switch (conf->xmitAlg) { + case NG_ONE2MANY_XMIT_ROUNDROBIN: + case NG_ONE2MANY_XMIT_ALL: + break; + default: + error = EINVAL; + break; + } + switch (conf->failAlg) { + case NG_ONE2MANY_FAIL_MANUAL: + case NG_ONE2MANY_FAIL_NOTIFY: + break; + default: + error = EINVAL; + break; + } + if (error != 0) + break; + + /* Normalized many link enabled bits */ + for (i = 0; i < NG_ONE2MANY_MAX_LINKS; i++) + conf->enabledLinks[i] = !!conf->enabledLinks[i]; + + /* Copy config and reset */ + bcopy(conf, &priv->conf, sizeof(*conf)); + ng_one2many_update_many(priv); + break; + } + case NGM_ONE2MANY_GET_CONFIG: + { + struct ng_one2many_config *conf; + + NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + conf = (struct ng_one2many_config *)resp->data; + bcopy(&priv->conf, conf, sizeof(priv->conf)); + break; + } + case NGM_ONE2MANY_GET_STATS: + case NGM_ONE2MANY_CLR_STATS: + case NGM_ONE2MANY_GETCLR_STATS: + { + struct ng_one2many_link *link; + int linkNum; + + /* Get link */ + if (msg->header.arglen != sizeof(int32_t)) { + error = EINVAL; + break; + } + linkNum = *((int32_t *)msg->data); + if (linkNum == NG_ONE2MANY_ONE_LINKNUM) + link = &priv->one; + else if (linkNum >= 0 + && linkNum < NG_ONE2MANY_MAX_LINKS) { + link = &priv->many[linkNum]; + } else { + error = EINVAL; + break; + } + + /* Get/clear stats */ + if (msg->header.cmd != NGM_ONE2MANY_CLR_STATS) { + NG_MKRESPONSE(resp, msg, + sizeof(link->stats), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + bcopy(&link->stats, + resp->data, sizeof(link->stats)); + } + if (msg->header.cmd != NGM_ONE2MANY_GET_STATS) + bzero(&link->stats, sizeof(link->stats)); + break; + } + default: + error = EINVAL; + break; + } + break; + /* + * One of our downstreams notifies us of link change. If we are + * configured to listen to these message, then we remove/add + * this hook from array of active hooks. + */ + case NGM_FLOW_COOKIE: + { + int linkNum; + + if (priv->conf.failAlg != NG_ONE2MANY_FAIL_NOTIFY) + break; + + if (lasthook == NULL) + break; + + linkNum = (intptr_t)NG_HOOK_PRIVATE(lasthook); + if (linkNum == NG_ONE2MANY_ONE_LINKNUM) + break; + + KASSERT((linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS), + ("%s: linkNum=%d", __func__, linkNum)); + + switch (msg->header.cmd) { + case NGM_LINK_IS_UP: + priv->conf.enabledLinks[linkNum] = 1; + ng_one2many_update_many(priv); + break; + case NGM_LINK_IS_DOWN: + priv->conf.enabledLinks[linkNum] = 0; + ng_one2many_update_many(priv); + break; + default: + break; + } + break; + } + default: + error = EINVAL; + break; + } + + /* Done */ + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive data on a hook + */ +static int +ng_one2many_rcvdata(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_one2many_link *src; + struct ng_one2many_link *dst = NULL; + int error = 0; + int linkNum; + int i; + struct mbuf *m; + + m = NGI_M(item); /* just peaking, mbuf still owned by item */ + /* Get link number */ + linkNum = (intptr_t)NG_HOOK_PRIVATE(hook); + KASSERT(linkNum == NG_ONE2MANY_ONE_LINKNUM + || (linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS), + ("%s: linkNum=%d", __func__, linkNum)); + + /* Figure out source link */ + src = (linkNum == NG_ONE2MANY_ONE_LINKNUM) ? + &priv->one : &priv->many[linkNum]; + KASSERT(src->hook != NULL, ("%s: no src%d", __func__, linkNum)); + + /* Update receive stats */ + src->stats.recvPackets++; + src->stats.recvOctets += m->m_pkthdr.len; + + /* Figure out destination link */ + if (linkNum == NG_ONE2MANY_ONE_LINKNUM) { + if (priv->numActiveMany == 0) { + NG_FREE_ITEM(item); + return (ENOTCONN); + } + switch(priv->conf.xmitAlg) { + case NG_ONE2MANY_XMIT_ROUNDROBIN: + dst = &priv->many[priv->activeMany[priv->nextMany]]; + priv->nextMany = (priv->nextMany + 1) % priv->numActiveMany; + break; + case NG_ONE2MANY_XMIT_ALL: + /* no need to copy data for the 1st one */ + dst = &priv->many[priv->activeMany[0]]; + + /* make copies of data and send for all links + * except the first one, which we'll do last + */ + for (i = 1; i < priv->numActiveMany; i++) { + struct mbuf *m2; + struct ng_one2many_link *mdst; + + mdst = &priv->many[priv->activeMany[i]]; + m2 = m_dup(m, M_DONTWAIT); /* XXX m_copypacket() */ + if (m2 == NULL) { + mdst->stats.memoryFailures++; + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (ENOBUFS); + } + /* Update transmit stats */ + mdst->stats.xmitPackets++; + mdst->stats.xmitOctets += m->m_pkthdr.len; + NG_SEND_DATA_ONLY(error, mdst->hook, m2); + } + break; +#ifdef INVARIANTS + default: + panic("%s: invalid xmitAlg", __func__); +#endif + } + } else { + dst = &priv->one; + } + + /* Update transmit stats */ + dst->stats.xmitPackets++; + dst->stats.xmitOctets += m->m_pkthdr.len; + + /* Deliver packet */ + NG_FWD_ITEM_HOOK(error, item, dst->hook); + return (error); +} + +/* + * Shutdown node + */ +static int +ng_one2many_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + KASSERT(priv->numActiveMany == 0, + ("%s: numActiveMany=%d", __func__, priv->numActiveMany)); + FREE(priv, M_NETGRAPH); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + return (0); +} + +/* + * Hook disconnection. + */ +static int +ng_one2many_disconnect(hook_p hook) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + int linkNum; + + /* Get link number */ + linkNum = (intptr_t)NG_HOOK_PRIVATE(hook); + KASSERT(linkNum == NG_ONE2MANY_ONE_LINKNUM + || (linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS), + ("%s: linkNum=%d", __func__, linkNum)); + + /* Nuke the link */ + if (linkNum == NG_ONE2MANY_ONE_LINKNUM) + priv->one.hook = NULL; + else { + priv->many[linkNum].hook = NULL; + priv->conf.enabledLinks[linkNum] = 0; + ng_one2many_update_many(priv); + } + + /* If no hooks left, go away */ + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) + ng_rmnode_self(NG_HOOK_NODE(hook)); + return (0); +} + +/****************************************************************** + OTHER FUNCTIONS +******************************************************************/ + +/* + * Update internal state after the addition or removal of a "many" link + */ +static void +ng_one2many_update_many(priv_p priv) +{ + uint16_t saveActive = priv->numActiveMany; + int linkNum; + + /* Update list of which "many" links are up */ + priv->numActiveMany = 0; + for (linkNum = 0; linkNum < NG_ONE2MANY_MAX_LINKS; linkNum++) { + switch (priv->conf.failAlg) { + case NG_ONE2MANY_FAIL_MANUAL: + case NG_ONE2MANY_FAIL_NOTIFY: + if (priv->many[linkNum].hook != NULL + && priv->conf.enabledLinks[linkNum]) { + priv->activeMany[priv->numActiveMany] = linkNum; + priv->numActiveMany++; + } + break; +#ifdef INVARIANTS + default: + panic("%s: invalid failAlg", __func__); +#endif + } + } + + if (priv->numActiveMany == 0 && saveActive > 0) + ng_one2many_notify(priv, NGM_LINK_IS_DOWN); + + if (saveActive == 0 && priv->numActiveMany > 0) + ng_one2many_notify(priv, NGM_LINK_IS_UP); + + /* Update transmit algorithm state */ + switch (priv->conf.xmitAlg) { + case NG_ONE2MANY_XMIT_ROUNDROBIN: + if (priv->numActiveMany > 0) + priv->nextMany %= priv->numActiveMany; + break; + case NG_ONE2MANY_XMIT_ALL: + break; +#ifdef INVARIANTS + default: + panic("%s: invalid xmitAlg", __func__); +#endif + } +} + +/* + * Notify upstream if we are out of links, or we have at least one link. + */ +static void +ng_one2many_notify(priv_p priv, uint32_t cmd) +{ + struct ng_mesg *msg; + int dummy_error = 0; + + if (priv->one.hook == NULL) + return; + + NG_MKMESSAGE(msg, NGM_FLOW_COOKIE, cmd, 0, M_NOWAIT); + if (msg != NULL) + NG_SEND_MSG_HOOK(dummy_error, priv->node, msg, priv->one.hook, 0); +} diff --git a/sys/netgraph7/ng_one2many.h b/sys/netgraph7/ng_one2many.h new file mode 100644 index 0000000000..c233a53c72 --- /dev/null +++ b/sys/netgraph7/ng_one2many.h @@ -0,0 +1,113 @@ +/* + * ng_one2many.h + */ + +/*- + * Copyright (c) 2000 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_one2many.h,v 1.8 2005/01/07 01:45:39 imp Exp $ + */ + +#ifndef _NETGRAPH_NG_ONE2MANY_H_ +#define _NETGRAPH_NG_ONE2MANY_H_ + +/* Node type name and magic cookie */ +#define NG_ONE2MANY_NODE_TYPE "one2many" +#define NGM_ONE2MANY_COOKIE 1100897444 + +/* Hook names */ +#define NG_ONE2MANY_HOOK_ONE "one" +#define NG_ONE2MANY_HOOK_MANY_PREFIX "many" /* append decimal integer */ +#define NG_ONE2MANY_HOOK_MANY_FMT "many%d" /* for use with printf(3) */ + +/* Maximum number of supported "many" links */ +#define NG_ONE2MANY_MAX_LINKS 64 + +/* Link number used to indicate the "one" hook */ +#define NG_ONE2MANY_ONE_LINKNUM (-1) + +/* Algorithms for outgoing packet distribution (XXX only one so far) */ +#define NG_ONE2MANY_XMIT_ROUNDROBIN 1 /* round-robin delivery */ +#define NG_ONE2MANY_XMIT_ALL 2 /* send packets to all many hooks */ + +/* Algorithms for detecting link failure (XXX only one so far) */ +#define NG_ONE2MANY_FAIL_MANUAL 1 /* use enabledLinks[] array */ +#define NG_ONE2MANY_FAIL_NOTIFY 2 /* listen to flow control msgs */ + +/* Node configuration structure */ +struct ng_one2many_config { + u_int32_t xmitAlg; /* how to distribute packets */ + u_int32_t failAlg; /* how to detect link failure */ + u_char enabledLinks[NG_ONE2MANY_MAX_LINKS]; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_ONE2MANY_CONFIG_TYPE_INFO(atype) { \ + { "xmitAlg", &ng_parse_uint32_type }, \ + { "failAlg", &ng_parse_uint32_type }, \ + { "enabledLinks", (atype) }, \ + { NULL } \ +} + +/* Statistics structure (one for each link) */ +struct ng_one2many_link_stats { + u_int64_t recvOctets; /* total octets rec'd on link */ + u_int64_t recvPackets; /* total pkts rec'd on link */ + u_int64_t xmitOctets; /* total octets xmit'd on link */ + u_int64_t xmitPackets; /* total pkts xmit'd on link */ + u_int64_t memoryFailures; /* times couldn't get mem or mbuf */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_ONE2MANY_LINK_STATS_TYPE_INFO { \ + { "recvOctets", &ng_parse_uint64_type }, \ + { "recvPackets", &ng_parse_uint64_type }, \ + { "xmitOctets", &ng_parse_uint64_type }, \ + { "xmitPackets", &ng_parse_uint64_type }, \ + { "memoryFailures", &ng_parse_uint64_type }, \ + { NULL } \ +} + +/* Netgraph control messages */ +enum { + NGM_ONE2MANY_SET_CONFIG, /* set configuration */ + NGM_ONE2MANY_GET_CONFIG, /* get configuration */ + NGM_ONE2MANY_GET_STATS, /* get link stats */ + NGM_ONE2MANY_CLR_STATS, /* clear link stats */ + NGM_ONE2MANY_GETCLR_STATS, /* atomically get & clear link stats */ +}; + +#endif /* _NETGRAPH_NG_ONE2MANY_H_ */ + diff --git a/sys/netgraph7/ng_parse.c b/sys/netgraph7/ng_parse.c new file mode 100644 index 0000000000..255a45d026 --- /dev/null +++ b/sys/netgraph7/ng_parse.c @@ -0,0 +1,1912 @@ +/* + * ng_parse.c + */ + +/*- + * Copyright (c) 1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $Whistle: ng_parse.c,v 1.3 1999/11/29 01:43:48 archie Exp $ + * $FreeBSD: src/sys/netgraph/ng_parse.c,v 1.30 2007/06/23 00:02:20 mjacob Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include +#include +#include + +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_PARSE, "netgraph_parse", "netgraph parse info"); +#else +#define M_NETGRAPH_PARSE M_NETGRAPH +#endif + +/* Compute alignment for primitive integral types */ +struct int16_temp { + char x; + int16_t y; +}; + +struct int32_temp { + char x; + int32_t y; +}; + +struct int64_temp { + char x; + int64_t y; +}; + +#define INT8_ALIGNMENT 1 +#define INT16_ALIGNMENT ((size_t)&((struct int16_temp *)0)->y) +#define INT32_ALIGNMENT ((size_t)&((struct int32_temp *)0)->y) +#define INT64_ALIGNMENT ((size_t)&((struct int64_temp *)0)->y) + +/* Output format for integral types */ +#define INT_UNSIGNED 0 +#define INT_SIGNED 1 +#define INT_HEX 2 + +/* Type of composite object: struct, array, or fixedarray */ +enum comptype { + CT_STRUCT, + CT_ARRAY, + CT_FIXEDARRAY, +}; + +/* Composite types helper functions */ +static int ng_parse_composite(const struct ng_parse_type *type, + const char *s, int *off, const u_char *start, + u_char *const buf, int *buflen, enum comptype ctype); +static int ng_unparse_composite(const struct ng_parse_type *type, + const u_char *data, int *off, char *cbuf, int cbuflen, + enum comptype ctype); +static int ng_get_composite_elem_default(const struct ng_parse_type *type, + int index, const u_char *start, u_char *buf, + int *buflen, enum comptype ctype); +static int ng_get_composite_len(const struct ng_parse_type *type, + const u_char *start, const u_char *buf, + enum comptype ctype); +static const struct ng_parse_type *ng_get_composite_etype(const struct + ng_parse_type *type, int index, enum comptype ctype); +static int ng_parse_get_elem_pad(const struct ng_parse_type *type, + int index, enum comptype ctype, int posn); + +/* Parsing helper functions */ +static int ng_parse_skip_value(const char *s, int off, int *lenp); +static int ng_parse_append(char **cbufp, int *cbuflenp, + const char *fmt, ...); + +/* Poor man's virtual method calls */ +#define METHOD(t,m) (ng_get_ ## m ## _method(t)) +#define INVOKE(t,m) (*METHOD(t,m)) + +static ng_parse_t *ng_get_parse_method(const struct ng_parse_type *t); +static ng_unparse_t *ng_get_unparse_method(const struct ng_parse_type *t); +static ng_getDefault_t *ng_get_getDefault_method(const + struct ng_parse_type *t); +static ng_getAlign_t *ng_get_getAlign_method(const struct ng_parse_type *t); + +#define ALIGNMENT(t) (METHOD(t, getAlign) == NULL ? \ + 0 : INVOKE(t, getAlign)(t)) + +/************************************************************************ + PUBLIC FUNCTIONS + ************************************************************************/ + +/* + * Convert an ASCII string to binary according to the supplied type descriptor + */ +int +ng_parse(const struct ng_parse_type *type, + const char *string, int *off, u_char *buf, int *buflen) +{ + return INVOKE(type, parse)(type, string, off, buf, buf, buflen); +} + +/* + * Convert binary to an ASCII string according to the supplied type descriptor + */ +int +ng_unparse(const struct ng_parse_type *type, + const u_char *data, char *cbuf, int cbuflen) +{ + int off = 0; + + return INVOKE(type, unparse)(type, data, &off, cbuf, cbuflen); +} + +/* + * Fill in the default value according to the supplied type descriptor + */ +int +ng_parse_getDefault(const struct ng_parse_type *type, u_char *buf, int *buflen) +{ + ng_getDefault_t *const func = METHOD(type, getDefault); + + if (func == NULL) + return (EOPNOTSUPP); + return (*func)(type, buf, buf, buflen); +} + + +/************************************************************************ + STRUCTURE TYPE + ************************************************************************/ + +static int +ng_struct_parse(const struct ng_parse_type *type, + const char *s, int *off, const u_char *const start, + u_char *const buf, int *buflen) +{ + return ng_parse_composite(type, s, off, start, buf, buflen, CT_STRUCT); +} + +static int +ng_struct_unparse(const struct ng_parse_type *type, + const u_char *data, int *off, char *cbuf, int cbuflen) +{ + return ng_unparse_composite(type, data, off, cbuf, cbuflen, CT_STRUCT); +} + +static int +ng_struct_getDefault(const struct ng_parse_type *type, + const u_char *const start, u_char *buf, int *buflen) +{ + int off = 0; + + return ng_parse_composite(type, + "{}", &off, start, buf, buflen, CT_STRUCT); +} + +static int +ng_struct_getAlign(const struct ng_parse_type *type) +{ + const struct ng_parse_struct_field *field; + int align = 0; + + for (field = type->info; field->name != NULL; field++) { + int falign = ALIGNMENT(field->type); + + if (falign > align) + align = falign; + } + return align; +} + +const struct ng_parse_type ng_parse_struct_type = { + NULL, + NULL, + NULL, + ng_struct_parse, + ng_struct_unparse, + ng_struct_getDefault, + ng_struct_getAlign +}; + +/************************************************************************ + FIXED LENGTH ARRAY TYPE + ************************************************************************/ + +static int +ng_fixedarray_parse(const struct ng_parse_type *type, + const char *s, int *off, const u_char *const start, + u_char *const buf, int *buflen) +{ + return ng_parse_composite(type, + s, off, start, buf, buflen, CT_FIXEDARRAY); +} + +static int +ng_fixedarray_unparse(const struct ng_parse_type *type, + const u_char *data, int *off, char *cbuf, int cbuflen) +{ + return ng_unparse_composite(type, + data, off, cbuf, cbuflen, CT_FIXEDARRAY); +} + +static int +ng_fixedarray_getDefault(const struct ng_parse_type *type, + const u_char *const start, u_char *buf, int *buflen) +{ + int off = 0; + + return ng_parse_composite(type, + "[]", &off, start, buf, buflen, CT_FIXEDARRAY); +} + +static int +ng_fixedarray_getAlign(const struct ng_parse_type *type) +{ + const struct ng_parse_fixedarray_info *fi = type->info; + + return ALIGNMENT(fi->elementType); +} + +const struct ng_parse_type ng_parse_fixedarray_type = { + NULL, + NULL, + NULL, + ng_fixedarray_parse, + ng_fixedarray_unparse, + ng_fixedarray_getDefault, + ng_fixedarray_getAlign +}; + +/************************************************************************ + VARIABLE LENGTH ARRAY TYPE + ************************************************************************/ + +static int +ng_array_parse(const struct ng_parse_type *type, + const char *s, int *off, const u_char *const start, + u_char *const buf, int *buflen) +{ + return ng_parse_composite(type, s, off, start, buf, buflen, CT_ARRAY); +} + +static int +ng_array_unparse(const struct ng_parse_type *type, + const u_char *data, int *off, char *cbuf, int cbuflen) +{ + return ng_unparse_composite(type, data, off, cbuf, cbuflen, CT_ARRAY); +} + +static int +ng_array_getDefault(const struct ng_parse_type *type, + const u_char *const start, u_char *buf, int *buflen) +{ + int off = 0; + + return ng_parse_composite(type, + "[]", &off, start, buf, buflen, CT_ARRAY); +} + +static int +ng_array_getAlign(const struct ng_parse_type *type) +{ + const struct ng_parse_array_info *ai = type->info; + + return ALIGNMENT(ai->elementType); +} + +const struct ng_parse_type ng_parse_array_type = { + NULL, + NULL, + NULL, + ng_array_parse, + ng_array_unparse, + ng_array_getDefault, + ng_array_getAlign +}; + +/************************************************************************ + INT8 TYPE + ************************************************************************/ + +static int +ng_int8_parse(const struct ng_parse_type *type, + const char *s, int *off, const u_char *const start, + u_char *const buf, int *buflen) +{ + long val; + int8_t val8; + char *eptr; + + val = strtol(s + *off, &eptr, 0); + if (val < (int8_t)0x80 || val > (u_int8_t)0xff || eptr == s + *off) + return (EINVAL); + *off = eptr - s; + val8 = (int8_t)val; + bcopy(&val8, buf, sizeof(int8_t)); + *buflen = sizeof(int8_t); + return (0); +} + +static int +ng_int8_unparse(const struct ng_parse_type *type, + const u_char *data, int *off, char *cbuf, int cbuflen) +{ + const char *fmt; + int fval; + int error; + int8_t val; + + bcopy(data + *off, &val, sizeof(int8_t)); + switch ((intptr_t)type->info) { + case INT_SIGNED: + fmt = "%d"; + fval = val; + break; + case INT_UNSIGNED: + fmt = "%u"; + fval = (u_int8_t)val; + break; + case INT_HEX: + fmt = "0x%x"; + fval = (u_int8_t)val; + break; + default: + panic("%s: unknown type", __func__); +#ifdef RESTARTABLE_PANICS + return(0); +#endif + } + if ((error = ng_parse_append(&cbuf, &cbuflen, fmt, fval)) != 0) + return (error); + *off += sizeof(int8_t); + return (0); +} + +static int +ng_int8_getDefault(const struct ng_parse_type *type, + const u_char *const start, u_char *buf, int *buflen) +{ + int8_t val; + + if (*buflen < sizeof(int8_t)) + return (ERANGE); + val = 0; + bcopy(&val, buf, sizeof(int8_t)); + *buflen = sizeof(int8_t); + return (0); +} + +static int +ng_int8_getAlign(const struct ng_parse_type *type) +{ + return INT8_ALIGNMENT; +} + +const struct ng_parse_type ng_parse_int8_type = { + NULL, + (void *)INT_SIGNED, + NULL, + ng_int8_parse, + ng_int8_unparse, + ng_int8_getDefault, + ng_int8_getAlign +}; + +const struct ng_parse_type ng_parse_uint8_type = { + &ng_parse_int8_type, + (void *)INT_UNSIGNED +}; + +const struct ng_parse_type ng_parse_hint8_type = { + &ng_parse_int8_type, + (void *)INT_HEX +}; + +/************************************************************************ + INT16 TYPE + ************************************************************************/ + +static int +ng_int16_parse(const struct ng_parse_type *type, + const char *s, int *off, const u_char *const start, + u_char *const buf, int *buflen) +{ + long val; + int16_t val16; + char *eptr; + + val = strtol(s + *off, &eptr, 0); + if (val < (int16_t)0x8000 + || val > (u_int16_t)0xffff || eptr == s + *off) + return (EINVAL); + *off = eptr - s; + val16 = (int16_t)val; + bcopy(&val16, buf, sizeof(int16_t)); + *buflen = sizeof(int16_t); + return (0); +} + +static int +ng_int16_unparse(const struct ng_parse_type *type, + const u_char *data, int *off, char *cbuf, int cbuflen) +{ + const char *fmt; + int fval; + int error; + int16_t val; + + bcopy(data + *off, &val, sizeof(int16_t)); + switch ((intptr_t)type->info) { + case INT_SIGNED: + fmt = "%d"; + fval = val; + break; + case INT_UNSIGNED: + fmt = "%u"; + fval = (u_int16_t)val; + break; + case INT_HEX: + fmt = "0x%x"; + fval = (u_int16_t)val; + break; + default: + panic("%s: unknown type", __func__); +#ifdef RESTARTABLE_PANICS + return(0); +#endif + } + if ((error = ng_parse_append(&cbuf, &cbuflen, fmt, fval)) != 0) + return (error); + *off += sizeof(int16_t); + return (0); +} + +static int +ng_int16_getDefault(const struct ng_parse_type *type, + const u_char *const start, u_char *buf, int *buflen) +{ + int16_t val; + + if (*buflen < sizeof(int16_t)) + return (ERANGE); + val = 0; + bcopy(&val, buf, sizeof(int16_t)); + *buflen = sizeof(int16_t); + return (0); +} + +static int +ng_int16_getAlign(const struct ng_parse_type *type) +{ + return INT16_ALIGNMENT; +} + +const struct ng_parse_type ng_parse_int16_type = { + NULL, + (void *)INT_SIGNED, + NULL, + ng_int16_parse, + ng_int16_unparse, + ng_int16_getDefault, + ng_int16_getAlign +}; + +const struct ng_parse_type ng_parse_uint16_type = { + &ng_parse_int16_type, + (void *)INT_UNSIGNED +}; + +const struct ng_parse_type ng_parse_hint16_type = { + &ng_parse_int16_type, + (void *)INT_HEX +}; + +/************************************************************************ + INT32 TYPE + ************************************************************************/ + +static int +ng_int32_parse(const struct ng_parse_type *type, + const char *s, int *off, const u_char *const start, + u_char *const buf, int *buflen) +{ + long val; /* assumes long is at least 32 bits */ + int32_t val32; + char *eptr; + + if ((intptr_t)type->info == INT_SIGNED) + val = strtol(s + *off, &eptr, 0); + else + val = strtoul(s + *off, &eptr, 0); + if (val < (int32_t)0x80000000 + || val > (u_int32_t)0xffffffff || eptr == s + *off) + return (EINVAL); + *off = eptr - s; + val32 = (int32_t)val; + bcopy(&val32, buf, sizeof(int32_t)); + *buflen = sizeof(int32_t); + return (0); +} + +static int +ng_int32_unparse(const struct ng_parse_type *type, + const u_char *data, int *off, char *cbuf, int cbuflen) +{ + const char *fmt; + long fval; + int error; + int32_t val; + + bcopy(data + *off, &val, sizeof(int32_t)); + switch ((intptr_t)type->info) { + case INT_SIGNED: + fmt = "%ld"; + fval = val; + break; + case INT_UNSIGNED: + fmt = "%lu"; + fval = (u_int32_t)val; + break; + case INT_HEX: + fmt = "0x%lx"; + fval = (u_int32_t)val; + break; + default: + panic("%s: unknown type", __func__); +#ifdef RESTARTABLE_PANICS + return(0); +#endif + } + if ((error = ng_parse_append(&cbuf, &cbuflen, fmt, fval)) != 0) + return (error); + *off += sizeof(int32_t); + return (0); +} + +static int +ng_int32_getDefault(const struct ng_parse_type *type, + const u_char *const start, u_char *buf, int *buflen) +{ + int32_t val; + + if (*buflen < sizeof(int32_t)) + return (ERANGE); + val = 0; + bcopy(&val, buf, sizeof(int32_t)); + *buflen = sizeof(int32_t); + return (0); +} + +static int +ng_int32_getAlign(const struct ng_parse_type *type) +{ + return INT32_ALIGNMENT; +} + +const struct ng_parse_type ng_parse_int32_type = { + NULL, + (void *)INT_SIGNED, + NULL, + ng_int32_parse, + ng_int32_unparse, + ng_int32_getDefault, + ng_int32_getAlign +}; + +const struct ng_parse_type ng_parse_uint32_type = { + &ng_parse_int32_type, + (void *)INT_UNSIGNED +}; + +const struct ng_parse_type ng_parse_hint32_type = { + &ng_parse_int32_type, + (void *)INT_HEX +}; + +/************************************************************************ + INT64 TYPE + ************************************************************************/ + +static int +ng_int64_parse(const struct ng_parse_type *type, + const char *s, int *off, const u_char *const start, + u_char *const buf, int *buflen) +{ + quad_t val; + int64_t val64; + char *eptr; + + val = strtoq(s + *off, &eptr, 0); + if (eptr == s + *off) + return (EINVAL); + *off = eptr - s; + val64 = (int64_t)val; + bcopy(&val64, buf, sizeof(int64_t)); + *buflen = sizeof(int64_t); + return (0); +} + +static int +ng_int64_unparse(const struct ng_parse_type *type, + const u_char *data, int *off, char *cbuf, int cbuflen) +{ + const char *fmt; + long long fval; + int64_t val; + int error; + + bcopy(data + *off, &val, sizeof(int64_t)); + switch ((intptr_t)type->info) { + case INT_SIGNED: + fmt = "%lld"; + fval = val; + break; + case INT_UNSIGNED: + fmt = "%llu"; + fval = (u_int64_t)val; + break; + case INT_HEX: + fmt = "0x%llx"; + fval = (u_int64_t)val; + break; + default: + panic("%s: unknown type", __func__); +#ifdef RESTARTABLE_PANICS + return(0); +#endif + } + if ((error = ng_parse_append(&cbuf, &cbuflen, fmt, fval)) != 0) + return (error); + *off += sizeof(int64_t); + return (0); +} + +static int +ng_int64_getDefault(const struct ng_parse_type *type, + const u_char *const start, u_char *buf, int *buflen) +{ + int64_t val; + + if (*buflen < sizeof(int64_t)) + return (ERANGE); + val = 0; + bcopy(&val, buf, sizeof(int64_t)); + *buflen = sizeof(int64_t); + return (0); +} + +static int +ng_int64_getAlign(const struct ng_parse_type *type) +{ + return INT64_ALIGNMENT; +} + +const struct ng_parse_type ng_parse_int64_type = { + NULL, + (void *)INT_SIGNED, + NULL, + ng_int64_parse, + ng_int64_unparse, + ng_int64_getDefault, + ng_int64_getAlign +}; + +const struct ng_parse_type ng_parse_uint64_type = { + &ng_parse_int64_type, + (void *)INT_UNSIGNED +}; + +const struct ng_parse_type ng_parse_hint64_type = { + &ng_parse_int64_type, + (void *)INT_HEX +}; + +/************************************************************************ + STRING TYPE + ************************************************************************/ + +static int +ng_string_parse(const struct ng_parse_type *type, + const char *s, int *off, const u_char *const start, + u_char *const buf, int *buflen) +{ + char *sval; + int len; + int slen; + + if ((sval = ng_get_string_token(s, off, &len, &slen)) == NULL) + return (EINVAL); + *off += len; + bcopy(sval, buf, slen + 1); + FREE(sval, M_NETGRAPH_PARSE); + *buflen = slen + 1; + return (0); +} + +static int +ng_string_unparse(const struct ng_parse_type *type, + const u_char *data, int *off, char *cbuf, int cbuflen) +{ + const char *const raw = (const char *)data + *off; + char *const s = ng_encode_string(raw, strlen(raw)); + int error; + + if (s == NULL) + return (ENOMEM); + if ((error = ng_parse_append(&cbuf, &cbuflen, "%s", s)) != 0) { + FREE(s, M_NETGRAPH_PARSE); + return (error); + } + *off += strlen(raw) + 1; + FREE(s, M_NETGRAPH_PARSE); + return (0); +} + +static int +ng_string_getDefault(const struct ng_parse_type *type, + const u_char *const start, u_char *buf, int *buflen) +{ + + if (*buflen < 1) + return (ERANGE); + buf[0] = (u_char)'\0'; + *buflen = 1; + return (0); +} + +const struct ng_parse_type ng_parse_string_type = { + NULL, + NULL, + NULL, + ng_string_parse, + ng_string_unparse, + ng_string_getDefault, + NULL +}; + +/************************************************************************ + FIXED BUFFER STRING TYPE + ************************************************************************/ + +static int +ng_fixedstring_parse(const struct ng_parse_type *type, + const char *s, int *off, const u_char *const start, + u_char *const buf, int *buflen) +{ + const struct ng_parse_fixedstring_info *const fi = type->info; + char *sval; + int len; + int slen; + + if ((sval = ng_get_string_token(s, off, &len, &slen)) == NULL) + return (EINVAL); + if (slen + 1 > fi->bufSize) { + FREE(sval, M_NETGRAPH_PARSE); + return (E2BIG); + } + *off += len; + bcopy(sval, buf, slen); + FREE(sval, M_NETGRAPH_PARSE); + bzero(buf + slen, fi->bufSize - slen); + *buflen = fi->bufSize; + return (0); +} + +static int +ng_fixedstring_unparse(const struct ng_parse_type *type, + const u_char *data, int *off, char *cbuf, int cbuflen) +{ + const struct ng_parse_fixedstring_info *const fi = type->info; + int error, temp = *off; + + if ((error = ng_string_unparse(type, data, &temp, cbuf, cbuflen)) != 0) + return (error); + *off += fi->bufSize; + return (0); +} + +static int +ng_fixedstring_getDefault(const struct ng_parse_type *type, + const u_char *const start, u_char *buf, int *buflen) +{ + const struct ng_parse_fixedstring_info *const fi = type->info; + + if (*buflen < fi->bufSize) + return (ERANGE); + bzero(buf, fi->bufSize); + *buflen = fi->bufSize; + return (0); +} + +const struct ng_parse_type ng_parse_fixedstring_type = { + NULL, + NULL, + NULL, + ng_fixedstring_parse, + ng_fixedstring_unparse, + ng_fixedstring_getDefault, + NULL +}; + +const struct ng_parse_fixedstring_info ng_parse_nodebuf_info = { + NG_NODESIZ +}; +const struct ng_parse_type ng_parse_nodebuf_type = { + &ng_parse_fixedstring_type, + &ng_parse_nodebuf_info +}; + +const struct ng_parse_fixedstring_info ng_parse_hookbuf_info = { + NG_HOOKSIZ +}; +const struct ng_parse_type ng_parse_hookbuf_type = { + &ng_parse_fixedstring_type, + &ng_parse_hookbuf_info +}; + +const struct ng_parse_fixedstring_info ng_parse_pathbuf_info = { + NG_PATHSIZ +}; +const struct ng_parse_type ng_parse_pathbuf_type = { + &ng_parse_fixedstring_type, + &ng_parse_pathbuf_info +}; + +const struct ng_parse_fixedstring_info ng_parse_typebuf_info = { + NG_TYPESIZ +}; +const struct ng_parse_type ng_parse_typebuf_type = { + &ng_parse_fixedstring_type, + &ng_parse_typebuf_info +}; + +const struct ng_parse_fixedstring_info ng_parse_cmdbuf_info = { + NG_CMDSTRSIZ +}; +const struct ng_parse_type ng_parse_cmdbuf_type = { + &ng_parse_fixedstring_type, + &ng_parse_cmdbuf_info +}; + +/************************************************************************ + EXPLICITLY SIZED STRING TYPE + ************************************************************************/ + +static int +ng_sizedstring_parse(const struct ng_parse_type *type, + const char *s, int *off, const u_char *const start, + u_char *const buf, int *buflen) +{ + char *sval; + int len; + int slen; + + if ((sval = ng_get_string_token(s, off, &len, &slen)) == NULL) + return (EINVAL); + if (slen > USHRT_MAX) { + FREE(sval, M_NETGRAPH_PARSE); + return (EINVAL); + } + *off += len; + *((u_int16_t *)buf) = (u_int16_t)slen; + bcopy(sval, buf + 2, slen); + FREE(sval, M_NETGRAPH_PARSE); + *buflen = 2 + slen; + return (0); +} + +static int +ng_sizedstring_unparse(const struct ng_parse_type *type, + const u_char *data, int *off, char *cbuf, int cbuflen) +{ + const char *const raw = (const char *)data + *off + 2; + const int slen = *((const u_int16_t *)(data + *off)); + char *const s = ng_encode_string(raw, slen); + int error; + + if (s == NULL) + return (ENOMEM); + if ((error = ng_parse_append(&cbuf, &cbuflen, "%s", s)) != 0) { + FREE(s, M_NETGRAPH_PARSE); + return (error); + } + FREE(s, M_NETGRAPH_PARSE); + *off += slen + 2; + return (0); +} + +static int +ng_sizedstring_getDefault(const struct ng_parse_type *type, + const u_char *const start, u_char *buf, int *buflen) +{ + if (*buflen < 2) + return (ERANGE); + bzero(buf, 2); + *buflen = 2; + return (0); +} + +const struct ng_parse_type ng_parse_sizedstring_type = { + NULL, + NULL, + NULL, + ng_sizedstring_parse, + ng_sizedstring_unparse, + ng_sizedstring_getDefault, + NULL +}; + +/************************************************************************ + IP ADDRESS TYPE + ************************************************************************/ + +static int +ng_ipaddr_parse(const struct ng_parse_type *type, + const char *s, int *off, const u_char *const start, + u_char *const buf, int *buflen) +{ + int i, error; + + for (i = 0; i < 4; i++) { + if ((error = ng_int8_parse(&ng_parse_int8_type, + s, off, start, buf + i, buflen)) != 0) + return (error); + if (i < 3 && s[*off] != '.') + return (EINVAL); + (*off)++; + } + *buflen = 4; + return (0); +} + +static int +ng_ipaddr_unparse(const struct ng_parse_type *type, + const u_char *data, int *off, char *cbuf, int cbuflen) +{ + struct in_addr ip; + int error; + + bcopy(data + *off, &ip, sizeof(ip)); + if ((error = ng_parse_append(&cbuf, &cbuflen, "%d.%d.%d.%d", + ((u_char *)&ip)[0], ((u_char *)&ip)[1], + ((u_char *)&ip)[2], ((u_char *)&ip)[3])) != 0) + return (error); + *off += sizeof(ip); + return (0); +} + +static int +ng_ipaddr_getDefault(const struct ng_parse_type *type, + const u_char *const start, u_char *buf, int *buflen) +{ + struct in_addr ip = { 0 }; + + if (*buflen < sizeof(ip)) + return (ERANGE); + bcopy(&ip, buf, sizeof(ip)); + *buflen = sizeof(ip); + return (0); +} + +const struct ng_parse_type ng_parse_ipaddr_type = { + NULL, + NULL, + NULL, + ng_ipaddr_parse, + ng_ipaddr_unparse, + ng_ipaddr_getDefault, + ng_int32_getAlign +}; + +/************************************************************************ + ETHERNET ADDRESS TYPE + ************************************************************************/ + +static int +ng_enaddr_parse(const struct ng_parse_type *type, + const char *s, int *const off, const u_char *const start, + u_char *const buf, int *const buflen) +{ + char *eptr; + u_long val; + int i; + + if (*buflen < ETHER_ADDR_LEN) + return (ERANGE); + for (i = 0; i < ETHER_ADDR_LEN; i++) { + val = strtoul(s + *off, &eptr, 16); + if (val > 0xff || eptr == s + *off) + return (EINVAL); + buf[i] = (u_char)val; + *off = (eptr - s); + if (i < ETHER_ADDR_LEN - 1) { + if (*eptr != ':') + return (EINVAL); + (*off)++; + } + } + *buflen = ETHER_ADDR_LEN; + return (0); +} + +static int +ng_enaddr_unparse(const struct ng_parse_type *type, + const u_char *data, int *off, char *cbuf, int cbuflen) +{ + int len; + + len = snprintf(cbuf, cbuflen, "%02x:%02x:%02x:%02x:%02x:%02x", + data[*off], data[*off + 1], data[*off + 2], + data[*off + 3], data[*off + 4], data[*off + 5]); + if (len >= cbuflen) + return (ERANGE); + *off += ETHER_ADDR_LEN; + return (0); +} + +const struct ng_parse_type ng_parse_enaddr_type = { + NULL, + NULL, + NULL, + ng_enaddr_parse, + ng_enaddr_unparse, + NULL, + 0 +}; + +/************************************************************************ + BYTE ARRAY TYPE + ************************************************************************/ + +/* Get the length of a byte array */ +static int +ng_parse_bytearray_subtype_getLength(const struct ng_parse_type *type, + const u_char *start, const u_char *buf) +{ + ng_parse_array_getLength_t *const getLength = type->private; + + return (*getLength)(type, start, buf); +} + +/* Byte array element type is hex int8 */ +static const struct ng_parse_array_info ng_parse_bytearray_subtype_info = { + &ng_parse_hint8_type, + &ng_parse_bytearray_subtype_getLength, + NULL +}; +static const struct ng_parse_type ng_parse_bytearray_subtype = { + &ng_parse_array_type, + &ng_parse_bytearray_subtype_info +}; + +static int +ng_bytearray_parse(const struct ng_parse_type *type, + const char *s, int *off, const u_char *const start, + u_char *const buf, int *buflen) +{ + char *str; + int toklen; + int slen; + + /* We accept either an array of bytes or a string constant */ + if ((str = ng_get_string_token(s, off, &toklen, &slen)) != NULL) { + ng_parse_array_getLength_t *const getLength = type->info; + int arraylen; + + arraylen = (*getLength)(type, start, buf); + if (arraylen > *buflen) { + FREE(str, M_NETGRAPH_PARSE); + return (ERANGE); + } + if (slen > arraylen) { + FREE(str, M_NETGRAPH_PARSE); + return (E2BIG); + } + bcopy(str, buf, slen); + bzero(buf + slen, arraylen - slen); + FREE(str, M_NETGRAPH_PARSE); + *off += toklen; + *buflen = arraylen; + return (0); + } else { + struct ng_parse_type subtype; + + subtype = ng_parse_bytearray_subtype; + *(const void **)&subtype.private = type->info; + return ng_array_parse(&subtype, s, off, start, buf, buflen); + } +} + +static int +ng_bytearray_unparse(const struct ng_parse_type *type, + const u_char *data, int *off, char *cbuf, int cbuflen) +{ + struct ng_parse_type subtype; + + subtype = ng_parse_bytearray_subtype; + *(const void **)&subtype.private = type->info; + return ng_array_unparse(&subtype, data, off, cbuf, cbuflen); +} + +static int +ng_bytearray_getDefault(const struct ng_parse_type *type, + const u_char *const start, u_char *buf, int *buflen) +{ + struct ng_parse_type subtype; + + subtype = ng_parse_bytearray_subtype; + *(const void **)&subtype.private = type->info; + return ng_array_getDefault(&subtype, start, buf, buflen); +} + +const struct ng_parse_type ng_parse_bytearray_type = { + NULL, + NULL, + NULL, + ng_bytearray_parse, + ng_bytearray_unparse, + ng_bytearray_getDefault, + NULL +}; + +/************************************************************************ + STRUCT NG_MESG TYPE + ************************************************************************/ + +/* Get msg->header.arglen when "buf" is pointing to msg->data */ +static int +ng_parse_ng_mesg_getLength(const struct ng_parse_type *type, + const u_char *start, const u_char *buf) +{ + const struct ng_mesg *msg; + + msg = (const struct ng_mesg *)(buf - sizeof(*msg)); + return msg->header.arglen; +} + +/* Type for the variable length data portion of a struct ng_mesg */ +static const struct ng_parse_type ng_msg_data_type = { + &ng_parse_bytearray_type, + &ng_parse_ng_mesg_getLength +}; + +/* Type for the entire struct ng_mesg header with data section */ +static const struct ng_parse_struct_field ng_parse_ng_mesg_type_fields[] + = NG_GENERIC_NG_MESG_INFO(&ng_msg_data_type); +const struct ng_parse_type ng_parse_ng_mesg_type = { + &ng_parse_struct_type, + &ng_parse_ng_mesg_type_fields, +}; + +/************************************************************************ + COMPOSITE HELPER ROUTINES + ************************************************************************/ + +/* + * Convert a structure or array from ASCII to binary + */ +static int +ng_parse_composite(const struct ng_parse_type *type, const char *s, + int *off, const u_char *const start, u_char *const buf, int *buflen, + const enum comptype ctype) +{ + const int num = ng_get_composite_len(type, start, buf, ctype); + int nextIndex = 0; /* next implicit array index */ + u_int index; /* field or element index */ + int *foff; /* field value offsets in string */ + int align, len, blen, error = 0; + + /* Initialize */ + MALLOC(foff, int *, num * sizeof(*foff), M_NETGRAPH_PARSE, M_NOWAIT | M_ZERO); + if (foff == NULL) { + error = ENOMEM; + goto done; + } + + /* Get opening brace/bracket */ + if (ng_parse_get_token(s, off, &len) + != (ctype == CT_STRUCT ? T_LBRACE : T_LBRACKET)) { + error = EINVAL; + goto done; + } + *off += len; + + /* Get individual element value positions in the string */ + for (;;) { + enum ng_parse_token tok; + + /* Check for closing brace/bracket */ + tok = ng_parse_get_token(s, off, &len); + if (tok == (ctype == CT_STRUCT ? T_RBRACE : T_RBRACKET)) { + *off += len; + break; + } + + /* For arrays, the 'name' (ie, index) is optional, so + distinguish name from values by seeing if the next + token is an equals sign */ + if (ctype != CT_STRUCT) { + int len2, off2; + char *eptr; + + /* If an opening brace/bracket, index is implied */ + if (tok == T_LBRACE || tok == T_LBRACKET) { + index = nextIndex++; + goto gotIndex; + } + + /* Might be an index, might be a value, either way... */ + if (tok != T_WORD) { + error = EINVAL; + goto done; + } + + /* If no equals sign follows, index is implied */ + off2 = *off + len; + if (ng_parse_get_token(s, &off2, &len2) != T_EQUALS) { + index = nextIndex++; + goto gotIndex; + } + + /* Index was specified explicitly; parse it */ + index = (u_int)strtoul(s + *off, &eptr, 0); + if (index < 0 || eptr - (s + *off) != len) { + error = EINVAL; + goto done; + } + nextIndex = index + 1; + *off += len + len2; + } else { /* a structure field */ + const struct ng_parse_struct_field *const + fields = type->info; + + /* Find the field by name (required) in field list */ + if (tok != T_WORD) { + error = EINVAL; + goto done; + } + for (index = 0; index < num; index++) { + const struct ng_parse_struct_field *const + field = &fields[index]; + + if (strncmp(&s[*off], field->name, len) == 0 + && field->name[len] == '\0') + break; + } + if (index == num) { + error = ENOENT; + goto done; + } + *off += len; + + /* Get equals sign */ + if (ng_parse_get_token(s, off, &len) != T_EQUALS) { + error = EINVAL; + goto done; + } + *off += len; + } +gotIndex: + + /* Check array index */ + if (index >= num) { + error = E2BIG; + goto done; + } + + /* Save value's position and skip over it for now */ + if (foff[index] != 0) { + error = EALREADY; /* duplicate */ + goto done; + } + while (isspace(s[*off])) + (*off)++; + foff[index] = *off; + if ((error = ng_parse_skip_value(s, *off, &len)) != 0) + goto done; + *off += len; + } + + /* Now build binary structure from supplied values and defaults */ + for (blen = index = 0; index < num; index++) { + const struct ng_parse_type *const + etype = ng_get_composite_etype(type, index, ctype); + int k, pad, vlen; + + /* Zero-pad any alignment bytes */ + pad = ng_parse_get_elem_pad(type, index, ctype, blen); + for (k = 0; k < pad; k++) { + if (blen >= *buflen) { + error = ERANGE; + goto done; + } + buf[blen++] = 0; + } + + /* Get value */ + vlen = *buflen - blen; + if (foff[index] == 0) { /* use default value */ + error = ng_get_composite_elem_default(type, index, + start, buf + blen, &vlen, ctype); + } else { /* parse given value */ + *off = foff[index]; + error = INVOKE(etype, parse)(etype, + s, off, start, buf + blen, &vlen); + } + if (error != 0) + goto done; + blen += vlen; + } + + /* Make total composite structure size a multiple of its alignment */ + if ((align = ALIGNMENT(type)) != 0) { + while (blen % align != 0) { + if (blen >= *buflen) { + error = ERANGE; + goto done; + } + buf[blen++] = 0; + } + } + + /* Done */ + *buflen = blen; +done: + if (foff != NULL) + FREE(foff, M_NETGRAPH_PARSE); + return (error); +} + +/* + * Convert an array or structure from binary to ASCII + */ +static int +ng_unparse_composite(const struct ng_parse_type *type, const u_char *data, + int *off, char *cbuf, int cbuflen, const enum comptype ctype) +{ + const struct ng_mesg *const hdr + = (const struct ng_mesg *)(data - sizeof(*hdr)); + const int num = ng_get_composite_len(type, data, data + *off, ctype); + const int workSize = 20 * 1024; /* XXX hard coded constant */ + int nextIndex = 0, didOne = 0; + int error, index; + u_char *workBuf; + + /* Get workspace for checking default values */ + MALLOC(workBuf, u_char *, workSize, M_NETGRAPH_PARSE, M_NOWAIT); + if (workBuf == NULL) + return (ENOMEM); + + /* Opening brace/bracket */ + if ((error = ng_parse_append(&cbuf, &cbuflen, "%c", + (ctype == CT_STRUCT) ? '{' : '[')) != 0) + goto fail; + + /* Do each item */ + for (index = 0; index < num; index++) { + const struct ng_parse_type *const + etype = ng_get_composite_etype(type, index, ctype); + + /* Skip any alignment pad bytes */ + *off += ng_parse_get_elem_pad(type, index, ctype, *off); + + /* + * See if element is equal to its default value; skip if so. + * Copy struct ng_mesg header for types that peek into it. + */ + if (sizeof(*hdr) + *off < workSize) { + int tempsize = workSize - sizeof(*hdr) - *off; + + bcopy(hdr, workBuf, sizeof(*hdr) + *off); + if (ng_get_composite_elem_default(type, index, workBuf + + sizeof(*hdr), workBuf + sizeof(*hdr) + *off, + &tempsize, ctype) == 0 + && bcmp(workBuf + sizeof(*hdr) + *off, + data + *off, tempsize) == 0) { + *off += tempsize; + continue; + } + } + + /* Print name= */ + if ((error = ng_parse_append(&cbuf, &cbuflen, " ")) != 0) + goto fail; + if (ctype != CT_STRUCT) { + if (index != nextIndex) { + nextIndex = index; + if ((error = ng_parse_append(&cbuf, + &cbuflen, "%d=", index)) != 0) + goto fail; + } + nextIndex++; + } else { + const struct ng_parse_struct_field *const + fields = type->info; + + if ((error = ng_parse_append(&cbuf, + &cbuflen, "%s=", fields[index].name)) != 0) + goto fail; + } + + /* Print value */ + if ((error = INVOKE(etype, unparse) + (etype, data, off, cbuf, cbuflen)) != 0) { + FREE(workBuf, M_NETGRAPH_PARSE); + return (error); + } + cbuflen -= strlen(cbuf); + cbuf += strlen(cbuf); + didOne = 1; + } + + /* Closing brace/bracket */ + error = ng_parse_append(&cbuf, &cbuflen, "%s%c", + didOne ? " " : "", (ctype == CT_STRUCT) ? '}' : ']'); + +fail: + /* Clean up after failure */ + FREE(workBuf, M_NETGRAPH_PARSE); + return (error); +} + +/* + * Generate the default value for an element of an array or structure + * Returns EOPNOTSUPP if default value is unspecified. + */ +static int +ng_get_composite_elem_default(const struct ng_parse_type *type, + int index, const u_char *const start, u_char *buf, int *buflen, + const enum comptype ctype) +{ + const struct ng_parse_type *etype; + ng_getDefault_t *func; + + switch (ctype) { + case CT_STRUCT: + break; + case CT_ARRAY: + { + const struct ng_parse_array_info *const ai = type->info; + + if (ai->getDefault != NULL) { + return (*ai->getDefault)(type, + index, start, buf, buflen); + } + break; + } + case CT_FIXEDARRAY: + { + const struct ng_parse_fixedarray_info *const fi = type->info; + + if (*fi->getDefault != NULL) { + return (*fi->getDefault)(type, + index, start, buf, buflen); + } + break; + } + default: + panic("%s", __func__); + } + + /* Default to element type default */ + etype = ng_get_composite_etype(type, index, ctype); + func = METHOD(etype, getDefault); + if (func == NULL) + return (EOPNOTSUPP); + return (*func)(etype, start, buf, buflen); +} + +/* + * Get the number of elements in a struct, variable or fixed array. + */ +static int +ng_get_composite_len(const struct ng_parse_type *type, + const u_char *const start, const u_char *buf, + const enum comptype ctype) +{ + switch (ctype) { + case CT_STRUCT: + { + const struct ng_parse_struct_field *const fields = type->info; + int numFields = 0; + + for (numFields = 0; ; numFields++) { + const struct ng_parse_struct_field *const + fi = &fields[numFields]; + + if (fi->name == NULL) + break; + } + return (numFields); + } + case CT_ARRAY: + { + const struct ng_parse_array_info *const ai = type->info; + + return (*ai->getLength)(type, start, buf); + } + case CT_FIXEDARRAY: + { + const struct ng_parse_fixedarray_info *const fi = type->info; + + return fi->length; + } + default: + panic("%s", __func__); + } + return (0); +} + +/* + * Return the type of the index'th element of a composite structure + */ +static const struct ng_parse_type * +ng_get_composite_etype(const struct ng_parse_type *type, + int index, const enum comptype ctype) +{ + const struct ng_parse_type *etype = NULL; + + switch (ctype) { + case CT_STRUCT: + { + const struct ng_parse_struct_field *const fields = type->info; + + etype = fields[index].type; + break; + } + case CT_ARRAY: + { + const struct ng_parse_array_info *const ai = type->info; + + etype = ai->elementType; + break; + } + case CT_FIXEDARRAY: + { + const struct ng_parse_fixedarray_info *const fi = type->info; + + etype = fi->elementType; + break; + } + default: + panic("%s", __func__); + } + return (etype); +} + +/* + * Get the number of bytes to skip to align for the next + * element in a composite structure. + */ +static int +ng_parse_get_elem_pad(const struct ng_parse_type *type, + int index, enum comptype ctype, int posn) +{ + const struct ng_parse_type *const + etype = ng_get_composite_etype(type, index, ctype); + int align; + + /* Get element's alignment, and possibly override */ + align = ALIGNMENT(etype); + if (ctype == CT_STRUCT) { + const struct ng_parse_struct_field *const fields = type->info; + + if (fields[index].alignment != 0) + align = fields[index].alignment; + } + + /* Return number of bytes to skip to align */ + return (align ? (align - (posn % align)) % align : 0); +} + +/************************************************************************ + PARSING HELPER ROUTINES + ************************************************************************/ + +/* + * Append to a fixed length string buffer. + */ +static int +ng_parse_append(char **cbufp, int *cbuflenp, const char *fmt, ...) +{ + va_list args; + int len; + + va_start(args, fmt); + len = vsnprintf(*cbufp, *cbuflenp, fmt, args); + va_end(args); + if (len >= *cbuflenp) + return ERANGE; + *cbufp += len; + *cbuflenp -= len; + + return (0); +} + +/* + * Skip over a value + */ +static int +ng_parse_skip_value(const char *s, int off0, int *lenp) +{ + int len, nbracket, nbrace; + int off = off0; + + len = nbracket = nbrace = 0; + do { + switch (ng_parse_get_token(s, &off, &len)) { + case T_LBRACKET: + nbracket++; + break; + case T_LBRACE: + nbrace++; + break; + case T_RBRACKET: + if (nbracket-- == 0) + return (EINVAL); + break; + case T_RBRACE: + if (nbrace-- == 0) + return (EINVAL); + break; + case T_EOF: + return (EINVAL); + default: + break; + } + off += len; + } while (nbracket > 0 || nbrace > 0); + *lenp = off - off0; + return (0); +} + +/* + * Find the next token in the string, starting at offset *startp. + * Returns the token type, with *startp pointing to the first char + * and *lenp the length. + */ +enum ng_parse_token +ng_parse_get_token(const char *s, int *startp, int *lenp) +{ + char *t; + int i; + + while (isspace(s[*startp])) + (*startp)++; + switch (s[*startp]) { + case '\0': + *lenp = 0; + return T_EOF; + case '{': + *lenp = 1; + return T_LBRACE; + case '}': + *lenp = 1; + return T_RBRACE; + case '[': + *lenp = 1; + return T_LBRACKET; + case ']': + *lenp = 1; + return T_RBRACKET; + case '=': + *lenp = 1; + return T_EQUALS; + case '"': + if ((t = ng_get_string_token(s, startp, lenp, NULL)) == NULL) + return T_ERROR; + FREE(t, M_NETGRAPH_PARSE); + return T_STRING; + default: + for (i = *startp + 1; s[i] != '\0' && !isspace(s[i]) + && s[i] != '{' && s[i] != '}' && s[i] != '[' + && s[i] != ']' && s[i] != '=' && s[i] != '"'; i++) + ; + *lenp = i - *startp; + return T_WORD; + } +} + +/* + * Get a string token, which must be enclosed in double quotes. + * The normal C backslash escapes are recognized. + */ +char * +ng_get_string_token(const char *s, int *startp, int *lenp, int *slenp) +{ + char *cbuf, *p; + int start, off; + int slen; + + while (isspace(s[*startp])) + (*startp)++; + start = *startp; + if (s[*startp] != '"') + return (NULL); + MALLOC(cbuf, char *, strlen(s + start), M_NETGRAPH_PARSE, M_NOWAIT); + if (cbuf == NULL) + return (NULL); + strcpy(cbuf, s + start + 1); + for (slen = 0, off = 1, p = cbuf; *p != '\0'; slen++, off++, p++) { + if (*p == '"') { + *p = '\0'; + *lenp = off + 1; + if (slenp != NULL) + *slenp = slen; + return (cbuf); + } else if (p[0] == '\\' && p[1] != '\0') { + int x, k; + char *v; + + strcpy(p, p + 1); + v = p; + switch (*p) { + case 't': + *v = '\t'; + off++; + continue; + case 'n': + *v = '\n'; + off++; + continue; + case 'r': + *v = '\r'; + off++; + continue; + case 'v': + *v = '\v'; + off++; + continue; + case 'f': + *v = '\f'; + off++; + continue; + case '"': + *v = '"'; + off++; + continue; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + for (x = k = 0; + k < 3 && *v >= '0' && *v <= '7'; v++) { + x = (x << 3) + (*v - '0'); + off++; + } + *--v = (char)x; + break; + case 'x': + for (v++, x = k = 0; + k < 2 && isxdigit(*v); v++) { + x = (x << 4) + (isdigit(*v) ? + (*v - '0') : + (tolower(*v) - 'a' + 10)); + off++; + } + *--v = (char)x; + break; + default: + continue; + } + strcpy(p, v); + } + } + FREE(cbuf, M_NETGRAPH_PARSE); + return (NULL); /* no closing quote */ +} + +/* + * Encode a string so it can be safely put in double quotes. + * Caller must free the result. Exactly "slen" characters + * are encoded. + */ +char * +ng_encode_string(const char *raw, int slen) +{ + char *cbuf; + int off = 0; + int i; + + MALLOC(cbuf, char *, strlen(raw) * 4 + 3, M_NETGRAPH_PARSE, M_NOWAIT); + if (cbuf == NULL) + return (NULL); + cbuf[off++] = '"'; + for (i = 0; i < slen; i++, raw++) { + switch (*raw) { + case '\t': + cbuf[off++] = '\\'; + cbuf[off++] = 't'; + break; + case '\f': + cbuf[off++] = '\\'; + cbuf[off++] = 'f'; + break; + case '\n': + cbuf[off++] = '\\'; + cbuf[off++] = 'n'; + break; + case '\r': + cbuf[off++] = '\\'; + cbuf[off++] = 'r'; + break; + case '\v': + cbuf[off++] = '\\'; + cbuf[off++] = 'v'; + break; + case '"': + case '\\': + cbuf[off++] = '\\'; + cbuf[off++] = *raw; + break; + default: + if (*raw < 0x20 || *raw > 0x7e) { + off += sprintf(cbuf + off, + "\\x%02x", (u_char)*raw); + break; + } + cbuf[off++] = *raw; + break; + } + } + cbuf[off++] = '"'; + cbuf[off] = '\0'; + return (cbuf); +} + +/************************************************************************ + VIRTUAL METHOD LOOKUP + ************************************************************************/ + +static ng_parse_t * +ng_get_parse_method(const struct ng_parse_type *t) +{ + while (t != NULL && t->parse == NULL) + t = t->supertype; + return (t ? t->parse : NULL); +} + +static ng_unparse_t * +ng_get_unparse_method(const struct ng_parse_type *t) +{ + while (t != NULL && t->unparse == NULL) + t = t->supertype; + return (t ? t->unparse : NULL); +} + +static ng_getDefault_t * +ng_get_getDefault_method(const struct ng_parse_type *t) +{ + while (t != NULL && t->getDefault == NULL) + t = t->supertype; + return (t ? t->getDefault : NULL); +} + +static ng_getAlign_t * +ng_get_getAlign_method(const struct ng_parse_type *t) +{ + while (t != NULL && t->getAlign == NULL) + t = t->supertype; + return (t ? t->getAlign : NULL); +} + diff --git a/sys/netgraph7/ng_parse.h b/sys/netgraph7/ng_parse.h new file mode 100644 index 0000000000..6f32c58c09 --- /dev/null +++ b/sys/netgraph7/ng_parse.h @@ -0,0 +1,540 @@ +/* + * ng_parse.h + */ + +/*- + * Copyright (c) 1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $Whistle: ng_parse.h,v 1.2 1999/11/29 01:43:48 archie Exp $ + * $FreeBSD: src/sys/netgraph/ng_parse.h,v 1.13 2005/10/28 14:41:28 ru Exp $ + */ + +#ifndef _NETGRAPH_NG_PARSE_H_ +#define _NETGRAPH_NG_PARSE_H_ + +/* + + This defines a library of routines for converting between various C + language types in binary form and ASCII strings. Types are user + definable. Several pre-defined types are supplied, for some common + C types: structures, variable and fixed length arrays, integer types, + variable and fixed length strings, IP addresses, etc. + + A netgraph node type may provide a list of types that correspond to + the structures it expects to send and receive in the arguments field + of a control message. This allows these messages to be converted + between their native binary form and the corresponding ASCII form. + + A future use of the ASCII form may be for inter-machine communication + of control messages, because the ASCII form is machine independent + whereas the native binary form is not. + + Syntax + ------ + + Structures: + + '{' [ = ... ] '}' + + Omitted fields have their default values by implication. + The order in which the fields are specified does not matter. + + Arrays: + + '[' [ [index=] ... ] ']' + + Element value may be specified with or without the "=" prefix; + If omitted, the index after the previous element is used. + Omitted fields have their default values by implication. + + Strings: + + "foo bar blah\r\n" + + That is, strings are specified just like C strings. The usual + backslash escapes are accepted. + + Other simple types (integers, IP addresses) have their obvious forms. + + Example + ------- + + Suppose we have a netgraph command that takes as an argument + a 'struct foo' shown below. Here is an example of a possible + value for the structure, and the corresponding ASCII encoding + of that value: + + Structure Binary value + --------- ------------ + + struct foo { + struct in_addr ip; 01 02 03 04 + int bar; 00 00 00 00 + char label[8]; 61 62 63 0a 00 00 00 00 + u_char alen; 03 00 + short ary[]; 05 00 00 00 0a 00 + }; + + ASCII value + ----------- + + { ip=1.2.3.4 label="abc\n" alen=3 ary=[ 5 2=10 ] } + + Note that omitted fields and array elements get their default + values ("bar" and ary[2]), and that the alignment is handled + automatically (the extra 00 byte after "alen"). Also, since byte + order and alignment are inherently machine dependent, so is this + conversion process. The above example shows an x86 (little + endian) encoding. Also the above example is tricky because the + structure is variable length, depending on 'alen', the number of + elements in the array 'ary'. + + Here is how one would define a parse type for the above structure, + subclassing the pre-defined types below. We construct the type in + a 'bottom up' fashion, defining each field's type first, then the + type for the whole structure ('//' comments used to avoid breakage). + + // Super-type info for 'label' field + struct ng_parse_fixedstring_info foo_label_info = { 8 }; + + // Parse type for 'label' field + struct ng_parse_type foo_label_type = { + &ng_parse_fixedstring_type // super-type + &foo_label_info // super-type info + }; + + #define OFFSETOF(s, e) ((char *)&((s *)0)->e - (char *)((s *)0)) + + // Function to compute the length of the array 'ary', which + // is variable length, depending on the previous field 'alen'. + // Upon entry 'buf' will be pointing at &ary[0]. + int + foo_ary_getLength(const struct ng_parse_type *type, + const u_char *start, const u_char *buf) + { + const struct foo *f; + + f = (const struct foo *)(buf - OFFSETOF(struct foo, ary)); + return f->alen; + } + + // Super-type info for 'ary' field + struct ng_parse_array_info foo_ary_info = { + &ng_parse_int16_type, // element type + &foo_ary_getLength // func to get array length + } + + // Parse type for 'ary' field + struct ng_parse_type foo_ary_type = { + &ng_parse_array_type, // super-type + &foo_ary_info // super-type info + }; + + // Super-type info for struct foo + struct ng_parse_struct_field foo_fields[] = { + { "ip", &ng_parse_ipaddr_type }, + { "bar", &ng_parse_int32_type }, + { "label", &foo_label_type }, + { "alen", &ng_parse_uint8_type }, + { "ary", &foo_ary_type }, + { NULL } + }; + + // Parse type for struct foo + struct ng_parse_type foo_type = { + &ng_parse_struct_type, // super-type + &foo_fields // super-type info + }; + + To define a type, you can define it as a sub-type of a predefined + type as shown above, possibly overriding some of the predefined + type's methods, or define an entirely new syntax, with the restriction + that the ASCII representation of your type's value must not contain + any whitespace or any of these characters: { } [ ] = " + + See ng_ksocket.c for an example of how to do this for 'struct sockaddr'. + See ng_parse.c to see implementations of the pre-defined types below. + +*/ + +/************************************************************************ + METHODS REQUIRED BY A TYPE + ************************************************************************/ + +/* + * Three methods are required for a type. These may be given explicitly + * or, if NULL, inherited from the super-type. The 'getDefault' method + * is always optional; the others are required if there is no super-type. + */ + +struct ng_parse_type; + +/* + * Convert ASCII to binary according to the supplied type. + * + * The ASCII characters begin at offset *off in 'string'. The binary + * representation is put into 'buf', which has at least *buflen bytes. + * 'start' points to the first byte output by ng_parse() (ie, start <= buf). + * + * Upon return, *buflen contains the length of the new binary data, and + * *off is updated to point just past the end of the parsed range of + * characters, or, in the case of an error, to the offending character(s). + * + * Return values: + * 0 Success; *buflen holds the length of the data + * and *off points just past the last char parsed. + * EALREADY Field specified twice + * ENOENT Unknown field + * E2BIG Array or character string overflow + * ERANGE Output was longer than *buflen bytes + * EINVAL Parse failure or other invalid content + * ENOMEM Out of memory + * EOPNOTSUPP Mandatory array/structure element missing + */ +typedef int ng_parse_t(const struct ng_parse_type *type, const char *string, + int *off, const u_char *start, + u_char *buf, int *buflen); + +/* + * Convert binary to ASCII according to the supplied type. + * + * The results are put into 'buf', which is at least buflen bytes long. + * *off points to the current byte in 'data' and should be updated + * before return to point just past the last byte unparsed. + * + * Returns: + * 0 Success + * ERANGE Output was longer than buflen bytes + */ +typedef int ng_unparse_t(const struct ng_parse_type *type, + const u_char *data, int *off, char *buf, int buflen); + +/* + * Compute the default value according to the supplied type. + * + * Store the result in 'buf', which is at least *buflen bytes long. + * Upon return *buflen contains the length of the output. + * + * Returns: + * 0 Success + * ERANGE Output was longer than *buflen bytes + * EOPNOTSUPP Default value is not specified for this type + */ +typedef int ng_getDefault_t(const struct ng_parse_type *type, + const u_char *start, u_char *buf, int *buflen); + +/* + * Return the alignment requirement of this type. Zero is same as one. + */ +typedef int ng_getAlign_t(const struct ng_parse_type *type); + +/************************************************************************ + TYPE DEFINITION + ************************************************************************/ + +/* + * This structure describes a type, which may be a sub-type of another + * type by pointing to it with 'supertype' and possibly omitting methods. + * Typically the super-type requires some type-specific info, which is + * supplied by the 'info' field. + * + * The 'private' field is ignored by all of the pre-defined types. + * Sub-types may use it as they see fit. + * + * The 'getDefault' method may always be omitted (even if there is no + * super-type), which means the value for any item of this type must + * always be explicitly given. + */ +struct ng_parse_type { + const struct ng_parse_type *supertype; /* super-type, if any */ + const void *info; /* type-specific info */ + void *private; /* client private info */ + ng_parse_t *parse; /* parse method */ + ng_unparse_t *unparse; /* unparse method */ + ng_getDefault_t *getDefault; /* get default value method */ + ng_getAlign_t *getAlign; /* get alignment */ +}; + +/************************************************************************ + PRE-DEFINED TYPES + ************************************************************************/ + +/* + * STRUCTURE TYPE + * + * This type supports arbitrary C structures. The normal field alignment + * rules for the local machine are applied. Fields are always parsed in + * field order, no matter what order they are listed in the ASCII string. + * + * Default value: Determined on a per-field basis + * Additional info: struct ng_parse_struct_field * + */ +extern const struct ng_parse_type ng_parse_struct_type; + +/* Each field has a name, type, and optional alignment override. If the + override is non-zero, the alignment is determined from the field type. + Note: add an extra struct ng_parse_struct_field with name == NULL + to indicate the end of the list. */ +struct ng_parse_struct_field { + const char *name; /* field name */ + const struct ng_parse_type *type; /* field type */ + int alignment; /* override alignment */ +}; + +/* + * FIXED LENGTH ARRAY TYPE + * + * This type supports fixed length arrays, having any element type. + * + * Default value: As returned by getDefault for each index + * Additional info: struct ng_parse_fixedarray_info * + */ +extern const struct ng_parse_type ng_parse_fixedarray_type; + +/* + * Get the default value for the element at index 'index'. This method + * may be NULL, in which case the default value is computed from the + * element type. Otherwise, it should fill in the default value at *buf + * (having size *buflen) and update *buflen to the length of the filled-in + * value before return. If there is not enough routine return ERANGE. + */ +typedef int ng_parse_array_getDefault_t(const struct ng_parse_type *type, + int index, const u_char *start, + u_char *buf, int *buflen); + +struct ng_parse_fixedarray_info { + const struct ng_parse_type *elementType; + int length; + ng_parse_array_getDefault_t *getDefault; +}; + +/* + * VARIABLE LENGTH ARRAY TYPE + * + * Same as fixed length arrays, except that the length is determined + * by a function instead of a constant value. + * + * Default value: Same as with fixed length arrays + * Additional info: struct ng_parse_array_info * + */ +extern const struct ng_parse_type ng_parse_array_type; + +/* + * Return the length of the array. If the array is a field in a structure, + * all prior fields are guaranteed to be filled in already. Upon entry, + * 'start' is equal to the first byte parsed in this run, while 'buf' points + * to the first element of the array to be filled in. + */ +typedef int ng_parse_array_getLength_t(const struct ng_parse_type *type, + const u_char *start, const u_char *buf); + +struct ng_parse_array_info { + const struct ng_parse_type *elementType; + ng_parse_array_getLength_t *getLength; + ng_parse_array_getDefault_t *getDefault; +}; + +/* + * ARBITRARY LENGTH STRING TYPE + * + * For arbirary length, NUL-terminated strings. + * + * Default value: Empty string + * Additional info: None required + */ +extern const struct ng_parse_type ng_parse_string_type; + +/* + * BOUNDED LENGTH STRING TYPE + * + * These are strings that have a fixed-size buffer, and always include + * a terminating NUL character. + * + * Default value: Empty string + * Additional info: struct ng_parse_fixedstring_info * + */ +extern const struct ng_parse_type ng_parse_fixedstring_type; + +struct ng_parse_fixedstring_info { + int bufSize; /* size of buffer (including NUL) */ +}; + +/* + * EXPLICITLY SIZED STRING TYPE + * + * These are strings that have a two byte length field preceding them. + * Parsed strings are NOT NUL-terminated. + * + * Default value: Empty string + * Additional info: None + */ +extern const struct ng_parse_type ng_parse_sizedstring_type; + +/* + * COMMONLY USED BOUNDED LENGTH STRING TYPES + */ +extern const struct ng_parse_type ng_parse_nodebuf_type; /* NG_NODESIZ */ +extern const struct ng_parse_type ng_parse_hookbuf_type; /* NG_HOOKSIZ */ +extern const struct ng_parse_type ng_parse_pathbuf_type; /* NG_PATHSIZ */ +extern const struct ng_parse_type ng_parse_typebuf_type; /* NG_TYPESIZ */ +extern const struct ng_parse_type ng_parse_cmdbuf_type; /* NG_CMDSTRSIZ */ + +/* + * INTEGER TYPES + * + * Default value: 0 + * Additional info: None required + */ +extern const struct ng_parse_type ng_parse_int8_type; +extern const struct ng_parse_type ng_parse_int16_type; +extern const struct ng_parse_type ng_parse_int32_type; +extern const struct ng_parse_type ng_parse_int64_type; + +/* Same thing but unparse as unsigned quantities */ +extern const struct ng_parse_type ng_parse_uint8_type; +extern const struct ng_parse_type ng_parse_uint16_type; +extern const struct ng_parse_type ng_parse_uint32_type; +extern const struct ng_parse_type ng_parse_uint64_type; + +/* Same thing but unparse as hex quantities, e.g., "0xe7" */ +extern const struct ng_parse_type ng_parse_hint8_type; +extern const struct ng_parse_type ng_parse_hint16_type; +extern const struct ng_parse_type ng_parse_hint32_type; +extern const struct ng_parse_type ng_parse_hint64_type; + +/* + * IP ADDRESS TYPE + * + * Default value: 0.0.0.0 + * Additional info: None required + */ +extern const struct ng_parse_type ng_parse_ipaddr_type; + +/* + * ETHERNET ADDRESS TYPE + * + * Default value: None + * Additional info: None required + */ +extern const struct ng_parse_type ng_parse_enaddr_type; + +/* + * VARIABLE LENGTH BYTE ARRAY TYPE + * + * The bytes are displayed in hex. The ASCII form may be either an + * array of bytes or a string constant, in which case the array is + * zero-filled after the string bytes. + * + * Default value: All bytes are zero + * Additional info: ng_parse_array_getLength_t * + */ +extern const struct ng_parse_type ng_parse_bytearray_type; + +/* + * NETGRAPH CONTROL MESSAGE TYPE + * + * This is the parse type for a struct ng_mesg. + * + * Default value: All fields zero + * Additional info: None required + */ +extern const struct ng_parse_type ng_parse_ng_mesg_type; + +/************************************************************************ + CONVERSTION AND PARSING ROUTINES + ************************************************************************/ + +/* Tokens for parsing structs and arrays */ +enum ng_parse_token { + T_LBRACE, /* '{' */ + T_RBRACE, /* '}' */ + T_LBRACKET, /* '[' */ + T_RBRACKET, /* ']' */ + T_EQUALS, /* '=' */ + T_STRING, /* string in double quotes */ + T_ERROR, /* error parsing string in double quotes */ + T_WORD, /* anything else containing no whitespace */ + T_EOF, /* end of string reached */ +}; + +/* + * See typedef ng_parse_t for definition + */ +extern int ng_parse(const struct ng_parse_type *type, const char *string, + int *off, u_char *buf, int *buflen); + +/* + * See typedef ng_unparse_t for definition (*off assumed to be zero). + */ +extern int ng_unparse(const struct ng_parse_type *type, + const u_char *data, char *buf, int buflen); + +/* + * See typedef ng_getDefault_t for definition + */ +extern int ng_parse_getDefault(const struct ng_parse_type *type, + u_char *buf, int *buflen); + +/* + * Parse a token: '*startp' is the offset to start looking. Upon + * successful return, '*startp' equals the beginning of the token + * and '*lenp' the length. If error, '*startp' points at the + * offending character(s). + */ +extern enum ng_parse_token ng_parse_get_token(const char *s, + int *startp, int *lenp); + +/* + * Like above, but specifically for getting a string token and returning + * the string value. The string token must be enclosed in double quotes + * and the normal C backslash escapes are recognized. The caller must + * eventually free() the returned result. Returns NULL if token is + * not a string token, or parse or other error. Otherwise, *lenp contains + * the number of characters parsed, and *slenp (if not NULL) contains + * the actual number of characters in the parsed string. + */ +extern char *ng_get_string_token(const char *s, int *startp, + int *lenp, int *slenp); + +/* + * Convert a raw string into a doubly-quoted string including any + * necessary backslash escapes. Caller must free the result. + * Returns NULL if ENOMEM. Normally "slen" should equal strlen(s) + * unless you want to encode NUL bytes. + */ +extern char *ng_encode_string(const char *s, int slen); + +#endif /* _NETGRAPH_NG_PARSE_H_ */ + diff --git a/sys/netgraph7/ng_ppp.c b/sys/netgraph7/ng_ppp.c new file mode 100644 index 0000000000..657268e890 --- /dev/null +++ b/sys/netgraph7/ng_ppp.c @@ -0,0 +1,2604 @@ +/*- + * Copyright (c) 1996-2000 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Copyright (c) 2007 Alexander Motin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Authors: Archie Cobbs , Alexander Motin + * + * $FreeBSD: src/sys/netgraph/ng_ppp.c,v 1.75 2008/02/06 20:37:34 mav Exp $ + * $Whistle: ng_ppp.c,v 1.24 1999/11/01 09:24:52 julian Exp $ + */ + +/* + * PPP node type data-flow. + * + * hook xmit layer recv hook + * ------------------------------------ + * inet -> -> inet + * ipv6 -> -> ipv6 + * ipx -> proto -> ipx + * atalk -> -> atalk + * bypass -> -> bypass + * -hcomp_xmit()----------proto_recv()- + * vjc_ip <- <- vjc_ip + * vjc_comp -> header compression -> vjc_comp + * vjc_uncomp -> -> vjc_uncomp + * vjc_vjip -> + * -comp_xmit()-----------hcomp_recv()- + * compress <- compression <- decompress + * compress -> -> decompress + * -crypt_xmit()-----------comp_recv()- + * encrypt <- encryption <- decrypt + * encrypt -> -> decrypt + * -ml_xmit()-------------crypt_recv()- + * multilink + * -link_xmit()--------------ml_recv()- + * linkX <- link <- linkX + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_PPP, "netgraph_ppp", "netgraph ppp node"); +#else +#define M_NETGRAPH_PPP M_NETGRAPH +#endif + +#define PROT_VALID(p) (((p) & 0x0101) == 0x0001) +#define PROT_COMPRESSABLE(p) (((p) & 0xff00) == 0x0000) + +/* Some PPP protocol numbers we're interested in */ +#define PROT_ATALK 0x0029 +#define PROT_COMPD 0x00fd +#define PROT_CRYPTD 0x0053 +#define PROT_IP 0x0021 +#define PROT_IPV6 0x0057 +#define PROT_IPX 0x002b +#define PROT_LCP 0xc021 +#define PROT_MP 0x003d +#define PROT_VJCOMP 0x002d +#define PROT_VJUNCOMP 0x002f + +/* Multilink PPP definitions */ +#define MP_MIN_MRRU 1500 /* per RFC 1990 */ +#define MP_INITIAL_SEQ 0 /* per RFC 1990 */ +#define MP_MIN_LINK_MRU 32 + +#define MP_SHORT_SEQ_MASK 0x00000fff /* short seq # mask */ +#define MP_SHORT_SEQ_HIBIT 0x00000800 /* short seq # high bit */ +#define MP_SHORT_FIRST_FLAG 0x00008000 /* first fragment in frame */ +#define MP_SHORT_LAST_FLAG 0x00004000 /* last fragment in frame */ + +#define MP_LONG_SEQ_MASK 0x00ffffff /* long seq # mask */ +#define MP_LONG_SEQ_HIBIT 0x00800000 /* long seq # high bit */ +#define MP_LONG_FIRST_FLAG 0x80000000 /* first fragment in frame */ +#define MP_LONG_LAST_FLAG 0x40000000 /* last fragment in frame */ + +#define MP_NOSEQ 0x7fffffff /* impossible sequence number */ + +/* Sign extension of MP sequence numbers */ +#define MP_SHORT_EXTEND(s) (((s) & MP_SHORT_SEQ_HIBIT) ? \ + ((s) | ~MP_SHORT_SEQ_MASK) \ + : ((s) & MP_SHORT_SEQ_MASK)) +#define MP_LONG_EXTEND(s) (((s) & MP_LONG_SEQ_HIBIT) ? \ + ((s) | ~MP_LONG_SEQ_MASK) \ + : ((s) & MP_LONG_SEQ_MASK)) + +/* Comparision of MP sequence numbers. Note: all sequence numbers + except priv->xseq are stored with the sign bit extended. */ +#define MP_SHORT_SEQ_DIFF(x,y) MP_SHORT_EXTEND((x) - (y)) +#define MP_LONG_SEQ_DIFF(x,y) MP_LONG_EXTEND((x) - (y)) + +#define MP_RECV_SEQ_DIFF(priv,x,y) \ + ((priv)->conf.recvShortSeq ? \ + MP_SHORT_SEQ_DIFF((x), (y)) : \ + MP_LONG_SEQ_DIFF((x), (y))) + +/* Increment receive sequence number */ +#define MP_NEXT_RECV_SEQ(priv,seq) \ + ((priv)->conf.recvShortSeq ? \ + MP_SHORT_EXTEND((seq) + 1) : \ + MP_LONG_EXTEND((seq) + 1)) + +/* Don't fragment transmitted packets to parts smaller than this */ +#define MP_MIN_FRAG_LEN 32 + +/* Maximum fragment reasssembly queue length */ +#define MP_MAX_QUEUE_LEN 128 + +/* Fragment queue scanner period */ +#define MP_FRAGTIMER_INTERVAL (hz/2) + +/* Average link overhead. XXX: Should be given by user-level */ +#define MP_AVERAGE_LINK_OVERHEAD 16 + +/* Keep this equal to ng_ppp_hook_names lower! */ +#define HOOK_INDEX_MAX 13 + +/* We store incoming fragments this way */ +struct ng_ppp_frag { + int seq; /* fragment seq# */ + uint8_t first; /* First in packet? */ + uint8_t last; /* Last in packet? */ + struct timeval timestamp; /* time of reception */ + struct mbuf *data; /* Fragment data */ + TAILQ_ENTRY(ng_ppp_frag) f_qent; /* Fragment queue */ +}; + +/* Per-link private information */ +struct ng_ppp_link { + struct ng_ppp_link_conf conf; /* link configuration */ + struct ng_ppp_link_stat64 stats; /* link stats */ + hook_p hook; /* connection to link data */ + int32_t seq; /* highest rec'd seq# - MSEQ */ + uint32_t latency; /* calculated link latency */ + struct timeval lastWrite; /* time of last write for MP */ + int bytesInQueue; /* bytes in the output queue for MP */ +}; + +/* Total per-node private information */ +struct ng_ppp_private { + struct ng_ppp_bund_conf conf; /* bundle config */ + struct ng_ppp_link_stat64 bundleStats; /* bundle stats */ + struct ng_ppp_link links[NG_PPP_MAX_LINKS];/* per-link info */ + int32_t xseq; /* next out MP seq # */ + int32_t mseq; /* min links[i].seq */ + uint16_t activeLinks[NG_PPP_MAX_LINKS]; /* indicies */ + uint16_t numActiveLinks; /* how many links up */ + uint16_t lastLink; /* for round robin */ + uint8_t vjCompHooked; /* VJ comp hooked up? */ + uint8_t allLinksEqual; /* all xmit the same? */ + hook_p hooks[HOOK_INDEX_MAX]; /* non-link hooks */ + struct ng_ppp_frag fragsmem[MP_MAX_QUEUE_LEN]; /* fragments storage */ + TAILQ_HEAD(ng_ppp_fraglist, ng_ppp_frag) /* fragment queue */ + frags; + TAILQ_HEAD(ng_ppp_fragfreelist, ng_ppp_frag) /* free fragment queue */ + fragsfree; + struct callout fragTimer; /* fraq queue check */ + struct mtx rmtx; /* recv mutex */ + struct mtx xmtx; /* xmit mutex */ +}; +typedef struct ng_ppp_private *priv_p; + +/* Netgraph node methods */ +static ng_constructor_t ng_ppp_constructor; +static ng_rcvmsg_t ng_ppp_rcvmsg; +static ng_shutdown_t ng_ppp_shutdown; +static ng_newhook_t ng_ppp_newhook; +static ng_rcvdata_t ng_ppp_rcvdata; +static ng_disconnect_t ng_ppp_disconnect; + +static ng_rcvdata_t ng_ppp_rcvdata_inet; +static ng_rcvdata_t ng_ppp_rcvdata_ipv6; +static ng_rcvdata_t ng_ppp_rcvdata_ipx; +static ng_rcvdata_t ng_ppp_rcvdata_atalk; +static ng_rcvdata_t ng_ppp_rcvdata_bypass; + +static ng_rcvdata_t ng_ppp_rcvdata_vjc_ip; +static ng_rcvdata_t ng_ppp_rcvdata_vjc_comp; +static ng_rcvdata_t ng_ppp_rcvdata_vjc_uncomp; +static ng_rcvdata_t ng_ppp_rcvdata_vjc_vjip; + +static ng_rcvdata_t ng_ppp_rcvdata_compress; +static ng_rcvdata_t ng_ppp_rcvdata_decompress; + +static ng_rcvdata_t ng_ppp_rcvdata_encrypt; +static ng_rcvdata_t ng_ppp_rcvdata_decrypt; + +/* We use integer indicies to refer to the non-link hooks. */ +static const struct { + char *const name; + ng_rcvdata_t *fn; +} ng_ppp_hook_names[] = { +#define HOOK_INDEX_ATALK 0 + { NG_PPP_HOOK_ATALK, ng_ppp_rcvdata_atalk }, +#define HOOK_INDEX_BYPASS 1 + { NG_PPP_HOOK_BYPASS, ng_ppp_rcvdata_bypass }, +#define HOOK_INDEX_COMPRESS 2 + { NG_PPP_HOOK_COMPRESS, ng_ppp_rcvdata_compress }, +#define HOOK_INDEX_ENCRYPT 3 + { NG_PPP_HOOK_ENCRYPT, ng_ppp_rcvdata_encrypt }, +#define HOOK_INDEX_DECOMPRESS 4 + { NG_PPP_HOOK_DECOMPRESS, ng_ppp_rcvdata_decompress }, +#define HOOK_INDEX_DECRYPT 5 + { NG_PPP_HOOK_DECRYPT, ng_ppp_rcvdata_decrypt }, +#define HOOK_INDEX_INET 6 + { NG_PPP_HOOK_INET, ng_ppp_rcvdata_inet }, +#define HOOK_INDEX_IPX 7 + { NG_PPP_HOOK_IPX, ng_ppp_rcvdata_ipx }, +#define HOOK_INDEX_VJC_COMP 8 + { NG_PPP_HOOK_VJC_COMP, ng_ppp_rcvdata_vjc_comp }, +#define HOOK_INDEX_VJC_IP 9 + { NG_PPP_HOOK_VJC_IP, ng_ppp_rcvdata_vjc_ip }, +#define HOOK_INDEX_VJC_UNCOMP 10 + { NG_PPP_HOOK_VJC_UNCOMP, ng_ppp_rcvdata_vjc_uncomp }, +#define HOOK_INDEX_VJC_VJIP 11 + { NG_PPP_HOOK_VJC_VJIP, ng_ppp_rcvdata_vjc_vjip }, +#define HOOK_INDEX_IPV6 12 + { NG_PPP_HOOK_IPV6, ng_ppp_rcvdata_ipv6 }, + { NULL, NULL } +}; + +/* Helper functions */ +static int ng_ppp_proto_recv(node_p node, item_p item, uint16_t proto, + uint16_t linkNum); +static int ng_ppp_hcomp_xmit(node_p node, item_p item, uint16_t proto); +static int ng_ppp_hcomp_recv(node_p node, item_p item, uint16_t proto, + uint16_t linkNum); +static int ng_ppp_comp_xmit(node_p node, item_p item, uint16_t proto); +static int ng_ppp_comp_recv(node_p node, item_p item, uint16_t proto, + uint16_t linkNum); +static int ng_ppp_crypt_xmit(node_p node, item_p item, uint16_t proto); +static int ng_ppp_crypt_recv(node_p node, item_p item, uint16_t proto, + uint16_t linkNum); +static int ng_ppp_mp_xmit(node_p node, item_p item, uint16_t proto); +static int ng_ppp_mp_recv(node_p node, item_p item, uint16_t proto, + uint16_t linkNum); +static int ng_ppp_link_xmit(node_p node, item_p item, uint16_t proto, + uint16_t linkNum, int plen); + +static int ng_ppp_bypass(node_p node, item_p item, uint16_t proto, + uint16_t linkNum); + +static void ng_ppp_bump_mseq(node_p node, int32_t new_mseq); +static int ng_ppp_frag_drop(node_p node); +static int ng_ppp_check_packet(node_p node); +static void ng_ppp_get_packet(node_p node, struct mbuf **mp); +static int ng_ppp_frag_process(node_p node, item_p oitem); +static int ng_ppp_frag_trim(node_p node); +static void ng_ppp_frag_timeout(node_p node, hook_p hook, void *arg1, + int arg2); +static void ng_ppp_frag_checkstale(node_p node); +static void ng_ppp_frag_reset(node_p node); +static void ng_ppp_mp_strategy(node_p node, int len, int *distrib); +static int ng_ppp_intcmp(void *latency, const void *v1, const void *v2); +static struct mbuf *ng_ppp_addproto(struct mbuf *m, uint16_t proto, int compOK); +static struct mbuf *ng_ppp_cutproto(struct mbuf *m, uint16_t *proto); +static struct mbuf *ng_ppp_prepend(struct mbuf *m, const void *buf, int len); +static int ng_ppp_config_valid(node_p node, + const struct ng_ppp_node_conf *newConf); +static void ng_ppp_update(node_p node, int newConf); +static void ng_ppp_start_frag_timer(node_p node); +static void ng_ppp_stop_frag_timer(node_p node); + +/* Parse type for struct ng_ppp_mp_state_type */ +static const struct ng_parse_fixedarray_info ng_ppp_rseq_array_info = { + &ng_parse_hint32_type, + NG_PPP_MAX_LINKS +}; +static const struct ng_parse_type ng_ppp_rseq_array_type = { + &ng_parse_fixedarray_type, + &ng_ppp_rseq_array_info, +}; +static const struct ng_parse_struct_field ng_ppp_mp_state_type_fields[] + = NG_PPP_MP_STATE_TYPE_INFO(&ng_ppp_rseq_array_type); +static const struct ng_parse_type ng_ppp_mp_state_type = { + &ng_parse_struct_type, + &ng_ppp_mp_state_type_fields +}; + +/* Parse type for struct ng_ppp_link_conf */ +static const struct ng_parse_struct_field ng_ppp_link_type_fields[] + = NG_PPP_LINK_TYPE_INFO; +static const struct ng_parse_type ng_ppp_link_type = { + &ng_parse_struct_type, + &ng_ppp_link_type_fields +}; + +/* Parse type for struct ng_ppp_bund_conf */ +static const struct ng_parse_struct_field ng_ppp_bund_type_fields[] + = NG_PPP_BUND_TYPE_INFO; +static const struct ng_parse_type ng_ppp_bund_type = { + &ng_parse_struct_type, + &ng_ppp_bund_type_fields +}; + +/* Parse type for struct ng_ppp_node_conf */ +static const struct ng_parse_fixedarray_info ng_ppp_array_info = { + &ng_ppp_link_type, + NG_PPP_MAX_LINKS +}; +static const struct ng_parse_type ng_ppp_link_array_type = { + &ng_parse_fixedarray_type, + &ng_ppp_array_info, +}; +static const struct ng_parse_struct_field ng_ppp_conf_type_fields[] + = NG_PPP_CONFIG_TYPE_INFO(&ng_ppp_bund_type, &ng_ppp_link_array_type); +static const struct ng_parse_type ng_ppp_conf_type = { + &ng_parse_struct_type, + &ng_ppp_conf_type_fields +}; + +/* Parse type for struct ng_ppp_link_stat */ +static const struct ng_parse_struct_field ng_ppp_stats_type_fields[] + = NG_PPP_STATS_TYPE_INFO; +static const struct ng_parse_type ng_ppp_stats_type = { + &ng_parse_struct_type, + &ng_ppp_stats_type_fields +}; + +/* Parse type for struct ng_ppp_link_stat64 */ +static const struct ng_parse_struct_field ng_ppp_stats64_type_fields[] + = NG_PPP_STATS64_TYPE_INFO; +static const struct ng_parse_type ng_ppp_stats64_type = { + &ng_parse_struct_type, + &ng_ppp_stats64_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_ppp_cmds[] = { + { + NGM_PPP_COOKIE, + NGM_PPP_SET_CONFIG, + "setconfig", + &ng_ppp_conf_type, + NULL + }, + { + NGM_PPP_COOKIE, + NGM_PPP_GET_CONFIG, + "getconfig", + NULL, + &ng_ppp_conf_type + }, + { + NGM_PPP_COOKIE, + NGM_PPP_GET_MP_STATE, + "getmpstate", + NULL, + &ng_ppp_mp_state_type + }, + { + NGM_PPP_COOKIE, + NGM_PPP_GET_LINK_STATS, + "getstats", + &ng_parse_int16_type, + &ng_ppp_stats_type + }, + { + NGM_PPP_COOKIE, + NGM_PPP_CLR_LINK_STATS, + "clrstats", + &ng_parse_int16_type, + NULL + }, + { + NGM_PPP_COOKIE, + NGM_PPP_GETCLR_LINK_STATS, + "getclrstats", + &ng_parse_int16_type, + &ng_ppp_stats_type + }, + { + NGM_PPP_COOKIE, + NGM_PPP_GET_LINK_STATS64, + "getstats64", + &ng_parse_int16_type, + &ng_ppp_stats64_type + }, + { + NGM_PPP_COOKIE, + NGM_PPP_GETCLR_LINK_STATS64, + "getclrstats64", + &ng_parse_int16_type, + &ng_ppp_stats64_type + }, + { 0 } +}; + +/* Node type descriptor */ +static struct ng_type ng_ppp_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_PPP_NODE_TYPE, + .constructor = ng_ppp_constructor, + .rcvmsg = ng_ppp_rcvmsg, + .shutdown = ng_ppp_shutdown, + .newhook = ng_ppp_newhook, + .rcvdata = ng_ppp_rcvdata, + .disconnect = ng_ppp_disconnect, + .cmdlist = ng_ppp_cmds, +}; +NETGRAPH_INIT(ppp, &ng_ppp_typestruct); + +/* Address and control field header */ +static const uint8_t ng_ppp_acf[2] = { 0xff, 0x03 }; + +/* Maximum time we'll let a complete incoming packet sit in the queue */ +static const struct timeval ng_ppp_max_staleness = { 2, 0 }; /* 2 seconds */ + +#define ERROUT(x) do { error = (x); goto done; } while (0) + +/************************************************************************ + NETGRAPH NODE STUFF + ************************************************************************/ + +/* + * Node type constructor + */ +static int +ng_ppp_constructor(node_p node) +{ + priv_p priv; + int i; + + /* Allocate private structure */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH_PPP, M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + + NG_NODE_SET_PRIVATE(node, priv); + + /* Initialize state */ + TAILQ_INIT(&priv->frags); + TAILQ_INIT(&priv->fragsfree); + for (i = 0; i < MP_MAX_QUEUE_LEN; i++) + TAILQ_INSERT_TAIL(&priv->fragsfree, &priv->fragsmem[i], f_qent); + for (i = 0; i < NG_PPP_MAX_LINKS; i++) + priv->links[i].seq = MP_NOSEQ; + ng_callout_init(&priv->fragTimer); + + mtx_init(&priv->rmtx, "ng_ppp_recv", NULL, MTX_DEF); + mtx_init(&priv->xmtx, "ng_ppp_xmit", NULL, MTX_DEF); + + /* Done */ + return (0); +} + +/* + * Give our OK for a hook to be added + */ +static int +ng_ppp_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + hook_p *hookPtr = NULL; + int linkNum = -1; + int hookIndex = -1; + + /* Figure out which hook it is */ + if (strncmp(name, NG_PPP_HOOK_LINK_PREFIX, /* a link hook? */ + strlen(NG_PPP_HOOK_LINK_PREFIX)) == 0) { + const char *cp; + char *eptr; + + cp = name + strlen(NG_PPP_HOOK_LINK_PREFIX); + if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) + return (EINVAL); + linkNum = (int)strtoul(cp, &eptr, 10); + if (*eptr != '\0' || linkNum < 0 || linkNum >= NG_PPP_MAX_LINKS) + return (EINVAL); + hookPtr = &priv->links[linkNum].hook; + hookIndex = ~linkNum; + + /* See if hook is already connected. */ + if (*hookPtr != NULL) + return (EISCONN); + + /* Disallow more than one link unless multilink is enabled. */ + if (priv->links[linkNum].conf.enableLink && + !priv->conf.enableMultilink && priv->numActiveLinks >= 1) + return (ENODEV); + + } else { /* must be a non-link hook */ + int i; + + for (i = 0; ng_ppp_hook_names[i].name != NULL; i++) { + if (strcmp(name, ng_ppp_hook_names[i].name) == 0) { + hookPtr = &priv->hooks[i]; + hookIndex = i; + break; + } + } + if (ng_ppp_hook_names[i].name == NULL) + return (EINVAL); /* no such hook */ + + /* See if hook is already connected */ + if (*hookPtr != NULL) + return (EISCONN); + + /* Every non-linkX hook have it's own function. */ + NG_HOOK_SET_RCVDATA(hook, ng_ppp_hook_names[i].fn); + } + + /* OK */ + *hookPtr = hook; + NG_HOOK_SET_PRIVATE(hook, (void *)(intptr_t)hookIndex); + ng_ppp_update(node, 0); + return (0); +} + +/* + * Receive a control message + */ +static int +ng_ppp_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_PPP_COOKIE: + switch (msg->header.cmd) { + case NGM_PPP_SET_CONFIG: + { + struct ng_ppp_node_conf *const conf = + (struct ng_ppp_node_conf *)msg->data; + int i; + + /* Check for invalid or illegal config */ + if (msg->header.arglen != sizeof(*conf)) + ERROUT(EINVAL); + if (!ng_ppp_config_valid(node, conf)) + ERROUT(EINVAL); + + /* Copy config */ + priv->conf = conf->bund; + for (i = 0; i < NG_PPP_MAX_LINKS; i++) + priv->links[i].conf = conf->links[i]; + ng_ppp_update(node, 1); + break; + } + case NGM_PPP_GET_CONFIG: + { + struct ng_ppp_node_conf *conf; + int i; + + NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + conf = (struct ng_ppp_node_conf *)resp->data; + conf->bund = priv->conf; + for (i = 0; i < NG_PPP_MAX_LINKS; i++) + conf->links[i] = priv->links[i].conf; + break; + } + case NGM_PPP_GET_MP_STATE: + { + struct ng_ppp_mp_state *info; + int i; + + NG_MKRESPONSE(resp, msg, sizeof(*info), M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + info = (struct ng_ppp_mp_state *)resp->data; + bzero(info, sizeof(*info)); + for (i = 0; i < NG_PPP_MAX_LINKS; i++) { + if (priv->links[i].seq != MP_NOSEQ) + info->rseq[i] = priv->links[i].seq; + } + info->mseq = priv->mseq; + info->xseq = priv->xseq; + break; + } + case NGM_PPP_GET_LINK_STATS: + case NGM_PPP_CLR_LINK_STATS: + case NGM_PPP_GETCLR_LINK_STATS: + case NGM_PPP_GET_LINK_STATS64: + case NGM_PPP_GETCLR_LINK_STATS64: + { + struct ng_ppp_link_stat64 *stats; + uint16_t linkNum; + + /* Process request. */ + if (msg->header.arglen != sizeof(uint16_t)) + ERROUT(EINVAL); + linkNum = *((uint16_t *) msg->data); + if (linkNum >= NG_PPP_MAX_LINKS + && linkNum != NG_PPP_BUNDLE_LINKNUM) + ERROUT(EINVAL); + stats = (linkNum == NG_PPP_BUNDLE_LINKNUM) ? + &priv->bundleStats : &priv->links[linkNum].stats; + + /* Make 64bit reply. */ + if (msg->header.cmd == NGM_PPP_GET_LINK_STATS64 || + msg->header.cmd == NGM_PPP_GETCLR_LINK_STATS64) { + NG_MKRESPONSE(resp, msg, + sizeof(struct ng_ppp_link_stat64), M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + bcopy(stats, resp->data, sizeof(*stats)); + } else + /* Make 32bit reply. */ + if (msg->header.cmd == NGM_PPP_GET_LINK_STATS || + msg->header.cmd == NGM_PPP_GETCLR_LINK_STATS) { + struct ng_ppp_link_stat *rs; + NG_MKRESPONSE(resp, msg, + sizeof(struct ng_ppp_link_stat), M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + rs = (struct ng_ppp_link_stat *)resp->data; + /* Truncate 64->32 bits. */ + rs->xmitFrames = stats->xmitFrames; + rs->xmitOctets = stats->xmitOctets; + rs->recvFrames = stats->recvFrames; + rs->recvOctets = stats->recvOctets; + rs->badProtos = stats->badProtos; + rs->runts = stats->runts; + rs->dupFragments = stats->dupFragments; + rs->dropFragments = stats->dropFragments; + } + /* Clear stats. */ + if (msg->header.cmd != NGM_PPP_GET_LINK_STATS && + msg->header.cmd != NGM_PPP_GET_LINK_STATS64) + bzero(stats, sizeof(*stats)); + break; + } + default: + error = EINVAL; + break; + } + break; + case NGM_VJC_COOKIE: + { + /* + * Forward it to the vjc node. leave the + * old return address alone. + * If we have no hook, let NG_RESPOND_MSG + * clean up any remaining resources. + * Because we have no resp, the item will be freed + * along with anything it references. Don't + * let msg be freed twice. + */ + NGI_MSG(item) = msg; /* put it back in the item */ + msg = NULL; + if ((lasthook = priv->hooks[HOOK_INDEX_VJC_IP])) { + NG_FWD_ITEM_HOOK(error, item, lasthook); + } + return (error); + } + default: + error = EINVAL; + break; + } +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Destroy node + */ +static int +ng_ppp_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + /* Stop fragment queue timer */ + ng_ppp_stop_frag_timer(node); + + /* Take down netgraph node */ + ng_ppp_frag_reset(node); + mtx_destroy(&priv->rmtx); + mtx_destroy(&priv->xmtx); + bzero(priv, sizeof(*priv)); + FREE(priv, M_NETGRAPH_PPP); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); /* let the node escape */ + return (0); +} + +/* + * Hook disconnection + */ +static int +ng_ppp_disconnect(hook_p hook) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + const int index = (intptr_t)NG_HOOK_PRIVATE(hook); + + /* Zero out hook pointer */ + if (index < 0) + priv->links[~index].hook = NULL; + else + priv->hooks[index] = NULL; + + /* Update derived info (or go away if no hooks left). */ + if (NG_NODE_NUMHOOKS(node) > 0) + ng_ppp_update(node, 0); + else if (NG_NODE_IS_VALID(node)) + ng_rmnode_self(node); + + return (0); +} + +/* + * Proto layer + */ + +/* + * Receive data on a hook inet. + */ +static int +ng_ppp_rcvdata_inet(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + + if (!priv->conf.enableIP) { + NG_FREE_ITEM(item); + return (ENXIO); + } + return (ng_ppp_hcomp_xmit(NG_HOOK_NODE(hook), item, PROT_IP)); +} + +/* + * Receive data on a hook ipv6. + */ +static int +ng_ppp_rcvdata_ipv6(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + + if (!priv->conf.enableIPv6) { + NG_FREE_ITEM(item); + return (ENXIO); + } + return (ng_ppp_hcomp_xmit(NG_HOOK_NODE(hook), item, PROT_IPV6)); +} + +/* + * Receive data on a hook atalk. + */ +static int +ng_ppp_rcvdata_atalk(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + + if (!priv->conf.enableAtalk) { + NG_FREE_ITEM(item); + return (ENXIO); + } + return (ng_ppp_hcomp_xmit(NG_HOOK_NODE(hook), item, PROT_ATALK)); +} + +/* + * Receive data on a hook ipx + */ +static int +ng_ppp_rcvdata_ipx(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + + if (!priv->conf.enableIPX) { + NG_FREE_ITEM(item); + return (ENXIO); + } + return (ng_ppp_hcomp_xmit(NG_HOOK_NODE(hook), item, PROT_IPX)); +} + +/* + * Receive data on a hook bypass + */ +static int +ng_ppp_rcvdata_bypass(hook_p hook, item_p item) +{ + uint16_t linkNum; + uint16_t proto; + struct mbuf *m; + + NGI_GET_M(item, m); + if (m->m_pkthdr.len < 4) { + NG_FREE_ITEM(item); + return (EINVAL); + } + if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) { + NG_FREE_ITEM(item); + return (ENOBUFS); + } + linkNum = ntohs(mtod(m, uint16_t *)[0]); + proto = ntohs(mtod(m, uint16_t *)[1]); + m_adj(m, 4); + NGI_M(item) = m; + + if (linkNum == NG_PPP_BUNDLE_LINKNUM) + return (ng_ppp_hcomp_xmit(NG_HOOK_NODE(hook), item, proto)); + else + return (ng_ppp_link_xmit(NG_HOOK_NODE(hook), item, proto, + linkNum, 0)); +} + +static int +ng_ppp_bypass(node_p node, item_p item, uint16_t proto, uint16_t linkNum) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + uint16_t hdr[2]; + struct mbuf *m; + int error; + + if (priv->hooks[HOOK_INDEX_BYPASS] == NULL) { + NG_FREE_ITEM(item); + return (ENXIO); + } + + /* Add 4-byte bypass header. */ + hdr[0] = htons(linkNum); + hdr[1] = htons(proto); + + NGI_GET_M(item, m); + if ((m = ng_ppp_prepend(m, &hdr, 4)) == NULL) { + NG_FREE_ITEM(item); + return (ENOBUFS); + } + NGI_M(item) = m; + + /* Send packet out hook. */ + NG_FWD_ITEM_HOOK(error, item, priv->hooks[HOOK_INDEX_BYPASS]); + return (error); +} + +static int +ng_ppp_proto_recv(node_p node, item_p item, uint16_t proto, uint16_t linkNum) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + hook_p outHook = NULL; + int error; + + switch (proto) { + case PROT_IP: + if (priv->conf.enableIP) + outHook = priv->hooks[HOOK_INDEX_INET]; + break; + case PROT_IPV6: + if (priv->conf.enableIPv6) + outHook = priv->hooks[HOOK_INDEX_IPV6]; + break; + case PROT_ATALK: + if (priv->conf.enableAtalk) + outHook = priv->hooks[HOOK_INDEX_ATALK]; + break; + case PROT_IPX: + if (priv->conf.enableIPX) + outHook = priv->hooks[HOOK_INDEX_IPX]; + break; + } + + if (outHook == NULL) + return (ng_ppp_bypass(node, item, proto, linkNum)); + + /* Send packet out hook. */ + NG_FWD_ITEM_HOOK(error, item, outHook); + return (error); +} + +/* + * Header compression layer + */ + +static int +ng_ppp_hcomp_xmit(node_p node, item_p item, uint16_t proto) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (proto == PROT_IP && + priv->conf.enableVJCompression && + priv->vjCompHooked) { + int error; + + /* Send packet out hook. */ + NG_FWD_ITEM_HOOK(error, item, priv->hooks[HOOK_INDEX_VJC_IP]); + return (error); + } + + return (ng_ppp_comp_xmit(node, item, proto)); +} + +/* + * Receive data on a hook vjc_comp. + */ +static int +ng_ppp_rcvdata_vjc_comp(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + + if (!priv->conf.enableVJCompression) { + NG_FREE_ITEM(item); + return (ENXIO); + } + return (ng_ppp_comp_xmit(node, item, PROT_VJCOMP)); +} + +/* + * Receive data on a hook vjc_uncomp. + */ +static int +ng_ppp_rcvdata_vjc_uncomp(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + + if (!priv->conf.enableVJCompression) { + NG_FREE_ITEM(item); + return (ENXIO); + } + return (ng_ppp_comp_xmit(node, item, PROT_VJUNCOMP)); +} + +/* + * Receive data on a hook vjc_vjip. + */ +static int +ng_ppp_rcvdata_vjc_vjip(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + + if (!priv->conf.enableVJCompression) { + NG_FREE_ITEM(item); + return (ENXIO); + } + return (ng_ppp_comp_xmit(node, item, PROT_IP)); +} + +static int +ng_ppp_hcomp_recv(node_p node, item_p item, uint16_t proto, uint16_t linkNum) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (priv->conf.enableVJDecompression && priv->vjCompHooked) { + hook_p outHook = NULL; + + switch (proto) { + case PROT_VJCOMP: + outHook = priv->hooks[HOOK_INDEX_VJC_COMP]; + break; + case PROT_VJUNCOMP: + outHook = priv->hooks[HOOK_INDEX_VJC_UNCOMP]; + break; + } + + if (outHook) { + int error; + + /* Send packet out hook. */ + NG_FWD_ITEM_HOOK(error, item, outHook); + return (error); + } + } + + return (ng_ppp_proto_recv(node, item, proto, linkNum)); +} + +/* + * Receive data on a hook vjc_ip. + */ +static int +ng_ppp_rcvdata_vjc_ip(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + + if (!priv->conf.enableVJDecompression) { + NG_FREE_ITEM(item); + return (ENXIO); + } + return (ng_ppp_proto_recv(node, item, PROT_IP, NG_PPP_BUNDLE_LINKNUM)); +} + +/* + * Compression layer + */ + +static int +ng_ppp_comp_xmit(node_p node, item_p item, uint16_t proto) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (priv->conf.enableCompression && + proto < 0x4000 && + proto != PROT_COMPD && + proto != PROT_CRYPTD && + priv->hooks[HOOK_INDEX_COMPRESS] != NULL) { + struct mbuf *m; + int error; + + NGI_GET_M(item, m); + if ((m = ng_ppp_addproto(m, proto, 0)) == NULL) { + NG_FREE_ITEM(item); + return (ENOBUFS); + } + NGI_M(item) = m; + + /* Send packet out hook. */ + NG_FWD_ITEM_HOOK(error, item, priv->hooks[HOOK_INDEX_COMPRESS]); + return (error); + } + + return (ng_ppp_crypt_xmit(node, item, proto)); +} + +/* + * Receive data on a hook compress. + */ +static int +ng_ppp_rcvdata_compress(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + uint16_t proto; + + switch (priv->conf.enableCompression) { + case NG_PPP_COMPRESS_NONE: + NG_FREE_ITEM(item); + return (ENXIO); + case NG_PPP_COMPRESS_FULL: + { + struct mbuf *m; + + NGI_GET_M(item, m); + if ((m = ng_ppp_cutproto(m, &proto)) == NULL) { + NG_FREE_ITEM(item); + return (EIO); + } + NGI_M(item) = m; + if (!PROT_VALID(proto)) { + NG_FREE_ITEM(item); + return (EIO); + } + } + break; + default: + proto = PROT_COMPD; + break; + } + return (ng_ppp_crypt_xmit(node, item, proto)); +} + +static int +ng_ppp_comp_recv(node_p node, item_p item, uint16_t proto, uint16_t linkNum) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (proto < 0x4000 && + ((proto == PROT_COMPD && priv->conf.enableDecompression) || + priv->conf.enableDecompression == NG_PPP_DECOMPRESS_FULL) && + priv->hooks[HOOK_INDEX_DECOMPRESS] != NULL) { + int error; + + if (priv->conf.enableDecompression == NG_PPP_DECOMPRESS_FULL) { + struct mbuf *m; + NGI_GET_M(item, m); + if ((m = ng_ppp_addproto(m, proto, 0)) == NULL) { + NG_FREE_ITEM(item); + return (EIO); + } + NGI_M(item) = m; + } + + /* Send packet out hook. */ + NG_FWD_ITEM_HOOK(error, item, + priv->hooks[HOOK_INDEX_DECOMPRESS]); + return (error); + } else if (proto == PROT_COMPD) { + /* Disabled protos MUST be silently discarded, but + * unsupported MUST not. Let user-level decide this. */ + return (ng_ppp_bypass(node, item, proto, linkNum)); + } + + return (ng_ppp_hcomp_recv(node, item, proto, linkNum)); +} + +/* + * Receive data on a hook decompress. + */ +static int +ng_ppp_rcvdata_decompress(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + uint16_t proto; + struct mbuf *m; + + if (!priv->conf.enableDecompression) { + NG_FREE_ITEM(item); + return (ENXIO); + } + NGI_GET_M(item, m); + if ((m = ng_ppp_cutproto(m, &proto)) == NULL) { + NG_FREE_ITEM(item); + return (EIO); + } + NGI_M(item) = m; + if (!PROT_VALID(proto)) { + priv->bundleStats.badProtos++; + NG_FREE_ITEM(item); + return (EIO); + } + return (ng_ppp_hcomp_recv(node, item, proto, NG_PPP_BUNDLE_LINKNUM)); +} + +/* + * Encryption layer + */ + +static int +ng_ppp_crypt_xmit(node_p node, item_p item, uint16_t proto) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (priv->conf.enableEncryption && + proto < 0x4000 && + proto != PROT_CRYPTD && + priv->hooks[HOOK_INDEX_ENCRYPT] != NULL) { + struct mbuf *m; + int error; + + NGI_GET_M(item, m); + if ((m = ng_ppp_addproto(m, proto, 0)) == NULL) { + NG_FREE_ITEM(item); + return (ENOBUFS); + } + NGI_M(item) = m; + + /* Send packet out hook. */ + NG_FWD_ITEM_HOOK(error, item, priv->hooks[HOOK_INDEX_ENCRYPT]); + return (error); + } + + return (ng_ppp_mp_xmit(node, item, proto)); +} + +/* + * Receive data on a hook encrypt. + */ +static int +ng_ppp_rcvdata_encrypt(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + + if (!priv->conf.enableEncryption) { + NG_FREE_ITEM(item); + return (ENXIO); + } + return (ng_ppp_mp_xmit(node, item, PROT_CRYPTD)); +} + +static int +ng_ppp_crypt_recv(node_p node, item_p item, uint16_t proto, uint16_t linkNum) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (proto == PROT_CRYPTD) { + if (priv->conf.enableDecryption && + priv->hooks[HOOK_INDEX_DECRYPT] != NULL) { + int error; + + /* Send packet out hook. */ + NG_FWD_ITEM_HOOK(error, item, + priv->hooks[HOOK_INDEX_DECRYPT]); + return (error); + } else { + /* Disabled protos MUST be silently discarded, but + * unsupported MUST not. Let user-level decide this. */ + return (ng_ppp_bypass(node, item, proto, linkNum)); + } + } + + return (ng_ppp_comp_recv(node, item, proto, linkNum)); +} + +/* + * Receive data on a hook decrypt. + */ +static int +ng_ppp_rcvdata_decrypt(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + uint16_t proto; + struct mbuf *m; + + if (!priv->conf.enableDecryption) { + NG_FREE_ITEM(item); + return (ENXIO); + } + NGI_GET_M(item, m); + if ((m = ng_ppp_cutproto(m, &proto)) == NULL) { + NG_FREE_ITEM(item); + return (EIO); + } + NGI_M(item) = m; + if (!PROT_VALID(proto)) { + priv->bundleStats.badProtos++; + NG_FREE_ITEM(item); + return (EIO); + } + return (ng_ppp_comp_recv(node, item, proto, NG_PPP_BUNDLE_LINKNUM)); +} + +/* + * Link layer + */ + +static int +ng_ppp_link_xmit(node_p node, item_p item, uint16_t proto, uint16_t linkNum, int plen) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_ppp_link *link; + int len, error; + struct mbuf *m; + uint16_t mru; + + /* Check if link correct. */ + if (linkNum >= NG_PPP_MAX_LINKS) { + ERROUT(ENETDOWN); + } + + /* Get link pointer (optimization). */ + link = &priv->links[linkNum]; + + /* Check link status (if real). */ + if (link->hook == NULL) { + ERROUT(ENETDOWN); + } + + /* Extract mbuf. */ + NGI_GET_M(item, m); + + /* Check peer's MRU for this link. */ + mru = link->conf.mru; + if (mru != 0 && m->m_pkthdr.len > mru) { + NG_FREE_M(m); + ERROUT(EMSGSIZE); + } + + /* Prepend protocol number, possibly compressed. */ + if ((m = ng_ppp_addproto(m, proto, link->conf.enableProtoComp)) == + NULL) { + ERROUT(ENOBUFS); + } + + /* Prepend address and control field (unless compressed). */ + if (proto == PROT_LCP || !link->conf.enableACFComp) { + if ((m = ng_ppp_prepend(m, &ng_ppp_acf, 2)) == NULL) + ERROUT(ENOBUFS); + } + + /* Deliver frame. */ + len = m->m_pkthdr.len; + NG_FWD_NEW_DATA(error, item, link->hook, m); + + mtx_lock(&priv->xmtx); + + /* Update link stats. */ + link->stats.xmitFrames++; + link->stats.xmitOctets += len; + + /* Update bundle stats. */ + if (plen > 0) { + priv->bundleStats.xmitFrames++; + priv->bundleStats.xmitOctets += plen; + } + + /* Update 'bytes in queue' counter. */ + if (error == 0) { + /* bytesInQueue and lastWrite required only for mp_strategy. */ + if (priv->conf.enableMultilink && !priv->allLinksEqual && + !priv->conf.enableRoundRobin) { + /* If queue was empty, then mark this time. */ + if (link->bytesInQueue == 0) + getmicrouptime(&link->lastWrite); + link->bytesInQueue += len + MP_AVERAGE_LINK_OVERHEAD; + /* Limit max queue length to 50 pkts. BW can be defined + incorrectly and link may not signal overload. */ + if (link->bytesInQueue > 50 * 1600) + link->bytesInQueue = 50 * 1600; + } + } + mtx_unlock(&priv->xmtx); + return (error); + +done: + NG_FREE_ITEM(item); + return (error); +} + +/* + * Receive data on a hook linkX. + */ +static int +ng_ppp_rcvdata(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + const int index = (intptr_t)NG_HOOK_PRIVATE(hook); + const uint16_t linkNum = (uint16_t)~index; + struct ng_ppp_link * const link = &priv->links[linkNum]; + uint16_t proto; + struct mbuf *m; + int error = 0; + + KASSERT(linkNum < NG_PPP_MAX_LINKS, + ("%s: bogus index 0x%x", __func__, index)); + + NGI_GET_M(item, m); + + mtx_lock(&priv->rmtx); + + /* Stats */ + link->stats.recvFrames++; + link->stats.recvOctets += m->m_pkthdr.len; + + /* Strip address and control fields, if present. */ + if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) + ERROUT(ENOBUFS); + if (mtod(m, uint8_t *)[0] == 0xff && + mtod(m, uint8_t *)[1] == 0x03) + m_adj(m, 2); + + /* Get protocol number */ + if ((m = ng_ppp_cutproto(m, &proto)) == NULL) + ERROUT(ENOBUFS); + NGI_M(item) = m; /* Put changed m back into item. */ + + if (!PROT_VALID(proto)) { + link->stats.badProtos++; + ERROUT(EIO); + } + + /* LCP packets must go directly to bypass. */ + if (proto >= 0xB000) { + mtx_unlock(&priv->rmtx); + return (ng_ppp_bypass(node, item, proto, linkNum)); + } + + /* Other packets are denied on a disabled link. */ + if (!link->conf.enableLink) + ERROUT(ENXIO); + + /* Proceed to multilink layer. Mutex will be unlocked inside. */ + error = ng_ppp_mp_recv(node, item, proto, linkNum); + mtx_assert(&priv->rmtx, MA_NOTOWNED); + return (error); + +done: + mtx_unlock(&priv->rmtx); + NG_FREE_ITEM(item); + return (error); +} + +/* + * Multilink layer + */ + +/* + * Handle an incoming multi-link fragment + * + * The fragment reassembly algorithm is somewhat complex. This is mainly + * because we are required not to reorder the reconstructed packets, yet + * fragments are only guaranteed to arrive in order on a per-link basis. + * In other words, when we have a complete packet ready, but the previous + * packet is still incomplete, we have to decide between delivering the + * complete packet and throwing away the incomplete one, or waiting to + * see if the remainder of the incomplete one arrives, at which time we + * can deliver both packets, in order. + * + * This problem is exacerbated by "sequence number slew", which is when + * the sequence numbers coming in from different links are far apart from + * each other. In particular, certain unnamed equipment (*cough* Ascend) + * has been seen to generate sequence number slew of up to 10 on an ISDN + * 2B-channel MP link. There is nothing invalid about sequence number slew + * but it makes the reasssembly process have to work harder. + * + * However, the peer is required to transmit fragments in order on each + * link. That means if we define MSEQ as the minimum over all links of + * the highest sequence number received on that link, then we can always + * give up any hope of receiving a fragment with sequence number < MSEQ in + * the future (all of this using 'wraparound' sequence number space). + * Therefore we can always immediately throw away incomplete packets + * missing fragments with sequence numbers < MSEQ. + * + * Here is an overview of our algorithm: + * + * o Received fragments are inserted into a queue, for which we + * maintain these invariants between calls to this function: + * + * - Fragments are ordered in the queue by sequence number + * - If a complete packet is at the head of the queue, then + * the first fragment in the packet has seq# > MSEQ + 1 + * (otherwise, we could deliver it immediately) + * - If any fragments have seq# < MSEQ, then they are necessarily + * part of a packet whose missing seq#'s are all > MSEQ (otherwise, + * we can throw them away because they'll never be completed) + * - The queue contains at most MP_MAX_QUEUE_LEN fragments + * + * o We have a periodic timer that checks the queue for the first + * complete packet that has been sitting in the queue "too long". + * When one is detected, all previous (incomplete) fragments are + * discarded, their missing fragments are declared lost and MSEQ + * is increased. + * + * o If we recieve a fragment with seq# < MSEQ, we throw it away + * because we've already delcared it lost. + * + * This assumes linkNum != NG_PPP_BUNDLE_LINKNUM. + */ +static int +ng_ppp_mp_recv(node_p node, item_p item, uint16_t proto, uint16_t linkNum) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_ppp_link *const link = &priv->links[linkNum]; + struct ng_ppp_frag *frag; + struct ng_ppp_frag *qent; + int i, diff, inserted; + struct mbuf *m; + int error = 0; + + if ((!priv->conf.enableMultilink) || proto != PROT_MP) { + /* Stats */ + priv->bundleStats.recvFrames++; + priv->bundleStats.recvOctets += NGI_M(item)->m_pkthdr.len; + + mtx_unlock(&priv->rmtx); + return (ng_ppp_crypt_recv(node, item, proto, linkNum)); + } + + NGI_GET_M(item, m); + + /* Get a new frag struct from the free queue */ + if ((frag = TAILQ_FIRST(&priv->fragsfree)) == NULL) { + printf("No free fragments headers in ng_ppp!\n"); + NG_FREE_M(m); + goto process; + } + + /* Extract fragment information from MP header */ + if (priv->conf.recvShortSeq) { + uint16_t shdr; + + if (m->m_pkthdr.len < 2) { + link->stats.runts++; + NG_FREE_M(m); + ERROUT(EINVAL); + } + if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) + ERROUT(ENOBUFS); + + shdr = ntohs(*mtod(m, uint16_t *)); + frag->seq = MP_SHORT_EXTEND(shdr); + frag->first = (shdr & MP_SHORT_FIRST_FLAG) != 0; + frag->last = (shdr & MP_SHORT_LAST_FLAG) != 0; + diff = MP_SHORT_SEQ_DIFF(frag->seq, priv->mseq); + m_adj(m, 2); + } else { + uint32_t lhdr; + + if (m->m_pkthdr.len < 4) { + link->stats.runts++; + NG_FREE_M(m); + ERROUT(EINVAL); + } + if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) + ERROUT(ENOBUFS); + + lhdr = ntohl(*mtod(m, uint32_t *)); + frag->seq = MP_LONG_EXTEND(lhdr); + frag->first = (lhdr & MP_LONG_FIRST_FLAG) != 0; + frag->last = (lhdr & MP_LONG_LAST_FLAG) != 0; + diff = MP_LONG_SEQ_DIFF(frag->seq, priv->mseq); + m_adj(m, 4); + } + frag->data = m; + getmicrouptime(&frag->timestamp); + + /* If sequence number is < MSEQ, we've already declared this + fragment as lost, so we have no choice now but to drop it */ + if (diff < 0) { + link->stats.dropFragments++; + NG_FREE_M(m); + ERROUT(0); + } + + /* Update highest received sequence number on this link and MSEQ */ + priv->mseq = link->seq = frag->seq; + for (i = 0; i < priv->numActiveLinks; i++) { + struct ng_ppp_link *const alink = + &priv->links[priv->activeLinks[i]]; + + if (MP_RECV_SEQ_DIFF(priv, alink->seq, priv->mseq) < 0) + priv->mseq = alink->seq; + } + + /* Remove frag struct from free queue. */ + TAILQ_REMOVE(&priv->fragsfree, frag, f_qent); + + /* Add fragment to queue, which is sorted by sequence number */ + inserted = 0; + TAILQ_FOREACH_REVERSE(qent, &priv->frags, ng_ppp_fraglist, f_qent) { + diff = MP_RECV_SEQ_DIFF(priv, frag->seq, qent->seq); + if (diff > 0) { + TAILQ_INSERT_AFTER(&priv->frags, qent, frag, f_qent); + inserted = 1; + break; + } else if (diff == 0) { /* should never happen! */ + link->stats.dupFragments++; + NG_FREE_M(frag->data); + TAILQ_INSERT_HEAD(&priv->fragsfree, frag, f_qent); + ERROUT(EINVAL); + } + } + if (!inserted) + TAILQ_INSERT_HEAD(&priv->frags, frag, f_qent); + +process: + /* Process the queue */ + /* NOTE: rmtx will be unlocked for sending time! */ + error = ng_ppp_frag_process(node, item); + mtx_unlock(&priv->rmtx); + return (error); + +done: + mtx_unlock(&priv->rmtx); + NG_FREE_ITEM(item); + return (error); +} + +/************************************************************************ + HELPER STUFF + ************************************************************************/ + +/* + * If new mseq > current then set it and update all active links + */ +static void +ng_ppp_bump_mseq(node_p node, int32_t new_mseq) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + int i; + + if (MP_RECV_SEQ_DIFF(priv, priv->mseq, new_mseq) < 0) { + priv->mseq = new_mseq; + for (i = 0; i < priv->numActiveLinks; i++) { + struct ng_ppp_link *const alink = + &priv->links[priv->activeLinks[i]]; + + if (MP_RECV_SEQ_DIFF(priv, + alink->seq, new_mseq) < 0) + alink->seq = new_mseq; + } + } +} + +/* + * Examine our list of fragments, and determine if there is a + * complete and deliverable packet at the head of the list. + * Return 1 if so, zero otherwise. + */ +static int +ng_ppp_check_packet(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_ppp_frag *qent, *qnext; + + /* Check for empty queue */ + if (TAILQ_EMPTY(&priv->frags)) + return (0); + + /* Check first fragment is the start of a deliverable packet */ + qent = TAILQ_FIRST(&priv->frags); + if (!qent->first || MP_RECV_SEQ_DIFF(priv, qent->seq, priv->mseq) > 1) + return (0); + + /* Check that all the fragments are there */ + while (!qent->last) { + qnext = TAILQ_NEXT(qent, f_qent); + if (qnext == NULL) /* end of queue */ + return (0); + if (qnext->seq != MP_NEXT_RECV_SEQ(priv, qent->seq)) + return (0); + qent = qnext; + } + + /* Got one */ + return (1); +} + +/* + * Pull a completed packet off the head of the incoming fragment queue. + * This assumes there is a completed packet there to pull off. + */ +static void +ng_ppp_get_packet(node_p node, struct mbuf **mp) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_ppp_frag *qent, *qnext; + struct mbuf *m = NULL, *tail; + + qent = TAILQ_FIRST(&priv->frags); + KASSERT(!TAILQ_EMPTY(&priv->frags) && qent->first, + ("%s: no packet", __func__)); + for (tail = NULL; qent != NULL; qent = qnext) { + qnext = TAILQ_NEXT(qent, f_qent); + KASSERT(!TAILQ_EMPTY(&priv->frags), + ("%s: empty q", __func__)); + TAILQ_REMOVE(&priv->frags, qent, f_qent); + if (tail == NULL) + tail = m = qent->data; + else { + m->m_pkthdr.len += qent->data->m_pkthdr.len; + tail->m_next = qent->data; + } + while (tail->m_next != NULL) + tail = tail->m_next; + if (qent->last) { + qnext = NULL; + /* Bump MSEQ if necessary */ + ng_ppp_bump_mseq(node, qent->seq); + } + TAILQ_INSERT_HEAD(&priv->fragsfree, qent, f_qent); + } + *mp = m; +} + +/* + * Trim fragments from the queue whose packets can never be completed. + * This assumes a complete packet is NOT at the beginning of the queue. + * Returns 1 if fragments were removed, zero otherwise. + */ +static int +ng_ppp_frag_trim(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_ppp_frag *qent, *qnext = NULL; + int removed = 0; + + /* Scan for "dead" fragments and remove them */ + while (1) { + int dead = 0; + + /* If queue is empty, we're done */ + if (TAILQ_EMPTY(&priv->frags)) + break; + + /* Determine whether first fragment can ever be completed */ + TAILQ_FOREACH(qent, &priv->frags, f_qent) { + if (MP_RECV_SEQ_DIFF(priv, qent->seq, priv->mseq) >= 0) + break; + qnext = TAILQ_NEXT(qent, f_qent); + KASSERT(qnext != NULL, + ("%s: last frag < MSEQ?", __func__)); + if (qnext->seq != MP_NEXT_RECV_SEQ(priv, qent->seq) + || qent->last || qnext->first) { + dead = 1; + break; + } + } + if (!dead) + break; + + /* Remove fragment and all others in the same packet */ + while ((qent = TAILQ_FIRST(&priv->frags)) != qnext) { + KASSERT(!TAILQ_EMPTY(&priv->frags), + ("%s: empty q", __func__)); + priv->bundleStats.dropFragments++; + TAILQ_REMOVE(&priv->frags, qent, f_qent); + NG_FREE_M(qent->data); + TAILQ_INSERT_HEAD(&priv->fragsfree, qent, f_qent); + removed = 1; + } + } + return (removed); +} + +/* + * Drop fragments on queue overflow. + * Returns 1 if fragments were removed, zero otherwise. + */ +static int +ng_ppp_frag_drop(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + /* Check queue length */ + if (TAILQ_EMPTY(&priv->fragsfree)) { + struct ng_ppp_frag *qent; + + /* Get oldest fragment */ + KASSERT(!TAILQ_EMPTY(&priv->frags), + ("%s: empty q", __func__)); + qent = TAILQ_FIRST(&priv->frags); + + /* Bump MSEQ if necessary */ + ng_ppp_bump_mseq(node, qent->seq); + + /* Drop it */ + priv->bundleStats.dropFragments++; + TAILQ_REMOVE(&priv->frags, qent, f_qent); + NG_FREE_M(qent->data); + TAILQ_INSERT_HEAD(&priv->fragsfree, qent, f_qent); + + return (1); + } + return (0); +} + +/* + * Run the queue, restoring the queue invariants + */ +static int +ng_ppp_frag_process(node_p node, item_p oitem) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct mbuf *m; + item_p item; + uint16_t proto; + + do { + /* Deliver any deliverable packets */ + while (ng_ppp_check_packet(node)) { + ng_ppp_get_packet(node, &m); + if ((m = ng_ppp_cutproto(m, &proto)) == NULL) + continue; + if (!PROT_VALID(proto)) { + priv->bundleStats.badProtos++; + NG_FREE_M(m); + continue; + } + if (oitem) { /* If original item present - reuse it. */ + item = oitem; + oitem = NULL; + NGI_M(item) = m; + } else { + item = ng_package_data(m, NG_NOFLAGS); + } + if (item != NULL) { + /* Stats */ + priv->bundleStats.recvFrames++; + priv->bundleStats.recvOctets += + NGI_M(item)->m_pkthdr.len; + + /* Drop mutex for the sending time. + * Priv may change, but we are ready! + */ + mtx_unlock(&priv->rmtx); + ng_ppp_crypt_recv(node, item, proto, + NG_PPP_BUNDLE_LINKNUM); + mtx_lock(&priv->rmtx); + } + } + /* Delete dead fragments and try again */ + } while (ng_ppp_frag_trim(node) || ng_ppp_frag_drop(node)); + + /* If we haven't reused original item - free it. */ + if (oitem) NG_FREE_ITEM(oitem); + + /* Done */ + return (0); +} + +/* + * Check for 'stale' completed packets that need to be delivered + * + * If a link goes down or has a temporary failure, MSEQ can get + * "stuck", because no new incoming fragments appear on that link. + * This can cause completed packets to never get delivered if + * their sequence numbers are all > MSEQ + 1. + * + * This routine checks how long all of the completed packets have + * been sitting in the queue, and if too long, removes fragments + * from the queue and increments MSEQ to allow them to be delivered. + */ +static void +ng_ppp_frag_checkstale(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_ppp_frag *qent, *beg, *end; + struct timeval now, age; + struct mbuf *m; + int seq; + item_p item; + int endseq; + uint16_t proto; + + now.tv_sec = 0; /* uninitialized state */ + while (1) { + + /* If queue is empty, we're done */ + if (TAILQ_EMPTY(&priv->frags)) + break; + + /* Find the first complete packet in the queue */ + beg = end = NULL; + seq = TAILQ_FIRST(&priv->frags)->seq; + TAILQ_FOREACH(qent, &priv->frags, f_qent) { + if (qent->first) + beg = qent; + else if (qent->seq != seq) + beg = NULL; + if (beg != NULL && qent->last) { + end = qent; + break; + } + seq = MP_NEXT_RECV_SEQ(priv, seq); + } + + /* If none found, exit */ + if (end == NULL) + break; + + /* Get current time (we assume we've been up for >= 1 second) */ + if (now.tv_sec == 0) + getmicrouptime(&now); + + /* Check if packet has been queued too long */ + age = now; + timevalsub(&age, &beg->timestamp); + if (timevalcmp(&age, &ng_ppp_max_staleness, < )) + break; + + /* Throw away junk fragments in front of the completed packet */ + while ((qent = TAILQ_FIRST(&priv->frags)) != beg) { + KASSERT(!TAILQ_EMPTY(&priv->frags), + ("%s: empty q", __func__)); + priv->bundleStats.dropFragments++; + TAILQ_REMOVE(&priv->frags, qent, f_qent); + NG_FREE_M(qent->data); + TAILQ_INSERT_HEAD(&priv->fragsfree, qent, f_qent); + } + + /* Extract completed packet */ + endseq = end->seq; + ng_ppp_get_packet(node, &m); + + if ((m = ng_ppp_cutproto(m, &proto)) == NULL) + continue; + if (!PROT_VALID(proto)) { + priv->bundleStats.badProtos++; + NG_FREE_M(m); + continue; + } + + /* Deliver packet */ + if ((item = ng_package_data(m, NG_NOFLAGS)) != NULL) { + /* Stats */ + priv->bundleStats.recvFrames++; + priv->bundleStats.recvOctets += NGI_M(item)->m_pkthdr.len; + + ng_ppp_crypt_recv(node, item, proto, + NG_PPP_BUNDLE_LINKNUM); + } + } +} + +/* + * Periodically call ng_ppp_frag_checkstale() + */ +static void +ng_ppp_frag_timeout(node_p node, hook_p hook, void *arg1, int arg2) +{ + /* XXX: is this needed? */ + if (NG_NODE_NOT_VALID(node)) + return; + + /* Scan the fragment queue */ + ng_ppp_frag_checkstale(node); + + /* Start timer again */ + ng_ppp_start_frag_timer(node); +} + +/* + * Deliver a frame out on the bundle, i.e., figure out how to fragment + * the frame across the individual PPP links and do so. + */ +static int +ng_ppp_mp_xmit(node_p node, item_p item, uint16_t proto) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + const int hdr_len = priv->conf.xmitShortSeq ? 2 : 4; + int distrib[NG_PPP_MAX_LINKS]; + int firstFragment; + int activeLinkNum; + struct mbuf *m; + int plen; + int frags; + int32_t seq; + + /* At least one link must be active */ + if (priv->numActiveLinks == 0) { + NG_FREE_ITEM(item); + return (ENETDOWN); + } + + /* Save length for later stats. */ + plen = NGI_M(item)->m_pkthdr.len; + + if (!priv->conf.enableMultilink) { + return (ng_ppp_link_xmit(node, item, proto, + priv->activeLinks[0], plen)); + } + + /* Extract mbuf. */ + NGI_GET_M(item, m); + + /* Prepend protocol number, possibly compressed. */ + if ((m = ng_ppp_addproto(m, proto, 1)) == NULL) { + NG_FREE_ITEM(item); + return (ENOBUFS); + } + + /* Clear distribution plan */ + bzero(&distrib, priv->numActiveLinks * sizeof(distrib[0])); + + mtx_lock(&priv->xmtx); + + /* Round-robin strategy */ + if (priv->conf.enableRoundRobin) { + activeLinkNum = priv->lastLink++ % priv->numActiveLinks; + distrib[activeLinkNum] = m->m_pkthdr.len; + goto deliver; + } + + /* Strategy when all links are equivalent (optimize the common case) */ + if (priv->allLinksEqual) { + int numFrags, fraction, remain; + int i; + + /* Calculate optimal fragment count */ + numFrags = priv->numActiveLinks; + if (numFrags > m->m_pkthdr.len / MP_MIN_FRAG_LEN) + numFrags = m->m_pkthdr.len / MP_MIN_FRAG_LEN; + if (numFrags == 0) + numFrags = 1; + + fraction = m->m_pkthdr.len / numFrags; + remain = m->m_pkthdr.len - (fraction * numFrags); + + /* Assign distribution */ + for (i = 0; i < numFrags; i++) { + distrib[priv->lastLink++ % priv->numActiveLinks] + = fraction + (((remain--) > 0)?1:0); + } + goto deliver; + } + + /* Strategy when all links are not equivalent */ + ng_ppp_mp_strategy(node, m->m_pkthdr.len, distrib); + +deliver: + /* Estimate fragments count */ + frags = 0; + for (activeLinkNum = priv->numActiveLinks - 1; + activeLinkNum >= 0; activeLinkNum--) { + const uint16_t linkNum = priv->activeLinks[activeLinkNum]; + struct ng_ppp_link *const link = &priv->links[linkNum]; + + frags += (distrib[activeLinkNum] + link->conf.mru - hdr_len - 1) / + (link->conf.mru - hdr_len); + } + + /* Get out initial sequence number */ + seq = priv->xseq; + + /* Update next sequence number */ + if (priv->conf.xmitShortSeq) { + priv->xseq = (seq + frags) & MP_SHORT_SEQ_MASK; + } else { + priv->xseq = (seq + frags) & MP_LONG_SEQ_MASK; + } + + mtx_unlock(&priv->xmtx); + + /* Send alloted portions of frame out on the link(s) */ + for (firstFragment = 1, activeLinkNum = priv->numActiveLinks - 1; + activeLinkNum >= 0; activeLinkNum--) { + const uint16_t linkNum = priv->activeLinks[activeLinkNum]; + struct ng_ppp_link *const link = &priv->links[linkNum]; + + /* Deliver fragment(s) out the next link */ + for ( ; distrib[activeLinkNum] > 0; firstFragment = 0) { + int len, lastFragment, error; + struct mbuf *m2; + + /* Calculate fragment length; don't exceed link MTU */ + len = distrib[activeLinkNum]; + if (len > link->conf.mru - hdr_len) + len = link->conf.mru - hdr_len; + distrib[activeLinkNum] -= len; + lastFragment = (len == m->m_pkthdr.len); + + /* Split off next fragment as "m2" */ + m2 = m; + if (!lastFragment) { + struct mbuf *n = m_split(m, len, M_DONTWAIT); + + if (n == NULL) { + NG_FREE_M(m); + if (firstFragment) + NG_FREE_ITEM(item); + return (ENOMEM); + } + m_tag_copy_chain(n, m, M_DONTWAIT); + m = n; + } + + /* Prepend MP header */ + if (priv->conf.xmitShortSeq) { + uint16_t shdr; + + shdr = seq; + seq = (seq + 1) & MP_SHORT_SEQ_MASK; + if (firstFragment) + shdr |= MP_SHORT_FIRST_FLAG; + if (lastFragment) + shdr |= MP_SHORT_LAST_FLAG; + shdr = htons(shdr); + m2 = ng_ppp_prepend(m2, &shdr, 2); + } else { + uint32_t lhdr; + + lhdr = seq; + seq = (seq + 1) & MP_LONG_SEQ_MASK; + if (firstFragment) + lhdr |= MP_LONG_FIRST_FLAG; + if (lastFragment) + lhdr |= MP_LONG_LAST_FLAG; + lhdr = htonl(lhdr); + m2 = ng_ppp_prepend(m2, &lhdr, 4); + } + if (m2 == NULL) { + if (!lastFragment) + m_freem(m); + if (firstFragment) + NG_FREE_ITEM(item); + return (ENOBUFS); + } + + /* Send fragment */ + if (firstFragment) { + NGI_M(item) = m2; /* Reuse original item. */ + } else { + item = ng_package_data(m2, NG_NOFLAGS); + } + if (item != NULL) { + error = ng_ppp_link_xmit(node, item, PROT_MP, + linkNum, (firstFragment?plen:0)); + if (error != 0) { + if (!lastFragment) + NG_FREE_M(m); + return (error); + } + } + } + } + + /* Done */ + return (0); +} + +/* + * Computing the optimal fragmentation + * ----------------------------------- + * + * This routine tries to compute the optimal fragmentation pattern based + * on each link's latency, bandwidth, and calculated additional latency. + * The latter quantity is the additional latency caused by previously + * written data that has not been transmitted yet. + * + * This algorithm is only useful when not all of the links have the + * same latency and bandwidth values. + * + * The essential idea is to make the last bit of each fragment of the + * frame arrive at the opposite end at the exact same time. This greedy + * algorithm is optimal, in that no other scheduling could result in any + * packet arriving any sooner unless packets are delivered out of order. + * + * Suppose link i has bandwidth b_i (in tens of bytes per milisecond) and + * latency l_i (in miliseconds). Consider the function function f_i(t) + * which is equal to the number of bytes that will have arrived at + * the peer after t miliseconds if we start writing continuously at + * time t = 0. Then f_i(t) = b_i * (t - l_i) = ((b_i * t) - (l_i * b_i). + * That is, f_i(t) is a line with slope b_i and y-intersect -(l_i * b_i). + * Note that the y-intersect is always <= zero because latency can't be + * negative. Note also that really the function is f_i(t) except when + * f_i(t) is negative, in which case the function is zero. To take + * care of this, let Q_i(t) = { if (f_i(t) > 0) return 1; else return 0; }. + * So the actual number of bytes that will have arrived at the peer after + * t miliseconds is f_i(t) * Q_i(t). + * + * At any given time, each link has some additional latency a_i >= 0 + * due to previously written fragment(s) which are still in the queue. + * This value is easily computed from the time since last transmission, + * the previous latency value, the number of bytes written, and the + * link's bandwidth. + * + * Assume that l_i includes any a_i already, and that the links are + * sorted by latency, so that l_i <= l_{i+1}. + * + * Let N be the total number of bytes in the current frame we are sending. + * + * Suppose we were to start writing bytes at time t = 0 on all links + * simultaneously, which is the most we can possibly do. Then let + * F(t) be equal to the total number of bytes received by the peer + * after t miliseconds. Then F(t) = Sum_i (f_i(t) * Q_i(t)). + * + * Our goal is simply this: fragment the frame across the links such + * that the peer is able to reconstruct the completed frame as soon as + * possible, i.e., at the least possible value of t. Call this value t_0. + * + * Then it follows that F(t_0) = N. Our strategy is first to find the value + * of t_0, and then deduce how many bytes to write to each link. + * + * Rewriting F(t_0): + * + * t_0 = ( N + Sum_i ( l_i * b_i * Q_i(t_0) ) ) / Sum_i ( b_i * Q_i(t_0) ) + * + * Now, we note that Q_i(t) is constant for l_i <= t <= l_{i+1}. t_0 will + * lie in one of these ranges. To find it, we just need to find the i such + * that F(l_i) <= N <= F(l_{i+1}). Then we compute all the constant values + * for Q_i() in this range, plug in the remaining values, solving for t_0. + * + * Once t_0 is known, then the number of bytes to send on link i is + * just f_i(t_0) * Q_i(t_0). + * + * In other words, we start allocating bytes to the links one at a time. + * We keep adding links until the frame is completely sent. Some links + * may not get any bytes because their latency is too high. + * + * Is all this work really worth the trouble? Depends on the situation. + * The bigger the ratio of computer speed to link speed, and the more + * important total bundle latency is (e.g., for interactive response time), + * the more it's worth it. There is however the cost of calling this + * function for every frame. The running time is O(n^2) where n is the + * number of links that receive a non-zero number of bytes. + * + * Since latency is measured in miliseconds, the "resolution" of this + * algorithm is one milisecond. + * + * To avoid this algorithm altogether, configure all links to have the + * same latency and bandwidth. + */ +static void +ng_ppp_mp_strategy(node_p node, int len, int *distrib) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + int latency[NG_PPP_MAX_LINKS]; + int sortByLatency[NG_PPP_MAX_LINKS]; + int activeLinkNum; + int t0, total, topSum, botSum; + struct timeval now; + int i, numFragments; + + /* If only one link, this gets real easy */ + if (priv->numActiveLinks == 1) { + distrib[0] = len; + return; + } + + /* Get current time */ + getmicrouptime(&now); + + /* Compute latencies for each link at this point in time */ + for (activeLinkNum = 0; + activeLinkNum < priv->numActiveLinks; activeLinkNum++) { + struct ng_ppp_link *alink; + struct timeval diff; + int xmitBytes; + + /* Start with base latency value */ + alink = &priv->links[priv->activeLinks[activeLinkNum]]; + latency[activeLinkNum] = alink->latency; + sortByLatency[activeLinkNum] = activeLinkNum; /* see below */ + + /* Any additional latency? */ + if (alink->bytesInQueue == 0) + continue; + + /* Compute time delta since last write */ + diff = now; + timevalsub(&diff, &alink->lastWrite); + + /* alink->bytesInQueue will be changed, mark change time. */ + alink->lastWrite = now; + + if (now.tv_sec < 0 || diff.tv_sec >= 10) { /* sanity */ + alink->bytesInQueue = 0; + continue; + } + + /* How many bytes could have transmitted since last write? */ + xmitBytes = (alink->conf.bandwidth * 10 * diff.tv_sec) + + (alink->conf.bandwidth * (diff.tv_usec / 1000)) / 100; + alink->bytesInQueue -= xmitBytes; + if (alink->bytesInQueue < 0) + alink->bytesInQueue = 0; + else + latency[activeLinkNum] += + (100 * alink->bytesInQueue) / alink->conf.bandwidth; + } + + /* Sort active links by latency */ + qsort_r(sortByLatency, + priv->numActiveLinks, sizeof(*sortByLatency), latency, ng_ppp_intcmp); + + /* Find the interval we need (add links in sortByLatency[] order) */ + for (numFragments = 1; + numFragments < priv->numActiveLinks; numFragments++) { + for (total = i = 0; i < numFragments; i++) { + int flowTime; + + flowTime = latency[sortByLatency[numFragments]] + - latency[sortByLatency[i]]; + total += ((flowTime * priv->links[ + priv->activeLinks[sortByLatency[i]]].conf.bandwidth) + + 99) / 100; + } + if (total >= len) + break; + } + + /* Solve for t_0 in that interval */ + for (topSum = botSum = i = 0; i < numFragments; i++) { + int bw = priv->links[ + priv->activeLinks[sortByLatency[i]]].conf.bandwidth; + + topSum += latency[sortByLatency[i]] * bw; /* / 100 */ + botSum += bw; /* / 100 */ + } + t0 = ((len * 100) + topSum + botSum / 2) / botSum; + + /* Compute f_i(t_0) all i */ + for (total = i = 0; i < numFragments; i++) { + int bw = priv->links[ + priv->activeLinks[sortByLatency[i]]].conf.bandwidth; + + distrib[sortByLatency[i]] = + (bw * (t0 - latency[sortByLatency[i]]) + 50) / 100; + total += distrib[sortByLatency[i]]; + } + + /* Deal with any rounding error */ + if (total < len) { + struct ng_ppp_link *fastLink = + &priv->links[priv->activeLinks[sortByLatency[0]]]; + int fast = 0; + + /* Find the fastest link */ + for (i = 1; i < numFragments; i++) { + struct ng_ppp_link *const link = + &priv->links[priv->activeLinks[sortByLatency[i]]]; + + if (link->conf.bandwidth > fastLink->conf.bandwidth) { + fast = i; + fastLink = link; + } + } + distrib[sortByLatency[fast]] += len - total; + } else while (total > len) { + struct ng_ppp_link *slowLink = + &priv->links[priv->activeLinks[sortByLatency[0]]]; + int delta, slow = 0; + + /* Find the slowest link that still has bytes to remove */ + for (i = 1; i < numFragments; i++) { + struct ng_ppp_link *const link = + &priv->links[priv->activeLinks[sortByLatency[i]]]; + + if (distrib[sortByLatency[slow]] == 0 + || (distrib[sortByLatency[i]] > 0 + && link->conf.bandwidth < + slowLink->conf.bandwidth)) { + slow = i; + slowLink = link; + } + } + delta = total - len; + if (delta > distrib[sortByLatency[slow]]) + delta = distrib[sortByLatency[slow]]; + distrib[sortByLatency[slow]] -= delta; + total -= delta; + } +} + +/* + * Compare two integers + */ +static int +ng_ppp_intcmp(void *latency, const void *v1, const void *v2) +{ + const int index1 = *((const int *) v1); + const int index2 = *((const int *) v2); + + return ((int *)latency)[index1] - ((int *)latency)[index2]; +} + +/* + * Prepend a possibly compressed PPP protocol number in front of a frame + */ +static struct mbuf * +ng_ppp_addproto(struct mbuf *m, uint16_t proto, int compOK) +{ + if (compOK && PROT_COMPRESSABLE(proto)) { + uint8_t pbyte = (uint8_t)proto; + + return ng_ppp_prepend(m, &pbyte, 1); + } else { + uint16_t pword = htons((uint16_t)proto); + + return ng_ppp_prepend(m, &pword, 2); + } +} + +/* + * Cut a possibly compressed PPP protocol number from the front of a frame. + */ +static struct mbuf * +ng_ppp_cutproto(struct mbuf *m, uint16_t *proto) +{ + + *proto = 0; + if (m->m_len < 1 && (m = m_pullup(m, 1)) == NULL) + return (NULL); + + *proto = *mtod(m, uint8_t *); + m_adj(m, 1); + + if (!PROT_VALID(*proto)) { + if (m->m_len < 1 && (m = m_pullup(m, 1)) == NULL) + return (NULL); + + *proto = (*proto << 8) + *mtod(m, uint8_t *); + m_adj(m, 1); + } + + return (m); +} + +/* + * Prepend some bytes to an mbuf. + */ +static struct mbuf * +ng_ppp_prepend(struct mbuf *m, const void *buf, int len) +{ + M_PREPEND(m, len, M_DONTWAIT); + if (m == NULL || (m->m_len < len && (m = m_pullup(m, len)) == NULL)) + return (NULL); + bcopy(buf, mtod(m, uint8_t *), len); + return (m); +} + +/* + * Update private information that is derived from other private information + */ +static void +ng_ppp_update(node_p node, int newConf) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + int i; + + /* Update active status for VJ Compression */ + priv->vjCompHooked = priv->hooks[HOOK_INDEX_VJC_IP] != NULL + && priv->hooks[HOOK_INDEX_VJC_COMP] != NULL + && priv->hooks[HOOK_INDEX_VJC_UNCOMP] != NULL + && priv->hooks[HOOK_INDEX_VJC_VJIP] != NULL; + + /* Increase latency for each link an amount equal to one MP header */ + if (newConf) { + for (i = 0; i < NG_PPP_MAX_LINKS; i++) { + int hdrBytes; + + if (priv->links[i].conf.bandwidth == 0) + continue; + + hdrBytes = MP_AVERAGE_LINK_OVERHEAD + + (priv->links[i].conf.enableACFComp ? 0 : 2) + + (priv->links[i].conf.enableProtoComp ? 1 : 2) + + (priv->conf.xmitShortSeq ? 2 : 4); + priv->links[i].latency = + priv->links[i].conf.latency + + (hdrBytes / priv->links[i].conf.bandwidth + 50) / 100; + } + } + + /* Update list of active links */ + bzero(&priv->activeLinks, sizeof(priv->activeLinks)); + priv->numActiveLinks = 0; + priv->allLinksEqual = 1; + for (i = 0; i < NG_PPP_MAX_LINKS; i++) { + struct ng_ppp_link *const link = &priv->links[i]; + + /* Is link active? */ + if (link->conf.enableLink && link->hook != NULL) { + struct ng_ppp_link *link0; + + /* Add link to list of active links */ + priv->activeLinks[priv->numActiveLinks++] = i; + link0 = &priv->links[priv->activeLinks[0]]; + + /* Determine if all links are still equal */ + if (link->latency != link0->latency + || link->conf.bandwidth != link0->conf.bandwidth) + priv->allLinksEqual = 0; + + /* Initialize rec'd sequence number */ + if (link->seq == MP_NOSEQ) { + link->seq = (link == link0) ? + MP_INITIAL_SEQ : link0->seq; + } + } else + link->seq = MP_NOSEQ; + } + + /* Update MP state as multi-link is active or not */ + if (priv->conf.enableMultilink && priv->numActiveLinks > 0) + ng_ppp_start_frag_timer(node); + else { + ng_ppp_stop_frag_timer(node); + ng_ppp_frag_reset(node); + priv->xseq = MP_INITIAL_SEQ; + priv->mseq = MP_INITIAL_SEQ; + for (i = 0; i < NG_PPP_MAX_LINKS; i++) { + struct ng_ppp_link *const link = &priv->links[i]; + + bzero(&link->lastWrite, sizeof(link->lastWrite)); + link->bytesInQueue = 0; + link->seq = MP_NOSEQ; + } + } +} + +/* + * Determine if a new configuration would represent a valid change + * from the current configuration and link activity status. + */ +static int +ng_ppp_config_valid(node_p node, const struct ng_ppp_node_conf *newConf) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + int i, newNumLinksActive; + + /* Check per-link config and count how many links would be active */ + for (newNumLinksActive = i = 0; i < NG_PPP_MAX_LINKS; i++) { + if (newConf->links[i].enableLink && priv->links[i].hook != NULL) + newNumLinksActive++; + if (!newConf->links[i].enableLink) + continue; + if (newConf->links[i].mru < MP_MIN_LINK_MRU) + return (0); + if (newConf->links[i].bandwidth == 0) + return (0); + if (newConf->links[i].bandwidth > NG_PPP_MAX_BANDWIDTH) + return (0); + if (newConf->links[i].latency > NG_PPP_MAX_LATENCY) + return (0); + } + + /* Check bundle parameters */ + if (newConf->bund.enableMultilink && newConf->bund.mrru < MP_MIN_MRRU) + return (0); + + /* Disallow changes to multi-link configuration while MP is active */ + if (priv->numActiveLinks > 0 && newNumLinksActive > 0) { + if (!priv->conf.enableMultilink + != !newConf->bund.enableMultilink + || !priv->conf.xmitShortSeq != !newConf->bund.xmitShortSeq + || !priv->conf.recvShortSeq != !newConf->bund.recvShortSeq) + return (0); + } + + /* At most one link can be active unless multi-link is enabled */ + if (!newConf->bund.enableMultilink && newNumLinksActive > 1) + return (0); + + /* Configuration change would be valid */ + return (1); +} + +/* + * Free all entries in the fragment queue + */ +static void +ng_ppp_frag_reset(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_ppp_frag *qent, *qnext; + + for (qent = TAILQ_FIRST(&priv->frags); qent; qent = qnext) { + qnext = TAILQ_NEXT(qent, f_qent); + NG_FREE_M(qent->data); + TAILQ_INSERT_HEAD(&priv->fragsfree, qent, f_qent); + } + TAILQ_INIT(&priv->frags); +} + +/* + * Start fragment queue timer + */ +static void +ng_ppp_start_frag_timer(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (!(callout_pending(&priv->fragTimer))) + ng_callout(&priv->fragTimer, node, NULL, MP_FRAGTIMER_INTERVAL, + ng_ppp_frag_timeout, NULL, 0); +} + +/* + * Stop fragment queue timer + */ +static void +ng_ppp_stop_frag_timer(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (callout_pending(&priv->fragTimer)) + ng_uncallout(&priv->fragTimer, node); +} diff --git a/sys/netgraph7/ng_ppp.h b/sys/netgraph7/ng_ppp.h new file mode 100644 index 0000000000..99dddf05ba --- /dev/null +++ b/sys/netgraph7/ng_ppp.h @@ -0,0 +1,245 @@ +/* + * ng_ppp.h + */ + +/*- + * Copyright (c) 1996-2000 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_ppp.h,v 1.14 2007/08/01 20:49:35 mav Exp $ + * $Whistle: ng_ppp.h,v 1.8 1999/01/25 02:40:02 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_PPP_H_ +#define _NETGRAPH_NG_PPP_H_ + +/* Node type name and magic cookie */ +#define NG_PPP_NODE_TYPE "ppp" +#define NGM_PPP_COOKIE 940897795 + +/* 64bit stats presence flag */ +#define NG_PPP_STATS64 + +/* Maximum number of supported links */ +#define NG_PPP_MAX_LINKS 16 + +/* Pseudo-link number representing the multi-link bundle */ +#define NG_PPP_BUNDLE_LINKNUM 0xffff + +/* Max allowable link latency (miliseconds) and bandwidth (bytes/second/10) */ +#define NG_PPP_MAX_LATENCY 1000 /* 1 second */ +#define NG_PPP_MAX_BANDWIDTH 125000 /* 10 Mbits / second */ + +/* Hook names */ +#define NG_PPP_HOOK_BYPASS "bypass" /* unknown protocols */ +#define NG_PPP_HOOK_COMPRESS "compress" /* outgoing compression */ +#define NG_PPP_HOOK_DECOMPRESS "decompress" /* incoming decompression */ +#define NG_PPP_HOOK_ENCRYPT "encrypt" /* outgoing encryption */ +#define NG_PPP_HOOK_DECRYPT "decrypt" /* incoming decryption */ +#define NG_PPP_HOOK_VJC_IP "vjc_ip" /* VJC raw IP */ +#define NG_PPP_HOOK_VJC_COMP "vjc_vjcomp" /* VJC compressed TCP */ +#define NG_PPP_HOOK_VJC_UNCOMP "vjc_vjuncomp" /* VJC uncompressed TCP */ +#define NG_PPP_HOOK_VJC_VJIP "vjc_vjip" /* VJC uncompressed IP */ +#define NG_PPP_HOOK_INET "inet" /* IP packet data */ +#define NG_PPP_HOOK_ATALK "atalk" /* AppleTalk packet data */ +#define NG_PPP_HOOK_IPX "ipx" /* IPX packet data */ +#define NG_PPP_HOOK_IPV6 "ipv6" /* IPv6 packet data */ + +#define NG_PPP_HOOK_LINK_PREFIX "link" /* append decimal link number */ + +/* Compress hook operation modes */ +enum { + NG_PPP_COMPRESS_NONE = 0, /* compression disabled */ + NG_PPP_COMPRESS_SIMPLE, /* original operation mode */ + NG_PPP_COMPRESS_FULL, /* compressor returns proto */ +}; + +/* Decompress hook operation modes */ +enum { + NG_PPP_DECOMPRESS_NONE = 0, /* decompression disabled */ + NG_PPP_DECOMPRESS_SIMPLE, /* original operation mode */ + NG_PPP_DECOMPRESS_FULL, /* forward any packet to decompressor */ +}; + +/* Netgraph commands */ +enum { + NGM_PPP_SET_CONFIG = 1, /* takes struct ng_ppp_node_conf */ + NGM_PPP_GET_CONFIG, /* returns ng_ppp_node_conf */ + NGM_PPP_GET_MP_STATE, /* returns ng_ppp_mp_state */ + NGM_PPP_GET_LINK_STATS, /* takes link #, returns stats struct */ + NGM_PPP_CLR_LINK_STATS, /* takes link #, clears link stats */ + NGM_PPP_GETCLR_LINK_STATS, /* takes link #, returns & clrs stats */ + NGM_PPP_GET_LINK_STATS64, /* takes link #, returns stats64 struct */ + NGM_PPP_GETCLR_LINK_STATS64, /* takes link #, returns stats64 & clrs */ +}; + +/* Multi-link sequence number state (for debugging) */ +struct ng_ppp_mp_state { + int32_t rseq[NG_PPP_MAX_LINKS]; /* highest rec'd MP seq # */ + int32_t mseq; /* min rseq[i] */ + int32_t xseq; /* next xmit MP seq # */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_PPP_MP_STATE_TYPE_INFO(atype) { \ + { "rseq", (atype) }, \ + { "mseq", &ng_parse_hint32_type }, \ + { "xseq", &ng_parse_hint32_type }, \ + { NULL } \ +} + +/* Per-link config structure */ +struct ng_ppp_link_conf { + u_char enableLink; /* enable this link */ + u_char enableProtoComp;/* enable protocol field compression */ + u_char enableACFComp; /* enable addr/ctrl field compression */ + u_int16_t mru; /* peer MRU */ + u_int32_t latency; /* link latency (in milliseconds) */ + u_int32_t bandwidth; /* link bandwidth (in bytes/sec/10) */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_PPP_LINK_TYPE_INFO { \ + { "enableLink", &ng_parse_uint8_type }, \ + { "enableProtoComp", &ng_parse_uint8_type }, \ + { "enableACFComp", &ng_parse_uint8_type }, \ + { "mru", &ng_parse_uint16_type }, \ + { "latency", &ng_parse_uint32_type }, \ + { "bandwidth", &ng_parse_uint32_type }, \ + { NULL } \ +} + +/* Bundle config structure */ +struct ng_ppp_bund_conf { + u_int16_t mrru; /* multilink peer MRRU */ + u_char enableMultilink; /* enable multilink */ + u_char recvShortSeq; /* recv multilink short seq # */ + u_char xmitShortSeq; /* xmit multilink short seq # */ + u_char enableRoundRobin; /* xmit whole packets */ + u_char enableIP; /* enable IP data flow */ + u_char enableIPv6; /* enable IPv6 data flow */ + u_char enableAtalk; /* enable AppleTalk data flow */ + u_char enableIPX; /* enable IPX data flow */ + u_char enableCompression; /* enable PPP compression */ + u_char enableDecompression; /* enable PPP decompression */ + u_char enableEncryption; /* enable PPP encryption */ + u_char enableDecryption; /* enable PPP decryption */ + u_char enableVJCompression; /* enable VJ compression */ + u_char enableVJDecompression; /* enable VJ decompression */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_PPP_BUND_TYPE_INFO { \ + { "mrru", &ng_parse_uint16_type }, \ + { "enableMultilink", &ng_parse_uint8_type }, \ + { "recvShortSeq", &ng_parse_uint8_type }, \ + { "xmitShortSeq", &ng_parse_uint8_type }, \ + { "enableRoundRobin", &ng_parse_uint8_type }, \ + { "enableIP", &ng_parse_uint8_type }, \ + { "enableIPv6", &ng_parse_uint8_type }, \ + { "enableAtalk", &ng_parse_uint8_type }, \ + { "enableIPX", &ng_parse_uint8_type }, \ + { "enableCompression", &ng_parse_uint8_type }, \ + { "enableDecompression", &ng_parse_uint8_type }, \ + { "enableEncryption", &ng_parse_uint8_type }, \ + { "enableDecryption", &ng_parse_uint8_type }, \ + { "enableVJCompression", &ng_parse_uint8_type }, \ + { "enableVJDecompression", &ng_parse_uint8_type }, \ + { NULL } \ +} + +/* Total node config structure */ +struct ng_ppp_node_conf { + struct ng_ppp_bund_conf bund; + struct ng_ppp_link_conf links[NG_PPP_MAX_LINKS]; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_PPP_CONFIG_TYPE_INFO(bctype, arytype) { \ + { "bund", (bctype) }, \ + { "links", (arytype) }, \ + { NULL } \ +} + +/* Statistics struct for a link (or the bundle if NG_PPP_BUNDLE_LINKNUM) */ +struct ng_ppp_link_stat { + u_int32_t xmitFrames; /* xmit frames on link */ + u_int32_t xmitOctets; /* xmit octets on link */ + u_int32_t recvFrames; /* recv frames on link */ + u_int32_t recvOctets; /* recv octets on link */ + u_int32_t badProtos; /* frames rec'd with bogus protocol */ + u_int32_t runts; /* Too short MP fragments */ + u_int32_t dupFragments; /* MP frames with duplicate seq # */ + u_int32_t dropFragments; /* MP fragments we had to drop */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_PPP_STATS_TYPE_INFO { \ + { "xmitFrames", &ng_parse_uint32_type }, \ + { "xmitOctets", &ng_parse_uint32_type }, \ + { "recvFrames", &ng_parse_uint32_type }, \ + { "recvOctets", &ng_parse_uint32_type }, \ + { "badProtos", &ng_parse_uint32_type }, \ + { "runts", &ng_parse_uint32_type }, \ + { "dupFragments", &ng_parse_uint32_type }, \ + { "dropFragments", &ng_parse_uint32_type }, \ + { NULL } \ +} + +/* Statistics struct for a link (or the bundle if NG_PPP_BUNDLE_LINKNUM) */ +struct ng_ppp_link_stat64 { + u_int64_t xmitFrames; /* xmit frames on link */ + u_int64_t xmitOctets; /* xmit octets on link */ + u_int64_t recvFrames; /* recv frames on link */ + u_int64_t recvOctets; /* recv octets on link */ + u_int64_t badProtos; /* frames rec'd with bogus protocol */ + u_int64_t runts; /* Too short MP fragments */ + u_int64_t dupFragments; /* MP frames with duplicate seq # */ + u_int64_t dropFragments; /* MP fragments we had to drop */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_PPP_STATS64_TYPE_INFO { \ + { "xmitFrames", &ng_parse_uint64_type }, \ + { "xmitOctets", &ng_parse_uint64_type }, \ + { "recvFrames", &ng_parse_uint64_type }, \ + { "recvOctets", &ng_parse_uint64_type }, \ + { "badProtos", &ng_parse_uint64_type }, \ + { "runts", &ng_parse_uint64_type }, \ + { "dupFragments", &ng_parse_uint64_type }, \ + { "dropFragments", &ng_parse_uint64_type }, \ + { NULL } \ +} + +#endif /* _NETGRAPH_NG_PPP_H_ */ diff --git a/sys/netgraph7/ng_pppoe.c b/sys/netgraph7/ng_pppoe.c new file mode 100644 index 0000000000..0ed0adb48f --- /dev/null +++ b/sys/netgraph7/ng_pppoe.c @@ -0,0 +1,1915 @@ +/* + * ng_pppoe.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_pppoe.c,v 1.94 2008/03/03 19:36:03 mav Exp $ + * $Whistle: ng_pppoe.c,v 1.10 1999/11/01 09:24:52 julian Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_PPPOE, "netgraph_pppoe", "netgraph pppoe node"); +#else +#define M_NETGRAPH_PPPOE M_NETGRAPH +#endif + +#define SIGNOFF "session closed" + +/* + * This section contains the netgraph method declarations for the + * pppoe node. These methods define the netgraph pppoe 'type'. + */ + +static ng_constructor_t ng_pppoe_constructor; +static ng_rcvmsg_t ng_pppoe_rcvmsg; +static ng_shutdown_t ng_pppoe_shutdown; +static ng_newhook_t ng_pppoe_newhook; +static ng_connect_t ng_pppoe_connect; +static ng_rcvdata_t ng_pppoe_rcvdata; +static ng_rcvdata_t ng_pppoe_rcvdata_ether; +static ng_rcvdata_t ng_pppoe_rcvdata_debug; +static ng_disconnect_t ng_pppoe_disconnect; + +/* Parse type for struct ngpppoe_init_data */ +static const struct ng_parse_struct_field ngpppoe_init_data_type_fields[] + = NG_PPPOE_INIT_DATA_TYPE_INFO; +static const struct ng_parse_type ngpppoe_init_data_state_type = { + &ng_parse_struct_type, + &ngpppoe_init_data_type_fields +}; + +/* Parse type for struct ngpppoe_sts */ +static const struct ng_parse_struct_field ng_pppoe_sts_type_fields[] + = NG_PPPOE_STS_TYPE_INFO; +static const struct ng_parse_type ng_pppoe_sts_state_type = { + &ng_parse_struct_type, + &ng_pppoe_sts_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_pppoe_cmds[] = { + { + NGM_PPPOE_COOKIE, + NGM_PPPOE_CONNECT, + "pppoe_connect", + &ngpppoe_init_data_state_type, + NULL + }, + { + NGM_PPPOE_COOKIE, + NGM_PPPOE_LISTEN, + "pppoe_listen", + &ngpppoe_init_data_state_type, + NULL + }, + { + NGM_PPPOE_COOKIE, + NGM_PPPOE_OFFER, + "pppoe_offer", + &ngpppoe_init_data_state_type, + NULL + }, + { + NGM_PPPOE_COOKIE, + NGM_PPPOE_SERVICE, + "pppoe_service", + &ngpppoe_init_data_state_type, + NULL + }, + { + NGM_PPPOE_COOKIE, + NGM_PPPOE_SUCCESS, + "pppoe_success", + &ng_pppoe_sts_state_type, + NULL + }, + { + NGM_PPPOE_COOKIE, + NGM_PPPOE_FAIL, + "pppoe_fail", + &ng_pppoe_sts_state_type, + NULL + }, + { + NGM_PPPOE_COOKIE, + NGM_PPPOE_CLOSE, + "pppoe_close", + &ng_pppoe_sts_state_type, + NULL + }, + { + NGM_PPPOE_COOKIE, + NGM_PPPOE_SETMODE, + "pppoe_setmode", + &ng_parse_string_type, + NULL + }, + { + NGM_PPPOE_COOKIE, + NGM_PPPOE_GETMODE, + "pppoe_getmode", + NULL, + &ng_parse_string_type + }, + { + NGM_PPPOE_COOKIE, + NGM_PPPOE_SETENADDR, + "setenaddr", + &ng_parse_enaddr_type, + NULL + }, + { 0 } +}; + +/* Netgraph node type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_PPPOE_NODE_TYPE, + .constructor = ng_pppoe_constructor, + .rcvmsg = ng_pppoe_rcvmsg, + .shutdown = ng_pppoe_shutdown, + .newhook = ng_pppoe_newhook, + .connect = ng_pppoe_connect, + .rcvdata = ng_pppoe_rcvdata, + .disconnect = ng_pppoe_disconnect, + .cmdlist = ng_pppoe_cmds, +}; +NETGRAPH_INIT(pppoe, &typestruct); + +/* + * States for the session state machine. + * These have no meaning if there is no hook attached yet. + */ +enum state { + PPPOE_SNONE=0, /* [both] Initial state */ + PPPOE_LISTENING, /* [Daemon] Listening for discover initiation pkt */ + PPPOE_SINIT, /* [Client] Sent discovery initiation */ + PPPOE_PRIMED, /* [Server] Awaiting PADI from daemon */ + PPPOE_SOFFER, /* [Server] Sent offer message (got PADI)*/ + PPPOE_SREQ, /* [Client] Sent a Request */ + PPPOE_NEWCONNECTED, /* [Server] Connection established, No data received */ + PPPOE_CONNECTED, /* [Both] Connection established, Data received */ + PPPOE_DEAD /* [Both] */ +}; + +#define NUMTAGS 20 /* number of tags we are set up to work with */ + +/* + * Information we store for each hook on each node for negotiating the + * session. The mbuf and cluster are freed once negotiation has completed. + * The whole negotiation block is then discarded. + */ + +struct sess_neg { + struct mbuf *m; /* holds cluster with last sent packet */ + union packet *pkt; /* points within the above cluster */ + struct callout handle; /* see timeout(9) */ + u_int timeout; /* 0,1,2,4,8,16 etc. seconds */ + u_int numtags; + const struct pppoe_tag *tags[NUMTAGS]; + u_int service_len; + u_int ac_name_len; + + struct datatag service; + struct datatag ac_name; +}; +typedef struct sess_neg *negp; + +/* + * Session information that is needed after connection. + */ +struct sess_con { + hook_p hook; + uint16_t Session_ID; + enum state state; + ng_ID_t creator; /* who to notify */ + struct pppoe_full_hdr pkt_hdr; /* used when connected */ + negp neg; /* used when negotiating */ + LIST_ENTRY(sess_con) sessions; +}; +typedef struct sess_con *sessp; + +#define SESSHASHSIZE 0x0100 +#define SESSHASH(x) (((x) ^ ((x) >> 8)) & (SESSHASHSIZE - 1)) + +struct sess_hash_entry { + struct mtx mtx; + LIST_HEAD(hhead, sess_con) head; +}; + +/* + * Information we store for each node + */ +struct PPPoE { + node_p node; /* back pointer to node */ + hook_p ethernet_hook; + hook_p debug_hook; + u_int packets_in; /* packets in from ethernet */ + u_int packets_out; /* packets out towards ethernet */ + uint32_t flags; +#define COMPAT_3COM 0x00000001 +#define COMPAT_DLINK 0x00000002 + struct ether_header eh; + LIST_HEAD(, sess_con) listeners; + struct sess_hash_entry sesshash[SESSHASHSIZE]; +}; +typedef struct PPPoE *priv_p; + +union uniq { + char bytes[sizeof(void *)]; + void *pointer; +}; + +#define LEAVE(x) do { error = x; goto quit; } while(0) +static void pppoe_start(sessp sp); +static void pppoe_ticker(node_p node, hook_p hook, void *arg1, int arg2); +static const struct pppoe_tag *scan_tags(sessp sp, + const struct pppoe_hdr* ph); +static int pppoe_send_event(sessp sp, enum cmd cmdid); + +/************************************************************************* + * Some basic utilities from the Linux version with author's permission.* + * Author: Michal Ostrowski * + ************************************************************************/ + + + +/* + * Return the location where the next tag can be put + */ +static __inline const struct pppoe_tag* +next_tag(const struct pppoe_hdr* ph) +{ + return (const struct pppoe_tag*)(((const char*)&ph->tag[0]) + + ntohs(ph->length)); +} + +/* + * Look for a tag of a specific type. + * Don't trust any length the other end says, + * but assume we already sanity checked ph->length. + */ +static const struct pppoe_tag* +get_tag(const struct pppoe_hdr* ph, uint16_t idx) +{ + const char *const end = (const char *)next_tag(ph); + const struct pppoe_tag *pt = &ph->tag[0]; + const char *ptn; + + /* + * Keep processing tags while a tag header will still fit. + */ + while((const char*)(pt + 1) <= end) { + /* + * If the tag data would go past the end of the packet, abort. + */ + ptn = (((const char *)(pt + 1)) + ntohs(pt->tag_len)); + if (ptn > end) { + CTR2(KTR_NET, "%20s: invalid length for tag %d", + __func__, idx); + return (NULL); + } + if (pt->tag_type == idx) { + CTR2(KTR_NET, "%20s: found tag %d", __func__, idx); + return (pt); + } + + pt = (const struct pppoe_tag*)ptn; + } + + CTR2(KTR_NET, "%20s: not found tag %d", __func__, idx); + return (NULL); +} + +/************************************************************************** + * Inlines to initialise or add tags to a session's tag list. + **************************************************************************/ +/* + * Initialise the session's tag list. + */ +static void +init_tags(sessp sp) +{ + KASSERT(sp->neg != NULL, ("%s: no neg", __func__)); + sp->neg->numtags = 0; +} + +static void +insert_tag(sessp sp, const struct pppoe_tag *tp) +{ + negp neg = sp->neg; + int i; + + KASSERT(neg != NULL, ("%s: no neg", __func__)); + if ((i = neg->numtags++) < NUMTAGS) { + neg->tags[i] = tp; + } else { + log(LOG_NOTICE, "ng_pppoe: asked to add too many tags to " + "packet\n"); + neg->numtags--; + } +} + +/* + * Make up a packet, using the tags filled out for the session. + * + * Assume that the actual pppoe header and ethernet header + * are filled out externally to this routine. + * Also assume that neg->wh points to the correct + * location at the front of the buffer space. + */ +static void +make_packet(sessp sp) { + struct pppoe_full_hdr *wh = &sp->neg->pkt->pkt_header; + const struct pppoe_tag **tag; + char *dp; + int count; + int tlen; + uint16_t length = 0; + + KASSERT((sp->neg != NULL) && (sp->neg->m != NULL), + ("%s: called from wrong state", __func__)); + CTR2(KTR_NET, "%20s: called %d", __func__, sp->Session_ID); + + dp = (char *)wh->ph.tag; + for (count = 0, tag = sp->neg->tags; + ((count < sp->neg->numtags) && (count < NUMTAGS)); + tag++, count++) { + tlen = ntohs((*tag)->tag_len) + sizeof(**tag); + if ((length + tlen) > (ETHER_MAX_LEN - 4 - sizeof(*wh))) { + log(LOG_NOTICE, "ng_pppoe: tags too long\n"); + sp->neg->numtags = count; + break; /* XXX chop off what's too long */ + } + bcopy(*tag, (char *)dp, tlen); + length += tlen; + dp += tlen; + } + wh->ph.length = htons(length); + sp->neg->m->m_len = length + sizeof(*wh); + sp->neg->m->m_pkthdr.len = length + sizeof(*wh); +} + +/************************************************************************** + * Routines to match a service. * + **************************************************************************/ + +/* + * Find a hook that has a service string that matches that + * we are seeking. For now use a simple string. + * In the future we may need something like regexp(). + * + * Null string is a wildcard (ANY service), according to RFC2516. + * And historical FreeBSD wildcard is also "*". + */ + +static hook_p +pppoe_match_svc(node_p node, const struct pppoe_tag *tag) +{ + const priv_p privp = NG_NODE_PRIVATE(node); + sessp sp; + + LIST_FOREACH(sp, &privp->listeners, sessions) { + negp neg = sp->neg; + + /* Empty Service-Name matches any service. */ + if (neg->service_len == 0) + break; + + /* Special case for a blank or "*" service name (wildcard). */ + if (neg->service_len == 1 && neg->service.data[0] == '*') + break; + + /* If the lengths don't match, that aint it. */ + if (neg->service_len != ntohs(tag->tag_len)) + continue; + + if (strncmp(tag->tag_data, neg->service.data, + ntohs(tag->tag_len)) == 0) + break; + } + CTR3(KTR_NET, "%20s: matched %p for %s", __func__, + sp?sp->hook:NULL, tag->tag_data); + + return (sp?sp->hook:NULL); +} + +/* + * Broadcast the PADI packet in m0 to all listening hooks. + * This routine is called when a PADI with empty Service-Name + * tag is received. Client should receive PADOs with all + * available services. + */ +static int +pppoe_broadcast_padi(node_p node, struct mbuf *m0) +{ + const priv_p privp = NG_NODE_PRIVATE(node); + sessp sp; + int error = 0; + + LIST_FOREACH(sp, &privp->listeners, sessions) { + struct mbuf *m; + + m = m_dup(m0, M_DONTWAIT); + if (m == NULL) + return (ENOMEM); + NG_SEND_DATA_ONLY(error, sp->hook, m); + if (error) + return (error); + } + + return (0); +} + +/* + * Find a hook, which name equals to given service. + */ +static hook_p +pppoe_find_svc(node_p node, const char *svc_name, int svc_len) +{ + const priv_p privp = NG_NODE_PRIVATE(node); + sessp sp; + + LIST_FOREACH(sp, &privp->listeners, sessions) { + negp neg = sp->neg; + + if (neg->service_len == svc_len && + strncmp(svc_name, neg->service.data, svc_len) == 0) + return (sp->hook); + } + + return (NULL); +} + +/************************************************************************** + * Routines to find a particular session that matches an incoming packet. * + **************************************************************************/ +/* Find free session and add to hash. */ +static uint16_t +pppoe_getnewsession(sessp sp) +{ + const priv_p privp = NG_NODE_PRIVATE(NG_HOOK_NODE(sp->hook)); + static uint16_t pppoe_sid = 1; + sessp tsp; + uint16_t val, hash; + +restart: + /* Atomicity is not needed here as value will be checked. */ + val = pppoe_sid++; + /* Spec says 0xFFFF is reserved, also don't use 0x0000. */ + if (val == 0xffff || val == 0x0000) + val = pppoe_sid = 1; + + /* Check it isn't already in use. */ + hash = SESSHASH(val); + mtx_lock(&privp->sesshash[hash].mtx); + LIST_FOREACH(tsp, &privp->sesshash[hash].head, sessions) { + if (tsp->Session_ID == val) + break; + } + if (!tsp) { + sp->Session_ID = val; + LIST_INSERT_HEAD(&privp->sesshash[hash].head, sp, sessions); + } + mtx_unlock(&privp->sesshash[hash].mtx); + if (tsp) + goto restart; + + CTR2(KTR_NET, "%20s: new sid %d", __func__, val); + + return (val); +} + +/* Add specified session to hash. */ +static void +pppoe_addsession(sessp sp) +{ + const priv_p privp = NG_NODE_PRIVATE(NG_HOOK_NODE(sp->hook)); + uint16_t hash = SESSHASH(sp->Session_ID); + + mtx_lock(&privp->sesshash[hash].mtx); + LIST_INSERT_HEAD(&privp->sesshash[hash].head, sp, sessions); + mtx_unlock(&privp->sesshash[hash].mtx); +} + +/* Delete specified session from hash. */ +static void +pppoe_delsession(sessp sp) +{ + const priv_p privp = NG_NODE_PRIVATE(NG_HOOK_NODE(sp->hook)); + uint16_t hash = SESSHASH(sp->Session_ID); + + mtx_lock(&privp->sesshash[hash].mtx); + LIST_REMOVE(sp, sessions); + mtx_unlock(&privp->sesshash[hash].mtx); +} + +/* Find matching peer/session combination. */ +static sessp +pppoe_findsession(priv_p privp, const struct pppoe_full_hdr *wh) +{ + uint16_t session = ntohs(wh->ph.sid); + uint16_t hash = SESSHASH(session); + sessp sp = NULL; + + mtx_lock(&privp->sesshash[hash].mtx); + LIST_FOREACH(sp, &privp->sesshash[hash].head, sessions) { + if (sp->Session_ID == session && + bcmp(sp->pkt_hdr.eh.ether_dhost, + wh->eh.ether_shost, ETHER_ADDR_LEN) == 0) { + break; + } + } + mtx_unlock(&privp->sesshash[hash].mtx); + CTR3(KTR_NET, "%20s: matched %p for %d", __func__, sp?sp->hook:NULL, + session); + + return (sp); +} + +static hook_p +pppoe_finduniq(node_p node, const struct pppoe_tag *tag) +{ + hook_p hook = NULL; + union uniq uniq; + + bcopy(tag->tag_data, uniq.bytes, sizeof(void *)); + /* Cycle through all known hooks. */ + LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) { + /* Skip any nonsession hook. */ + if (NG_HOOK_PRIVATE(hook) == NULL) + continue; + if (uniq.pointer == NG_HOOK_PRIVATE(hook)) + break; + } + CTR3(KTR_NET, "%20s: matched %p for %p", __func__, hook, uniq.pointer); + + return (hook); +} + +/************************************************************************** + * Start of Netgraph entrypoints. * + **************************************************************************/ + +/* + * Allocate the private data structure and link it with node. + */ +static int +ng_pppoe_constructor(node_p node) +{ + priv_p privp; + int i; + + /* Initialize private descriptor. */ + privp = malloc(sizeof(*privp), M_NETGRAPH_PPPOE, M_NOWAIT | M_ZERO); + if (privp == NULL) + return (ENOMEM); + + /* Link structs together; this counts as our one reference to *node. */ + NG_NODE_SET_PRIVATE(node, privp); + privp->node = node; + + /* Initialize to standard mode. */ + memset(&privp->eh.ether_dhost, 0xff, ETHER_ADDR_LEN); + privp->eh.ether_type = ETHERTYPE_PPPOE_DISC; + + LIST_INIT(&privp->listeners); + for (i = 0; i < SESSHASHSIZE; i++) { + mtx_init(&privp->sesshash[i].mtx, "PPPoE hash mutex", NULL, MTX_DEF); + LIST_INIT(&privp->sesshash[i].head); + } + + CTR3(KTR_NET, "%20s: created node [%x] (%p)", + __func__, node->nd_ID, node); + + return (0); +} + +/* + * Give our ok for a hook to be added... + * point the hook's private info to the hook structure. + * + * The following hook names are special: + * "ethernet": the hook that should be connected to a NIC. + * "debug": copies of data sent out here (when I write the code). + * All other hook names need only be unique. (the framework checks this). + */ +static int +ng_pppoe_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p privp = NG_NODE_PRIVATE(node); + sessp sp; + + if (strcmp(name, NG_PPPOE_HOOK_ETHERNET) == 0) { + privp->ethernet_hook = hook; + NG_HOOK_SET_RCVDATA(hook, ng_pppoe_rcvdata_ether); + } else if (strcmp(name, NG_PPPOE_HOOK_DEBUG) == 0) { + privp->debug_hook = hook; + NG_HOOK_SET_RCVDATA(hook, ng_pppoe_rcvdata_debug); + } else { + /* + * Any other unique name is OK. + * The infrastructure has already checked that it's unique, + * so just allocate it and hook it in. + */ + sp = malloc(sizeof(*sp), M_NETGRAPH_PPPOE, M_NOWAIT | M_ZERO); + if (sp == NULL) + return (ENOMEM); + + NG_HOOK_SET_PRIVATE(hook, sp); + sp->hook = hook; + } + CTR5(KTR_NET, "%20s: node [%x] (%p) connected hook %s (%p)", + __func__, node->nd_ID, node, name, hook); + + return(0); +} + +/* + * Hook has been added successfully. Request the MAC address of + * the underlying Ethernet node. + */ +static int +ng_pppoe_connect(hook_p hook) +{ + const priv_p privp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct ng_mesg *msg; + int error; + + if (hook != privp->ethernet_hook) + return (0); + + /* + * If this is Ethernet hook, then request MAC address + * from our downstream. + */ + NG_MKMESSAGE(msg, NGM_ETHER_COOKIE, NGM_ETHER_GET_ENADDR, 0, M_NOWAIT); + if (msg == NULL) + return (ENOBUFS); + + /* + * Our hook and peer hook have HK_INVALID flag set, + * so we can't use NG_SEND_MSG_HOOK() macro here. + */ + NG_SEND_MSG_ID(error, privp->node, msg, + NG_NODE_ID(NG_PEER_NODE(privp->ethernet_hook)), + NG_NODE_ID(privp->node)); + + return (error); +} +/* + * Get a netgraph control message. + * Check it is one we understand. If needed, send a response. + * We sometimes save the address for an async action later. + * Always free the message. + */ +static int +ng_pppoe_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + priv_p privp = NG_NODE_PRIVATE(node); + struct ngpppoe_init_data *ourmsg = NULL; + struct ng_mesg *resp = NULL; + int error = 0; + hook_p hook = NULL; + sessp sp = NULL; + negp neg = NULL; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + CTR5(KTR_NET, "%20s: node [%x] (%p) got message %d with cookie %d", + __func__, node->nd_ID, node, msg->header.cmd, + msg->header.typecookie); + + /* Deal with message according to cookie and command. */ + switch (msg->header.typecookie) { + case NGM_PPPOE_COOKIE: + switch (msg->header.cmd) { + case NGM_PPPOE_CONNECT: + case NGM_PPPOE_LISTEN: + case NGM_PPPOE_OFFER: + case NGM_PPPOE_SERVICE: + ourmsg = (struct ngpppoe_init_data *)msg->data; + if (msg->header.arglen < sizeof(*ourmsg)) { + log(LOG_ERR, "ng_pppoe[%x]: init data too " + "small\n", node->nd_ID); + LEAVE(EMSGSIZE); + } + if (msg->header.arglen - sizeof(*ourmsg) > + PPPOE_SERVICE_NAME_SIZE) { + log(LOG_ERR, "ng_pppoe[%x]: service name " + "too big\n", node->nd_ID); + LEAVE(EMSGSIZE); + } + if (msg->header.arglen - sizeof(*ourmsg) < + ourmsg->data_len) { + log(LOG_ERR, "ng_pppoe[%x]: init data has bad " + "length, %d should be %zd\n", node->nd_ID, + ourmsg->data_len, + msg->header.arglen - sizeof (*ourmsg)); + LEAVE(EMSGSIZE); + } + + /* Make sure strcmp will terminate safely. */ + ourmsg->hook[sizeof(ourmsg->hook) - 1] = '\0'; + + /* Find hook by name. */ + hook = ng_findhook(node, ourmsg->hook); + if (hook == NULL) + LEAVE(ENOENT); + + sp = NG_HOOK_PRIVATE(hook); + if (sp == NULL) + LEAVE(EINVAL); + + if (msg->header.cmd == NGM_PPPOE_LISTEN) { + /* + * Ensure we aren't already listening for this + * service. + */ + if (pppoe_find_svc(node, ourmsg->data, + ourmsg->data_len) != NULL) + LEAVE(EEXIST); + } + + /* + * PPPOE_SERVICE advertisments are set up + * on sessions that are in PRIMED state. + */ + if (msg->header.cmd == NGM_PPPOE_SERVICE) + break; + + if (sp->state != PPPOE_SNONE) { + log(LOG_NOTICE, "ng_pppoe[%x]: Session already " + "active\n", node->nd_ID); + LEAVE(EISCONN); + } + + /* + * Set up prototype header. + */ + neg = malloc(sizeof(*neg), M_NETGRAPH_PPPOE, + M_NOWAIT | M_ZERO); + + if (neg == NULL) + LEAVE(ENOMEM); + + neg->m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (neg->m == NULL) { + free(neg, M_NETGRAPH_PPPOE); + LEAVE(ENOBUFS); + } + neg->m->m_pkthdr.rcvif = NULL; + sp->neg = neg; + ng_callout_init(&neg->handle); + neg->m->m_len = sizeof(struct pppoe_full_hdr); + neg->pkt = mtod(neg->m, union packet*); + memcpy((void *)&neg->pkt->pkt_header.eh, + &privp->eh, sizeof(struct ether_header)); + neg->pkt->pkt_header.ph.ver = 0x1; + neg->pkt->pkt_header.ph.type = 0x1; + neg->pkt->pkt_header.ph.sid = 0x0000; + neg->timeout = 0; + + sp->creator = NGI_RETADDR(item); + } + switch (msg->header.cmd) { + case NGM_PPPOE_GET_STATUS: + { + struct ngpppoestat *stats; + + NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); + if (!resp) + LEAVE(ENOMEM); + + stats = (struct ngpppoestat *) resp->data; + stats->packets_in = privp->packets_in; + stats->packets_out = privp->packets_out; + break; + } + case NGM_PPPOE_CONNECT: + { + /* + * Check the hook exists and is Uninitialised. + * Send a PADI request, and start the timeout logic. + * Store the originator of this message so we can send + * a success of fail message to them later. + * Move the session to SINIT. + * Set up the session to the correct state and + * start it. + */ + int i, acnlen = 0, acnsep = 0, srvlen; + for (i = 0; i < ourmsg->data_len; i++) { + if (ourmsg->data[i] == '\\') { + acnlen = i; + acnsep = 1; + break; + } + } + srvlen = ourmsg->data_len - acnlen - acnsep; + + bcopy(ourmsg->data, neg->ac_name.data, acnlen); + neg->ac_name_len = acnlen; + + neg->service.hdr.tag_type = PTT_SRV_NAME; + neg->service.hdr.tag_len = htons((uint16_t)srvlen); + bcopy(ourmsg->data + acnlen + acnsep, + neg->service.data, srvlen); + neg->service_len = srvlen; + pppoe_start(sp); + break; + } + case NGM_PPPOE_LISTEN: + /* + * Check the hook exists and is Uninitialised. + * Install the service matching string. + * Store the originator of this message so we can send + * a success of fail message to them later. + * Move the hook to 'LISTENING' + */ + neg->service.hdr.tag_type = PTT_SRV_NAME; + neg->service.hdr.tag_len = + htons((uint16_t)ourmsg->data_len); + + if (ourmsg->data_len) + bcopy(ourmsg->data, neg->service.data, + ourmsg->data_len); + neg->service_len = ourmsg->data_len; + neg->pkt->pkt_header.ph.code = PADT_CODE; + /* + * Wait for PADI packet coming from Ethernet. + */ + sp->state = PPPOE_LISTENING; + LIST_INSERT_HEAD(&privp->listeners, sp, sessions); + break; + case NGM_PPPOE_OFFER: + /* + * Check the hook exists and is Uninitialised. + * Store the originator of this message so we can send + * a success of fail message to them later. + * Store the AC-Name given and go to PRIMED. + */ + neg->ac_name.hdr.tag_type = PTT_AC_NAME; + neg->ac_name.hdr.tag_len = + htons((uint16_t)ourmsg->data_len); + if (ourmsg->data_len) + bcopy(ourmsg->data, neg->ac_name.data, + ourmsg->data_len); + neg->ac_name_len = ourmsg->data_len; + neg->pkt->pkt_header.ph.code = PADO_CODE; + /* + * Wait for PADI packet coming from hook. + */ + sp->state = PPPOE_PRIMED; + break; + case NGM_PPPOE_SERVICE: + /* + * Check the session is primed. + * for now just allow ONE service to be advertised. + * If you do it twice you just overwrite. + */ + if (sp->state != PPPOE_PRIMED) { + log(LOG_NOTICE, "ng_pppoe[%x]: session not " + "primed\n", node->nd_ID); + LEAVE(EISCONN); + } + neg = sp->neg; + neg->service.hdr.tag_type = PTT_SRV_NAME; + neg->service.hdr.tag_len = + htons((uint16_t)ourmsg->data_len); + + if (ourmsg->data_len) + bcopy(ourmsg->data, neg->service.data, + ourmsg->data_len); + neg->service_len = ourmsg->data_len; + break; + case NGM_PPPOE_SETMODE: + { + char *s; + size_t len; + + if (msg->header.arglen == 0) + LEAVE(EINVAL); + + s = (char *)msg->data; + len = msg->header.arglen - 1; + + /* Search for matching mode string. */ + if (len == strlen(NG_PPPOE_STANDARD) && + (strncmp(NG_PPPOE_STANDARD, s, len) == 0)) { + privp->flags = 0; + privp->eh.ether_type = ETHERTYPE_PPPOE_DISC; + break; + } + if (len == strlen(NG_PPPOE_3COM) && + (strncmp(NG_PPPOE_3COM, s, len) == 0)) { + privp->flags |= COMPAT_3COM; + privp->eh.ether_type = + ETHERTYPE_PPPOE_3COM_DISC; + break; + } + if (len == strlen(NG_PPPOE_DLINK) && + (strncmp(NG_PPPOE_DLINK, s, len) == 0)) { + privp->flags |= COMPAT_DLINK; + break; + } + error = EINVAL; + break; + } + case NGM_PPPOE_GETMODE: + { + char *s; + size_t len = 0; + + if (privp->flags == 0) + len += strlen(NG_PPPOE_STANDARD) + 1; + if (privp->flags & COMPAT_3COM) + len += strlen(NG_PPPOE_3COM) + 1; + if (privp->flags & COMPAT_DLINK) + len += strlen(NG_PPPOE_DLINK) + 1; + + NG_MKRESPONSE(resp, msg, len, M_NOWAIT); + if (resp == NULL) + LEAVE(ENOMEM); + + s = (char *)resp->data; + if (privp->flags == 0) { + len = strlen(NG_PPPOE_STANDARD); + strlcpy(s, NG_PPPOE_STANDARD, len + 1); + break; + } + if (privp->flags & COMPAT_3COM) { + len = strlen(NG_PPPOE_3COM); + strlcpy(s, NG_PPPOE_3COM, len + 1); + s += len; + } + if (privp->flags & COMPAT_DLINK) { + if (s != resp->data) + *s++ = '|'; + len = strlen(NG_PPPOE_DLINK); + strlcpy(s, NG_PPPOE_DLINK, len + 1); + } + break; + } + case NGM_PPPOE_SETENADDR: + if (msg->header.arglen != ETHER_ADDR_LEN) + LEAVE(EINVAL); + bcopy(msg->data, &privp->eh.ether_shost, + ETHER_ADDR_LEN); + break; + default: + LEAVE(EINVAL); + } + break; + case NGM_ETHER_COOKIE: + if (!(msg->header.flags & NGF_RESP)) + LEAVE(EINVAL); + switch (msg->header.cmd) { + case NGM_ETHER_GET_ENADDR: + if (msg->header.arglen != ETHER_ADDR_LEN) + LEAVE(EINVAL); + bcopy(msg->data, &privp->eh.ether_shost, + ETHER_ADDR_LEN); + break; + default: + LEAVE(EINVAL); + } + break; + default: + LEAVE(EINVAL); + } + + /* Take care of synchronous response, if any. */ +quit: + CTR2(KTR_NET, "%20s: returning %d", __func__, error); + NG_RESPOND_MSG(error, node, item, resp); + /* Free the message and return. */ + NG_FREE_MSG(msg); + return(error); +} + +/* + * Start a client into the first state. A separate function because + * it can be needed if the negotiation times out. + */ +static void +pppoe_start(sessp sp) +{ + hook_p hook = sp->hook; + node_p node = NG_HOOK_NODE(hook); + priv_p privp = NG_NODE_PRIVATE(node); + negp neg = sp->neg; + struct { + struct pppoe_tag hdr; + union uniq data; + } __packed uniqtag; + struct mbuf *m0; + int error; + + /* + * Kick the state machine into starting up. + */ + CTR2(KTR_NET, "%20s: called %d", __func__, sp->Session_ID); + sp->state = PPPOE_SINIT; + /* + * Reset the packet header to broadcast. Since we are + * in a client mode use configured ethertype. + */ + memcpy((void *)&neg->pkt->pkt_header.eh, &privp->eh, + sizeof(struct ether_header)); + neg->pkt->pkt_header.ph.code = PADI_CODE; + uniqtag.hdr.tag_type = PTT_HOST_UNIQ; + uniqtag.hdr.tag_len = htons((u_int16_t)sizeof(uniqtag.data)); + uniqtag.data.pointer = sp; + init_tags(sp); + insert_tag(sp, &uniqtag.hdr); + insert_tag(sp, &neg->service.hdr); + make_packet(sp); + /* + * Send packet and prepare to retransmit it after timeout. + */ + ng_callout(&neg->handle, node, hook, PPPOE_INITIAL_TIMEOUT * hz, + pppoe_ticker, NULL, 0); + neg->timeout = PPPOE_INITIAL_TIMEOUT * 2; + m0 = m_copypacket(neg->m, M_DONTWAIT); + NG_SEND_DATA_ONLY(error, privp->ethernet_hook, m0); +} + +static int +send_acname(sessp sp, const struct pppoe_tag *tag) +{ + int error, tlen; + struct ng_mesg *msg; + struct ngpppoe_sts *sts; + + CTR2(KTR_NET, "%20s: called %d", __func__, sp->Session_ID); + + NG_MKMESSAGE(msg, NGM_PPPOE_COOKIE, NGM_PPPOE_ACNAME, + sizeof(struct ngpppoe_sts), M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + sts = (struct ngpppoe_sts *)msg->data; + tlen = min(NG_HOOKSIZ - 1, ntohs(tag->tag_len)); + strncpy(sts->hook, tag->tag_data, tlen); + sts->hook[tlen] = '\0'; + NG_SEND_MSG_ID(error, NG_HOOK_NODE(sp->hook), msg, sp->creator, 0); + + return (error); +} + +static int +send_sessionid(sessp sp) +{ + int error; + struct ng_mesg *msg; + + CTR2(KTR_NET, "%20s: called %d", __func__, sp->Session_ID); + + NG_MKMESSAGE(msg, NGM_PPPOE_COOKIE, NGM_PPPOE_SESSIONID, + sizeof(uint16_t), M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + + *(uint16_t *)msg->data = sp->Session_ID; + NG_SEND_MSG_ID(error, NG_HOOK_NODE(sp->hook), msg, sp->creator, 0); + + return (error); +} + +/* + * Receive data from session hook and do something with it. + */ +static int +ng_pppoe_rcvdata(hook_p hook, item_p item) +{ + node_p node = NG_HOOK_NODE(hook); + const priv_p privp = NG_NODE_PRIVATE(node); + sessp sp = NG_HOOK_PRIVATE(hook); + struct pppoe_full_hdr *wh; + struct mbuf *m; + int error; + + CTR6(KTR_NET, "%20s: node [%x] (%p) received %p on \"%s\" (%p)", + __func__, node->nd_ID, node, item, hook->hk_name, hook); + + NGI_GET_M(item, m); + switch (sp->state) { + case PPPOE_NEWCONNECTED: + case PPPOE_CONNECTED: { + /* + * Remove PPP address and control fields, if any. + * For example, ng_ppp(4) always sends LCP packets + * with address and control fields as required by + * generic PPP. PPPoE is an exception to the rule. + */ + if (m->m_pkthdr.len >= 2) { + if (m->m_len < 2 && !(m = m_pullup(m, 2))) + LEAVE(ENOBUFS); + if (mtod(m, u_char *)[0] == 0xff && + mtod(m, u_char *)[1] == 0x03) + m_adj(m, 2); + } + /* + * Bang in a pre-made header, and set the length up + * to be correct. Then send it to the ethernet driver. + */ + M_PREPEND(m, sizeof(*wh), M_DONTWAIT); + if (m == NULL) + LEAVE(ENOBUFS); + + wh = mtod(m, struct pppoe_full_hdr *); + bcopy(&sp->pkt_hdr, wh, sizeof(*wh)); + wh->ph.length = htons(m->m_pkthdr.len - sizeof(*wh)); + NG_FWD_NEW_DATA(error, item, privp->ethernet_hook, m); + privp->packets_out++; + break; + } + case PPPOE_PRIMED: { + struct { + struct pppoe_tag hdr; + union uniq data; + } __packed uniqtag; + const struct pppoe_tag *tag; + struct mbuf *m0; + const struct pppoe_hdr *ph; + negp neg = sp->neg; + uint16_t session; + uint16_t length; + uint8_t code; + + /* + * A PADI packet is being returned by the application + * that has set up this hook. This indicates that it + * wants us to offer service. + */ + if (m->m_len < sizeof(*wh)) { + m = m_pullup(m, sizeof(*wh)); + if (m == NULL) + LEAVE(ENOBUFS); + } + wh = mtod(m, struct pppoe_full_hdr *); + ph = &wh->ph; + session = ntohs(wh->ph.sid); + length = ntohs(wh->ph.length); + code = wh->ph.code; + /* Use peers mode in session. */ + neg->pkt->pkt_header.eh.ether_type = wh->eh.ether_type; + if (code != PADI_CODE) + LEAVE(EINVAL); + ng_uncallout(&neg->handle, node); + + /* + * This is the first time we hear + * from the client, so note it's + * unicast address, replacing the + * broadcast address. + */ + bcopy(wh->eh.ether_shost, + neg->pkt->pkt_header.eh.ether_dhost, + ETHER_ADDR_LEN); + sp->state = PPPOE_SOFFER; + neg->timeout = 0; + neg->pkt->pkt_header.ph.code = PADO_CODE; + + /* + * Start working out the tags to respond with. + */ + uniqtag.hdr.tag_type = PTT_AC_COOKIE; + uniqtag.hdr.tag_len = htons((u_int16_t)sizeof(sp)); + uniqtag.data.pointer = sp; + init_tags(sp); + insert_tag(sp, &neg->ac_name.hdr); /* AC_NAME */ + if ((tag = get_tag(ph, PTT_SRV_NAME))) + insert_tag(sp, tag); /* return service */ + /* + * If we have a NULL service request + * and have an extra service defined in this hook, + * then also add a tag for the extra service. + * XXX this is a hack. eventually we should be able + * to support advertising many services, not just one + */ + if (((tag == NULL) || (tag->tag_len == 0)) && + (neg->service.hdr.tag_len != 0)) { + insert_tag(sp, &neg->service.hdr); /* SERVICE */ + } + if ((tag = get_tag(ph, PTT_HOST_UNIQ))) + insert_tag(sp, tag); /* returned hostunique */ + insert_tag(sp, &uniqtag.hdr); + scan_tags(sp, ph); + make_packet(sp); + /* + * Send the offer but if they don't respond + * in PPPOE_OFFER_TIMEOUT seconds, forget about it. + */ + ng_callout(&neg->handle, node, hook, PPPOE_OFFER_TIMEOUT * hz, + pppoe_ticker, NULL, 0); + m0 = m_copypacket(sp->neg->m, M_DONTWAIT); + NG_FWD_NEW_DATA(error, item, privp->ethernet_hook, m0); + privp->packets_out++; + break; + } + + /* + * Packets coming from the hook make no sense + * to sessions in the rest of states. Throw them away. + */ + default: + LEAVE(ENETUNREACH); + } +quit: + if (item) + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (error); +} + +/* + * Receive data from ether and do something with it. + */ +static int +ng_pppoe_rcvdata_ether(hook_p hook, item_p item) +{ + node_p node = NG_HOOK_NODE(hook); + const priv_p privp = NG_NODE_PRIVATE(node); + sessp sp = NG_HOOK_PRIVATE(hook); + const struct pppoe_tag *utag = NULL, *tag = NULL; + const struct pppoe_full_hdr *wh; + const struct pppoe_hdr *ph; + negp neg = NULL; + struct mbuf *m; + hook_p sendhook; + int error = 0; + uint16_t session; + uint16_t length; + uint8_t code; + struct mbuf *m0; + + CTR6(KTR_NET, "%20s: node [%x] (%p) received %p on \"%s\" (%p)", + __func__, node->nd_ID, node, item, hook->hk_name, hook); + + NGI_GET_M(item, m); + /* + * Dig out various fields from the packet. + * Use them to decide where to send it. + */ + privp->packets_in++; + if( m->m_len < sizeof(*wh)) { + m = m_pullup(m, sizeof(*wh)); /* Checks length */ + if (m == NULL) { + log(LOG_NOTICE, "ng_pppoe[%x]: couldn't " + "m_pullup(wh)\n", node->nd_ID); + LEAVE(ENOBUFS); + } + } + wh = mtod(m, struct pppoe_full_hdr *); + length = ntohs(wh->ph.length); + switch(wh->eh.ether_type) { + case ETHERTYPE_PPPOE_3COM_DISC: /* fall through */ + case ETHERTYPE_PPPOE_DISC: + /* + * We need to try to make sure that the tag area + * is contiguous, or we could wander off the end + * of a buffer and make a mess. + * (Linux wouldn't have this problem). + */ + if (m->m_pkthdr.len <= MHLEN) { + if( m->m_len < m->m_pkthdr.len) { + m = m_pullup(m, m->m_pkthdr.len); + if (m == NULL) { + log(LOG_NOTICE, "ng_pppoe[%x]: " + "couldn't m_pullup(pkthdr)\n", + node->nd_ID); + LEAVE(ENOBUFS); + } + } + } + if (m->m_len != m->m_pkthdr.len) { + /* + * It's not all in one piece. + * We need to do extra work. + * Put it into a cluster. + */ + struct mbuf *n; + n = m_dup(m, M_DONTWAIT); + m_freem(m); + m = n; + if (m) { + /* just check we got a cluster */ + if (m->m_len != m->m_pkthdr.len) { + m_freem(m); + m = NULL; + } + } + if (m == NULL) { + log(LOG_NOTICE, "ng_pppoe[%x]: packet " + "fragmented\n", node->nd_ID); + LEAVE(EMSGSIZE); + } + } + wh = mtod(m, struct pppoe_full_hdr *); + length = ntohs(wh->ph.length); + ph = &wh->ph; + session = ntohs(wh->ph.sid); + code = wh->ph.code; + + switch(code) { + case PADI_CODE: + /* + * We are a server: + * Look for a hook with the required service and send + * the ENTIRE packet up there. It should come back to + * a new hook in PRIMED state. Look there for further + * processing. + */ + tag = get_tag(ph, PTT_SRV_NAME); + if (tag == NULL) { + CTR1(KTR_NET, "%20s: PADI w/o Service-Name", + __func__); + LEAVE(ENETUNREACH); + } + + /* + * First, try to match Service-Name against our + * listening hooks. If no success and we are in D-Link + * compat mode and Service-Name is empty, then we + * broadcast the PADI to all listening hooks. + */ + sendhook = pppoe_match_svc(node, tag); + if (sendhook != NULL) + NG_FWD_NEW_DATA(error, item, sendhook, m); + else if (privp->flags & COMPAT_DLINK && + ntohs(tag->tag_len) == 0) + error = pppoe_broadcast_padi(node, m); + else + error = ENETUNREACH; + break; + case PADO_CODE: + /* + * We are a client: + * Use the host_uniq tag to find the hook this is in + * response to. Received #2, now send #3 + * For now simply accept the first we receive. + */ + utag = get_tag(ph, PTT_HOST_UNIQ); + if ((utag == NULL) || + (ntohs(utag->tag_len) != sizeof(sp))) { + log(LOG_NOTICE, "ng_pppoe[%x]: no host " + "unique field\n", node->nd_ID); + LEAVE(ENETUNREACH); + } + + sendhook = pppoe_finduniq(node, utag); + if (sendhook == NULL) { + log(LOG_NOTICE, "ng_pppoe[%x]: no " + "matching session\n", node->nd_ID); + LEAVE(ENETUNREACH); + } + + /* + * Check the session is in the right state. + * It needs to be in PPPOE_SINIT. + */ + sp = NG_HOOK_PRIVATE(sendhook); + if (sp->state == PPPOE_SREQ || + sp->state == PPPOE_CONNECTED) { + break; /* Multiple PADO is OK. */ + } + if (sp->state != PPPOE_SINIT) { + log(LOG_NOTICE, "ng_pppoe[%x]: session " + "in wrong state\n", node->nd_ID); + LEAVE(ENETUNREACH); + } + neg = sp->neg; + /* If requested specific AC-name, check it. */ + if (neg->ac_name_len) { + tag = get_tag(ph, PTT_AC_NAME); + if (!tag) { + /* No PTT_AC_NAME in PADO */ + break; + } + if (neg->ac_name_len != htons(tag->tag_len) || + strncmp(neg->ac_name.data, tag->tag_data, + neg->ac_name_len) != 0) { + break; + } + } + sp->state = PPPOE_SREQ; + ng_uncallout(&neg->handle, node); + + /* + * This is the first time we hear + * from the server, so note it's + * unicast address, replacing the + * broadcast address . + */ + bcopy(wh->eh.ether_shost, + neg->pkt->pkt_header.eh.ether_dhost, + ETHER_ADDR_LEN); + neg->timeout = 0; + neg->pkt->pkt_header.ph.code = PADR_CODE; + init_tags(sp); + insert_tag(sp, utag); /* Host Unique */ + if ((tag = get_tag(ph, PTT_AC_COOKIE))) + insert_tag(sp, tag); /* return cookie */ + if ((tag = get_tag(ph, PTT_AC_NAME))) { + insert_tag(sp, tag); /* return it */ + send_acname(sp, tag); + } + insert_tag(sp, &neg->service.hdr); /* Service */ + scan_tags(sp, ph); + make_packet(sp); + sp->state = PPPOE_SREQ; + ng_callout(&neg->handle, node, sp->hook, + PPPOE_INITIAL_TIMEOUT * hz, + pppoe_ticker, NULL, 0); + neg->timeout = PPPOE_INITIAL_TIMEOUT * 2; + m0 = m_copypacket(neg->m, M_DONTWAIT); + NG_FWD_NEW_DATA(error, item, privp->ethernet_hook, m0); + break; + case PADR_CODE: + /* + * We are a server: + * Use the ac_cookie tag to find the + * hook this is in response to. + */ + utag = get_tag(ph, PTT_AC_COOKIE); + if ((utag == NULL) || + (ntohs(utag->tag_len) != sizeof(sp))) { + LEAVE(ENETUNREACH); + } + + sendhook = pppoe_finduniq(node, utag); + if (sendhook == NULL) + LEAVE(ENETUNREACH); + + /* + * Check the session is in the right state. + * It needs to be in PPPOE_SOFFER or PPPOE_NEWCONNECTED. + * If the latter, then this is a retry by the client, + * so be nice, and resend. + */ + sp = NG_HOOK_PRIVATE(sendhook); + if (sp->state == PPPOE_NEWCONNECTED) { + /* + * Whoa! drop back to resend that PADS packet. + * We should still have a copy of it. + */ + sp->state = PPPOE_SOFFER; + } else if (sp->state != PPPOE_SOFFER) + LEAVE (ENETUNREACH); + neg = sp->neg; + ng_uncallout(&neg->handle, node); + neg->pkt->pkt_header.ph.code = PADS_CODE; + if (sp->Session_ID == 0) { + neg->pkt->pkt_header.ph.sid = + htons(pppoe_getnewsession(sp)); + } + send_sessionid(sp); + neg->timeout = 0; + /* + * start working out the tags to respond with. + */ + init_tags(sp); + insert_tag(sp, &neg->ac_name.hdr); /* AC_NAME */ + if ((tag = get_tag(ph, PTT_SRV_NAME))) + insert_tag(sp, tag);/* return service */ + if ((tag = get_tag(ph, PTT_HOST_UNIQ))) + insert_tag(sp, tag); /* return it */ + insert_tag(sp, utag); /* ac_cookie */ + scan_tags(sp, ph); + make_packet(sp); + sp->state = PPPOE_NEWCONNECTED; + + /* Send the PADS without a timeout - we're now connected. */ + m0 = m_copypacket(sp->neg->m, M_DONTWAIT); + NG_FWD_NEW_DATA(error, item, privp->ethernet_hook, m0); + + /* + * Having sent the last Negotiation header, + * Set up the stored packet header to be correct for + * the actual session. But keep the negotialtion stuff + * around in case we need to resend this last packet. + * We'll discard it when we move from NEWCONNECTED + * to CONNECTED + */ + sp->pkt_hdr = neg->pkt->pkt_header; + /* Configure ethertype depending on what + * ethertype was used at discovery phase */ + if (sp->pkt_hdr.eh.ether_type == + ETHERTYPE_PPPOE_3COM_DISC) + sp->pkt_hdr.eh.ether_type + = ETHERTYPE_PPPOE_3COM_SESS; + else + sp->pkt_hdr.eh.ether_type + = ETHERTYPE_PPPOE_SESS; + sp->pkt_hdr.ph.code = 0; + pppoe_send_event(sp, NGM_PPPOE_SUCCESS); + break; + case PADS_CODE: + /* + * We are a client: + * Use the host_uniq tag to find the hook this is in + * response to. Take the session ID and store it away. + * Also make sure the pre-made header is correct and + * set us into Session mode. + */ + utag = get_tag(ph, PTT_HOST_UNIQ); + if ((utag == NULL) || + (ntohs(utag->tag_len) != sizeof(sp))) { + LEAVE (ENETUNREACH); + } + sendhook = pppoe_finduniq(node, utag); + if (sendhook == NULL) + LEAVE(ENETUNREACH); + + /* + * Check the session is in the right state. + * It needs to be in PPPOE_SREQ. + */ + sp = NG_HOOK_PRIVATE(sendhook); + if (sp->state != PPPOE_SREQ) + LEAVE(ENETUNREACH); + neg = sp->neg; + ng_uncallout(&neg->handle, node); + neg->pkt->pkt_header.ph.sid = wh->ph.sid; + sp->Session_ID = ntohs(wh->ph.sid); + pppoe_addsession(sp); + send_sessionid(sp); + neg->timeout = 0; + sp->state = PPPOE_CONNECTED; + /* + * Now we have gone to Connected mode, + * Free all resources needed for negotiation. + * Keep a copy of the header we will be using. + */ + sp->pkt_hdr = neg->pkt->pkt_header; + if (privp->flags & COMPAT_3COM) + sp->pkt_hdr.eh.ether_type + = ETHERTYPE_PPPOE_3COM_SESS; + else + sp->pkt_hdr.eh.ether_type + = ETHERTYPE_PPPOE_SESS; + sp->pkt_hdr.ph.code = 0; + m_freem(neg->m); + free(sp->neg, M_NETGRAPH_PPPOE); + sp->neg = NULL; + pppoe_send_event(sp, NGM_PPPOE_SUCCESS); + break; + case PADT_CODE: + /* + * Find matching peer/session combination. + */ + sp = pppoe_findsession(privp, wh); + if (sp == NULL) + LEAVE(ENETUNREACH); + /* Disconnect that hook. */ + ng_rmhook_self(sp->hook); + break; + default: + LEAVE(EPFNOSUPPORT); + } + break; + case ETHERTYPE_PPPOE_3COM_SESS: + case ETHERTYPE_PPPOE_SESS: + /* + * Find matching peer/session combination. + */ + sp = pppoe_findsession(privp, wh); + if (sp == NULL) + LEAVE (ENETUNREACH); + m_adj(m, sizeof(*wh)); + + /* If packet too short, dump it. */ + if (m->m_pkthdr.len < length) + LEAVE(EMSGSIZE); + /* Also need to trim excess at the end */ + if (m->m_pkthdr.len > length) { + m_adj(m, -((int)(m->m_pkthdr.len - length))); + } + if ( sp->state != PPPOE_CONNECTED) { + if (sp->state == PPPOE_NEWCONNECTED) { + sp->state = PPPOE_CONNECTED; + /* + * Now we have gone to Connected mode, + * Free all resources needed for negotiation. + * Be paranoid about whether there may be + * a timeout. + */ + m_freem(sp->neg->m); + ng_uncallout(&sp->neg->handle, node); + free(sp->neg, M_NETGRAPH_PPPOE); + sp->neg = NULL; + } else { + LEAVE (ENETUNREACH); + } + } + NG_FWD_NEW_DATA(error, item, sp->hook, m); + break; + default: + LEAVE(EPFNOSUPPORT); + } +quit: + if (item) + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (error); +} + +/* + * Receive data from debug hook and bypass it to ether. + */ +static int +ng_pppoe_rcvdata_debug(hook_p hook, item_p item) +{ + node_p node = NG_HOOK_NODE(hook); + const priv_p privp = NG_NODE_PRIVATE(node); + int error; + + CTR6(KTR_NET, "%20s: node [%x] (%p) received %p on \"%s\" (%p)", + __func__, node->nd_ID, node, item, hook->hk_name, hook); + + NG_FWD_ITEM_HOOK(error, item, privp->ethernet_hook); + privp->packets_out++; + return (error); +} + +/* + * Do local shutdown processing.. + * If we are a persistant device, we might refuse to go away, and + * we'd only remove our links and reset ourself. + */ +static int +ng_pppoe_shutdown(node_p node) +{ + const priv_p privp = NG_NODE_PRIVATE(node); + int i; + + for (i = 0; i < SESSHASHSIZE; i++) + mtx_destroy(&privp->sesshash[i].mtx); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(privp->node); + free(privp, M_NETGRAPH_PPPOE); + return (0); +} + +/* + * Hook disconnection + * + * Clean up all dangling links and information about the session/hook. + * For this type, removal of the last link destroys the node. + */ +static int +ng_pppoe_disconnect(hook_p hook) +{ + node_p node = NG_HOOK_NODE(hook); + priv_p privp = NG_NODE_PRIVATE(node); + sessp sp; + + if (hook == privp->debug_hook) { + privp->debug_hook = NULL; + } else if (hook == privp->ethernet_hook) { + privp->ethernet_hook = NULL; + if (NG_NODE_IS_VALID(node)) + ng_rmnode_self(node); + } else { + sp = NG_HOOK_PRIVATE(hook); + if (sp->state != PPPOE_SNONE ) { + pppoe_send_event(sp, NGM_PPPOE_CLOSE); + } + /* + * According to the spec, if we are connected, + * we should send a DISC packet if we are shutting down + * a session. + */ + if ((privp->ethernet_hook) + && ((sp->state == PPPOE_CONNECTED) + || (sp->state == PPPOE_NEWCONNECTED))) { + struct mbuf *m; + + /* Generate a packet of that type. */ + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) + log(LOG_NOTICE, "ng_pppoe[%x]: session out of " + "mbufs\n", node->nd_ID); + else { + struct pppoe_full_hdr *wh; + struct pppoe_tag *tag; + int msglen = strlen(SIGNOFF); + int error = 0; + + m->m_pkthdr.rcvif = NULL; + m->m_pkthdr.len = m->m_len = sizeof(*wh); + wh = mtod(m, struct pppoe_full_hdr *); + bcopy(&sp->pkt_hdr, wh, sizeof(*wh)); + + /* Revert the stored header to DISC/PADT mode. */ + wh->ph.code = PADT_CODE; + /* + * Configure ethertype depending on what + * was used during sessions stage. + */ + if (wh->eh.ether_type == + ETHERTYPE_PPPOE_3COM_SESS) + wh->eh.ether_type = ETHERTYPE_PPPOE_3COM_DISC; + else + wh->eh.ether_type = ETHERTYPE_PPPOE_DISC; + /* + * Add a General error message and adjust + * sizes. + */ + tag = wh->ph.tag; + tag->tag_type = PTT_GEN_ERR; + tag->tag_len = htons((u_int16_t)msglen); + strncpy(tag->tag_data, SIGNOFF, msglen); + m->m_pkthdr.len = (m->m_len += sizeof(*tag) + + msglen); + wh->ph.length = htons(sizeof(*tag) + msglen); + NG_SEND_DATA_ONLY(error, + privp->ethernet_hook, m); + } + } + if (sp->state == PPPOE_LISTENING) + LIST_REMOVE(sp, sessions); + else if (sp->Session_ID) + pppoe_delsession(sp); + /* + * As long as we have somewhere to store the timeout handle, + * we may have a timeout pending.. get rid of it. + */ + if (sp->neg) { + ng_uncallout(&sp->neg->handle, node); + if (sp->neg->m) + m_freem(sp->neg->m); + free(sp->neg, M_NETGRAPH_PPPOE); + } + free(sp, M_NETGRAPH_PPPOE); + NG_HOOK_SET_PRIVATE(hook, NULL); + } + if ((NG_NODE_NUMHOOKS(node) == 0) && + (NG_NODE_IS_VALID(node))) + ng_rmnode_self(node); + return (0); +} + +/* + * Timeouts come here. + */ +static void +pppoe_ticker(node_p node, hook_p hook, void *arg1, int arg2) +{ + priv_p privp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + sessp sp = NG_HOOK_PRIVATE(hook); + negp neg = sp->neg; + struct mbuf *m0 = NULL; + int error = 0; + + CTR6(KTR_NET, "%20s: node [%x] (%p) hook \"%s\" (%p) session %d", + __func__, node->nd_ID, node, hook->hk_name, hook, sp->Session_ID); + switch(sp->state) { + /* + * Resend the last packet, using an exponential backoff. + * After a period of time, stop growing the backoff, + * And either leave it, or revert to the start. + */ + case PPPOE_SINIT: + case PPPOE_SREQ: + /* Timeouts on these produce resends. */ + m0 = m_copypacket(sp->neg->m, M_DONTWAIT); + NG_SEND_DATA_ONLY( error, privp->ethernet_hook, m0); + ng_callout(&neg->handle, node, hook, neg->timeout * hz, + pppoe_ticker, NULL, 0); + if ((neg->timeout <<= 1) > PPPOE_TIMEOUT_LIMIT) { + if (sp->state == PPPOE_SREQ) { + /* Revert to SINIT mode. */ + pppoe_start(sp); + } else { + neg->timeout = PPPOE_TIMEOUT_LIMIT; + } + } + break; + case PPPOE_PRIMED: + case PPPOE_SOFFER: + /* A timeout on these says "give up" */ + ng_rmhook_self(hook); + break; + default: + /* Timeouts have no meaning in other states. */ + log(LOG_NOTICE, "ng_pppoe[%x]: unexpected timeout\n", + node->nd_ID); + } +} + +/* + * Parse an incoming packet to see if any tags should be copied to the + * output packet. Don't do any tags that have been handled in the main + * state machine. + */ +static const struct pppoe_tag* +scan_tags(sessp sp, const struct pppoe_hdr* ph) +{ + const char *const end = (const char *)next_tag(ph); + const char *ptn; + const struct pppoe_tag *pt = &ph->tag[0]; + + /* + * Keep processing tags while a tag header will still fit. + */ + CTR2(KTR_NET, "%20s: called %d", __func__, sp->Session_ID); + + while((const char*)(pt + 1) <= end) { + /* + * If the tag data would go past the end of the packet, abort. + */ + ptn = (((const char *)(pt + 1)) + ntohs(pt->tag_len)); + if(ptn > end) + return NULL; + + switch (pt->tag_type) { + case PTT_RELAY_SID: + insert_tag(sp, pt); + break; + case PTT_EOL: + return NULL; + case PTT_SRV_NAME: + case PTT_AC_NAME: + case PTT_HOST_UNIQ: + case PTT_AC_COOKIE: + case PTT_VENDOR: + case PTT_SRV_ERR: + case PTT_SYS_ERR: + case PTT_GEN_ERR: + break; + } + pt = (const struct pppoe_tag*)ptn; + } + return NULL; +} + +static int +pppoe_send_event(sessp sp, enum cmd cmdid) +{ + int error; + struct ng_mesg *msg; + struct ngpppoe_sts *sts; + + CTR2(KTR_NET, "%20s: called %d", __func__, sp->Session_ID); + + NG_MKMESSAGE(msg, NGM_PPPOE_COOKIE, cmdid, + sizeof(struct ngpppoe_sts), M_NOWAIT); + if (msg == NULL) + return (ENOMEM); + sts = (struct ngpppoe_sts *)msg->data; + strncpy(sts->hook, NG_HOOK_NAME(sp->hook), NG_HOOKSIZ); + NG_SEND_MSG_ID(error, NG_HOOK_NODE(sp->hook), msg, sp->creator, 0); + return (error); +} diff --git a/sys/netgraph7/ng_pppoe.h b/sys/netgraph7/ng_pppoe.h new file mode 100644 index 0000000000..8dfbec0888 --- /dev/null +++ b/sys/netgraph7/ng_pppoe.h @@ -0,0 +1,257 @@ +/* + * ng_pppoe.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_pppoe.h,v 1.25 2007/12/26 19:15:07 mav Exp $ + * $Whistle: ng_pppoe.h,v 1.7 1999/10/16 10:16:43 julian Exp $ + */ + +#ifndef _NETGRAPH_NG_PPPOE_H_ +#define _NETGRAPH_NG_PPPOE_H_ + +/******************************************************************** + * Netgraph hook constants etc. + ********************************************************************/ +/* Node type name. This should be unique among all netgraph node types */ +#define NG_PPPOE_NODE_TYPE "pppoe" + +#define NGM_PPPOE_COOKIE 1089893072 + +#define PPPOE_SERVICE_NAME_SIZE 64 /* for now */ + +/* Hook names */ +#define NG_PPPOE_HOOK_ETHERNET "ethernet" +#define NG_PPPOE_HOOK_DEBUG "debug" + +/* Mode names */ +#define NG_PPPOE_STANDARD "standard" +#define NG_PPPOE_3COM "3Com" +#define NG_PPPOE_NONSTANDARD NG_PPPOE_3COM +#define NG_PPPOE_DLINK "D-Link" + +/********************************************************************** + * Netgraph commands understood by this node type. + * FAIL, SUCCESS, CLOSE and ACNAME are sent by the node rather than received. + ********************************************************************/ +enum cmd { + NGM_PPPOE_SET_FLAG = 1, + NGM_PPPOE_CONNECT = 2, /* Client, Try find this service */ + NGM_PPPOE_LISTEN = 3, /* Server, Await a request for this service */ + NGM_PPPOE_OFFER = 4, /* Server, hook X should respond (*) */ + NGM_PPPOE_SUCCESS = 5, /* State machine connected */ + NGM_PPPOE_FAIL = 6, /* State machine could not connect */ + NGM_PPPOE_CLOSE = 7, /* Session closed down */ + NGM_PPPOE_SERVICE = 8, /* additional Service to advertise (in PADO) */ + NGM_PPPOE_ACNAME = 9, /* AC_NAME for informational purposes */ + NGM_PPPOE_GET_STATUS = 10, /* data in/out */ + NGM_PPPOE_SESSIONID = 11, /* Session_ID for informational purposes */ + NGM_PPPOE_SETMODE = 12, /* set to standard or compat modes */ + NGM_PPPOE_GETMODE = 13, /* see current mode */ + NGM_PPPOE_SETENADDR = 14, /* set Ethernet address */ +}; + +/*********************** + * Structures passed in the various netgraph command messages. + ***********************/ +/* This structure is returned by the NGM_PPPOE_GET_STATUS command */ +struct ngpppoestat { + u_int packets_in; /* packets in from ethernet */ + u_int packets_out; /* packets out towards ethernet */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_PPPOESTAT_TYPE_INFO { \ + { "packets_in", &ng_parse_uint_type }, \ + { "packets_out", &ng_parse_uint_type }, \ + { NULL } \ +} + +/* + * When this structure is accepted by the NGM_PPPOE_CONNECT command : + * The data field is MANDATORY. + * The session sends out a PADI request for the named service. + * + * + * When this structure is accepted by the NGM_PPPOE_LISTEN command. + * If no service is given this is assumed to accept ALL PADI requests. + * This may at some time take a regexp expression, but not yet. + * Matching PADI requests will be passed up the named hook. + * + * + * When this structure is accepted by the NGM_PPPOE_OFFER command: + * The AC-NAme field is set from that given and a PADI + * packet is expected to arrive from the session control daemon, on the + * named hook. The session will then issue the appropriate PADO + * and begin negotiation. + */ +struct ngpppoe_init_data { + char hook[NG_HOOKSIZ]; /* hook to monitor on */ + u_int16_t data_len; /* Length of the service name */ + char data[]; /* init data goes here */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_PPPOE_INIT_DATA_TYPE_INFO { \ + { "hook", &ng_parse_hookbuf_type }, \ + { "data", &ng_parse_sizedstring_type }, \ + { NULL } \ +} + +/* + * This structure is used by the asychronous success and failure messages. + * (to report which hook has failed or connected). The message is sent + * to whoever requested the connection. (close may use this too). + */ +struct ngpppoe_sts { + char hook[NG_HOOKSIZ]; /* hook associated with event session */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_PPPOE_STS_TYPE_INFO { \ + { "hook", &ng_parse_hookbuf_type }, \ + { NULL } \ +} + + +/******************************************************************** + * Constants and definitions specific to pppoe + ********************************************************************/ + +#define PPPOE_TIMEOUT_LIMIT 64 +#define PPPOE_OFFER_TIMEOUT 16 +#define PPPOE_INITIAL_TIMEOUT 2 + +/* Codes to identify message types */ +#define PADI_CODE 0x09 +#define PADO_CODE 0x07 +#define PADR_CODE 0x19 +#define PADS_CODE 0x65 +#define PADT_CODE 0xa7 + +/* Tag identifiers */ +#if BYTE_ORDER == BIG_ENDIAN +#define PTT_EOL (0x0000) +#define PTT_SRV_NAME (0x0101) +#define PTT_AC_NAME (0x0102) +#define PTT_HOST_UNIQ (0x0103) +#define PTT_AC_COOKIE (0x0104) +#define PTT_VENDOR (0x0105) +#define PTT_RELAY_SID (0x0110) +#define PTT_SRV_ERR (0x0201) +#define PTT_SYS_ERR (0x0202) +#define PTT_GEN_ERR (0x0203) + +#define ETHERTYPE_PPPOE_DISC 0x8863 /* pppoe discovery packets */ +#define ETHERTYPE_PPPOE_SESS 0x8864 /* pppoe session packets */ +#define ETHERTYPE_PPPOE_3COM_DISC 0x3c12 /* pppoe discovery packets 3com? */ +#define ETHERTYPE_PPPOE_3COM_SESS 0x3c13 /* pppoe session packets 3com? */ +#else +#define PTT_EOL (0x0000) +#define PTT_SRV_NAME (0x0101) +#define PTT_AC_NAME (0x0201) +#define PTT_HOST_UNIQ (0x0301) +#define PTT_AC_COOKIE (0x0401) +#define PTT_VENDOR (0x0501) +#define PTT_RELAY_SID (0x1001) +#define PTT_SRV_ERR (0x0102) +#define PTT_SYS_ERR (0x0202) +#define PTT_GEN_ERR (0x0302) + +#define ETHERTYPE_PPPOE_DISC 0x6388 /* pppoe discovery packets */ +#define ETHERTYPE_PPPOE_SESS 0x6488 /* pppoe session packets */ +#define ETHERTYPE_PPPOE_3COM_DISC 0x123c /* pppoe discovery packets 3com? */ +#define ETHERTYPE_PPPOE_3COM_SESS 0x133c /* pppoe session packets 3com? */ +#endif + +struct pppoe_tag { + u_int16_t tag_type; + u_int16_t tag_len; + char tag_data[]; +}__packed; + +struct pppoe_hdr{ + u_int8_t ver:4; + u_int8_t type:4; + u_int8_t code; + u_int16_t sid; + u_int16_t length; + struct pppoe_tag tag[]; +}__packed; + + +struct pppoe_full_hdr { + struct ether_header eh; + struct pppoe_hdr ph; +}__packed; + +union packet { + struct pppoe_full_hdr pkt_header; + u_int8_t bytes[2048]; +}; + +struct datatag { + struct pppoe_tag hdr; + u_int8_t data[PPPOE_SERVICE_NAME_SIZE]; +}; + + +/* + * Define the order in which we will place tags in packets + * this may be ignored + */ +/* for PADI */ +#define TAGI_SVC 0 +#define TAGI_HUNIQ 1 +/* for PADO */ +#define TAGO_ACNAME 0 +#define TAGO_SVC 1 +#define TAGO_COOKIE 2 +#define TAGO_HUNIQ 3 +/* for PADR */ +#define TAGR_SVC 0 +#define TAGR_HUNIQ 1 +#define TAGR_COOKIE 2 +/* for PADS */ +#define TAGS_ACNAME 0 +#define TAGS_SVC 1 +#define TAGS_COOKIE 2 +#define TAGS_HUNIQ 3 +/* for PADT */ + +#endif /* _NETGRAPH_NG_PPPOE_H_ */ + diff --git a/sys/netgraph7/ng_pptpgre.c b/sys/netgraph7/ng_pptpgre.c new file mode 100644 index 0000000000..0816710ebb --- /dev/null +++ b/sys/netgraph7/ng_pptpgre.c @@ -0,0 +1,985 @@ +/* + * ng_pptpgre.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_pptpgre.c,v 1.42 2008/03/26 21:19:03 mav Exp $ + * $Whistle: ng_pptpgre.c,v 1.7 1999/12/08 00:10:06 archie Exp $ + */ + +/* + * PPTP/GRE netgraph node type. + * + * This node type does the GRE encapsulation as specified for the PPTP + * protocol (RFC 2637, section 4). This includes sequencing and + * retransmission of frames, but not the actual packet delivery nor + * any of the TCP control stream protocol. + * + * The "upper" hook of this node is suitable for attaching to a "ppp" + * node link hook. The "lower" hook of this node is suitable for attaching + * to a "ksocket" node on hook "inet/raw/gre". + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +/* GRE packet format, as used by PPTP */ +struct greheader { +#if BYTE_ORDER == LITTLE_ENDIAN + u_char recursion:3; /* recursion control */ + u_char ssr:1; /* strict source route */ + u_char hasSeq:1; /* sequence number present */ + u_char hasKey:1; /* key present */ + u_char hasRoute:1; /* routing present */ + u_char hasSum:1; /* checksum present */ + u_char vers:3; /* version */ + u_char flags:4; /* flags */ + u_char hasAck:1; /* acknowlege number present */ +#elif BYTE_ORDER == BIG_ENDIAN + u_char hasSum:1; /* checksum present */ + u_char hasRoute:1; /* routing present */ + u_char hasKey:1; /* key present */ + u_char hasSeq:1; /* sequence number present */ + u_char ssr:1; /* strict source route */ + u_char recursion:3; /* recursion control */ + u_char hasAck:1; /* acknowlege number present */ + u_char flags:4; /* flags */ + u_char vers:3; /* version */ +#else +#error BYTE_ORDER is not defined properly +#endif + u_int16_t proto; /* protocol (ethertype) */ + u_int16_t length; /* payload length */ + u_int16_t cid; /* call id */ + u_int32_t data[0]; /* opt. seq, ack, then data */ +}; + +/* The PPTP protocol ID used in the GRE 'proto' field */ +#define PPTP_GRE_PROTO 0x880b + +/* Bits that must be set a certain way in all PPTP/GRE packets */ +#define PPTP_INIT_VALUE ((0x2001 << 16) | PPTP_GRE_PROTO) +#define PPTP_INIT_MASK 0xef7fffff + +/* Min and max packet length */ +#define PPTP_MAX_PAYLOAD (0xffff - sizeof(struct greheader) - 8) + +/* All times are scaled by this (PPTP_TIME_SCALE time units = 1 sec.) */ +#define PPTP_TIME_SCALE 1024 /* milliseconds */ +typedef u_int64_t pptptime_t; + +/* Acknowledgment timeout parameters and functions */ +#define PPTP_XMIT_WIN 16 /* max xmit window */ +#define PPTP_MIN_TIMEOUT (PPTP_TIME_SCALE / 83) /* 12 milliseconds */ +#define PPTP_MAX_TIMEOUT (3 * PPTP_TIME_SCALE) /* 3 seconds */ + +/* When we recieve a packet, we wait to see if there's an outgoing packet + we can piggy-back the ACK off of. These parameters determine the mimimum + and maxmimum length of time we're willing to wait in order to do that. + These have no effect unless "enableDelayedAck" is turned on. */ +#define PPTP_MIN_ACK_DELAY (PPTP_TIME_SCALE / 500) /* 2 milliseconds */ +#define PPTP_MAX_ACK_DELAY (PPTP_TIME_SCALE / 2) /* 500 milliseconds */ + +/* See RFC 2637 section 4.4 */ +#define PPTP_ACK_ALPHA(x) (((x) + 4) >> 3) /* alpha = 0.125 */ +#define PPTP_ACK_BETA(x) (((x) + 2) >> 2) /* beta = 0.25 */ +#define PPTP_ACK_CHI(x) ((x) << 2) /* chi = 4 */ +#define PPTP_ACK_DELTA(x) ((x) << 1) /* delta = 2 */ + +#define PPTP_SEQ_DIFF(x,y) ((int32_t)(x) - (int32_t)(y)) + +#define SESSHASHSIZE 0x0020 +#define SESSHASH(x) (((x) ^ ((x) >> 8)) & (SESSHASHSIZE - 1)) + +/* We keep packet retransmit and acknowlegement state in this struct */ +struct ng_pptpgre_sess { + node_p node; /* this node pointer */ + hook_p hook; /* hook to upper layers */ + struct ng_pptpgre_conf conf; /* configuration info */ + struct mtx mtx; /* session mutex */ + u_int32_t recvSeq; /* last seq # we rcv'd */ + u_int32_t xmitSeq; /* last seq # we sent */ + u_int32_t recvAck; /* last seq # peer ack'd */ + u_int32_t xmitAck; /* last seq # we ack'd */ + int32_t ato; /* adaptive time-out value */ + int32_t rtt; /* round trip time estimate */ + int32_t dev; /* deviation estimate */ + u_int16_t xmitWin; /* size of xmit window */ + struct callout sackTimer; /* send ack timer */ + struct callout rackTimer; /* recv ack timer */ + u_int32_t winAck; /* seq when xmitWin will grow */ + pptptime_t timeSent[PPTP_XMIT_WIN]; + LIST_ENTRY(ng_pptpgre_sess) sessions; +}; +typedef struct ng_pptpgre_sess *hpriv_p; + +/* Node private data */ +struct ng_pptpgre_private { + hook_p upper; /* hook to upper layers */ + hook_p lower; /* hook to lower layers */ + struct ng_pptpgre_sess uppersess; /* default session for compat */ + LIST_HEAD(, ng_pptpgre_sess) sesshash[SESSHASHSIZE]; + struct ng_pptpgre_stats stats; /* node statistics */ +}; +typedef struct ng_pptpgre_private *priv_p; + +/* Netgraph node methods */ +static ng_constructor_t ng_pptpgre_constructor; +static ng_rcvmsg_t ng_pptpgre_rcvmsg; +static ng_shutdown_t ng_pptpgre_shutdown; +static ng_newhook_t ng_pptpgre_newhook; +static ng_rcvdata_t ng_pptpgre_rcvdata; +static ng_rcvdata_t ng_pptpgre_rcvdata_lower; +static ng_disconnect_t ng_pptpgre_disconnect; + +/* Helper functions */ +static int ng_pptpgre_xmit(hpriv_p hpriv, item_p item); +static void ng_pptpgre_start_send_ack_timer(hpriv_p hpriv); +static void ng_pptpgre_start_recv_ack_timer(hpriv_p hpriv); +static void ng_pptpgre_recv_ack_timeout(node_p node, hook_p hook, + void *arg1, int arg2); +static void ng_pptpgre_send_ack_timeout(node_p node, hook_p hook, + void *arg1, int arg2); +static hpriv_p ng_pptpgre_find_session(priv_p privp, u_int16_t cid); +static void ng_pptpgre_reset(hpriv_p hpriv); +static pptptime_t ng_pptpgre_time(void); + +/* Parse type for struct ng_pptpgre_conf */ +static const struct ng_parse_struct_field ng_pptpgre_conf_type_fields[] + = NG_PPTPGRE_CONF_TYPE_INFO; +static const struct ng_parse_type ng_pptpgre_conf_type = { + &ng_parse_struct_type, + &ng_pptpgre_conf_type_fields, +}; + +/* Parse type for struct ng_pptpgre_stats */ +static const struct ng_parse_struct_field ng_pptpgre_stats_type_fields[] + = NG_PPTPGRE_STATS_TYPE_INFO; +static const struct ng_parse_type ng_pptp_stats_type = { + &ng_parse_struct_type, + &ng_pptpgre_stats_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_pptpgre_cmdlist[] = { + { + NGM_PPTPGRE_COOKIE, + NGM_PPTPGRE_SET_CONFIG, + "setconfig", + &ng_pptpgre_conf_type, + NULL + }, + { + NGM_PPTPGRE_COOKIE, + NGM_PPTPGRE_GET_CONFIG, + "getconfig", + &ng_parse_hint16_type, + &ng_pptpgre_conf_type + }, + { + NGM_PPTPGRE_COOKIE, + NGM_PPTPGRE_GET_STATS, + "getstats", + NULL, + &ng_pptp_stats_type + }, + { + NGM_PPTPGRE_COOKIE, + NGM_PPTPGRE_CLR_STATS, + "clrstats", + NULL, + NULL + }, + { + NGM_PPTPGRE_COOKIE, + NGM_PPTPGRE_GETCLR_STATS, + "getclrstats", + NULL, + &ng_pptp_stats_type + }, + { 0 } +}; + +/* Node type descriptor */ +static struct ng_type ng_pptpgre_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_PPTPGRE_NODE_TYPE, + .constructor = ng_pptpgre_constructor, + .rcvmsg = ng_pptpgre_rcvmsg, + .shutdown = ng_pptpgre_shutdown, + .newhook = ng_pptpgre_newhook, + .rcvdata = ng_pptpgre_rcvdata, + .disconnect = ng_pptpgre_disconnect, + .cmdlist = ng_pptpgre_cmdlist, +}; +NETGRAPH_INIT(pptpgre, &ng_pptpgre_typestruct); + +#define ERROUT(x) do { error = (x); goto done; } while (0) + +/************************************************************************ + NETGRAPH NODE STUFF + ************************************************************************/ + +/* + * Node type constructor + */ +static int +ng_pptpgre_constructor(node_p node) +{ + priv_p priv; + int i; + + /* Allocate private structure */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + + NG_NODE_SET_PRIVATE(node, priv); + + /* Initialize state */ + mtx_init(&priv->uppersess.mtx, "ng_pptp", NULL, MTX_DEF); + ng_callout_init(&priv->uppersess.sackTimer); + ng_callout_init(&priv->uppersess.rackTimer); + priv->uppersess.node = node; + + for (i = 0; i < SESSHASHSIZE; i++) + LIST_INIT(&priv->sesshash[i]); + + LIST_INSERT_HEAD(&priv->sesshash[0], &priv->uppersess, sessions); + + /* Done */ + return (0); +} + +/* + * Give our OK for a hook to be added. + */ +static int +ng_pptpgre_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + /* Check hook name */ + if (strcmp(name, NG_PPTPGRE_HOOK_UPPER) == 0) { + priv->upper = hook; + priv->uppersess.hook = hook; + NG_HOOK_SET_PRIVATE(hook, &priv->uppersess); + } else if (strcmp(name, NG_PPTPGRE_HOOK_LOWER) == 0) { + priv->lower = hook; + NG_HOOK_SET_RCVDATA(hook, ng_pptpgre_rcvdata_lower); + } else { + static const char hexdig[16] = "0123456789abcdef"; + const char *hex; + hpriv_p hpriv; + int i, j; + uint16_t cid, hash; + + /* Parse hook name to get session ID */ + if (strncmp(name, NG_PPTPGRE_HOOK_SESSION_P, + sizeof(NG_PPTPGRE_HOOK_SESSION_P) - 1) != 0) + return (EINVAL); + hex = name + sizeof(NG_PPTPGRE_HOOK_SESSION_P) - 1; + for (cid = i = 0; i < 4; i++) { + for (j = 0; j < 16 && hex[i] != hexdig[j]; j++); + if (j == 16) + return (EINVAL); + cid = (cid << 4) | j; + } + if (hex[i] != '\0') + return (EINVAL); + + hpriv = malloc(sizeof(*hpriv), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (hpriv == NULL) + return (ENOMEM); + + /* Initialize state */ + mtx_init(&hpriv->mtx, "ng_pptp", NULL, MTX_DEF); + ng_callout_init(&hpriv->sackTimer); + ng_callout_init(&hpriv->rackTimer); + hpriv->conf.cid = cid; + hpriv->node = node; + hpriv->hook = hook; + NG_HOOK_SET_PRIVATE(hook, hpriv); + + hash = SESSHASH(cid); + LIST_INSERT_HEAD(&priv->sesshash[hash], hpriv, sessions); + } + + return (0); +} + +/* + * Receive a control message. + */ +static int +ng_pptpgre_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_PPTPGRE_COOKIE: + switch (msg->header.cmd) { + case NGM_PPTPGRE_SET_CONFIG: + { + struct ng_pptpgre_conf *const newConf = + (struct ng_pptpgre_conf *) msg->data; + hpriv_p hpriv; + uint16_t hash; + + /* Check for invalid or illegal config */ + if (msg->header.arglen != sizeof(*newConf)) + ERROUT(EINVAL); + /* Try to find session by cid. */ + hpriv = ng_pptpgre_find_session(priv, newConf->cid); + /* If not present - use upper. */ + if (hpriv == NULL) { + hpriv = &priv->uppersess; + LIST_REMOVE(hpriv, sessions); + hash = SESSHASH(newConf->cid); + LIST_INSERT_HEAD(&priv->sesshash[hash], hpriv, + sessions); + } + ng_pptpgre_reset(hpriv); /* reset on configure */ + hpriv->conf = *newConf; + break; + } + case NGM_PPTPGRE_GET_CONFIG: + { + hpriv_p hpriv; + + if (msg->header.arglen == 2) { + /* Try to find session by cid. */ + hpriv = ng_pptpgre_find_session(priv, + *((uint16_t *)msg->data)); + if (hpriv == NULL) + ERROUT(EINVAL); + } else if (msg->header.arglen == 0) { + /* Use upper. */ + hpriv = &priv->uppersess; + } else + ERROUT(EINVAL); + NG_MKRESPONSE(resp, msg, sizeof(hpriv->conf), M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + bcopy(&hpriv->conf, resp->data, sizeof(hpriv->conf)); + break; + } + case NGM_PPTPGRE_GET_STATS: + case NGM_PPTPGRE_CLR_STATS: + case NGM_PPTPGRE_GETCLR_STATS: + { + if (msg->header.cmd != NGM_PPTPGRE_CLR_STATS) { + NG_MKRESPONSE(resp, msg, + sizeof(priv->stats), M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + bcopy(&priv->stats, + resp->data, sizeof(priv->stats)); + } + if (msg->header.cmd != NGM_PPTPGRE_GET_STATS) + bzero(&priv->stats, sizeof(priv->stats)); + break; + } + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive incoming data on a hook. + */ +static int +ng_pptpgre_rcvdata(hook_p hook, item_p item) +{ + const hpriv_p hpriv = NG_HOOK_PRIVATE(hook); + int rval; + + /* If not configured, reject */ + if (!hpriv->conf.enabled) { + NG_FREE_ITEM(item); + return (ENXIO); + } + + mtx_lock(&hpriv->mtx); + + rval = ng_pptpgre_xmit(hpriv, item); + + mtx_assert(&hpriv->mtx, MA_NOTOWNED); + + return (rval); +} + +/* + * Hook disconnection + */ +static int +ng_pptpgre_disconnect(hook_p hook) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + const hpriv_p hpriv = NG_HOOK_PRIVATE(hook); + + /* Zero out hook pointer */ + if (hook == priv->upper) { + priv->upper = NULL; + priv->uppersess.hook = NULL; + } else if (hook == priv->lower) { + priv->lower = NULL; + } else { + /* Reset node (stops timers) */ + ng_pptpgre_reset(hpriv); + + LIST_REMOVE(hpriv, sessions); + mtx_destroy(&hpriv->mtx); + free(hpriv, M_NETGRAPH); + } + + /* Go away if no longer connected to anything */ + if ((NG_NODE_NUMHOOKS(node) == 0) + && (NG_NODE_IS_VALID(node))) + ng_rmnode_self(node); + return (0); +} + +/* + * Destroy node + */ +static int +ng_pptpgre_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + /* Reset node (stops timers) */ + ng_pptpgre_reset(&priv->uppersess); + + LIST_REMOVE(&priv->uppersess, sessions); + mtx_destroy(&priv->uppersess.mtx); + + FREE(priv, M_NETGRAPH); + + /* Decrement ref count */ + NG_NODE_UNREF(node); + return (0); +} + +/************************************************************************* + TRANSMIT AND RECEIVE FUNCTIONS +*************************************************************************/ + +/* + * Transmit an outgoing frame, or just an ack if m is NULL. + */ +static int +ng_pptpgre_xmit(hpriv_p hpriv, item_p item) +{ + const priv_p priv = NG_NODE_PRIVATE(hpriv->node); + u_char buf[sizeof(struct greheader) + 2 * sizeof(u_int32_t)]; + struct greheader *const gre = (struct greheader *)buf; + int grelen, error; + struct mbuf *m; + + mtx_assert(&hpriv->mtx, MA_OWNED); + + if (item) { + NGI_GET_M(item, m); + } else { + m = NULL; + } + /* Check if there's data */ + if (m != NULL) { + + /* Check if windowing is enabled */ + if (hpriv->conf.enableWindowing) { + /* Is our transmit window full? */ + if ((u_int32_t)PPTP_SEQ_DIFF(hpriv->xmitSeq, + hpriv->recvAck) >= hpriv->xmitWin) { + priv->stats.xmitDrops++; + ERROUT(ENOBUFS); + } + } + + /* Sanity check frame length */ + if (m != NULL && m->m_pkthdr.len > PPTP_MAX_PAYLOAD) { + priv->stats.xmitTooBig++; + ERROUT(EMSGSIZE); + } + } else { + priv->stats.xmitLoneAcks++; + } + + /* Build GRE header */ + ((u_int32_t *)gre)[0] = htonl(PPTP_INIT_VALUE); + gre->length = (m != NULL) ? htons((u_short)m->m_pkthdr.len) : 0; + gre->cid = htons(hpriv->conf.peerCid); + + /* Include sequence number if packet contains any data */ + if (m != NULL) { + gre->hasSeq = 1; + if (hpriv->conf.enableWindowing) { + hpriv->timeSent[hpriv->xmitSeq - hpriv->recvAck] + = ng_pptpgre_time(); + } + hpriv->xmitSeq++; + gre->data[0] = htonl(hpriv->xmitSeq); + } + + /* Include acknowledgement (and stop send ack timer) if needed */ + if (hpriv->conf.enableAlwaysAck || hpriv->xmitAck != hpriv->recvSeq) { + gre->hasAck = 1; + gre->data[gre->hasSeq] = htonl(hpriv->recvSeq); + hpriv->xmitAck = hpriv->recvSeq; + if (hpriv->conf.enableDelayedAck) + ng_uncallout(&hpriv->sackTimer, hpriv->node); + } + + /* Prepend GRE header to outgoing frame */ + grelen = sizeof(*gre) + sizeof(u_int32_t) * (gre->hasSeq + gre->hasAck); + if (m == NULL) { + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + priv->stats.memoryFailures++; + ERROUT(ENOBUFS); + } + m->m_len = m->m_pkthdr.len = grelen; + m->m_pkthdr.rcvif = NULL; + } else { + M_PREPEND(m, grelen, M_DONTWAIT); + if (m == NULL || (m->m_len < grelen + && (m = m_pullup(m, grelen)) == NULL)) { + priv->stats.memoryFailures++; + ERROUT(ENOBUFS); + } + } + bcopy(gre, mtod(m, u_char *), grelen); + + /* Update stats */ + priv->stats.xmitPackets++; + priv->stats.xmitOctets += m->m_pkthdr.len; + + /* + * XXX: we should reset timer only after an item has been sent + * successfully. + */ + if (hpriv->conf.enableWindowing && + gre->hasSeq && hpriv->xmitSeq == hpriv->recvAck + 1) + ng_pptpgre_start_recv_ack_timer(hpriv); + + mtx_unlock(&hpriv->mtx); + + /* Deliver packet */ + if (item) { + NG_FWD_NEW_DATA(error, item, priv->lower, m); + } else { + NG_SEND_DATA_ONLY(error, priv->lower, m); + } + + return (error); + +done: + mtx_unlock(&hpriv->mtx); + NG_FREE_M(m); + if (item) + NG_FREE_ITEM(item); + return (error); +} + +/* + * Handle an incoming packet. The packet includes the IP header. + */ +static int +ng_pptpgre_rcvdata_lower(hook_p hook, item_p item) +{ + hpriv_p hpriv; + node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + int iphlen, grelen, extralen; + const struct greheader *gre; + const struct ip *ip; + int error = 0; + struct mbuf *m; + + NGI_GET_M(item, m); + /* Update stats */ + priv->stats.recvPackets++; + priv->stats.recvOctets += m->m_pkthdr.len; + + /* Sanity check packet length */ + if (m->m_pkthdr.len < sizeof(*ip) + sizeof(*gre)) { + priv->stats.recvRunts++; + ERROUT(EINVAL); + } + + /* Safely pull up the complete IP+GRE headers */ + if (m->m_len < sizeof(*ip) + sizeof(*gre) + && (m = m_pullup(m, sizeof(*ip) + sizeof(*gre))) == NULL) { + priv->stats.memoryFailures++; + ERROUT(ENOBUFS); + } + ip = mtod(m, const struct ip *); + iphlen = ip->ip_hl << 2; + if (m->m_len < iphlen + sizeof(*gre)) { + if ((m = m_pullup(m, iphlen + sizeof(*gre))) == NULL) { + priv->stats.memoryFailures++; + ERROUT(ENOBUFS); + } + ip = mtod(m, const struct ip *); + } + gre = (const struct greheader *)((const u_char *)ip + iphlen); + grelen = sizeof(*gre) + sizeof(u_int32_t) * (gre->hasSeq + gre->hasAck); + if (m->m_pkthdr.len < iphlen + grelen) { + priv->stats.recvRunts++; + ERROUT(EINVAL); + } + if (m->m_len < iphlen + grelen) { + if ((m = m_pullup(m, iphlen + grelen)) == NULL) { + priv->stats.memoryFailures++; + ERROUT(ENOBUFS); + } + ip = mtod(m, const struct ip *); + gre = (const struct greheader *)((const u_char *)ip + iphlen); + } + + /* Sanity check packet length and GRE header bits */ + extralen = m->m_pkthdr.len + - (iphlen + grelen + gre->hasSeq * (u_int16_t)ntohs(gre->length)); + if (extralen < 0) { + priv->stats.recvBadGRE++; + ERROUT(EINVAL); + } + if ((ntohl(*((const u_int32_t *)gre)) & PPTP_INIT_MASK) + != PPTP_INIT_VALUE) { + priv->stats.recvBadGRE++; + ERROUT(EINVAL); + } + + hpriv = ng_pptpgre_find_session(priv, ntohs(gre->cid)); + if (hpriv == NULL || hpriv->hook == NULL || !hpriv->conf.enabled) { + priv->stats.recvBadCID++; + ERROUT(EINVAL); + } + mtx_lock(&hpriv->mtx); + + /* Look for peer ack */ + if (gre->hasAck) { + const u_int32_t ack = ntohl(gre->data[gre->hasSeq]); + const int index = ack - hpriv->recvAck - 1; + long sample; + long diff; + + /* Sanity check ack value */ + if (PPTP_SEQ_DIFF(ack, hpriv->xmitSeq) > 0) { + priv->stats.recvBadAcks++; + goto badAck; /* we never sent it! */ + } + if (PPTP_SEQ_DIFF(ack, hpriv->recvAck) <= 0) + goto badAck; /* ack already timed out */ + hpriv->recvAck = ack; + + /* Update adaptive timeout stuff */ + if (hpriv->conf.enableWindowing) { + sample = ng_pptpgre_time() - hpriv->timeSent[index]; + diff = sample - hpriv->rtt; + hpriv->rtt += PPTP_ACK_ALPHA(diff); + if (diff < 0) + diff = -diff; + hpriv->dev += PPTP_ACK_BETA(diff - hpriv->dev); + /* +2 to compensate low precision of int math */ + hpriv->ato = hpriv->rtt + PPTP_ACK_CHI(hpriv->dev + 2); + if (hpriv->ato > PPTP_MAX_TIMEOUT) + hpriv->ato = PPTP_MAX_TIMEOUT; + else if (hpriv->ato < PPTP_MIN_TIMEOUT) + hpriv->ato = PPTP_MIN_TIMEOUT; + + /* Shift packet transmit times in our transmit window */ + bcopy(hpriv->timeSent + index + 1, hpriv->timeSent, + sizeof(*hpriv->timeSent) + * (PPTP_XMIT_WIN - (index + 1))); + + /* If we sent an entire window, increase window size */ + if (PPTP_SEQ_DIFF(ack, hpriv->winAck) >= 0 + && hpriv->xmitWin < PPTP_XMIT_WIN) { + hpriv->xmitWin++; + hpriv->winAck = ack + hpriv->xmitWin; + } + + /* Stop/(re)start receive ACK timer as necessary */ + ng_uncallout(&hpriv->rackTimer, hpriv->node); + if (hpriv->recvAck != hpriv->xmitSeq) + ng_pptpgre_start_recv_ack_timer(hpriv); + } + } +badAck: + + /* See if frame contains any data */ + if (gre->hasSeq) { + const u_int32_t seq = ntohl(gre->data[0]); + + /* Sanity check sequence number */ + if (PPTP_SEQ_DIFF(seq, hpriv->recvSeq) <= 0) { + if (seq == hpriv->recvSeq) + priv->stats.recvDuplicates++; + else + priv->stats.recvOutOfOrder++; + mtx_unlock(&hpriv->mtx); + ERROUT(EINVAL); + } + hpriv->recvSeq = seq; + + /* We need to acknowledge this packet; do it soon... */ + if (!(callout_pending(&hpriv->sackTimer))) { + /* If delayed ACK is disabled, send it now */ + if (!hpriv->conf.enableDelayedAck) { /* ack now */ + ng_pptpgre_xmit(hpriv, NULL); + /* ng_pptpgre_xmit() drops the mutex */ + } else { /* ack later */ + ng_pptpgre_start_send_ack_timer(hpriv); + mtx_unlock(&hpriv->mtx); + } + } else + mtx_unlock(&hpriv->mtx); + + /* Trim mbuf down to internal payload */ + m_adj(m, iphlen + grelen); + if (extralen > 0) + m_adj(m, -extralen); + + mtx_assert(&hpriv->mtx, MA_NOTOWNED); + + /* Deliver frame to upper layers */ + NG_FWD_NEW_DATA(error, item, hpriv->hook, m); + } else { + priv->stats.recvLoneAcks++; + mtx_unlock(&hpriv->mtx); + NG_FREE_ITEM(item); + NG_FREE_M(m); /* no data to deliver */ + } + + return (error); + +done: + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (error); +} + +/************************************************************************* + TIMER RELATED FUNCTIONS +*************************************************************************/ + +/* + * Start a timer for the peer's acknowledging our oldest unacknowledged + * sequence number. If we get an ack for this sequence number before + * the timer goes off, we cancel the timer. Resets currently running + * recv ack timer, if any. + */ +static void +ng_pptpgre_start_recv_ack_timer(hpriv_p hpriv) +{ + int remain, ticks; + + /* Compute how long until oldest unack'd packet times out, + and reset the timer to that time. */ + remain = (hpriv->timeSent[0] + hpriv->ato) - ng_pptpgre_time(); + if (remain < 0) + remain = 0; + + /* Be conservative: timeout can happen up to 1 tick early */ + ticks = (((remain * hz) + PPTP_TIME_SCALE - 1) / PPTP_TIME_SCALE) + 1; + ng_callout(&hpriv->rackTimer, hpriv->node, hpriv->hook, + ticks, ng_pptpgre_recv_ack_timeout, hpriv, 0); +} + +/* + * The peer has failed to acknowledge the oldest unacknowledged sequence + * number within the time allotted. Update our adaptive timeout parameters + * and reset/restart the recv ack timer. + */ +static void +ng_pptpgre_recv_ack_timeout(node_p node, hook_p hook, void *arg1, int arg2) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + const hpriv_p hpriv = arg1; + + /* Update adaptive timeout stuff */ + priv->stats.recvAckTimeouts++; + hpriv->rtt = PPTP_ACK_DELTA(hpriv->rtt) + 1; /* +1 to avoid delta*0 case */ + hpriv->ato = hpriv->rtt + PPTP_ACK_CHI(hpriv->dev); + if (hpriv->ato > PPTP_MAX_TIMEOUT) + hpriv->ato = PPTP_MAX_TIMEOUT; + else if (hpriv->ato < PPTP_MIN_TIMEOUT) + hpriv->ato = PPTP_MIN_TIMEOUT; + + /* Reset ack and sliding window */ + hpriv->recvAck = hpriv->xmitSeq; /* pretend we got the ack */ + hpriv->xmitWin = (hpriv->xmitWin + 1) / 2; /* shrink transmit window */ + hpriv->winAck = hpriv->recvAck + hpriv->xmitWin; /* reset win expand time */ +} + +/* + * Start the send ack timer. This assumes the timer is not + * already running. + */ +static void +ng_pptpgre_start_send_ack_timer(hpriv_p hpriv) +{ + int ackTimeout, ticks; + + /* Take 1/4 of the estimated round trip time */ + ackTimeout = (hpriv->rtt >> 2); + if (ackTimeout < PPTP_MIN_ACK_DELAY) + ackTimeout = PPTP_MIN_ACK_DELAY; + else if (ackTimeout > PPTP_MAX_ACK_DELAY) + ackTimeout = PPTP_MAX_ACK_DELAY; + + /* Be conservative: timeout can happen up to 1 tick early */ + ticks = (((ackTimeout * hz) + PPTP_TIME_SCALE - 1) / PPTP_TIME_SCALE); + ng_callout(&hpriv->sackTimer, hpriv->node, hpriv->hook, + ticks, ng_pptpgre_send_ack_timeout, hpriv, 0); +} + +/* + * We've waited as long as we're willing to wait before sending an + * acknowledgement to the peer for received frames. We had hoped to + * be able to piggy back our acknowledgement on an outgoing data frame, + * but apparently there haven't been any since. So send the ack now. + */ +static void +ng_pptpgre_send_ack_timeout(node_p node, hook_p hook, void *arg1, int arg2) +{ + const hpriv_p hpriv = arg1; + + mtx_lock(&hpriv->mtx); + /* Send a frame with an ack but no payload */ + ng_pptpgre_xmit(hpriv, NULL); + mtx_assert(&hpriv->mtx, MA_NOTOWNED); +} + +/************************************************************************* + MISC FUNCTIONS +*************************************************************************/ + +/* + * Find the hook with a given session ID. + */ +static hpriv_p +ng_pptpgre_find_session(priv_p privp, u_int16_t cid) +{ + uint16_t hash = SESSHASH(cid); + hpriv_p hpriv = NULL; + + LIST_FOREACH(hpriv, &privp->sesshash[hash], sessions) { + if (hpriv->conf.cid == cid) + break; + } + + return (hpriv); +} + +/* + * Reset state (must be called with lock held or from writer) + */ +static void +ng_pptpgre_reset(hpriv_p hpriv) +{ + /* Reset adaptive timeout state */ + hpriv->ato = PPTP_MAX_TIMEOUT; + hpriv->rtt = PPTP_TIME_SCALE / 10; + if (hpriv->conf.peerPpd > 1) /* ppd = 0 treat as = 1 */ + hpriv->rtt *= hpriv->conf.peerPpd; + hpriv->dev = 0; + hpriv->xmitWin = (hpriv->conf.recvWin + 1) / 2; + if (hpriv->xmitWin < 2) /* often the first packet is lost */ + hpriv->xmitWin = 2; /* because the peer isn't ready */ + else if (hpriv->xmitWin > PPTP_XMIT_WIN) + hpriv->xmitWin = PPTP_XMIT_WIN; + hpriv->winAck = hpriv->xmitWin; + + /* Reset sequence numbers */ + hpriv->recvSeq = ~0; + hpriv->recvAck = ~0; + hpriv->xmitSeq = ~0; + hpriv->xmitAck = ~0; + + /* Stop timers */ + ng_uncallout(&hpriv->sackTimer, hpriv->node); + ng_uncallout(&hpriv->rackTimer, hpriv->node); +} + +/* + * Return the current time scaled & translated to our internally used format. + */ +static pptptime_t +ng_pptpgre_time(void) +{ + struct timeval tv; + pptptime_t t; + + microuptime(&tv); + t = (pptptime_t)tv.tv_sec * PPTP_TIME_SCALE; + t += tv.tv_usec / (1000000 / PPTP_TIME_SCALE); + return(t); +} diff --git a/sys/netgraph7/ng_pptpgre.h b/sys/netgraph7/ng_pptpgre.h new file mode 100644 index 0000000000..90f0c5bb4e --- /dev/null +++ b/sys/netgraph7/ng_pptpgre.h @@ -0,0 +1,135 @@ +/* + * ng_pptpgre.h + */ + +/*- + * Copyright (c) 1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_pptpgre.h,v 1.10 2008/03/24 22:55:22 mav Exp $ + * $Whistle: ng_pptpgre.h,v 1.3 1999/12/08 00:11:36 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_PPTPGRE_H_ +#define _NETGRAPH_NG_PPTPGRE_H_ + +/* Node type name and magic cookie */ +#define NG_PPTPGRE_NODE_TYPE "pptpgre" +#define NGM_PPTPGRE_COOKIE 1082548365 + +/* Hook names */ +#define NG_PPTPGRE_HOOK_UPPER "upper" /* to upper layers */ +#define NG_PPTPGRE_HOOK_LOWER "lower" /* to lower layers */ + +/* Session hooks: prefix plus hex session ID, e.g., "session_3e14" */ +#define NG_PPTPGRE_HOOK_SESSION_P "session_" +#define NG_PPTPGRE_HOOK_SESSION_F "session_%04x" + +/* Configuration for a session */ +struct ng_pptpgre_conf { + u_char enabled; /* enables traffic flow */ + u_char enableDelayedAck;/* enables delayed acks */ + u_char enableAlwaysAck;/* always include ack with data */ + u_char enableWindowing;/* enable windowing algorithm */ + u_int16_t cid; /* my call id */ + u_int16_t peerCid; /* peer call id */ + u_int16_t recvWin; /* peer recv window size */ + u_int16_t peerPpd; /* peer packet processing delay + (in units of 1/10 of a second) */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_PPTPGRE_CONF_TYPE_INFO { \ + { "enabled", &ng_parse_uint8_type }, \ + { "enableDelayedAck", &ng_parse_uint8_type }, \ + { "enableAlwaysAck", &ng_parse_uint8_type }, \ + { "enableWindowing", &ng_parse_uint8_type }, \ + { "cid", &ng_parse_hint16_type }, \ + { "peerCid", &ng_parse_hint16_type }, \ + { "recvWin", &ng_parse_uint16_type }, \ + { "peerPpd", &ng_parse_uint16_type }, \ + { NULL } \ +} + +/* Statistics struct */ +struct ng_pptpgre_stats { + u_int32_t xmitPackets; /* number of GRE packets xmit */ + u_int32_t xmitOctets; /* number of GRE octets xmit */ + u_int32_t xmitLoneAcks; /* ack-only packets transmitted */ + u_int32_t xmitDrops; /* xmits dropped due to full window */ + u_int32_t xmitTooBig; /* xmits dropped because too big */ + u_int32_t recvPackets; /* number of GRE packets rec'd */ + u_int32_t recvOctets; /* number of GRE octets rec'd */ + u_int32_t recvRunts; /* too short packets rec'd */ + u_int32_t recvBadGRE; /* bogus packets rec'd (bad GRE hdr) */ + u_int32_t recvBadAcks; /* bogus ack's rec'd in GRE header */ + u_int32_t recvBadCID; /* pkts with unknown call ID rec'd */ + u_int32_t recvOutOfOrder; /* packets rec'd out of order */ + u_int32_t recvDuplicates; /* packets rec'd with duplicate seq # */ + u_int32_t recvLoneAcks; /* ack-only packets rec'd */ + u_int32_t recvAckTimeouts; /* times peer failed to ack in time */ + u_int32_t memoryFailures; /* times we couldn't allocate memory */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_PPTPGRE_STATS_TYPE_INFO { \ + { "xmitPackets", &ng_parse_uint32_type }, \ + { "xmitOctets", &ng_parse_uint32_type }, \ + { "xmitLoneAcks", &ng_parse_uint32_type }, \ + { "xmitDrops", &ng_parse_uint32_type }, \ + { "xmitTooBig", &ng_parse_uint32_type }, \ + { "recvPackets", &ng_parse_uint32_type }, \ + { "recvOctets", &ng_parse_uint32_type }, \ + { "recvRunts", &ng_parse_uint32_type }, \ + { "recvBadGRE", &ng_parse_uint32_type }, \ + { "recvBadAcks", &ng_parse_uint32_type }, \ + { "recvBadCID", &ng_parse_uint32_type }, \ + { "recvOutOfOrder", &ng_parse_uint32_type }, \ + { "recvDuplicates", &ng_parse_uint32_type }, \ + { "recvLoneAcks", &ng_parse_uint32_type }, \ + { "recvAckTimeouts", &ng_parse_uint32_type }, \ + { "memoryFailures", &ng_parse_uint32_type }, \ + { NULL } \ +} + +/* Netgraph commands */ +enum { + NGM_PPTPGRE_SET_CONFIG = 1, /* supply a struct ng_pptpgre_conf */ + NGM_PPTPGRE_GET_CONFIG, /* returns a struct ng_pptpgre_conf */ + NGM_PPTPGRE_GET_STATS, /* returns struct ng_pptpgre_stats */ + NGM_PPTPGRE_CLR_STATS, /* clears stats */ + NGM_PPTPGRE_GETCLR_STATS, /* returns & clears stats */ +}; + +#endif /* _NETGRAPH_NG_PPTPGRE_H_ */ diff --git a/sys/netgraph7/ng_pred1.c b/sys/netgraph7/ng_pred1.c new file mode 100644 index 0000000000..8d6cc7dddf --- /dev/null +++ b/sys/netgraph7/ng_pred1.c @@ -0,0 +1,698 @@ +/*- + * Copyright (c) 2006 Alexander Motin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_pred1.c,v 1.3 2008/01/27 02:04:12 mav Exp $ + */ + +/* + * Predictor-1 PPP compression netgraph node type. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "opt_netgraph.h" + +MALLOC_DEFINE(M_NETGRAPH_PRED1, "netgraph_pred1", "netgraph pred1 node "); + +/* PRED1 header length */ +#define PRED1_HDRLEN 2 + +#define PRED1_TABLE_SIZE 0x10000 +#define PRED1_BUF_SIZE 4096 +#define PPP_INITFCS 0xffff /* Initial FCS value */ +#define PPP_GOODFCS 0xf0b8 /* Good final FCS value */ + +/* + * The following hash code is the heart of the algorithm: + * it builds a sliding hash sum of the previous 3-and-a-bit + * characters which will be used to index the guess table. + * A better hash function would result in additional compression, + * at the expense of time. + */ + +#define HASH(x) priv->Hash = (priv->Hash << 4) ^ (x) + +/* Node private data */ +struct ng_pred1_private { + struct ng_pred1_config cfg; /* configuration */ + u_char GuessTable[PRED1_TABLE_SIZE]; /* dictionary */ + u_char inbuf[PRED1_BUF_SIZE]; /* input buffer */ + u_char outbuf[PRED1_BUF_SIZE]; /* output buffer */ + struct ng_pred1_stats stats; /* statistics */ + uint16_t Hash; + ng_ID_t ctrlnode; /* path to controlling node */ + uint16_t seqnum; /* sequence number */ + u_char compress; /* compress/decompress flag */ +}; +typedef struct ng_pred1_private *priv_p; + +/* Netgraph node methods */ +static ng_constructor_t ng_pred1_constructor; +static ng_rcvmsg_t ng_pred1_rcvmsg; +static ng_shutdown_t ng_pred1_shutdown; +static ng_newhook_t ng_pred1_newhook; +static ng_rcvdata_t ng_pred1_rcvdata; +static ng_disconnect_t ng_pred1_disconnect; + +/* Helper functions */ +static int ng_pred1_compress(node_p node, struct mbuf *m, + struct mbuf **resultp); +static int ng_pred1_decompress(node_p node, struct mbuf *m, + struct mbuf **resultp); +static void Pred1Init(node_p node); +static int Pred1Compress(node_p node, u_char *source, u_char *dest, + int len); +static int Pred1Decompress(node_p node, u_char *source, u_char *dest, + int slen, int dlen); +static void Pred1SyncTable(node_p node, u_char *source, int len); +static uint16_t Crc16(uint16_t fcs, u_char *cp, int len); + +static const uint16_t Crc16Table[]; + +/* Parse type for struct ng_pred1_config. */ +static const struct ng_parse_struct_field ng_pred1_config_type_fields[] + = NG_PRED1_CONFIG_INFO; +static const struct ng_parse_type ng_pred1_config_type = { + &ng_parse_struct_type, + ng_pred1_config_type_fields +}; + +/* Parse type for struct ng_pred1_stat. */ +static const struct ng_parse_struct_field ng_pred1_stats_type_fields[] + = NG_PRED1_STATS_INFO; +static const struct ng_parse_type ng_pred1_stat_type = { + &ng_parse_struct_type, + ng_pred1_stats_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII. */ +static const struct ng_cmdlist ng_pred1_cmds[] = { + { + NGM_PRED1_COOKIE, + NGM_PRED1_CONFIG, + "config", + &ng_pred1_config_type, + NULL + }, + { + NGM_PRED1_COOKIE, + NGM_PRED1_RESETREQ, + "resetreq", + NULL, + NULL + }, + { + NGM_PRED1_COOKIE, + NGM_PRED1_GET_STATS, + "getstats", + NULL, + &ng_pred1_stat_type + }, + { + NGM_PRED1_COOKIE, + NGM_PRED1_CLR_STATS, + "clrstats", + NULL, + NULL + }, + { + NGM_PRED1_COOKIE, + NGM_PRED1_GETCLR_STATS, + "getclrstats", + NULL, + &ng_pred1_stat_type + }, + { 0 } +}; + +/* Node type descriptor */ +static struct ng_type ng_pred1_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_PRED1_NODE_TYPE, + .constructor = ng_pred1_constructor, + .rcvmsg = ng_pred1_rcvmsg, + .shutdown = ng_pred1_shutdown, + .newhook = ng_pred1_newhook, + .rcvdata = ng_pred1_rcvdata, + .disconnect = ng_pred1_disconnect, + .cmdlist = ng_pred1_cmds, +}; +NETGRAPH_INIT(pred1, &ng_pred1_typestruct); + +#define ERROUT(x) do { error = (x); goto done; } while (0) + +/************************************************************************ + NETGRAPH NODE STUFF + ************************************************************************/ + +/* + * Node type constructor + */ +static int +ng_pred1_constructor(node_p node) +{ + priv_p priv; + + /* Allocate private structure. */ + priv = malloc(sizeof(*priv), M_NETGRAPH_PRED1, M_WAITOK | M_ZERO); + + NG_NODE_SET_PRIVATE(node, priv); + + /* This node is not thread safe. */ + NG_NODE_FORCE_WRITER(node); + + /* Done */ + return (0); +} + +/* + * Give our OK for a hook to be added. + */ +static int +ng_pred1_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (NG_NODE_NUMHOOKS(node) > 0) + return (EINVAL); + + if (strcmp(name, NG_PRED1_HOOK_COMP) == 0) + priv->compress = 1; + else if (strcmp(name, NG_PRED1_HOOK_DECOMP) == 0) + priv->compress = 0; + else + return (EINVAL); + + return (0); +} + +/* + * Receive a control message. + */ +static int +ng_pred1_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + + if (msg->header.typecookie != NGM_PRED1_COOKIE) + ERROUT(EINVAL); + + switch (msg->header.cmd) { + case NGM_PRED1_CONFIG: + { + struct ng_pred1_config *const cfg = + (struct ng_pred1_config *)msg->data; + + /* Check configuration. */ + if (msg->header.arglen != sizeof(*cfg)) + ERROUT(EINVAL); + + /* Configuration is OK, reset to it. */ + priv->cfg = *cfg; + + /* Save return address so we can send reset-req's. */ + priv->ctrlnode = NGI_RETADDR(item); + + /* Clear our state. */ + Pred1Init(node); + + break; + } + case NGM_PRED1_RESETREQ: + Pred1Init(node); + break; + + case NGM_PRED1_GET_STATS: + case NGM_PRED1_CLR_STATS: + case NGM_PRED1_GETCLR_STATS: + { + /* Create response. */ + if (msg->header.cmd != NGM_PRED1_CLR_STATS) { + NG_MKRESPONSE(resp, msg, + sizeof(struct ng_pred1_stats), M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + bcopy(&priv->stats, resp->data, + sizeof(struct ng_pred1_stats)); + } + + if (msg->header.cmd != NGM_PRED1_GET_STATS) + bzero(&priv->stats, sizeof(struct ng_pred1_stats)); + break; + } + + default: + error = EINVAL; + break; + } +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive incoming data on our hook. + */ +static int +ng_pred1_rcvdata(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + struct mbuf *m, *out; + int error; + + if (!priv->cfg.enable) { + NG_FREE_ITEM(item); + return (ENXIO); + } + + NGI_GET_M(item, m); + /* Compress. */ + if (priv->compress) { + if ((error = ng_pred1_compress(node, m, &out)) != 0) { + NG_FREE_ITEM(item); + log(LOG_NOTICE, "%s: error: %d\n", __func__, error); + return (error); + } + + } else { /* Decompress. */ + if ((error = ng_pred1_decompress(node, m, &out)) != 0) { + NG_FREE_ITEM(item); + log(LOG_NOTICE, "%s: error: %d\n", __func__, error); + if (priv->ctrlnode != 0) { + struct ng_mesg *msg; + + /* Need to send a reset-request. */ + NG_MKMESSAGE(msg, NGM_PRED1_COOKIE, + NGM_PRED1_RESETREQ, 0, M_NOWAIT); + if (msg == NULL) + return (error); + NG_SEND_MSG_ID(error, node, msg, + priv->ctrlnode, 0); + } + return (error); + } + } + + NG_FWD_NEW_DATA(error, item, hook, out); + return (error); +} + +/* + * Destroy node. + */ +static int +ng_pred1_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + free(priv, M_NETGRAPH_PRED1); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); /* Let the node escape. */ + return (0); +} + +/* + * Hook disconnection + */ +static int +ng_pred1_disconnect(hook_p hook) +{ + const node_p node = NG_HOOK_NODE(hook); + + Pred1Init(node); + + /* Go away if no longer connected. */ + if ((NG_NODE_NUMHOOKS(node) == 0) && NG_NODE_IS_VALID(node)) + ng_rmnode_self(node); + return (0); +} + +/************************************************************************ + HELPER STUFF + ************************************************************************/ + +/* + * Compress/encrypt a packet and put the result in a new mbuf at *resultp. + * The original mbuf is not free'd. + */ +static int +ng_pred1_compress(node_p node, struct mbuf *m, struct mbuf **resultp) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + int outlen, inlen; + u_char *out; + uint16_t fcs, lenn; + int len; + + /* Initialize. */ + *resultp = NULL; + + inlen = m->m_pkthdr.len; + + priv->stats.FramesPlain++; + priv->stats.InOctets += inlen; + + /* Reserve space for expansion. */ + if (inlen > (PRED1_BUF_SIZE*8/9 + 1 + 4)) { + priv->stats.Errors++; + NG_FREE_M(m); + return (ENOMEM); + } + + /* Work with contiguous regions of memory. */ + m_copydata(m, 0, inlen, (caddr_t)(priv->inbuf + 2)); + + NG_FREE_M(m); + + lenn = htons(inlen & 0x7FFF); + + /* Compute FCS. */ + fcs = Crc16(PPP_INITFCS, (u_char *)&lenn, 2); + fcs = Crc16(fcs, priv->inbuf + 2, inlen); + fcs = ~fcs; + + /* Compress data. */ + len = Pred1Compress(node, priv->inbuf + 2, priv->outbuf + 2, inlen); + + /* What happened? */ + if (len < inlen) { + out = priv->outbuf; + outlen = 2 + len; + *(uint16_t *)out = lenn; + *out |= 0x80; + priv->stats.FramesComp++; + } else { + out = priv->inbuf; + outlen = 2 + inlen; + *(uint16_t *)out = lenn; + priv->stats.FramesUncomp++; + } + + /* Add FCS. */ + (out + outlen)[0] = fcs & 0xFF; + (out + outlen)[1] = fcs >> 8; + + /* Calculate resulting size. */ + outlen += 2; + + /* Return packet in an mbuf. */ + *resultp = m_devget((caddr_t)out, outlen, 0, NULL, NULL); + if (*resultp == NULL) { + priv->stats.Errors++; + return (ENOMEM); + }; + + priv->stats.OutOctets += outlen; + + return (0); +} + +/* + * Decompress/decrypt packet and put the result in a new mbuf at *resultp. + * The original mbuf is not free'd. + */ +static int +ng_pred1_decompress(node_p node, struct mbuf *m, struct mbuf **resultp) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + int inlen; + uint16_t len, len1, cf, lenn; + uint16_t fcs; + + /* Initialize. */ + *resultp = NULL; + + inlen = m->m_pkthdr.len; + + if (inlen > PRED1_BUF_SIZE) { + priv->stats.Errors++; + NG_FREE_M(m); + return (ENOMEM); + } + + /* Work with contiguous regions of memory. */ + m_copydata(m, 0, inlen, (caddr_t)priv->inbuf); + + priv->stats.InOctets += inlen; + + /* Get initial length value. */ + len = priv->inbuf[0] << 8; + len += priv->inbuf[1]; + + cf = (len & 0x8000); + len &= 0x7fff; + + /* Is data compressed or not really? */ + if (cf) { + NG_FREE_M(m); + + priv->stats.FramesComp++; + len1 = Pred1Decompress(node, priv->inbuf + 2, priv->outbuf, + inlen - 4, PRED1_BUF_SIZE); + if (len != len1) { + /* Error is detected. Send reset request */ + priv->stats.Errors++; + log(LOG_NOTICE, "ng_pred1: Comp length error (%d) " + "--> len (%d)\n", len, len1); + return (EIO); + } + + /* + * CRC check on receive is defined in RFC. It is surely required + * for compressed frames to signal dictionary corruption, + * but it is actually useless for uncompressed frames because + * the same check has already done by HDLC and/or other layer. + */ + lenn = htons(len); + fcs = Crc16(PPP_INITFCS, (u_char *)&lenn, 2); + fcs = Crc16(fcs, priv->outbuf, len); + fcs = Crc16(fcs, priv->inbuf + inlen - 2, 2); + + if (fcs != PPP_GOODFCS) { + priv->stats.Errors++; + log(LOG_NOTICE, "ng_pred1: Pred1: Bad CRC-16\n"); + return (EIO); + } + + /* Return packet in an mbuf. */ + *resultp = m_devget((caddr_t)priv->outbuf, len, 0, NULL, NULL); + if (*resultp == NULL) { + priv->stats.Errors++; + return (ENOMEM); + }; + + } else { + priv->stats.FramesUncomp++; + if (len != (inlen - 4)) { + /* Wrong length. Send reset request */ + priv->stats.Errors++; + log(LOG_NOTICE, "ng_pred1: Uncomp length error (%d) " + "--> len (%d)\n", len, inlen - 4); + NG_FREE_M(m); + return (EIO); + } + Pred1SyncTable(node, priv->inbuf + 2, len); + m_adj(m, 2); /* Strip length. */ + m_adj(m, -2); /* Strip fcs. */ + *resultp = m; + } + + priv->stats.FramesPlain++; + priv->stats.OutOctets += len; + + return (0); +} + +/* + * Pred1Init() + */ + +static void +Pred1Init(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + priv->Hash = 0; + memset(priv->GuessTable, 0, PRED1_TABLE_SIZE); +} + +/* + * Pred1Compress() + */ + +static int +Pred1Compress(node_p node, u_char *source, u_char *dest, int len) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + int i; + u_char flags; + u_char *flagdest, *orgdest; + + orgdest = dest; + while (len) { + flagdest = dest++; + flags = 0; /* All guesses are wrong initially. */ + for (i = 0; i < 8 && len; i++) { + if (priv->GuessTable[priv->Hash] == *source) + /* Guess was right - don't output. */ + flags |= (1 << i); + else { + /* Guess wrong, output char. */ + priv->GuessTable[priv->Hash] = *source; + *dest++ = *source; + } + HASH(*source++); + len--; + } + *flagdest = flags; + } + return (dest - orgdest); +} + +/* + * Pred1Decompress() + * + * Returns decompressed size, or -1 if we ran out of space. + */ + +static int +Pred1Decompress(node_p node, u_char *source, u_char *dest, int slen, int dlen) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + int i; + u_char flags, *orgdest; + + orgdest = dest; + while (slen) { + flags = *source++; + slen--; + for (i = 0; i < 8; i++, flags >>= 1) { + if (dlen <= 0) + return(-1); + if (flags & 0x01) + /* Guess correct */ + *dest = priv->GuessTable[priv->Hash]; + else { + if (!slen) + /* We seem to be really done -- cabo. */ + break; + + /* Guess wrong. */ + priv->GuessTable[priv->Hash] = *source; + /* Read from source. */ + *dest = *source++; + slen--; + } + HASH(*dest++); + dlen--; + } + } + return (dest - orgdest); +} + +/* + * Pred1SyncTable() + */ + +static void +Pred1SyncTable(node_p node, u_char *source, int len) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + while (len--) { + priv->GuessTable[priv->Hash] = *source; + HASH(*source++); + } +} + +/* + * Crc16() + * + * Compute the 16 bit frame check value, per RFC 1171 Appendix B, + * on an array of bytes. + */ + +static uint16_t +Crc16(uint16_t crc, u_char *cp, int len) +{ + while (len--) + crc = (crc >> 8) ^ Crc16Table[(crc ^ *cp++) & 0xff]; + return (crc); +} + +static const uint16_t Crc16Table[256] = { +/* 00 */ 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, +/* 08 */ 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, +/* 10 */ 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, +/* 18 */ 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, +/* 20 */ 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, +/* 28 */ 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, +/* 30 */ 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, +/* 38 */ 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, +/* 40 */ 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, +/* 48 */ 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, +/* 50 */ 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, +/* 58 */ 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, +/* 60 */ 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, +/* 68 */ 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, +/* 70 */ 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, +/* 78 */ 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, +/* 80 */ 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, +/* 88 */ 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, +/* 90 */ 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, +/* 98 */ 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, +/* a0 */ 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, +/* a8 */ 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, +/* b0 */ 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, +/* b8 */ 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, +/* c0 */ 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, +/* c8 */ 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, +/* d0 */ 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, +/* d8 */ 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, +/* e0 */ 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, +/* e8 */ 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, +/* f0 */ 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, +/* f8 */ 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 +}; + diff --git a/sys/netgraph7/ng_pred1.h b/sys/netgraph7/ng_pred1.h new file mode 100644 index 0000000000..ae60d87c44 --- /dev/null +++ b/sys/netgraph7/ng_pred1.h @@ -0,0 +1,83 @@ +/*- + * Copyright (c) 2006 Alexander Motin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_pred1.h,v 1.1 2006/12/29 09:54:32 glebius Exp $ + */ + +#ifndef _NETGRAPH_NG_PRED1_H_ +#define _NETGRAPH_NG_PRED1_H_ + +/* Node type name and magic cookie */ +#define NG_PRED1_NODE_TYPE "pred1" +#define NGM_PRED1_COOKIE 1166902612 + +/* Hook names */ +#define NG_PRED1_HOOK_COMP "comp" /* compression hook */ +#define NG_PRED1_HOOK_DECOMP "decomp" /* decompression hook */ + +/* Config struct */ +struct ng_pred1_config { + u_char enable; /* node enabled */ +}; + +/* Keep this in sync with the above structure definition. */ +#define NG_PRED1_CONFIG_INFO { \ + { "enable", &ng_parse_uint8_type }, \ + { NULL } \ +} + +/* Statistics structure for one direction. */ +struct ng_pred1_stats { + uint64_t FramesPlain; + uint64_t FramesComp; + uint64_t FramesUncomp; + uint64_t InOctets; + uint64_t OutOctets; + uint64_t Errors; +}; + +/* Keep this in sync with the above structure definition. */ +#define NG_PRED1_STATS_INFO { \ + { "FramesPlain",&ng_parse_uint64_type }, \ + { "FramesComp", &ng_parse_uint64_type }, \ + { "FramesUncomp", &ng_parse_uint64_type }, \ + { "InOctets", &ng_parse_uint64_type }, \ + { "OutOctets", &ng_parse_uint64_type }, \ + { "Errors", &ng_parse_uint64_type }, \ + { NULL } \ +} + +/* Netgraph commands */ +enum { + NGM_PRED1_CONFIG = 1, + NGM_PRED1_RESETREQ, /* sent either way! */ + NGM_PRED1_GET_STATS, + NGM_PRED1_CLR_STATS, + NGM_PRED1_GETCLR_STATS, +}; + +#endif /* _NETGRAPH_NG_PRED1_H_ */ + diff --git a/sys/netgraph7/ng_rfc1490.c b/sys/netgraph7/ng_rfc1490.c new file mode 100644 index 0000000000..20c46bed43 --- /dev/null +++ b/sys/netgraph7/ng_rfc1490.c @@ -0,0 +1,491 @@ +/* + * ng_rfc1490.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_rfc1490.c,v 1.24 2005/01/07 01:45:39 imp Exp $ + * $Whistle: ng_rfc1490.c,v 1.22 1999/11/01 09:24:52 julian Exp $ + */ + +/* + * This node does RFC 1490 multiplexing. + * + * NOTE: RFC 1490 is updated by RFC 2427. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +/* + * DEFINITIONS + */ + +/* Q.922 stuff -- see RFC 1490 */ +#define HDLC_UI 0x03 + +#define NLPID_IP 0xCC +#define NLPID_PPP 0xCF +#define NLPID_SNAP 0x80 +#define NLPID_Q933 0x08 +#define NLPID_CLNP 0x81 +#define NLPID_ESIS 0x82 +#define NLPID_ISIS 0x83 + +#define ERROUT(x) do { error = (x); goto done; } while (0) + +/* Encapsulation methods we understand */ +enum { + NG_RFC1490_ENCAP_IETF_IP = 1, /* see RFC2427, chapter 7, table 1 */ + NG_RFC1490_ENCAP_IETF_SNAP, /* see RFC2427, chapter 7, table 2 */ + NG_RFC1490_ENCAP_CISCO, /* Cisco's proprietary encapsulation */ +}; + +struct ng_rfc1490_encap_t { + u_int8_t method; + const char *name; +}; + +static const struct ng_rfc1490_encap_t ng_rfc1490_encaps[] = { + { NG_RFC1490_ENCAP_IETF_IP, "ietf-ip" }, + { NG_RFC1490_ENCAP_IETF_SNAP, "ietf-snap" }, + { NG_RFC1490_ENCAP_CISCO, "cisco" }, + { 0, NULL}, +}; + +/* Node private data */ +struct ng_rfc1490_private { + hook_p downlink; + hook_p ppp; + hook_p inet; + hook_p ethernet; + const struct ng_rfc1490_encap_t *enc; +}; +typedef struct ng_rfc1490_private *priv_p; + +/* Netgraph node methods */ +static ng_constructor_t ng_rfc1490_constructor; +static ng_rcvmsg_t ng_rfc1490_rcvmsg; +static ng_shutdown_t ng_rfc1490_shutdown; +static ng_newhook_t ng_rfc1490_newhook; +static ng_rcvdata_t ng_rfc1490_rcvdata; +static ng_disconnect_t ng_rfc1490_disconnect; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_rfc1490_cmds[] = { + { + NGM_RFC1490_COOKIE, + NGM_RFC1490_SET_ENCAP, + "setencap", + &ng_parse_string_type, + NULL + }, + { + NGM_RFC1490_COOKIE, + NGM_RFC1490_GET_ENCAP, + "getencap", + NULL, + &ng_parse_string_type + }, + { 0 } +}; + +/* Node type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_RFC1490_NODE_TYPE, + .constructor = ng_rfc1490_constructor, + .rcvmsg = ng_rfc1490_rcvmsg, + .shutdown = ng_rfc1490_shutdown, + .newhook = ng_rfc1490_newhook, + .rcvdata = ng_rfc1490_rcvdata, + .disconnect = ng_rfc1490_disconnect, + .cmdlist = ng_rfc1490_cmds, +}; +NETGRAPH_INIT(rfc1490, &typestruct); + +/************************************************************************ + NETGRAPH NODE STUFF + ************************************************************************/ + +/* + * Node constructor + */ +static int +ng_rfc1490_constructor(node_p node) +{ + priv_p priv; + + /* Allocate private structure */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + + /* Initialize to default encapsulation method - ietf-ip */ + priv->enc = ng_rfc1490_encaps; + + NG_NODE_SET_PRIVATE(node, priv); + + /* Done */ + return (0); +} + +/* + * Give our ok for a hook to be added + */ +static int +ng_rfc1490_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (!strcmp(name, NG_RFC1490_HOOK_DOWNSTREAM)) { + if (priv->downlink) + return (EISCONN); + priv->downlink = hook; + } else if (!strcmp(name, NG_RFC1490_HOOK_PPP)) { + if (priv->ppp) + return (EISCONN); + priv->ppp = hook; + } else if (!strcmp(name, NG_RFC1490_HOOK_INET)) { + if (priv->inet) + return (EISCONN); + priv->inet = hook; + } else if (!strcmp(name, NG_RFC1490_HOOK_ETHERNET)) { + if (priv->ethernet) + return (EISCONN); + priv->ethernet = hook; + } else + return (EINVAL); + return (0); +} + +/* + * Receive a control message. + */ +static int +ng_rfc1490_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *msg; + struct ng_mesg *resp = NULL; + int error = 0; + + NGI_GET_MSG(item, msg); + + if (msg->header.typecookie == NGM_RFC1490_COOKIE) { + switch (msg->header.cmd) { + case NGM_RFC1490_SET_ENCAP: + { + const struct ng_rfc1490_encap_t *enc; + char *s; + size_t len; + + if (msg->header.arglen == 0) + ERROUT(EINVAL); + + s = (char *)msg->data; + len = msg->header.arglen - 1; + + /* Search for matching encapsulation method */ + for (enc = ng_rfc1490_encaps; enc->method != 0; enc++ ) + if ((strlen(enc->name) == len) && + !strncmp(enc->name, s, len)) + break; /* found */ + + if (enc->method != 0) + priv->enc = enc; + else + error = EINVAL; + break; + } + case NGM_RFC1490_GET_ENCAP: + + NG_MKRESPONSE(resp, msg, strlen(priv->enc->name) + 1, M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + + strlcpy((char *)resp->data, priv->enc->name, + strlen(priv->enc->name) + 1); + break; + + default: + error = EINVAL; + break; + } + } else + error = EINVAL; + +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive data on a hook and encapsulate according to RFC 1490. + * Only those nodes marked (*) are supported by this routine so far. + * + * Q.922 control + * | + * | + * --------------------------------------------------------------------- + * | 0x03 | | + * UI I Frame Cisco + * | | Encapsulation + * --------------------------------- -------------- | + * | 0x08 | 0x81 |0xCC |0xCF | 0x00 |..01.... |..10.... -------------- + * | | | | | 0x80 | | |0x800 | + * Q.933 CLNP IP(*) PPP(*) SNAP ISO 8208 ISO 8208 | | + * | (rfc1973) | Modulo 8 Modulo 128 IP(*) Others + * | | + * -------------------- OUI + * | | | + * L2 ID L3 ID ------------------------- + * | User |00-80-C2 |00-00-00 + * | specified | | + * | 0x70 PID Ethertype + * | | | + * ------------------- -----------------... ---------- + * |0x51 |0x4E | |0x4C |0x7 |0xB | |0x806 | + * | | | | | | | | | + * 7776 Q.922 Others 802.2 802.3(*) 802.6 Others IP(*) Others + * + * + */ + +#define MAX_ENCAPS_HDR 8 +#define OUICMP(P,A,B,C) ((P)[0]==(A) && (P)[1]==(B) && (P)[2]==(C)) + +static int +ng_rfc1490_rcvdata(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + int error = 0; + struct mbuf *m; + + NGI_GET_M(item, m); + if (hook == priv->downlink) { + const u_char *start; + const u_char *ptr; + + if (m->m_len < MAX_ENCAPS_HDR + && !(m = m_pullup(m, MAX_ENCAPS_HDR))) + ERROUT(ENOBUFS); + ptr = start = mtod(m, const u_char *); + + if (priv->enc->method == NG_RFC1490_ENCAP_CISCO) + goto switch_on_etype; + + /* Must be UI frame */ + if (*ptr++ != HDLC_UI) + ERROUT(0); + + /* Eat optional zero pad byte */ + if (*ptr == 0x00) + ptr++; + + /* Multiplex on NLPID */ + switch (*ptr++) { + case NLPID_SNAP: + if (OUICMP(ptr, 0, 0, 0)) { /* It's an ethertype */ + u_int16_t etype; + + ptr += 3; +switch_on_etype: etype = ntohs(*((const u_int16_t *)ptr)); + ptr += 2; + m_adj(m, ptr - start); + switch (etype) { + case ETHERTYPE_IP: + NG_FWD_NEW_DATA(error, item, + priv->inet, m); + break; + case ETHERTYPE_ARP: + case ETHERTYPE_REVARP: + default: + ERROUT(0); + } + } else if (OUICMP(ptr, 0x00, 0x80, 0xc2)) { + /* 802.1 bridging */ + ptr += 3; + if (*ptr++ != 0x00) + ERROUT(0); /* unknown PID octet 0 */ + if (*ptr++ != 0x07) + ERROUT(0); /* not FCS-less 802.3 */ + m_adj(m, ptr - start); + NG_FWD_NEW_DATA(error, item, priv->ethernet, m); + } else /* Other weird stuff... */ + ERROUT(0); + break; + case NLPID_IP: + m_adj(m, ptr - start); + NG_FWD_NEW_DATA(error, item, priv->inet, m); + break; + case NLPID_PPP: + m_adj(m, ptr - start); + NG_FWD_NEW_DATA(error, item, priv->ppp, m); + break; + case NLPID_Q933: + case NLPID_CLNP: + case NLPID_ESIS: + case NLPID_ISIS: + ERROUT(0); + default: /* Try PPP (see RFC 1973) */ + ptr--; /* NLPID becomes PPP proto */ + if ((*ptr & 0x01) == 0x01) + ERROUT(0); + m_adj(m, ptr - start); + NG_FWD_NEW_DATA(error, item, priv->ppp, m); + break; + } + } else if (hook == priv->ppp) { + M_PREPEND(m, 2, M_DONTWAIT); /* Prepend PPP NLPID */ + if (!m) + ERROUT(ENOBUFS); + mtod(m, u_char *)[0] = HDLC_UI; + mtod(m, u_char *)[1] = NLPID_PPP; + NG_FWD_NEW_DATA(error, item, priv->downlink, m); + } else if (hook == priv->inet) { + switch (priv->enc->method) { + case NG_RFC1490_ENCAP_IETF_IP: + M_PREPEND(m, 2, M_DONTWAIT); /* Prepend IP NLPID */ + if (!m) + ERROUT(ENOBUFS); + mtod(m, u_char *)[0] = HDLC_UI; + mtod(m, u_char *)[1] = NLPID_IP; + break; + case NG_RFC1490_ENCAP_IETF_SNAP: + /* + * According to RFC2427 frame should begin with + * HDLC_UI PAD NLIPID OUI PID + * 03 00 80 00 00 00 08 00 + */ + M_PREPEND(m, 8, M_DONTWAIT); + if (!m) + ERROUT(ENOBUFS); + mtod(m, u_char *)[0] = HDLC_UI; + mtod(m, u_char *)[1] = 0x00; /* PAD */ + mtod(m, u_char *)[2] = NLPID_SNAP; + bzero((char *)(mtod(m, u_char *) + 3), 3); /* OUI 0-0-0 */ + *((u_int16_t *)mtod(m, u_int16_t *) + 6/sizeof(u_int16_t)) + = htons(ETHERTYPE_IP); /* PID */ + break; + case NG_RFC1490_ENCAP_CISCO: + M_PREPEND(m, 2, M_DONTWAIT); /* Prepend IP ethertype */ + if (!m) + ERROUT(ENOBUFS); + *((u_int16_t *)mtod(m, u_int16_t *)) = htons(ETHERTYPE_IP); + break; + } + NG_FWD_NEW_DATA(error, item, priv->downlink, m); + } else if (hook == priv->ethernet) { + M_PREPEND(m, 8, M_DONTWAIT); /* Prepend NLPID, OUI, PID */ + if (!m) + ERROUT(ENOBUFS); + mtod(m, u_char *)[0] = HDLC_UI; + mtod(m, u_char *)[1] = 0x00; /* pad */ + mtod(m, u_char *)[2] = NLPID_SNAP; + mtod(m, u_char *)[3] = 0x00; /* OUI */ + mtod(m, u_char *)[4] = 0x80; + mtod(m, u_char *)[5] = 0xc2; + mtod(m, u_char *)[6] = 0x00; /* PID */ + mtod(m, u_char *)[7] = 0x07; + NG_FWD_NEW_DATA(error, item, priv->downlink, m); + } else + panic(__func__); + +done: + if (item) + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (error); +} + +/* + * Nuke node + */ +static int +ng_rfc1490_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + /* Take down netgraph node */ + bzero(priv, sizeof(*priv)); + FREE(priv, M_NETGRAPH); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); /* let the node escape */ + return (0); +} + +/* + * Hook disconnection + */ +static int +ng_rfc1490_disconnect(hook_p hook) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) + ng_rmnode_self(NG_HOOK_NODE(hook)); + else if (hook == priv->downlink) + priv->downlink = NULL; + else if (hook == priv->inet) + priv->inet = NULL; + else if (hook == priv->ppp) + priv->ppp = NULL; + else if (hook == priv->ethernet) + priv->ethernet = NULL; + else + panic(__func__); + return (0); +} + diff --git a/sys/netgraph7/ng_rfc1490.h b/sys/netgraph7/ng_rfc1490.h new file mode 100644 index 0000000000..478e1dc0d1 --- /dev/null +++ b/sys/netgraph7/ng_rfc1490.h @@ -0,0 +1,63 @@ +/* + * ng_rfc1490.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_rfc1490.h,v 1.7 2005/01/07 01:45:39 imp Exp $ + * $Whistle: ng_rfc1490.h,v 1.7 1999/01/20 00:54:15 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_RFC1490_H_ +#define _NETGRAPH_NG_RFC1490_H_ + +/* Node type name */ +#define NG_RFC1490_NODE_TYPE "rfc1490" +#define NGM_RFC1490_COOKIE 1086947474 + +/* Hook names */ +#define NG_RFC1490_HOOK_DOWNSTREAM "downstream" +#define NG_RFC1490_HOOK_INET "inet" +#define NG_RFC1490_HOOK_PPP "ppp" +#define NG_RFC1490_HOOK_ETHERNET "ethernet" + +/* Netgraph commands */ +enum { + NGM_RFC1490_SET_ENCAP, /* sets encapsulation method */ + NGM_RFC1490_GET_ENCAP, /* gets current encapsulation method */ +}; + +#endif /* _NETGRAPH_NG_RFC1490_H_ */ diff --git a/sys/netgraph7/ng_sample.c b/sys/netgraph7/ng_sample.c new file mode 100644 index 0000000000..d78f7bcce9 --- /dev/null +++ b/sys/netgraph7/ng_sample.c @@ -0,0 +1,499 @@ +/* + * ng_sample.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_sample.c,v 1.30 2005/02/06 19:24:59 glebius Exp $ + * $Whistle: ng_sample.c,v 1.13 1999/11/01 09:24:52 julian Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* If you do complicated mallocs you may want to do this */ +/* and use it for your mallocs */ +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_XXX, "netgraph_xxx", "netgraph xxx node "); +#else +#define M_NETGRAPH_XXX M_NETGRAPH +#endif + +/* + * This section contains the netgraph method declarations for the + * sample node. These methods define the netgraph 'type'. + */ + +static ng_constructor_t ng_xxx_constructor; +static ng_rcvmsg_t ng_xxx_rcvmsg; +static ng_shutdown_t ng_xxx_shutdown; +static ng_newhook_t ng_xxx_newhook; +static ng_connect_t ng_xxx_connect; +static ng_rcvdata_t ng_xxx_rcvdata; +static ng_disconnect_t ng_xxx_disconnect; + +/* Parse type for struct ngxxxstat */ +static const struct ng_parse_struct_field ng_xxx_stat_type_fields[] + = NG_XXX_STATS_TYPE_INFO; +static const struct ng_parse_type ng_xxx_stat_type = { + &ng_parse_struct_type, + &ng_xxx_stat_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_xxx_cmdlist[] = { + { + NGM_XXX_COOKIE, + NGM_XXX_GET_STATUS, + "getstatus", + NULL, + &ng_xxx_stat_type, + }, + { + NGM_XXX_COOKIE, + NGM_XXX_SET_FLAG, + "setflag", + &ng_parse_int32_type, + NULL + }, + { 0 } +}; + +/* Netgraph node type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_XXX_NODE_TYPE, + .constructor = ng_xxx_constructor, + .rcvmsg = ng_xxx_rcvmsg, + .shutdown = ng_xxx_shutdown, + .newhook = ng_xxx_newhook, +/* .findhook = ng_xxx_findhook, */ + .connect = ng_xxx_connect, + .rcvdata = ng_xxx_rcvdata, + .disconnect = ng_xxx_disconnect, + .cmdlist = ng_xxx_cmdlist, +}; +NETGRAPH_INIT(xxx, &typestruct); + +/* Information we store for each hook on each node */ +struct XXX_hookinfo { + int dlci; /* The DLCI it represents, -1 == downstream */ + int channel; /* The channel representing this DLCI */ + hook_p hook; +}; + +/* Information we store for each node */ +struct XXX { + struct XXX_hookinfo channel[XXX_NUM_DLCIS]; + struct XXX_hookinfo downstream_hook; + node_p node; /* back pointer to node */ + hook_p debughook; + u_int packets_in; /* packets in from downstream */ + u_int packets_out; /* packets out towards downstream */ + u_int32_t flags; +}; +typedef struct XXX *xxx_p; + +/* + * Allocate the private data structure. The generic node has already + * been created. Link them together. We arrive with a reference to the node + * i.e. the reference count is incremented for us already. + * + * If this were a device node than this work would be done in the attach() + * routine and the constructor would return EINVAL as you should not be able + * to creatednodes that depend on hardware (unless you can add the hardware :) + */ +static int +ng_xxx_constructor(node_p node) +{ + xxx_p privdata; + int i; + + /* Initialize private descriptor */ + MALLOC(privdata, xxx_p, sizeof(*privdata), M_NETGRAPH, + M_NOWAIT | M_ZERO); + if (privdata == NULL) + return (ENOMEM); + for (i = 0; i < XXX_NUM_DLCIS; i++) { + privdata->channel[i].dlci = -2; + privdata->channel[i].channel = i; + } + + /* Link structs together; this counts as our one reference to *nodep */ + NG_NODE_SET_PRIVATE(node, privdata); + privdata->node = node; + return (0); +} + +/* + * Give our ok for a hook to be added... + * If we are not running this might kick a device into life. + * Possibly decode information out of the hook name. + * Add the hook's private info to the hook structure. + * (if we had some). In this example, we assume that there is a + * an array of structs, called 'channel' in the private info, + * one for each active channel. The private + * pointer of each hook points to the appropriate XXX_hookinfo struct + * so that the source of an input packet is easily identified. + * (a dlci is a frame relay channel) + */ +static int +ng_xxx_newhook(node_p node, hook_p hook, const char *name) +{ + const xxx_p xxxp = NG_NODE_PRIVATE(node); + const char *cp; + int dlci = 0; + int chan; + +#if 0 + /* Possibly start up the device if it's not already going */ + if ((xxxp->flags & SCF_RUNNING) == 0) { + ng_xxx_start_hardware(xxxp); + } +#endif + + /* Example of how one might use hooks with embedded numbers: All + * hooks start with 'dlci' and have a decimal trailing channel + * number up to 4 digits Use the leadin defined int he associated .h + * file. */ + if (strncmp(name, + NG_XXX_HOOK_DLCI_LEADIN, strlen(NG_XXX_HOOK_DLCI_LEADIN)) == 0) { + char *eptr; + + cp = name + strlen(NG_XXX_HOOK_DLCI_LEADIN); + if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) + return (EINVAL); + dlci = (int)strtoul(cp, &eptr, 10); + if (*eptr != '\0' || dlci < 0 || dlci > 1023) + return (EINVAL); + + /* We have a dlci, now either find it, or allocate it */ + for (chan = 0; chan < XXX_NUM_DLCIS; chan++) + if (xxxp->channel[chan].dlci == dlci) + break; + if (chan == XXX_NUM_DLCIS) { + for (chan = 0; chan < XXX_NUM_DLCIS; chan++) + if (xxxp->channel[chan].dlci == -2) + break; + if (chan == XXX_NUM_DLCIS) + return (ENOBUFS); + xxxp->channel[chan].dlci = dlci; + } + if (xxxp->channel[chan].hook != NULL) + return (EADDRINUSE); + NG_HOOK_SET_PRIVATE(hook, xxxp->channel + chan); + xxxp->channel[chan].hook = hook; + return (0); + } else if (strcmp(name, NG_XXX_HOOK_DOWNSTREAM) == 0) { + /* Example of simple predefined hooks. */ + /* do something specific to the downstream connection */ + xxxp->downstream_hook.hook = hook; + NG_HOOK_SET_PRIVATE(hook, &xxxp->downstream_hook); + } else if (strcmp(name, NG_XXX_HOOK_DEBUG) == 0) { + /* do something specific to a debug connection */ + xxxp->debughook = hook; + NG_HOOK_SET_PRIVATE(hook, NULL); + } else + return (EINVAL); /* not a hook we know about */ + return(0); +} + +/* + * Get a netgraph control message. + * We actually recieve a queue item that has a pointer to the message. + * If we free the item, the message will be freed too, unless we remove + * it from the item using NGI_GET_MSG(); + * The return address is also stored in the item, as an ng_ID_t, + * accessible as NGI_RETADDR(item); + * Check it is one we understand. If needed, send a response. + * We could save the address for an async action later, but don't here. + * Always free the message. + * The response should be in a malloc'd region that the caller can 'free'. + * A response is not required. + * Theoretically you could respond defferently to old message types if + * the cookie in the header didn't match what we consider to be current + * (so that old userland programs could continue to work). + */ +static int +ng_xxx_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const xxx_p xxxp = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + /* Deal with message according to cookie and command */ + switch (msg->header.typecookie) { + case NGM_XXX_COOKIE: + switch (msg->header.cmd) { + case NGM_XXX_GET_STATUS: + { + struct ngxxxstat *stats; + + NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); + if (!resp) { + error = ENOMEM; + break; + } + stats = (struct ngxxxstat *) resp->data; + stats->packets_in = xxxp->packets_in; + stats->packets_out = xxxp->packets_out; + break; + } + case NGM_XXX_SET_FLAG: + if (msg->header.arglen != sizeof(u_int32_t)) { + error = EINVAL; + break; + } + xxxp->flags = *((u_int32_t *) msg->data); + break; + default: + error = EINVAL; /* unknown command */ + break; + } + break; + default: + error = EINVAL; /* unknown cookie type */ + break; + } + + /* Take care of synchronous response, if any */ + NG_RESPOND_MSG(error, node, item, resp); + /* Free the message and return */ + NG_FREE_MSG(msg); + return(error); +} + +/* + * Receive data, and do something with it. + * Actually we receive a queue item which holds the data. + * If we free the item it will also free the data unless we have + * previously disassociated it using the NGI_GET_M() macro. + * Possibly send it out on another link after processing. + * Possibly do something different if it comes from different + * hooks. The caller will never free m, so if we use up this data or + * abort we must free it. + * + * If we want, we may decide to force this data to be queued and reprocessed + * at the netgraph NETISR time. + * We would do that by setting the HK_QUEUE flag on our hook. We would do that + * in the connect() method. + */ +static int +ng_xxx_rcvdata(hook_p hook, item_p item ) +{ + const xxx_p xxxp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + int chan = -2; + int dlci = -2; + int error; + struct mbuf *m; + + NGI_GET_M(item, m); + if (NG_HOOK_PRIVATE(hook)) { + dlci = ((struct XXX_hookinfo *) NG_HOOK_PRIVATE(hook))->dlci; + chan = ((struct XXX_hookinfo *) NG_HOOK_PRIVATE(hook))->channel; + if (dlci != -1) { + /* If received on a DLCI hook process for this + * channel and pass it to the downstream module. + * Normally one would add a multiplexing header at + * the front here */ + /* M_PREPEND(....) ; */ + /* mtod(m, xxxxxx)->dlci = dlci; */ + NG_FWD_NEW_DATA(error, item, + xxxp->downstream_hook.hook, m); + xxxp->packets_out++; + } else { + /* data came from the multiplexed link */ + dlci = 1; /* get dlci from header */ + /* madjust(....) *//* chop off header */ + for (chan = 0; chan < XXX_NUM_DLCIS; chan++) + if (xxxp->channel[chan].dlci == dlci) + break; + if (chan == XXX_NUM_DLCIS) { + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (ENETUNREACH); + } + /* If we were called at splnet, use the following: + * NG_SEND_DATA_ONLY(error, otherhook, m); if this + * node is running at some SPL other than SPLNET + * then you should use instead: error = + * ng_queueit(otherhook, m, NULL); m = NULL; + * This queues the data using the standard NETISR + * system and schedules the data to be picked + * up again once the system has moved to SPLNET and + * the processing of the data can continue. After + * these are run 'm' should be considered + * as invalid and NG_SEND_DATA actually zaps them. */ + NG_FWD_NEW_DATA(error, item, + xxxp->channel[chan].hook, m); + xxxp->packets_in++; + } + } else { + /* It's the debug hook, throw it away.. */ + if (hook == xxxp->downstream_hook.hook) { + NG_FREE_ITEM(item); + NG_FREE_M(m); + } + } + return 0; +} + +#if 0 +/* + * If this were a device node, the data may have been received in response + * to some interrupt. + * in which case it would probably look as follows: + */ +devintr() +{ + int error; + + /* get packet from device and send on */ + m = MGET(blah blah) + + NG_SEND_DATA_ONLY(error, xxxp->upstream_hook.hook, m); + /* see note above in xxx_rcvdata() */ + /* and ng_xxx_connect() */ +} + +#endif /* 0 */ + +/* + * Do local shutdown processing.. + * All our links and the name have already been removed. + * If we are a persistant device, we might refuse to go away. + * In the case of a persistant node we signal the framework that we + * are still in business by clearing the NGF_INVALID bit. However + * If we find the NGF_REALLY_DIE bit set, this means that + * we REALLY need to die (e.g. hardware removed). + * This would have been set using the NG_NODE_REALLY_DIE(node) + * macro in some device dependent function (not shown here) before + * calling ng_rmnode_self(). + */ +static int +ng_xxx_shutdown(node_p node) +{ + const xxx_p privdata = NG_NODE_PRIVATE(node); + +#ifndef PERSISTANT_NODE + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + FREE(privdata, M_NETGRAPH); +#else + if (node->nd_flags & NGF_REALLY_DIE) { + /* + * WE came here because the widget card is being unloaded, + * so stop being persistant. + * Actually undo all the things we did on creation. + */ + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(privdata->node); + FREE(privdata, M_NETGRAPH); + return (0); + } + NG_NODE_REVIVE(node); /* tell ng_rmnode() we will persist */ +#endif /* PERSISTANT_NODE */ + return (0); +} + +/* + * This is called once we've already connected a new hook to the other node. + * It gives us a chance to balk at the last minute. + */ +static int +ng_xxx_connect(hook_p hook) +{ +#if 0 + /* + * If we were a driver running at other than splnet then + * we should set the QUEUE bit on the edge so that we + * will deliver by queing. + */ + if /*it is the upstream hook */ + NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); +#endif +#if 0 + /* + * If for some reason we want incoming date to be queued + * by the NETISR system and delivered later we can set the same bit on + * OUR hook. (maybe to allow unwinding of the stack) + */ + + if (NG_HOOK_PRIVATE(hook)) { + int dlci; + /* + * If it's dlci 1023, requeue it so that it's handled + * at a lower priority. This is how a node decides to + * defer a data message. + */ + dlci = ((struct XXX_hookinfo *) NG_HOOK_PRIVATE(hook))->dlci; + if (dlci == 1023) { + NG_HOOK_FORCE_QUEUE(hook); + } +#endif + /* otherwise be really amiable and just say "YUP that's OK by me! " */ + return (0); +} + +/* + * Hook disconnection + * + * For this type, removal of the last link destroys the node + */ +static int +ng_xxx_disconnect(hook_p hook) +{ + if (NG_HOOK_PRIVATE(hook)) + ((struct XXX_hookinfo *) (NG_HOOK_PRIVATE(hook)))->hook = NULL; + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) /* already shutting down? */ + ng_rmnode_self(NG_HOOK_NODE(hook)); + return (0); +} + diff --git a/sys/netgraph7/ng_sample.h b/sys/netgraph7/ng_sample.h new file mode 100644 index 0000000000..c7c13bd8b2 --- /dev/null +++ b/sys/netgraph7/ng_sample.h @@ -0,0 +1,89 @@ +/* + * ng_sample.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_sample.h,v 1.7 2005/01/07 01:45:39 imp Exp $ + * $Whistle: ng_sample.h,v 1.3 1999/01/20 00:22:14 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_SAMPLE_H_ +#define _NETGRAPH_NG_SAMPLE_H_ + +/* Node type name. This should be unique among all netgraph node types */ +#define NG_XXX_NODE_TYPE "sample" + +/* Node type cookie. Should also be unique. This value MUST change whenever + an incompatible change is made to this header file, to insure consistency. + The de facto method for generating cookies is to take the output of the + date command: date -u +'%s' */ +#define NGM_XXX_COOKIE 915491374 + +/* Number of active DLCI's we can handle */ +#define XXX_NUM_DLCIS 16 + +/* Hook names */ +#define NG_XXX_HOOK_DLCI_LEADIN "dlci" +#define NG_XXX_HOOK_DOWNSTREAM "downstream" +#define NG_XXX_HOOK_DEBUG "debug" + +/* Netgraph commands understood by this node type */ +enum { + NGM_XXX_SET_FLAG = 1, + NGM_XXX_GET_STATUS, +}; + +/* This structure is returned by the NGM_XXX_GET_STATUS command */ +struct ngxxxstat { + u_int32_t packets_in; /* packets in from downstream */ + u_int32_t packets_out; /* packets out towards downstream */ +}; + +/* + * This is used to define the 'parse type' for a struct ngxxxstat, which + * is bascially a description of how to convert a binary struct ngxxxstat + * to an ASCII string and back. See ng_parse.h for more info. + * + * This needs to be kept in sync with the above structure definition + */ +#define NG_XXX_STATS_TYPE_INFO { \ + { "packets_in", &ng_parse_uint32_type }, \ + { "packets_out", &ng_parse_uint32_type }, \ + { NULL } \ +} + +#endif /* _NETGRAPH_NG_SAMPLE_H_ */ diff --git a/sys/netgraph7/ng_socket.c b/sys/netgraph7/ng_socket.c new file mode 100644 index 0000000000..f8db8fa2a6 --- /dev/null +++ b/sys/netgraph7/ng_socket.c @@ -0,0 +1,1140 @@ +/* + * ng_socket.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_socket.c,v 1.85 2008/03/11 21:58:48 mav Exp $ + * $Whistle: ng_socket.c,v 1.28 1999/11/01 09:24:52 julian Exp $ + */ + +/* + * Netgraph socket nodes + * + * There are two types of netgraph sockets, control and data. + * Control sockets have a netgraph node, but data sockets are + * parasitic on control sockets, and have no node of their own. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef NOTYET +#include +#endif +#include +#include +#include +#include + +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_PATH, "netgraph_path", "netgraph path info "); +MALLOC_DEFINE(M_NETGRAPH_SOCK, "netgraph_sock", "netgraph socket info "); +#else +#define M_NETGRAPH_PATH M_NETGRAPH +#define M_NETGRAPH_SOCK M_NETGRAPH +#endif + +/* + * It's Ascii-art time! + * +-------------+ +-------------+ + * |socket (ctl)| |socket (data)| + * +-------------+ +-------------+ + * ^ ^ + * | | + * v v + * +-----------+ +-----------+ + * |pcb (ctl)| |pcb (data)| + * +-----------+ +-----------+ + * ^ ^ + * | | + * v v + * +--------------------------+ + * | Socket type private | + * | data | + * +--------------------------+ + * ^ + * | + * v + * +----------------+ + * | struct ng_node | + * +----------------+ + */ + +/* Netgraph node methods */ +static ng_constructor_t ngs_constructor; +static ng_rcvmsg_t ngs_rcvmsg; +static ng_shutdown_t ngs_shutdown; +static ng_newhook_t ngs_newhook; +static ng_connect_t ngs_connect; +static ng_rcvdata_t ngs_rcvdata; +static ng_disconnect_t ngs_disconnect; + +/* Internal methods */ +static int ng_attach_data(struct socket *so); +static int ng_attach_cntl(struct socket *so); +static int ng_attach_common(struct socket *so, int type); +static void ng_detach_common(struct ngpcb *pcbp, int type); +static void ng_socket_free_priv(struct ngsock *priv); +#ifdef NOTYET +static int ng_internalize(struct mbuf *m, struct thread *p); +#endif +static int ng_connect_data(struct sockaddr *nam, struct ngpcb *pcbp); +static int ng_bind(struct sockaddr *nam, struct ngpcb *pcbp); + +static int ngs_mod_event(module_t mod, int event, void *data); +static void ng_socket_item_applied(void *context, int error); + +/* Netgraph type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_SOCKET_NODE_TYPE, + .mod_event = ngs_mod_event, + .constructor = ngs_constructor, + .rcvmsg = ngs_rcvmsg, + .shutdown = ngs_shutdown, + .newhook = ngs_newhook, + .connect = ngs_connect, + .rcvdata = ngs_rcvdata, + .disconnect = ngs_disconnect, +}; +NETGRAPH_INIT_ORDERED(socket, &typestruct, SI_SUB_PROTO_DOMAIN, SI_ORDER_ANY); + +/* Buffer space */ +static u_long ngpdg_sendspace = 20 * 1024; /* really max datagram size */ +SYSCTL_INT(_net_graph, OID_AUTO, maxdgram, CTLFLAG_RW, + &ngpdg_sendspace , 0, "Maximum outgoing Netgraph datagram size"); +static u_long ngpdg_recvspace = 20 * 1024; +SYSCTL_INT(_net_graph, OID_AUTO, recvspace, CTLFLAG_RW, + &ngpdg_recvspace , 0, "Maximum space for incoming Netgraph datagrams"); + +#define sotongpcb(so) ((struct ngpcb *)(so)->so_pcb) + +/* If getting unexplained errors returned, set this to "kdb_enter("X"); */ +#ifndef TRAP_ERROR +#define TRAP_ERROR +#endif + +/*************************************************************** + Control sockets +***************************************************************/ + +static int +ngc_attach(struct socket *so, int proto, struct thread *td) +{ + struct ngpcb *const pcbp = sotongpcb(so); + int error; + + error = priv_check(td, PRIV_NETGRAPH_CONTROL); + if (error) + return (error); + if (pcbp != NULL) + return (EISCONN); + return (ng_attach_cntl(so)); +} + +static void +ngc_detach(struct socket *so) +{ + struct ngpcb *const pcbp = sotongpcb(so); + + KASSERT(pcbp != NULL, ("ngc_detach: pcbp == NULL")); + ng_detach_common(pcbp, NG_CONTROL); +} + +static int +ngc_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, + struct mbuf *control, struct thread *td) +{ + struct ngpcb *const pcbp = sotongpcb(so); + struct ngsock *const priv = NG_NODE_PRIVATE(pcbp->sockdata->node); + struct sockaddr_ng *const sap = (struct sockaddr_ng *) addr; + struct ng_mesg *msg; + struct mbuf *m0; + item_p item; + char *path = NULL; + int len, error = 0; + struct ng_apply_info apply; + +#ifdef NOTYET + if (control && (error = ng_internalize(control, td))) { + if (pcbp->sockdata == NULL) { + error = ENOTCONN; + goto release; + } + } +#else /* NOTYET */ + if (control) { + error = EINVAL; + goto release; + } +#endif /* NOTYET */ + + /* Require destination as there may be >= 1 hooks on this node. */ + if (addr == NULL) { + error = EDESTADDRREQ; + goto release; + } + + /* + * Allocate an expendable buffer for the path, chop off + * the sockaddr header, and make sure it's NUL terminated. + */ + len = sap->sg_len - 2; + path = malloc(len + 1, M_NETGRAPH_PATH, M_WAITOK); + bcopy(sap->sg_data, path, len); + path[len] = '\0'; + + /* + * Move the actual message out of mbufs into a linear buffer. + * Start by adding up the size of the data. (could use mh_len?) + */ + for (len = 0, m0 = m; m0 != NULL; m0 = m0->m_next) + len += m0->m_len; + + /* + * Move the data into a linear buffer as well. + * Messages are not delivered in mbufs. + */ + msg = malloc(len + 1, M_NETGRAPH_MSG, M_WAITOK); + m_copydata(m, 0, len, (char *)msg); + + if (msg->header.version != NG_VERSION) { + free(msg, M_NETGRAPH_MSG); + error = EINVAL; + goto release; + } + + /* + * Hack alert! + * We look into the message and if it mkpeers a node of unknown type, we + * try to load it. We need to do this now, in syscall thread, because if + * message gets queued and applied later we will get panic. + */ + if (msg->header.typecookie == NGM_GENERIC_COOKIE && + msg->header.cmd == NGM_MKPEER) { + struct ngm_mkpeer *const mkp = (struct ngm_mkpeer *) msg->data; + struct ng_type *type; + + if ((type = ng_findtype(mkp->type)) == NULL) { + char filename[NG_TYPESIZ + 3]; + int fileid; + + /* Not found, try to load it as a loadable module. */ + snprintf(filename, sizeof(filename), "ng_%s", + mkp->type); + error = kern_kldload(curthread, filename, &fileid); + if (error != 0) { + free(msg, M_NETGRAPH_MSG); + goto release; + } + + /* See if type has been loaded successfully. */ + if ((type = ng_findtype(mkp->type)) == NULL) { + free(msg, M_NETGRAPH_MSG); + (void)kern_kldunload(curthread, fileid, + LINKER_UNLOAD_NORMAL); + error = ENXIO; + goto release; + } + } + } + + item = ng_package_msg(msg, M_WAITOK); + if ((error = ng_address_path((pcbp->sockdata->node), item, path, 0)) + != 0) { +#ifdef TRACE_MESSAGES + printf("ng_address_path: errx=%d\n", error); +#endif + goto release; + } + +#ifdef TRACE_MESSAGES + printf("[%x]:<---------[socket]: c=<%d>cmd=%x(%s) f=%x #%d (%s)\n", + item->el_dest->nd_ID, + msg->header.typecookie, + msg->header.cmd, + msg->header.cmdstr, + msg->header.flags, + msg->header.token, + item->el_dest->nd_type->name); +#endif + SAVE_LINE(item); + /* + * We do not want to return from syscall until the item + * is processed by destination node. We register callback + * on the item, which will update priv->error when item + * was applied. + * If ng_snd_item() has queued item, we sleep until + * callback wakes us up. + */ + bzero(&apply, sizeof(apply)); + apply.apply = ng_socket_item_applied; + apply.context = priv; + item->apply = &apply; + priv->error = -1; + + error = ng_snd_item(item, 0); + + mtx_lock(&priv->mtx); + if (priv->error == -1) + msleep(priv, &priv->mtx, 0, "ngsock", 0); + mtx_unlock(&priv->mtx); + KASSERT(priv->error != -1, + ("ng_socket: priv->error wasn't updated")); + error = priv->error; + +release: + if (path != NULL) + free(path, M_NETGRAPH_PATH); + if (control != NULL) + m_freem(control); + if (m != NULL) + m_freem(m); + return (error); +} + +static int +ngc_bind(struct socket *so, struct sockaddr *nam, struct thread *td) +{ + struct ngpcb *const pcbp = sotongpcb(so); + + if (pcbp == 0) + return (EINVAL); + return (ng_bind(nam, pcbp)); +} + +static int +ngc_connect(struct socket *so, struct sockaddr *nam, struct thread *td) +{ + /* + * At this time refuse to do this.. it used to + * do something but it was undocumented and not used. + */ + printf("program tried to connect control socket to remote node\n"); + return (EINVAL); +} + +/*************************************************************** + Data sockets +***************************************************************/ + +static int +ngd_attach(struct socket *so, int proto, struct thread *td) +{ + struct ngpcb *const pcbp = sotongpcb(so); + + if (pcbp != NULL) + return (EISCONN); + return (ng_attach_data(so)); +} + +static void +ngd_detach(struct socket *so) +{ + struct ngpcb *const pcbp = sotongpcb(so); + + KASSERT(pcbp != NULL, ("ngd_detach: pcbp == NULL")); + ng_detach_common(pcbp, NG_DATA); +} + +static int +ngd_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, + struct mbuf *control, struct thread *td) +{ + struct ngpcb *const pcbp = sotongpcb(so); + struct sockaddr_ng *const sap = (struct sockaddr_ng *) addr; + int len, error; + hook_p hook = NULL; + char hookname[NG_HOOKSIZ]; + + if ((pcbp == NULL) || (control != NULL)) { + error = EINVAL; + goto release; + } + if (pcbp->sockdata == NULL) { + error = ENOTCONN; + goto release; + } + + if (sap == NULL) + len = 0; /* Make compiler happy. */ + else + len = sap->sg_len - 2; + + /* + * If the user used any of these ways to not specify an address + * then handle specially. + */ + if ((sap == NULL) || (len <= 0) || (*sap->sg_data == '\0')) { + if (NG_NODE_NUMHOOKS(pcbp->sockdata->node) != 1) { + error = EDESTADDRREQ; + goto release; + } + /* + * If exactly one hook exists, just use it. + * Special case to allow write(2) to work on an ng_socket. + */ + hook = LIST_FIRST(&pcbp->sockdata->node->nd_hooks); + } else { + if (len >= NG_HOOKSIZ) { + error = EINVAL; + goto release; + } + + /* + * chop off the sockaddr header, and make sure it's NUL + * terminated + */ + bcopy(sap->sg_data, hookname, len); + hookname[len] = '\0'; + + /* Find the correct hook from 'hookname' */ + hook = ng_findhook(pcbp->sockdata->node, hookname); + if (hook == NULL) { + error = EHOSTUNREACH; + goto release; + } + } + + /* Send data. */ + NG_SEND_DATA_FLAGS(error, hook, m, NG_WAITOK); + +release: + if (control != NULL) + m_freem(control); + if (m != NULL) + m_freem(m); + return (error); +} + +static int +ngd_connect(struct socket *so, struct sockaddr *nam, struct thread *td) +{ + struct ngpcb *const pcbp = sotongpcb(so); + + if (pcbp == 0) + return (EINVAL); + return (ng_connect_data(nam, pcbp)); +} + +/* + * Used for both data and control sockets + */ +static int +ng_getsockaddr(struct socket *so, struct sockaddr **addr) +{ + struct ngpcb *pcbp; + struct sockaddr_ng *sg; + int sg_len; + int error = 0; + + /* Why isn't sg_data a `char[1]' ? :-( */ + sg_len = sizeof(struct sockaddr_ng) - sizeof(sg->sg_data) + 1; + + pcbp = sotongpcb(so); + if ((pcbp == NULL) || (pcbp->sockdata == NULL)) + /* XXXGL: can this still happen? */ + return (EINVAL); + + mtx_lock(&pcbp->sockdata->mtx); + if (pcbp->sockdata->node != NULL) { + node_p node = pcbp->sockdata->node; + int namelen = 0; /* silence compiler! */ + + if (NG_NODE_HAS_NAME(node)) + sg_len += namelen = strlen(NG_NODE_NAME(node)); + + sg = malloc(sg_len, M_SONAME, M_WAITOK | M_ZERO); + + if (NG_NODE_HAS_NAME(node)) + bcopy(NG_NODE_NAME(node), sg->sg_data, namelen); + + sg->sg_len = sg_len; + sg->sg_family = AF_NETGRAPH; + *addr = (struct sockaddr *)sg; + mtx_unlock(&pcbp->sockdata->mtx); + } else { + mtx_unlock(&pcbp->sockdata->mtx); + error = EINVAL; + } + + return (error); +} + +/* + * Attach a socket to it's protocol specific partner. + * For a control socket, actually create a netgraph node and attach + * to it as well. + */ + +static int +ng_attach_cntl(struct socket *so) +{ + struct ngsock *priv; + struct ngpcb *pcbp; + int error; + + /* Allocate node private info */ + priv = malloc(sizeof(*priv), M_NETGRAPH_SOCK, M_WAITOK | M_ZERO); + + /* Setup protocol control block */ + if ((error = ng_attach_common(so, NG_CONTROL)) != 0) { + free(priv, M_NETGRAPH_SOCK); + return (error); + } + pcbp = sotongpcb(so); + + /* Link the pcb the private data. */ + priv->ctlsock = pcbp; + pcbp->sockdata = priv; + priv->refs++; + + /* Initialize mutex. */ + mtx_init(&priv->mtx, "ng_socket", NULL, MTX_DEF); + + /* Make the generic node components */ + if ((error = ng_make_node_common(&typestruct, &priv->node)) != 0) { + free(priv, M_NETGRAPH_SOCK); + ng_detach_common(pcbp, NG_CONTROL); + return (error); + } + + /* Link the node and the private data. */ + NG_NODE_SET_PRIVATE(priv->node, priv); + NG_NODE_REF(priv->node); + priv->refs++; + + return (0); +} + +static int +ng_attach_data(struct socket *so) +{ + return (ng_attach_common(so, NG_DATA)); +} + +/* + * Set up a socket protocol control block. + * This code is shared between control and data sockets. + */ +static int +ng_attach_common(struct socket *so, int type) +{ + struct ngpcb *pcbp; + int error; + + /* Standard socket setup stuff. */ + error = soreserve(so, ngpdg_sendspace, ngpdg_recvspace); + if (error) + return (error); + + /* Allocate the pcb. */ + pcbp = malloc(sizeof(struct ngpcb), M_PCB, M_WAITOK | M_ZERO); + pcbp->type = type; + + /* Link the pcb and the socket. */ + so->so_pcb = (caddr_t)pcbp; + pcbp->ng_socket = so; + + return (0); +} + +/* + * Disassociate the socket from it's protocol specific + * partner. If it's attached to a node's private data structure, + * then unlink from that too. If we were the last socket attached to it, + * then shut down the entire node. Shared code for control and data sockets. + */ +static void +ng_detach_common(struct ngpcb *pcbp, int which) +{ + struct ngsock *priv = pcbp->sockdata; + + if (priv != NULL) { + mtx_lock(&priv->mtx); + + switch (which) { + case NG_CONTROL: + priv->ctlsock = NULL; + break; + case NG_DATA: + priv->datasock = NULL; + break; + default: + panic(__func__); + } + pcbp->sockdata = NULL; + + ng_socket_free_priv(priv); + } + + pcbp->ng_socket->so_pcb = NULL; + free(pcbp, M_PCB); +} + +/* + * Remove a reference from node private data. + */ +static void +ng_socket_free_priv(struct ngsock *priv) +{ + mtx_assert(&priv->mtx, MA_OWNED); + + priv->refs--; + + if (priv->refs == 0) { + mtx_destroy(&priv->mtx); + free(priv, M_NETGRAPH_SOCK); + return; + } + + if ((priv->refs == 1) && (priv->node != NULL)) { + node_p node = priv->node; + + priv->node = NULL; + mtx_unlock(&priv->mtx); + NG_NODE_UNREF(node); + ng_rmnode_self(node); + } else + mtx_unlock(&priv->mtx); +} + +#ifdef NOTYET +/* + * File descriptors can be passed into an AF_NETGRAPH socket. + * Note, that file descriptors cannot be passed OUT. + * Only character device descriptors are accepted. + * Character devices are useful to connect a graph to a device, + * which after all is the purpose of this whole system. + */ +static int +ng_internalize(struct mbuf *control, struct thread *td) +{ + const struct cmsghdr *cm = mtod(control, const struct cmsghdr *); + struct file *fp; + struct vnode *vn; + int oldfds; + int fd; + + if (cm->cmsg_type != SCM_RIGHTS || cm->cmsg_level != SOL_SOCKET || + cm->cmsg_len != control->m_len) { + TRAP_ERROR; + return (EINVAL); + } + + /* Check there is only one FD. XXX what would more than one signify? */ + oldfds = ((caddr_t)cm + cm->cmsg_len - (caddr_t)data) / sizeof (int); + if (oldfds != 1) { + TRAP_ERROR; + return (EINVAL); + } + + /* Check that the FD given is legit. and change it to a pointer to a + * struct file. */ + fd = CMSG_DATA(cm); + if ((error = fget(td, fd, &fp)) != 0) + return (error); + + /* Depending on what kind of resource it is, act differently. For + * devices, we treat it as a file. For an AF_NETGRAPH socket, + * shortcut straight to the node. */ + switch (fp->f_type) { + case DTYPE_VNODE: + vn = fp->f_data; + if (vn && (vn->v_type == VCHR)) { + /* for a VCHR, actually reference the FILE */ + fhold(fp); + /* XXX then what :) */ + /* how to pass on to other modules? */ + } else { + fdrop(fp, td); + TRAP_ERROR; + return (EINVAL); + } + break; + default: + fdrop(fp, td); + TRAP_ERROR; + return (EINVAL); + } + fdrop(fp, td); + return (0); +} +#endif /* NOTYET */ + +/* + * Connect the data socket to a named control socket node. + */ +static int +ng_connect_data(struct sockaddr *nam, struct ngpcb *pcbp) +{ + struct sockaddr_ng *sap; + node_p farnode; + struct ngsock *priv; + int error; + item_p item; + + /* If we are already connected, don't do it again. */ + if (pcbp->sockdata != NULL) + return (EISCONN); + + /* + * Find the target (victim) and check it doesn't already have + * a data socket. Also check it is a 'socket' type node. + * Use ng_package_data() and ng_address_path() to do this. + */ + + sap = (struct sockaddr_ng *) nam; + /* The item will hold the node reference. */ + item = ng_package_data(NULL, NG_WAITOK); + + if ((error = ng_address_path(NULL, item, sap->sg_data, 0))) + return (error); /* item is freed on failure */ + + /* + * Extract node from item and free item. Remember we now have + * a reference on the node. The item holds it for us. + * when we free the item we release the reference. + */ + farnode = item->el_dest; /* shortcut */ + if (strcmp(farnode->nd_type->name, NG_SOCKET_NODE_TYPE) != 0) { + NG_FREE_ITEM(item); /* drop the reference to the node */ + return (EINVAL); + } + priv = NG_NODE_PRIVATE(farnode); + if (priv->datasock != NULL) { + NG_FREE_ITEM(item); /* drop the reference to the node */ + return (EADDRINUSE); + } + + /* + * Link the PCB and the private data struct. and note the extra + * reference. Drop the extra reference on the node. + */ + mtx_lock(&priv->mtx); + priv->datasock = pcbp; + pcbp->sockdata = priv; + priv->refs++; + mtx_unlock(&priv->mtx); + NG_FREE_ITEM(item); /* drop the reference to the node */ + return (0); +} + +/* + * Binding a socket means giving the corresponding node a name + */ +static int +ng_bind(struct sockaddr *nam, struct ngpcb *pcbp) +{ + struct ngsock *const priv = pcbp->sockdata; + struct sockaddr_ng *const sap = (struct sockaddr_ng *) nam; + + if (priv == NULL) { + TRAP_ERROR; + return (EINVAL); + } + if ((sap->sg_len < 4) || (sap->sg_len > (NG_NODESIZ + 2)) || + (sap->sg_data[0] == '\0') || + (sap->sg_data[sap->sg_len - 3] != '\0')) { + TRAP_ERROR; + return (EINVAL); + } + return (ng_name_node(priv->node, sap->sg_data)); +} + +/*************************************************************** + Netgraph node +***************************************************************/ + +/* + * You can only create new nodes from the socket end of things. + */ +static int +ngs_constructor(node_p nodep) +{ + return (EINVAL); +} + +/* + * We allow any hook to be connected to the node. + * There is no per-hook private information though. + */ +static int +ngs_newhook(node_p node, hook_p hook, const char *name) +{ + NG_HOOK_SET_PRIVATE(hook, NG_NODE_PRIVATE(node)); + return (0); +} + +/* + * If only one hook, allow read(2) and write(2) to work. + */ +static int +ngs_connect(hook_p hook) +{ + node_p node = NG_HOOK_NODE(hook); + struct ngsock *priv = NG_NODE_PRIVATE(node); + + if ((priv->datasock) && (priv->datasock->ng_socket)) { + if (NG_NODE_NUMHOOKS(node) == 1) + priv->datasock->ng_socket->so_state |= SS_ISCONNECTED; + else + priv->datasock->ng_socket->so_state &= ~SS_ISCONNECTED; + } + return (0); +} + +/* + * Incoming messages get passed up to the control socket. + * Unless they are for us specifically (socket_type) + */ +static int +ngs_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct ngsock *const priv = NG_NODE_PRIVATE(node); + struct ngpcb *const pcbp = priv->ctlsock; + struct socket *so; + struct sockaddr_ng addr; + struct ng_mesg *msg; + struct mbuf *m; + ng_ID_t retaddr = NGI_RETADDR(item); + int addrlen; + int error = 0; + + NGI_GET_MSG(item, msg); + NG_FREE_ITEM(item); + + /* + * Only allow mesgs to be passed if we have the control socket. + * Data sockets can only support the generic messages. + */ + if (pcbp == NULL) { + TRAP_ERROR; + NG_FREE_MSG(msg); + return (EINVAL); + } + so = pcbp->ng_socket; + +#ifdef TRACE_MESSAGES + printf("[%x]:---------->[socket]: c=<%d>cmd=%x(%s) f=%x #%d\n", + retaddr, + msg->header.typecookie, + msg->header.cmd, + msg->header.cmdstr, + msg->header.flags, + msg->header.token); +#endif + + if (msg->header.typecookie == NGM_SOCKET_COOKIE) { + switch (msg->header.cmd) { + case NGM_SOCK_CMD_NOLINGER: + priv->flags |= NGS_FLAG_NOLINGER; + break; + case NGM_SOCK_CMD_LINGER: + priv->flags &= ~NGS_FLAG_NOLINGER; + break; + default: + error = EINVAL; /* unknown command */ + } + /* Free the message and return. */ + NG_FREE_MSG(msg); + return (error); + } + + /* Get the return address into a sockaddr. */ + bzero(&addr, sizeof(addr)); + addr.sg_len = sizeof(addr); + addr.sg_family = AF_NETGRAPH; + addrlen = snprintf((char *)&addr.sg_data, sizeof(addr.sg_data), + "[%x]:", retaddr); + if (addrlen < 0 || addrlen > sizeof(addr.sg_data)) { + printf("%s: snprintf([%x]) failed - %d\n", __func__, retaddr, + addrlen); + NG_FREE_MSG(msg); + return (EINVAL); + } + + /* Copy the message itself into an mbuf chain. */ + m = m_devget((caddr_t)msg, sizeof(struct ng_mesg) + msg->header.arglen, + 0, NULL, NULL); + + /* + * Here we free the message. We need to do that + * regardless of whether we got mbufs. + */ + NG_FREE_MSG(msg); + + if (m == NULL) { + TRAP_ERROR; + return (ENOBUFS); + } + + /* Send it up to the socket. */ + if (sbappendaddr(&so->so_rcv, (struct sockaddr *)&addr, m, NULL) == 0) { + TRAP_ERROR; + m_freem(m); + return (ENOBUFS); + } + sorwakeup(so); + + return (error); +} + +/* + * Receive data on a hook + */ +static int +ngs_rcvdata(hook_p hook, item_p item) +{ + struct ngsock *const priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct ngpcb *const pcbp = priv->datasock; + struct socket *so; + struct sockaddr_ng *addr; + char *addrbuf[NG_HOOKSIZ + 4]; + int addrlen; + struct mbuf *m; + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + /* If there is no data socket, black-hole it. */ + if (pcbp == NULL) { + NG_FREE_M(m); + return (0); + } + so = pcbp->ng_socket; + + /* Get the return address into a sockaddr. */ + addrlen = strlen(NG_HOOK_NAME(hook)); /* <= NG_HOOKSIZ - 1 */ + addr = (struct sockaddr_ng *) addrbuf; + addr->sg_len = addrlen + 3; + addr->sg_family = AF_NETGRAPH; + bcopy(NG_HOOK_NAME(hook), addr->sg_data, addrlen); + addr->sg_data[addrlen] = '\0'; + + /* Try to tell the socket which hook it came in on. */ + if (sbappendaddr(&so->so_rcv, (struct sockaddr *)addr, m, NULL) == 0) { + m_freem(m); + TRAP_ERROR; + return (ENOBUFS); + } + sorwakeup(so); + return (0); +} + +/* + * Hook disconnection + * + * For this type, removal of the last link destroys the node + * if the NOLINGER flag is set. + */ +static int +ngs_disconnect(hook_p hook) +{ + node_p node = NG_HOOK_NODE(hook); + struct ngsock *const priv = NG_NODE_PRIVATE(node); + + if ((priv->datasock) && (priv->datasock->ng_socket)) { + if (NG_NODE_NUMHOOKS(node) == 1) + priv->datasock->ng_socket->so_state |= SS_ISCONNECTED; + else + priv->datasock->ng_socket->so_state &= ~SS_ISCONNECTED; + } + + if ((priv->flags & NGS_FLAG_NOLINGER) && + (NG_NODE_NUMHOOKS(node) == 0) && (NG_NODE_IS_VALID(node))) + ng_rmnode_self(node); + + return (0); +} + +/* + * Do local shutdown processing. + * In this case, that involves making sure the socket + * knows we should be shutting down. + */ +static int +ngs_shutdown(node_p node) +{ + struct ngsock *const priv = NG_NODE_PRIVATE(node); + struct ngpcb *const dpcbp = priv->datasock; + struct ngpcb *const pcbp = priv->ctlsock; + + if (dpcbp != NULL) + soisdisconnected(dpcbp->ng_socket); + + if (pcbp != NULL) + soisdisconnected(pcbp->ng_socket); + + mtx_lock(&priv->mtx); + priv->node = NULL; + NG_NODE_SET_PRIVATE(node, NULL); + ng_socket_free_priv(priv); + + NG_NODE_UNREF(node); + return (0); +} + +static void +ng_socket_item_applied(void *context, int error) +{ + struct ngsock *const priv = (struct ngsock *)context; + + mtx_lock(&priv->mtx); + priv->error = error; + wakeup(priv); + mtx_unlock(&priv->mtx); + +} + +static int +dummy_disconnect(struct socket *so) +{ + return (0); +} +/* + * Control and data socket type descriptors + * + * XXXRW: Perhaps _close should do something? + */ + +static struct pr_usrreqs ngc_usrreqs = { + .pru_abort = NULL, + .pru_attach = ngc_attach, + .pru_bind = ngc_bind, + .pru_connect = ngc_connect, + .pru_detach = ngc_detach, + .pru_disconnect = dummy_disconnect, + .pru_peeraddr = NULL, + .pru_send = ngc_send, + .pru_shutdown = NULL, + .pru_sockaddr = ng_getsockaddr, + .pru_close = NULL, +}; + +static struct pr_usrreqs ngd_usrreqs = { + .pru_abort = NULL, + .pru_attach = ngd_attach, + .pru_bind = NULL, + .pru_connect = ngd_connect, + .pru_detach = ngd_detach, + .pru_disconnect = dummy_disconnect, + .pru_peeraddr = NULL, + .pru_send = ngd_send, + .pru_shutdown = NULL, + .pru_sockaddr = ng_getsockaddr, + .pru_close = NULL, +}; + +/* + * Definitions of protocols supported in the NETGRAPH domain. + */ + +extern struct domain ngdomain; /* stop compiler warnings */ + +static struct protosw ngsw[] = { +{ + .pr_type = SOCK_DGRAM, + .pr_domain = &ngdomain, + .pr_protocol = NG_CONTROL, + .pr_flags = PR_ATOMIC | PR_ADDR /* | PR_RIGHTS */, + .pr_usrreqs = &ngc_usrreqs +}, +{ + .pr_type = SOCK_DGRAM, + .pr_domain = &ngdomain, + .pr_protocol = NG_DATA, + .pr_flags = PR_ATOMIC | PR_ADDR, + .pr_usrreqs = &ngd_usrreqs +} +}; + +struct domain ngdomain = { + .dom_family = AF_NETGRAPH, + .dom_name = "netgraph", + .dom_protosw = ngsw, + .dom_protoswNPROTOSW = &ngsw[sizeof(ngsw) / sizeof(ngsw[0])] +}; + +/* + * Handle loading and unloading for this node type. + * This is to handle auxiliary linkages (e.g protocol domain addition). + */ +static int +ngs_mod_event(module_t mod, int event, void *data) +{ + int error = 0; + + switch (event) { + case MOD_LOAD: + /* Register protocol domain. */ + net_add_domain(&ngdomain); + break; + case MOD_UNLOAD: +#ifdef NOTYET + /* Unregister protocol domain XXX can't do this yet.. */ + if ((error = net_rm_domain(&ngdomain)) != 0) + break; + else +#endif + error = EBUSY; + break; + default: + error = EOPNOTSUPP; + break; + } + return (error); +} + +SYSCTL_INT(_net_graph, OID_AUTO, family, CTLFLAG_RD, 0, AF_NETGRAPH, ""); +SYSCTL_NODE(_net_graph, OID_AUTO, data, CTLFLAG_RW, 0, "DATA"); +SYSCTL_INT(_net_graph_data, OID_AUTO, proto, CTLFLAG_RD, 0, NG_DATA, ""); +SYSCTL_NODE(_net_graph, OID_AUTO, control, CTLFLAG_RW, 0, "CONTROL"); +SYSCTL_INT(_net_graph_control, OID_AUTO, proto, CTLFLAG_RD, 0, NG_CONTROL, ""); + diff --git a/sys/netgraph7/ng_socket.h b/sys/netgraph7/ng_socket.h new file mode 100644 index 0000000000..0d832b4655 --- /dev/null +++ b/sys/netgraph7/ng_socket.h @@ -0,0 +1,69 @@ +/* + * ng_socket.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_socket.h,v 1.7 2006/10/17 11:03:55 glebius Exp $ + * $Whistle: ng_socket.h,v 1.5 1999/01/20 00:22:14 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_SOCKET_H_ +#define _NETGRAPH_NG_SOCKET_H_ + +/* Netgraph node type name and cookie */ +#define NG_SOCKET_NODE_TYPE "socket" +#define NGM_SOCKET_COOKIE 851601233 + +/* Netgraph socket(2) constants */ +#define NG_DATA 1 +#define NG_CONTROL 2 + +/* Commands */ +enum { + NGM_SOCK_CMD_NOLINGER = 1, /* close the socket with last hook */ + NGM_SOCK_CMD_LINGER /* Keep socket even if 0 hooks */ +}; + +/* Netgraph version of struct sockaddr */ +struct sockaddr_ng { + unsigned char sg_len; /* total length */ + sa_family_t sg_family; /* address family */ + char sg_data[14]; /* actually longer; address value */ +}; + +#endif /* _NETGRAPH_NG_SOCKET_H_ */ + diff --git a/sys/netgraph7/ng_socketvar.h b/sys/netgraph7/ng_socketvar.h new file mode 100644 index 0000000000..67d38f7c32 --- /dev/null +++ b/sys/netgraph7/ng_socketvar.h @@ -0,0 +1,68 @@ +/* + * netgraph.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_socketvar.h,v 1.10 2005/07/05 17:35:20 glebius Exp $ + * $Whistle: ng_socketvar.h,v 1.1 1999/01/20 21:35:39 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_SOCKETVAR_H_ +#define _NETGRAPH_NG_SOCKETVAR_H_ + +/* Netgraph protocol control block for each socket */ +struct ngpcb { + struct socket *ng_socket; /* the socket */ + struct ngsock *sockdata; /* netgraph info */ + LIST_ENTRY(ngpcb) socks; /* linked list of sockets */ + int type; /* NG_CONTROL or NG_DATA */ +}; + +/* Per-node private data */ +struct ngsock { + struct ng_node *node; /* the associated netgraph node */ + struct ngpcb *datasock; /* optional data socket */ + struct ngpcb *ctlsock; /* optional control socket */ + int flags; + int refs; + struct mtx mtx; /* mtx to wait on */ + int error; /* place to store error */ +}; +#define NGS_FLAG_NOLINGER 1 /* close with last hook */ + +#endif /* _NETGRAPH_NG_SOCKETVAR_H_ */ + diff --git a/sys/netgraph7/ng_source.c b/sys/netgraph7/ng_source.c new file mode 100644 index 0000000000..01e4daa982 --- /dev/null +++ b/sys/netgraph7/ng_source.c @@ -0,0 +1,920 @@ +/* + * ng_source.c + */ + +/*- + * Copyright (c) 2005 Gleb Smirnoff + * Copyright 2002 Sandvine Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Sandvine Inc.; provided, + * however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Sandvine Inc. + * trademarks, including the mark "SANDVINE" on advertising, endorsements, + * or otherwise except as such appears in the above copyright notice or in + * the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY SANDVINE "AS IS", AND TO THE MAXIMUM + * EXTENT PERMITTED BY LAW, SANDVINE MAKES NO REPRESENTATIONS OR WARRANTIES, + * EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, + * ANY AND ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE, OR NON-INFRINGEMENT. SANDVINE DOES NOT WARRANT, GUARANTEE, OR + * MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE + * USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY + * OR OTHERWISE. IN NO EVENT SHALL SANDVINE BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF SANDVINE IS ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Author: Dave Chapeskie + */ + +#include +__FBSDID("$FreeBSD: src/sys/netgraph/ng_source.c,v 1.30 2007/03/02 14:36:19 emaste Exp $"); + +/* + * This node is used for high speed packet geneneration. It queues + * all data recieved on its 'input' hook and when told to start via + * a control message it sends the packets out its 'output' hook. In + * this way this node can be preloaded with a packet stream which it + * can then send continuously as fast as possible. + * + * Currently it just copies the mbufs as required. It could do various + * tricks to try and avoid this. Probably the best performance would + * be achieved by modifying the appropriate drivers to be told to + * self-re-enqueue packets (e.g. the if_bge driver could reuse the same + * transmit descriptors) under control of this node; perhaps via some + * flag in the mbuf or some such. The node could peek at an appropriate + * ifnet flag to see if such support is available for the connected + * interface. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NG_SOURCE_INTR_TICKS 1 +#define NG_SOURCE_DRIVER_IFQ_MAXLEN (4*1024) + +#define mtod_off(m,off,t) ((t)(mtod((m),caddr_t)+(off))) + +/* Per node info */ +struct privdata { + node_p node; + hook_p input; + hook_p output; + struct ng_source_stats stats; + struct ifqueue snd_queue; /* packets to send */ + struct mbuf *last_packet; /* last pkt in queue */ + struct ifnet *output_ifp; + struct callout intr_ch; + uint64_t packets; /* packets to send */ + uint32_t queueOctets; + struct ng_source_embed_info embed_timestamp; + struct ng_source_embed_cnt_info embed_counter[NG_SOURCE_COUNTERS]; +}; +typedef struct privdata *sc_p; + +/* Node flags */ +#define NG_SOURCE_ACTIVE (NGF_TYPE1) + +/* Netgraph methods */ +static ng_constructor_t ng_source_constructor; +static ng_rcvmsg_t ng_source_rcvmsg; +static ng_shutdown_t ng_source_rmnode; +static ng_newhook_t ng_source_newhook; +static ng_connect_t ng_source_connect; +static ng_rcvdata_t ng_source_rcvdata; +static ng_disconnect_t ng_source_disconnect; + +/* Other functions */ +static void ng_source_intr(node_p, hook_p, void *, int); +static void ng_source_clr_data (sc_p); +static int ng_source_start (sc_p, uint64_t); +static void ng_source_stop (sc_p); +static int ng_source_send (sc_p, int, int *); +static int ng_source_store_output_ifp(sc_p, char *); +static void ng_source_packet_mod(sc_p, struct mbuf *, + int, int, caddr_t, int); +static void ng_source_mod_counter(sc_p sc, + struct ng_source_embed_cnt_info *cnt, + struct mbuf *m, int increment); +static int ng_source_dup_mod(sc_p, struct mbuf *, + struct mbuf **); + +/* Parse type for timeval */ +static const struct ng_parse_struct_field ng_source_timeval_type_fields[] = { + { "tv_sec", &ng_parse_int32_type }, + { "tv_usec", &ng_parse_int32_type }, + { NULL } +}; +const struct ng_parse_type ng_source_timeval_type = { + &ng_parse_struct_type, + &ng_source_timeval_type_fields +}; + +/* Parse type for struct ng_source_stats */ +static const struct ng_parse_struct_field ng_source_stats_type_fields[] + = NG_SOURCE_STATS_TYPE_INFO; +static const struct ng_parse_type ng_source_stats_type = { + &ng_parse_struct_type, + &ng_source_stats_type_fields +}; + +/* Parse type for struct ng_source_embed_info */ +static const struct ng_parse_struct_field ng_source_embed_type_fields[] = + NG_SOURCE_EMBED_TYPE_INFO; +static const struct ng_parse_type ng_source_embed_type = { + &ng_parse_struct_type, + &ng_source_embed_type_fields +}; + +/* Parse type for struct ng_source_embed_cnt_info */ +static const struct ng_parse_struct_field ng_source_embed_cnt_type_fields[] = + NG_SOURCE_EMBED_CNT_TYPE_INFO; +static const struct ng_parse_type ng_source_embed_cnt_type = { + &ng_parse_struct_type, + &ng_source_embed_cnt_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_source_cmds[] = { + { + NGM_SOURCE_COOKIE, + NGM_SOURCE_GET_STATS, + "getstats", + NULL, + &ng_source_stats_type + }, + { + NGM_SOURCE_COOKIE, + NGM_SOURCE_CLR_STATS, + "clrstats", + NULL, + NULL + }, + { + NGM_SOURCE_COOKIE, + NGM_SOURCE_GETCLR_STATS, + "getclrstats", + NULL, + &ng_source_stats_type + }, + { + NGM_SOURCE_COOKIE, + NGM_SOURCE_START, + "start", + &ng_parse_uint64_type, + NULL + }, + { + NGM_SOURCE_COOKIE, + NGM_SOURCE_STOP, + "stop", + NULL, + NULL + }, + { + NGM_SOURCE_COOKIE, + NGM_SOURCE_CLR_DATA, + "clrdata", + NULL, + NULL + }, + { + NGM_SOURCE_COOKIE, + NGM_SOURCE_SETIFACE, + "setiface", + &ng_parse_string_type, + NULL + }, + { + NGM_SOURCE_COOKIE, + NGM_SOURCE_SETPPS, + "setpps", + &ng_parse_uint32_type, + NULL + }, + { + NGM_SOURCE_COOKIE, + NGM_SOURCE_SET_TIMESTAMP, + "settimestamp", + &ng_source_embed_type, + NULL + }, + { + NGM_SOURCE_COOKIE, + NGM_SOURCE_GET_TIMESTAMP, + "gettimestamp", + NULL, + &ng_source_embed_type + }, + { + NGM_SOURCE_COOKIE, + NGM_SOURCE_SET_COUNTER, + "setcounter", + &ng_source_embed_cnt_type, + NULL + }, + { + NGM_SOURCE_COOKIE, + NGM_SOURCE_GET_COUNTER, + "getcounter", + &ng_parse_uint8_type, + &ng_source_embed_cnt_type + }, + { 0 } +}; + +/* Netgraph type descriptor */ +static struct ng_type ng_source_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_SOURCE_NODE_TYPE, + .constructor = ng_source_constructor, + .rcvmsg = ng_source_rcvmsg, + .shutdown = ng_source_rmnode, + .newhook = ng_source_newhook, + .connect = ng_source_connect, + .rcvdata = ng_source_rcvdata, + .disconnect = ng_source_disconnect, + .cmdlist = ng_source_cmds, +}; +NETGRAPH_INIT(source, &ng_source_typestruct); + +static int ng_source_set_autosrc(sc_p, uint32_t); + +/* + * Node constructor + */ +static int +ng_source_constructor(node_p node) +{ + sc_p sc; + + sc = malloc(sizeof(*sc), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (sc == NULL) + return (ENOMEM); + + NG_NODE_SET_PRIVATE(node, sc); + sc->node = node; + sc->snd_queue.ifq_maxlen = 2048; /* XXX not checked */ + ng_callout_init(&sc->intr_ch); + + return (0); +} + +/* + * Add a hook + */ +static int +ng_source_newhook(node_p node, hook_p hook, const char *name) +{ + sc_p sc = NG_NODE_PRIVATE(node); + + if (strcmp(name, NG_SOURCE_HOOK_INPUT) == 0) { + sc->input = hook; + } else if (strcmp(name, NG_SOURCE_HOOK_OUTPUT) == 0) { + sc->output = hook; + sc->output_ifp = 0; + bzero(&sc->stats, sizeof(sc->stats)); + } else + return (EINVAL); + + return (0); +} + +/* + * Hook has been added + */ +static int +ng_source_connect(hook_p hook) +{ + sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct ng_mesg *msg; + int dummy_error = 0; + + /* + * If this is "output" hook, then request information + * from our downstream. + */ + if (hook == sc->output) { + NG_MKMESSAGE(msg, NGM_ETHER_COOKIE, NGM_ETHER_GET_IFNAME, + 0, M_NOWAIT); + if (msg == NULL) + return (ENOBUFS); + + /* + * Our hook and peer hook have HK_INVALID flag set, + * so we can't use NG_SEND_MSG_HOOK() macro here. + */ + NG_SEND_MSG_ID(dummy_error, sc->node, msg, + NG_NODE_ID(NG_PEER_NODE(sc->output)), NG_NODE_ID(sc->node)); + } + + return (0); +} + +/* + * Receive a control message + */ +static int +ng_source_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + sc_p sc = NG_NODE_PRIVATE(node); + struct ng_mesg *msg, *resp = NULL; + int error = 0; + + NGI_GET_MSG(item, msg); + + switch (msg->header.typecookie) { + case NGM_SOURCE_COOKIE: + if (msg->header.flags & NGF_RESP) { + error = EINVAL; + break; + } + switch (msg->header.cmd) { + case NGM_SOURCE_GET_STATS: + case NGM_SOURCE_CLR_STATS: + case NGM_SOURCE_GETCLR_STATS: + { + struct ng_source_stats *stats; + + if (msg->header.cmd != NGM_SOURCE_CLR_STATS) { + NG_MKRESPONSE(resp, msg, + sizeof(*stats), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + goto done; + } + sc->stats.queueOctets = sc->queueOctets; + sc->stats.queueFrames = sc->snd_queue.ifq_len; + if ((sc->node->nd_flags & NG_SOURCE_ACTIVE) + && !timevalisset(&sc->stats.endTime)) { + getmicrotime(&sc->stats.elapsedTime); + timevalsub(&sc->stats.elapsedTime, + &sc->stats.startTime); + } + stats = (struct ng_source_stats *)resp->data; + bcopy(&sc->stats, stats, sizeof(* stats)); + } + if (msg->header.cmd != NGM_SOURCE_GET_STATS) + bzero(&sc->stats, sizeof(sc->stats)); + } + break; + case NGM_SOURCE_START: + { + uint64_t packets; + + if (msg->header.arglen != sizeof(uint64_t)) { + error = EINVAL; + break; + } + + packets = *(uint64_t *)msg->data; + + error = ng_source_start(sc, packets); + + break; + } + case NGM_SOURCE_STOP: + ng_source_stop(sc); + break; + case NGM_SOURCE_CLR_DATA: + ng_source_clr_data(sc); + break; + case NGM_SOURCE_SETIFACE: + { + char *ifname = (char *)msg->data; + + if (msg->header.arglen < 2) { + error = EINVAL; + break; + } + + ng_source_store_output_ifp(sc, ifname); + break; + } + case NGM_SOURCE_SETPPS: + { + uint32_t pps; + + if (msg->header.arglen != sizeof(uint32_t)) { + error = EINVAL; + break; + } + + pps = *(uint32_t *)msg->data; + + sc->stats.maxPps = pps; + + break; + } + case NGM_SOURCE_SET_TIMESTAMP: + { + struct ng_source_embed_info *embed; + + if (msg->header.arglen != sizeof(*embed)) { + error = EINVAL; + goto done; + } + embed = (struct ng_source_embed_info *)msg->data; + bcopy(embed, &sc->embed_timestamp, sizeof(*embed)); + + break; + } + case NGM_SOURCE_GET_TIMESTAMP: + { + struct ng_source_embed_info *embed; + + NG_MKRESPONSE(resp, msg, sizeof(*embed), M_DONTWAIT); + if (resp == NULL) { + error = ENOMEM; + goto done; + } + embed = (struct ng_source_embed_info *)resp->data; + bcopy(&sc->embed_timestamp, embed, sizeof(*embed)); + + break; + } + case NGM_SOURCE_SET_COUNTER: + { + struct ng_source_embed_cnt_info *embed; + + if (msg->header.arglen != sizeof(*embed)) { + error = EINVAL; + goto done; + } + embed = (struct ng_source_embed_cnt_info *)msg->data; + if (embed->index >= NG_SOURCE_COUNTERS || + !(embed->width == 1 || embed->width == 2 || + embed->width == 4)) { + error = EINVAL; + goto done; + } + bcopy(embed, &sc->embed_counter[embed->index], + sizeof(*embed)); + + break; + } + case NGM_SOURCE_GET_COUNTER: + { + uint8_t index = *(uint8_t *)msg->data; + struct ng_source_embed_cnt_info *embed; + + if (index >= NG_SOURCE_COUNTERS) { + error = EINVAL; + goto done; + } + NG_MKRESPONSE(resp, msg, sizeof(*embed), M_DONTWAIT); + if (resp == NULL) { + error = ENOMEM; + goto done; + } + embed = (struct ng_source_embed_cnt_info *)resp->data; + bcopy(&sc->embed_counter[index], embed, sizeof(*embed)); + + break; + } + default: + error = EINVAL; + break; + } + break; + case NGM_ETHER_COOKIE: + if (!(msg->header.flags & NGF_RESP)) { + error = EINVAL; + break; + } + switch (msg->header.cmd) { + case NGM_ETHER_GET_IFNAME: + { + char *ifname = (char *)msg->data; + + if (msg->header.arglen < 2) { + error = EINVAL; + break; + } + + if (ng_source_store_output_ifp(sc, ifname) == 0) + ng_source_set_autosrc(sc, 0); + break; + } + default: + error = EINVAL; + } + break; + default: + error = EINVAL; + break; + } + +done: + /* Take care of synchronous response, if any. */ + NG_RESPOND_MSG(error, node, item, resp); + /* Free the message and return. */ + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive data on a hook + * + * If data comes in the input hook, enqueue it on the send queue. + * If data comes in the output hook, discard it. + */ +static int +ng_source_rcvdata(hook_p hook, item_p item) +{ + sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct mbuf *m; + int error = 0; + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + /* Which hook? */ + if (hook == sc->output) { + /* discard */ + NG_FREE_M(m); + return (error); + } + KASSERT(hook == sc->input, ("%s: no hook!", __func__)); + + /* Enqueue packet. */ + /* XXX should we check IF_QFULL() ? */ + _IF_ENQUEUE(&sc->snd_queue, m); + sc->queueOctets += m->m_pkthdr.len; + sc->last_packet = m; + + return (0); +} + +/* + * Shutdown processing + */ +static int +ng_source_rmnode(node_p node) +{ + sc_p sc = NG_NODE_PRIVATE(node); + + ng_source_stop(sc); + ng_source_clr_data(sc); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + free(sc, M_NETGRAPH); + + return (0); +} + +/* + * Hook disconnection + */ +static int +ng_source_disconnect(hook_p hook) +{ + sc_p sc; + + sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + KASSERT(sc != NULL, ("%s: null node private", __func__)); + if (NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0 || hook == sc->output) + ng_rmnode_self(NG_HOOK_NODE(hook)); + return (0); +} + +/* + * Set sc->output_ifp to point to the the struct ifnet of the interface + * reached via our output hook. + */ +static int +ng_source_store_output_ifp(sc_p sc, char *ifname) +{ + struct ifnet *ifp; + int s; + + ifp = ifunit(ifname); + + if (ifp == NULL) { + printf("%s: can't find interface %d\n", __func__, if_index); + return (EINVAL); + } + sc->output_ifp = ifp; + +#if 1 + /* XXX mucking with a drivers ifqueue size is ugly but we need it + * to queue a lot of packets to get close to line rate on a gigabit + * interface with small packets. + * XXX we should restore the original value at stop or disconnect + */ + s = splimp(); /* XXX is this required? */ + if (ifp->if_snd.ifq_maxlen < NG_SOURCE_DRIVER_IFQ_MAXLEN) { + printf("ng_source: changing ifq_maxlen from %d to %d\n", + ifp->if_snd.ifq_maxlen, NG_SOURCE_DRIVER_IFQ_MAXLEN); + ifp->if_snd.ifq_maxlen = NG_SOURCE_DRIVER_IFQ_MAXLEN; + } + splx(s); +#endif + return (0); +} + +/* + * Set the attached ethernet node's ethernet source address override flag. + */ +static int +ng_source_set_autosrc(sc_p sc, uint32_t flag) +{ + struct ng_mesg *msg; + int error = 0; + + NG_MKMESSAGE(msg, NGM_ETHER_COOKIE, NGM_ETHER_SET_AUTOSRC, + sizeof (uint32_t), M_NOWAIT); + if (msg == NULL) + return(ENOBUFS); + + *(uint32_t *)msg->data = flag; + NG_SEND_MSG_HOOK(error, sc->node, msg, sc->output, 0); + return (error); +} + +/* + * Clear out the data we've queued + */ +static void +ng_source_clr_data (sc_p sc) +{ + struct mbuf *m; + + for (;;) { + _IF_DEQUEUE(&sc->snd_queue, m); + if (m == NULL) + break; + NG_FREE_M(m); + } + sc->queueOctets = 0; + sc->last_packet = NULL; +} + +/* + * Start sending queued data out the output hook + */ +static int +ng_source_start(sc_p sc, uint64_t packets) +{ + if (sc->output_ifp == NULL) { + printf("ng_source: start without iface configured\n"); + return (ENXIO); + } + + if (sc->node->nd_flags & NG_SOURCE_ACTIVE) + return (EBUSY); + + sc->node->nd_flags |= NG_SOURCE_ACTIVE; + + sc->packets = packets; + timevalclear(&sc->stats.elapsedTime); + timevalclear(&sc->stats.endTime); + getmicrotime(&sc->stats.startTime); + getmicrotime(&sc->stats.lastTime); + ng_callout(&sc->intr_ch, sc->node, NULL, 0, + ng_source_intr, sc, 0); + + return (0); +} + +/* + * Stop sending queued data out the output hook + */ +static void +ng_source_stop(sc_p sc) +{ + ng_uncallout(&sc->intr_ch, sc->node); + sc->node->nd_flags &= ~NG_SOURCE_ACTIVE; + getmicrotime(&sc->stats.endTime); + sc->stats.elapsedTime = sc->stats.endTime; + timevalsub(&sc->stats.elapsedTime, &sc->stats.startTime); +} + +/* + * While active called every NG_SOURCE_INTR_TICKS ticks. + * Sends as many packets as the interface connected to our + * output hook is able to enqueue. + */ +static void +ng_source_intr(node_p node, hook_p hook, void *arg1, int arg2) +{ + sc_p sc = (sc_p)arg1; + struct ifqueue *ifq; + int packets; + + KASSERT(sc != NULL, ("%s: null node private", __func__)); + + if (sc->packets == 0 || sc->output == NULL + || (sc->node->nd_flags & NG_SOURCE_ACTIVE) == 0) { + ng_source_stop(sc); + return; + } + + if (sc->output_ifp != NULL) { + ifq = (struct ifqueue *)&sc->output_ifp->if_snd; + packets = ifq->ifq_maxlen - ifq->ifq_len; + } else + packets = sc->snd_queue.ifq_len; + + if (sc->stats.maxPps != 0) { + struct timeval now, elapsed; + uint64_t usec; + int maxpkt; + + getmicrotime(&now); + elapsed = now; + timevalsub(&elapsed, &sc->stats.lastTime); + usec = elapsed.tv_sec * 1000000 + elapsed.tv_usec; + maxpkt = (uint64_t)sc->stats.maxPps * usec / 1000000; + sc->stats.lastTime = now; + if (packets > maxpkt) + packets = maxpkt; + } + + ng_source_send(sc, packets, NULL); + if (sc->packets == 0) + ng_source_stop(sc); + else + ng_callout(&sc->intr_ch, node, NULL, NG_SOURCE_INTR_TICKS, + ng_source_intr, sc, 0); +} + +/* + * Send packets out our output hook. + */ +static int +ng_source_send(sc_p sc, int tosend, int *sent_p) +{ + struct mbuf *m, *m2; + int sent; + int error = 0; + + KASSERT(tosend >= 0, ("%s: negative tosend param", __func__)); + KASSERT(sc->node->nd_flags & NG_SOURCE_ACTIVE, + ("%s: inactive node", __func__)); + + if ((uint64_t)tosend > sc->packets) + tosend = sc->packets; + + /* Go through the queue sending packets one by one. */ + for (sent = 0; error == 0 && sent < tosend; ++sent) { + _IF_DEQUEUE(&sc->snd_queue, m); + if (m == NULL) + break; + + /* Duplicate and modify the packet. */ + error = ng_source_dup_mod(sc, m, &m2); + if (error) { + if (error == ENOBUFS) + _IF_PREPEND(&sc->snd_queue, m); + else + _IF_ENQUEUE(&sc->snd_queue, m); + break; + } + + /* Re-enqueue the original packet for us. */ + _IF_ENQUEUE(&sc->snd_queue, m); + + sc->stats.outFrames++; + sc->stats.outOctets += m2->m_pkthdr.len; + NG_SEND_DATA_ONLY(error, sc->output, m2); + if (error) + break; + } + + sc->packets -= sent; + if (sent_p != NULL) + *sent_p = sent; + return (error); +} + +/* + * Modify packet in 'm' by changing 'len' bytes starting at 'offset' + * to data in 'cp'. + * + * The packet data in 'm' must be in a contiguous buffer in a single mbuf. + */ +static void +ng_source_packet_mod(sc_p sc, struct mbuf *m, int offset, int len, caddr_t cp, + int flags) +{ + if (len == 0) + return; + + /* Can't modify beyond end of packet. */ + /* TODO: Pad packet for this case. */ + if (offset + len > m->m_len) + return; + + bcopy(cp, mtod_off(m, offset, caddr_t), len); +} + +static void +ng_source_mod_counter(sc_p sc, struct ng_source_embed_cnt_info *cnt, + struct mbuf *m, int increment) +{ + caddr_t cp; + uint32_t val; + + val = htonl(cnt->next_val); + cp = (caddr_t)&val + sizeof(val) - cnt->width; + ng_source_packet_mod(sc, m, cnt->offset, cnt->width, cp, cnt->flags); + + if (increment) { + cnt->next_val += increment; + + if (increment > 0 && cnt->next_val > cnt->max_val) { + cnt->next_val = cnt->min_val - 1 + + (cnt->next_val - cnt->max_val); + if (cnt->next_val > cnt->max_val) + cnt->next_val = cnt->max_val; + } else if (increment < 0 && cnt->next_val < cnt->min_val) { + cnt->next_val = cnt->max_val + 1 + + (cnt->next_val - cnt->min_val); + if (cnt->next_val < cnt->min_val) + cnt->next_val = cnt->max_val; + } + } +} + +static int +ng_source_dup_mod(sc_p sc, struct mbuf *m0, struct mbuf **m_ptr) +{ + struct mbuf *m; + struct ng_source_embed_cnt_info *cnt; + struct ng_source_embed_info *ts; + int modify; + int error = 0; + int i, increment; + + /* Are we going to modify packets? */ + modify = sc->embed_timestamp.flags & NGM_SOURCE_EMBED_ENABLE; + for (i = 0; !modify && i < NG_SOURCE_COUNTERS; ++i) + modify = sc->embed_counter[i].flags & NGM_SOURCE_EMBED_ENABLE; + + /* Duplicate the packet. */ + if (modify) + m = m_dup(m0, M_DONTWAIT); + else + m = m_copypacket(m0, M_DONTWAIT); + if (m == NULL) { + error = ENOBUFS; + goto done; + } + *m_ptr = m; + + if (!modify) + goto done; + + /* Modify the copied packet for sending. */ + KASSERT(M_WRITABLE(m), ("%s: packet not writable", __func__)); + + for (i = 0; i < NG_SOURCE_COUNTERS; ++i) { + cnt = &sc->embed_counter[i]; + if (cnt->flags & NGM_SOURCE_EMBED_ENABLE) { + if ((cnt->flags & NGM_SOURCE_INC_CNT_PER_LIST) == 0 || + sc->last_packet == m0) + increment = cnt->increment; + else + increment = 0; + ng_source_mod_counter(sc, cnt, m, increment); + } + } + + ts = &sc->embed_timestamp; + if (ts->flags & NGM_SOURCE_EMBED_ENABLE) { + struct timeval now; + getmicrotime(&now); + now.tv_sec = htonl(now.tv_sec); + now.tv_usec = htonl(now.tv_usec); + ng_source_packet_mod(sc, m, ts->offset, sizeof (now), + (caddr_t)&now, ts->flags); + } + +done: + return(error); +} diff --git a/sys/netgraph7/ng_source.h b/sys/netgraph7/ng_source.h new file mode 100644 index 0000000000..cbbb38a162 --- /dev/null +++ b/sys/netgraph7/ng_source.h @@ -0,0 +1,139 @@ +/* + * ng_source.h + */ + +/*- + * Copyright 2002 Sandvine Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Sandvine Inc.; provided, + * however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Sandvine Inc. + * trademarks, including the mark "SANDVINE" on advertising, endorsements, + * or otherwise except as such appears in the above copyright notice or in + * the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY SANDVINE "AS IS", AND TO THE MAXIMUM + * EXTENT PERMITTED BY LAW, SANDVINE MAKES NO REPRESENTATIONS OR WARRANTIES, + * EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, + * ANY AND ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE, OR NON-INFRINGEMENT. SANDVINE DOES NOT WARRANT, GUARANTEE, OR + * MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE + * USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY + * OR OTHERWISE. IN NO EVENT SHALL SANDVINE BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF SANDVINE IS ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Author: Dave Chapeskie + * + * $FreeBSD: src/sys/netgraph/ng_source.h,v 1.9 2007/03/02 01:44:04 emaste Exp $ + */ + +#ifndef _NETGRAPH_NG_SOURCE_H_ +#define _NETGRAPH_NG_SOURCE_H_ + +/* Node type name and magic cookie */ +#define NG_SOURCE_NODE_TYPE "source" +#define NGM_SOURCE_COOKIE 1110646684 + +/* Hook names */ +#define NG_SOURCE_HOOK_INPUT "input" +#define NG_SOURCE_HOOK_OUTPUT "output" + +/* Statistics structure returned by NGM_SOURCE_GET_STATS */ +struct ng_source_stats { + uint64_t outOctets; + uint64_t outFrames; + uint32_t queueOctets; + uint32_t queueFrames; + uint32_t maxPps; + struct timeval startTime; + struct timeval endTime; + struct timeval elapsedTime; + struct timeval lastTime; +}; + +extern const struct ng_parse_type ng_source_timeval_type; +/* Keep this in sync with the above structure definition */ +#define NG_SOURCE_STATS_TYPE_INFO { \ + { "outOctets", &ng_parse_uint64_type }, \ + { "outFrames", &ng_parse_uint64_type }, \ + { "queueOctets", &ng_parse_uint32_type }, \ + { "queueFrames", &ng_parse_uint32_type }, \ + { "maxPps", &ng_parse_uint32_type }, \ + { "startTime", &ng_source_timeval_type }, \ + { "endTime", &ng_source_timeval_type }, \ + { "elapsedTime", &ng_source_timeval_type }, \ + { "lastTime", &ng_source_timeval_type }, \ + { NULL } \ +} + +/* Packet embedding info for NGM_SOURCE_GET/SET_TIMESTAMP */ +struct ng_source_embed_info { + uint16_t offset; /* offset from ethernet header */ + uint8_t flags; + uint8_t spare; +}; +#define NGM_SOURCE_EMBED_ENABLE 0x01 /* enable embedding */ +#define NGM_SOURCE_INC_CNT_PER_LIST 0x02 /* increment once per list */ + +/* Keep this in sync with the above structure definition. */ +#define NG_SOURCE_EMBED_TYPE_INFO { \ + { "offset", &ng_parse_hint16_type }, \ + { "flags", &ng_parse_hint8_type }, \ + { NULL } \ +} + +/* Packet embedding info for NGM_SOURCE_GET/SET_COUNTER */ +#define NG_SOURCE_COUNTERS 4 +struct ng_source_embed_cnt_info { + uint16_t offset; /* offset from ethernet header */ + uint8_t flags; /* as above */ + uint8_t width; /* in bytes (1, 2, 4) */ + uint32_t next_val; + uint32_t min_val; + uint32_t max_val; + int32_t increment; + uint8_t index; /* which counter (0..3) */ +}; + +/* Keep this in sync with the above structure definition. */ +#define NG_SOURCE_EMBED_CNT_TYPE_INFO { \ + { "offset", &ng_parse_hint16_type }, \ + { "flags", &ng_parse_hint8_type }, \ + { "width", &ng_parse_uint8_type }, \ + { "next_val", &ng_parse_uint32_type }, \ + { "min_val", &ng_parse_uint32_type }, \ + { "max_val", &ng_parse_uint32_type }, \ + { "increment", &ng_parse_int32_type }, \ + { "index", &ng_parse_uint8_type }, \ + { NULL } \ +} + +/* Netgraph commands */ +enum { + NGM_SOURCE_GET_STATS = 1, /* get stats */ + NGM_SOURCE_CLR_STATS, /* clear stats */ + NGM_SOURCE_GETCLR_STATS, /* atomically get and clear stats */ + NGM_SOURCE_START, /* start sending queued data */ + NGM_SOURCE_STOP, /* stop sending queued data */ + NGM_SOURCE_CLR_DATA, /* clear the queued data */ + NGM_SOURCE_SETIFACE, /* configure downstream iface */ + NGM_SOURCE_SETPPS, /* rate-limiting packets per second */ + NGM_SOURCE_SET_TIMESTAMP, /* embed xmit timestamp */ + NGM_SOURCE_GET_TIMESTAMP, + NGM_SOURCE_SET_COUNTER, /* embed counter */ + NGM_SOURCE_GET_COUNTER, +}; + +#endif /* _NETGRAPH_NG_SOURCE_H_ */ diff --git a/sys/netgraph7/ng_split.c b/sys/netgraph7/ng_split.c new file mode 100644 index 0000000000..aa3620006c --- /dev/null +++ b/sys/netgraph7/ng_split.c @@ -0,0 +1,179 @@ +/*- + * + * Copyright (c) 1999-2000, Vitaly V Belekhov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_split.c,v 1.7 2005/08/29 13:47:08 glebius Exp $ + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Netgraph methods */ +static ng_constructor_t ng_split_constructor; +static ng_shutdown_t ng_split_shutdown; +static ng_newhook_t ng_split_newhook; +static ng_rcvdata_t ng_split_rcvdata; +static ng_disconnect_t ng_split_disconnect; + +/* Node type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_SPLIT_NODE_TYPE, + .constructor = ng_split_constructor, + .shutdown = ng_split_shutdown, + .newhook = ng_split_newhook, + .rcvdata = ng_split_rcvdata, + .disconnect = ng_split_disconnect, +}; +NETGRAPH_INIT(ng_split, &typestruct); + +/* Node private data */ +struct ng_split_private { + hook_p out; + hook_p in; + hook_p mixed; + node_p node; /* Our netgraph node */ +}; +typedef struct ng_split_private *priv_p; + +/************************************************************************ + NETGRAPH NODE STUFF + ************************************************************************/ + +/* + * Constructor for a node + */ +static int +ng_split_constructor(node_p node) +{ + priv_p priv; + + /* Allocate node */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_ZERO | M_NOWAIT); + if (priv == NULL) + return (ENOMEM); + + /* Link together node and private info */ + NG_NODE_SET_PRIVATE(node, priv); + priv->node = node; + + /* Done */ + return (0); +} + +/* + * Give our ok for a hook to be added + */ +static int +ng_split_newhook(node_p node, hook_p hook, const char *name) +{ + priv_p priv = NG_NODE_PRIVATE(node); + hook_p *localhook; + + if (strcmp(name, NG_SPLIT_HOOK_MIXED) == 0) { + localhook = &priv->mixed; + } else if (strcmp(name, NG_SPLIT_HOOK_IN) == 0) { + localhook = &priv->in; + } else if (strcmp(name, NG_SPLIT_HOOK_OUT) == 0) { + localhook = &priv->out; + } else + return (EINVAL); + + if (*localhook != NULL) + return (EISCONN); + *localhook = hook; + NG_HOOK_SET_PRIVATE(hook, localhook); + + return (0); +} + +/* + * Recive data from a hook. + */ +static int +ng_split_rcvdata(hook_p hook, item_p item) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + int error = 0; + + if (hook == priv->out) { + printf("ng_split: got packet from out hook!\n"); + NG_FREE_ITEM(item); + error = EINVAL; + } else if ((hook == priv->in) && (priv->mixed != NULL)) { + NG_FWD_ITEM_HOOK(error, item, priv->mixed); + } else if ((hook == priv->mixed) && (priv->out != NULL)) { + NG_FWD_ITEM_HOOK(error, item, priv->out); + } + + if (item) + NG_FREE_ITEM(item); + + return (error); +} + +static int +ng_split_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + FREE(priv, M_NETGRAPH); + + return (0); +} + +/* + * Hook disconnection + */ +static int +ng_split_disconnect(hook_p hook) +{ + hook_p *localhook = NG_HOOK_PRIVATE(hook); + + KASSERT(localhook != NULL, ("%s: null info", __func__)); + *localhook = NULL; + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) { + ng_rmnode_self(NG_HOOK_NODE(hook)); + } + + return (0); +} diff --git a/sys/netgraph7/ng_split.h b/sys/netgraph7/ng_split.h new file mode 100644 index 0000000000..d581b34857 --- /dev/null +++ b/sys/netgraph7/ng_split.h @@ -0,0 +1,45 @@ +/*- + * + * Copyright (c) 1999-2000, Vitaly V Belekhov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_split.h,v 1.5 2005/10/25 20:56:12 ru Exp $ + * + */ + + +#ifndef _NETGRAPH_NG_SPLIT_H_ +#define _NETGRAPH_NG_SPLIT_H_ + +/* Node type name and magic cookie */ +#define NG_SPLIT_NODE_TYPE "split" +#define NGM_SPLIT_COOKIE 949409402 + +/* My hook names */ +#define NG_SPLIT_HOOK_MIXED "mixed" /* Mixed stream (in/out) */ +#define NG_SPLIT_HOOK_OUT "out" /* Output to outhook (sending out) */ +#define NG_SPLIT_HOOK_IN "in" /* Input from inhook (recieving) */ + +#endif /* _NETGRAPH_NG_SPLIT_H_ */ diff --git a/sys/netgraph7/ng_sppp.c b/sys/netgraph7/ng_sppp.c new file mode 100644 index 0000000000..ddfb554310 --- /dev/null +++ b/sys/netgraph7/ng_sppp.c @@ -0,0 +1,422 @@ +/* + * ng_sppp.c Netgraph to Sppp module. + */ + +/*- + * Copyright (C) 2002-2004 Cronyx Engineering. + * Copyright (C) 2002-2004 Roman Kurakin + * + * This software is distributed with NO WARRANTIES, not even the implied + * warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Authors grant any other persons or organisations a permission to use, + * modify and redistribute this software in source and binary forms, + * as long as this message is kept with the software, all derivative + * works or modified versions. + * + * Cronyx Id: ng_sppp.c,v 1.1.2.10 2004/03/01 15:17:21 rik Exp $ + */ +#include +__FBSDID("$FreeBSD: src/sys/netgraph/ng_sppp.c,v 1.11 2006/12/29 13:59:50 jhb Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_SPPP, "netgraph_sppp", "netgraph sppp node "); +#else +#define M_NETGRAPH_SPPP M_NETGRAPH +#endif + +/* Node private data */ +struct ng_sppp_private { + struct ifnet *ifp; /* Our interface */ + int unit; /* Interface unit number */ + node_p node; /* Our netgraph node */ + hook_p hook; /* Hook */ +}; +typedef struct ng_sppp_private *priv_p; + +/* Interface methods */ +static void ng_sppp_start (struct ifnet *ifp); +static int ng_sppp_ioctl (struct ifnet *ifp, u_long cmd, caddr_t data); + +/* Netgraph methods */ +static ng_constructor_t ng_sppp_constructor; +static ng_rcvmsg_t ng_sppp_rcvmsg; +static ng_shutdown_t ng_sppp_shutdown; +static ng_newhook_t ng_sppp_newhook; +static ng_rcvdata_t ng_sppp_rcvdata; +static ng_disconnect_t ng_sppp_disconnect; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_sppp_cmds[] = { + { + NGM_SPPP_COOKIE, + NGM_SPPP_GET_IFNAME, + "getifname", + NULL, + &ng_parse_string_type + }, + { 0 } +}; + +/* Node type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_SPPP_NODE_TYPE, + .constructor = ng_sppp_constructor, + .rcvmsg = ng_sppp_rcvmsg, + .shutdown = ng_sppp_shutdown, + .newhook = ng_sppp_newhook, + .rcvdata = ng_sppp_rcvdata, + .disconnect = ng_sppp_disconnect, + .cmdlist = ng_sppp_cmds, +}; +NETGRAPH_INIT(sppp, &typestruct); + +MODULE_DEPEND (ng_sppp, sppp, 1, 1, 1); + +/* We keep a bitmap indicating which unit numbers are free. + Zero means the unit number is free, one means it's taken. */ +static unsigned char *ng_sppp_units = NULL; +static unsigned char ng_sppp_units_len = 0; +static unsigned char ng_units_in_use = 0; + +/* + * Find the first free unit number for a new interface. + * Increase the size of the unit bitmap as necessary. + */ +static __inline int +ng_sppp_get_unit (int *unit) +{ + int index, bit; + unsigned char mask; + + for (index = 0; index < ng_sppp_units_len + && ng_sppp_units[index] == 0xFF; index++); + if (index == ng_sppp_units_len) { /* extend array */ + unsigned char *newarray; + int newlen; + + newlen = (2 * ng_sppp_units_len) + sizeof (*ng_sppp_units); + MALLOC (newarray, unsigned char *, + newlen * sizeof (*ng_sppp_units), M_NETGRAPH_SPPP, M_NOWAIT); + if (newarray == NULL) + return (ENOMEM); + bcopy (ng_sppp_units, newarray, + ng_sppp_units_len * sizeof (*ng_sppp_units)); + bzero (newarray + ng_sppp_units_len, + newlen - ng_sppp_units_len); + if (ng_sppp_units != NULL) + FREE (ng_sppp_units, M_NETGRAPH_SPPP); + ng_sppp_units = newarray; + ng_sppp_units_len = newlen; + } + mask = ng_sppp_units[index]; + for (bit = 0; (mask & 1) != 0; bit++) + mask >>= 1; + KASSERT ((bit >= 0 && bit < NBBY), + ("%s: word=%d bit=%d", __func__, ng_sppp_units[index], bit)); + ng_sppp_units[index] |= (1 << bit); + *unit = (index * NBBY) + bit; + ng_units_in_use++; + return (0); +} + +/* + * Free a no longer needed unit number. + */ +static __inline void +ng_sppp_free_unit (int unit) +{ + int index, bit; + + index = unit / NBBY; + bit = unit % NBBY; + KASSERT (index < ng_sppp_units_len, + ("%s: unit=%d len=%d", __func__, unit, ng_sppp_units_len)); + KASSERT ((ng_sppp_units[index] & (1 << bit)) != 0, + ("%s: unit=%d is free", __func__, unit)); + ng_sppp_units[index] &= ~(1 << bit); + + ng_units_in_use--; + if (ng_units_in_use == 0) { + FREE (ng_sppp_units, M_NETGRAPH_SPPP); + ng_sppp_units_len = 0; + ng_sppp_units = NULL; + } +} + +/************************************************************************ + INTERFACE STUFF + ************************************************************************/ + +/* + * Process an ioctl for the interface + */ +static int +ng_sppp_ioctl (struct ifnet *ifp, u_long command, caddr_t data) +{ + int error = 0; + + error = sppp_ioctl (ifp, command, data); + if (error) + return error; + + return error; +} + +/* + * This routine should never be called + */ + +static void +ng_sppp_start (struct ifnet *ifp) +{ + struct mbuf *m; + int len, error = 0; + priv_p priv = ifp->if_softc; + + /* Check interface flags */ + /* + * This has side effects. It is not good idea to stop sending if we + * are not UP. If we are not running we still want to send LCP term + * packets. + */ +/* if (!((ifp->if_flags & IFF_UP) && */ +/* (ifp->if_drv_flags & IFF_DRV_RUNNING))) { */ +/* return;*/ +/* }*/ + + if (ifp->if_drv_flags & IFF_DRV_OACTIVE) + return; + + if (!priv->hook) + return; + + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + + while ((m = sppp_dequeue (ifp)) != NULL) { + BPF_MTAP (ifp, m); + len = m->m_pkthdr.len; + + NG_SEND_DATA_ONLY (error, priv->hook, m); + + if (error) { + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + return; + } + } + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; +} + +/************************************************************************ + NETGRAPH NODE STUFF + ************************************************************************/ + +/* + * Constructor for a node + */ +static int +ng_sppp_constructor (node_p node) +{ + struct sppp *pp; + struct ifnet *ifp; + priv_p priv; + int error = 0; + + /* Allocate node and interface private structures */ + MALLOC (priv, priv_p, sizeof(*priv), M_NETGRAPH_SPPP, M_NOWAIT|M_ZERO); + if (priv == NULL) + return (ENOMEM); + + ifp = if_alloc(IFT_PPP); + if (ifp == NULL) { + FREE (priv, M_NETGRAPH_SPPP); + return (ENOSPC); + } + pp = IFP2SP(ifp); + + /* Link them together */ + ifp->if_softc = priv; + priv->ifp = ifp; + + /* Get an interface unit number */ + if ((error = ng_sppp_get_unit(&priv->unit)) != 0) { + FREE (pp, M_NETGRAPH_SPPP); + FREE (priv, M_NETGRAPH_SPPP); + return (error); + } + + + /* Link together node and private info */ + NG_NODE_SET_PRIVATE (node, priv); + priv->node = node; + + /* Initialize interface structure */ + if_initname (SP2IFP(pp), NG_SPPP_IFACE_NAME, priv->unit); + ifp->if_start = ng_sppp_start; + ifp->if_ioctl = ng_sppp_ioctl; + ifp->if_watchdog = NULL; + ifp->if_flags = (IFF_POINTOPOINT|IFF_MULTICAST); + + /* Give this node the same name as the interface (if possible) */ + if (ng_name_node(node, SP2IFP(pp)->if_xname) != 0) + log (LOG_WARNING, "%s: can't acquire netgraph name\n", + SP2IFP(pp)->if_xname); + + /* Attach the interface */ + sppp_attach (ifp); + if_attach (ifp); + bpfattach (ifp, DLT_NULL, sizeof(u_int32_t)); + + /* Done */ + return (0); +} + +/* + * Give our ok for a hook to be added + */ +static int +ng_sppp_newhook (node_p node, hook_p hook, const char *name) +{ + priv_p priv = NG_NODE_PRIVATE (node); + + if (strcmp (name, NG_SPPP_HOOK_DOWNSTREAM) != 0) + return (EINVAL); + + if (priv->hook) + return (EISCONN); + + priv->hook = hook; + NG_HOOK_SET_PRIVATE (hook, priv); + + return (0); +} + +/* + * Receive a control message + */ +static int +ng_sppp_rcvmsg (node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE (node); + struct ng_mesg *msg = NULL; + struct ng_mesg *resp = NULL; + struct sppp *const pp = IFP2SP(priv->ifp); + int error = 0; + + NGI_GET_MSG (item, msg); + switch (msg->header.typecookie) { + case NGM_SPPP_COOKIE: + switch (msg->header.cmd) { + case NGM_SPPP_GET_IFNAME: + NG_MKRESPONSE (resp, msg, IFNAMSIZ, M_NOWAIT); + if (!resp) { + error = ENOMEM; + break; + } + strlcpy(resp->data, SP2IFP(pp)->if_xname, IFNAMSIZ); + break; + + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } + NG_RESPOND_MSG (error, node, item, resp); + NG_FREE_MSG (msg); + return (error); +} + +/* + * Recive data from a hook. Pass the packet to the correct input routine. + */ +static int +ng_sppp_rcvdata (hook_p hook, item_p item) +{ + struct mbuf *m; + const priv_p priv = NG_NODE_PRIVATE (NG_HOOK_NODE (hook)); + struct sppp *const pp = IFP2SP(priv->ifp); + + NGI_GET_M (item, m); + NG_FREE_ITEM (item); + /* Sanity checks */ + KASSERT (m->m_flags & M_PKTHDR, ("%s: not pkthdr", __func__)); + if ((SP2IFP(pp)->if_flags & IFF_UP) == 0) { + NG_FREE_M (m); + return (ENETDOWN); + } + + /* Update interface stats */ + SP2IFP(pp)->if_ipackets++; + + /* Note receiving interface */ + m->m_pkthdr.rcvif = SP2IFP(pp); + + /* Berkeley packet filter */ + BPF_MTAP (SP2IFP(pp), m); + + /* Send packet */ + sppp_input (SP2IFP(pp), m); + return 0; +} + +/* + * Shutdown and remove the node and its associated interface. + */ +static int +ng_sppp_shutdown (node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + /* Detach from the packet filter list of interfaces. */ + bpfdetach (priv->ifp); + sppp_detach (priv->ifp); + if_detach (priv->ifp); + if_free(priv->ifp); + ng_sppp_free_unit (priv->unit); + FREE (priv, M_NETGRAPH_SPPP); + NG_NODE_SET_PRIVATE (node, NULL); + NG_NODE_UNREF (node); + return (0); +} + +/* + * Hook disconnection. + */ +static int +ng_sppp_disconnect (hook_p hook) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + if (priv) + priv->hook = NULL; + + return (0); +} diff --git a/sys/netgraph7/ng_sppp.h b/sys/netgraph7/ng_sppp.h new file mode 100644 index 0000000000..8c9ad429b5 --- /dev/null +++ b/sys/netgraph7/ng_sppp.h @@ -0,0 +1,39 @@ +/* + * ng_sppp.h Netgraph to Sppp module. + */ + +/*- + * Copyright (C) 2002-2004 Cronyx Engineering. + * Copyright (C) 2002-2004 Roman Kurakin + * + * This software is distributed with NO WARRANTIES, not even the implied + * warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Authors grant any other persons or organisations a permission to use, + * modify and redistribute this software in source and binary forms, + * as long as this message is kept with the software, all derivative + * works or modified versions. + * + * $FreeBSD: src/sys/netgraph/ng_sppp.h,v 1.3 2005/02/03 13:03:31 ru Exp $ + * Cronyx Id: ng_sppp.h,v 1.1.2.6 2004/03/01 15:17:21 rik Exp $ + */ + +#ifndef _NETGRAPH_SPPP_H_ +#define _NETGRAPH_SPPP_H_ + +/* Node type name and magic cookie */ +#define NG_SPPP_NODE_TYPE "sppp" +#define NGM_SPPP_COOKIE 1040804655 + +/* Interface base name */ +#define NG_SPPP_IFACE_NAME "sppp" + +/* My hook names */ +#define NG_SPPP_HOOK_DOWNSTREAM "downstream" + +/* Netgraph commands */ +enum { + NGM_SPPP_GET_IFNAME = 1, /* returns struct ng_sppp_ifname */ +}; + +#endif /* _NETGRAPH_SPPP_H_ */ diff --git a/sys/netgraph7/ng_tag.c b/sys/netgraph7/ng_tag.c new file mode 100644 index 0000000000..603060ad4c --- /dev/null +++ b/sys/netgraph7/ng_tag.c @@ -0,0 +1,717 @@ +/*- + * Copyright (c) 2006 Vadim Goncharov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Portions Copyright (c) 1999 Whistle Communications, Inc. + * (ng_bpf by Archie Cobbs ) + * + * $FreeBSD: src/sys/netgraph/ng_tag.c,v 1.1 2006/06/27 12:45:28 glebius Exp $ + */ + +/* + * TAG NETGRAPH NODE TYPE + * + * This node type accepts an arbitrary number of hooks. Each hook can be + * configured for an mbuf_tags(9) definition and two hook names: a hook + * for matched packets, and a hook for packets, that didn't match. Incoming + * packets are examined for configured tag, matched packets are delivered + * out via first hook, and not matched out via second. If corresponding hook + * is not configured, packets are dropped. + * + * A hook can also have an outgoing tag definition configured, so that + * all packets leaving the hook will be unconditionally appended with newly + * allocated tag. + * + * Both hooks can be set to null tag definitions (that is, with zeroed + * fields), so that packet tags are unmodified on output or all packets + * are unconditionally forwarded to non-matching hook on input. There is + * also a possibility to replace tags by specifying strip flag on input + * and replacing tag on corresponding output tag (or simply remove tag if + * no tag specified on output). + * + * If compiled with NG_TAG_DEBUG, each hook also keeps statistics about + * how many packets have matched, etc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_TAG, "netgraph_tag", "netgraph tag node "); +#else +#define M_NETGRAPH_TAG M_NETGRAPH +#endif + +#define ERROUT(x) do { error = (x); goto done; } while (0) + +/* + * Per hook private info. + * + * We've separated API and ABI here, to make easier changes in this node, + * if needed. If you want to change representation, please do not break API. + * We still keep API structures in memory to simplify access to them for + * GET* messages, but most of data is accessed in internal representation + * only. The reason for this is to speed things up - if data will be + * accessed from API structures, there would be double pointer dereferencing + * in the code, which almost necessarily leads to CPU cache misses and + * reloads. + * + * We also do another optimization by using resolved pointers to + * destination hooks instead of expensive ng_findhook(). + */ +struct ng_tag_hookinfo { + hook_p hi_match; /* matching hook pointer */ + hook_p hi_nonmatch; /* non-matching hook pointer */ + uint32_t in_tag_cookie; + uint32_t out_tag_cookie; + uint16_t in_tag_id; + uint16_t in_tag_len; + uint16_t out_tag_id; + uint16_t out_tag_len; + uint8_t strip; + void *in_tag_data; + void *out_tag_data; + struct ng_tag_hookin *in; + struct ng_tag_hookout *out; +#ifdef NG_TAG_DEBUG + struct ng_tag_hookstat stats; +#endif +}; +typedef struct ng_tag_hookinfo *hinfo_p; + +/* Netgraph methods. */ +static ng_constructor_t ng_tag_constructor; +static ng_rcvmsg_t ng_tag_rcvmsg; +static ng_shutdown_t ng_tag_shutdown; +static ng_newhook_t ng_tag_newhook; +static ng_rcvdata_t ng_tag_rcvdata; +static ng_disconnect_t ng_tag_disconnect; + +/* Internal helper functions. */ +static int ng_tag_setdata_in(hook_p hook, const struct ng_tag_hookin *hp); +static int ng_tag_setdata_out(hook_p hook, const struct ng_tag_hookout *hp); + +/* Parse types for the field 'tag_data' in structs ng_tag_hookin and out. */ +static int +ng_tag_hookinary_getLength(const struct ng_parse_type *type, + const u_char *start, const u_char *buf) +{ + const struct ng_tag_hookin *hp; + + hp = (const struct ng_tag_hookin *) + (buf - offsetof(struct ng_tag_hookin, tag_data)); + return (hp->tag_len); +} + +static int +ng_tag_hookoutary_getLength(const struct ng_parse_type *type, + const u_char *start, const u_char *buf) +{ + const struct ng_tag_hookout *hp; + + hp = (const struct ng_tag_hookout *) + (buf - offsetof(struct ng_tag_hookout, tag_data)); + return (hp->tag_len); +} + +static const struct ng_parse_type ng_tag_hookinary_type = { + &ng_parse_bytearray_type, + &ng_tag_hookinary_getLength +}; + +static const struct ng_parse_type ng_tag_hookoutary_type = { + &ng_parse_bytearray_type, + &ng_tag_hookoutary_getLength +}; + +/* Parse type for struct ng_tag_hookin. */ +static const struct ng_parse_struct_field ng_tag_hookin_type_fields[] + = NG_TAG_HOOKIN_TYPE_INFO(&ng_tag_hookinary_type); +static const struct ng_parse_type ng_tag_hookin_type = { + &ng_parse_struct_type, + &ng_tag_hookin_type_fields +}; + +/* Parse type for struct ng_tag_hookout. */ +static const struct ng_parse_struct_field ng_tag_hookout_type_fields[] + = NG_TAG_HOOKOUT_TYPE_INFO(&ng_tag_hookoutary_type); +static const struct ng_parse_type ng_tag_hookout_type = { + &ng_parse_struct_type, + &ng_tag_hookout_type_fields +}; + +#ifdef NG_TAG_DEBUG +/* Parse type for struct ng_tag_hookstat. */ +static const struct ng_parse_struct_field ng_tag_hookstat_type_fields[] + = NG_TAG_HOOKSTAT_TYPE_INFO; +static const struct ng_parse_type ng_tag_hookstat_type = { + &ng_parse_struct_type, + &ng_tag_hookstat_type_fields +}; +#endif + +/* List of commands and how to convert arguments to/from ASCII. */ +static const struct ng_cmdlist ng_tag_cmdlist[] = { + { + NGM_TAG_COOKIE, + NGM_TAG_SET_HOOKIN, + "sethookin", + &ng_tag_hookin_type, + NULL + }, + { + NGM_TAG_COOKIE, + NGM_TAG_GET_HOOKIN, + "gethookin", + &ng_parse_hookbuf_type, + &ng_tag_hookin_type + }, + { + NGM_TAG_COOKIE, + NGM_TAG_SET_HOOKOUT, + "sethookout", + &ng_tag_hookout_type, + NULL + }, + { + NGM_TAG_COOKIE, + NGM_TAG_GET_HOOKOUT, + "gethookout", + &ng_parse_hookbuf_type, + &ng_tag_hookout_type + }, +#ifdef NG_TAG_DEBUG + { + NGM_TAG_COOKIE, + NGM_TAG_GET_STATS, + "getstats", + &ng_parse_hookbuf_type, + &ng_tag_hookstat_type + }, + { + NGM_TAG_COOKIE, + NGM_TAG_CLR_STATS, + "clrstats", + &ng_parse_hookbuf_type, + NULL + }, + { + NGM_TAG_COOKIE, + NGM_TAG_GETCLR_STATS, + "getclrstats", + &ng_parse_hookbuf_type, + &ng_tag_hookstat_type + }, +#endif + { 0 } +}; + +/* Netgraph type descriptor. */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_TAG_NODE_TYPE, + .constructor = ng_tag_constructor, + .rcvmsg = ng_tag_rcvmsg, + .shutdown = ng_tag_shutdown, + .newhook = ng_tag_newhook, + .rcvdata = ng_tag_rcvdata, + .disconnect = ng_tag_disconnect, + .cmdlist = ng_tag_cmdlist, +}; +NETGRAPH_INIT(tag, &typestruct); + +/* + * This are default API structures (initialized to zeroes) which are + * returned in response to GET* messages when no configuration was made. + * One could ask why to have this structures at all when we have + * ng_tag_hookinfo initialized to zero and don't need in and out structures + * at all to operate. Unfortunatelly, we have to return thisHook field + * in response to messages so the fastest and simpliest way is to have + * this default structures and initialize thisHook once at hook creation + * rather than to do it on every response. + */ + +/* Default tag values for a hook that matches nothing. */ +static const struct ng_tag_hookin ng_tag_default_in = { + { '\0' }, /* to be filled in at hook creation time */ + { '\0' }, + { '\0' }, + 0, + 0, + 0, + 0 +}; + +/* Default tag values for a hook that adds nothing */ +static const struct ng_tag_hookout ng_tag_default_out = { + { '\0' }, /* to be filled in at hook creation time */ + 0, + 0, + 0 +}; + +/* + * Node constructor. + * + * We don't keep any per-node private data - we do it on per-hook basis. + */ +static int +ng_tag_constructor(node_p node) +{ + return (0); +} + +/* + * Add a hook. + */ +static int +ng_tag_newhook(node_p node, hook_p hook, const char *name) +{ + hinfo_p hip; + int error; + + /* Create hook private structure. */ + MALLOC(hip, hinfo_p, sizeof(*hip), M_NETGRAPH_TAG, M_WAITOK | M_ZERO); + /* M_WAITOK can't return NULL. */ + NG_HOOK_SET_PRIVATE(hook, hip); + + /* + * After M_ZERO both in and out hook pointers are set to NULL, + * as well as all members and pointers to in and out API + * structures, so we need to set explicitly only thisHook field + * in that structures (after allocating them, of course). + */ + + /* Attach the default IN data. */ + if ((error = ng_tag_setdata_in(hook, &ng_tag_default_in)) != 0) { + FREE(hip, M_NETGRAPH_TAG); + return (error); + } + + /* Attach the default OUT data. */ + if ((error = ng_tag_setdata_out(hook, &ng_tag_default_out)) != 0) { + FREE(hip, M_NETGRAPH_TAG); + return (error); + } + + /* + * Set hook name. This is done only once at hook creation time + * since hook name can't change, rather than to do it on every + * response to messages requesting API structures with data who + * we are etc. + */ + strncpy(hip->in->thisHook, name, sizeof(hip->in->thisHook) - 1); + hip->in->thisHook[sizeof(hip->in->thisHook) - 1] = '\0'; + strncpy(hip->out->thisHook, name, sizeof(hip->out->thisHook) - 1); + hip->out->thisHook[sizeof(hip->out->thisHook) - 1] = '\0'; + return (0); +} + +/* + * Receive a control message. + */ +static int +ng_tag_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct ng_mesg *msg; + struct ng_mesg *resp = NULL; + int error = 0; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_TAG_COOKIE: + switch (msg->header.cmd) { + case NGM_TAG_SET_HOOKIN: + { + struct ng_tag_hookin *const + hp = (struct ng_tag_hookin *)msg->data; + hook_p hook; + + /* Sanity check. */ + if (msg->header.arglen < sizeof(*hp) + || msg->header.arglen != + NG_TAG_HOOKIN_SIZE(hp->tag_len)) + ERROUT(EINVAL); + + /* Find hook. */ + if ((hook = ng_findhook(node, hp->thisHook)) == NULL) + ERROUT(ENOENT); + + /* Set new tag values. */ + if ((error = ng_tag_setdata_in(hook, hp)) != 0) + ERROUT(error); + break; + } + + case NGM_TAG_SET_HOOKOUT: + { + struct ng_tag_hookout *const + hp = (struct ng_tag_hookout *)msg->data; + hook_p hook; + + /* Sanity check. */ + if (msg->header.arglen < sizeof(*hp) + || msg->header.arglen != + NG_TAG_HOOKOUT_SIZE(hp->tag_len)) + ERROUT(EINVAL); + + /* Find hook. */ + if ((hook = ng_findhook(node, hp->thisHook)) == NULL) + ERROUT(ENOENT); + + /* Set new tag values. */ + if ((error = ng_tag_setdata_out(hook, hp)) != 0) + ERROUT(error); + break; + } + + case NGM_TAG_GET_HOOKIN: + { + struct ng_tag_hookin *hp; + hook_p hook; + + /* Sanity check. */ + if (msg->header.arglen == 0) + ERROUT(EINVAL); + msg->data[msg->header.arglen - 1] = '\0'; + + /* Find hook. */ + if ((hook = ng_findhook(node, msg->data)) == NULL) + ERROUT(ENOENT); + + /* Build response. */ + hp = ((hinfo_p)NG_HOOK_PRIVATE(hook))->in; + NG_MKRESPONSE(resp, msg, + NG_TAG_HOOKIN_SIZE(hp->tag_len), M_WAITOK); + /* M_WAITOK can't return NULL. */ + bcopy(hp, resp->data, + NG_TAG_HOOKIN_SIZE(hp->tag_len)); + break; + } + + case NGM_TAG_GET_HOOKOUT: + { + struct ng_tag_hookout *hp; + hook_p hook; + + /* Sanity check. */ + if (msg->header.arglen == 0) + ERROUT(EINVAL); + msg->data[msg->header.arglen - 1] = '\0'; + + /* Find hook. */ + if ((hook = ng_findhook(node, msg->data)) == NULL) + ERROUT(ENOENT); + + /* Build response. */ + hp = ((hinfo_p)NG_HOOK_PRIVATE(hook))->out; + NG_MKRESPONSE(resp, msg, + NG_TAG_HOOKOUT_SIZE(hp->tag_len), M_WAITOK); + /* M_WAITOK can't return NULL. */ + bcopy(hp, resp->data, + NG_TAG_HOOKOUT_SIZE(hp->tag_len)); + break; + } + +#ifdef NG_TAG_DEBUG + case NGM_TAG_GET_STATS: + case NGM_TAG_CLR_STATS: + case NGM_TAG_GETCLR_STATS: + { + struct ng_tag_hookstat *stats; + hook_p hook; + + /* Sanity check. */ + if (msg->header.arglen == 0) + ERROUT(EINVAL); + msg->data[msg->header.arglen - 1] = '\0'; + + /* Find hook. */ + if ((hook = ng_findhook(node, msg->data)) == NULL) + ERROUT(ENOENT); + stats = &((hinfo_p)NG_HOOK_PRIVATE(hook))->stats; + + /* Build response (if desired). */ + if (msg->header.cmd != NGM_TAG_CLR_STATS) { + NG_MKRESPONSE(resp, + msg, sizeof(*stats), M_WAITOK); + /* M_WAITOK can't return NULL. */ + bcopy(stats, resp->data, sizeof(*stats)); + } + + /* Clear stats (if desired). */ + if (msg->header.cmd != NGM_TAG_GET_STATS) + bzero(stats, sizeof(*stats)); + break; + } +#endif /* NG_TAG_DEBUG */ + + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive data on a hook. + * + * Apply the filter, and then drop or forward packet as appropriate. + */ +static int +ng_tag_rcvdata(hook_p hook, item_p item) +{ + struct mbuf *m; + struct m_tag *tag = NULL; + const hinfo_p hip = NG_HOOK_PRIVATE(hook); + uint16_t type, tag_len; + uint32_t cookie; + hinfo_p dhip; + hook_p dest; + int totlen; + int found = 0, error = 0; + + m = NGI_M(item); /* 'item' still owns it.. we are peeking */ + totlen = m->m_pkthdr.len; + +#ifdef NG_TAG_DEBUG + hip->stats.recvFrames++; + hip->stats.recvOctets += totlen; +#endif + + /* Looking up incoming tag. */ + cookie = hip->in_tag_cookie; + type = hip->in_tag_id; + tag_len = hip->in_tag_len; + + /* + * We treat case of all zeroes specially (that is, cookie and + * type are equal to zero), as we assume that such tag + * can never occur in the wild. So we don't waste time trying + * to find such tag (for example, these are zeroes after hook + * creation in default structures). + */ + if ((cookie != 0) || (type != 0)) { + tag = m_tag_locate(m, cookie, type, NULL); + while (tag != NULL) { + if (memcmp((void *)(tag + 1), + hip->in_tag_data, tag_len) == 0) { + found = 1; + break; + } + tag = m_tag_locate(m, cookie, type, tag); + } + } + + /* See if we got a match and find destination hook. */ + if (found) { +#ifdef NG_TAG_DEBUG + hip->stats.recvMatchFrames++; + hip->stats.recvMatchOctets += totlen; +#endif + if (hip->strip) + m_tag_delete(m, tag); + dest = hip->hi_match; + } else + dest = hip->hi_nonmatch; + if (dest == NULL) { + NG_FREE_ITEM(item); + return (0); + } + + /* Deliver frame out destination hook. */ + dhip = NG_HOOK_PRIVATE(dest); + +#ifdef NG_TAG_DEBUG + dhip->stats.xmitOctets += totlen; + dhip->stats.xmitFrames++; +#endif + + cookie = dhip->out_tag_cookie; + type = dhip->out_tag_id; + tag_len = dhip->out_tag_len; + + if ((cookie != 0) || (type != 0)) { + tag = m_tag_alloc(cookie, type, tag_len, M_NOWAIT); + /* XXX may be free the mbuf if tag allocation failed? */ + if (tag != NULL) { + if (tag_len != 0) { + /* copy tag data to its place */ + memcpy((void *)(tag + 1), + dhip->out_tag_data, tag_len); + } + m_tag_prepend(m, tag); + } + } + + NG_FWD_ITEM_HOOK(error, item, dest); + return (error); +} + +/* + * Shutdown processing. + */ +static int +ng_tag_shutdown(node_p node) +{ + NG_NODE_UNREF(node); + return (0); +} + +/* + * Hook disconnection. + * + * We must check all hooks, since they may reference this one. + */ +static int +ng_tag_disconnect(hook_p hook) +{ + const hinfo_p hip = NG_HOOK_PRIVATE(hook); + node_p node = NG_HOOK_NODE(hook); + hook_p hook2; + + KASSERT(hip != NULL, ("%s: null info", __func__)); + + LIST_FOREACH(hook2, &node->nd_hooks, hk_hooks) { + hinfo_p priv = NG_HOOK_PRIVATE(hook2); + + if (priv->hi_match == hook) + priv->hi_match = NULL; + if (priv->hi_nonmatch == hook) + priv->hi_nonmatch = NULL; + } + + FREE(hip->in, M_NETGRAPH_TAG); + FREE(hip->out, M_NETGRAPH_TAG); + FREE(hip, M_NETGRAPH_TAG); + NG_HOOK_SET_PRIVATE(hook, NULL); /* for good measure */ + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && + (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) { + ng_rmnode_self(NG_HOOK_NODE(hook)); + } + return (0); +} + +/************************************************************************ + HELPER STUFF + ************************************************************************/ + +/* + * Set the IN tag values associated with a hook. + */ +static int +ng_tag_setdata_in(hook_p hook, const struct ng_tag_hookin *hp0) +{ + const hinfo_p hip = NG_HOOK_PRIVATE(hook); + struct ng_tag_hookin *hp; + int size; + + /* Make a copy of the tag values and data. */ + size = NG_TAG_HOOKIN_SIZE(hp0->tag_len); + MALLOC(hp, struct ng_tag_hookin *, size, M_NETGRAPH_TAG, M_WAITOK); + /* M_WAITOK can't return NULL. */ + bcopy(hp0, hp, size); + + /* Free previous tag, if any, and assign new one. */ + if (hip->in != NULL) + FREE(hip->in, M_NETGRAPH_TAG); + hip->in = hp; + + /* + * Resolve hook names to pointers. + * + * As ng_findhook() is expensive operation to do it on every packet + * after tag matching check, we do it here and use resolved pointers + * where appropriate. + * + * XXX The drawback is that user can configure a hook to use + * ifMatch/ifNotMatch hooks that do not yet exist and will be added + * by user later, so that resolved pointers will be NULL even + * if the hook already exists, causing node to drop packets and + * user to report bugs. We could do check for this situation on + * every hook creation with pointers correction, but that involves + * re-resolving for all pointers in all hooks, up to O(n^2) operations, + * so we better document this in man page for user not to do + * configuration before creating all hooks. + */ + hip->hi_match = ng_findhook(NG_HOOK_NODE(hook), hip->in->ifMatch); + hip->hi_nonmatch = ng_findhook(NG_HOOK_NODE(hook), hip->in->ifNotMatch); + + /* Fill internal values from API structures. */ + hip->in_tag_cookie = hip->in->tag_cookie; + hip->in_tag_id = hip->in->tag_id; + hip->in_tag_len = hip->in->tag_len; + hip->strip = hip->in->strip; + hip->in_tag_data = (void*)(hip->in->tag_data); + return (0); +} + +/* + * Set the OUT tag values associated with a hook. + */ +static int +ng_tag_setdata_out(hook_p hook, const struct ng_tag_hookout *hp0) +{ + const hinfo_p hip = NG_HOOK_PRIVATE(hook); + struct ng_tag_hookout *hp; + int size; + + /* Make a copy of the tag values and data. */ + size = NG_TAG_HOOKOUT_SIZE(hp0->tag_len); + MALLOC(hp, struct ng_tag_hookout *, size, M_NETGRAPH_TAG, M_WAITOK); + /* M_WAITOK can't return NULL. */ + bcopy(hp0, hp, size); + + /* Free previous tag, if any, and assign new one. */ + if (hip->out != NULL) + FREE(hip->out, M_NETGRAPH_TAG); + hip->out = hp; + + /* Fill internal values from API structures. */ + hip->out_tag_cookie = hip->out->tag_cookie; + hip->out_tag_id = hip->out->tag_id; + hip->out_tag_len = hip->out->tag_len; + hip->out_tag_data = (void*)(hip->out->tag_data); + return (0); +} + diff --git a/sys/netgraph7/ng_tag.h b/sys/netgraph7/ng_tag.h new file mode 100644 index 0000000000..14744b7f09 --- /dev/null +++ b/sys/netgraph7/ng_tag.h @@ -0,0 +1,130 @@ +/*- + * Copyright (c) 2006 Vadim Goncharov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_tag.h,v 1.1 2006/06/27 12:45:28 glebius Exp $ + */ + +#ifndef _NETGRAPH_NG_TAG_H_ +#define _NETGRAPH_NG_TAG_H_ + +/* Node type name and magic cookie. */ +#define NG_TAG_NODE_TYPE "tag" +#define NGM_TAG_COOKIE 1149771193 + +/* + * The types of tag_cookie, tag_len and tag_id in structures below + * must be the same as corresponding members m_tag_cookie, m_tag_len + * and m_tag_id in struct m_tag (defined in ). + */ + +/* Tag match structure for every (input) hook. */ +struct ng_tag_hookin { + char thisHook[NG_HOOKSIZ]; /* name of hook */ + char ifMatch[NG_HOOKSIZ]; /* match dest hook */ + char ifNotMatch[NG_HOOKSIZ]; /* !match dest hook */ + uint8_t strip; /* strip tag if found */ + uint32_t tag_cookie; /* ABI/Module ID */ + uint16_t tag_id; /* tag ID */ + uint16_t tag_len; /* length of data */ + uint8_t tag_data[0]; /* tag data */ +}; + +/* Tag set structure for every (output) hook. */ +struct ng_tag_hookout { + char thisHook[NG_HOOKSIZ]; /* name of hook */ + uint32_t tag_cookie; /* ABI/Module ID */ + uint16_t tag_id; /* tag ID */ + uint16_t tag_len; /* length of data */ + uint8_t tag_data[0]; /* tag data */ +}; + +#define NG_TAG_HOOKIN_SIZE(taglen) \ + (sizeof(struct ng_tag_hookin) + (taglen)) + +#define NG_TAG_HOOKOUT_SIZE(taglen) \ + (sizeof(struct ng_tag_hookout) + (taglen)) + +/* Keep this in sync with the above structures definitions. */ +#define NG_TAG_HOOKIN_TYPE_INFO(tdtype) { \ + { "thisHook", &ng_parse_hookbuf_type }, \ + { "ifMatch", &ng_parse_hookbuf_type }, \ + { "ifNotMatch", &ng_parse_hookbuf_type }, \ + { "strip", &ng_parse_uint8_type }, \ + { "tag_cookie", &ng_parse_uint32_type }, \ + { "tag_id", &ng_parse_uint16_type }, \ + { "tag_len", &ng_parse_uint16_type }, \ + { "tag_data", (tdtype) }, \ + { NULL } \ +} + +#define NG_TAG_HOOKOUT_TYPE_INFO(tdtype) { \ + { "thisHook", &ng_parse_hookbuf_type }, \ + { "tag_cookie", &ng_parse_uint32_type }, \ + { "tag_id", &ng_parse_uint16_type }, \ + { "tag_len", &ng_parse_uint16_type }, \ + { "tag_data", (tdtype) }, \ + { NULL } \ +} + +#ifdef NG_TAG_DEBUG + +/* Statistics structure for one hook. */ +struct ng_tag_hookstat { + uint64_t recvFrames; + uint64_t recvOctets; + uint64_t recvMatchFrames; + uint64_t recvMatchOctets; + uint64_t xmitFrames; + uint64_t xmitOctets; +}; + +/* Keep this in sync with the above structure definition. */ +#define NG_TAG_HOOKSTAT_TYPE_INFO { \ + { "recvFrames", &ng_parse_uint64_type }, \ + { "recvOctets", &ng_parse_uint64_type }, \ + { "recvMatchFrames", &ng_parse_uint64_type }, \ + { "recvMatchOctets", &ng_parse_uint64_type }, \ + { "xmitFrames", &ng_parse_uint64_type }, \ + { "xmitOctets", &ng_parse_uint64_type }, \ + { NULL } \ +} + +#endif /* NG_TAG_DEBUG */ + +/* Netgraph commands. */ +enum { + NGM_TAG_SET_HOOKIN = 1, /* supply a struct ng_tag_hookin */ + NGM_TAG_GET_HOOKIN, /* returns a struct ng_tag_hookin */ + NGM_TAG_SET_HOOKOUT, /* supply a struct ng_tag_hookout */ + NGM_TAG_GET_HOOKOUT, /* returns a struct ng_tag_hookout */ +#ifdef NG_TAG_DEBUG + NGM_TAG_GET_STATS, /* supply name as char[NG_HOOKSIZ] */ + NGM_TAG_CLR_STATS, /* supply name as char[NG_HOOKSIZ] */ + NGM_TAG_GETCLR_STATS, /* supply name as char[NG_HOOKSIZ] */ +#endif +}; + +#endif /* _NETGRAPH_NG_TAG_H_ */ diff --git a/sys/netgraph7/ng_tcpmss.c b/sys/netgraph7/ng_tcpmss.c new file mode 100644 index 0000000000..8e4098921c --- /dev/null +++ b/sys/netgraph7/ng_tcpmss.c @@ -0,0 +1,443 @@ +/*- + * ng_tcpmss.c + * + * Copyright (c) 2004, Alexey Popov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This software includes fragments of the following programs: + * tcpmssd Ruslan Ermilov + * + * $FreeBSD: src/sys/netgraph/ng_tcpmss.c,v 1.4 2007/01/15 05:01:31 glebius Exp $ + */ + +/* + * This node is netgraph tool for workaround of PMTUD problem. It acts + * like filter for IP packets. If configured, it reduces MSS of TCP SYN + * packets. + * + * Configuration can be done by sending NGM_TCPMSS_CONFIG message. The + * message sets filter for incoming packets on hook 'inHook'. Packet's + * TCP MSS field is lowered to 'maxMSS' parameter and resulting packet + * is sent to 'outHook'. + * + * XXX: statistics are updated not atomically, so they may broke on SMP. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +/* Per hook info. */ +typedef struct { + hook_p outHook; + struct ng_tcpmss_hookstat stats; +} *hpriv_p; + +/* Netgraph methods. */ +static ng_constructor_t ng_tcpmss_constructor; +static ng_rcvmsg_t ng_tcpmss_rcvmsg; +static ng_newhook_t ng_tcpmss_newhook; +static ng_rcvdata_t ng_tcpmss_rcvdata; +static ng_disconnect_t ng_tcpmss_disconnect; + +static int correct_mss(struct tcphdr *, int, uint16_t, int); + +/* Parse type for struct ng_tcpmss_hookstat. */ +static const struct ng_parse_struct_field ng_tcpmss_hookstat_type_fields[] + = NG_TCPMSS_HOOKSTAT_INFO; +static const struct ng_parse_type ng_tcpmss_hookstat_type = { + &ng_parse_struct_type, + &ng_tcpmss_hookstat_type_fields +}; + +/* Parse type for struct ng_tcpmss_config. */ +static const struct ng_parse_struct_field ng_tcpmss_config_type_fields[] + = NG_TCPMSS_CONFIG_INFO; +static const struct ng_parse_type ng_tcpmss_config_type = { + &ng_parse_struct_type, + ng_tcpmss_config_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII. */ +static const struct ng_cmdlist ng_tcpmss_cmds[] = { + { + NGM_TCPMSS_COOKIE, + NGM_TCPMSS_GET_STATS, + "getstats", + &ng_parse_hookbuf_type, + &ng_tcpmss_hookstat_type + }, + { + NGM_TCPMSS_COOKIE, + NGM_TCPMSS_CLR_STATS, + "clrstats", + &ng_parse_hookbuf_type, + NULL + }, + { + NGM_TCPMSS_COOKIE, + NGM_TCPMSS_GETCLR_STATS, + "getclrstats", + &ng_parse_hookbuf_type, + &ng_tcpmss_hookstat_type + }, + { + NGM_TCPMSS_COOKIE, + NGM_TCPMSS_CONFIG, + "config", + &ng_tcpmss_config_type, + NULL + }, + { 0 } +}; + +/* Netgraph type descriptor. */ +static struct ng_type ng_tcpmss_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_TCPMSS_NODE_TYPE, + .constructor = ng_tcpmss_constructor, + .rcvmsg = ng_tcpmss_rcvmsg, + .newhook = ng_tcpmss_newhook, + .rcvdata = ng_tcpmss_rcvdata, + .disconnect = ng_tcpmss_disconnect, + .cmdlist = ng_tcpmss_cmds, +}; + +NETGRAPH_INIT(tcpmss, &ng_tcpmss_typestruct); + +#define ERROUT(x) { error = (x); goto done; } + +/* + * Node constructor. No special actions required. + */ +static int +ng_tcpmss_constructor(node_p node) +{ + return (0); +} + +/* + * Add a hook. Any unique name is OK. + */ +static int +ng_tcpmss_newhook(node_p node, hook_p hook, const char *name) +{ + hpriv_p priv; + + MALLOC(priv, hpriv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + + NG_HOOK_SET_PRIVATE(hook, priv); + + return (0); +} + +/* + * Receive a control message. + */ +static int +ng_tcpmss_rcvmsg +(node_p node, item_p item, hook_p lasthook) +{ + struct ng_mesg *msg, *resp = NULL; + int error = 0; + + NGI_GET_MSG(item, msg); + + switch (msg->header.typecookie) { + case NGM_TCPMSS_COOKIE: + switch (msg->header.cmd) { + case NGM_TCPMSS_GET_STATS: + case NGM_TCPMSS_CLR_STATS: + case NGM_TCPMSS_GETCLR_STATS: + { + hook_p hook; + hpriv_p priv; + + /* Check that message is long enough. */ + if (msg->header.arglen != NG_HOOKSIZ) + ERROUT(EINVAL); + + /* Find this hook. */ + hook = ng_findhook(node, (char *)msg->data); + if (hook == NULL) + ERROUT(ENOENT); + + priv = NG_HOOK_PRIVATE(hook); + + /* Create response. */ + if (msg->header.cmd != NGM_TCPMSS_CLR_STATS) { + NG_MKRESPONSE(resp, msg, + sizeof(struct ng_tcpmss_hookstat), M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + bcopy(&priv->stats, resp->data, + sizeof(struct ng_tcpmss_hookstat)); + } + + if (msg->header.cmd != NGM_TCPMSS_GET_STATS) + bzero(&priv->stats, + sizeof(struct ng_tcpmss_hookstat)); + break; + } + case NGM_TCPMSS_CONFIG: + { + struct ng_tcpmss_config *set; + hook_p in, out; + hpriv_p priv; + + /* Check that message is long enough. */ + if (msg->header.arglen != + sizeof(struct ng_tcpmss_config)) + ERROUT(EINVAL); + + set = (struct ng_tcpmss_config *)msg->data; + in = ng_findhook(node, set->inHook); + out = ng_findhook(node, set->outHook); + if (in == NULL || out == NULL) + ERROUT(ENOENT); + + /* Configure MSS hack. */ + priv = NG_HOOK_PRIVATE(in); + priv->outHook = out; + priv->stats.maxMSS = set->maxMSS; + + break; + } + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } + +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + + return (error); +} + +/* + * Receive data on a hook, and hack MSS. + * + */ +static int +ng_tcpmss_rcvdata(hook_p hook, item_p item) +{ + hpriv_p priv = NG_HOOK_PRIVATE(hook); + struct mbuf *m = NULL; + struct ip *ip; + struct tcphdr *tcp; + int iphlen, tcphlen, pktlen; + int pullup_len = 0; + int error = 0; + + /* Drop packets if filter is not configured on this hook. */ + if (priv->outHook == NULL) + goto done; + + NGI_GET_M(item, m); + + /* Update stats on incoming hook. */ + pktlen = m->m_pkthdr.len; + priv->stats.Octets += pktlen; + priv->stats.Packets++; + + /* Check whether we configured to fix MSS. */ + if (priv->stats.maxMSS == 0) + goto send; + +#define M_CHECK(length) do { \ + pullup_len += length; \ + if ((m)->m_pkthdr.len < pullup_len) \ + goto send; \ + if ((m)->m_len < pullup_len && \ + (((m) = m_pullup((m), pullup_len)) == NULL)) \ + ERROUT(ENOBUFS); \ + } while (0) + + /* Check mbuf packet size and arrange for IP header. */ + M_CHECK(sizeof(struct ip)); + ip = mtod(m, struct ip *); + + /* Check IP version. */ + if (ip->ip_v != IPVERSION) + ERROUT(EINVAL); + + /* Check IP header length. */ + iphlen = ip->ip_hl << 2; + if (iphlen < sizeof(struct ip) || iphlen > pktlen ) + ERROUT(EINVAL); + + /* Check if it is TCP. */ + if (!(ip->ip_p == IPPROTO_TCP)) + goto send; + + /* Check mbuf packet size and arrange for IP+TCP header */ + M_CHECK(iphlen - sizeof(struct ip) + sizeof(struct tcphdr)); + ip = mtod(m, struct ip *); + tcp = (struct tcphdr *)((caddr_t )ip + iphlen); + + /* Check TCP header length. */ + tcphlen = tcp->th_off << 2; + if (tcphlen < sizeof(struct tcphdr) || tcphlen > pktlen - iphlen) + ERROUT(EINVAL); + + /* Check SYN packet and has options. */ + if (!(tcp->th_flags & TH_SYN) || tcphlen == sizeof(struct tcphdr)) + goto send; + + /* Update SYN stats. */ + priv->stats.SYNPkts++; + + M_CHECK(tcphlen - sizeof(struct tcphdr)); + ip = mtod(m, struct ip *); + tcp = (struct tcphdr *)((caddr_t )ip + iphlen); + +#undef M_CHECK + + /* Fix MSS and update stats. */ + if (correct_mss(tcp, tcphlen, priv->stats.maxMSS, + m->m_pkthdr.csum_flags)) + priv->stats.FixedPkts++; + +send: + /* Deliver frame out destination hook. */ + NG_FWD_NEW_DATA(error, item, priv->outHook, m); + + return (error); + +done: + NG_FREE_ITEM(item); + NG_FREE_M(m); + + return (error); +} + +/* + * Hook disconnection. + * We must check all hooks, since they may reference this one. + */ +static int +ng_tcpmss_disconnect(hook_p hook) +{ + node_p node = NG_HOOK_NODE(hook); + hook_p hook2; + + LIST_FOREACH(hook2, &node->nd_hooks, hk_hooks) { + hpriv_p priv = NG_HOOK_PRIVATE(hook2); + + if (priv->outHook == hook) + priv->outHook = NULL; + } + + FREE(NG_HOOK_PRIVATE(hook), M_NETGRAPH); + + if (NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + ng_rmnode_self(NG_HOOK_NODE(hook)); + + return (0); +} + +/* + * Code from tcpmssd. + */ + +/*- + * The following macro is used to update an + * internet checksum. "acc" is a 32-bit + * accumulation of all the changes to the + * checksum (adding in old 16-bit words and + * subtracting out new words), and "cksum" + * is the checksum value to be updated. + */ +#define TCPMSS_ADJUST_CHECKSUM(acc, cksum) do { \ + acc += cksum; \ + if (acc < 0) { \ + acc = -acc; \ + acc = (acc >> 16) + (acc & 0xffff); \ + acc += acc >> 16; \ + cksum = (u_short) ~acc; \ + } else { \ + acc = (acc >> 16) + (acc & 0xffff); \ + acc += acc >> 16; \ + cksum = (u_short) acc; \ + } \ +} while (0); + +static int +correct_mss(struct tcphdr *tc, int hlen, uint16_t maxmss, int flags) +{ + int olen, optlen; + u_char *opt; + uint16_t *mss; + int accumulate; + int res = 0; + + for (olen = hlen - sizeof(struct tcphdr), opt = (u_char *)(tc + 1); + olen > 0; olen -= optlen, opt += optlen) { + if (*opt == TCPOPT_EOL) + break; + else if (*opt == TCPOPT_NOP) + optlen = 1; + else { + optlen = *(opt + 1); + if (optlen <= 0 || optlen > olen) + break; + if (*opt == TCPOPT_MAXSEG) { + if (optlen != TCPOLEN_MAXSEG) + continue; + mss = (uint16_t *)(opt + 2); + if (ntohs(*mss) > maxmss) { + accumulate = *mss; + *mss = htons(maxmss); + accumulate -= *mss; + if ((flags & CSUM_TCP) == 0) + TCPMSS_ADJUST_CHECKSUM(accumulate, tc->th_sum); + res = 1; + } + } + } + } + return (res); +} diff --git a/sys/netgraph7/ng_tcpmss.h b/sys/netgraph7/ng_tcpmss.h new file mode 100644 index 0000000000..bec96ef3bd --- /dev/null +++ b/sys/netgraph7/ng_tcpmss.h @@ -0,0 +1,82 @@ +/*- + * ng_tcpmss.h + * + * Copyright (c) 2004, Alexey Popov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/netgraph/ng_tcpmss.h,v 1.1 2005/06/10 08:02:34 glebius Exp $ + */ + +#ifndef _NETGRAPH_TCPMSS_H_ +#define _NETGRAPH_TCPMSS_H_ + +/* Node type name and magic cookie */ +#define NG_TCPMSS_NODE_TYPE "tcpmss" +#define NGM_TCPMSS_COOKIE 1097623478 + +/* Statistics structure for one hook. */ +struct ng_tcpmss_hookstat { + uint64_t Octets; + uint64_t Packets; + uint16_t maxMSS; + uint64_t SYNPkts; + uint64_t FixedPkts; +}; + +/* Keep this in sync with the above structure definition. */ +#define NG_TCPMSS_HOOKSTAT_INFO { \ + { "Octets", &ng_parse_uint64_type }, \ + { "Packets", &ng_parse_uint64_type }, \ + { "maxMSS", &ng_parse_uint16_type }, \ + { "SYNPkts", &ng_parse_uint64_type }, \ + { "FixedPkts", &ng_parse_uint64_type }, \ + { NULL } \ +} + + +/* Structure for NGM_TCPMSS_CONFIG. */ +struct ng_tcpmss_config { + char inHook[NG_HOOKSIZ]; + char outHook[NG_HOOKSIZ]; + uint16_t maxMSS; +}; + +/* Keep this in sync with the above structure definition. */ +#define NG_TCPMSS_CONFIG_INFO { \ + { "inHook", &ng_parse_hookbuf_type }, \ + { "outHook", &ng_parse_hookbuf_type }, \ + { "maxMSS", &ng_parse_uint16_type }, \ + { NULL } \ +} + +/* Netgraph commands */ +enum { + NGM_TCPMSS_GET_STATS = 1, /* Get stats. */ + NGM_TCPMSS_CLR_STATS, /* Clear stats. */ + NGM_TCPMSS_GETCLR_STATS, /* "Atomically" get and clear stats. */ + NGM_TCPMSS_CONFIG /* Set configuration. */ +}; + +#endif /* _NETGRAPH_TCPMSS_H_ */ diff --git a/sys/netgraph7/ng_tee.c b/sys/netgraph7/ng_tee.c new file mode 100644 index 0000000000..c1ce9c871a --- /dev/null +++ b/sys/netgraph7/ng_tee.c @@ -0,0 +1,397 @@ + +/* + * ng_tee.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer + * + * $FreeBSD: src/sys/netgraph/ng_tee.c,v 1.35 2008/02/24 10:13:32 mav Exp $ + * $Whistle: ng_tee.c,v 1.18 1999/11/01 09:24:52 julian Exp $ + */ + +/* + * This node is like the tee(1) command and is useful for ``snooping.'' + * It has 4 hooks: left, right, left2right, and right2left. Data + * entering from the right is passed to the left and duplicated on + * right2left, and data entering from the left is passed to the right + * and duplicated on left2right. Data entering from left2right is + * sent to left, and data from right2left to right. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Per hook info */ +struct hookinfo { + hook_p hook; + struct hookinfo *dest, *dup; + struct ng_tee_hookstat stats; +}; +typedef struct hookinfo *hi_p; + +/* Per node info */ +struct privdata { + struct hookinfo left; + struct hookinfo right; + struct hookinfo left2right; + struct hookinfo right2left; +}; +typedef struct privdata *sc_p; + +/* Netgraph methods */ +static ng_constructor_t ng_tee_constructor; +static ng_rcvmsg_t ng_tee_rcvmsg; +static ng_close_t ng_tee_close; +static ng_shutdown_t ng_tee_shutdown; +static ng_newhook_t ng_tee_newhook; +static ng_rcvdata_t ng_tee_rcvdata; +static ng_disconnect_t ng_tee_disconnect; + +/* Parse type for struct ng_tee_hookstat */ +static const struct ng_parse_struct_field ng_tee_hookstat_type_fields[] + = NG_TEE_HOOKSTAT_INFO; +static const struct ng_parse_type ng_tee_hookstat_type = { + &ng_parse_struct_type, + &ng_tee_hookstat_type_fields +}; + +/* Parse type for struct ng_tee_stats */ +static const struct ng_parse_struct_field ng_tee_stats_type_fields[] + = NG_TEE_STATS_INFO(&ng_tee_hookstat_type); +static const struct ng_parse_type ng_tee_stats_type = { + &ng_parse_struct_type, + &ng_tee_stats_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_tee_cmds[] = { + { + NGM_TEE_COOKIE, + NGM_TEE_GET_STATS, + "getstats", + NULL, + &ng_tee_stats_type + }, + { + NGM_TEE_COOKIE, + NGM_TEE_CLR_STATS, + "clrstats", + NULL, + NULL + }, + { + NGM_TEE_COOKIE, + NGM_TEE_GETCLR_STATS, + "getclrstats", + NULL, + &ng_tee_stats_type + }, + { 0 } +}; + +/* Netgraph type descriptor */ +static struct ng_type ng_tee_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_TEE_NODE_TYPE, + .constructor = ng_tee_constructor, + .rcvmsg = ng_tee_rcvmsg, + .close = ng_tee_close, + .shutdown = ng_tee_shutdown, + .newhook = ng_tee_newhook, + .rcvdata = ng_tee_rcvdata, + .disconnect = ng_tee_disconnect, + .cmdlist = ng_tee_cmds, +}; +NETGRAPH_INIT(tee, &ng_tee_typestruct); + +/* + * Node constructor + */ +static int +ng_tee_constructor(node_p node) +{ + sc_p privdata; + + MALLOC(privdata, sc_p, sizeof(*privdata), M_NETGRAPH, M_NOWAIT|M_ZERO); + if (privdata == NULL) + return (ENOMEM); + + NG_NODE_SET_PRIVATE(node, privdata); + return (0); +} + +/* + * Add a hook + */ +static int +ng_tee_newhook(node_p node, hook_p hook, const char *name) +{ + sc_p privdata = NG_NODE_PRIVATE(node); + hi_p hinfo; + + /* Precalculate internal pathes. */ + if (strcmp(name, NG_TEE_HOOK_RIGHT) == 0) { + hinfo = &privdata->right; + if (privdata->left.dest) + privdata->left.dup = privdata->left.dest; + privdata->left.dest = hinfo; + privdata->right2left.dest = hinfo; + } else if (strcmp(name, NG_TEE_HOOK_LEFT) == 0) { + hinfo = &privdata->left; + if (privdata->right.dest) + privdata->right.dup = privdata->right.dest; + privdata->right.dest = hinfo; + privdata->left2right.dest = hinfo; + } else if (strcmp(name, NG_TEE_HOOK_RIGHT2LEFT) == 0) { + hinfo = &privdata->right2left; + if (privdata->right.dest) + privdata->right.dup = hinfo; + else + privdata->right.dest = hinfo; + } else if (strcmp(name, NG_TEE_HOOK_LEFT2RIGHT) == 0) { + hinfo = &privdata->left2right; + if (privdata->left.dest) + privdata->left.dup = hinfo; + else + privdata->left.dest = hinfo; + } else + return (EINVAL); + hinfo->hook = hook; + bzero(&hinfo->stats, sizeof(hinfo->stats)); + NG_HOOK_SET_PRIVATE(hook, hinfo); + return (0); +} + +/* + * Receive a control message + */ +static int +ng_tee_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const sc_p sc = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_TEE_COOKIE: + switch (msg->header.cmd) { + case NGM_TEE_GET_STATS: + case NGM_TEE_CLR_STATS: + case NGM_TEE_GETCLR_STATS: + { + struct ng_tee_stats *stats; + + if (msg->header.cmd != NGM_TEE_CLR_STATS) { + NG_MKRESPONSE(resp, msg, + sizeof(*stats), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + goto done; + } + stats = (struct ng_tee_stats *)resp->data; + bcopy(&sc->right.stats, &stats->right, + sizeof(stats->right)); + bcopy(&sc->left.stats, &stats->left, + sizeof(stats->left)); + bcopy(&sc->right2left.stats, &stats->right2left, + sizeof(stats->right2left)); + bcopy(&sc->left2right.stats, &stats->left2right, + sizeof(stats->left2right)); + } + if (msg->header.cmd != NGM_TEE_GET_STATS) { + bzero(&sc->right.stats, + sizeof(sc->right.stats)); + bzero(&sc->left.stats, + sizeof(sc->left.stats)); + bzero(&sc->right2left.stats, + sizeof(sc->right2left.stats)); + bzero(&sc->left2right.stats, + sizeof(sc->left2right.stats)); + } + break; + } + default: + error = EINVAL; + break; + } + break; + case NGM_FLOW_COOKIE: + if (lasthook == sc->left.hook || lasthook == sc->right.hook) { + hi_p const hinfo = NG_HOOK_PRIVATE(lasthook); + if (hinfo && hinfo->dest) { + NGI_MSG(item) = msg; + NG_FWD_ITEM_HOOK(error, item, hinfo->dest->hook); + return (error); + } + } + break; + default: + error = EINVAL; + break; + } +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive data on a hook + * + * If data comes in the right link send a copy out right2left, and then + * send the original onwards out through the left link. + * Do the opposite for data coming in from the left link. + * Data coming in right2left or left2right is forwarded + * on through the appropriate destination hook as if it had come + * from the other side. + */ +static int +ng_tee_rcvdata(hook_p hook, item_p item) +{ + const hi_p hinfo = NG_HOOK_PRIVATE(hook); + hi_p h; + int error = 0; + struct mbuf *m; + + m = NGI_M(item); + + /* Update stats on incoming hook */ + hinfo->stats.inOctets += m->m_pkthdr.len; + hinfo->stats.inFrames++; + + /* Duplicate packet if requried */ + if (hinfo->dup) { + struct mbuf *m2; + + /* Copy packet (failure will not stop the original)*/ + m2 = m_dup(m, M_DONTWAIT); + if (m2) { + /* Deliver duplicate */ + h = hinfo->dup; + NG_SEND_DATA_ONLY(error, h->hook, m2); + if (error == 0) { + h->stats.outOctets += m->m_pkthdr.len; + h->stats.outFrames++; + } + } + } + /* Deliver frame out destination hook */ + if (hinfo->dest) { + h = hinfo->dest; + h->stats.outOctets += m->m_pkthdr.len; + h->stats.outFrames++; + NG_FWD_ITEM_HOOK(error, item, h->hook); + } else + NG_FREE_ITEM(item); + return (error); +} + +/* + * We are going to be shut down soon + * + * If we have both a left and right hook, then we probably want to extricate + * ourselves and leave the two peers still linked to each other. Otherwise we + * should just shut down as a normal node would. + */ +static int +ng_tee_close(node_p node) +{ + const sc_p privdata = NG_NODE_PRIVATE(node); + + if (privdata->left.hook && privdata->right.hook) + ng_bypass(privdata->left.hook, privdata->right.hook); + + return (0); +} + +/* + * Shutdown processing + */ +static int +ng_tee_shutdown(node_p node) +{ + const sc_p privdata = NG_NODE_PRIVATE(node); + + NG_NODE_SET_PRIVATE(node, NULL); + FREE(privdata, M_NETGRAPH); + NG_NODE_UNREF(node); + return (0); +} + +/* + * Hook disconnection + */ +static int +ng_tee_disconnect(hook_p hook) +{ + sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + hi_p const hinfo = NG_HOOK_PRIVATE(hook); + + KASSERT(hinfo != NULL, ("%s: null info", __func__)); + hinfo->hook = NULL; + + /* Recalculate internal pathes. */ + if (sc->left.dest == hinfo) { + sc->left.dest = sc->left.dup; + sc->left.dup = NULL; + } else if (sc->left.dup == hinfo) + sc->left.dup = NULL; + if (sc->right.dest == hinfo) { + sc->right.dest = sc->right.dup; + sc->right.dup = NULL; + } else if (sc->right.dup == hinfo) + sc->right.dup = NULL; + if (sc->left2right.dest == hinfo) + sc->left2right.dest = NULL; + if (sc->right2left.dest == hinfo) + sc->right2left.dest = NULL; + + /* Die when last hook disconnected. */ + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && + NG_NODE_IS_VALID(NG_HOOK_NODE(hook))) + ng_rmnode_self(NG_HOOK_NODE(hook)); + return (0); +} diff --git a/sys/netgraph7/ng_tee.h b/sys/netgraph7/ng_tee.h new file mode 100644 index 0000000000..da3e22096e --- /dev/null +++ b/sys/netgraph7/ng_tee.h @@ -0,0 +1,99 @@ + +/* + * ng_tee.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_tee.h,v 1.8 2005/01/07 01:45:39 imp Exp $ + * $Whistle: ng_tee.h,v 1.2 1999/01/20 00:22:14 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_TEE_H_ +#define _NETGRAPH_NG_TEE_H_ + +/* Node type name and magic cookie */ +#define NG_TEE_NODE_TYPE "tee" +#define NGM_TEE_COOKIE 916107047 + +/* Hook names */ +#define NG_TEE_HOOK_RIGHT "right" +#define NG_TEE_HOOK_LEFT "left" +#define NG_TEE_HOOK_RIGHT2LEFT "right2left" +#define NG_TEE_HOOK_LEFT2RIGHT "left2right" + +/* Statistics structure for one hook */ +struct ng_tee_hookstat { + u_int64_t inOctets; + u_int64_t inFrames; + u_int64_t outOctets; + u_int64_t outFrames; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_TEE_HOOKSTAT_INFO { \ + { "inOctets", &ng_parse_uint64_type }, \ + { "inFrames", &ng_parse_uint64_type }, \ + { "outOctets", &ng_parse_uint64_type }, \ + { "outFrames", &ng_parse_uint64_type }, \ + { NULL } \ +} + +/* Statistics structure returned by NGM_TEE_GET_STATS */ +struct ng_tee_stats { + struct ng_tee_hookstat right; + struct ng_tee_hookstat left; + struct ng_tee_hookstat right2left; + struct ng_tee_hookstat left2right; +}; + +/* Keep this in sync with the above structure definition */ +#define NG_TEE_STATS_INFO(hstype) { \ + { "right", (hstype) }, \ + { "left", (hstype) }, \ + { "right2left", (hstype) }, \ + { "left2right", (hstype) }, \ + { NULL } \ +} + +/* Netgraph commands */ +enum { + NGM_TEE_GET_STATS = 1, /* get stats */ + NGM_TEE_CLR_STATS, /* clear stats */ + NGM_TEE_GETCLR_STATS, /* atomically get and clear stats */ +}; + +#endif /* _NETGRAPH_NG_TEE_H_ */ diff --git a/sys/netgraph7/ng_tty.c b/sys/netgraph7/ng_tty.c new file mode 100644 index 0000000000..69717f51e9 --- /dev/null +++ b/sys/netgraph7/ng_tty.c @@ -0,0 +1,702 @@ +/* + * ng_tty.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_tty.c,v 1.37 2006/11/06 13:42:03 rwatson Exp $ + * $Whistle: ng_tty.c,v 1.21 1999/11/01 09:24:52 julian Exp $ + */ + +/* + * This file implements a terminal line discipline that is also a + * netgraph node. Installing this line discipline on a terminal device + * instantiates a new netgraph node of this type, which allows access + * to the device via the "hook" hook of the node. + * + * Once the line discipline is installed, you can find out the name + * of the corresponding netgraph node via a NGIOCGINFO ioctl(). + * + * Incoming characters are delievered to the hook one at a time, each + * in its own mbuf. You may optionally define a ``hotchar,'' which causes + * incoming characters to be buffered up until either the hotchar is + * seen or the mbuf is full (MHLEN bytes). Then all buffered characters + * are immediately delivered. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +/* Misc defs */ +#define MAX_MBUFQ 3 /* Max number of queued mbufs */ +#define NGT_HIWATER 400 /* High water mark on output */ + +/* Per-node private info */ +struct ngt_sc { + struct tty *tp; /* Terminal device */ + node_p node; /* Netgraph node */ + hook_p hook; /* Netgraph hook */ + struct ifqueue outq; /* Queue of outgoing data */ + struct mbuf *m; /* Incoming data buffer */ + short hotchar; /* Hotchar, or -1 if none */ + u_int flags; /* Flags */ + struct callout chand; /* See man timeout(9) */ +}; +typedef struct ngt_sc *sc_p; + +/* Flags */ +#define FLG_DEBUG 0x0002 +#define FLG_DIE 0x0004 + +/* Line discipline methods */ +static int ngt_open(struct cdev *dev, struct tty *tp); +static int ngt_close(struct tty *tp, int flag); +static int ngt_read(struct tty *tp, struct uio *uio, int flag); +static int ngt_write(struct tty *tp, struct uio *uio, int flag); +static int ngt_tioctl(struct tty *tp, + u_long cmd, caddr_t data, int flag, struct thread *); +static int ngt_input(int c, struct tty *tp); +static int ngt_start(struct tty *tp); + +/* Netgraph methods */ +static ng_constructor_t ngt_constructor; +static ng_rcvmsg_t ngt_rcvmsg; +static ng_shutdown_t ngt_shutdown; +static ng_newhook_t ngt_newhook; +static ng_connect_t ngt_connect; +static ng_rcvdata_t ngt_rcvdata; +static ng_disconnect_t ngt_disconnect; +static int ngt_mod_event(module_t mod, int event, void *data); + +/* Other stuff */ +static void ngt_timeout(node_p node, hook_p hook, void *arg1, int arg2); + +#define ERROUT(x) do { error = (x); goto done; } while (0) + +/* Line discipline descriptor */ +static struct linesw ngt_disc = { + .l_open = ngt_open, + .l_close = ngt_close, + .l_read = ngt_read, + .l_write = ngt_write, + .l_ioctl = ngt_tioctl, + .l_rint = ngt_input, + .l_start = ngt_start, + .l_modem = ttymodem, +}; + +/* Netgraph node type descriptor */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_TTY_NODE_TYPE, + .mod_event = ngt_mod_event, + .constructor = ngt_constructor, + .rcvmsg = ngt_rcvmsg, + .shutdown = ngt_shutdown, + .newhook = ngt_newhook, + .connect = ngt_connect, + .rcvdata = ngt_rcvdata, + .disconnect = ngt_disconnect, +}; +NETGRAPH_INIT(tty, &typestruct); + +/* + * Locking: + * + * - node private data and tp->t_lsc is protected by mutex in struct + * ifqueue, locking is done using IF_XXX() macros. + * - in all tty methods we should acquire node ifqueue mutex, when accessing + * private data. + * - in _rcvdata() we should use locked versions of IF_{EN,DE}QUEUE() since + * we may have multiple _rcvdata() threads. + * - when calling any of tty methods from netgraph methods, we should + * acquire tty locking (now Giant). + * + * - ngt_unit is incremented atomically. + */ + +#define NGTLOCK(sc) IF_LOCK(&sc->outq) +#define NGTUNLOCK(sc) IF_UNLOCK(&sc->outq) + +static int ngt_unit; +static int ngt_ldisc; + +/****************************************************************** + LINE DISCIPLINE METHODS +******************************************************************/ + +/* + * Set our line discipline on the tty. + * Called from device open routine or ttioctl() + */ +static int +ngt_open(struct cdev *dev, struct tty *tp) +{ + struct thread *const td = curthread; /* XXX */ + char name[sizeof(NG_TTY_NODE_TYPE) + 8]; + sc_p sc; + int error; + + /* Super-user only */ + error = priv_check(td, PRIV_NETGRAPH_TTY); + if (error) + return (error); + + /* Initialize private struct */ + MALLOC(sc, sc_p, sizeof(*sc), M_NETGRAPH, M_WAITOK | M_ZERO); + if (sc == NULL) + return (ENOMEM); + + sc->tp = tp; + sc->hotchar = tp->t_hotchar = NG_TTY_DFL_HOTCHAR; + mtx_init(&sc->outq.ifq_mtx, "ng_tty node+queue", NULL, MTX_DEF); + IFQ_SET_MAXLEN(&sc->outq, MAX_MBUFQ); + + NGTLOCK(sc); + + /* Setup netgraph node */ + error = ng_make_node_common(&typestruct, &sc->node); + if (error) { + NGTUNLOCK(sc); + FREE(sc, M_NETGRAPH); + return (error); + } + + atomic_add_int(&ngt_unit, 1); + snprintf(name, sizeof(name), "%s%d", typestruct.name, ngt_unit); + + /* Assign node its name */ + if ((error = ng_name_node(sc->node, name))) { + sc->flags |= FLG_DIE; + NGTUNLOCK(sc); + NG_NODE_UNREF(sc->node); + log(LOG_ERR, "%s: node name exists?\n", name); + return (error); + } + + /* Set back pointers */ + NG_NODE_SET_PRIVATE(sc->node, sc); + tp->t_lsc = sc; + + ng_callout_init(&sc->chand); + + /* + * Pre-allocate cblocks to the an appropriate amount. + * I'm not sure what is appropriate. + */ + ttyflush(tp, FREAD | FWRITE); + clist_alloc_cblocks(&tp->t_canq, 0, 0); + clist_alloc_cblocks(&tp->t_rawq, 0, 0); + clist_alloc_cblocks(&tp->t_outq, + MLEN + NGT_HIWATER, MLEN + NGT_HIWATER); + + NGTUNLOCK(sc); + + return (0); +} + +/* + * Line specific close routine, called from device close routine + * and from ttioctl. This causes the node to be destroyed as well. + */ +static int +ngt_close(struct tty *tp, int flag) +{ + const sc_p sc = (sc_p) tp->t_lsc; + + ttyflush(tp, FREAD | FWRITE); + clist_free_cblocks(&tp->t_outq); + if (sc != NULL) { + NGTLOCK(sc); + if (callout_pending(&sc->chand)) + ng_uncallout(&sc->chand, sc->node); + tp->t_lsc = NULL; + sc->flags |= FLG_DIE; + NGTUNLOCK(sc); + ng_rmnode_self(sc->node); + } + return (0); +} + +/* + * Once the device has been turned into a node, we don't allow reading. + */ +static int +ngt_read(struct tty *tp, struct uio *uio, int flag) +{ + return (EIO); +} + +/* + * Once the device has been turned into a node, we don't allow writing. + */ +static int +ngt_write(struct tty *tp, struct uio *uio, int flag) +{ + return (EIO); +} + +/* + * We implement the NGIOCGINFO ioctl() defined in ng_message.h. + */ +static int +ngt_tioctl(struct tty *tp, u_long cmd, caddr_t data, int flag, struct thread *td) +{ + const sc_p sc = (sc_p) tp->t_lsc; + + if (sc == NULL) + /* No node attached */ + return (0); + + switch (cmd) { + case NGIOCGINFO: + { + struct nodeinfo *const ni = (struct nodeinfo *) data; + const node_p node = sc->node; + + bzero(ni, sizeof(*ni)); + NGTLOCK(sc); + if (NG_NODE_HAS_NAME(node)) + strncpy(ni->name, NG_NODE_NAME(node), sizeof(ni->name) - 1); + strncpy(ni->type, node->nd_type->name, sizeof(ni->type) - 1); + ni->id = (u_int32_t) ng_node2ID(node); + ni->hooks = NG_NODE_NUMHOOKS(node); + NGTUNLOCK(sc); + break; + } + default: + return (ENOIOCTL); + } + + return (0); +} + +/* + * Receive data coming from the device. We get one character at + * a time, which is kindof silly. + * + * Full locking of softc is not required, since we are the only + * user of sc->m. + */ +static int +ngt_input(int c, struct tty *tp) +{ + sc_p sc; + node_p node; + struct mbuf *m; + int error = 0; + + sc = (sc_p) tp->t_lsc; + if (sc == NULL) + /* No node attached */ + return (0); + + node = sc->node; + + if (tp != sc->tp) + panic("ngt_input"); + + /* Check for error conditions */ + if ((tp->t_state & TS_CONNECTED) == 0) { + if (sc->flags & FLG_DEBUG) + log(LOG_DEBUG, "%s: no carrier\n", NG_NODE_NAME(node)); + return (0); + } + if (c & TTY_ERRORMASK) { + /* framing error or overrun on this char */ + if (sc->flags & FLG_DEBUG) + log(LOG_DEBUG, "%s: line error %x\n", + NG_NODE_NAME(node), c & TTY_ERRORMASK); + return (0); + } + c &= TTY_CHARMASK; + + /* Get a new header mbuf if we need one */ + if (!(m = sc->m)) { + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (!m) { + if (sc->flags & FLG_DEBUG) + log(LOG_ERR, + "%s: can't get mbuf\n", NG_NODE_NAME(node)); + return (ENOBUFS); + } + m->m_len = m->m_pkthdr.len = 0; + m->m_pkthdr.rcvif = NULL; + sc->m = m; + } + + /* Add char to mbuf */ + *mtod(m, u_char *) = c; + m->m_data++; + m->m_len++; + m->m_pkthdr.len++; + + /* Ship off mbuf if it's time */ + if (sc->hotchar == -1 || c == sc->hotchar || m->m_len >= MHLEN) { + m->m_data = m->m_pktdat; + sc->m = NULL; + + /* + * We have built our mbuf without checking that we actually + * have a hook to send it. This was done to avoid + * acquiring mutex on each character. Check now. + * + */ + + NGTLOCK(sc); + if (sc->hook == NULL) { + NGTUNLOCK(sc); + m_freem(m); + return (0); /* XXX: original behavior */ + } + NG_SEND_DATA_ONLY(error, sc->hook, m); /* Will queue */ + NGTUNLOCK(sc); + } + + return (error); +} + +/* + * This is called when the device driver is ready for more output. + * Also called from ngt_rcv_data() when a new mbuf is available for output. + */ +static int +ngt_start(struct tty *tp) +{ + const sc_p sc = (sc_p) tp->t_lsc; + + while (tp->t_outq.c_cc < NGT_HIWATER) { /* XXX 2.2 specific ? */ + struct mbuf *m; + + /* Remove first mbuf from queue */ + IF_DEQUEUE(&sc->outq, m); + if (m == NULL) + break; + + /* Send as much of it as possible */ + while (m != NULL) { + int sent; + + sent = m->m_len + - b_to_q(mtod(m, u_char *), m->m_len, &tp->t_outq); + m->m_data += sent; + m->m_len -= sent; + if (m->m_len > 0) + break; /* device can't take no more */ + m = m_free(m); + } + + /* Put remainder of mbuf chain (if any) back on queue */ + if (m != NULL) { + IF_PREPEND(&sc->outq, m); + break; + } + } + + /* Call output process whether or not there is any output. We are + * being called in lieu of ttstart and must do what it would. */ + tt_oproc(tp); + + /* This timeout is needed for operation on a pseudo-tty, because the + * pty code doesn't call pppstart after it has drained the t_outq. */ + /* XXX: outq not locked */ + if (!IFQ_IS_EMPTY(&sc->outq) && !callout_pending(&sc->chand)) + ng_callout(&sc->chand, sc->node, NULL, 1, ngt_timeout, NULL, 0); + + return (0); +} + +/* + * We still have data to output to the device, so try sending more. + */ +static void +ngt_timeout(node_p node, hook_p hook, void *arg1, int arg2) +{ + const sc_p sc = NG_NODE_PRIVATE(node); + + mtx_lock(&Giant); + ngt_start(sc->tp); + mtx_unlock(&Giant); +} + +/****************************************************************** + NETGRAPH NODE METHODS +******************************************************************/ + +/* + * Initialize a new node of this type. + * + * We only allow nodes to be created as a result of setting + * the line discipline on a tty, so always return an error if not. + */ +static int +ngt_constructor(node_p node) +{ + return (EOPNOTSUPP); +} + +/* + * Add a new hook. There can only be one. + */ +static int +ngt_newhook(node_p node, hook_p hook, const char *name) +{ + const sc_p sc = NG_NODE_PRIVATE(node); + + if (strcmp(name, NG_TTY_HOOK)) + return (EINVAL); + + if (sc->hook) + return (EISCONN); + + NGTLOCK(sc); + sc->hook = hook; + NGTUNLOCK(sc); + + return (0); +} + +/* + * Set the hook into queueing mode (for outgoing packets), + * so that we wont deliver mbuf thru the whole graph holding + * tty locks. + */ +static int +ngt_connect(hook_p hook) +{ + NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); + /* + * XXX: While ngt_start() is Giant-locked, queue incoming + * packets, too. Otherwise we acquire Giant holding some + * IP stack locks, e.g. divinp, and this makes WITNESS scream. + */ + NG_HOOK_FORCE_QUEUE(hook); + return (0); +} + +/* + * Disconnect the hook + */ +static int +ngt_disconnect(hook_p hook) +{ + const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + if (hook != sc->hook) + panic(__func__); + + NGTLOCK(sc); + sc->hook = NULL; + NGTUNLOCK(sc); + + return (0); +} + +/* + * Remove this node. The does the netgraph portion of the shutdown. + * This should only be called indirectly from ngt_close(). + * + * tp->t_lsc is already NULL, so we should be protected from + * tty calls now. + */ +static int +ngt_shutdown(node_p node) +{ + const sc_p sc = NG_NODE_PRIVATE(node); + + NGTLOCK(sc); + if (!(sc->flags & FLG_DIE)) { + NGTUNLOCK(sc); + return (EOPNOTSUPP); + } + NGTUNLOCK(sc); + + /* Free resources */ + _IF_DRAIN(&sc->outq); + mtx_destroy(&(sc)->outq.ifq_mtx); + m_freem(sc->m); + NG_NODE_UNREF(sc->node); + FREE(sc, M_NETGRAPH); + + return (0); +} + +/* + * Receive incoming data from netgraph system. Put it on our + * output queue and start output if necessary. + */ +static int +ngt_rcvdata(hook_p hook, item_p item) +{ + const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct mbuf *m; + int qlen; + + if (hook != sc->hook) + panic(__func__); + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + IF_LOCK(&sc->outq); + if (_IF_QFULL(&sc->outq)) { + _IF_DROP(&sc->outq); + IF_UNLOCK(&sc->outq); + NG_FREE_M(m); + return (ENOBUFS); + } + + _IF_ENQUEUE(&sc->outq, m); + qlen = sc->outq.ifq_len; + IF_UNLOCK(&sc->outq); + + /* + * If qlen > 1, then we should already have a scheduled callout. + */ + if (qlen == 1) { + mtx_lock(&Giant); + ngt_start(sc->tp); + mtx_unlock(&Giant); + } + + return (0); +} + +/* + * Receive control message + */ +static int +ngt_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const sc_p sc = NG_NODE_PRIVATE(node); + struct ng_mesg *msg, *resp = NULL; + int error = 0; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_TTY_COOKIE: + switch (msg->header.cmd) { + case NGM_TTY_SET_HOTCHAR: + { + int hotchar; + + if (msg->header.arglen != sizeof(int)) + ERROUT(EINVAL); + hotchar = *((int *) msg->data); + if (hotchar != (u_char) hotchar && hotchar != -1) + ERROUT(EINVAL); + sc->hotchar = hotchar; /* race condition is OK */ + break; + } + case NGM_TTY_GET_HOTCHAR: + NG_MKRESPONSE(resp, msg, sizeof(int), M_NOWAIT); + if (!resp) + ERROUT(ENOMEM); + /* Race condition here is OK */ + *((int *) resp->data) = sc->hotchar; + break; + default: + ERROUT(EINVAL); + } + break; + default: + ERROUT(EINVAL); + } +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/****************************************************************** + INITIALIZATION +******************************************************************/ + +/* + * Handle loading and unloading for this node type + */ +static int +ngt_mod_event(module_t mod, int event, void *data) +{ + int error = 0; + + switch (event) { + case MOD_LOAD: + + /* Register line discipline */ + mtx_lock(&Giant); + if ((ngt_ldisc = ldisc_register(NETGRAPHDISC, &ngt_disc)) < 0) { + mtx_unlock(&Giant); + log(LOG_ERR, "%s: can't register line discipline", + __func__); + return (EIO); + } + mtx_unlock(&Giant); + break; + + case MOD_UNLOAD: + + /* Unregister line discipline */ + mtx_lock(&Giant); + ldisc_deregister(ngt_ldisc); + mtx_unlock(&Giant); + break; + + default: + error = EOPNOTSUPP; + break; + } + return (error); +} diff --git a/sys/netgraph7/ng_tty.h b/sys/netgraph7/ng_tty.h new file mode 100644 index 0000000000..e4e79f8ed2 --- /dev/null +++ b/sys/netgraph7/ng_tty.h @@ -0,0 +1,64 @@ + +/* + * ng_tty.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_tty.h,v 1.4 2005/01/07 01:45:39 imp Exp $ + * $Whistle: ng_tty.h,v 1.7 1999/01/20 00:22:15 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_TTY_H_ +#define _NETGRAPH_NG_TTY_H_ + +/* Node type name and magic cookie */ +#define NG_TTY_NODE_TYPE "tty" +#define NGM_TTY_COOKIE 886279262 + +/* Default hot char */ +#define NG_TTY_DFL_HOTCHAR 0x7e /* PPP flag byte */ + +/* Hook names */ +#define NG_TTY_HOOK "hook" + +/* Netgraph commands */ +enum { + NGM_TTY_GET_HOTCHAR = 1, + NGM_TTY_SET_HOTCHAR, +}; + +#endif /* _NETGRAPH_NG_TTY_H_ */ diff --git a/sys/netgraph7/ng_vjc.c b/sys/netgraph7/ng_vjc.c new file mode 100644 index 0000000000..bca3ca399a --- /dev/null +++ b/sys/netgraph7/ng_vjc.c @@ -0,0 +1,614 @@ + +/* + * ng_vjc.c + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_vjc.c,v 1.26 2005/12/04 00:25:03 ru Exp $ + * $Whistle: ng_vjc.c,v 1.17 1999/11/01 09:24:52 julian Exp $ + */ + +/* + * This node performs Van Jacobson IP header (de)compression. + * You must have included net/slcompress.c in your kernel compilation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +/* Check agreement with slcompress.c */ +#if MAX_STATES != NG_VJC_MAX_CHANNELS +#error NG_VJC_MAX_CHANNELS must be the same as MAX_STATES +#endif + +/* Maximum length of a compressed TCP VJ header */ +#define MAX_VJHEADER 19 + +/* Node private data */ +struct ng_vjc_private { + struct ngm_vjc_config conf; + struct slcompress slc; + hook_p ip; + hook_p vjcomp; + hook_p vjuncomp; + hook_p vjip; +}; +typedef struct ng_vjc_private *priv_p; + +#define ERROUT(x) do { error = (x); goto done; } while (0) + +/* Netgraph node methods */ +static ng_constructor_t ng_vjc_constructor; +static ng_rcvmsg_t ng_vjc_rcvmsg; +static ng_shutdown_t ng_vjc_shutdown; +static ng_newhook_t ng_vjc_newhook; +static ng_rcvdata_t ng_vjc_rcvdata; +static ng_disconnect_t ng_vjc_disconnect; + +/* Helper stuff */ +static struct mbuf *ng_vjc_pulluphdrs(struct mbuf *m, int knownTCP); + +/* Parse type for struct ngm_vjc_config */ +static const struct ng_parse_struct_field ng_vjc_config_type_fields[] + = NG_VJC_CONFIG_TYPE_INFO; +static const struct ng_parse_type ng_vjc_config_type = { + &ng_parse_struct_type, + &ng_vjc_config_type_fields +}; + +/* Parse type for the 'last_cs' and 'cs_next' fields in struct slcompress, + which are pointers converted to integer indices, so parse them that way. */ +#ifndef __LP64__ +#define NG_VJC_TSTATE_PTR_TYPE &ng_parse_uint32_type +#else +#define NG_VJC_TSTATE_PTR_TYPE &ng_parse_uint64_type +#endif + +/* Parse type for the 'cs_hdr' field in a struct cstate. Ideally we would + like to use a 'struct ip' type instead of a simple array of bytes. */ +static const struct ng_parse_fixedarray_info ng_vjc_cs_hdr_type_info = { + &ng_parse_hint8_type, + MAX_HDR +}; +static const struct ng_parse_type ng_vjc_cs_hdr_type = { + &ng_parse_fixedarray_type, + &ng_vjc_cs_hdr_type_info +}; + +/* Parse type for a struct cstate */ +static const struct ng_parse_struct_field ng_vjc_cstate_type_fields[] = { + { "cs_next", NG_VJC_TSTATE_PTR_TYPE }, + { "cs_hlen", &ng_parse_uint16_type }, + { "cs_id", &ng_parse_uint8_type }, + { "cs_filler", &ng_parse_uint8_type }, + { "cs_hdr", &ng_vjc_cs_hdr_type }, + { NULL } +}; +static const struct ng_parse_type ng_vjc_cstate_type = { + &ng_parse_struct_type, + &ng_vjc_cstate_type_fields +}; + +/* Parse type for an array of MAX_STATES struct cstate's, ie, tstate & rstate */ +static const struct ng_parse_fixedarray_info ng_vjc_cstatearray_type_info = { + &ng_vjc_cstate_type, + MAX_STATES +}; +static const struct ng_parse_type ng_vjc_cstatearray_type = { + &ng_parse_fixedarray_type, + &ng_vjc_cstatearray_type_info +}; + +/* Parse type for struct slcompress. Keep this in sync with the + definition of struct slcompress defined in */ +static const struct ng_parse_struct_field ng_vjc_slcompress_type_fields[] = { + { "last_cs", NG_VJC_TSTATE_PTR_TYPE }, + { "last_recv", &ng_parse_uint8_type }, + { "last_xmit", &ng_parse_uint8_type }, + { "flags", &ng_parse_hint16_type }, +#ifndef SL_NO_STATS + { "sls_packets", &ng_parse_uint32_type }, + { "sls_compressed", &ng_parse_uint32_type }, + { "sls_searches", &ng_parse_uint32_type }, + { "sls_misses", &ng_parse_uint32_type }, + { "sls_uncompressedin", &ng_parse_uint32_type }, + { "sls_compressedin", &ng_parse_uint32_type }, + { "sls_errorin", &ng_parse_uint32_type }, + { "sls_tossed", &ng_parse_uint32_type }, +#endif + { "tstate", &ng_vjc_cstatearray_type }, + { "rstate", &ng_vjc_cstatearray_type }, + { NULL } +}; +static const struct ng_parse_type ng_vjc_slcompress_type = { + &ng_parse_struct_type, + &ng_vjc_slcompress_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_vjc_cmds[] = { + { + NGM_VJC_COOKIE, + NGM_VJC_SET_CONFIG, + "setconfig", + &ng_vjc_config_type, + NULL + }, + { + NGM_VJC_COOKIE, + NGM_VJC_GET_CONFIG, + "getconfig", + NULL, + &ng_vjc_config_type, + }, + { + NGM_VJC_COOKIE, + NGM_VJC_GET_STATE, + "getstate", + NULL, + &ng_vjc_slcompress_type, + }, + { + NGM_VJC_COOKIE, + NGM_VJC_CLR_STATS, + "clrstats", + NULL, + NULL, + }, + { + NGM_VJC_COOKIE, + NGM_VJC_RECV_ERROR, + "recverror", + NULL, + NULL, + }, + { 0 } +}; + +/* Node type descriptor */ +static struct ng_type ng_vjc_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_VJC_NODE_TYPE, + .constructor = ng_vjc_constructor, + .rcvmsg = ng_vjc_rcvmsg, + .shutdown = ng_vjc_shutdown, + .newhook = ng_vjc_newhook, + .rcvdata = ng_vjc_rcvdata, + .disconnect = ng_vjc_disconnect, + .cmdlist = ng_vjc_cmds, +}; +NETGRAPH_INIT(vjc, &ng_vjc_typestruct); + +/************************************************************************ + NETGRAPH NODE METHODS + ************************************************************************/ + +/* + * Create a new node + */ +static int +ng_vjc_constructor(node_p node) +{ + priv_p priv; + + /* Allocate private structure */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + + NG_NODE_SET_PRIVATE(node, priv); + + /* Done */ + return (0); +} + +/* + * Add a new hook + */ +static int +ng_vjc_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + hook_p *hookp; + + /* Get hook */ + if (strcmp(name, NG_VJC_HOOK_IP) == 0) + hookp = &priv->ip; + else if (strcmp(name, NG_VJC_HOOK_VJCOMP) == 0) + hookp = &priv->vjcomp; + else if (strcmp(name, NG_VJC_HOOK_VJUNCOMP) == 0) + hookp = &priv->vjuncomp; + else if (strcmp(name, NG_VJC_HOOK_VJIP) == 0) + hookp = &priv->vjip; + else + return (EINVAL); + + /* See if already connected */ + if (*hookp) + return (EISCONN); + + /* OK */ + *hookp = hook; + return (0); +} + +/* + * Receive a control message + */ +static int +ng_vjc_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + /* Check type cookie */ + switch (msg->header.typecookie) { + case NGM_VJC_COOKIE: + switch (msg->header.cmd) { + case NGM_VJC_SET_CONFIG: + { + struct ngm_vjc_config *const c = + (struct ngm_vjc_config *) msg->data; + + if (msg->header.arglen != sizeof(*c)) + ERROUT(EINVAL); + if ((priv->conf.enableComp || priv->conf.enableDecomp) + && (c->enableComp || c->enableDecomp)) + ERROUT(EALREADY); + if (c->enableComp) { + if (c->maxChannel > NG_VJC_MAX_CHANNELS - 1 + || c->maxChannel < NG_VJC_MIN_CHANNELS - 1) + ERROUT(EINVAL); + } else + c->maxChannel = NG_VJC_MAX_CHANNELS - 1; + if (c->enableComp != 0 || c->enableDecomp != 0) { + bzero(&priv->slc, sizeof(priv->slc)); + sl_compress_init(&priv->slc, c->maxChannel); + } + priv->conf = *c; + break; + } + case NGM_VJC_GET_CONFIG: + { + struct ngm_vjc_config *conf; + + NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + conf = (struct ngm_vjc_config *)resp->data; + *conf = priv->conf; + break; + } + case NGM_VJC_GET_STATE: + { + const struct slcompress *const sl0 = &priv->slc; + struct slcompress *sl; + u_int16_t index; + int i; + + /* Get response structure */ + NG_MKRESPONSE(resp, msg, sizeof(*sl), M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + sl = (struct slcompress *)resp->data; + *sl = *sl0; + + /* Replace pointers with integer indicies */ + if (sl->last_cs != NULL) { + index = sl0->last_cs - sl0->tstate; + bzero(&sl->last_cs, sizeof(sl->last_cs)); + *((u_int16_t *)&sl->last_cs) = index; + } + for (i = 0; i < MAX_STATES; i++) { + struct cstate *const cs = &sl->tstate[i]; + + index = sl0->tstate[i].cs_next - sl0->tstate; + bzero(&cs->cs_next, sizeof(cs->cs_next)); + *((u_int16_t *)&cs->cs_next) = index; + } + break; + } + case NGM_VJC_CLR_STATS: + priv->slc.sls_packets = 0; + priv->slc.sls_compressed = 0; + priv->slc.sls_searches = 0; + priv->slc.sls_misses = 0; + priv->slc.sls_uncompressedin = 0; + priv->slc.sls_compressedin = 0; + priv->slc.sls_errorin = 0; + priv->slc.sls_tossed = 0; + break; + case NGM_VJC_RECV_ERROR: + sl_uncompress_tcp(NULL, 0, TYPE_ERROR, &priv->slc); + break; + default: + error = EINVAL; + break; + } + break; + default: + error = EINVAL; + break; + } +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Receive data + */ +static int +ng_vjc_rcvdata(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + int error = 0; + struct mbuf *m; + + NGI_GET_M(item, m); + if (hook == priv->ip) { /* outgoing packet */ + u_int type = TYPE_IP; + + /* Compress packet if enabled and proto is TCP */ + if (priv->conf.enableComp) { + struct ip *ip; + + if ((m = ng_vjc_pulluphdrs(m, 0)) == NULL) { + NG_FREE_ITEM(item); + return (ENOBUFS); + } + ip = mtod(m, struct ip *); + if (ip->ip_p == IPPROTO_TCP) { + const int origLen = m->m_len; + + type = sl_compress_tcp(m, ip, + &priv->slc, priv->conf.compressCID); + m->m_pkthdr.len += m->m_len - origLen; + } + } + + /* Dispatch to the appropriate outgoing hook */ + switch (type) { + case TYPE_IP: + hook = priv->vjip; + break; + case TYPE_UNCOMPRESSED_TCP: + hook = priv->vjuncomp; + break; + case TYPE_COMPRESSED_TCP: + hook = priv->vjcomp; + break; + default: + panic("%s: type=%d", __func__, type); + } + } else if (hook == priv->vjcomp) { /* incoming compressed packet */ + int vjlen, need2pullup; + struct mbuf *hm; + u_char *hdr; + u_int hlen; + + /* Are we decompressing? */ + if (!priv->conf.enableDecomp) { + NG_FREE_M(m); + NG_FREE_ITEM(item); + return (ENXIO); + } + + /* Pull up the necessary amount from the mbuf */ + need2pullup = MAX_VJHEADER; + if (need2pullup > m->m_pkthdr.len) + need2pullup = m->m_pkthdr.len; + if (m->m_len < need2pullup + && (m = m_pullup(m, need2pullup)) == NULL) { + priv->slc.sls_errorin++; + NG_FREE_ITEM(item); + return (ENOBUFS); + } + + /* Uncompress packet to reconstruct TCP/IP header */ + vjlen = sl_uncompress_tcp_core(mtod(m, u_char *), + m->m_len, m->m_pkthdr.len, TYPE_COMPRESSED_TCP, + &priv->slc, &hdr, &hlen); + if (vjlen <= 0) { + NG_FREE_M(m); + NG_FREE_ITEM(item); + return (EINVAL); + } + m_adj(m, vjlen); + + /* Copy the reconstructed TCP/IP headers into a new mbuf */ + MGETHDR(hm, M_DONTWAIT, MT_DATA); + if (hm == NULL) { + priv->slc.sls_errorin++; + NG_FREE_M(m); + NG_FREE_ITEM(item); + return (ENOBUFS); + } + hm->m_len = 0; + hm->m_pkthdr.rcvif = NULL; + if (hlen > MHLEN) { /* unlikely, but can happen */ + MCLGET(hm, M_DONTWAIT); + if ((hm->m_flags & M_EXT) == 0) { + m_freem(hm); + priv->slc.sls_errorin++; + NG_FREE_M(m); + NG_FREE_ITEM(item); + return (ENOBUFS); + } + } + bcopy(hdr, mtod(hm, u_char *), hlen); + hm->m_len = hlen; + + /* Glue TCP/IP headers and rest of packet together */ + hm->m_next = m; + hm->m_pkthdr.len = hlen + m->m_pkthdr.len; + m = hm; + hook = priv->ip; + } else if (hook == priv->vjuncomp) { /* incoming uncompressed pkt */ + u_char *hdr; + u_int hlen; + + /* Are we decompressing? */ + if (!priv->conf.enableDecomp) { + NG_FREE_M(m); + NG_FREE_ITEM(item); + return (ENXIO); + } + + /* Pull up IP+TCP headers */ + if ((m = ng_vjc_pulluphdrs(m, 1)) == NULL) { + NG_FREE_ITEM(item); + return (ENOBUFS); + } + + /* Run packet through uncompressor */ + if (sl_uncompress_tcp_core(mtod(m, u_char *), + m->m_len, m->m_pkthdr.len, TYPE_UNCOMPRESSED_TCP, + &priv->slc, &hdr, &hlen) < 0) { + NG_FREE_M(m); + NG_FREE_ITEM(item); + return (EINVAL); + } + hook = priv->ip; + } else if (hook == priv->vjip) /* incoming regular packet (bypass) */ + hook = priv->ip; + else + panic("%s: unknown hook", __func__); + + /* Send result back out */ + NG_FWD_NEW_DATA(error, item, hook, m); + return (error); +} + +/* + * Shutdown node + */ +static int +ng_vjc_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + bzero(priv, sizeof(*priv)); + FREE(priv, M_NETGRAPH); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + return (0); +} + +/* + * Hook disconnection + */ +static int +ng_vjc_disconnect(hook_p hook) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + + /* Zero out hook pointer */ + if (hook == priv->ip) + priv->ip = NULL; + else if (hook == priv->vjcomp) + priv->vjcomp = NULL; + else if (hook == priv->vjuncomp) + priv->vjuncomp = NULL; + else if (hook == priv->vjip) + priv->vjip = NULL; + else + panic("%s: unknown hook", __func__); + + /* Go away if no hooks left */ + if ((NG_NODE_NUMHOOKS(node) == 0) + && (NG_NODE_IS_VALID(node))) + ng_rmnode_self(node); + return (0); +} + +/************************************************************************ + HELPER STUFF + ************************************************************************/ + +/* + * Pull up the full IP and TCP headers of a packet. If packet is not + * a TCP packet, just pull up the IP header. + */ +static struct mbuf * +ng_vjc_pulluphdrs(struct mbuf *m, int knownTCP) +{ + struct ip *ip; + struct tcphdr *tcp; + int ihlen, thlen; + + if (m->m_len < sizeof(*ip) && (m = m_pullup(m, sizeof(*ip))) == NULL) + return (NULL); + ip = mtod(m, struct ip *); + if (!knownTCP && ip->ip_p != IPPROTO_TCP) + return (m); + ihlen = ip->ip_hl << 2; + if (m->m_len < ihlen + sizeof(*tcp)) { + if ((m = m_pullup(m, ihlen + sizeof(*tcp))) == NULL) + return (NULL); + ip = mtod(m, struct ip *); + } + tcp = (struct tcphdr *)((u_char *)ip + ihlen); + thlen = tcp->th_off << 2; + if (m->m_len < ihlen + thlen) + m = m_pullup(m, ihlen + thlen); + return (m); +} + diff --git a/sys/netgraph7/ng_vjc.h b/sys/netgraph7/ng_vjc.h new file mode 100644 index 0000000000..26da5a0c20 --- /dev/null +++ b/sys/netgraph7/ng_vjc.h @@ -0,0 +1,88 @@ + +/* + * ng_vjc.h + */ + +/*- + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Archie Cobbs + * + * $FreeBSD: src/sys/netgraph/ng_vjc.h,v 1.9 2005/01/07 01:45:39 imp Exp $ + * $Whistle: ng_vjc.h,v 1.6 1999/01/25 02:40:22 archie Exp $ + */ + +#ifndef _NETGRAPH_NG_VJC_H_ +#define _NETGRAPH_NG_VJC_H_ + + /* Node type name and magic cookie */ +#define NG_VJC_NODE_TYPE "vjc" +#define NGM_VJC_COOKIE 868219210 + + /* Hook names */ +#define NG_VJC_HOOK_IP "ip" /* normal IP traffic */ +#define NG_VJC_HOOK_VJCOMP "vjcomp" /* compressed TCP */ +#define NG_VJC_HOOK_VJUNCOMP "vjuncomp" /* uncompressed TCP */ +#define NG_VJC_HOOK_VJIP "vjip" /* uncompressed IP */ + + /* Minimum and maximum number of compression channels */ +#define NG_VJC_MIN_CHANNELS 4 +#define NG_VJC_MAX_CHANNELS 16 + + /* Configure struct */ +struct ngm_vjc_config { + u_char enableComp; /* Enable compression */ + u_char enableDecomp; /* Enable decompression */ + u_char maxChannel; /* Number of compression channels - 1 */ + u_char compressCID; /* OK to compress outgoing CID's */ +}; + +/* Keep this in sync with the above structure definition */ +#define NG_VJC_CONFIG_TYPE_INFO { \ + { "enableComp", &ng_parse_uint8_type }, \ + { "enableDecomp", &ng_parse_uint8_type }, \ + { "maxChannel", &ng_parse_uint8_type }, \ + { "compressCID", &ng_parse_uint8_type }, \ + { NULL } \ +} + + /* Netgraph commands */ +enum { + NGM_VJC_SET_CONFIG, /* Supply a struct ngm_vjc_config */ + NGM_VJC_GET_CONFIG, /* Returns a struct ngm_vjc_config */ + NGM_VJC_GET_STATE, /* Returns current struct slcompress */ + NGM_VJC_CLR_STATS, /* Clears statistics counters */ + NGM_VJC_RECV_ERROR, /* Indicate loss of incoming frame */ +}; + +#endif /* _NETGRAPH_NG_VJC_H_ */ diff --git a/sys/netgraph7/ng_vlan.c b/sys/netgraph7/ng_vlan.c new file mode 100644 index 0000000000..4d44c6fc87 --- /dev/null +++ b/sys/netgraph7/ng_vlan.c @@ -0,0 +1,468 @@ +/*- + * Copyright (c) 2003 IPNET Internet Communication Company + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Author: Ruslan Ermilov + * + * $FreeBSD: src/sys/netgraph/ng_vlan.c,v 1.5 2007/06/11 15:29:02 imp Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +static ng_constructor_t ng_vlan_constructor; +static ng_rcvmsg_t ng_vlan_rcvmsg; +static ng_shutdown_t ng_vlan_shutdown; +static ng_newhook_t ng_vlan_newhook; +static ng_rcvdata_t ng_vlan_rcvdata; +static ng_disconnect_t ng_vlan_disconnect; + +/* Parse type for struct ng_vlan_filter. */ +static const struct ng_parse_struct_field ng_vlan_filter_fields[] = + NG_VLAN_FILTER_FIELDS; +static const struct ng_parse_type ng_vlan_filter_type = { + &ng_parse_struct_type, + &ng_vlan_filter_fields +}; + +static int +ng_vlan_getTableLength(const struct ng_parse_type *type, + const u_char *start, const u_char *buf) +{ + const struct ng_vlan_table *const table = + (const struct ng_vlan_table *)(buf - sizeof(u_int32_t)); + + return table->n; +} + +/* Parse type for struct ng_vlan_table. */ +static const struct ng_parse_array_info ng_vlan_table_array_info = { + &ng_vlan_filter_type, + ng_vlan_getTableLength +}; +static const struct ng_parse_type ng_vlan_table_array_type = { + &ng_parse_array_type, + &ng_vlan_table_array_info +}; +static const struct ng_parse_struct_field ng_vlan_table_fields[] = + NG_VLAN_TABLE_FIELDS; +static const struct ng_parse_type ng_vlan_table_type = { + &ng_parse_struct_type, + &ng_vlan_table_fields +}; + +/* List of commands and how to convert arguments to/from ASCII. */ +static const struct ng_cmdlist ng_vlan_cmdlist[] = { + { + NGM_VLAN_COOKIE, + NGM_VLAN_ADD_FILTER, + "addfilter", + &ng_vlan_filter_type, + NULL + }, + { + NGM_VLAN_COOKIE, + NGM_VLAN_DEL_FILTER, + "delfilter", + &ng_parse_hookbuf_type, + NULL + }, + { + NGM_VLAN_COOKIE, + NGM_VLAN_GET_TABLE, + "gettable", + NULL, + &ng_vlan_table_type + }, + { 0 } +}; + +static struct ng_type ng_vlan_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_VLAN_NODE_TYPE, + .constructor = ng_vlan_constructor, + .rcvmsg = ng_vlan_rcvmsg, + .shutdown = ng_vlan_shutdown, + .newhook = ng_vlan_newhook, + .rcvdata = ng_vlan_rcvdata, + .disconnect = ng_vlan_disconnect, + .cmdlist = ng_vlan_cmdlist, +}; +NETGRAPH_INIT(vlan, &ng_vlan_typestruct); + +struct filter { + LIST_ENTRY(filter) next; + u_int16_t vlan; + hook_p hook; +}; + +#define HASHSIZE 16 +#define HASH(id) ((((id) >> 8) ^ ((id) >> 4) ^ (id)) & 0x0f) +LIST_HEAD(filterhead, filter); + +typedef struct { + hook_p downstream_hook; + hook_p nomatch_hook; + struct filterhead hashtable[HASHSIZE]; + u_int32_t nent; +} *priv_p; + +static struct filter * +ng_vlan_findentry(priv_p priv, u_int16_t vlan) +{ + struct filterhead *chain = &priv->hashtable[HASH(vlan)]; + struct filter *f; + + LIST_FOREACH(f, chain, next) + if (f->vlan == vlan) + return (f); + return (NULL); +} + +static int +ng_vlan_constructor(node_p node) +{ + priv_p priv; + int i; + + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); + if (priv == NULL) + return (ENOMEM); + for (i = 0; i < HASHSIZE; i++) + LIST_INIT(&priv->hashtable[i]); + NG_NODE_SET_PRIVATE(node, priv); + return (0); +} + +static int +ng_vlan_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (strcmp(name, NG_VLAN_HOOK_DOWNSTREAM) == 0) + priv->downstream_hook = hook; + else if (strcmp(name, NG_VLAN_HOOK_NOMATCH) == 0) + priv->nomatch_hook = hook; + else { + /* + * Any other hook name is valid and can + * later be associated with a filter rule. + */ + } + NG_HOOK_SET_PRIVATE(hook, NULL); + return (0); +} + +static int +ng_vlan_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + int error = 0; + struct ng_mesg *msg, *resp = NULL; + struct ng_vlan_filter *vf; + struct filter *f; + hook_p hook; + struct ng_vlan_table *t; + int i; + + NGI_GET_MSG(item, msg); + /* Deal with message according to cookie and command. */ + switch (msg->header.typecookie) { + case NGM_VLAN_COOKIE: + switch (msg->header.cmd) { + case NGM_VLAN_ADD_FILTER: + /* Check that message is long enough. */ + if (msg->header.arglen != sizeof(*vf)) { + error = EINVAL; + break; + } + vf = (struct ng_vlan_filter *)msg->data; + /* Sanity check the VLAN ID value. */ + if (vf->vlan & ~EVL_VLID_MASK) { + error = EINVAL; + break; + } + /* Check that a referenced hook exists. */ + hook = ng_findhook(node, vf->hook); + if (hook == NULL) { + error = ENOENT; + break; + } + /* And is not one of the special hooks. */ + if (hook == priv->downstream_hook || + hook == priv->nomatch_hook) { + error = EINVAL; + break; + } + /* And is not already in service. */ + if (NG_HOOK_PRIVATE(hook) != NULL) { + error = EEXIST; + break; + } + /* Check we don't already trap this VLAN. */ + if (ng_vlan_findentry(priv, vf->vlan)) { + error = EEXIST; + break; + } + /* Create filter. */ + MALLOC(f, struct filter *, sizeof(*f), + M_NETGRAPH, M_NOWAIT | M_ZERO); + if (f == NULL) { + error = ENOMEM; + break; + } + /* Link filter and hook together. */ + f->hook = hook; + f->vlan = vf->vlan; + NG_HOOK_SET_PRIVATE(hook, f); + /* Register filter in a hash table. */ + LIST_INSERT_HEAD( + &priv->hashtable[HASH(f->vlan)], f, next); + priv->nent++; + break; + case NGM_VLAN_DEL_FILTER: + /* Check that message is long enough. */ + if (msg->header.arglen != NG_HOOKSIZ) { + error = EINVAL; + break; + } + /* Check that hook exists and is active. */ + hook = ng_findhook(node, (char *)msg->data); + if (hook == NULL || + (f = NG_HOOK_PRIVATE(hook)) == NULL) { + error = ENOENT; + break; + } + /* Purge a rule that refers to this hook. */ + NG_HOOK_SET_PRIVATE(hook, NULL); + LIST_REMOVE(f, next); + priv->nent--; + FREE(f, M_NETGRAPH); + break; + case NGM_VLAN_GET_TABLE: + NG_MKRESPONSE(resp, msg, sizeof(*t) + + priv->nent * sizeof(*t->filter), M_NOWAIT); + if (resp == NULL) { + error = ENOMEM; + break; + } + t = (struct ng_vlan_table *)resp->data; + t->n = priv->nent; + vf = &t->filter[0]; + for (i = 0; i < HASHSIZE; i++) { + LIST_FOREACH(f, &priv->hashtable[i], next) { + vf->vlan = f->vlan; + strncpy(vf->hook, NG_HOOK_NAME(f->hook), + NG_HOOKSIZ); + vf++; + } + } + break; + default: /* Unknown command. */ + error = EINVAL; + break; + } + break; + case NGM_FLOW_COOKIE: + { + struct ng_mesg *copy; + struct filterhead *chain; + struct filter *f; + + /* + * Flow control messages should come only + * from downstream. + */ + + if (lasthook == NULL) + break; + if (lasthook != priv->downstream_hook) + break; + + /* Broadcast the event to all uplinks. */ + for (i = 0, chain = priv->hashtable; i < HASHSIZE; + i++, chain++) + LIST_FOREACH(f, chain, next) { + NG_COPYMESSAGE(copy, msg, M_NOWAIT); + if (copy == NULL) + continue; + NG_SEND_MSG_HOOK(error, node, copy, f->hook, 0); + } + + break; + } + default: /* Unknown type cookie. */ + error = EINVAL; + break; + } + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +static int +ng_vlan_rcvdata(hook_p hook, item_p item) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct ether_header *eh; + struct ether_vlan_header *evl = NULL; + int error; + u_int16_t vlan; + struct mbuf *m; + struct filter *f; + + /* Make sure we have an entire header. */ + NGI_GET_M(item, m); + if (m->m_len < sizeof(*eh) && + (m = m_pullup(m, sizeof(*eh))) == NULL) { + NG_FREE_ITEM(item); + return (EINVAL); + } + eh = mtod(m, struct ether_header *); + if (hook == priv->downstream_hook) { + /* + * If from downstream, select between a match hook + * or the nomatch hook. + */ + if (m->m_flags & M_VLANTAG || + eh->ether_type == htons(ETHERTYPE_VLAN)) { + if (m->m_flags & M_VLANTAG) { + /* + * Packet is tagged, m contains a normal + * Ethernet frame; tag is stored out-of-band. + */ + vlan = EVL_VLANOFTAG(m->m_pkthdr.ether_vtag); + } else { + if (m->m_len < sizeof(*evl) && + (m = m_pullup(m, sizeof(*evl))) == NULL) { + NG_FREE_ITEM(item); + return (EINVAL); + } + evl = mtod(m, struct ether_vlan_header *); + vlan = EVL_VLANOFTAG(ntohs(evl->evl_tag)); + } + if ((f = ng_vlan_findentry(priv, vlan)) != NULL) { + if (m->m_flags & M_VLANTAG) { + m->m_pkthdr.ether_vtag = 0; + m->m_flags &= ~M_VLANTAG; + } else { + evl->evl_encap_proto = evl->evl_proto; + bcopy(mtod(m, caddr_t), + mtod(m, caddr_t) + + ETHER_VLAN_ENCAP_LEN, + ETHER_HDR_LEN); + m_adj(m, ETHER_VLAN_ENCAP_LEN); + } + } + } else + f = NULL; + if (f != NULL) + NG_FWD_NEW_DATA(error, item, f->hook, m); + else + NG_FWD_NEW_DATA(error, item, priv->nomatch_hook, m); + } else { + /* + * It is heading towards the downstream. + * If from nomatch, pass it unmodified. + * Otherwise, do the VLAN encapsulation. + */ + if (hook != priv->nomatch_hook) { + if ((f = NG_HOOK_PRIVATE(hook)) == NULL) { + NG_FREE_ITEM(item); + NG_FREE_M(m); + return (EOPNOTSUPP); + } + M_PREPEND(m, ETHER_VLAN_ENCAP_LEN, M_DONTWAIT); + /* M_PREPEND takes care of m_len and m_pkthdr.len. */ + if (m == NULL || (m->m_len < sizeof(*evl) && + (m = m_pullup(m, sizeof(*evl))) == NULL)) { + NG_FREE_ITEM(item); + return (ENOMEM); + } + /* + * Transform the Ethernet header into an Ethernet header + * with 802.1Q encapsulation. + */ + bcopy(mtod(m, char *) + ETHER_VLAN_ENCAP_LEN, + mtod(m, char *), ETHER_HDR_LEN); + evl = mtod(m, struct ether_vlan_header *); + evl->evl_proto = evl->evl_encap_proto; + evl->evl_encap_proto = htons(ETHERTYPE_VLAN); + evl->evl_tag = htons(f->vlan); + } + NG_FWD_NEW_DATA(error, item, priv->downstream_hook, m); + } + return (error); +} + +static int +ng_vlan_shutdown(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + FREE(priv, M_NETGRAPH); + return (0); +} + +static int +ng_vlan_disconnect(hook_p hook) +{ + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct filter *f; + + if (hook == priv->downstream_hook) + priv->downstream_hook = NULL; + else if (hook == priv->nomatch_hook) + priv->nomatch_hook = NULL; + else { + /* Purge a rule that refers to this hook. */ + if ((f = NG_HOOK_PRIVATE(hook)) != NULL) { + LIST_REMOVE(f, next); + priv->nent--; + FREE(f, M_NETGRAPH); + } + } + NG_HOOK_SET_PRIVATE(hook, NULL); + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && + (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) + ng_rmnode_self(NG_HOOK_NODE(hook)); + return (0); +} diff --git a/sys/netgraph7/ng_vlan.h b/sys/netgraph7/ng_vlan.h new file mode 100644 index 0000000000..b74920c1e3 --- /dev/null +++ b/sys/netgraph7/ng_vlan.h @@ -0,0 +1,75 @@ +/*- + * Copyright (c) 2003 IPNET Internet Communication Company + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Author: Ruslan Ermilov + * + * $FreeBSD: src/sys/netgraph/ng_vlan.h,v 1.2 2005/10/28 14:41:28 ru Exp $ + */ + +#ifndef _NETGRAPH_NG_VLAN_H_ +#define _NETGRAPH_NG_VLAN_H_ + +/* Node type name and magic cookie. */ +#define NG_VLAN_NODE_TYPE "vlan" +#define NGM_VLAN_COOKIE 1068486472 + +/* Hook names. */ +#define NG_VLAN_HOOK_DOWNSTREAM "downstream" +#define NG_VLAN_HOOK_NOMATCH "nomatch" + +/* Netgraph commands. */ +enum { + NGM_VLAN_ADD_FILTER = 1, + NGM_VLAN_DEL_FILTER, + NGM_VLAN_GET_TABLE +}; + +/* For NGM_VLAN_ADD_FILTER control message. */ +struct ng_vlan_filter { + char hook[NG_HOOKSIZ]; + u_int16_t vlan; +}; + +/* Keep this in sync with the above structure definition. */ +#define NG_VLAN_FILTER_FIELDS { \ + { "hook", &ng_parse_hookbuf_type }, \ + { "vlan", &ng_parse_uint16_type }, \ + { NULL } \ +} + +/* Structure returned by NGM_VLAN_GET_TABLE. */ +struct ng_vlan_table { + u_int32_t n; + struct ng_vlan_filter filter[]; +}; + +/* Keep this in sync with the above structure definition. */ +#define NG_VLAN_TABLE_FIELDS { \ + { "n", &ng_parse_uint32_type }, \ + { "filter", &ng_vlan_table_array_type }, \ + { NULL } \ +} + +#endif /* _NETGRAPH_NG_VLAN_H_ */ -- 2.41.0